@toolbox-web/grid 2.2.0 → 2.4.0

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 (124) hide show
  1. package/all.d.ts +1 -0
  2. package/all.js +2 -2
  3. package/all.js.map +1 -1
  4. package/custom-elements.json +21 -2
  5. package/index.js +1 -1
  6. package/index.js.map +1 -1
  7. package/lib/core/adapter-conformance.d.ts +23 -0
  8. package/lib/core/grid.d.ts +15 -16
  9. package/lib/core/internal/diagnostics.d.ts +5 -1
  10. package/lib/core/internal/dom-builder.d.ts +0 -25
  11. package/lib/core/internal/drag-drop-registry.d.ts +66 -0
  12. package/lib/core/internal/render-scheduler.d.ts +9 -8
  13. package/lib/core/plugin/base-plugin.d.ts +23 -0
  14. package/lib/core/plugin/plugin-manager.d.ts +9 -0
  15. package/lib/core/types.d.ts +139 -58
  16. package/lib/features/registry.js.map +1 -1
  17. package/lib/features/reorder-rows.d.ts +3 -3
  18. package/lib/features/reorder-rows.js +1 -1
  19. package/lib/features/reorder-rows.js.map +1 -1
  20. package/lib/features/row-drag-drop.d.ts +9 -0
  21. package/lib/features/row-drag-drop.js +2 -0
  22. package/lib/features/row-drag-drop.js.map +1 -0
  23. package/lib/features/server-side.js.map +1 -1
  24. package/lib/plugins/clipboard/index.js +1 -1
  25. package/lib/plugins/clipboard/index.js.map +1 -1
  26. package/lib/plugins/column-virtualization/index.js +1 -1
  27. package/lib/plugins/column-virtualization/index.js.map +1 -1
  28. package/lib/plugins/context-menu/index.js +1 -1
  29. package/lib/plugins/context-menu/index.js.map +1 -1
  30. package/lib/plugins/editing/index.js +1 -1
  31. package/lib/plugins/editing/index.js.map +1 -1
  32. package/lib/plugins/export/ExportPlugin.d.ts +89 -0
  33. package/lib/plugins/export/index.d.ts +3 -2
  34. package/lib/plugins/export/index.js +1 -1
  35. package/lib/plugins/export/index.js.map +1 -1
  36. package/lib/plugins/export/types.d.ts +30 -0
  37. package/lib/plugins/filtering/index.js +1 -1
  38. package/lib/plugins/filtering/index.js.map +1 -1
  39. package/lib/plugins/grouping-columns/index.js +1 -1
  40. package/lib/plugins/grouping-columns/index.js.map +1 -1
  41. package/lib/plugins/grouping-rows/index.js +2 -2
  42. package/lib/plugins/grouping-rows/index.js.map +1 -1
  43. package/lib/plugins/master-detail/index.js +1 -1
  44. package/lib/plugins/master-detail/index.js.map +1 -1
  45. package/lib/plugins/multi-sort/index.js +1 -1
  46. package/lib/plugins/multi-sort/index.js.map +1 -1
  47. package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +1 -1
  48. package/lib/plugins/pinned-columns/index.js +1 -1
  49. package/lib/plugins/pinned-columns/index.js.map +1 -1
  50. package/lib/plugins/pinned-columns/types.d.ts +7 -0
  51. package/lib/plugins/pinned-rows/index.js +1 -1
  52. package/lib/plugins/pinned-rows/index.js.map +1 -1
  53. package/lib/plugins/pivot/index.js +1 -1
  54. package/lib/plugins/pivot/index.js.map +1 -1
  55. package/lib/plugins/print/index.js +1 -1
  56. package/lib/plugins/print/index.js.map +1 -1
  57. package/lib/plugins/reorder-columns/ReorderPlugin.d.ts +13 -1
  58. package/lib/plugins/reorder-columns/column-drag.d.ts +7 -1
  59. package/lib/plugins/reorder-columns/index.js +1 -1
  60. package/lib/plugins/reorder-columns/index.js.map +1 -1
  61. package/lib/plugins/reorder-columns/types.d.ts +12 -0
  62. package/lib/plugins/reorder-rows/RowReorderPlugin.d.ts +14 -156
  63. package/lib/plugins/reorder-rows/index.d.ts +10 -4
  64. package/lib/plugins/reorder-rows/index.js +1 -1
  65. package/lib/plugins/reorder-rows/index.js.map +1 -1
  66. package/lib/plugins/reorder-rows/types.d.ts +9 -86
  67. package/lib/plugins/responsive/index.js +1 -1
  68. package/lib/plugins/responsive/index.js.map +1 -1
  69. package/lib/plugins/row-drag-drop/RowDragDropPlugin.d.ts +106 -0
  70. package/lib/plugins/row-drag-drop/index.d.ts +9 -0
  71. package/lib/plugins/row-drag-drop/index.js +2 -0
  72. package/lib/plugins/row-drag-drop/index.js.map +1 -0
  73. package/lib/plugins/row-drag-drop/types.d.ts +255 -0
  74. package/lib/plugins/selection/index.js +1 -1
  75. package/lib/plugins/selection/index.js.map +1 -1
  76. package/lib/plugins/selection/types.d.ts +8 -0
  77. package/lib/plugins/server-side/ServerSidePlugin.d.ts +13 -0
  78. package/lib/plugins/server-side/datasource-types.d.ts +54 -7
  79. package/lib/plugins/server-side/datasource.d.ts +10 -2
  80. package/lib/plugins/server-side/index.d.ts +1 -1
  81. package/lib/plugins/server-side/index.js +1 -1
  82. package/lib/plugins/server-side/index.js.map +1 -1
  83. package/lib/plugins/server-side/types.d.ts +1 -1
  84. package/lib/plugins/shared/drag-drop-protocol.d.ts +98 -0
  85. package/lib/plugins/tooltip/index.js +1 -1
  86. package/lib/plugins/tooltip/index.js.map +1 -1
  87. package/lib/plugins/tree/TreePlugin.d.ts +19 -6
  88. package/lib/plugins/tree/index.js +1 -1
  89. package/lib/plugins/tree/index.js.map +1 -1
  90. package/lib/plugins/undo-redo/index.js +1 -1
  91. package/lib/plugins/undo-redo/index.js.map +1 -1
  92. package/lib/plugins/visibility/VisibilityPlugin.d.ts +11 -1
  93. package/lib/plugins/visibility/index.js +1 -1
  94. package/lib/plugins/visibility/index.js.map +1 -1
  95. package/package.json +1 -1
  96. package/public.d.ts +1 -0
  97. package/umd/grid.all.umd.js +1 -1
  98. package/umd/grid.all.umd.js.map +1 -1
  99. package/umd/grid.umd.js +1 -1
  100. package/umd/grid.umd.js.map +1 -1
  101. package/umd/plugins/clipboard.umd.js +1 -1
  102. package/umd/plugins/clipboard.umd.js.map +1 -1
  103. package/umd/plugins/export.umd.js +1 -1
  104. package/umd/plugins/export.umd.js.map +1 -1
  105. package/umd/plugins/multi-sort.umd.js +1 -1
  106. package/umd/plugins/multi-sort.umd.js.map +1 -1
  107. package/umd/plugins/pinned-columns.umd.js +1 -1
  108. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  109. package/umd/plugins/print.umd.js +1 -1
  110. package/umd/plugins/print.umd.js.map +1 -1
  111. package/umd/plugins/reorder-columns.umd.js +1 -1
  112. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  113. package/umd/plugins/reorder-rows.umd.js +1 -1
  114. package/umd/plugins/reorder-rows.umd.js.map +1 -1
  115. package/umd/plugins/row-drag-drop.umd.js +2 -0
  116. package/umd/plugins/row-drag-drop.umd.js.map +1 -0
  117. package/umd/plugins/selection.umd.js +1 -1
  118. package/umd/plugins/selection.umd.js.map +1 -1
  119. package/umd/plugins/server-side.umd.js +1 -1
  120. package/umd/plugins/server-side.umd.js.map +1 -1
  121. package/umd/plugins/tree.umd.js +1 -1
  122. package/umd/plugins/tree.umd.js.map +1 -1
  123. package/umd/plugins/visibility.umd.js +1 -1
  124. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -0,0 +1,23 @@
