@iyulab/flex-table 0.40.2 → 0.40.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.40.3] - 2026-09-30
4
+
5
+ ### Fixed
6
+
7
+ - **Screen readers get the position of a row and a cell in the whole table, not in the rendered part.**
8
+ The grid renders only the visible rows and columns, so without indices a screen reader announced the
9
+ first rendered row as row 1 after scrolling. Rows now carry `aria-rowindex` and cells, column headers
10
+ and footer cells `aria-colindex` (1-based, in display order). `aria-rowcount` now counts the header
11
+ row and the footer row as the ARIA grid pattern defines it (it counted data rows only, so it is one or
12
+ two higher than before). The footer row's cells now have the grid cell role — the footer was a row
13
+ without cells.
14
+
3
15
  ## [0.40.2] - 2026-09-30
4
16
 
5
17
  ### Fixed
package/README.md CHANGED
@@ -67,7 +67,7 @@ npm install @iyulab/flex-table
67
67
  - **Data Mode** — Client-side or server-side sorting/filtering (`dataMode`)
68
68
  - **Context Menu** — `context-menu` event for custom right-click menus
69
69
  - **React Wrapper** — `@iyulab/flex-table/react` subpath for idiomatic React usage
70
- - **ARIA** — `role="grid"`, `aria-sort`, `aria-selected`, `aria-readonly`, `aria-invalid`, `aria-rowcount`, `aria-colcount`
70
+ - **ARIA** — `role="grid"`, `aria-sort`, `aria-selected`, `aria-readonly`, `aria-invalid`, `aria-rowcount`, `aria-colcount`, `aria-rowindex`, `aria-colindex`
71
71
 
72
72
  ## Sizing
73
73
 
@@ -471,6 +471,7 @@ conformance claim for the success criteria it does not list.
471
471
  | SC 2.5.8 Target Size (Minimum) | Sortable headers, column menu buttons, the open column menu, the open filter dropdown (text and number), the find/replace bar, the cell context menu and row selection checkboxes are at least 24×24 CSS px or meet the spacing exception (24px between centers, counting the neighbouring resize handles), and are actually hit at that position. Two small targets use the equivalent-control exception: the 6px column resize handle (every resize is also in the column menu) and the hidden-column marker (its column menu offers **Show: …**) | `tests/browser/target-size.browser.test.ts` (real Chromium) |
472
472
  | SC 2.1.1 Keyboard (pointer-cursor check) | Nothing the grid renders shows a pointer cursor without being an interactive element. Sorting by header click also has a keyboard path — the column menu's **Sort ascending** / **Sort descending** (the check alone cannot see that, because the header cell wraps the menu button) | `tests/browser/target-size.browser.test.ts` · `src/flex-table.test.ts` |
473
473
  | SC 2.5.7 Dragging Movements | Resizing never requires a drag — the column menu's **Auto-fit width**, **Wider** and **Narrower** are single clicks | `tests/browser/column-menu.browser.test.ts` |
474
+ | SC 1.3.1 Info and Relationships (virtualized grid) | Only the visible rows and columns are in the DOM, so every row carries `aria-rowindex` and every cell `aria-colindex` (1-based, in display order), and the grid's `aria-rowcount` / `aria-colcount` give the full size — the header row is row 1, data rows start at 2, and the footer row (if any) is last. A screen reader announces "row 621 of 10001" after scrolling instead of counting the rows that happen to be rendered. The footer row's cells are grid cells | `tests/browser/aria-grid-index.browser.test.ts` |
474
475
 
475
476
  Not yet measured: the boolean and date filter dropdowns and the comment popup. Color contrast comes
476
477
  from the `@iyulab/components` tokens this package reads.
@@ -2291,7 +2291,7 @@ var q = {
2291
2291
  }
