@jielga/tmdatagrid 2.0.0-beta.2 → 2.0.0-beta.21

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.
Files changed (150) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1664 -632
  3. package/dist/index.js +5226 -3223
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/anatomy.md +102 -0
  7. package/docs/cell-selection.md +154 -0
  8. package/docs/column-layout.md +204 -0
  9. package/docs/columns.md +262 -0
  10. package/docs/components.md +304 -0
  11. package/docs/editing.md +603 -0
  12. package/docs/editors.md +250 -0
  13. package/docs/export.md +326 -0
  14. package/docs/filtering.md +358 -0
  15. package/docs/getting-started.md +123 -0
  16. package/docs/grouping.md +165 -0
  17. package/docs/loading-and-empty.md +92 -0
  18. package/docs/localization.md +79 -0
  19. package/docs/menu.md +143 -0
  20. package/docs/pagination.md +144 -0
  21. package/docs/persistence.md +111 -0
  22. package/docs/portfolio-rebalancer.md +94 -0
  23. package/docs/query-builder.md +175 -0
  24. package/docs/quick-search.md +83 -0
  25. package/docs/row-details.md +113 -0
  26. package/docs/row-interaction.md +148 -0
  27. package/docs/row-pinning.md +132 -0
  28. package/docs/row-selection.md +134 -0
  29. package/docs/row-styling.md +133 -0
  30. package/docs/scrolling.md +111 -0
  31. package/docs/server-query.md +246 -0
  32. package/docs/server-side.md +206 -0
  33. package/docs/sorting.md +101 -0
  34. package/docs/styling.md +126 -0
  35. package/docs/summary-row.md +76 -0
  36. package/docs/testing.md +309 -0
  37. package/docs/toolbar.md +161 -0
  38. package/docs/use-tm-data-grid.md +361 -0
  39. package/package.json +21 -45
  40. package/skills/appearance/SKILL.md +70 -17
  41. package/skills/cell-selection/SKILL.md +70 -76
  42. package/skills/columns/SKILL.md +131 -32
  43. package/skills/data/SKILL.md +100 -23
  44. package/skills/editing/SKILL.md +217 -96
  45. package/skills/editing/references/common-mistakes.md +111 -24
  46. package/skills/editing/references/editing-api.md +63 -39
  47. package/skills/editing/references/editors-and-validation.md +77 -19
  48. package/skills/filtering/SKILL.md +148 -40
  49. package/skills/getting-started/SKILL.md +18 -16
  50. package/skills/grouping/SKILL.md +32 -15
  51. package/skills/options/SKILL.md +39 -9
  52. package/skills/rows/SKILL.md +22 -18
  53. package/skills/server-side/SKILL.md +170 -17
  54. package/skills/testing/SKILL.md +10 -7
  55. package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +7 -19
  56. package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +7 -1
  57. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +39 -23
  58. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +106 -38
  59. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  60. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  61. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +4 -4
  62. package/src/components/TMDataGridDraftActions.tsx +307 -0
  63. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +58 -50
  64. package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +150 -115
  65. package/src/components/TMDataGridExportPicker.module.css +77 -0
  66. package/src/components/TMDataGridExportPicker.tsx +234 -0
  67. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  68. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  69. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +7 -5
  70. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  71. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  72. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -13
  73. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +4 -3
  74. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +10 -0
  75. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +100 -28
  76. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  77. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  78. package/src/components/TMDataGridMenu.tsx +354 -0
  79. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +12 -7
  80. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +90 -67
  81. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +678 -156
  82. package/src/components/TMDataGridToolbar.module.css +21 -0
  83. package/src/components/TMDataGridToolbar.tsx +181 -0
  84. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  85. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  86. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  87. package/src/components/editors/TMDataGridNumberEditor.tsx +70 -0
  88. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  89. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  90. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +17 -31
  91. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  92. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  93. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  94. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  95. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  96. package/src/components/filters/controlLayout.ts +32 -0
  97. package/src/components/filters/filterControlFor.ts +65 -0
  98. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  99. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  100. package/src/components/useHideableColumns.ts +52 -0
  101. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  102. package/src/{tmdatagrid/core → core}/capabilities.ts +14 -6
  103. package/src/{tmdatagrid/core → core}/columnOptions.ts +46 -0
  104. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  105. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  106. package/src/core/controlledState.ts +179 -0
  107. package/src/core/controlledStateSync.ts +108 -0
  108. package/src/core/deletedRows.ts +34 -0
  109. package/src/core/dom.ts +74 -0
  110. package/src/core/editEngine.ts +2476 -0
  111. package/src/{tmdatagrid/core → core}/editorFocus.ts +8 -4
  112. package/src/core/export.ts +843 -0
  113. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  114. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  115. package/src/core/filterSurface.ts +99 -0
  116. package/src/{tmdatagrid/core → core}/labels.ts +66 -8
  117. package/src/{tmdatagrid/core → core}/labelsSv.ts +26 -3
  118. package/src/core/pageReset.ts +120 -0
  119. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  120. package/src/core/resizePreview.ts +141 -0
  121. package/src/core/summary.ts +59 -0
  122. package/src/core/useSettledTableState.ts +36 -0
  123. package/src/{tmdatagrid/index.ts → index.ts} +75 -12
  124. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +734 -135
  125. package/src/useTMDataGridExport.ts +78 -0
  126. package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -162
  127. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  128. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  129. package/src/tmdatagrid/components/TMDataGridToolbar.module.css +0 -12
  130. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -162
  131. package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +0 -40
  132. package/src/tmdatagrid/core/cellExport.ts +0 -320
  133. package/src/tmdatagrid/core/editEngine.ts +0 -1006
  134. package/src/tmdatagrid/core/summary.ts +0 -35
  135. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  136. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  137. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  138. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  141. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  142. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  143. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  144. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/grouping.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
@@ -0,0 +1,179 @@
1
+ import type { TableState } from "@tanstack/react-table";
2
+ import type { TMDataGridFeatures } from "../useTMDataGrid";
3
+
4
+ type GridState = TableState<TMDataGridFeatures>;
5
+
6
+ /**
7
+ * Support for TanStack's `state` option (controlled state).
8
+ *
9
+ * table-core re-reads `options.state` on every render (`useTable` calls
10
+ * `setOptions` while rendering) and writes any slice whose value is not
11
+ * identical to the table's current value into the table's atom. The write
12
+ * publishes the store and re-renders the consumer, so a slice object built in
13
+ * the render body causes an infinite render loop.
14
+ *
15
+ * {@link stabilizeControlledState} prevents the loop by forwarding the
16
+ * previous render's value for a slice whose contents are unchanged.
17
+ * {@link findFrozenStateSlices} detects a controlled slice without its
18
+ * `onXChange` callback, which the grid cannot write to.
19
+ */
20
+
21
+ /**
22
+ * The `onXChange` option that owns each controlled slice - every slice v9
23
+ * defines. `globalFilterFn` is not among them: in this beta it is a table
24
+ * option, not state.
25
+ */
26
+ export const CONTROLLED_SLICE_HANDLERS = {
27
+ columnFilters: "onColumnFiltersChange",
28
+ columnOrder: "onColumnOrderChange",
29
+ columnPinning: "onColumnPinningChange",
30
+ columnResizing: "onColumnResizingChange",
31
+ columnSizing: "onColumnSizingChange",
32
+ columnVisibility: "onColumnVisibilityChange",
33
+ expanded: "onExpandedChange",
34
+ globalFilter: "onGlobalFilterChange",
35
+ grouping: "onGroupingChange",
36
+ pagination: "onPaginationChange",
37
+ rowPinning: "onRowPinningChange",
38
+ rowSelection: "onRowSelectionChange",
39
+ sorting: "onSortingChange",
40
+ } as const satisfies Partial<Record<keyof GridState, string>>;
41
+
42
+ export type TMDataGridControlledSlice = keyof typeof CONTROLLED_SLICE_HANDLERS;
43
+
44
+ /** The options {@link findFrozenStateSlices} reads - a subset of `TableOptions`. */
45
+ type ControlledOptions = {
46
+ state?: Partial<GridState>;
47
+ /**
48
+ * The v9 way of owning a slice: an atom the table writes through directly.
49
+ * It outranks `state`, so a slice backed by one is never frozen.
50
+ */
51
+ atoms?: Record<string, unknown>;
52
+ } & Partial<
53
+ Record<(typeof CONTROLLED_SLICE_HANDLERS)[TMDataGridControlledSlice], unknown>
54
+ >;
55
+
56
+ const hasOwn = (value: object, key: string): boolean =>
57
+ Object.prototype.hasOwnProperty.call(value, key);
58
+
59
+ /**
60
+ * A plain data object: `Object.prototype` or null prototype. table-core builds
61
+ * its state maps with `Object.create(null)`, so null prototypes must compare
62
+ * as plain objects.
63
+ */
64
+ function isDataObject(value: unknown): value is Record<string, unknown> {
65
+ if (typeof value !== "object" || value === null) return false;
66
+ const proto = Object.getPrototypeOf(value);
67
+ return proto === Object.prototype || proto === null;
68
+ }
69
+
70
+ /**
71
+ * Structural equality for state values.
72
+ *
73
+ * `Date`s compare by time; the built-in date filters hold them in filter
74
+ * values. `Map`s and class instances compare by identity, so a controlled
75
+ * slice containing one rebuilt each render reads as changed on every render
76
+ * and re-renders the table. Use primitives, plain objects, arrays or `Date`s
77
+ * in controlled state.
78
+ */
79
+ export function sameStateValue(a: unknown, b: unknown): boolean {
80
+ if (Object.is(a, b)) return true;
81
+ if (a instanceof Date || b instanceof Date) {
82
+ return (
83
+ a instanceof Date && b instanceof Date && a.getTime() === b.getTime()
84
+ );
85
+ }
86
+ if (Array.isArray(a) || Array.isArray(b)) {
87
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
88
+ return (
89
+ a.length === b.length &&
90
+ a.every((item, index) => sameStateValue(item, b[index]))
91
+ );
92
+ }
93
+ if (!isDataObject(a) || !isDataObject(b)) return false;
94
+ const keys = Object.keys(a);
95
+ if (keys.length !== Object.keys(b).length) return false;
96
+ return keys.every((key) => hasOwn(b, key) && sameStateValue(a[key], b[key]));
97
+ }
98
+
99
+ /**
100
+ * Removes keys whose value is `undefined`, as in
101
+ * `state: { sorting: cond ? sorting : undefined }`.
102
+ *
103
+ * TanStack's sync writes every present key into the slice's atom, `undefined`
104
+ * included, which breaks everything that reads the slice. A key set to
105
+ * `undefined` is treated as not controlled: the key is removed and the slice
106
+ * falls back to the table's own state. Identity is preserved when there is
107
+ * nothing to remove; the stabilizer compares by it.
108
+ */
109
+ export function withoutUndefinedSlices(
110
+ state: Partial<GridState> | undefined,
111
+ ): Partial<GridState> | undefined {
112
+ if (state === undefined) return state;
113
+ if (!Object.values(state).includes(undefined)) return state;
114
+ const compacted: Record<string, unknown> = {};
115
+ for (const [key, value] of Object.entries(state)) {
116
+ if (value !== undefined) compacted[key] = value;
117
+ }
118
+ return compacted as Partial<GridState>;
119
+ }
120
+
121
+ /**
122
+ * Returns `next` with every slice that structurally equals its counterpart in
123
+ * `previous` replaced by the previous object, so table-core's identity-based
124
+ * sync finds nothing to write. Returns `previous` when nothing changed.
125
+ */
126
+ export function stabilizeControlledState(
127
+ next: Partial<GridState> | undefined,
128
+ previous: Partial<GridState> | undefined,
129
+ ): Partial<GridState> | undefined {
130
+ if (next === undefined || previous === undefined || next === previous) {
131
+ return next;
132
+ }
133
+
134
+ const keys = Object.keys(next);
135
+ const stabilized: Record<string, unknown> = {};
136
+ let changed = keys.length !== Object.keys(previous).length;
137
+
138
+ for (const key of keys) {
139
+ const nextValue = (next as Record<string, unknown>)[key];
140
+ const previousValue = (previous as Record<string, unknown>)[key];
141
+ if (hasOwn(previous, key) && sameStateValue(nextValue, previousValue)) {
142
+ // Unchanged contents: keep the object the table already holds.
143
+ stabilized[key] = previousValue;
144
+ } else {
145
+ stabilized[key] = nextValue;
146
+ changed = true;
147
+ }
148
+ }
149
+
150
+ return changed ? (stabilized as Partial<GridState>) : previous;
151
+ }
152
+
153
+ /**
154
+ * Controlled slices without a write path: `state.x` is set but no `onXChange`
155
+ * was passed and no external atom owns the slice. TanStack routes every write
156
+ * through the callback, so such a slice cannot change. Reported so the hook
157
+ * can warn in development; `initialState` is the option for a starting value.
158
+ */
159
+ export function findFrozenStateSlices(
160
+ options: ControlledOptions,
161
+ ): Array<{ slice: TMDataGridControlledSlice; handler: string }> {
162
+ const state = options.state;
163
+ if (state === undefined) return [];
164
+
165
+ return (
166
+ Object.entries(CONTROLLED_SLICE_HANDLERS) as Array<
167
+ [TMDataGridControlledSlice, string]
168
+ >
169
+ )
170
+ .filter(
171
+ ([slice, handler]) =>
172
+ // Undefined-valued keys are removed before the table sees them; the
173
+ // slice is not controlled.
174
+ (state as Record<string, unknown>)[slice] !== undefined &&
175
+ (options as Record<string, unknown>)[handler] === undefined &&
176
+ options.atoms?.[slice] === undefined,
177
+ )
178
+ .map(([slice, handler]) => ({ slice, handler }));
179
+ }
@@ -0,0 +1,108 @@
1
+ import type { ReadonlyStore, Store } from "@tanstack/store";
2
+
3
+ /**
4
+ * Timing repair for TanStack's controlled-state sync.
5
+ *
6
+ * `useTable` calls `table.setOptions` from its render body, and that syncs
7
+ * `options.state` into the table's atoms. An atom write publishes the store
8
+ * synchronously, so every component subscribed to `table.store` schedules a
9
+ * setState while the consumer's component is still rendering - which React
10
+ * reports as "Cannot update a component (X) while rendering a different
11
+ * component (Y)", pointing at the consumer's component rather than at the
12
+ * grid.
13
+ *
14
+ * The write has to stay where it is: the atoms must hold the controlled value
15
+ * before the table builds its row models for that render. Only the
16
+ * notification is early, so it moves to a microtask. `getSnapshot` already
17
+ * returns the new value, so a component reads the same state either way; the
18
+ * subscription is only what schedules a re-render for the components the
19
+ * consumer's own render does not reach.
20
+ *
21
+ * Scope and assumptions. Only `table.store` is patched: a subscription made
22
+ * on `table.atoms.*` or `table.baseAtoms.*` keeps the synchronous timing.
23
+ * The patch leans on three table-core 9.0.0-beta.21 internals - the store is
24
+ * created once in `constructTable`, `useTable` returns a shallow copy that
25
+ * shares it, and the sync runs inside `setOptions` during render. The
26
+ * publish test in controlledState.test.tsx fails loudly if any of them
27
+ * moves; it is the guard for this module, not a redundant check. The
28
+ * intended end state - owning the controlled slices through `options.atoms`,
29
+ * which removes the render-time sync entirely - is in the backlog.
30
+ */
31
+
32
+ type Observer = ((value: unknown) => void) | { next?: (value: unknown) => void };
33
+
34
+ type Subscribe = (observer: Observer) => { unsubscribe: () => void };
35
+
36
+ /**
37
+ * True while `useTable` is syncing controlled state inside a render pass.
38
+ * Process-global, so while one grid is inside its `useTable` call a publish
39
+ * on any patched store is deferred too - harmless, since nothing else runs
40
+ * during a synchronous render pass.
41
+ */
42
+ let syncing = false;
43
+
44
+ /**
45
+ * Called immediately before the `useTable` call that performs the sync.
46
+ *
47
+ * The microtask is the backstop that bounds the flag to the current task: if
48
+ * `useTable` throws and an error boundary unmounts the grid, nothing after
49
+ * the call runs, and without it every patched store would keep deferring its
50
+ * notifications for the life of the page.
51
+ */
52
+ export function beginControlledStateSync(): void {
53
+ syncing = true;
54
+ queueMicrotask(() => {
55
+ syncing = false;
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Called immediately after it - the prompt clear; correctness rests on the
61
+ * backstop above. Not a `finally` around the call, because hooks inside
62
+ * `try` opt the whole hook out of the React Compiler.
63
+ */
64
+ export function endControlledStateSync(): void {
65
+ syncing = false;
66
+ }
67
+
68
+ const patched = new WeakSet<object>();
69
+
70
+ /**
71
+ * Wraps a table store's `subscribe` so notifications raised during the sync
72
+ * are delivered in a microtask. Idempotent, and installed on the store object
73
+ * itself rather than on a copy of the table - rows hold the original, so
74
+ * `row.table.store` has to be the patched one.
75
+ */
76
+ export function deferControlledStateSyncPublishes<TState>(
77
+ store: Store<TState> | ReadonlyStore<TState>,
78
+ ): void {
79
+ if (patched.has(store)) return;
80
+ patched.add(store);
81
+
82
+ const subscribe = store.subscribe.bind(store) as Subscribe;
83
+
84
+ (store as unknown as { subscribe: Subscribe }).subscribe = (observer) => {
85
+ const next =
86
+ typeof observer === "function" ? observer : observer.next?.bind(observer);
87
+ if (next === undefined) return subscribe(observer);
88
+
89
+ let live = true;
90
+ const subscription = subscribe((value) => {
91
+ if (!syncing) {
92
+ next(value);
93
+ return;
94
+ }
95
+ queueMicrotask(() => {
96
+ // The subscriber may have unmounted between the write and the flush.
97
+ if (live) next(value);
98
+ });
99
+ });
100
+
101
+ return {
102
+ unsubscribe: () => {
103
+ live = false;
104
+ subscription.unsubscribe();
105
+ },
106
+ };
107
+ };
108
+ }
@@ -0,0 +1,34 @@
1
+ import type { RowData } from "@tanstack/react-table";
2
+ import type { TMDataGridTable } from "../useTMDataGrid";
3
+
4
+ /**
5
+ * Which rows of a table carry a deletion mark, for readers that hold the
6
+ * table and nothing else - `exportGrid`, `buildGridCellMatrix` and the
7
+ * export data they build on. The marks live in the edit engine, which those
8
+ * readers cannot reach; the hook registers the engine's answer per table
9
+ * instead. A table built without the grid has no marks.
10
+ */
11
+ // Keyed on the store, not the table: `useTable` hands out a fresh shallow
12
+ // copy of the table on every render, and every copy shares the one store.
13
+ const sources = new WeakMap<object, (rowId: string) => boolean>();
14
+
15
+ /** Registers the answer for one table; returns the unregister. */
16
+ export function registerDeletedRows<TData extends RowData>(
17
+ table: TMDataGridTable<TData>,
18
+ isRowDeleted: (rowId: string) => boolean,
19
+ ): () => void {
20
+ sources.set(table.store, isRowDeleted);
21
+ return () => {
22
+ if (sources.get(table.store) === isRowDeleted) {
23
+ sources.delete(table.store);
24
+ }
25
+ };
26
+ }
27
+
28
+ /** Whether the row is marked for deletion under `editing.draft`. */
29
+ export function isRowMarkedDeleted<TData extends RowData>(
30
+ table: TMDataGridTable<TData>,
31
+ rowId: string,
32
+ ): boolean {
33
+ return sources.get(table.store)?.(rowId) ?? false;
34
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * DOM checks that survive a second realm.
3
+ *
4
+ * The grid can be rendered through a portal into a window opened with
5
+ * `window.open`. That window is a separate JavaScript realm: its nodes and
6
+ * events are instances of *its* `Node`, `Element`, `HTMLInputElement` and
7
+ * `MouseEvent`, not the opener's. A plain `x instanceof HTMLInputElement`
8
+ * is therefore false for an input sitting in the grid's own window, and the
9
+ * check silently takes the wrong branch.
10
+ *
11
+ * Every helper here answers the same question either by duck-typing, or by
12
+ * taking the constructor from the realm the value itself came from.
13
+ */
14
+
15
+ /**
16
+ * The window a node lives in - the realm to take its constructors from.
17
+ *
18
+ * A document made by `createHTMLDocument` has no window of its own, so the
19
+ * global one is the fallback.
20
+ */
21
+ export function windowOf(node: Node): Window & typeof globalThis {
22
+ return node.ownerDocument?.defaultView ?? window;
23
+ }
24
+
25
+ /**
26
+ * Whether a value is a DOM node.
27
+ *
28
+ * Duck-typed on `nodeType`, because `instanceof Node` is false for a node
29
+ * from another realm.
30
+ */
31
+ export function isNode(value: unknown): value is Node {
32
+ return (
33
+ typeof value === "object" &&
34
+ value !== null &&
35
+ typeof (value as Node).nodeType === "number"
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Whether a value is an element - a node whose `nodeType` is 1
41
+ * (`Node.ELEMENT_NODE`), read as a number so no realm's constant is needed.
42
+ */
43
+ export function isElement(value: unknown): value is Element {
44
+ return isNode(value) && value.nodeType === 1;
45
+ }
46
+
47
+ /** Whether a value is an HTML element, asked of its own realm's constructor. */
48
+ export function isHTMLElement(value: unknown): value is HTMLElement {
49
+ return isElement(value) && value instanceof windowOf(value).HTMLElement;
50
+ }
51
+
52
+ /** Whether a value is an `<input>`, asked of its own realm's constructor. */
53
+ export function isInputElement(value: unknown): value is HTMLInputElement {
54
+ return isElement(value) && value instanceof windowOf(value).HTMLInputElement;
55
+ }
56
+
57
+ /** Whether a value is a `<button>`, asked of its own realm's constructor. */
58
+ export function isButtonElement(value: unknown): value is HTMLButtonElement {
59
+ return isElement(value) && value instanceof windowOf(value).HTMLButtonElement;
60
+ }
61
+
62
+ /**
63
+ * Whether an event is a mouse event, asked of the constructor in the event's
64
+ * own window.
65
+ *
66
+ * A browser-dispatched event carries that window in `view`; one built by hand
67
+ * may not, and its target still knows the window.
68
+ */
69
+ export function isMouseEvent(event: Event): event is MouseEvent {
70
+ const view =
71
+ (event as UIEvent).view ??
72
+ (isNode(event.target) ? windowOf(event.target) : window);
73
+ return event instanceof (view as Window & typeof globalThis).MouseEvent;
74
+ }