1
+ import { FrameworkAdapter } from './types';
2
+ /**
3
+ * Method names that are technically optional on {@link FrameworkAdapter}
4
+ * but are actually consumed by the grid core. An adapter that skips any
5
+ * of these silently drops the matching feature for its framework users.
6
+ */
7
+ export declare const CORE_CONSUMED_ADAPTER_METHODS: ReadonlyArray<keyof FrameworkAdapter>;
8
+ export interface AdapterConformanceReport {
9
+ name: string;
10
+ implemented: (keyof FrameworkAdapter)[];
11
+ missing: (keyof FrameworkAdapter)[];
12
+ }
13
+ /**
14
+ * Inspect an adapter and report which core-consumed hooks are implemented
15
+ * vs. missing. Performs a shallow `typeof adapter[name] === 'function'`
16
+ * check — works for class instances, prototypes, and plain objects.
17
+ */
18
+ export declare function reportAdapterConformance(adapter: FrameworkAdapter): AdapterConformanceReport;
19
+ /**
20
+ * Assert that an adapter implements every core-consumed hook.
21
+ * Throws listing the missing method names. Intended for unit tests.
22
+ */
23
+ export declare function assertAdapterConformance(adapter: FrameworkAdapter): void;
@@ -1,3 +1,4 @@
1
+ import { RenderPhase } from './internal/render-scheduler';
1
2
  import { ShellState } from './internal/shell';
2
3
  import { AfterCellRenderContext, AfterRowRenderContext, CellMouseEvent } from './plugin';
3
4
  import { BaseGridPlugin } from './plugin/base-plugin';
@@ -511,7 +512,7 @@ export declare class DataGridElement<T = any> extends HTMLElement implements Int
511
512
  /** Updates ARIA row/col counts. Delegates to aria.ts module. */
512
513
  _updateAriaCounts(rowCount: number, colCount: number): void;
513
514
  /** @internal Request a render at the given phase through the scheduler. */
514
- _requestSchedulerPhase(phase: number, source: string): void;
515
+ _requestSchedulerPhase(phase: RenderPhase, source: string): void;
515
516
  /** @internal Plugin-specific row height override. */
516
517
  _getPluginRowHeight(row: T, index: number): number | undefined;
517
518
  /** @internal Let plugins adjust the virtual start index backwards. */
@@ -1222,15 +1223,21 @@ export declare class DataGridElement<T = any> extends HTMLElement implements Int
1222
1223
  *
1223
1224
  * // Later, restore the state
