@zeyos/zx 2.2.0 → 2.2.1

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/docs/llms.md CHANGED
@@ -777,7 +777,10 @@ horizontally, and multi-select adds a tri-state header checkbox plus Shift+click
777
777
  instead of laying out a result set nobody will scroll through. `growBy(count?)`, `showAll()`,
778
778
  `getRenderedCount()`, and `grow {rendered, total}`. `setData()` starts the batch over, since a new
779
779
  result set is not the old one grown. Row indices stay absolute — a row's index is its place in the
780
- data, not in what is on screen.
780
+ data, not in what is on screen. **Select-all covers the rendered rows, not the whole result set**;
781
+ call `showAll()` first to mean all of them. While growing, the table carries `aria-rowcount` and
782
+ each row an `aria-rowindex`, so assistive technology hears the real total rather than the size of
783
+ the batch.
781
784
  - **Responsive stacking** — `responsive: false|'sm'|'md'|'lg'|'xl'` stacks each row into a card of
782
785
  label/value pairs below that width, instead of leaving a horizontal scrollbar with the data
783
786
  hidden behind it. The width measured is **the table's own container**, not the viewport, so this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyos/zx",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Dependency-free, accessible UI component library for ZeyOS business applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -802,6 +802,7 @@ export class Table extends Component {
802
802
  /** @returns {void} */
803
803
  _renderHeader() {
804
804
  const row = h('tr', { class: 'zx-table__header-row' });
805
+ if (this._growStep) row.setAttribute('aria-rowindex', '1');
805
806
  if (this.options.selectable === 'multi') {
806
807
  this._selectAll = h('input', {
807
808
  class: 'zx-table__checkbox zx-table__select-all',
@@ -814,10 +815,15 @@ export class Table extends Component {
814
815
  }, this._selectAll);
815
816
  row.append(selectionHeader);
816
817
  this.listen(this._selectAll, 'click', () => {
818
+ /*
819
+ * The rendered rows, not every row in the data. With `growing` on, selecting a hundred
820
+ * records because the reader ticked a box above the ten they can see is a trap — the next
821
+ * click is usually a bulk action. `showAll()` first if you mean all of them.
822
+ */
817
823
  if (this._selectAll.checked) {
818
- for (const dataRow of this._data) this._selected.add(this._idFor(dataRow));
824
+ for (const dataRow of this._visibleData()) this._selected.add(this._idFor(dataRow));
819
825
  } else {
820
- this._selected.clear();
826
+ for (const dataRow of this._visibleData()) this._selected.delete(this._idFor(dataRow));
821
827
  }
822
828
  this._selectionAnchorId = null;
823
829
  this._syncSelection();
@@ -874,6 +880,11 @@ export class Table extends Component {
874
880
  this._visibleData().forEach((row, index) => fragment.append(this._createRow(row, index)));
875
881
  }
876
882
  this._tbody.replaceChildren(fragment);
883
+ /*
884
+ * With `growing` on, the DOM holds a prefix of the data — so without this a screen reader
885
+ * announces "row 3 of 10" for a table of ten thousand. The header counts as a row.
886
+ */
887
+ if (this._growStep) this._table.setAttribute('aria-rowcount', String(this._data.length + 1));
877
888
  this._syncSelectAll();
878
889
  this._syncGrowing();
879
890
  if (this._stacked) this._applyStackedRoles(true);
@@ -898,6 +909,8 @@ export class Table extends Component {
898
909
  _createRow(row, index) {
899
910
  const id = this._idFor(row);
900
911
  const tr = /** @type {HTMLTableRowElement} */ (h('tr', { dataset: { row: '' } }));
912
+ // Only a prefix of the data is in the DOM while growing, so each row states where it really is.
913
+ if (this._growStep) tr.setAttribute('aria-rowindex', String(index + 2));
901
914
  const rowClass = typeof this.options.rowClass === 'function' ? this.options.rowClass(row) : '';
902
915
  if (rowClass) tr.classList.add(...String(rowClass).split(/\s+/).filter(Boolean));
903
916
  if (this.options.selectable !== false) tr.setAttribute('aria-selected', String(this._selected.has(id)));
@@ -1421,10 +1434,15 @@ export class Table extends Component {
1421
1434
  /** @returns {void} */
1422
1435
  _syncSelectAll() {
1423
1436
  if (!this._selectAll) return;
1424
- const selectedRows = this._data.reduce((count, row) => count + Number(this._selected.has(this._idFor(row))), 0);
1425
- this._selectAll.disabled = this._data.length === 0;
1426
- this._selectAll.checked = this._data.length > 0 && selectedRows === this._data.length;
1427
- this._selectAll.indeterminate = selectedRows > 0 && selectedRows < this._data.length;
1437
+ // Measured against what is rendered, so the box reflects the rows the reader can actually see.
1438
+ const visible = this._visibleData();
1439
+ const selectedRows = visible.reduce(
1440
+ (count, row) => count + Number(this._selected.has(this._idFor(row))),
1441
+ 0
1442
+ );
1443
+ this._selectAll.disabled = visible.length === 0;
1444
+ this._selectAll.checked = visible.length > 0 && selectedRows === visible.length;
1445
+ this._selectAll.indeterminate = selectedRows > 0 && selectedRows < visible.length;
1428
1446
  }
1429
1447
 
1430
1448
  /** @returns {void} */