@toolbox-web/grid 2.3.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 (102) hide show
  1. package/all.d.ts +1 -0
  2. package/all.js +2 -2
  3. package/all.js.map +1 -1
  4. package/index.js +1 -1
  5. package/index.js.map +1 -1
  6. package/lib/core/grid.d.ts +2 -1
  7. package/lib/core/internal/diagnostics.d.ts +5 -1
  8. package/lib/core/internal/dom-builder.d.ts +0 -25
  9. package/lib/core/internal/drag-drop-registry.d.ts +66 -0
  10. package/lib/core/internal/render-scheduler.d.ts +9 -8
  11. package/lib/core/plugin/base-plugin.d.ts +23 -0
  12. package/lib/core/plugin/plugin-manager.d.ts +9 -0
  13. package/lib/core/types.d.ts +67 -46
  14. package/lib/features/registry.js.map +1 -1
  15. package/lib/features/reorder-rows.d.ts +3 -3
  16. package/lib/features/reorder-rows.js +1 -1
  17. package/lib/features/reorder-rows.js.map +1 -1
  18. package/lib/features/row-drag-drop.d.ts +9 -0
  19. package/lib/features/row-drag-drop.js +2 -0
  20. package/lib/features/row-drag-drop.js.map +1 -0
  21. package/lib/features/server-side.js.map +1 -1
  22. package/lib/plugins/clipboard/index.js +1 -1
  23. package/lib/plugins/clipboard/index.js.map +1 -1
  24. package/lib/plugins/column-virtualization/index.js +1 -1
  25. package/lib/plugins/column-virtualization/index.js.map +1 -1
  26. package/lib/plugins/context-menu/index.js +1 -1
  27. package/lib/plugins/context-menu/index.js.map +1 -1
  28. package/lib/plugins/editing/index.js +1 -1
  29. package/lib/plugins/editing/index.js.map +1 -1
  30. package/lib/plugins/export/ExportPlugin.d.ts +89 -0
  31. package/lib/plugins/export/index.d.ts +3 -2
  32. package/lib/plugins/export/index.js +1 -1
  33. package/lib/plugins/export/index.js.map +1 -1
  34. package/lib/plugins/export/types.d.ts +30 -0
  35. package/lib/plugins/filtering/index.js +1 -1
  36. package/lib/plugins/filtering/index.js.map +1 -1
  37. package/lib/plugins/grouping-columns/index.js +1 -1
  38. package/lib/plugins/grouping-columns/index.js.map +1 -1
  39. package/lib/plugins/grouping-rows/index.js +2 -2
  40. package/lib/plugins/grouping-rows/index.js.map +1 -1
  41. package/lib/plugins/master-detail/index.js +1 -1
  42. package/lib/plugins/master-detail/index.js.map +1 -1
  43. package/lib/plugins/multi-sort/index.js +1 -1
  44. package/lib/plugins/multi-sort/index.js.map +1 -1
  45. package/lib/plugins/pinned-columns/index.js +1 -1
  46. package/lib/plugins/pinned-columns/index.js.map +1 -1
  47. package/lib/plugins/pinned-rows/index.js +1 -1
  48. package/lib/plugins/pinned-rows/index.js.map +1 -1
  49. package/lib/plugins/pivot/index.js +1 -1
  50. package/lib/plugins/pivot/index.js.map +1 -1
  51. package/lib/plugins/print/index.js +1 -1
  52. package/lib/plugins/print/index.js.map +1 -1
  53. package/lib/plugins/reorder-columns/index.js +1 -1
  54. package/lib/plugins/reorder-columns/index.js.map +1 -1
  55. package/lib/plugins/reorder-rows/RowReorderPlugin.d.ts +14 -156
  56. package/lib/plugins/reorder-rows/index.d.ts +10 -4
  57. package/lib/plugins/reorder-rows/index.js +1 -1
  58. package/lib/plugins/reorder-rows/index.js.map +1 -1
  59. package/lib/plugins/reorder-rows/types.d.ts +9 -86
  60. package/lib/plugins/responsive/index.js +1 -1
  61. package/lib/plugins/responsive/index.js.map +1 -1
  62. package/lib/plugins/row-drag-drop/RowDragDropPlugin.d.ts +106 -0
  63. package/lib/plugins/row-drag-drop/index.d.ts +9 -0
  64. package/lib/plugins/row-drag-drop/index.js +2 -0
  65. package/lib/plugins/row-drag-drop/index.js.map +1 -0
  66. package/lib/plugins/row-drag-drop/types.d.ts +255 -0
  67. package/lib/plugins/selection/index.js +1 -1
  68. package/lib/plugins/selection/index.js.map +1 -1
  69. package/lib/plugins/server-side/ServerSidePlugin.d.ts +13 -0
  70. package/lib/plugins/server-side/datasource-types.d.ts +54 -7
  71. package/lib/plugins/server-side/datasource.d.ts +10 -2
  72. package/lib/plugins/server-side/index.d.ts +1 -1
  73. package/lib/plugins/server-side/index.js +1 -1
  74. package/lib/plugins/server-side/index.js.map +1 -1
  75. package/lib/plugins/server-side/types.d.ts +1 -1
  76. package/lib/plugins/shared/drag-drop-protocol.d.ts +98 -0
  77. package/lib/plugins/tooltip/index.js +1 -1
  78. package/lib/plugins/tooltip/index.js.map +1 -1
  79. package/lib/plugins/tree/TreePlugin.d.ts +19 -6
  80. package/lib/plugins/tree/index.js +1 -1
  81. package/lib/plugins/tree/index.js.map +1 -1
  82. package/lib/plugins/undo-redo/index.js +1 -1
  83. package/lib/plugins/undo-redo/index.js.map +1 -1
  84. package/lib/plugins/visibility/index.js +1 -1
  85. package/lib/plugins/visibility/index.js.map +1 -1
  86. package/package.json +1 -1
  87. package/umd/grid.all.umd.js +1 -1
  88. package/umd/grid.all.umd.js.map +1 -1
  89. package/umd/grid.umd.js +1 -1
  90. package/umd/grid.umd.js.map +1 -1
  91. package/umd/plugins/export.umd.js +1 -1
  92. package/umd/plugins/export.umd.js.map +1 -1
  93. package/umd/plugins/reorder-rows.umd.js +1 -1
  94. package/umd/plugins/reorder-rows.umd.js.map +1 -1
  95. package/umd/plugins/row-drag-drop.umd.js +2 -0
  96. package/umd/plugins/row-drag-drop.umd.js.map +1 -0
  97. package/umd/plugins/selection.umd.js +1 -1
  98. package/umd/plugins/selection.umd.js.map +1 -1
  99. package/umd/plugins/server-side.umd.js +1 -1
  100. package/umd/plugins/server-side.umd.js.map +1 -1
  101. package/umd/plugins/tree.umd.js +1 -1
  102. package/umd/plugins/tree.umd.js.map +1 -1