1224
1225
  * const saved = localStorage.getItem('gridState');
1225
- * if (saved) {
1226
- * grid.columnState = JSON.parse(saved);
1227
- * }
1226
+ * if (saved) grid.applyColumnState(JSON.parse(saved));
1228
1227
  * ```
1229
1228
  */
1230
1229
  getColumnState(): GridColumnState;
1231
1230
  /**
1232
- * Set the column state, restoring all saved preferences.
1233
- * Can be set before or after grid initialization.
1231
+ * Get the current column state.
1232
+ * Alias for `getColumnState()` for property-style read access.
1233
+ * @group State Persistence
1234
+ */
1235
+ get columnState(): GridColumnState | undefined;
1236
+ /**
1237
+ * Apply a previously saved column state, restoring column order, widths,
1238
+ * visibility, sort, and any plugin-contributed state. Can be called
1239
+ * before or after grid initialization — pre-init calls are deferred and
1240
+ * applied during setup.
1234
1241
  *
1235
1242
  * @group State Persistence
1236
1243
  * @fires column-state-change - Emitted after state is applied (if grid is initialized)
@@ -1239,18 +1246,10 @@ export declare class DataGridElement<T = any> extends HTMLElement implements Int
1239
1246
  * // Restore saved state on page load
1240
1247
  * const grid = queryGrid('tbw-grid');
1241
1248
  * const saved = localStorage.getItem('myGridState');
1242
- * if (saved) {
1243
- * grid.columnState = JSON.parse(saved);
1244
- * }
1249
+ * if (saved) grid.applyColumnState(JSON.parse(saved));
1245
1250
  * ```
1246
1251
  */
1247
- set columnState(state: GridColumnState | undefined);
1248
- /**
1249
- * Get the current column state.
1250
- * Alias for `getColumnState()` for property-style access.
1251
- * @group State Persistence
1252
- */
1253
- get columnState(): GridColumnState | undefined;
1252
+ applyColumnState(state: GridColumnState | undefined): void;
1254
1253
  /**
1255
1254
  * Get the current single-column sort state.
1256
1255
  *
@@ -64,8 +64,12 @@ export declare const MISSING_DEPENDENCY: "TBW020";
64
64
  export declare const OPTIONAL_DEPENDENCY: "TBW021";
65
65
  /** Two loaded plugins are incompatible. */
66
66
  export declare const INCOMPATIBLE_PLUGINS: "TBW022";
67
+ /** Two plugin instances resolve to the same canonical name (alias collapse). */
68
+ export declare const PLUGIN_ALIAS_COLLAPSE: "TBW023";
67
69
  /** Error thrown inside a plugin event handler. */
68
70
  export declare const PLUGIN_EVENT_ERROR: "TBW024";
71
+ /** Conflicting config values found while merging alias-collapsed plugin instances. */
72
+ export declare const PLUGIN_ALIAS_CONFIG_CONFLICT: "TBW025";
69
73
  /** Feature was re-registered (overwritten). */
70
74
  export declare const FEATURE_REREGISTERED: "TBW030";
71
75
  /** Feature configured but not imported. */
@@ -136,7 +140,7 @@ export declare const DATASOURCE_CHILD_FETCH_ERROR: "TBW141";
136
140
  export declare const DATASOURCE_NO_CHILD_HANDLER: "TBW142";
137
141
  /** ServerSidePlugin: concurrent request limit reached, request deferred. */
138
142
  export declare const DATASOURCE_THROTTLED: "TBW143";
