@iyulab/flex-table 0.48.6 → 0.49.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/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.49.1] - 2026-10-06
4
+
5
+ ### Fixed
6
+
7
+ - **The column filter is a dialog with a keyboard contract.** It is announced as a dialog named for its
8
+ column ("Filter Customer"); Tab and Shift+Tab stay inside it (it floats over the table, so focus that
9
+ left it would leave it open behind the next control); Escape closes it and returns focus to that
10
+ column's menu button — before, focus dropped to the page. A click outside still closes it without
11
+ moving focus.
12
+
13
+ ## [0.49.0] - 2026-10-06
14
+
15
+ ### Added
16
+
17
+ - **Typed events.** `FlexTableEventMap` (exported, with `CellChange`) maps each of the 31 event names to
18
+ its `CustomEvent<detail>`, and `FlexTable` overloads `addEventListener`/`removeEventListener` with it:
19
+ `table.addEventListener('sort-change', (e) => e.detail.criteria)` type-checks without a cast. Before,
20
+ a TypeScript listener got a plain `Event`. The names stay off the global event map on purpose — several
21
+ are generic (`sort-change`, `selection-change`) and would collide with other libraries.
22
+ - The element dispatches every event through one helper keyed on the map, so a detail that drifts from
23
+ the documented shape no longer compiles.
24
+
25
+ ### Changed
26
+
27
+ - **React `on*` props carry the detail types** (they were `CustomEvent<any>`). A handler that declared a
28
+ detail shape the event never had now fails to type-check — that is the point; handlers typed as plain
29
+ `CustomEvent` keep working.
30
+
31
+ ### Documentation
32
+
33
+ - The README's event tables list the seven events they were missing (`row-reorder`, `data-import`,
34
+ `fill-handle-apply`, `find-replace`, `comment-change`, `column-visibility-change`,
35
+ `header-context-menu`).
36
+
37
+ ### Fixed
38
+
39
+ - The package no longer ships empty `.d.ts` files for its own tests.
40
+
3
41
  ## [0.48.6] - 2026-10-06
4
42
 
5
43
  ### Fixed
package/README.md CHANGED
@@ -282,7 +282,10 @@ Default is `false`, matching `clear-undo-on-data-change`.
282
282
 
283
283
  ## Events
284
284
 
285
- All events use `CustomEvent` with `bubbles: true, composed: true`.
285
+ All events use `CustomEvent` with `bubbles: true, composed: true`. They are typed: `FlexTableEventMap` maps
286
+ each name to its `CustomEvent<detail>`, and `addEventListener` on a `FlexTable` (for example from
287
+ `document.querySelector('flex-table')`) uses it — `table.addEventListener('sort-change', (e) => e.detail.criteria)`
288
+ type-checks without a cast. The React wrapper's `on*` props carry the same types.
286
289
 
287
290
  ### Cell Events
288
291
 
@@ -302,6 +305,11 @@ All events use `CustomEvent` with `bubbles: true, composed: true`.
302
305
  | `row-delete` | `{ indices, rows }` | Rows deleted |
303
306
  | `row-activate` | `{ row, index, col, key }` | Enter pressed on a non-editable cell — the grid's own contract for "activate this row" (e.g. navigate to a detail view), guaranteed even though the internal Enter handler prevents the keystroke from reliably reaching a listener the host attaches to the same element |
304
307
  | `batch-update` | `{ changes: [{ row, key, oldValue, newValue }] }` | Batch update applied |
308
+ | `row-reorder` | `{ from, to }` | Row dragged to a new place (data indices) |
309
+ | `data-import` | `{ count }` | Rows imported from a file |
310
+ | `fill-handle-apply` | `{ sourceRange, targetRange, cells }` | Fill handle wrote `cells` (`{ dataRow, key, oldValue, newValue }`) |
311
+ | `find-replace` | `{ type, cells }` | Replace (`type: 'replace'`) or replace-all from the find panel; `cells` are `{ row, col, oldValue, newValue }` with `col` the column key |
312
+ | `comment-change` | `{ dataIndex, colKey, text }` | Cell comment set, changed or removed (`text: null`) |
305
313
 
306
314
  ### Column Events
307
315
 
@@ -312,6 +320,8 @@ All events use `CustomEvent` with `bubbles: true, composed: true`.
312
320
  | `column-reorder` | `{ key, oldIndex, newIndex }` | Column moved |