2292
2292
  updated(e) {
2293
2293
  this._focusEditor(), this._adjustFilterDropdown(), e.has("_headerMenu") && this._keepMenuInView(".ft-header-menu"), e.has("_bodyContextMenu") && this._bodyContextMenu && (this._keepMenuInView(".ft-body-context-menu"), this.shadowRoot?.querySelector(".ft-body-context-menu [role=\"menuitem\"]")?.focus()), e.has("stylesheets") && this._syncStylesheets();
2294
- let t = String(this._visibleRowCount), n = String(this.visibleColumns.length);
2294
+ let t = String(1 + this._visibleRowCount + +!!this.footerData), n = String(this.visibleColumns.length);
2295
2295
  if (this.getAttribute("aria-rowcount") !== t && this.setAttribute("aria-rowcount", t), this.getAttribute("aria-colcount") !== n && this.setAttribute("aria-colcount", n), this.loading ? this.getAttribute("aria-busy") !== "true" && this.setAttribute("aria-busy", "true") : this.hasAttribute("aria-busy") && this.removeAttribute("aria-busy"), e.has("_commentPopup") && this._commentPopup) {
2296
2296
  let e = this.shadowRoot?.querySelector(".ft-comment-popup textarea");
2297
2297
  e && (e.value = this.getComment(this._commentPopup.dataIndex, this._commentPopup.colKey) ?? "", e.focus(), e.select());
@@ -2992,6 +2992,7 @@ var q = {
2992
2992
  return i`
2993
2993
  <div class=${y}
2994
2994
  role="columnheader"
2995
+ aria-colindex=${t + 1}
2995
2996
  data-col-index=${t}
2996
2997
  style=${_}
2997
2998
  aria-sort=${p ?? a}
@@ -4007,7 +4008,7 @@ var q = {
4007
4008
  let e = this.data.length === 0 ? this.emptyMessage : this.noMatchingMessage;
4008
4009
  return i`
4009
4010
  ${n}
4010
- <div class="ft-header" role="row" style="width: ${o}px; height: ${r}px;">
4011
+ <div class="ft-header" role="row" aria-rowindex="1" style="width: ${o}px; height: ${r}px;">
4011
4012
  ${f}${p}
4012
4013
  ${m}
4013
4014
  ${h}
@@ -4022,7 +4023,7 @@ var q = {
4022
4023
  ${t}
4023
4024
  ${n}
4024
4025
  ${this._renderFindPanel()}
4025
- <div class="ft-header" role="row" style="width: ${o}px; height: ${r}px;">
4026
+ <div class="ft-header" role="row" aria-rowindex="1" style="width: ${o}px; height: ${r}px;">
4026
4027
  ${f}${p}
4027
4028
  ${m}
4028
4029
  ${h}
@@ -4069,19 +4070,19 @@ var q = {
4069
4070
  for (let e of n) {
4070
4071
  let t = r[e], n = this._getColWidth(t), o = t.pinned === "right" ? `position: absolute; top: 0; left: ${this._getPinnedRightLeft(e)}px; width: ${n}px; height: ${a}px; z-index: 2;` : `position: absolute; top: 0; left: ${s + this._getPinnedLeft(e)}px; width: ${n}px; height: ${a}px; z-index: 2;`;
4071
4072
  d.push(i`
4072
- <div class="ft-footer-cell ft-pinned" style=${o}>
4073
+ <div class="ft-footer-cell ft-pinned" role="gridcell" aria-colindex=${e + 1} style=${o}>
4073
4074
  ${this.footerData[t.key] ?? ""}</div>
4074
4075
  `);
4075
4076
  }
4076
4077
  for (let n = e; n < t; n++) {
4077
4078
  let e = r[n], t = this._getColWidth(e), o = this._colLeftOffsets[n] ?? 0, c = e.pinned === "left", l = e.pinned === "right", u = c || l, f;
4078
4079
  f = c ? `position: absolute; top: 0; left: ${s + this._getPinnedLeft(n)}px; width: ${t}px; height: ${a}px; z-index: 2;` : l ? `position: absolute; top: 0; right: ${-s + this._getPinnedRight(n)}px; width: ${t}px; height: ${a}px; z-index: 2;` : `left: ${o}px; width: ${t}px; height: ${a}px;`, d.push(i`
4079
- <div class="ft-footer-cell ${u ? "ft-pinned" : ""}" style=${f}>
4080
+ <div class="ft-footer-cell ${u ? "ft-pinned" : ""}" role="gridcell" aria-colindex=${n + 1} style=${f}>
4080
4081
  ${this.footerData[e.key] ?? ""}</div>
4081
4082
  `);
4082
4083
  }
4083
4084
  return i`
4084
- <div class="ft-footer" role="row" style="width: ${o}px; height: ${a}px;">
4085
+ <div class="ft-footer" role="row" aria-rowindex=${this._visibleRowCount + 2} style="width: ${o}px; height: ${a}px;">
4085
4086
  ${l}${u}
4086
4087
  ${d}
4087
4088
  </div>
@@ -4107,7 +4108,7 @@ var q = {
4107
4108
  for (let t of r) y.push(this._renderCell(c, o[t], e, t));
4108
4109
  for (let r = t; r < n; r++) y.push(this._renderCell(c, o[r], e, r));
4109
4110
  return i`
4110
- <div class="ft-row ${f} ${p ? "ft-row-selected" : ""}" role="row"
4111
+ <div class="ft-row ${f} ${p ? "ft-row-selected" : ""}" role="row" aria-rowindex=${e + 2}
4111
4112
  style="top: ${l}px; height: ${u}px; width: ${d}px;">
4112
4113
  ${g}${v}
4113
4114
  ${y}
@@ -4126,6 +4127,7 @@ var q = {
4126
4127
  }
4127
4128
  if (s) return i`
4128
4129
  <div class="ft-cell ft-editing ft-active ${d ? "ft-pinned" : ""}" role="gridcell"
4130
+ aria-colindex=${r + 1}
4129
4131
  data-col-index=${r}
4130
4132
  aria-selected="true"
4131
4133
  aria-readonly=${g ? "true" : a}
@@ -4146,6 +4148,7 @@ var q = {
4146
4148
  return i`
4147
4149
  <div class=${E ? C + " ft-has-comment" : C}
4148
4150
  role="gridcell"
4151
+ aria-colindex=${r + 1}
4149
4152
  data-col-index=${r}
4150
4153
  aria-selected=${_ ? "true" : "false"}
4151
4154
  aria-readonly=${g ? "true" : a}
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./flex-table-CbIsTvod.js";
1
+ import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./flex-table-DvETrA1w.js";
2
2
  export { o as FlexTable, e as RowSelectionState, t as UndoStack, n as effectiveAlign, i as exportData, r as flexTableLocale, a as renderCell };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-CbIsTvod.js";
1
+ import { t as e } from "./flex-table-DvETrA1w.js";
2
2
  import { i as t, t as n } from "./query-Dw4iO0ir.js";
3
3
  import { t as r } from "./view-DiLr-TdY.js";
4
4
  import i, { useCallback as a, useEffect as o, useMemo as s, useRef as c, useState as l } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.40.2",
3
+ "version": "0.40.3",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",