139
- export type DiagnosticCode = typeof MISSING_PLUGIN | typeof MISSING_PLUGIN_CONFIG | typeof CONFIG_RULE_ERROR | typeof CONFIG_RULE_WARN | typeof MISSING_DEPENDENCY | typeof OPTIONAL_DEPENDENCY | typeof INCOMPATIBLE_PLUGINS | typeof PLUGIN_EVENT_ERROR | typeof FEATURE_REREGISTERED | typeof FEATURE_NOT_IMPORTED | typeof FEATURE_MISSING_DEP | typeof MISSING_ROW_ID | typeof ROW_NOT_FOUND | typeof INVALID_COLUMN_WIDTH | typeof ROW_CLASS_ERROR | typeof CELL_CLASS_ERROR | typeof FORMAT_ERROR | typeof VIEW_MOUNT_ERROR | typeof VIEW_DISPATCH_ERROR | typeof TOOL_PANEL_MISSING_ATTR | typeof NO_TOOL_PANELS | typeof TOOL_PANEL_NOT_FOUND | typeof TOOL_PANEL_DUPLICATE | typeof HEADER_CONTENT_DUPLICATE | typeof TOOLBAR_CONTENT_DUPLICATE | typeof EDITOR_MOUNT_ERROR | typeof PRINT_IN_PROGRESS | typeof PRINT_NO_GRID | typeof PRINT_FAILED | typeof PRINT_DUPLICATE_ID | typeof CLIPBOARD_FAILED | typeof MISSING_BREAKPOINT | typeof TRANSACTION_IN_PROGRESS | typeof NO_TRANSACTION | typeof COLUMN_GROUP_NO_ID | typeof COLUMN_GROUPS_CONFLICT | typeof STYLE_EXTRACT_FAILED | typeof STYLE_NOT_FOUND | typeof INVALID_ATTRIBUTE_JSON | typeof DATASOURCE_FETCH_ERROR | typeof DATASOURCE_CHILD_FETCH_ERROR | typeof DATASOURCE_NO_CHILD_HANDLER | typeof DATASOURCE_THROTTLED;
143
+ export type DiagnosticCode = typeof MISSING_PLUGIN | typeof MISSING_PLUGIN_CONFIG | typeof CONFIG_RULE_ERROR | typeof CONFIG_RULE_WARN | typeof MISSING_DEPENDENCY | typeof OPTIONAL_DEPENDENCY | typeof INCOMPATIBLE_PLUGINS | typeof PLUGIN_ALIAS_COLLAPSE | typeof PLUGIN_EVENT_ERROR | typeof PLUGIN_ALIAS_CONFIG_CONFLICT | typeof FEATURE_REREGISTERED | typeof FEATURE_NOT_IMPORTED | typeof FEATURE_MISSING_DEP | typeof MISSING_ROW_ID | typeof ROW_NOT_FOUND | typeof INVALID_COLUMN_WIDTH | typeof ROW_CLASS_ERROR | typeof CELL_CLASS_ERROR | typeof FORMAT_ERROR | typeof VIEW_MOUNT_ERROR | typeof VIEW_DISPATCH_ERROR | typeof TOOL_PANEL_MISSING_ATTR | typeof NO_TOOL_PANELS | typeof TOOL_PANEL_NOT_FOUND | typeof TOOL_PANEL_DUPLICATE | typeof HEADER_CONTENT_DUPLICATE | typeof TOOLBAR_CONTENT_DUPLICATE | typeof EDITOR_MOUNT_ERROR | typeof PRINT_IN_PROGRESS | typeof PRINT_NO_GRID | typeof PRINT_FAILED | typeof PRINT_DUPLICATE_ID | typeof CLIPBOARD_FAILED | typeof MISSING_BREAKPOINT | typeof TRANSACTION_IN_PROGRESS | typeof NO_TRANSACTION | typeof COLUMN_GROUP_NO_ID | typeof COLUMN_GROUPS_CONFLICT | typeof STYLE_EXTRACT_FAILED | typeof STYLE_NOT_FOUND | typeof INVALID_ATTRIBUTE_JSON | typeof DATASOURCE_FETCH_ERROR | typeof DATASOURCE_CHILD_FETCH_ERROR | typeof DATASOURCE_NO_CHILD_HANDLER | typeof DATASOURCE_THROTTLED;
140
144
  /**
141
145
  * Format a diagnostic message with prefix, code, and docs link.
142
146
  *
@@ -8,31 +8,6 @@
8
8
  *
9
9
  * Benchmark: DOM construction is ~2-3x faster than innerHTML for complex structures.
10
10
  */
11
- /**
12
- * Create an element with attributes and optional children.
13
- * Optimized helper that avoids repeated function calls.
14
- */
15
- export declare function createElement<K extends keyof HTMLElementTagNameMap>(tag: K, attrs?: Record<string, string>, children?: (Node | string | null | undefined)[]): HTMLElementTagNameMap[K];
16
- /**
17
- * Create a text node (shorthand).
18
- */
19
- export declare function text(content: string): Text;
20
- /**
21
- * Create an element with class (common pattern).
22
- */
23
- export declare function div(className?: string, attrs?: Record<string, string>): HTMLDivElement;
24
- /**
25
- * Create a button element.
26
- */
27
- export declare function button(className?: string, attrs?: Record<string, string>, content?: string | Node): HTMLButtonElement;
28
- /**
29
- * Append multiple children to a parent element.
30
- */
31
- export declare function appendChildren(parent: Element, children: (Node | null | undefined)[]): void;
32
- /**
33
- * Set multiple attributes on an element.
34
- */
35
- export declare function setAttrs(el: Element, attrs: Record<string, string | undefined>): void;
36
11
  /**
37
12
  * Clone the grid content structure.
38
13
  * Using template cloning is faster than createElement for nested structures.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Drag-Drop Registry
3
+ *
4
+ * Module-level singleton that holds live (WeakRef) references to row objects
5
+ * being dragged across grids in the same JS heap. Used by `RowDragDropPlugin`
6
+ * to recover live references on drop, avoiding any serialization cost when
7
+ * source and target grids share a heap.
8
+ *
9
+ * Lives in `core/internal/` (not in the plugin bundle) so that two copies of
10
+ * `@toolbox-web/grid` loaded via different entry points (e.g. `/all` vs
11
+ * per-plugin imports) cannot end up with two separate registries that silently
12
+ * fall back to JSON.
13
+ *
14
+ * Cross-window / cross-origin-iframe drags do NOT go through this registry —
15
+ * they fall back to `dataTransfer` JSON.
16
+ *
17
+ * @internal
18
+ */
19
+ /**
20
+ * Register a drag session's live row references.
21
+ *
22
+ * Object rows are stored as `WeakRef<object>`; primitive rows cannot be held
23
+ * weakly and would either need a sentinel (which corrupts the row value on
24
+ * recovery) or be omitted (which misaligns indices). We instead flag the
25
+ * session as containing primitives — `lookupDragSession()` then returns
26
+ * `undefined` so callers fall back to the JSON payload, which round-trips
27
+ * primitives losslessly.
28
+ *
29
+ * @param sessionId - Unique drag session identifier (typically a UUID).
30
+ * @param rows - Rows being dragged.
31
+ * @param meta - Optional opaque metadata to associate with the session.
32
+ */
33
+ export declare function registerDragSession(sessionId: string, rows: readonly unknown[], meta?: unknown): void;
34
+ /**
35
+ * Look up the live row references for a drag session.
36
+ *
37
+ * Returns `undefined` when the caller MUST fall back to the JSON payload:
38
+ * - the session is unknown,
39
+ * - any registered reference has been garbage-collected (partial recovery
40
+ * would silently corrupt cross-grid moves), or
41
+ * - the session contains primitive rows (which cannot be held weakly).
42
+ */
43
+ export declare function lookupDragSession<T = unknown>(sessionId: string): T[] | undefined;
44
+ /**
45
+ * Get the metadata associated with a drag session, if any.
46
+ */
47
+ export declare function getDragSessionMeta<M = unknown>(sessionId: string): M | undefined;
48
+ /**
49
+ * Clear a drag session from the registry.
50
+ * Should be called from `dragend` on the source grid.
51
+ */
52
+ export declare function clearDragSession(sessionId: string): void;
53
+ /**
54
+ * @internal Test-only — clear all sessions.
55
+ */
56
+ export declare function _clearAllDragSessions(): void;
57
+ /**
58
+ * Generate a unique drag session identifier.
59
+ *
60
+ * Used as a Map key to recover live row references for in-process drags; this
61
+ * is **not** a security context. Uses `crypto.randomUUID()` when available
62
+ * (modern browsers in secure contexts, Node 19+, Bun) and falls back to
63
+ * `crypto.getRandomValues()` for older non-secure contexts. `Math.random()`
64
+ * is intentionally avoided to keep CodeQL clean.
65
+ */
66
+ export declare function newDragSessionId(): string;
@@ -5,20 +5,21 @@ import { InternalGrid } from '../types';
5
5
  *