313
321
  | `column-resize` | `{ key, width, colIndex }` | Column resized (drag, auto-fit, or keyboard) |
314
322
  | `column-select` | `{ colIndex, key, rowCount }` | Entire column selected |
323
+ | `column-visibility-change` | `{ key, hidden }` | Column hidden or shown |
324
+ | `header-context-menu` | `{ key, label, x, y }` | Right-click on a column header |
315
325
 
316
326
  ### Sort & Filter Events
317
327
 
@@ -509,6 +519,7 @@ conformance claim for the success criteria it does not list.
509
519
  |---|---|---|
510
520
  | 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) |
511
521
  | 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` |
522
+ | SC 2.4.3 Focus Order · SC 2.1.2 No Keyboard Trap | The column filter is a dialog named for its column: opening it from the column menu moves focus into it, Tab and Shift+Tab cycle inside it, and Escape closes it and returns focus to that column's menu button (Escape is the way out) | `tests/browser/column-menu.browser.test.ts` |
512
523
  | 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` |
513
524
  | 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` |
514
525
 
@@ -0,0 +1,199 @@
1
+ import type { CellPosition, CellRange } from './core/selection.js';
2
+ import type { SortCriteria } from './core/sorting.js';
3
+ import type { ColumnDefinition, DataRow } from './models/types.js';
4
+ /** One cell a bulk operation wrote: data row index, column key, value before and after. */
5
+ export interface CellChange {
6
+ row: number;
7
+ key: string;
8
+ oldValue: unknown;
9
+ newValue: unknown;
10
+ }
11
+ /**
12
+ * Every event `<flex-table>` dispatches, by name. All bubble and are composed.
13
+ *
14
+ * This map is the single source: the element dispatches through a typed helper keyed on it, so a
15
+ * detail that drifts from what is written here fails to compile. Listeners get the same types —
16
+ * `table.addEventListener('sort-change', (e) => e.detail.criteria)` — and so does the React
17
+ * wrapper's `onSortChange`. The names are element-scoped (not on the global event map) because
18
+ * several are generic (`sort-change`, `selection-change`) and other libraries use them too.
19
+ */
20
+ export interface FlexTableEventMap {
21
+ /** Row selection changed (`selectable`). Indices are data indices. */
22
+ 'selection-change': CustomEvent<{
23
+ selectedIndices: number[];
24
+ selectedRows: DataRow[];
25
+ }>;
26
+ /** After any undoable change, undo or redo. */
27
+ 'undo-state-change': CustomEvent<{
28
+ canUndo: boolean;
29
+ canRedo: boolean;
30
+ }>;
31
+ /** The set of column filters changed. `filteredCount` is the rows left visible. */
32
+ 'filter-change': CustomEvent<{
33
+ keys: string[];
34
+ filteredCount: number;
35
+ }>;
36
+ /** A column filter predicate threw for a row; the row is kept. */
37
+ 'filter-error': CustomEvent<{
38
+ error: unknown;
39
+ row: DataRow;
40
+ filterKey: string;
41
+ }>;
42
+ 'column-add': CustomEvent<{
43
+ column: ColumnDefinition;
44
+ index: number;
45
+ }>;
46
+ 'column-delete': CustomEvent<{
47
+ column: ColumnDefinition;
48
+ key: string;
49
+ index: number;
50
+ }>;
51
+ 'column-reorder': CustomEvent<{
52
+ key: string;
53
+ oldIndex: number;
54
+ newIndex: number;
55
+ }>;
56
+ 'column-visibility-change': CustomEvent<{
57
+ key: string;
58
+ hidden: boolean;
59
+ }>;
60
+ /** A column was resized — during the drag and once more when it ends. `colIndex` is the visible index. */
61
+ 'column-resize': CustomEvent<{
62
+ key: string;
63
+ width: number;
64
+ colIndex: number;
65
+ }>;
66
+ /** A whole column was selected. `colIndex` is the visible index. */
67
+ 'column-select': CustomEvent<{
68
+ colIndex: number;
69
+ key: string | undefined;
70
+ rowCount: number;
71
+ }>;
72
+ 'row-add': CustomEvent<{
73
+ row: DataRow;
74
+ index: number;
75
+ }>;
76
+ 'row-delete': CustomEvent<{
77
+ indices: number[];
78
+ rows: DataRow[];
79
+ }>;
80
+ /** A row was dragged to a new place. Both are data indices. */
81
+ 'row-reorder': CustomEvent<{
82
+ from: number;
83
+ to: number;
84
+ }>;
85
+ /** Enter on a non-editable cell — "activate this row". `index` is the data index, `col` the visible column index. */
86
+ 'row-activate': CustomEvent<{
87
+ row: DataRow;
88
+ index: number;
89
+ col: number;
90
+ key: string | undefined;
91
+ }>;
92
+ /** `batchUpdate()` applied its changes. */
93
+ 'batch-update': CustomEvent<{
94
+ changes: CellChange[];
95
+ }>;
96
+ /** The active cell moved; `null` when there is none. */
97
+ 'cell-select': CustomEvent<CellPosition | null>;
98
+ /** `row` is the data index, `col` the visible column index. */
99
+ 'cell-edit-start': CustomEvent<{
100
+ row: number;
101
+ col: number;
102
+ key: string;
103
+ value: unknown;
104
+ }>;
105
+ 'cell-edit-commit': CustomEvent<{
106
+ row: number;
107
+ col: number;
108
+ key: string;
109
+ oldValue: unknown;
110
+ newValue: unknown;
111
+ }>;
112
+ /** `row` and `col` are visible positions. */
113
+ 'cell-edit-cancel': CustomEvent<{
114
+ row: number;
115
+ col: number;
116
+ }>;
117
+ /** An edit was rejected (type, `required` or `validator`); the value was not written. */
118
+ 'validation-error': CustomEvent<{
119
+ row: number;
120
+ col: number;
121
+ key: string;
122
+ value: unknown;
123
+ error: string;
124
+ }>;
125
+ 'comment-change': CustomEvent<{
126
+ dataIndex: number;
127
+ colKey: string;
128
+ text: string | null;
129
+ }>;
130
+ /** Rows were imported from a file; `count` is how many. */
131
+ 'data-import': CustomEvent<{
132
+ count: number;
133
+ }>;
134
+ 'sort-change': CustomEvent<{
135
+ criteria: SortCriteria[];
136
+ }>;
137
+ /**
138
+ * Right-click on a body cell. Cancelable: `preventDefault()` keeps the built-in menu closed so you
139
+ * can show your own. `row` is the data index, `col` the visible column index.
140
+ */
141
+ 'context-menu': CustomEvent<{
142
+ x: number;
143
+ y: number;
144
+ row: number;
145
+ col: number;
146
+ key: string;
147
+ value: unknown;
148
+ rowData: DataRow | undefined;
149
+ }>;
150
+ 'header-context-menu': CustomEvent<{
151
+ key: string;
152
+ label: string;
153
+ x: number;
154
+ y: number;
155
+ }>;
156
+ /** Text reached the clipboard (`range` is in visible positions). */
157
+ 'clipboard-copy': CustomEvent<{
158
+ range: CellRange;
159
+ text: string;
160
+ }>;
161
+ /** Like `clipboard-copy`, and the range was cleared afterwards. */
162
+ 'clipboard-cut': CustomEvent<{
163
+ range: CellRange;
164
+ text: string;
165
+ }>;
166
+ /** A paste was applied. `addedRows` counts rows appended to fit it. */
167
+ 'clipboard-paste': CustomEvent<{
168
+ changes: Array<CellChange & {
169
+ col: number;
170
+ }>;
171
+ addedRows: number;
172
+ }>;
173
+ /** The clipboard refused a copy or paste (permission, focus, no secure context). */
174
+ 'clipboard-error': CustomEvent<{
175
+ action: 'copy' | 'paste';
176
+ error: unknown;
177
+ }>;
178
+ /** The fill handle wrote `cells` (data row index and key). */
179
+ 'fill-handle-apply': CustomEvent<{
180
+ sourceRange: CellRange;
181
+ targetRange: CellRange;
182
+ cells: Array<{
183
+ dataRow: number;
184
+ key: string;
185
+ oldValue: unknown;
186
+ newValue: unknown;
187
+ }>;
188
+ }>;
189
+ /** Replace or replace-all from the find panel. `row` is the data index, `col` the column key. */
190
+ 'find-replace': CustomEvent<{
191
+ type: 'replace' | 'replace-all';
192
+ cells: Array<{
193
+ row: number;
194
+ col: string;
195
+ oldValue: unknown;
196
+ newValue: unknown;
197
+ }>;
198
+ }>;
199
+ }