@@ -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. */
@@ -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';
@@ -557,7 +558,7 @@ export interface InternalGrid<T = any> extends PublicGrid<T>, GridConfig<T> {
557
558
  _invalidateVisibleColumnsCache(): void;
558
559
  /** @internal */ _renderVisibleRows(start: number, end: number, epoch?: number): void;
559
560
  /** @internal */ _updateAriaCounts(totalRows: number, totalCols: number): void;
560
- /** @internal */ _requestSchedulerPhase(phase: number, source: string): void;
561
+ /** @internal */ _requestSchedulerPhase(phase: RenderPhase, source: string): void;
561
562
  /** @internal */ _rebuildRowIdMap(): void;
562
563
  /** @internal */ _emitDataChange(): void;
563
564
  /** @internal */ _getPluginRowHeight(row: T, index: number): number | undefined;
@@ -1922,16 +1923,24 @@ export interface RowGroupRenderConfig {
1922
1923
  * - A built-in aggregator name: `'sum'`, `'avg'`, `'min'`, `'max'`, `'count'`
1923
1924
  * - A custom function that calculates the aggregate value
1924
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
+ *
1925
1930
  * @example
1926
1931
  * ```typescript
1927
- * // Built-in aggregator
1928
- * { field: 'amount', aggregator: 'sum' }
1932
+ * // Built-in aggregator on a group row config
1933
+ * { aggregators: { amount: 'sum' } }
1929
1934
  *
1930
1935
  * // Custom aggregator function
1931
- * { field: 'price', aggregator: (rows, field) => {
1932
- * const values = rows.map(r => r[field]).filter(v => v != null);
1933
- * return values.length ? Math.max(...values) : null;
1934
- * }}
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
+ * }
1935
1944
  * ```
1936
1945
  *
1937
1946
  * @see {@link RowGroupRenderConfig} for using aggregators in group rows
@@ -2367,32 +2376,46 @@ export interface GridConfig<TRow = any> {
2367
2376
  */
2368
2377
  animation?: AnimationConfig;
2369
2378
  /**
2370
- * Custom sort handler for full control over sorting behavior.
2379
+ * Custom sort handler for the entire grid.
2380
+ *
2381
+ * :::caution
2382
+ * **Prefer {@link BaseColumnConfig.sortComparator} over `sortHandler`.**
2371
2383
  *
2372
- * When provided, this handler is called instead of the built-in sorting logic.
2373
- * Enables custom sorting algorithms, server-side sorting, or plugin-specific sorting.
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).
2374
2403
  *
2375
2404
  * The handler receives:
2376
2405
  * - `rows`: Current row array to sort
2377
2406
  * - `sortState`: Sort field and direction (1 = asc, -1 = desc)
2378
2407
  * - `columns`: Column configurations (for accessing sortComparator)
2379
2408
  *
2380
- * Return the sorted array (sync) or a Promise that resolves to the sorted array (async).
2381
- * 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).
2382
2410
  *
2383
2411
  * @example
2384
2412
  * ```ts
2385
- * // Custom stable sort
2386
- * sortHandler: (rows, state, cols) => {
2387
- * return stableSort(rows, (a, b) => compare(a[state.field], b[state.field]) * state.direction);
2388
- * }
2389
- *
2390
- * // Server-side sorting
2391
- * sortHandler: async (rows, state) => {
2392
- * const response = await fetch(`/api/data?sort=${state.field}&dir=${state.direction}`);
2393
- * return response.json();
2394
- * }
2413
+ * // Replace the entire client-side sort engine with a custom stable sort
2414
+ * sortHandler: (rows, state) => stableSort(rows, state.field, state.direction);
2395
2415
  * ```
2416
+ *
2417
+ * @see {@link BaseColumnConfig.sortComparator} — recommended per-column override
2418
+ * @see {@link ServerSideConfig.dataSource} — recommended server-side sort path
2396
2419
  */
2397
2420
  sortHandler?: SortHandler<TRow>;
2398
2421
  /**
@@ -2572,8 +2595,17 @@ export interface SortState {
2572
2595
  /**
2573
2596
  * Custom sort handler function signature.
2574
2597
  *
2575
- * Enables full control over sorting behavior including server-side sorting,
2576
- * 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.
2577
2609
  *
2578
2610
  * @param rows - Current row array to sort
2579
2611
  * @param sortState - Sort field and direction
@@ -2582,32 +2614,17 @@ export interface SortState {
2582
2614
  *
2583
2615
  * @example
2584
2616
  * ```typescript
2585
- * // Custom client-side sort with locale awareness
2586
- * const localeSortHandler: SortHandler<Employee> = (rows, state, cols) => {
2587
- * const col = cols.find(c => c.field === state.field);
2588
- * return [...rows].sort((a, b) => {
2589
- * const aVal = String(a[state.field] ?? '');
2590
- * const bVal = String(b[state.field] ?? '');
2591
- * return aVal.localeCompare(bVal) * state.direction;
2592
- * });
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);
2593
2620
  * };
2594
2621
  *
2595
- * // Server-side sorting
2596
- * const serverSortHandler: SortHandler<Employee> = async (rows, state) => {
2597
- * const response = await fetch(
2598
- * `/api/employees?sortBy=${state.field}&dir=${state.direction}`
2599
- * );
2600
- * return response.json();
2601
- * };
2602
- *
2603
- * grid.gridConfig = {
2604
- * sortHandler: localeSortHandler,
2605
- * };
2622
+ * grid.gridConfig = { sortHandler: customSortHandler };
2606
2623
  * ```
2607
2624
  *
2608
2625
  * @see {@link SortState} for the sort state object
2609
- * @see {@link GridConfig.sortHandler} for configuring the handler
2610
- * @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
2611
2628
  */
2612
2629
  export type SortHandler<TRow = any> = (rows: TRow[], sortState: SortState, columns: ColumnConfig<TRow>[]) => TRow[] | Promise<TRow[]>;
2613
2630
  /**
@@ -3115,7 +3132,11 @@ export interface GridIcons {
3115
3132
  /** Print icon for print button. Default: '🖨️' */
3116
3133
  print?: IconValue;
3117
3134
  }
3118
- /** 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. */
3119
3140
  export declare const DEFAULT_GRID_ICONS: Required<GridIcons>;
3120
3141
  /**
3121
3142
  * Shell configuration for the grid's optional header bar and tool panels.
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sources":["../../../../../libs/grid/src/lib/core/internal/diagnostics.ts","../../../../../libs/grid/src/lib/features/registry.ts"],"sourcesContent":["/**\n * Centralized diagnostic messages for @toolbox-web/grid.\n *\n * Every user-facing warning, error, or info message in the grid has a unique\n * diagnostic code (e.g. `TBW001`). Each code maps to a section on the online\n * troubleshooting page, giving developers a direct link to resolution steps.\n *\n * ## Usage\n *\n * ```ts\n * import { MISSING_BREAKPOINT, warnDiagnostic, throwDiagnostic } from './diagnostics';\n *\n * // Warn with a code\n * warnDiagnostic(MISSING_BREAKPOINT, 'Set a breakpoint...', gridId);\n *\n * // Throw with a code\n * throwDiagnostic(MISSING_ROW_ID, 'Configure getRowId...', gridId);\n * ```\n *\n * Plugins should prefer `this.warn(MISSING_BREAKPOINT, message)` via BaseGridPlugin\n * instead of importing this module directly.\n *\n * @internal\n */\n\n// #region Grid Prefix\n\n/**\n * Build the `[tbw-grid]` or `[tbw-grid#my-id]` log prefix.\n * Pass `pluginName` for a scoped prefix like `[tbw-grid:SelectionPlugin]`.\n */\nexport function gridPrefix(gridId?: string, pluginName?: string): string {\n const id = gridId ? `#${gridId}` : '';\n const plugin = pluginName ? `:${pluginName}` : '';\n return `[tbw-grid${id}${plugin}]`;\n}\n\n// #endregion\n\n// #region Diagnostic Codes\n\n/**\n * Diagnostic codes used across the grid library.\n *\n * Each code is an individual export so that unused codes are tree-shaken\n * from bundles that don't reference them (esbuild can't tree-shake\n * properties from a single object).\n *\n * Naming: TBW + 3-digit number.\n * Ranges:\n * 001–019 Configuration validation (missing plugins, bad config)\n * 020–029 Plugin lifecycle (dependencies, incompatibilities, deprecation)\n * 030–039 Feature registry\n * 040–049 Row operations (row ID, row mutations)\n * 050–059 Column operations (width, template)\n * 060–069 Rendering (callbacks, formatters, external views)\n * 070–079 Shell (tool panels, header/toolbar content)\n * 080–089 Editing & editors\n * 090–099 Print\n * 100–109 Clipboard\n * 110–119 Plugin-specific (responsive, undo-redo, grouping-columns)\n * 120–129 Style injection\n * 130–139 Attribute parsing\n */\n\n// --- Config validation (001–019) ---\n/** Column uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN = 'TBW001' as const;\n/** Grid config uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN_CONFIG = 'TBW002' as const;\n/** Plugin config rule violation (error severity). */\nexport const CONFIG_RULE_ERROR = 'TBW003' as const;\n/** Plugin config rule violation (warning severity). */\nexport const CONFIG_RULE_WARN = 'TBW004' as const;\n\n// --- Plugin lifecycle (020–029) ---\n/** Required plugin dependency is missing. */\nexport const MISSING_DEPENDENCY = 'TBW020' as const;\n/** Optional plugin dependency is missing. */\nexport const OPTIONAL_DEPENDENCY = 'TBW021' as const;\n/** Two loaded plugins are incompatible. */\nexport const INCOMPATIBLE_PLUGINS = 'TBW022' as const;\n/** Error thrown inside a plugin event handler. */\nexport const PLUGIN_EVENT_ERROR = 'TBW024' as const;\n\n// --- Feature registry (030–039) ---\n/** Feature was re-registered (overwritten). */\nexport const FEATURE_REREGISTERED = 'TBW030' as const;\n/** Feature configured but not imported. */\nexport const FEATURE_NOT_IMPORTED = 'TBW031' as const;\n/** Feature depends on another feature that is not enabled. */\nexport const FEATURE_MISSING_DEP = 'TBW032' as const;\n\n// --- Row operations (040–049) ---\n/** Cannot determine row ID (no getRowId and no id property). */\nexport const MISSING_ROW_ID = 'TBW040' as const;\n/** Row with given ID not found. */\nexport const ROW_NOT_FOUND = 'TBW041' as const;\n\n// --- Column operations (050–059) ---\n/** Column has an invalid CSS width value. */\nexport const INVALID_COLUMN_WIDTH = 'TBW050' as const;\n\n// --- Rendering callbacks (060–069) ---\n/** rowClass callback threw an error. */\nexport const ROW_CLASS_ERROR = 'TBW060' as const;\n/** cellClass callback threw an error. */\nexport const CELL_CLASS_ERROR = 'TBW061' as const;\n/** Column format function threw an error. */\nexport const FORMAT_ERROR = 'TBW062' as const;\n/** External view mount() threw an error. */\nexport const VIEW_MOUNT_ERROR = 'TBW063' as const;\n/** External view event dispatch error. */\nexport const VIEW_DISPATCH_ERROR = 'TBW064' as const;\n\n// --- Shell (070–079) ---\n/** Tool panel missing required id or title. */\nexport const TOOL_PANEL_MISSING_ATTR = 'TBW070' as const;\n/** No tool panels registered. */\nexport const NO_TOOL_PANELS = 'TBW071' as const;\n/** Tool panel section not found. */\nexport const TOOL_PANEL_NOT_FOUND = 'TBW072' as const;\n/** Tool panel already registered. */\nexport const TOOL_PANEL_DUPLICATE = 'TBW073' as const;\n/** Header content already registered. */\nexport const HEADER_CONTENT_DUPLICATE = 'TBW074' as const;\n/** Toolbar content already registered. */\nexport const TOOLBAR_CONTENT_DUPLICATE = 'TBW075' as const;\n\n// --- Editing & editors (080–089) ---\n/** External editor mount() threw an error. */\nexport const EDITOR_MOUNT_ERROR = 'TBW080' as const;\n\n// --- Print (090–099) ---\n/** Print already in progress. */\nexport const PRINT_IN_PROGRESS = 'TBW090' as const;\n/** Grid not available for printing. */\nexport const PRINT_NO_GRID = 'TBW091' as const;\n/** Print operation failed. */\nexport const PRINT_FAILED = 'TBW092' as const;\n/** Multiple elements share the same grid ID (print isolation issue). */\nexport const PRINT_DUPLICATE_ID = 'TBW093' as const;\n\n// --- Clipboard (100–109) ---\n/** Clipboard API write failed. */\nexport const CLIPBOARD_FAILED = 'TBW100' as const;\n\n// --- Plugin-specific (110–119) ---\n/** ResponsivePlugin: no breakpoint configured. */\nexport const MISSING_BREAKPOINT = 'TBW110' as const;\n/** UndoRedoPlugin: transaction already in progress. */\nexport const TRANSACTION_IN_PROGRESS = 'TBW111' as const;\n/** UndoRedoPlugin: no transaction in progress. */\nexport const NO_TRANSACTION = 'TBW112' as const;\n/** GroupingColumnsPlugin: missing id or header on column group definition. */\nexport const COLUMN_GROUP_NO_ID = 'TBW113' as const;\n/** GroupingColumnsPlugin: conflicting columnGroups sources. */\nexport const COLUMN_GROUPS_CONFLICT = 'TBW114' as const;\n\n// --- Style injection (120–129) ---\n/** Failed to extract grid.css from document stylesheets. */\nexport const STYLE_EXTRACT_FAILED = 'TBW120' as const;\n/** Could not find grid.css in document.styleSheets. */\nexport const STYLE_NOT_FOUND = 'TBW121' as const;\n\n// --- Attribute parsing (130–139) ---\n/** Invalid JSON in an HTML attribute. */\nexport const INVALID_ATTRIBUTE_JSON = 'TBW130' as const;\n\n// --- DataSource / ServerSide (140–149) ---\n/** ServerSidePlugin: getRows() rejected. */\nexport const DATASOURCE_FETCH_ERROR = 'TBW140' as const;\n/** ServerSidePlugin: getChildRows() rejected. */\nexport const DATASOURCE_CHILD_FETCH_ERROR = 'TBW141' as const;\n/** ServerSidePlugin: getChildRows() not implemented but a plugin queried for children. */\nexport const DATASOURCE_NO_CHILD_HANDLER = 'TBW142' as const;\n/** ServerSidePlugin: concurrent request limit reached, request deferred. */\nexport const DATASOURCE_THROTTLED = 'TBW143' as const;\n\nexport type DiagnosticCode =\n | typeof MISSING_PLUGIN\n | typeof MISSING_PLUGIN_CONFIG\n | typeof CONFIG_RULE_ERROR\n | typeof CONFIG_RULE_WARN\n | typeof MISSING_DEPENDENCY\n | typeof OPTIONAL_DEPENDENCY\n | typeof INCOMPATIBLE_PLUGINS\n | typeof PLUGIN_EVENT_ERROR\n | typeof FEATURE_REREGISTERED\n | typeof FEATURE_NOT_IMPORTED\n | typeof FEATURE_MISSING_DEP\n | typeof MISSING_ROW_ID\n | typeof ROW_NOT_FOUND\n | typeof INVALID_COLUMN_WIDTH\n | typeof ROW_CLASS_ERROR\n | typeof CELL_CLASS_ERROR\n | typeof FORMAT_ERROR\n | typeof VIEW_MOUNT_ERROR\n | typeof VIEW_DISPATCH_ERROR\n | typeof TOOL_PANEL_MISSING_ATTR\n | typeof NO_TOOL_PANELS\n | typeof TOOL_PANEL_NOT_FOUND\n | typeof TOOL_PANEL_DUPLICATE\n | typeof HEADER_CONTENT_DUPLICATE\n | typeof TOOLBAR_CONTENT_DUPLICATE\n | typeof EDITOR_MOUNT_ERROR\n | typeof PRINT_IN_PROGRESS\n | typeof PRINT_NO_GRID\n | typeof PRINT_FAILED\n | typeof PRINT_DUPLICATE_ID\n | typeof CLIPBOARD_FAILED\n | typeof MISSING_BREAKPOINT\n | typeof TRANSACTION_IN_PROGRESS\n | typeof NO_TRANSACTION\n | typeof COLUMN_GROUP_NO_ID\n | typeof COLUMN_GROUPS_CONFLICT\n | typeof STYLE_EXTRACT_FAILED\n | typeof STYLE_NOT_FOUND\n | typeof INVALID_ATTRIBUTE_JSON\n | typeof DATASOURCE_FETCH_ERROR\n | typeof DATASOURCE_CHILD_FETCH_ERROR\n | typeof DATASOURCE_NO_CHILD_HANDLER\n | typeof DATASOURCE_THROTTLED;\n\n// #endregion\n\n// #region Docs URL\n\nconst DOCS_BASE = 'https://toolboxjs.com/grid/errors';\n\n/** Build a direct link to the troubleshooting section for a code. */\nfunction docsUrl(code: DiagnosticCode): string {\n return `${DOCS_BASE}#${code.toLowerCase()}`;\n}\n\n// #endregion\n\n// #region Formatting\n\n/**\n * Format a diagnostic message with prefix, code, and docs link.\n *\n * Output format:\n * ```\n * [tbw-grid#my-id] TBW001: Your message here.\n *\n * → More info: https://toolboxjs.com/grid/errors#tbw001\n * ```\n */\nexport function formatDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): string {\n const prefix = gridPrefix(gridId, pluginName);\n return `${prefix} ${code}: ${message}\\n\\n → More info: ${docsUrl(code)}`;\n}\n\n// #endregion\n\n// #region Public API\n\n/**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\nexport function throwDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): never {\n throw new Error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a warning with a diagnostic code and docs link.\n * Use for recoverable issues the developer should fix.\n */\nexport function warnDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.warn(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a debug message with a diagnostic code and docs link.\n * Use for optional/soft dependency notifications — visible only when\n * the browser DevTools \"Verbose\" log level is enabled.\n */\nexport function debugDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.debug(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an error with a diagnostic code and docs link.\n * Use for non-throwing errors (e.g., failed async operations).\n */\nexport function errorDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n// #endregion\n","/**\n * Core Feature Registry for @toolbox-web/grid\n *\n * This module provides a framework-agnostic registry for plugin factories.\n * Features are registered via side-effect imports, enabling tree-shaking\n * while maintaining a clean declarative API.\n *\n * @example\n * ```typescript\n * // Import features you need (side-effect imports)\n * import '@toolbox-web/grid/features/selection';\n * import '@toolbox-web/grid/features/filtering';\n *\n * // Configure grid declaratively\n * grid.gridConfig = {\n * features: {\n * selection: 'range',\n * filtering: { debounceMs: 200 },\n * },\n * };\n * ```\n *\n * @packageDocumentation\n * @module Features\n */\n\nimport {\n FEATURE_MISSING_DEP,\n FEATURE_NOT_IMPORTED,\n FEATURE_REREGISTERED,\n warnDiagnostic,\n} from '../core/internal/diagnostics';\nimport { setFeatureResolver } from '../core/internal/feature-hook';\nimport type { FeatureConfig, GridPlugin } from '../core/types';\n\n// #region Types\n\n/** Feature name — keys of the augmented FeatureConfig interface. */\nexport type FeatureName = keyof FeatureConfig;\n\n/** Factory function that creates a plugin from a feature config value. */\nexport type PluginFactory<TConfig = unknown> = (config: TConfig) => GridPlugin;\n\ninterface RegistryEntry {\n factory: PluginFactory;\n name: string;\n}\n\n// #endregion\n\n// #region Registry State\n\nconst featureRegistry = new Map<string, RegistryEntry>();\nconst warnedFeatures = new Set<string>();\n\n// #endregion\n\n// #region Registration API\n\n/** Runtime dev-mode check (localhost or 127.0.0.1). */\nconst isDev = (): boolean =>\n typeof window !== 'undefined' &&\n (window.location?.hostname === 'localhost' || window.location?.hostname === '127.0.0.1');\n\n/**\n * Register a feature's plugin factory.\n * Called by side-effect feature imports (e.g., `import '@toolbox-web/grid/features/selection'`).\n *\n * @param name - The feature name (matches a key on FeatureConfig)\n * @param factory - Function that creates a plugin instance from config\n */\nexport function registerFeature<K extends FeatureName>(name: K, factory: PluginFactory<FeatureConfig[K]>): void;\nexport function registerFeature(name: string, factory: PluginFactory): void;\nexport function registerFeature(name: string, factory: PluginFactory): void {\n if (isDev() && featureRegistry.has(name)) {\n warnDiagnostic(FEATURE_REREGISTERED, `Feature \"${name}\" was re-registered. Previous registration overwritten.`);\n }\n featureRegistry.set(name, { factory, name });\n}\n\n/**\n * Check if a feature has been registered.\n */\nexport function isFeatureRegistered(name: string): boolean {\n return featureRegistry.has(name);\n}\n\n/**\n * Get a registered feature's factory. Returns undefined if not registered.\n */\nexport function getFeatureFactory(name: string): PluginFactory | undefined {\n return featureRegistry.get(name)?.factory;\n}\n\n/**\n * Get all registered feature names.\n */\nexport function getRegisteredFeatures(): string[] {\n return Array.from(featureRegistry.keys());\n}\n\n// #endregion\n\n// #region Plugin Creation\n\n/**\n * Plugin dependency declarations.\n * Some plugins require others to be loaded first.\n */\nconst PLUGIN_DEPENDENCIES: Record<string, string[]> = {\n undoRedo: ['editing'],\n clipboard: ['selection'],\n};\n\n/**\n * Create a plugin instance for a single feature.\n * Shows a warning if the feature is not registered.\n */\nexport function createPluginFromFeature(name: string, config: unknown): GridPlugin | undefined {\n const entry = featureRegistry.get(name);\n\n if (!entry) {\n if (isDev() && !warnedFeatures.has(name)) {\n warnedFeatures.add(name);\n const kebab = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n warnDiagnostic(\n FEATURE_NOT_IMPORTED,\n `Feature \"${name}\" is configured but not registered.\\n` +\n `Add this import to enable it:\\n\\n` +\n ` import '@toolbox-web/grid/features/${kebab}';\\n`,\n );\n }\n return undefined;\n }\n\n return entry.factory(config);\n}\n\n/**\n * Validate feature dependencies and log warnings for missing ones.\n */\nfunction validateDependencies(featureNames: string[]): void {\n const featureSet = new Set(featureNames);\n\n for (const feature of featureNames) {\n const deps = PLUGIN_DEPENDENCIES[feature];\n if (!deps) continue;\n\n for (const dep of deps) {\n if (!featureSet.has(dep)) {\n if (isDev()) {\n warnDiagnostic(\n FEATURE_MISSING_DEP,\n `Feature \"${feature}\" requires \"${dep}\" to be enabled. ` + `Add \"${dep}\" to your features configuration.`,\n );\n }\n }\n }\n }\n}\n\n/**\n * Create plugin instances from a features configuration object.\n *\n * Handles:\n * - Dependency validation (clipboard needs selection)\n * - Dependency ordering (selection before clipboard)\n * - Skipping false/undefined values\n *\n * @param features - Partial FeatureConfig object\n * @returns Array of plugin instances ready for gridConfig.plugins\n */\nexport function createPluginsFromFeatures(features: Record<string, unknown>): GridPlugin[] {\n const plugins: GridPlugin[] = [];\n const enabledFeatures: string[] = [];\n\n // Collect enabled feature names\n for (const [key, value] of Object.entries(features)) {\n if (value === undefined || value === false) continue;\n enabledFeatures.push(key);\n }\n\n // Validate dependencies\n validateDependencies(enabledFeatures);\n\n // Create plugins in dependency order: dep-targets first, then the rest\n const dependencyOrder: string[] = [\n 'selection',\n 'editing',\n ...enabledFeatures.filter((f) => f !== 'selection' && f !== 'editing'),\n ];\n const orderedFeatures = [...new Set(dependencyOrder)].filter((f) => enabledFeatures.includes(f));\n\n for (const featureName of orderedFeatures) {\n const config = features[featureName];\n if (config === undefined || config === false) continue;\n\n const plugin = createPluginFromFeature(featureName, config);\n if (plugin) {\n plugins.push(plugin);\n }\n }\n\n return plugins;\n}\n\n// #endregion\n\n// #region Auto-Registration\n\n// Wire feature resolver into grid core so `gridConfig.features` is handled automatically.\n// This runs when any feature module is imported (they all import this registry).\nsetFeatureResolver(createPluginsFromFeatures as (features: Record<string, unknown>) => GridPlugin[]);\n\n// #endregion\n\n// #region Testing Utilities\n\n/**\n * Clear the registry. For testing only.\n * @internal\n */\nexport function clearFeatureRegistry(): void {\n featureRegistry.clear();\n warnedFeatures.clear();\n}\n\n// #endregion\n"],"names":["warnDiagnostic","code","message","gridId","pluginName","console","warn","toLowerCase","docsUrl","formatDiagnostic","featureRegistry","Map","warnedFeatures","Set","isDev","window","location","hostname","registerFeature","name","factory","has","set","isFeatureRegistered","getFeatureFactory","get","getRegisteredFeatures","Array","from","keys","PLUGIN_DEPENDENCIES","undoRedo","clipboard","createPluginFromFeature","config","entry","add","kebab","replace","createPluginsFromFeatures","features","plugins","enabledFeatures","key","value","Object","entries","push","featureNames","featureSet","feature","deps","dep","validateDependencies","dependencyOrder","filter","f","orderedFeatures","includes","featureName","plugin","clearFeatureRegistry","clear","setFeatureResolver"],"mappings":"uDA8QO,SAASA,EAAeC,EAAsBC,EAAiBC,EAAiBC,GACrFC,QAAQC,KAtBH,SAA0BL,EAAsBC,GAErD,MAAO,cAAaD,MAASC,uBApB/B,SAAiBD,GACf,MAAO,qCAAgBA,EAAKM,eAC9B,CAkB4DC,CAAQP,IACpE,CAmBeQ,CAAiBR,EAAMC,GACtC,CC5NA,MAAMQ,qBAAsBC,IACtBC,qBAAqBC,IAOrBC,EAAQ,IACM,oBAAXC,SACwB,cAA9BA,OAAOC,UAAUC,UAA0D,cAA9BF,OAAOC,UAAUC,UAW1D,SAASC,EAAgBC,EAAcC,GACxCN,KAAWJ,EAAgBW,IAAIF,IACjCnB,EDYgC,SCZK,YAAYmB,4DAEnDT,EAAgBY,IAAIH,EAAM,CAAEC,UAASD,QACvC,CAKO,SAASI,EAAoBJ,GAClC,OAAOT,EAAgBW,IAAIF,EAC7B,CAKO,SAASK,EAAkBL,GAChC,OAAOT,EAAgBe,IAAIN,IAAOC,OACpC,CAKO,SAASM,IACd,OAAOC,MAAMC,KAAKlB,EAAgBmB,OACpC,CAUA,MAAMC,EAAgD,CACpDC,SAAU,CAAC,WACXC,UAAW,CAAC,cAOP,SAASC,EAAwBd,EAAce,GACpD,MAAMC,EAAQzB,EAAgBe,IAAIN,GAElC,GAAKgB,EAcL,OAAOA,EAAMf,QAAQc,GAbnB,GAAIpB,MAAYF,EAAeS,IAAIF,GAAO,CACxCP,EAAewB,IAAIjB,GACnB,MAAMkB,EAAQlB,EAAKmB,QAAQ,kBAAmB,SAAS/B,cACvDP,EDpC8B,SCsC5B,YAAYmB,+GAE8BkB,QAE9C,CAKJ,CAoCO,SAASE,EAA0BC,GACxC,MAAMC,EAAwB,GACxBC,EAA4B,GAGlC,IAAA,MAAYC,EAAKC,KAAUC,OAAOC,QAAQN,QAC1B,IAAVI,IAAiC,IAAVA,GAC3BF,EAAgBK,KAAKJ,IAtCzB,SAA8BK,GAC5B,MAAMC,EAAa,IAAIpC,IAAImC,GAE3B,IAAA,MAAWE,KAAWF,EAAc,CAClC,MAAMG,EAAOrB,EAAoBoB,GACjC,GAAKC,EAEL,IAAA,MAAWC,KAAOD,EACXF,EAAW5B,IAAI+B,IACdtC,KACFd,ED5DyB,SC8DvB,YAAYkD,gBAAsBE,0BAAiCA,qCAK7E,CACF,CAwBEC,CAAqBX,GAGrB,MAAMY,EAA4B,CAChC,YACA,aACGZ,EAAgBa,OAAQC,GAAY,cAANA,GAA2B,YAANA,IAElDC,EAAkB,IAAI,IAAI5C,IAAIyC,IAAkBC,OAAQC,GAAMd,EAAgBgB,SAASF,IAE7F,IAAA,MAAWG,KAAeF,EAAiB,CACzC,MAAMvB,EAASM,EAASmB,GACxB,QAAe,IAAXzB,IAAmC,IAAXA,EAAkB,SAE9C,MAAM0B,EAAS3B,EAAwB0B,EAAazB,GAChD0B,GACFnB,EAAQM,KAAKa,EAEjB,CAEA,OAAOnB,CACT,CAkBO,SAASoB,IACdnD,EAAgBoD,QAChBlD,EAAekD,OACjB,CAbAC,EAAmBxB"}
1
+ {"version":3,"file":"registry.js","sources":["../../../../../libs/grid/src/lib/core/internal/diagnostics.ts","../../../../../libs/grid/src/lib/features/registry.ts"],"sourcesContent":["/**\n * Centralized diagnostic messages for @toolbox-web/grid.\n *\n * Every user-facing warning, error, or info message in the grid has a unique\n * diagnostic code (e.g. `TBW001`). Each code maps to a section on the online\n * troubleshooting page, giving developers a direct link to resolution steps.\n *\n * ## Usage\n *\n * ```ts\n * import { MISSING_BREAKPOINT, warnDiagnostic, throwDiagnostic } from './diagnostics';\n *\n * // Warn with a code\n * warnDiagnostic(MISSING_BREAKPOINT, 'Set a breakpoint...', gridId);\n *\n * // Throw with a code\n * throwDiagnostic(MISSING_ROW_ID, 'Configure getRowId...', gridId);\n * ```\n *\n * Plugins should prefer `this.warn(MISSING_BREAKPOINT, message)` via BaseGridPlugin\n * instead of importing this module directly.\n *\n * @internal\n */\n\n// #region Grid Prefix\n\n/**\n * Build the `[tbw-grid]` or `[tbw-grid#my-id]` log prefix.\n * Pass `pluginName` for a scoped prefix like `[tbw-grid:SelectionPlugin]`.\n */\nexport function gridPrefix(gridId?: string, pluginName?: string): string {\n const id = gridId ? `#${gridId}` : '';\n const plugin = pluginName ? `:${pluginName}` : '';\n return `[tbw-grid${id}${plugin}]`;\n}\n\n// #endregion\n\n// #region Diagnostic Codes\n\n/**\n * Diagnostic codes used across the grid library.\n *\n * Each code is an individual export so that unused codes are tree-shaken\n * from bundles that don't reference them (esbuild can't tree-shake\n * properties from a single object).\n *\n * Naming: TBW + 3-digit number.\n * Ranges:\n * 001–019 Configuration validation (missing plugins, bad config)\n * 020–029 Plugin lifecycle (dependencies, incompatibilities, deprecation)\n * 030–039 Feature registry\n * 040–049 Row operations (row ID, row mutations)\n * 050–059 Column operations (width, template)\n * 060–069 Rendering (callbacks, formatters, external views)\n * 070–079 Shell (tool panels, header/toolbar content)\n * 080–089 Editing & editors\n * 090–099 Print\n * 100–109 Clipboard\n * 110–119 Plugin-specific (responsive, undo-redo, grouping-columns)\n * 120–129 Style injection\n * 130–139 Attribute parsing\n */\n\n// --- Config validation (001–019) ---\n/** Column uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN = 'TBW001' as const;\n/** Grid config uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN_CONFIG = 'TBW002' as const;\n/** Plugin config rule violation (error severity). */\nexport const CONFIG_RULE_ERROR = 'TBW003' as const;\n/** Plugin config rule violation (warning severity). */\nexport const CONFIG_RULE_WARN = 'TBW004' as const;\n\n// --- Plugin lifecycle (020–029) ---\n/** Required plugin dependency is missing. */\nexport const MISSING_DEPENDENCY = 'TBW020' as const;\n/** Optional plugin dependency is missing. */\nexport const OPTIONAL_DEPENDENCY = 'TBW021' as const;\n/** Two loaded plugins are incompatible. */\nexport const INCOMPATIBLE_PLUGINS = 'TBW022' as const;\n/** Two plugin instances resolve to the same canonical name (alias collapse). */\nexport const PLUGIN_ALIAS_COLLAPSE = 'TBW023' as const;\n/** Error thrown inside a plugin event handler. */\nexport const PLUGIN_EVENT_ERROR = 'TBW024' as const;\n/** Conflicting config values found while merging alias-collapsed plugin instances. */\nexport const PLUGIN_ALIAS_CONFIG_CONFLICT = 'TBW025' as const;\n\n// --- Feature registry (030–039) ---\n/** Feature was re-registered (overwritten). */\nexport const FEATURE_REREGISTERED = 'TBW030' as const;\n/** Feature configured but not imported. */\nexport const FEATURE_NOT_IMPORTED = 'TBW031' as const;\n/** Feature depends on another feature that is not enabled. */\nexport const FEATURE_MISSING_DEP = 'TBW032' as const;\n\n// --- Row operations (040–049) ---\n/** Cannot determine row ID (no getRowId and no id property). */\nexport const MISSING_ROW_ID = 'TBW040' as const;\n/** Row with given ID not found. */\nexport const ROW_NOT_FOUND = 'TBW041' as const;\n\n// --- Column operations (050–059) ---\n/** Column has an invalid CSS width value. */\nexport const INVALID_COLUMN_WIDTH = 'TBW050' as const;\n\n// --- Rendering callbacks (060–069) ---\n/** rowClass callback threw an error. */\nexport const ROW_CLASS_ERROR = 'TBW060' as const;\n/** cellClass callback threw an error. */\nexport const CELL_CLASS_ERROR = 'TBW061' as const;\n/** Column format function threw an error. */\nexport const FORMAT_ERROR = 'TBW062' as const;\n/** External view mount() threw an error. */\nexport const VIEW_MOUNT_ERROR = 'TBW063' as const;\n/** External view event dispatch error. */\nexport const VIEW_DISPATCH_ERROR = 'TBW064' as const;\n\n// --- Shell (070–079) ---\n/** Tool panel missing required id or title. */\nexport const TOOL_PANEL_MISSING_ATTR = 'TBW070' as const;\n/** No tool panels registered. */\nexport const NO_TOOL_PANELS = 'TBW071' as const;\n/** Tool panel section not found. */\nexport const TOOL_PANEL_NOT_FOUND = 'TBW072' as const;\n/** Tool panel already registered. */\nexport const TOOL_PANEL_DUPLICATE = 'TBW073' as const;\n/** Header content already registered. */\nexport const HEADER_CONTENT_DUPLICATE = 'TBW074' as const;\n/** Toolbar content already registered. */\nexport const TOOLBAR_CONTENT_DUPLICATE = 'TBW075' as const;\n\n// --- Editing & editors (080–089) ---\n/** External editor mount() threw an error. */\nexport const EDITOR_MOUNT_ERROR = 'TBW080' as const;\n\n// --- Print (090–099) ---\n/** Print already in progress. */\nexport const PRINT_IN_PROGRESS = 'TBW090' as const;\n/** Grid not available for printing. */\nexport const PRINT_NO_GRID = 'TBW091' as const;\n/** Print operation failed. */\nexport const PRINT_FAILED = 'TBW092' as const;\n/** Multiple elements share the same grid ID (print isolation issue). */\nexport const PRINT_DUPLICATE_ID = 'TBW093' as const;\n\n// --- Clipboard (100–109) ---\n/** Clipboard API write failed. */\nexport const CLIPBOARD_FAILED = 'TBW100' as const;\n\n// --- Plugin-specific (110–119) ---\n/** ResponsivePlugin: no breakpoint configured. */\nexport const MISSING_BREAKPOINT = 'TBW110' as const;\n/** UndoRedoPlugin: transaction already in progress. */\nexport const TRANSACTION_IN_PROGRESS = 'TBW111' as const;\n/** UndoRedoPlugin: no transaction in progress. */\nexport const NO_TRANSACTION = 'TBW112' as const;\n/** GroupingColumnsPlugin: missing id or header on column group definition. */\nexport const COLUMN_GROUP_NO_ID = 'TBW113' as const;\n/** GroupingColumnsPlugin: conflicting columnGroups sources. */\nexport const COLUMN_GROUPS_CONFLICT = 'TBW114' as const;\n\n// --- Style injection (120–129) ---\n/** Failed to extract grid.css from document stylesheets. */\nexport const STYLE_EXTRACT_FAILED = 'TBW120' as const;\n/** Could not find grid.css in document.styleSheets. */\nexport const STYLE_NOT_FOUND = 'TBW121' as const;\n\n// --- Attribute parsing (130–139) ---\n/** Invalid JSON in an HTML attribute. */\nexport const INVALID_ATTRIBUTE_JSON = 'TBW130' as const;\n\n// --- DataSource / ServerSide (140–149) ---\n/** ServerSidePlugin: getRows() rejected. */\nexport const DATASOURCE_FETCH_ERROR = 'TBW140' as const;\n/** ServerSidePlugin: getChildRows() rejected. */\nexport const DATASOURCE_CHILD_FETCH_ERROR = 'TBW141' as const;\n/** ServerSidePlugin: getChildRows() not implemented but a plugin queried for children. */\nexport const DATASOURCE_NO_CHILD_HANDLER = 'TBW142' as const;\n/** ServerSidePlugin: concurrent request limit reached, request deferred. */\nexport const DATASOURCE_THROTTLED = 'TBW143' as const;\n\nexport type DiagnosticCode =\n | typeof MISSING_PLUGIN\n | typeof MISSING_PLUGIN_CONFIG\n | typeof CONFIG_RULE_ERROR\n | typeof CONFIG_RULE_WARN\n | typeof MISSING_DEPENDENCY\n | typeof OPTIONAL_DEPENDENCY\n | typeof INCOMPATIBLE_PLUGINS\n | typeof PLUGIN_ALIAS_COLLAPSE\n | typeof PLUGIN_EVENT_ERROR\n | typeof PLUGIN_ALIAS_CONFIG_CONFLICT\n | typeof FEATURE_REREGISTERED\n | typeof FEATURE_NOT_IMPORTED\n | typeof FEATURE_MISSING_DEP\n | typeof MISSING_ROW_ID\n | typeof ROW_NOT_FOUND\n | typeof INVALID_COLUMN_WIDTH\n | typeof ROW_CLASS_ERROR\n | typeof CELL_CLASS_ERROR\n | typeof FORMAT_ERROR\n | typeof VIEW_MOUNT_ERROR\n | typeof VIEW_DISPATCH_ERROR\n | typeof TOOL_PANEL_MISSING_ATTR\n | typeof NO_TOOL_PANELS\n | typeof TOOL_PANEL_NOT_FOUND\n | typeof TOOL_PANEL_DUPLICATE\n | typeof HEADER_CONTENT_DUPLICATE\n | typeof TOOLBAR_CONTENT_DUPLICATE\n | typeof EDITOR_MOUNT_ERROR\n | typeof PRINT_IN_PROGRESS\n | typeof PRINT_NO_GRID\n | typeof PRINT_FAILED\n | typeof PRINT_DUPLICATE_ID\n | typeof CLIPBOARD_FAILED\n | typeof MISSING_BREAKPOINT\n | typeof TRANSACTION_IN_PROGRESS\n | typeof NO_TRANSACTION\n | typeof COLUMN_GROUP_NO_ID\n | typeof COLUMN_GROUPS_CONFLICT\n | typeof STYLE_EXTRACT_FAILED\n | typeof STYLE_NOT_FOUND\n | typeof INVALID_ATTRIBUTE_JSON\n | typeof DATASOURCE_FETCH_ERROR\n | typeof DATASOURCE_CHILD_FETCH_ERROR\n | typeof DATASOURCE_NO_CHILD_HANDLER\n | typeof DATASOURCE_THROTTLED;\n\n// #endregion\n\n// #region Docs URL\n\nconst DOCS_BASE = 'https://toolboxjs.com/grid/errors';\n\n/** Build a direct link to the troubleshooting section for a code. */\nfunction docsUrl(code: DiagnosticCode): string {\n return `${DOCS_BASE}#${code.toLowerCase()}`;\n}\n\n// #endregion\n\n// #region Formatting\n\n/**\n * Format a diagnostic message with prefix, code, and docs link.\n *\n * Output format:\n * ```\n * [tbw-grid#my-id] TBW001: Your message here.\n *\n * → More info: https://toolboxjs.com/grid/errors#tbw001\n * ```\n */\nexport function formatDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): string {\n const prefix = gridPrefix(gridId, pluginName);\n return `${prefix} ${code}: ${message}\\n\\n → More info: ${docsUrl(code)}`;\n}\n\n// #endregion\n\n// #region Public API\n\n/**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\nexport function throwDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): never {\n throw new Error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a warning with a diagnostic code and docs link.\n * Use for recoverable issues the developer should fix.\n */\nexport function warnDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.warn(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a debug message with a diagnostic code and docs link.\n * Use for optional/soft dependency notifications — visible only when\n * the browser DevTools \"Verbose\" log level is enabled.\n */\nexport function debugDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.debug(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an error with a diagnostic code and docs link.\n * Use for non-throwing errors (e.g., failed async operations).\n */\nexport function errorDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n// #endregion\n","/**\n * Core Feature Registry for @toolbox-web/grid\n *\n * This module provides a framework-agnostic registry for plugin factories.\n * Features are registered via side-effect imports, enabling tree-shaking\n * while maintaining a clean declarative API.\n *\n * @example\n * ```typescript\n * // Import features you need (side-effect imports)\n * import '@toolbox-web/grid/features/selection';\n * import '@toolbox-web/grid/features/filtering';\n *\n * // Configure grid declaratively\n * grid.gridConfig = {\n * features: {\n * selection: 'range',\n * filtering: { debounceMs: 200 },\n * },\n * };\n * ```\n *\n * @packageDocumentation\n * @module Features\n */\n\nimport {\n FEATURE_MISSING_DEP,\n FEATURE_NOT_IMPORTED,\n FEATURE_REREGISTERED,\n warnDiagnostic,\n} from '../core/internal/diagnostics';\nimport { setFeatureResolver } from '../core/internal/feature-hook';\nimport type { FeatureConfig, GridPlugin } from '../core/types';\n\n// #region Types\n\n/** Feature name — keys of the augmented FeatureConfig interface. */\nexport type FeatureName = keyof FeatureConfig;\n\n/** Factory function that creates a plugin from a feature config value. */\nexport type PluginFactory<TConfig = unknown> = (config: TConfig) => GridPlugin;\n\ninterface RegistryEntry {\n factory: PluginFactory;\n name: string;\n}\n\n// #endregion\n\n// #region Registry State\n\nconst featureRegistry = new Map<string, RegistryEntry>();\nconst warnedFeatures = new Set<string>();\n\n// #endregion\n\n// #region Registration API\n\n/** Runtime dev-mode check (localhost or 127.0.0.1). */\nconst isDev = (): boolean =>\n typeof window !== 'undefined' &&\n (window.location?.hostname === 'localhost' || window.location?.hostname === '127.0.0.1');\n\n/**\n * Register a feature's plugin factory.\n * Called by side-effect feature imports (e.g., `import '@toolbox-web/grid/features/selection'`).\n *\n * @param name - The feature name (matches a key on FeatureConfig)\n * @param factory - Function that creates a plugin instance from config\n */\nexport function registerFeature<K extends FeatureName>(name: K, factory: PluginFactory<FeatureConfig[K]>): void;\nexport function registerFeature(name: string, factory: PluginFactory): void;\nexport function registerFeature(name: string, factory: PluginFactory): void {\n if (isDev() && featureRegistry.has(name)) {\n warnDiagnostic(FEATURE_REREGISTERED, `Feature \"${name}\" was re-registered. Previous registration overwritten.`);\n }\n featureRegistry.set(name, { factory, name });\n}\n\n/**\n * Check if a feature has been registered.\n */\nexport function isFeatureRegistered(name: string): boolean {\n return featureRegistry.has(name);\n}\n\n/**\n * Get a registered feature's factory. Returns undefined if not registered.\n */\nexport function getFeatureFactory(name: string): PluginFactory | undefined {\n return featureRegistry.get(name)?.factory;\n}\n\n/**\n * Get all registered feature names.\n */\nexport function getRegisteredFeatures(): string[] {\n return Array.from(featureRegistry.keys());\n}\n\n// #endregion\n\n// #region Plugin Creation\n\n/**\n * Plugin dependency declarations.\n * Some plugins require others to be loaded first.\n */\nconst PLUGIN_DEPENDENCIES: Record<string, string[]> = {\n undoRedo: ['editing'],\n clipboard: ['selection'],\n};\n\n/**\n * Create a plugin instance for a single feature.\n * Shows a warning if the feature is not registered.\n */\nexport function createPluginFromFeature(name: string, config: unknown): GridPlugin | undefined {\n const entry = featureRegistry.get(name);\n\n if (!entry) {\n if (isDev() && !warnedFeatures.has(name)) {\n warnedFeatures.add(name);\n const kebab = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n warnDiagnostic(\n FEATURE_NOT_IMPORTED,\n `Feature \"${name}\" is configured but not registered.\\n` +\n `Add this import to enable it:\\n\\n` +\n ` import '@toolbox-web/grid/features/${kebab}';\\n`,\n );\n }\n return undefined;\n }\n\n return entry.factory(config);\n}\n\n/**\n * Validate feature dependencies and log warnings for missing ones.\n */\nfunction validateDependencies(featureNames: string[]): void {\n const featureSet = new Set(featureNames);\n\n for (const feature of featureNames) {\n const deps = PLUGIN_DEPENDENCIES[feature];\n if (!deps) continue;\n\n for (const dep of deps) {\n if (!featureSet.has(dep)) {\n if (isDev()) {\n warnDiagnostic(\n FEATURE_MISSING_DEP,\n `Feature \"${feature}\" requires \"${dep}\" to be enabled. ` + `Add \"${dep}\" to your features configuration.`,\n );\n }\n }\n }\n }\n}\n\n/**\n * Create plugin instances from a features configuration object.\n *\n * Handles:\n * - Dependency validation (clipboard needs selection)\n * - Dependency ordering (selection before clipboard)\n * - Skipping false/undefined values\n *\n * @param features - Partial FeatureConfig object\n * @returns Array of plugin instances ready for gridConfig.plugins\n */\nexport function createPluginsFromFeatures(features: Record<string, unknown>): GridPlugin[] {\n const plugins: GridPlugin[] = [];\n const enabledFeatures: string[] = [];\n\n // Collect enabled feature names\n for (const [key, value] of Object.entries(features)) {\n if (value === undefined || value === false) continue;\n enabledFeatures.push(key);\n }\n\n // Validate dependencies\n validateDependencies(enabledFeatures);\n\n // Create plugins in dependency order: dep-targets first, then the rest\n const dependencyOrder: string[] = [\n 'selection',\n 'editing',\n ...enabledFeatures.filter((f) => f !== 'selection' && f !== 'editing'),\n ];\n const orderedFeatures = [...new Set(dependencyOrder)].filter((f) => enabledFeatures.includes(f));\n\n for (const featureName of orderedFeatures) {\n const config = features[featureName];\n if (config === undefined || config === false) continue;\n\n const plugin = createPluginFromFeature(featureName, config);\n if (plugin) {\n plugins.push(plugin);\n }\n }\n\n return plugins;\n}\n\n// #endregion\n\n// #region Auto-Registration\n\n// Wire feature resolver into grid core so `gridConfig.features` is handled automatically.\n// This runs when any feature module is imported (they all import this registry).\nsetFeatureResolver(createPluginsFromFeatures as (features: Record<string, unknown>) => GridPlugin[]);\n\n// #endregion\n\n// #region Testing Utilities\n\n/**\n * Clear the registry. For testing only.\n * @internal\n */\nexport function clearFeatureRegistry(): void {\n featureRegistry.clear();\n warnedFeatures.clear();\n}\n\n// #endregion\n"],"names":["warnDiagnostic","code","message","gridId","pluginName","console","warn","toLowerCase","docsUrl","formatDiagnostic","featureRegistry","Map","warnedFeatures","Set","isDev","window","location","hostname","registerFeature","name","factory","has","set","isFeatureRegistered","getFeatureFactory","get","getRegisteredFeatures","Array","from","keys","PLUGIN_DEPENDENCIES","undoRedo","clipboard","createPluginFromFeature","config","entry","add","kebab","replace","createPluginsFromFeatures","features","plugins","enabledFeatures","key","value","Object","entries","push","featureNames","featureSet","feature","deps","dep","validateDependencies","dependencyOrder","filter","f","orderedFeatures","includes","featureName","plugin","clearFeatureRegistry","clear","setFeatureResolver"],"mappings":"uDAoRO,SAASA,EAAeC,EAAsBC,EAAiBC,EAAiBC,GACrFC,QAAQC,KAtBH,SAA0BL,EAAsBC,GAErD,MAAO,cAAaD,MAASC,uBApB/B,SAAiBD,GACf,MAAO,qCAAgBA,EAAKM,eAC9B,CAkB4DC,CAAQP,IACpE,CAmBeQ,CAAiBR,EAAMC,GACtC,CClOA,MAAMQ,qBAAsBC,IACtBC,qBAAqBC,IAOrBC,EAAQ,IACM,oBAAXC,SACwB,cAA9BA,OAAOC,UAAUC,UAA0D,cAA9BF,OAAOC,UAAUC,UAW1D,SAASC,EAAgBC,EAAcC,GACxCN,KAAWJ,EAAgBW,IAAIF,IACjCnB,EDgBgC,SChBK,YAAYmB,4DAEnDT,EAAgBY,IAAIH,EAAM,CAAEC,UAASD,QACvC,CAKO,SAASI,EAAoBJ,GAClC,OAAOT,EAAgBW,IAAIF,EAC7B,CAKO,SAASK,EAAkBL,GAChC,OAAOT,EAAgBe,IAAIN,IAAOC,OACpC,CAKO,SAASM,IACd,OAAOC,MAAMC,KAAKlB,EAAgBmB,OACpC,CAUA,MAAMC,EAAgD,CACpDC,SAAU,CAAC,WACXC,UAAW,CAAC,cAOP,SAASC,EAAwBd,EAAce,GACpD,MAAMC,EAAQzB,EAAgBe,IAAIN,GAElC,GAAKgB,EAcL,OAAOA,EAAMf,QAAQc,GAbnB,GAAIpB,MAAYF,EAAeS,IAAIF,GAAO,CACxCP,EAAewB,IAAIjB,GACnB,MAAMkB,EAAQlB,EAAKmB,QAAQ,kBAAmB,SAAS/B,cACvDP,EDhC8B,SCkC5B,YAAYmB,+GAE8BkB,QAE9C,CAKJ,CAoCO,SAASE,EAA0BC,GACxC,MAAMC,EAAwB,GACxBC,EAA4B,GAGlC,IAAA,MAAYC,EAAKC,KAAUC,OAAOC,QAAQN,QAC1B,IAAVI,IAAiC,IAAVA,GAC3BF,EAAgBK,KAAKJ,IAtCzB,SAA8BK,GAC5B,MAAMC,EAAa,IAAIpC,IAAImC,GAE3B,IAAA,MAAWE,KAAWF,EAAc,CAClC,MAAMG,EAAOrB,EAAoBoB,GACjC,GAAKC,EAEL,IAAA,MAAWC,KAAOD,EACXF,EAAW5B,IAAI+B,IACdtC,KACFd,EDxDyB,SC0DvB,YAAYkD,gBAAsBE,0BAAiCA,qCAK7E,CACF,CAwBEC,CAAqBX,GAGrB,MAAMY,EAA4B,CAChC,YACA,aACGZ,EAAgBa,OAAQC,GAAY,cAANA,GAA2B,YAANA,IAElDC,EAAkB,IAAI,IAAI5C,IAAIyC,IAAkBC,OAAQC,GAAMd,EAAgBgB,SAASF,IAE7F,IAAA,MAAWG,KAAeF,EAAiB,CACzC,MAAMvB,EAASM,EAASmB,GACxB,QAAe,IAAXzB,IAAmC,IAAXA,EAAkB,SAE9C,MAAM0B,EAAS3B,EAAwB0B,EAAazB,GAChD0B,GACFnB,EAAQM,KAAKa,EAEjB,CAEA,OAAOnB,CACT,CAkBO,SAASoB,IACdnD,EAAgBoD,QAChBlD,EAAekD,OACjB,CAbAC,EAAmBxB"}
@@ -1,8 +1,8 @@
1
- import { RowReorderConfig } from '../plugins/reorder-rows';
1
+ import { RowDragDropConfig } from '../plugins/row-drag-drop';
2
2
  declare module '../core/types' {
3
3
  interface FeatureConfig {
4
- /** Enable row drag-to-reorder. */
5
- reorderRows?: boolean | RowReorderConfig;
4
+ /** @deprecated Use `rowDragDrop`. Forwarded to the same underlying plugin. */
5
+ reorderRows?: boolean | RowDragDropConfig;
6
6
  }
7
7
  }
8
8
  /** @internal Type anchor — forces bundlers to preserve this module's FeatureConfig augmentation when re-exported. */
@@ -1,2 +1,2 @@
1
- import{RowReorderPlugin as r}from"@toolbox-web/grid/plugins/reorder-rows";import{registerFeature as o}from"@toolbox-web/grid/features/registry";o("reorderRows",o=>!0===o?new r:new r(o??void 0));
1
+ import{RowDragDropPlugin as o}from"@toolbox-web/grid/plugins/row-drag-drop";import{registerFeature as r}from"@toolbox-web/grid/features/registry";r("reorderRows",r=>!0===r?new o:new o(r??void 0));
2
2
  //# sourceMappingURL=reorder-rows.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reorder-rows.js","sources":["../../../../../libs/grid/src/lib/features/reorder-rows.ts"],"sourcesContent":["/**\n * Row Reorder feature for @toolbox-web/grid\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid/features/reorder-rows';\n *\n * grid.gridConfig = { features: { reorderRows: true } };\n * ```\n */\n\nimport { RowReorderPlugin, type RowReorderConfig } from '../plugins/reorder-rows';\nimport { registerFeature } from './registry';\n\ndeclare module '../core/types' {\n interface FeatureConfig {\n /** Enable row drag-to-reorder. */\n reorderRows?: boolean | RowReorderConfig;\n }\n}\n\nconst factory = (config: unknown) => {\n if (config === true) {\n return new RowReorderPlugin();\n }\n return new RowReorderPlugin((config as RowReorderConfig) ?? undefined);\n};\n\nregisterFeature('reorderRows', factory);\n\n/** @internal Type anchor — forces bundlers to preserve this module's FeatureConfig augmentation when re-exported. */\nexport type _Augmentation = true;\n"],"names":["registerFeature","config","RowReorderPlugin"],"mappings":"gJA4BAA,EAAgB,cAPCC,IACA,IAAXA,EACK,IAAIC,EAEN,IAAIA,EAAkBD,QAA+B"}
1
+ {"version":3,"file":"reorder-rows.js","sources":["../../../../../libs/grid/src/lib/features/reorder-rows.ts"],"sourcesContent":["/**\n * Row Reorder feature (deprecated alias for `rowDragDrop`).\n *\n * `RowReorderPlugin` is now an alias of `RowDragDropPlugin`. This feature\n * module forwards to the new plugin so existing\n * `import '@toolbox-web/grid/features/reorder-rows'` calls keep working.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid/features/reorder-rows';\n *\n * grid.gridConfig = { features: { reorderRows: true } };\n * ```\n *\n * @deprecated Use `@toolbox-web/grid/features/row-drag-drop` and the\n * `rowDragDrop` feature key. This module will be removed in V3.\n */\n\nimport { RowDragDropPlugin, type RowDragDropConfig } from '../plugins/row-drag-drop';\nimport { registerFeature } from './registry';\n\ndeclare module '../core/types' {\n interface FeatureConfig {\n /** @deprecated Use `rowDragDrop`. Forwarded to the same underlying plugin. */\n reorderRows?: boolean | RowDragDropConfig;\n }\n}\n\nconst factory = (config: unknown) => {\n if (config === true) return new RowDragDropPlugin();\n return new RowDragDropPlugin((config as RowDragDropConfig) ?? undefined);\n};\n\nregisterFeature('reorderRows', factory);\n\n/** @internal Type anchor — forces bundlers to preserve this module's FeatureConfig augmentation when re-exported. */\nexport type _Augmentation = true;\n"],"names":["registerFeature","config","RowDragDropPlugin"],"mappings":"kJAiCAA,EAAgB,cALCC,IACA,IAAXA,EAAwB,IAAIC,EACzB,IAAIA,EAAmBD,QAAgC"}
@@ -0,0 +1,9 @@
1
+ import { RowDragDropConfig } from '../plugins/row-drag-drop';
2
+ declare module '../core/types' {
3
+ interface FeatureConfig {
4
+ /** Enable row drag-drop (intra-grid + optional cross-grid via `dropZone`). */
5
+ rowDragDrop?: boolean | RowDragDropConfig;
6
+ }
7
+ }
8
+ /** @internal Type anchor — forces bundlers to preserve this module's FeatureConfig augmentation when re-exported. */
9
+ export type _Augmentation = true;
@@ -0,0 +1,2 @@
1
+ import{RowDragDropPlugin as o}from"@toolbox-web/grid/plugins/row-drag-drop";import{registerFeature as r}from"@toolbox-web/grid/features/registry";r("rowDragDrop",r=>!0===r?new o:new o(r??void 0));
2
+ //# sourceMappingURL=row-drag-drop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row-drag-drop.js","sources":["../../../../../libs/grid/src/lib/features/row-drag-drop.ts"],"sourcesContent":["/**\n * Row Drag-Drop feature for @toolbox-web/grid\n *\n * Drag rows within a single grid and (with `dropZone`) between grids.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid/features/row-drag-drop';\n *\n * grid.gridConfig = { features: { rowDragDrop: { dropZone: 'tasks' } } };\n * ```\n */\n\nimport { RowDragDropPlugin, type RowDragDropConfig } from '../plugins/row-drag-drop';\nimport { registerFeature } from './registry';\n\ndeclare module '../core/types' {\n interface FeatureConfig {\n /** Enable row drag-drop (intra-grid + optional cross-grid via `dropZone`). */\n rowDragDrop?: boolean | RowDragDropConfig;\n }\n}\n\nconst factory = (config: unknown) => {\n if (config === true) {\n return new RowDragDropPlugin();\n }\n return new RowDragDropPlugin((config as RowDragDropConfig) ?? undefined);\n};\n\nregisterFeature('rowDragDrop', factory);\n\n/** @internal Type anchor — forces bundlers to preserve this module's FeatureConfig augmentation when re-exported. */\nexport type _Augmentation = true;\n"],"names":["registerFeature","config","RowDragDropPlugin"],"mappings":"kJA8BAA,EAAgB,cAPCC,IACA,IAAXA,EACK,IAAIC,EAEN,IAAIA,EAAmBD,QAAgC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server-side.js","sources":["../../../../../libs/grid/src/lib/features/server-side.ts"],"sourcesContent":["/**\n * Server-Side feature for @toolbox-web/grid\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid/features/server-side';\n *\n * grid.gridConfig = { features: { serverSide: { fetchRows: async (params) => ... } } };\n * ```\n */\n\nimport { ServerSidePlugin, type ServerSideConfig } from '../plugins/server-side';\nimport { registerFeature } from './registry';\n\ndeclare module '../core/types' {\n interface FeatureConfig {\n /** Enable server-side data fetching, sorting, filtering, etc. */\n serverSide?: ServerSideConfig;\n }\n}\n\nregisterFeature('serverSide', (config) => {\n return new ServerSidePlugin((config as ServerSideConfig) ?? undefined);\n});\n\n/** @internal Type anchor — forces bundlers to preserve this module's FeatureConfig augmentation when re-exported. */\nexport type _Augmentation = true;\n"],"names":["registerFeature","config","ServerSidePlugin"],"mappings":"+IAqBAA,EAAgB,aAAeC,GACtB,IAAIC,EAAkBD,QAA+B"}
1
+ {"version":3,"file":"server-side.js","sources":["../../../../../libs/grid/src/lib/features/server-side.ts"],"sourcesContent":["/**\n * Server-Side feature for @toolbox-web/grid\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid/features/server-side';\n *\n * grid.gridConfig = { features: { serverSide: { dataSource: { getRows: async (params) => ... } } } };\n * ```\n */\n\nimport { ServerSidePlugin, type ServerSideConfig } from '../plugins/server-side';\nimport { registerFeature } from './registry';\n\ndeclare module '../core/types' {\n interface FeatureConfig {\n /** Enable server-side data fetching, sorting, filtering, etc. */\n serverSide?: ServerSideConfig;\n }\n}\n\nregisterFeature('serverSide', (config) => {\n return new ServerSidePlugin((config as ServerSideConfig) ?? undefined);\n});\n\n/** @internal Type anchor — forces bundlers to preserve this module's FeatureConfig augmentation when re-exported. */\nexport type _Augmentation = true;\n"],"names":["registerFeature","config","ServerSidePlugin"],"mappings":"+IAqBAA,EAAgB,aAAeC,GACtB,IAAIC,EAAkBD,QAA+B"}