6
6
  * @category Plugin Development
7
7
  */
8
- export declare enum RenderPhase {
8
+ export declare const RenderPhase: {
9
9
  /** Lightweight style updates only (plugin afterRender hooks) */
10
- STYLE = 1,
10
+ readonly STYLE: 1;
11
11
  /** Virtual window recalculation (includes STYLE) */
12
- VIRTUALIZATION = 2,
12
+ readonly VIRTUALIZATION: 2;
13
13
  /** Header re-render (includes VIRTUALIZATION) */
14
- HEADER = 3,
14
+ readonly HEADER: 3;
15
15
  /** Row model rebuild (includes HEADER) */
16
- ROWS = 4,
16
+ readonly ROWS: 4;
17
17
  /** Column processing (includes ROWS) */
18
- COLUMNS = 5,
18
+ readonly COLUMNS: 5;
19
19
  /** Full render including config merge (includes COLUMNS) */
20
- FULL = 6
21
- }
20
+ readonly FULL: 6;
21
+ };
22
+ export type RenderPhase = (typeof RenderPhase)[keyof typeof RenderPhase];
22
23
  /**
23
24
  * @internal Scheduler now takes InternalGrid directly — no callback interface needed.
24
25
  */
@@ -368,6 +368,29 @@ export declare abstract class BaseGridPlugin<TConfig = unknown> implements GridP
368
368
  */
369
369
  protected get defaultConfig(): Partial<TConfig>;
370
370
  constructor(config?: Partial<TConfig>);
371
+ /**
372
+ * Merge user-supplied configuration from other plugin instances into this
373
+ * plugin's `userConfig`. Used by `PluginManager.attachAll()`'s alias-collapse
374
+ * pre-pass: when a consumer instantiates the same plugin under multiple
375
+ * names (e.g. `RowReorderPlugin` and `RowDragDropPlugin`, which are aliases
376
+ * after V2.x), only one instance is attached, and the other instances'
377
+ * configs are folded in via this method.
378
+ *
379
+ * Merge rules (shallow):
380
+ *
381
+ * - Same key, **same value** → silent.
382
+ * - Same key, **different values** (incl. function vs function) → throws
383
+ * a diagnostic naming the conflicting key. Conflicting callbacks
384
+ * (`canDrag`, `canDrop`, etc.) cannot be reconciled automatically.
385
+ * - **Disjoint keys** → merged cleanly.
386
+ *
387
+ * Subclasses may override to implement custom deep-merge logic, but should
388
+ * call `super.mergeConfigsFrom(others)` to inherit the conflict-detection.
389
+ *
390
+ * @internal Plugin infrastructure (used by `PluginManager.attachAll`).
391
+ * @throws Error with diagnostic code `TBW025` when configs conflict.
392
+ */
393
+ mergeConfigsFrom(others: readonly BaseGridPlugin<TConfig>[]): void;
371
394
  /**
372
395
  * Called when the plugin is attached to a grid.
373
396
  * Override to set up event listeners, initialize state, etc.
@@ -43,6 +43,15 @@ export declare class PluginManager {
43
43
  constructor(grid: GridElement);
44
44
  /**
45
45
  * Attach all plugins from the config.
46
+ *
47
+ * Runs an alias-collapse pre-pass before per-instance attach: when multiple
48
+ * plugin instances resolve to the same canonical constructor (e.g.
49
+ * `RowReorderPlugin` and `RowDragDropPlugin`, which point to the same class
50
+ * after V2.x), only the first instance is attached, and the others' user
51
+ * configs are folded into it via `BaseGridPlugin.mergeConfigsFrom()`.
52
+ *
53
+ * Skipped duplicates trigger a one-time `console.warn` with diagnostic code
54
+ * `TBW023` (suppressed in production builds via `import.meta.env.PROD`).
46
55
  */
