[ACS-9224] Added fallback gridcell and ensured proper columnheader and gridcell roles

This commit is contained in:
Shivangi917
2025-06-24 03:19:47 -04:00
parent faddad5a5f
commit c188da6338
2 changed files with 182 additions and 196 deletions
@@ -17,22 +17,20 @@
role="row"> role="row">
<!-- Drag --> <!-- Drag -->
<div *ngIf="enableDragRows" class="adf-datatable-cell-header adf-drag-column"> <div *ngIf="enableDragRows" class="adf-datatable-cell-header adf-drag-column" role="columnheader">
<div cdkDragHandle <span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.DRAG' | translate }}</span>
class="adf-drag-handle"
aria-hidden="true">
</div>
</div> </div>
<!-- Actions (left) --> <!-- Actions (left) -->
<div *ngIf="actions && actionsPosition === 'left'" <div *ngIf="actions && actionsPosition === 'left'" class="adf-actions-column adf-datatable-cell-header" role="columnheader">
class="adf-actions-column adf-datatable-cell-header"> <span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.ACTIONS' | translate }}</span>
<span class="adf-sr-only">{{ 'ADF-DATATABLE.ACCESSIBILITY.ACTIONS' | translate }}</span>
</div> </div>
<!-- Columns --> <!-- Columns -->
<div *ngIf="multiselect" class="adf-datatable-cell-header adf-datatable-checkbox"> <div *ngIf="multiselect" class="adf-datatable-cell-header adf-datatable-checkbox" role="columnheader">
<mat-checkbox [indeterminate]="isSelectAllIndeterminate" <mat-checkbox [indeterminate]="isSelectAllIndeterminate"
[checked]="isSelectAllChecked" [checked]="isSelectAllChecked"
(change)="onSelectAllClick($event)" (change)="onSelectAllClick($event)"
@@ -48,114 +46,129 @@
let columnIndex = index let columnIndex = index
let lastColumn = last" let lastColumn = last"
> >
<div <div
class="adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-cell-header adf-datatable-cell-data" class="adf-datatable-cell--{{col.type || 'text'}} {{col.cssClass}} adf-datatable-cell-header adf-datatable-cell-data"
*ngIf="col.title || !showProvidedActions" *ngIf="col.title || !showProvidedActions"
[attr.data-automation-id]="'auto_id_' + col.key" [attr.data-automation-id]="'auto_id_' + col.key"
[ngClass]="{ [ngClass]="{
'adf-sortable': col.sortable, 'adf-sortable': col.sortable,
'adf-datatable__cursor--pointer': !isResizing, 'adf-datatable__cursor--pointer': !isResizing,
'adf-datatable__header--sorted-asc': isColumnSorted(col, 'asc'), 'adf-datatable__header--sorted-asc': isColumnSorted(col, 'asc'),
'adf-datatable__header--sorted-desc': isColumnSorted(col, 'desc')}" 'adf-datatable__header--sorted-desc': isColumnSorted(col, 'desc')}"
[ngStyle]="(col.width) && !lastColumn && {'flex': getFlexValue(col)}" [ngStyle]="(col.width) && !lastColumn && {'flex': getFlexValue(col)}"
[attr.aria-label]="(col.title | translate) + (col.subtitle ? ' ' + col.subtitle : '')" [attr.aria-label]="(col.title | translate) + (col.subtitle ? ' ' + col.subtitle : '')"
(click)="onColumnHeaderClick(col, $event)" (click)="onColumnHeaderClick(col, $event)"
(keyup.enter)="onColumnHeaderClick(col, $event)" (keyup.enter)="onColumnHeaderClick(col, $event)"
role="columnheader" role="columnheader"
[attr.tabindex]="isHeaderVisible() ? 0 : null" [attr.tabindex]="isHeaderVisible() ? 0 : null"
[attr.aria-sort]="col.sortable ? (getAriaSort(col) | translate) : null" [attr.aria-sort]="col.sortable ? (getAriaSort(col) | translate) : null"
cdkDrag cdkDrag
cdkDragLockAxis="x" cdkDragLockAxis="x"
(cdkDragStarted)="isDraggingHeaderColumn = true" (cdkDragStarted)="isDraggingHeaderColumn = true"
(cdkDragDropped)="onDropHeaderColumn($event)" (cdkDragDropped)="onDropHeaderColumn($event)"
[cdkDragDisabled]="!col.draggable" [cdkDragDisabled]="!col.draggable"
(mouseenter)="hoveredHeaderColumnIndex = columnIndex" (mouseenter)="hoveredHeaderColumnIndex = columnIndex"
(mouseleave)="hoveredHeaderColumnIndex = -1" (mouseleave)="hoveredHeaderColumnIndex = -1"
adf-drop-zone dropTarget="header" adf-drop-zone dropTarget="header"
[dropColumn]="col" [dropColumn]="col"
>
<div
adf-resizable
#resizableElement="adf-resizable"
[coverPadding]="10"
(resizing)="onResizing($event, columnIndex)"
(resizeStart)="resizingColumnIndex = columnIndex"
(resizeEnd)="onResizingEnd()"
[attr.data-automation-id]="'auto_header_content_id_' + col.key"
class="adf-datatable-cell-header-content"
[ngClass]="{ 'adf-datatable-cell-header-content--hovered':
hoveredHeaderColumnIndex === columnIndex &&
!isDraggingHeaderColumn &&
!isResizing && col.sortable}"
>
<ng-container *ngIf="!col.header">
<span
*ngIf="col.title"
title="{{col.title | translate}}"
class="adf-datatable-cell-value"
>
{{col.title | translate}}
</span>
<span
*ngIf="col.subtitle"
title="{{col.subtitle | translate}}"
class="adf-datatable-cell-value adf-datatable-cell-header_subtitle"
>
({{col.subtitle | translate}})
</span>
<span *ngIf="col.title && col.sortable && isDraggingHeaderColumn" class="adf-sr-only" aria-live="polite">
{{ getSortLiveAnnouncement(col) | translate: { string: col.title | translate } }}
</span>
<span *ngIf="!col.title && !col.sortable && !headerFilterTemplate"
[attr.title]="'ADF-DATATABLE.ACCESSIBILITY.EMPTY_HEADER' | translate">
</span>
</ng-container>
<div *ngIf="col.header" class="adf-datatable-cell-value">
<ng-template [ngTemplateOutlet]="col.header" [ngTemplateOutletContext]="{$implicit: col}" />
</div>
<span
[class.adf-datatable__header--sorted-asc]="isColumnSorted(col, 'asc')"
[class.adf-datatable__header--sorted-desc]="isColumnSorted(col, 'desc')">
</span>
<ng-template *ngIf="allowFiltering" [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{$implicit: col}" />
<span
*ngIf="col.draggable"
cdkDragHandle
[ngClass]="{ 'adf-datatable-cell-header-drag-icon': !isResizing }"
> >
<adf-icon
*ngIf="hoveredHeaderColumnIndex === columnIndex && !isResizing" <div
value="adf:drag_indicator" adf-resizable
class="adf-datatable-cell-header-drag-icon-visible" #resizableElement="adf-resizable"
[attr.data-automation-id]="'adf-datatable-cell-header-drag-icon-'+col.key" /> [coverPadding]="10"
(resizing)="onResizing($event, columnIndex)"
(resizeStart)="resizingColumnIndex = columnIndex"
(resizeEnd)="onResizingEnd()"
[attr.data-automation-id]="'auto_header_content_id_' + col.key"
class="adf-datatable-cell-header-content"
[ngClass]="{ 'adf-datatable-cell-header-content--hovered':
hoveredHeaderColumnIndex === columnIndex &&
!isDraggingHeaderColumn &&
!isResizing && col.sortable}"
>
<ng-container *ngIf="!col.header">
<span
*ngIf="col.title"
title="{{col.title | translate}}"
class="adf-datatable-cell-value"
>
{{col.title | translate}}
</span>
<span
*ngIf="col.subtitle"
title="{{col.subtitle | translate}}"
class="adf-datatable-cell-value adf-datatable-cell-header_subtitle"
>
({{col.subtitle | translate}})
</span>
<span *ngIf="col.title && col.sortable && isDraggingHeaderColumn" class="adf-sr-only" aria-live="polite">
{{ getSortLiveAnnouncement(col) | translate: { string: col.title | translate } }}
</span>
<span
*ngIf="!col.title && !col.sortable && !headerFilterTemplate"
class="adf-sr-only">
{{ 'ADF-DATATABLE.ACCESSIBILITY.EMPTY_HEADER' | translate }}
</span>
</ng-container>
<div *ngIf="col.header" class="adf-datatable-cell-value">
<ng-template [ngTemplateOutlet]="col.header" [ngTemplateOutletContext]="{$implicit: col}" />
</div>
<span
[class.adf-datatable__header--sorted-asc]="isColumnSorted(col, 'asc')"
[class.adf-datatable__header--sorted-desc]="isColumnSorted(col, 'desc')">
</span>
<ng-template *ngIf="allowFiltering" [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{$implicit: col}" />
<span
*ngIf="col.draggable"
cdkDragHandle
[ngClass]="{ 'adf-datatable-cell-header-drag-icon': !isResizing }">
>
<adf-icon
*ngIf="hoveredHeaderColumnIndex === columnIndex && !isResizing"
value="adf:drag_indicator"
class="adf-datatable-cell-header-drag-icon-visible"
[attr.data-automation-id]="'adf-datatable-cell-header-drag-icon-'+col.key" />
</span>
</div>
<div
*ngIf="isResizingEnabled && col.resizable && !lastColumn"
[ngClass]="hoveredHeaderColumnIndex === columnIndex && !isResizing || resizingColumnIndex === columnIndex ? 'adf-datatable__resize-handle-visible' : 'adf-datatable__resize-handle-hidden'"
adf-resize-handle
tabindex="0"
role="button"
(click)="$event.stopPropagation()"
(keyup.enter)="$event.stopPropagation()"
class="adf-datatable__resize-handle"
[resizableContainer]="resizableElement"
[attr.aria-label]="'ADF-DATATABLE.ACCESSIBILITY.RESIZE_COLUMN' | translate">
<div class="adf-datatable__resize-handle--divider"></div>
</div>
<div class="adf-drop-header-cell-placeholder" *cdkDragPlaceholder></div>
</div>
</ng-container>
<!-- Fallback columnheader for ARIA compliance -->
<div
*ngIf="!enableDragRows && !multiselect && !(actions && actionsPosition === 'left') && getVisibleColumns().length === 0"
class="adf-datatable-cell-header"
role="columnheader"
>
<span class="adf-sr-only">
{{ 'ADF-DATATABLE.ACCESSIBILITY.EMPTY_HEADER' | translate }}
</span> </span>
</div> </div>
<div
*ngIf="isResizingEnabled && col.resizable && !lastColumn"
[ngClass]="hoveredHeaderColumnIndex === columnIndex && !isResizing || resizingColumnIndex === columnIndex ? 'adf-datatable__resize-handle-visible' : 'adf-datatable__resize-handle-hidden'"
adf-resize-handle
tabindex="0"
role="button"
(click)="$event.stopPropagation()"
(keyup.enter)="$event.stopPropagation()"
class="adf-datatable__resize-handle"
[resizableContainer]="resizableElement"
[attr.aria-label]="'ADF-DATATABLE.ACCESSIBILITY.RESIZE_COLUMN' | translate">
<div class="adf-datatable__resize-handle--divider"></div>
</div>
<div class="adf-drop-header-cell-placeholder" *cdkDragPlaceholder></div>
</div>
</ng-container>
<!-- Header actions (right) --> <!-- Header actions (right) -->
<div <div
@@ -163,6 +176,7 @@
(mainActionTemplate && showMainDatatableActions)" (mainActionTemplate && showMainDatatableActions)"
class="adf-actions-column adf-datatable-actions-menu adf-datatable-cell-header adf-datatable__actions-cell" class="adf-actions-column adf-datatable-actions-menu adf-datatable-cell-header adf-datatable__actions-cell"
[class.adf-datatable-actions-menu-provided]="showProvidedActions" [class.adf-datatable-actions-menu-provided]="showProvidedActions"
role="columnheader"
> >
<ng-container *ngIf="mainActionTemplate"> <ng-container *ngIf="mainActionTemplate">
<button <button
@@ -246,27 +260,30 @@
</mat-menu> </mat-menu>
</div> </div>
<label *ngIf="multiselect" <div *ngIf="multiselect" role="gridcell" class="adf-datatable-cell adf-datatable-checkbox adf-datatable-checkbox-single">
(keydown.enter)="onEnterKeyPressed(row, $any($event))" <label
(click)="onCheckboxLabelClick(row, $event)" (keydown.enter)="onEnterKeyPressed(row, $any($event))"
[for]="'select-file-' + idx" (click)="onCheckboxLabelClick(row, $event)"
class="adf-datatable-cell adf-datatable-checkbox adf-datatable-checkbox-single" [for]="'select-file-' + idx"
tabindex="0"> class="adf-datatable-checkbox-label"
tabindex="0">
<mat-checkbox
[id]="'select-file-' + idx"
[disabled]="!row?.isSelectable"
[class.adf-datatable-checkbox-selected]="row.isSelected"
[class.adf-datatable-hover-only]="displayCheckboxesOnHover"
[checked]="row.isSelected"
[attr.aria-checked]="row.isSelected"
[aria-label]="'ADF-DATATABLE.ACCESSIBILITY.SELECT_FILE' | translate"
data-adf-datatable-row-checkbox
(change)="onCheckboxChange(row, $event)"
class="adf-checkbox-sr-only">
{{ 'ADF-DATATABLE.ACCESSIBILITY.SELECT_FILE' | translate }}
</mat-checkbox>
</label>
</div>
<mat-checkbox
[id]="'select-file-' + idx"
[disabled]="!row?.isSelectable"
[class.adf-datatable-checkbox-selected]="row.isSelected"
[class.adf-datatable-hover-only]="displayCheckboxesOnHover"
[checked]="row.isSelected"
[attr.aria-checked]="row.isSelected"
[aria-label]="'ADF-DATATABLE.ACCESSIBILITY.SELECT_FILE' | translate"
data-adf-datatable-row-checkbox
(change)="onCheckboxChange(row, $event)"
class="adf-checkbox-sr-only">
{{ 'ADF-DATATABLE.ACCESSIBILITY.SELECT_FILE' | translate }}
</mat-checkbox>
</label>
<div <div
*ngFor="let col of getVisibleColumns(); let lastColumn = last;" *ngFor="let col of getVisibleColumns(); let lastColumn = last;"
@@ -414,6 +431,17 @@
</div> </div>
</div> </div>
<!-- Fallback gridcell for ARIA compliance -->
<div
*ngIf="!enableDragRows && !multiselect && !(actions && actionsPosition === 'left') && getVisibleColumns().length === 0"
class="adf-datatable-cell"
role="gridcell"
>
<span class="adf-sr-only">
{{ 'ADF-DATATABLE.ACCESSIBILITY.EMPTY_CELL' | translate }}
</span>
</div>
<!-- Row actions (right) --> <!-- Row actions (right) -->
<div *ngIf=" <div *ngIf="
!showProvidedActions && !showProvidedActions &&
@@ -460,7 +488,7 @@
<div <div
role="row" role="row"
class="adf-datatable-row adf-no-permission__row"> class="adf-datatable-row adf-no-permission__row">
<div class="adf-no-permission__cell adf-no-content-container adf-datatable-cell"> <div role="gridcell" class="adf-no-permission__cell adf-no-content-container adf-datatable-cell">
<ng-template *ngIf="noPermissionTemplate" <ng-template *ngIf="noPermissionTemplate"
ngFor [ngForOf]="[data]" ngFor [ngForOf]="[data]"
[ngForTemplate]="noPermissionTemplate" /> [ngForTemplate]="noPermissionTemplate" />
@@ -469,8 +497,8 @@
</ng-template> </ng-template>
</div> </div>
<ng-template #loadingRowTemplate> <ng-template #loadingRowTemplate>
<div class="adf-datatable-row adf-datatable-data-loading"> <div role="row" class="adf-datatable-row adf-datatable-data-loading">
<div class="adf-no-content-container adf-datatable-cell"> <div role="gridcell" class="adf-no-content-container adf-datatable-cell">
<ng-template *ngIf="loadingTemplate" <ng-template *ngIf="loadingTemplate"
ngFor [ngForOf]="[data]" ngFor [ngForOf]="[data]"
[ngForTemplate]="loadingTemplate" /> [ngForTemplate]="loadingTemplate" />
@@ -288,13 +288,11 @@ $data-table-cell-min-width-file-size: $data-table-cell-min-width-1 !default;
/* stylelint-disable-next-line no-duplicate-selectors */ /* stylelint-disable-next-line no-duplicate-selectors */
.adf-datatable-cell, .adf-datatable-cell,
.adf-datatable-cell-header { .adf-datatable-cell-header {
flex: 1 1 0%; flex: 1;
padding: 0; padding: 0;
align-items: center; align-items: center;
display: flex; display: flex;
align-self: stretch; align-self: stretch;
min-width: 0;
min-height: 48px;
&:first-child { &:first-child {
margin-left: 15px; margin-left: 15px;
@@ -302,65 +300,25 @@ $data-table-cell-min-width-file-size: $data-table-cell-min-width-1 !default;
} }
&.adf-drag-column { &.adf-drag-column {
flex: 0 0 24px; flex: 0;
width: 24px;
padding: 0;
min-height: 100%;
position: relative;
}
}
.adf-datatable-cell-container {
overflow: hidden;
min-height: inherit;
align-items: center;
display: flex;
width: 100%;
padding: 0 10px;
}
.adf-datatable-cell-value {
word-break: break-word;
display: block;
width: 100%;
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
padding: 17px 10px 10px;
}
}
.adf-drag-handle {
cursor: grab;
width: 24px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.2s ease, background-color 0.2s ease;
user-select: none;
touch-action: none;
position: absolute;
left: 0;
top: 0;
&:hover {
opacity: 0.8;
background-color: rgba(0, 0, 0, 0.03);
} }
&:active { .adf-datatable-cell-container {
cursor: grabbing; overflow: hidden;
opacity: 1; min-height: inherit;
background-color: rgba(0, 0, 0, 0.08); align-items: center;
display: flex;
width: 100%;
padding: 0 10px;
} }
&::after { .adf-datatable-cell-value {
content: '⋮⋮'; word-break: break-word;
font-size: 16px; display: block;
color: currentcolor;
opacity: 0.7; @media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
line-height: 1; padding: 17px 10px 10px;
}
} }
} }