Aman-Mittal opened a new issue, #286:
URL: https://github.com/apache/fineract-backoffice-ui/issues/286

   ## Business value
   
   `src/app/shared/components/data-table/data-table.component.ts:155-165` 
renders every sortable column header like this:
   
   ```html
   <th
     cdk-header-cell
     *cdkHeaderCellDef
     [appTooltip]="col.tooltip || ''"
     [attr.aria-sort]="ariaSortFor(col)"
     [class.sortable]="col.sortable"
     (click)="onSortHeaderClick(col)"
   >
   ```
   
   `aria-sort` is correct and it announces the current sort state properly. But 
a `<th>` is not focusable, and there is no `tabindex`, no `role="button"`, and 
no key handler. So the header **announces that it is sortable and then cannot 
be sorted**, which is worse than not exposing the state at all.
   
   This is one component used at **106 call sites across 103 files**. Sorting 
is mouse-only on every list screen in the application — clients, loans, 
savings, journal entries, offices, staff, all of them. Anyone working 
keyboard-only, and anyone using voice control or a switch device, can read a 
list but cannot reorder it. For a back-office application where finding one 
record among thousands is the core task, sorting is not a convenience.
   
   It also fails WCAG 2.1 SC 2.1.1 (Keyboard), a Level A criterion.
   
   **Why the linter did not catch it.** 
`@angular-eslint/template/click-events-have-key-events` is enabled, but the 
element carries `*cdkHeaderCellDef`, and the rule does not fire on elements 
inside a structural directive's template in this configuration. The a11y e2e 
also misses it: `e2e/accessibility.spec.ts` scopes axe with `.include()` to 
`ion-card` and runs on three screens, and axe cannot detect a missing key 
handler in any case — that is a manual check.
   
   ## Reproducing it
   
   Open any list screen with a sortable column. Press Tab repeatedly. Focus 
never lands on a column header. There is no key that sorts.
   
   ```
   grep -n "onSortHeaderClick\|tabindex\|keydown" 
src/app/shared/components/data-table/data-table.component.ts
   ```
   
   One click handler, no `tabindex`, no `keydown`.
   
   ## Describing the change
   
   The header cell needs to be operable, and the accessible role has to match 
what it does. Two shapes are reasonable:
   
   **Preferred — a real button inside the `<th>`:**
   
   ```html
   <th cdk-header-cell *cdkHeaderCellDef [attr.aria-sort]="ariaSortFor(col)">
     @if (col.sortable) {
       <button type="button" class="sort-button" 
(click)="onSortHeaderClick(col)">
         {{ col.label | translate }}
         <!-- sort indicator -->
       </button>
     } @else {
       {{ col.label | translate }}
     }
   </th>
   ```
   
   This gets focusability, Enter and Space activation, and the correct role for 
free, with no key handling to write and nothing to get wrong. `aria-sort` stays 
on the `<th>`, which is where the spec requires it.
   
   **Alternative — make the `<th>` itself operable** with 
`[attr.tabindex]="col.sortable ? 0 : null"`, `[attr.role]="col.sortable ? 
'button' : null"` and a `(keydown)` handler for Enter and Space. Fewer DOM 
changes, but you must remember `event.preventDefault()` on Space or the page 
scrolls, and you take on the role plumbing by hand.
   
   Either is acceptable. Please take the first unless the styling fights you.
   
   **Three things to be careful about.**
   
   1. **Only sortable columns.** A non-sortable header must not be focusable — 
putting every header in the tab order makes keyboard navigation of a wide table 
considerably worse. Note the current template applies `(click)` unconditionally 
and relies on `onSortHeaderClick` ignoring non-sortable columns; the 
focusability must be conditional even though the click currently is not.
   2. **Do not lose `aria-sort`.** It is already correct. Keep it on the 
`<th>`, not on the button.
   3. **Check the focus indicator is visible.** The header has its own 
background; a default outline may disappear against it. This is a real part of 
the fix, not polish — an invisible focus ring makes the feature unusable for 
the people it is for.
   
   ## Testing
   
   There is no platform acceptance test for this — it is a pure UI concern — so 
the tests have to assert the behaviour directly.
   
   **A unit spec** in `data-table.component.spec.ts` (the file exists and is 
one of the better specs in the repo):
   
   ```ts
   it('exposes sortable headers to the keyboard and not unsortable ones', () => 
{ ... });
   
   it('sorts on Enter and on Space', () => { ... });
   ```
   
   Assert on the rendered DOM, not on the component instance. Most specs in 
this repo call a handler directly and check a signal, which would pass 
throughout this bug — the entire defect is that nothing reaches the handler.
   
   **A mocked e2e** is the test that genuinely proves it, because it drives 
real focus:
   
   ```ts
   await page.getByRole('button', { name: /office name/i }).focus();
   await page.keyboard.press('Enter');
   await expect(page.locator('th[aria-sort="ascending"]')).toBeVisible();
   ```
   
   Reaching the header by keyboard alone (repeated `Tab` from a known starting 
point) is a stronger assertion than `.focus()` if you can make it stable.
   
   **Extending the axe coverage is welcome but will not catch this** — axe 
cannot detect a missing key handler. If you do touch 
`e2e/accessibility.spec.ts`, the more valuable change is widening the 
`.include()` scope beyond `ion-card` so the surrounding page is audited at all.
   
   ## Scope
   
   In scope: keyboard operability and focus visibility of sort headers in 
`app-data-table`.
   
   Out of scope: the clickable table rows in `client-search-v2.component.ts` 
and `global-search.component.ts`, which have the same class of problem and 
should get their own issue; and the wider accessibility gaps around dialog 
names and icon `aria-hidden`.
   
   ## Getting started
   
   - `src/app/shared/components/data-table/data-table.component.ts` — template 
around `:155`, styles in the same file.
   - Existing spec: 
`src/app/shared/components/data-table/data-table.component.spec.ts`
   - `npm test`, `npm run lint:prune` and `npm run build` must pass.
   - This component has 106 call sites. Do not change its inputs or outputs — 
the fix should be entirely internal, so no consumer needs touching. If you find 
yourself editing a feature component, step back.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to