47
56
  attachAll(plugins: BaseGridPlugin[]): void;
48
57
  /**
@@ -1,3 +1,4 @@
1
+ import { RenderPhase } from './internal/render-scheduler';
1
2
  import { ShellState } from './internal/shell';
2
3
  import { RowPosition } from './internal/virtualization';
3
4
  import { AfterCellRenderContext, AfterRowRenderContext, CellMouseEvent } from './plugin/types';
@@ -219,18 +220,29 @@ export interface PublicGrid<T = any> {
219
220
  */
220
221
  getColumnState?(): GridColumnState;
221
222
  /**
222
- * Set/restore the column state.
223
- * Can be set before or after grid initialization.
223
+ * Read the current column state. Property-style accessor that mirrors
224
+ * {@link PublicGrid.getColumnState}. To restore state, use
225
+ * {@link PublicGrid.applyColumnState}.
224
226
  *
225
227
  * @example
226
228
  * ```typescript
227
- * const saved = localStorage.getItem('gridState');
228
- * if (saved) {
229
- * grid.columnState = JSON.parse(saved);
230
- * }
229
+ * const snapshot = grid.columnState;
231
230
  * ```
232
231
  */
233
232
  columnState?: GridColumnState;
233
+ /**
234
+ * Apply a previously saved column state, restoring column order, widths,
235
+ * visibility, sort, and any plugin-contributed state. Can be called before
236
+ * or after grid initialization — pre-init calls are deferred and applied
237
+ * during setup.
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * const saved = localStorage.getItem('gridState');
242
+ * if (saved) grid.applyColumnState(JSON.parse(saved));
243
+ * ```
244
+ */
245
+ applyColumnState?(state: GridColumnState | undefined): void;
234
246
  /**
235
247
  * Get the current sort state.
236
248
  *
@@ -546,7 +558,7 @@ export interface InternalGrid<T = any> extends PublicGrid<T>, GridConfig<T> {
546
558
  _invalidateVisibleColumnsCache(): void;
547
559
  /** @internal */ _renderVisibleRows(start: number, end: number, epoch?: number): void;
548
560
  /** @internal */ _updateAriaCounts(totalRows: number, totalCols: number): void;
549
- /** @internal */ _requestSchedulerPhase(phase: number, source: string): void;
561
+ /** @internal */ _requestSchedulerPhase(phase: RenderPhase, source: string): void;
550
562
  /** @internal */ _rebuildRowIdMap(): void;
551
563
  /** @internal */ _emitDataChange(): void;
552
564
  /** @internal */ _getPluginRowHeight(row: T, index: number): number | undefined;
@@ -874,7 +886,58 @@ export interface BaseColumnConfig<TRow = any, TValue = any> {
874
886
  * ```
875
887
  */
876
888
  format?: (value: TValue, row: TRow) => string;
877
- /** Arbitrary extra metadata */
889
+ /**
890
+ * Marks this column as a **system / utility column** — a column that exists to
891
+ * support grid behaviour rather than to display user data.
892
+ *
893
+ * Built-in plugins set this on the columns they synthesize (selection checkbox,
894
+ * row-reorder drag handle, master-detail / tree / row-grouping expander). You can
895
+ * also set it on columns you author yourself when you want them to behave the same
896
+ * way — for example, a row-action menu column, a status indicator, or any
897
+ * developer-defined "system" column that should be visible in the grid only.
898
+ *
899
+ * Setting `utility: true` excludes the column from:
900
+ *
901
+ * - **Visibility panel** — not listed in the show/hide UI
902
+ * - **Column reorder** — header drag-drop and visibility-panel drag treat it as locked
903
+ * (equivalent to `lockPosition: true`)
904
+ * - **Print** — hidden by `PrintPlugin` during print
905
+ * - **Clipboard copy** — skipped by `ClipboardPlugin`
906
+ * - **Export** (CSV / JSON / Excel) — skipped by `ExportPlugin`
907
+ * - **Range / row selection** — clicks land on the column but selection ignores it
908
+ * - **Filter UI** — no filter button rendered, no filter model entry
909
+ *
910
+ * The column is still rendered in the grid and still receives `cellRenderer` /
911
+ * `viewRenderer` / `headerRenderer` callbacks — it is "hidden from the system,
912
+ * visible in the grid".
913
+ *
914
+ * Convention: name the field with a `__`-prefix (e.g. `__actions`) so it cannot
915
+ * collide with a real data field.
916
+ *
917
+ * @example A custom row-actions column
918
+ * ```ts
919
+ * {
920
+ * field: '__actions',
921
+ * header: '',
922
+ * width: 80,
923
+ * utility: true, // excluded from print, export, reorder, visibility, etc.
924
+ * resizable: false,
925
+ * sortable: false,
926
+ * filterable: false,
927
+ * viewRenderer: ({ row }) => createActionsButton(row),
928
+ * }
929
+ * ```
930
+ */
931
+ utility?: boolean;
932
+ /**
933
+ * Arbitrary extra metadata for application use.
934
+ *
935
+ * @remarks
936
+ * **Do not use `meta` for grid-recognized flags.** Properties like `lockPosition`,
937
+ * `lockVisible`, `lockPinning`, `pinned`, `utility`, and `checkboxColumn` are first-class
938
+ * augmented properties on `ColumnConfig` itself. Using `meta.<flag>` for any of them is
939
+ * deprecated and only kept as a runtime fallback for back-compat.
940
+ */
878
941
  meta?: Record<string, unknown>;
879
942
  }
880
943
  /**
@@ -1860,16 +1923,24 @@ export interface RowGroupRenderConfig {
1860
1923
  * - A built-in aggregator name: `'sum'`, `'avg'`, `'min'`, `'max'`, `'count'`
1861
1924
  * - A custom function that calculates the aggregate value
1862
1925
  *
1926
+ * Aggregators are not declared on a column directly — they are configured per
1927
+ * field on the consumer (e.g. {@link RowGroupRenderConfig.aggregators} for
1928
+ * group rows, keyed by column field name).
1929
+ *
1863
1930
  * @example
1864
1931
  * ```typescript
1865
- * // Built-in aggregator
1866
- * { field: 'amount', aggregator: 'sum' }
1932
+ * // Built-in aggregator on a group row config
1933
+ * { aggregators: { amount: 'sum' } }
1867
1934
  *
1868
1935
  * // Custom aggregator function
1869
- * { field: 'price', aggregator: (rows, field) => {
1870
- * const values = rows.map(r => r[field]).filter(v => v != null);
1871
- * return values.length ? Math.max(...values) : null;
1872
- * }}
1936
+ * {
1937
+ * aggregators: {
1938
+ * price: (rows, field) => {
1939
+ * const values = rows.map((r) => (r as any)[field]).filter((v) => v != null);
1940
+ * return values.length ? Math.max(...values as number[]) : null;
1941
+ * },
1942
+ * },
1943
+ * }
1873
1944
  * ```
1874
1945
  *
1875
1946
  * @see {@link RowGroupRenderConfig} for using aggregators in group rows
@@ -2305,32 +2376,46 @@ export interface GridConfig<TRow = any> {
2305
2376
  */
2306
2377
  animation?: AnimationConfig;
2307
2378
  /**
2308
- * Custom sort handler for full control over sorting behavior.
2379
+ * Custom sort handler for the entire grid.
2309
2380
  *
2310
- * When provided, this handler is called instead of the built-in sorting logic.
2311
- * Enables custom sorting algorithms, server-side sorting, or plugin-specific sorting.
2381
+ * :::caution
2382
+ * **Prefer {@link BaseColumnConfig.sortComparator} over `sortHandler`.**
2383
+ *
2384
+ * `sortHandler` is a low-level escape hatch with significant limitations:
2385
+ * - Only consulted by the **single-column** sort path (core header click,
2386
+ * `TreePlugin` per-level sort, `ServerSidePlugin` `sortMode: 'local'`).
2387
+ * - **Bypassed entirely** when `MultiSortPlugin` is loaded.
2388
+ * - Owns ALL columns at once — your handler must implement field/direction
2389
+ * dispatch and null handling for every sortable column itself.
2390
+ *
2391
+ * For per-column custom sort logic, use {@link BaseColumnConfig.sortComparator}
2392
+ * instead. It is honored by every sort code path in the grid (core, multi-sort,
2393
+ * tree, server-side) and is composable across columns.
2394
+ *
2395
+ * For server-side sort, prefer {@link ServerSideConfig.dataSource} — the
2396
+ * `sortModel` is shipped to your `getRows` handler so the backend can return
2397
+ * pre-sorted blocks.
2398
+ * :::
2399
+ *
2400
+ * Use `sortHandler` only when you need to replace the grid's sort engine
2401
+ * wholesale (e.g. integrating a third-party sort library that operates on
2402
+ * the full row array, or routing every sort through a single async pipeline).
2312
2403
  *
2313
2404
  * The handler receives:
2314
2405
  * - `rows`: Current row array to sort
2315
2406
  * - `sortState`: Sort field and direction (1 = asc, -1 = desc)
2316
2407
  * - `columns`: Column configurations (for accessing sortComparator)
2317
2408
  *
2318
- * Return the sorted array (sync) or a Promise that resolves to the sorted array (async).
2319
- * For server-side sorting, return a Promise that resolves when data is fetched.
2409
+ * Return the sorted array (sync) or a Promise that resolves to it (async).
2320
2410
  *
2321
2411
  * @example
2322
2412
  * ```ts
2323
- * // Custom stable sort
2324
- * sortHandler: (rows, state, cols) => {
2325
- * return stableSort(rows, (a, b) => compare(a[state.field], b[state.field]) * state.direction);
2326
- * }
2327
- *
2328
- * // Server-side sorting
2329
- * sortHandler: async (rows, state) => {
2330
- * const response = await fetch(`/api/data?sort=${state.field}&dir=${state.direction}`);
2331
- * return response.json();
2332
- * }
2413
+ * // Replace the entire client-side sort engine with a custom stable sort
2414
+ * sortHandler: (rows, state) => stableSort(rows, state.field, state.direction);
2333
2415
  * ```
2416
+ *
2417
+ * @see {@link BaseColumnConfig.sortComparator} — recommended per-column override
2418
+ * @see {@link ServerSideConfig.dataSource} — recommended server-side sort path
2334
2419
  */
2335
2420
  sortHandler?: SortHandler<TRow>;
2336
2421
  /**
@@ -2510,8 +2595,17 @@ export interface SortState {
2510
2595
  /**
2511
2596
  * Custom sort handler function signature.
2512
2597
  *
2513
- * Enables full control over sorting behavior including server-side sorting,
2514
- * custom algorithms, or multi-column sorting.
2598
+ * :::caution
2599
+ * **Prefer {@link BaseColumnConfig.sortComparator} over `SortHandler`.**
2600
+ * `sortHandler` is bypassed by `MultiSortPlugin` and only sees the single
2601
+ * active sort field. For per-column custom sort logic that survives every
2602
+ * sort code path (core, multi-sort, tree, server-side), set `sortComparator`
2603
+ * on the relevant columns instead. For server-side sort, use
2604
+ * {@link ServerSideConfig.dataSource}.
2605
+ * :::
2606
+ *
2607
+ * Use `SortHandler` only when you need to replace the grid's sort engine
2608
+ * wholesale.
2515
2609
  *
2516
2610
  * @param rows - Current row array to sort
2517
2611
  * @param sortState - Sort field and direction
@@ -2520,32 +2614,17 @@ export interface SortState {
2520
2614
  *
2521
2615
  * @example
2522
2616
  * ```typescript
2523
- * // Custom client-side sort with locale awareness
2524
- * const localeSortHandler: SortHandler<Employee> = (rows, state, cols) => {
2525
- * const col = cols.find(c => c.field === state.field);
2526
- * return [...rows].sort((a, b) => {
2527
- * const aVal = String(a[state.field] ?? '');
2528
- * const bVal = String(b[state.field] ?? '');
2529
- * return aVal.localeCompare(bVal) * state.direction;
2530
- * });
2617
+ * // Replace the built-in sort with a third-party stable sort library
2618
+ * const customSortHandler: SortHandler<Employee> = (rows, state) => {
2619
+ * return thirdPartyStableSort(rows, state.field, state.direction);
2531
2620
  * };
2532
2621
  *
2533
- * // Server-side sorting
2534
- * const serverSortHandler: SortHandler<Employee> = async (rows, state) => {
2535
- * const response = await fetch(
2536
- * `/api/employees?sortBy=${state.field}&dir=${state.direction}`
2537
- * );
2538
- * return response.json();
2539
- * };
2540
- *
2541
- * grid.gridConfig = {
2542
- * sortHandler: localeSortHandler,
2543
- * };
2622
+ * grid.gridConfig = { sortHandler: customSortHandler };
2544
2623
  * ```
2545
2624
  *
2546
2625
  * @see {@link SortState} for the sort state object
2547
- * @see {@link GridConfig.sortHandler} for configuring the handler
2548
- * @see {@link BaseColumnConfig.sortComparator} for column-level comparators
2626
+ * @see {@link GridConfig.sortHandler} for configuring the handler (and its caveats)
2627
+ * @see {@link BaseColumnConfig.sortComparator} recommended per-column comparator
2549
2628
  */
2550
2629
  export type SortHandler<TRow = any> = (rows: TRow[], sortState: SortState, columns: ColumnConfig<TRow>[]) => TRow[] | Promise<TRow[]>;
2551
2630
  /**
@@ -3053,7 +3132,11 @@ export interface GridIcons {
3053
3132
  /** Print icon for print button. Default: '🖨️' */
3054
3133
  print?: IconValue;
3055
3134
  }
3056
- /** Default icons used when not overridden */
3135
+ /** Default icons used when not overridden. Most entries are short text/emoji;
3136
+ * `filter` and `filterActive` are empty strings because the actual rendering
3137
+ * is driven by the `--tbw-icon-filter[-active]-mask` CSS custom properties
3138
+ * (see `core/styles/variables.css`). Userland that wants an HTML/SVG fallback
3139
+ * via `gridConfig.icons.filter = '<svg…>'` is unaffected — that path overrides this default. */
3057
3140
  export declare const DEFAULT_GRID_ICONS: Required<GridIcons>;
3058
3141
  /**
3059
3142
  * Shell configuration for the grid's optional header bar and tool panels.
@@ -3280,9 +3363,7 @@ export interface HeaderContentDefinition {
3280
3363
  *
3281
3364
  * // Restore on page load
3282
3365
  * const saved = localStorage.getItem('gridState');
3283
- * if (saved) {
3284
- * grid.columnState = JSON.parse(saved);
3285
- * }
3366
+ * if (saved) grid.applyColumnState(JSON.parse(saved));
3286
3367
  *
3287
3368
  * // Example column state structure
3288
3369
  * const state: GridColumnState = {
@@ -3343,7 +3424,7 @@ export interface ColumnSortState {
3343
3424
  * localStorage.setItem('grid-state', JSON.stringify(state));
3344
3425
  *
3345
3426
  * // Restore state
3346
- * grid.columnState = JSON.parse(localStorage.getItem('grid-state'));
3427
+ * grid.applyColumnState(JSON.parse(localStorage.getItem('grid-state')));
3347
3428
  * ```
3348
3429
  *
3349
3430
  * @see {@link ColumnState} for individual column state
@@ -3838,7 +3919,7 @@ export interface DataGridEventMap<TRow = unknown> {
3838
3919
  *
3839
3920
  * // Restore on load
3840
3921
  * const saved = localStorage.getItem('grid-state');
3841
- * if (saved) grid.columnState = JSON.parse(saved);
3922
+ * if (saved) grid.applyColumnState(JSON.parse(saved));
3842
3923
  * ```
3843
3924
  *
3844
3925
  * @see {@link GridColumnState}