Commit c7ff1304 authored by Olaf Schmidt-Wischhöfer's avatar Olaf Schmidt-Wischhöfer Committed by Benni Mack
Browse files

[BUGFIX] Use a HTML button for the collapsable IRRE header

Also add aria-expanded and aria-controls arguments for screen readers

Resolves: #92682
Releases: master, 10.4
Change-Id: I48d0edcbea4d185d11216048d7847ad3574d704d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/66271

Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Michael Telgkamp's avatarMichael Telgkamp <michael.telgkamp@mindscreen.de>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Michael Telgkamp's avatarMichael Telgkamp <michael.telgkamp@mindscreen.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 96cb0313
......@@ -399,23 +399,36 @@ select {
// Form Irre
//
.form-irre-header {
display: table;
display: flex;
align-items: center;
margin: -5px;
@include user-select(none);
}
.form-irre-header-cell {
display: table-cell;
vertical-align: middle;
white-space: nowrap;
padding: 5px;
}
.form-irre-header-button {
display: flex;
text-align: left;
align-items: center;
align-self: stretch;
background: transparent;
border: 0;
width: 100%;
margin-left: -15px;
padding-left: 20px;
}
.form-irre-header-body {
width: 100%;
font-weight: normal;
white-space: normal;
padding-left: 5px;
dl {
@extend .dl-horizontal;
......@@ -428,6 +441,10 @@ select {
padding-right: 0;
}
.form-irre-header-thumbnail {
padding-right: 5px;
}
.form-irre-header-control {
cursor: auto;
......
......@@ -107,20 +107,48 @@ class InlineControlContainer {
return <HTMLDivElement>document.querySelector('[data-object-id="' + objectId + '"]');
}
/**
* @param {string} objectId
* @return HTMLButtonElement
*/
private static getCollapseButton(objectId: string): HTMLButtonElement {
return <HTMLButtonElement>document.querySelector('[aria-controls="' + objectId + '_fields"]');
}
/**
* @param {string} objectId
*/
private static toggleElement(objectId: string): void {
const recordContainer = InlineControlContainer.getInlineRecordContainer(objectId);
if (recordContainer.classList.contains(States.collapsed)) {
recordContainer.classList.remove(States.collapsed);
recordContainer.classList.add(States.visible);
InlineControlContainer.expandElement(recordContainer, objectId);
} else {
recordContainer.classList.remove(States.visible);
recordContainer.classList.add(States.collapsed);
InlineControlContainer.collapseElement(recordContainer, objectId);
}
}
/**
* @param {HTMLDivElement} recordContainer
* @param {string} objectId
*/
private static collapseElement(recordContainer: HTMLDivElement, objectId: string): void {
const collapseButton = InlineControlContainer.getCollapseButton(objectId);
recordContainer.classList.remove(States.visible);
recordContainer.classList.add(States.collapsed);
collapseButton.setAttribute('aria-expanded', 'false');
}
/**
* @param {HTMLDivElement} recordContainer
* @param {string} objectId
*/
private static expandElement(recordContainer: HTMLDivElement, objectId: string): void {
const collapseButton = InlineControlContainer.getCollapseButton(objectId);
recordContainer.classList.remove(States.collapsed);
recordContainer.classList.add(States.visible);
collapseButton.setAttribute('aria-expanded', 'true');
}
/**
* @param {string} objectId
* @return boolean
......@@ -807,8 +835,7 @@ class InlineControlContainer {
const recordObjectId = this.container.dataset.objectGroup + Separators.structureSeparator + recordUid;
const recordContainer = InlineControlContainer.getInlineRecordContainer(recordObjectId);
if (recordContainer.classList.contains(States.visible)) {
recordContainer.classList.remove(States.visible);
recordContainer.classList.add(States.collapsed);
InlineControlContainer.collapseElement(recordContainer, recordObjectId);
if (InlineControlContainer.isNewRecord(recordObjectId)) {
InlineControlContainer.updateExpandedCollapsedStateLocally(recordObjectId, false);
......
......@@ -215,17 +215,20 @@ class InlineRecordContainer extends AbstractContainer
'data-placeholder-record' => $data['isInlineDefaultLanguageRecordInLocalizedParentContext'] ? '1' : '0'
];
$ariaExpanded = $data['isInlineChildExpanded'] ? 'true' : 'false';
$ariaControls = htmlspecialchars($objectId . '_fields', ENT_QUOTES | ENT_HTML5);
$ariaAttributesString = 'aria-expanded="' . $ariaExpanded . '" aria-controls="' . $ariaControls . '"';
$html = '
<div ' . GeneralUtility::implodeAttributes($containerAttributes, true) . '>
<div class="panel-heading" data-toggle="formengine-inline" id="' . htmlspecialchars($hashedObjectId) . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
<div class="panel-heading" data-toggle="formengine-inline" id="' . htmlspecialchars($hashedObjectId, ENT_QUOTES | ENT_HTML5) . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
<div class="form-irre-header">
<div class="form-irre-header-cell form-irre-header-icon">
<span class="caret"></span>
</div>
' . $this->renderForeignRecordHeader($data) . '
' . $this->renderForeignRecordHeader($data, $ariaAttributesString) . '
</div>
</div>
<div class="panel-collapse" id="' . htmlspecialchars($objectId) . '_fields">' . $html . $hiddenFieldHtml . $combinationHtml . '</div>
<div class="panel-collapse" id="' . $ariaControls . '">' . $html . $hiddenFieldHtml . $combinationHtml . '</div>
</div>';
}
......@@ -325,9 +328,10 @@ class InlineRecordContainer extends AbstractContainer
* Later on the command-icons are inserted here.
*
* @param array $data Current data
* @param string $ariaAttributesString HTML aria attributes for the collapse button
* @return string The HTML code of the header
*/
protected function renderForeignRecordHeader(array $data)
protected function renderForeignRecordHeader(array $data, string $ariaAttributesString)
{
$languageService = $this->getLanguageService();
$inlineConfig = $data['inlineParentConfig'];
......@@ -397,13 +401,15 @@ class InlineRecordContainer extends AbstractContainer
}
if (!empty($inlineConfig['appearance']['headerThumbnail']['field']) && $thumbnail) {
$mediaContainer = '<div class="form-irre-header-cell form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>';
$mediaContainer = '<div class="form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>';
} else {
$mediaContainer = '<div class="form-irre-header-cell form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>';
$mediaContainer = '<div class="form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>';
}
$header = $mediaContainer . '
<div class="form-irre-header-cell form-irre-header-body">' . $label . '</div>
<div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>';
$header = '<button class="form-irre-header-cell form-irre-header-button" ' . $ariaAttributesString . '>' .
$mediaContainer .
'<div class="form-irre-header-body">' . $label . '</div>' .
'</button>' .
'<div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>';
return $header;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment