@jielga/tmdatagrid 2.0.0-beta.9 → 2.0.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.
Files changed (157) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1281 -768
  3. package/dist/index.js +4607 -3250
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/adding-rows.md +132 -0
  7. package/docs/anatomy.md +119 -0
  8. package/docs/card-view.md +108 -0
  9. package/docs/cell-selection.md +194 -0
  10. package/docs/column-layout.md +182 -0
  11. package/docs/column-menu.md +66 -0
  12. package/docs/columns.md +269 -0
  13. package/docs/components.md +311 -0
  14. package/docs/draft-store.md +242 -0
  15. package/docs/editing.md +303 -0
  16. package/docs/editors.md +250 -0
  17. package/docs/export.md +319 -0
  18. package/docs/filtering.md +362 -0
  19. package/docs/getting-started.md +123 -0
  20. package/docs/grouping.md +165 -0
  21. package/docs/loading-and-empty.md +92 -0
  22. package/docs/localization.md +79 -0
  23. package/docs/menu.md +143 -0
  24. package/docs/migrating-to-2.md +163 -0
  25. package/docs/pagination.md +144 -0
  26. package/docs/persistence.md +114 -0
  27. package/docs/portfolio-rebalancer.md +94 -0
  28. package/docs/query-builder.md +179 -0
  29. package/docs/quick-search.md +84 -0
  30. package/docs/row-details.md +115 -0
  31. package/docs/row-interaction.md +149 -0
  32. package/docs/row-pinning.md +132 -0
  33. package/docs/row-selection.md +136 -0
  34. package/docs/row-styling.md +133 -0
  35. package/docs/scrolling.md +112 -0
  36. package/docs/server-query.md +246 -0
  37. package/docs/server-side.md +206 -0
  38. package/docs/sorting.md +101 -0
  39. package/docs/styling.md +126 -0
  40. package/docs/summary-row.md +76 -0
  41. package/docs/testing.md +744 -0
  42. package/docs/toolbar.md +161 -0
  43. package/docs/use-tm-data-grid.md +361 -0
  44. package/package.json +22 -46
  45. package/skills/appearance/SKILL.md +72 -19
  46. package/skills/cell-selection/SKILL.md +49 -48
  47. package/skills/columns/SKILL.md +125 -70
  48. package/skills/columns/references/columns-api.md +59 -0
  49. package/skills/data/SKILL.md +112 -18
  50. package/skills/editing/SKILL.md +76 -42
  51. package/skills/editing/references/common-mistakes.md +77 -69
  52. package/skills/editing/references/editing-api.md +25 -20
  53. package/skills/editing/references/editors-and-validation.md +80 -18
  54. package/skills/filtering/SKILL.md +155 -41
  55. package/skills/getting-started/SKILL.md +116 -16
  56. package/skills/grouping/SKILL.md +31 -16
  57. package/skills/migrating-to-2/SKILL.md +244 -0
  58. package/skills/options/SKILL.md +24 -12
  59. package/skills/rows/SKILL.md +22 -18
  60. package/skills/rows/references/rows-api.md +10 -6
  61. package/skills/server-side/SKILL.md +170 -17
  62. package/skills/testing/SKILL.md +150 -32
  63. package/skills/testing-components/SKILL.md +230 -0
  64. package/skills/testing-editing/SKILL.md +240 -0
  65. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
  66. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
  67. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  68. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  69. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +5 -51
  70. package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
  71. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
  72. package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
  73. package/src/components/TMDataGridExportPicker.module.css +77 -0
  74. package/src/components/TMDataGridExportPicker.tsx +234 -0
  75. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  76. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  77. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
  78. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  79. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  80. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
  81. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
  82. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
  83. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
  84. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  85. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  86. package/src/components/TMDataGridMenu.tsx +357 -0
  87. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
  88. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
  89. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
  90. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  91. package/src/components/TMDataGridToolbar.tsx +181 -0
  92. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  93. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  94. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  95. package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
  96. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  97. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  98. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
  99. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  100. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  101. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  102. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  103. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  104. package/src/components/filters/controlLayout.ts +32 -0
  105. package/src/components/filters/filterControlFor.ts +65 -0
  106. package/src/components/generatedColumns.tsx +187 -0
  107. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  108. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  109. package/src/components/useHideableColumns.ts +52 -0
  110. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  111. package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
  112. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  113. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  114. package/src/core/controlledStateSync.ts +108 -0
  115. package/src/core/deletedRows.ts +34 -0
  116. package/src/core/dom.ts +74 -0
  117. package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
  118. package/src/core/export.ts +704 -0
  119. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  120. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  121. package/src/core/filterSurface.ts +99 -0
  122. package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
  123. package/src/{tmdatagrid/core → core}/labels.ts +51 -6
  124. package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
  125. package/src/core/pageReset.ts +120 -0
  126. package/src/core/pagination.ts +81 -0
  127. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  128. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  129. package/src/{tmdatagrid/index.ts → index.ts} +69 -35
  130. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
  131. package/src/useTMDataGridExport.ts +78 -0
  132. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  133. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  134. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
  135. package/src/tmdatagrid/core/cellExport.ts +0 -320
  136. /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
  137. /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
  138. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  141. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  142. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  143. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  144. /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  151. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  152. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  153. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  154. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  155. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  156. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  157. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -1,6 +1,7 @@
1
1
  import type { Row } from "@tanstack/react-table";
2
2
  import {
3
3
  getDefaultOperator,
4
+ getOperatorsForType,
4
5
  type TMDataGridColumnType,
5
6
  type TMDataGridFilterOperator,
6
7
  } from "./filterOperators";
@@ -37,17 +38,36 @@ export function getColumnType(column: ColumnLike): TMDataGridColumnType {
37
38
  return column.columnDef.meta?.type ?? "string";
38
39
  }
39
40
 
41
+ /**
42
+ * The operators this column offers: the type's list, narrowed to
43
+ * `meta.filter.operators` when the column declares one. The type's order is
44
+ * kept so the menu reads the same on every column; an operator the type does
45
+ * not offer is dropped, and an allowlist that leaves nothing falls back to the
46
+ * type's full list rather than an empty menu.
47
+ */
48
+ export function getColumnOperators(
49
+ column: ColumnLike,
50
+ ): readonly TMDataGridFilterOperator[] {
51
+ const offered = getOperatorsForType(getColumnType(column));
52
+ const allowed = column.columnDef.meta?.filter?.operators;
53
+ if (!allowed) return offered;
54
+ const narrowed = offered.filter((operator) => allowed.includes(operator));
55
+ return narrowed.length > 0 ? narrowed : offered;
56
+ }
57
+
40
58
  /**
41
59
  * The operator a fresh filter on this column starts with -
42
- * `meta.filter.defaultOperator`, else the type's default.
60
+ * `meta.filter.defaultOperator`, else the type's default where the column
61
+ * offers it, else the first operator it does offer.
43
62
  */
44
63
  export function getColumnDefaultOperator(
45
64
  column: ColumnLike,
46
65
  ): TMDataGridFilterOperator {
47
- return (
48
- column.columnDef.meta?.filter?.defaultOperator ??
49
- getDefaultOperator(getColumnType(column))
50
- );
66
+ const declared = column.columnDef.meta?.filter?.defaultOperator;
67
+ if (declared) return declared;
68
+ const offered = getColumnOperators(column);
69
+ const typeDefault = getDefaultOperator(getColumnType(column));
70
+ return offered.includes(typeDefault) ? typeDefault : offered[0];
51
71
  }
52
72
 
53
73
  /**
@@ -139,3 +159,22 @@ export function isColumnReorderable(column: ColumnLike): boolean {
139
159
  if (column.columnDef.meta?.enableOrdering === false) return false;
140
160
  return column.parent === undefined;
141
161
  }
162
+
163
+ /** The list length from which a column chooser shows its search box. */
164
+ export const COLUMN_SEARCH_MIN = 6;
165
+
166
+ /** `"auto"` shows the search box from `COLUMN_SEARCH_MIN` columns. */
167
+ export type TMDataGridColumnSearchable = boolean | "auto";
168
+
169
+ /**
170
+ * Whether a column list shows a search box. A handful of columns is read at
171
+ * a glance, so `"auto"` keeps the input out of the way until the list is long
172
+ * enough to need one.
173
+ */
174
+ export function showColumnSearch(
175
+ searchable: TMDataGridColumnSearchable,
176
+ count: number,
177
+ ): boolean {
178
+ if (searchable === "auto") return count >= COLUMN_SEARCH_MIN;
179
+ return searchable;
180
+ }
@@ -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` and the export data it builds on.
7
+ * The marks live in the edit engine, which those readers cannot reach; the
8
+ * hook registers the engine's answer per table instead. A table built without
9
+ * 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
+ }