@toolbox-web/grid-angular 2.4.1 → 2.5.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.
- package/LICENSE +21 -0
- package/README.md +49 -1
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +3 -0
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular.mjs +235 -12
- package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/toolbox-web-grid-angular-features-selection.d.ts +8 -0
- package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular.d.ts +161 -5
- package/types/toolbox-web-grid-angular.d.ts.map +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Øystein Amundsen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @toolbox-web/grid-angular
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@toolbox-web/grid-angular)
|
|
4
|
-
[](
|
|
4
|
+
[](./LICENSE)
|
|
5
5
|
[](https://github.com/sponsors/OysteinAmundsen)
|
|
6
6
|
|
|
7
7
|
Angular adapter for `@toolbox-web/grid` data grid component. Provides directives for declarative template-driven cell renderers and editors.
|
|
@@ -344,6 +344,38 @@ export class MyGridComponent {}
|
|
|
344
344
|
|
|
345
345
|
> **Auto-wiring:** If your editor component emits a `commit` event with the new value, the adapter automatically calls the grid's commit function. Similarly for `cancel`. This means you can skip the explicit `onCommit`/`onCancel` bindings!
|
|
346
346
|
|
|
347
|
+
### Custom Header Templates
|
|
348
|
+
|
|
349
|
+
`*tbwHeader` and `*tbwHeaderLabel` are the Angular equivalent of React's
|
|
350
|
+
`<GridColumn headerRenderer>` render prop and Vue's `#header` / `#headerLabel` slots.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { Grid, TbwHeader, TbwHeaderLabel } from '@toolbox-web/grid-angular';
|
|
354
|
+
|
|
355
|
+
@Component({
|
|
356
|
+
imports: [Grid, TbwHeader, TbwHeaderLabel],
|
|
357
|
+
template: `
|
|
358
|
+
<tbw-grid [rows]="rows">
|
|
359
|
+
<!-- Label only — the grid keeps sort icons, filter buttons, resize handles -->
|
|
360
|
+
<tbw-grid-column field="name">
|
|
361
|
+
<strong *tbwHeaderLabel="let value">{{ value }}</strong>
|
|
362
|
+
</tbw-grid-column>
|
|
363
|
+
|
|
364
|
+
<!-- Whole header cell — you own the affordances -->
|
|
365
|
+
<tbw-grid-column field="salary" sortable>
|
|
366
|
+
<span *tbwHeader="let value; column as column">💰 {{ value }}</span>
|
|
367
|
+
</tbw-grid-column>
|
|
368
|
+
</tbw-grid>
|
|
369
|
+
`,
|
|
370
|
+
})
|
|
371
|
+
export class MyGridComponent {}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**`*tbwHeader` context:** `$implicit` / `value` (header text), `column`, `sortState`,
|
|
375
|
+
`filterActive`, `cellEl`, `renderSortIcon()`, `renderFilterButton()`.
|
|
376
|
+
|
|
377
|
+
**`*tbwHeaderLabel` context:** `$implicit` / `value`, `column`.
|
|
378
|
+
|
|
347
379
|
## Nested Directive Syntax (Alternative)
|
|
348
380
|
|
|
349
381
|
For more explicit control, you can use the nested directive syntax with `<ng-template>`:
|
|
@@ -726,6 +758,20 @@ export class MyGridComponent {
|
|
|
726
758
|
|
|
727
759
|
> **Tip:** Prefer feature props (see [Enabling Features](#enabling-features) above) for simpler code and tree-shaking.
|
|
728
760
|
|
|
761
|
+
There is also a dedicated `[plugins]` input, matching `<DataGrid plugins={...}>` in React
|
|
762
|
+
and `:plugins` in Vue. When `[plugins]` is set, feature directives are ignored — the array
|
|
763
|
+
is the full plugin list:
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
@Component({
|
|
767
|
+
imports: [Grid],
|
|
768
|
+
template: `<tbw-grid [rows]="rows" [plugins]="plugins" />`,
|
|
769
|
+
})
|
|
770
|
+
export class MyGridComponent {
|
|
771
|
+
plugins = [new SelectionPlugin({ mode: 'row' })];
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
729
775
|
Or import all plugins at once (larger bundle, but convenient):
|
|
730
776
|
|
|
731
777
|
```typescript
|
|
@@ -1097,6 +1143,8 @@ export class TextFilterComponent extends BaseFilterPanel {
|
|
|
1097
1143
|
| `GridFormArray` | `tbw-grid[formControlName]`, `tbw-grid[formControl]` | Reactive Forms integration |
|
|
1098
1144
|
| `TbwRenderer` | `*tbwRenderer` | Structural directive for cell views |
|
|
1099
1145
|
| `TbwEditor` | `*tbwEditor` | Structural directive for cell editors |
|
|
1146
|
+
| `TbwHeader` | `*tbwHeader` | Structural directive for the whole header cell |
|
|
1147
|
+
| `TbwHeaderLabel` | `*tbwHeaderLabel` | Structural directive for the header label only |
|
|
1100
1148
|
| `GridColumnView` | `tbw-grid-column-view` | Nested directive for cell views |
|
|
1101
1149
|
| `GridColumnEditor` | `tbw-grid-column-editor` | Nested directive for cell editors |
|
|
1102
1150
|
| `GridDetailView` | `tbw-grid-detail` | Master-detail panel template |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-selection.mjs","sources":["../../../../libs/grid-angular/features/selection/src/grid-selection.directive.ts","../../../../libs/grid-angular/features/selection/src/index.ts","../../../../libs/grid-angular/features/selection/src/toolbox-web-grid-angular-features-selection.ts"],"sourcesContent":["/**\n * `GridSelectionDirective` — owns `[selection]` and `(selectionChange)` on\n * `<tbw-grid>`. See `GridFilteringDirective` for the full rationale and\n * lifecycle contract; the same patterns apply here.\n *\n * @category Directive\n */\nimport { Directive, ElementRef, inject, input, OnDestroy, OnInit, output } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { claimEvent, registerFeatureClaim, unclaimEvent, unregisterFeatureClaim } from '@toolbox-web/grid-angular';\nimport type { SelectionChangeDetail, SelectionConfig } from '@toolbox-web/grid/plugins/selection';\n\n/**\n * Owns the binding(s) `[selection], [selectionChange]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.\n *\n * @category Directive\n */\n@Directive({\n selector: 'tbw-grid[selection], tbw-grid[selectionChange]',\n standalone: true,\n})\nexport class GridSelectionDirective implements OnInit, OnDestroy {\n private readonly elementRef = inject(ElementRef<DataGridElement>);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly selection = input<'cell' | 'row' | 'range' | SelectionConfig<any>>();\n readonly selectionChange = output<SelectionChangeDetail>();\n\n private listener?: (e: Event) => void;\n\n constructor() {\n const grid = this.elementRef.nativeElement;\n registerFeatureClaim(grid, 'selection', () => this.selection());\n claimEvent(grid, 'selection-change');\n }\n\n ngOnInit(): void {\n const grid = this.elementRef.nativeElement;\n this.listener = (e: Event) => this.selectionChange.emit((e as CustomEvent<SelectionChangeDetail>).detail);\n grid.addEventListener('selection-change', this.listener);\n }\n\n ngOnDestroy(): void {\n const grid = this.elementRef.nativeElement;\n if (this.listener) {\n grid.removeEventListener('selection-change', this.listener);\n this.listener = undefined;\n }\n unregisterFeatureClaim(grid, 'selection');\n unclaimEvent(grid, 'selection-change');\n }\n}\n","/**\n * Selection feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `selection` input on Grid directive.\n * Also exports `injectGridSelection()` for programmatic selection control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/selection';\n *\n * <tbw-grid [selection]=\"'range'\" />\n * ```\n *\n * @example Using injectGridSelection\n * ```typescript\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({...})\n * export class MyComponent {\n * private selection = injectGridSelection<Employee>();\n *\n * selectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelected() {\n * return this.selection.getSelection();\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport '@toolbox-web/grid/features/selection';\nimport {\n SelectionPlugin,\n type CellRange,\n type SelectionChangeDetail,\n type SelectionResult,\n} from '@toolbox-web/grid/plugins/selection';\nexport { GridSelectionDirective } from './grid-selection.directive';\nexport type { _Augmentation as _SelectionAugmentation } from '@toolbox-web/grid/features/selection';\n\n/**\n * Selection methods returned from injectGridSelection.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n * This ensures it works with lazy-rendered tabs, conditional rendering, etc.\n */\nexport interface SelectionMethods<TRow = unknown> {\n /**\n * Select all rows (row mode) or all cells (range mode).\n */\n selectAll: () => void;\n\n /**\n * Clear all selection.\n */\n clearSelection: () => void;\n\n /**\n * Get the current selection state (imperative, point-in-time snapshot).\n * For reactive selection state, use the `selection` signal instead.\n */\n getSelection: () => SelectionResult | null;\n\n /**\n * Check if a specific cell is selected.\n */\n isCellSelected: (row: number, col: number) => boolean;\n\n /**\n * Set selection ranges programmatically.\n */\n setRanges: (ranges: CellRange[]) => void;\n\n /**\n * Reactive selection state. Updates automatically whenever the selection changes.\n * Null when no SelectionPlugin is active or no selection has been made yet.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selection()?.ranges?.length ?? 0 }} cells selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => (this.selection.selection()?.ranges?.length ?? 0) > 0);\n * ```\n */\n selection: Signal<SelectionResult | null>;\n\n /**\n * Reactive selected row indices (sorted ascending). Updates automatically.\n * Convenience signal for row-mode selection — returns `[]` in cell/range modes\n * or when nothing is selected.\n *\n * **Prefer `selectedRows`** for getting actual row objects — it handles\n * index-to-object resolution correctly regardless of sorting/filtering.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selectedRowIndices().length }} rows selected\n * ```\n */\n selectedRowIndices: Signal<number[]>;\n\n /**\n * Reactive selected row objects. Updates automatically whenever the selection changes.\n * Works in all selection modes (row, cell, range) — returns the actual row objects\n * from the grid's processed (sorted/filtered) rows.\n *\n * This is the recommended way to get selected rows. Unlike manual index mapping,\n * it correctly resolves rows even when the grid is sorted or filtered.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection<Employee>();\n *\n * // In template:\n * // {{ selection.selectedRows().length }} rows selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => this.selection.selectedRows().length > 0);\n * ```\n */\n selectedRows: Signal<TRow[]>;\n\n /**\n * Signal indicating if grid is ready.\n * The grid is discovered lazily, so this updates when first method call succeeds.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic selection control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization. This ensures it works reliably with:\n * - Lazy-rendered tabs\n * - Conditional rendering (*ngIf)\n * - Dynamic component loading\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/selection';\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"handleSelectAll()\">Select All</button>\n * <tbw-grid [rows]=\"rows\" [selection]=\"'range'\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * selection = injectGridSelection();\n *\n * handleSelectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelectedRows() {\n * const selection = this.selection.getSelection();\n * if (!selection) return [];\n * // Derive rows from selection.ranges as needed\n * }\n * }\n * ```\n *\n * @param selector - Optional CSS selector to target a specific grid element.\n * Defaults to `'tbw-grid'` (first grid in the component). Use when the\n * component contains multiple grids, e.g. `'tbw-grid.primary'` or `'#my-grid'`.\n */\nexport function injectGridSelection<TRow = unknown>(selector = 'tbw-grid'): SelectionMethods<TRow> {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive selection state\n const selectionSignal = signal<SelectionResult | null>(null);\n const selectedRowIndicesSignal = signal<number[]>([]);\n const selectedRowsSignal = signal<TRow[]>([]);\n\n // Lazy discovery: cached grid reference\n let cachedGrid: DataGridElement<TRow> | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Handle selection-change events from the grid.\n * Updates both reactive signals.\n */\n const onSelectionChange = (detail: unknown): void => {\n const selectionDetail = detail as SelectionChangeDetail;\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n selectedRowIndicesSignal.set(selectionDetail.mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n /**\n * Attach the selection-change event listener to the grid element.\n * Called once when the grid is first discovered.\n */\n const attachListener = (grid: DataGridElement<TRow>): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n const unsub = grid.on('selection-change', onSelectionChange);\n\n destroyRef.onDestroy(() => {\n unsub();\n });\n };\n\n /**\n * Lazily find the grid element. Called on each method invocation.\n * Caches the reference once found and triggers ready() check.\n */\n const getGrid = (): DataGridElement<TRow> | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector(selector) as DataGridElement<TRow> | null;\n if (grid) {\n cachedGrid = grid;\n attachListener(grid);\n // Start ready() check only once\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => {\n if (grid.getPluginByName('selection')) {\n isReady.set(true);\n } else {\n setTimeout(() => isReady.set(true), 0);\n }\n });\n }\n }\n return grid;\n };\n\n const getPlugin = (): SelectionPlugin | undefined => {\n return getGrid()?.getPluginByName('selection') as SelectionPlugin | undefined;\n };\n\n /**\n * Sync reactive signals with the current plugin state.\n * Called once when the grid is first discovered and ready.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n const mode = (plugin as any).config?.mode;\n selectedRowIndicesSignal.set(mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n // Discover the grid after the first render so the selection-change\n // listener is attached without requiring a programmatic method call.\n // Uses a MutationObserver as fallback for lazy-rendered tabs, *ngIf,\n // @defer, etc. where the grid may not be in the DOM on first render.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(() => {\n if (grid.getPluginByName('selection')) {\n syncSignals();\n } else {\n setTimeout(syncSignals, 0);\n }\n });\n return;\n }\n\n // Grid not in DOM yet — watch for it to appear.\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(() => {\n if (discovered.getPluginByName('selection')) {\n syncSignals();\n } else {\n setTimeout(syncSignals, 0);\n }\n });\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n selection: selectionSignal.asReadonly(),\n selectedRowIndices: selectedRowIndicesSignal.asReadonly(),\n selectedRows: selectedRowsSignal.asReadonly(),\n\n selectAll: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:selection] SelectionPlugin not found.\\n\\n` +\n ` → Enable selection on the grid:\\n` +\n ` <tbw-grid [selection]=\"'range'\" />`,\n );\n return;\n }\n const grid = getGrid();\n // Cast to any to access protected config\n const mode = (plugin as any).config?.mode;\n\n if (mode === 'row') {\n const rowCount = grid?.rows?.length ?? 0;\n const allIndices = new Set<number>();\n for (let i = 0; i < rowCount; i++) allIndices.add(i);\n (plugin as any).selected = allIndices;\n (plugin as any).requestAfterRender?.();\n } else if (mode === 'range') {\n const rowCount = grid?.rows?.length ?? 0;\n const colCount = (grid as any)?._columns?.length ?? 0;\n if (rowCount > 0 && colCount > 0) {\n plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: rowCount - 1, col: colCount - 1 } }]);\n }\n }\n },\n\n clearSelection: () => {\n getPlugin()?.clearSelection();\n },\n\n getSelection: () => {\n return getPlugin()?.getSelection() ?? null;\n },\n\n isCellSelected: (row: number, col: number) => {\n return getPlugin()?.isCellSelected(row, col) ?? false;\n },\n\n setRanges: (ranges: CellRange[]) => {\n getPlugin()?.setRanges(ranges);\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolbox-web-grid-angular-features-selection.mjs","sources":["../../../../libs/grid-angular/features/selection/src/grid-selection.directive.ts","../../../../libs/grid-angular/features/selection/src/index.ts","../../../../libs/grid-angular/features/selection/src/toolbox-web-grid-angular-features-selection.ts"],"sourcesContent":["/**\n * `GridSelectionDirective` — owns `[selection]` and `(selectionChange)` on\n * `<tbw-grid>`. See `GridFilteringDirective` for the full rationale and\n * lifecycle contract; the same patterns apply here.\n *\n * @category Directive\n */\nimport { Directive, ElementRef, inject, input, OnDestroy, OnInit, output } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { claimEvent, registerFeatureClaim, unclaimEvent, unregisterFeatureClaim } from '@toolbox-web/grid-angular';\nimport type { SelectionChangeDetail, SelectionConfig } from '@toolbox-web/grid/plugins/selection';\n\n/**\n * Owns the binding(s) `[selection], [selectionChange]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.\n *\n * @category Directive\n */\n@Directive({\n selector: 'tbw-grid[selection], tbw-grid[selectionChange]',\n standalone: true,\n})\nexport class GridSelectionDirective implements OnInit, OnDestroy {\n private readonly elementRef = inject(ElementRef<DataGridElement>);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly selection = input<'cell' | 'row' | 'range' | SelectionConfig<any>>();\n readonly selectionChange = output<SelectionChangeDetail>();\n\n private listener?: (e: Event) => void;\n\n constructor() {\n const grid = this.elementRef.nativeElement;\n registerFeatureClaim(grid, 'selection', () => this.selection());\n claimEvent(grid, 'selection-change');\n }\n\n ngOnInit(): void {\n const grid = this.elementRef.nativeElement;\n this.listener = (e: Event) => this.selectionChange.emit((e as CustomEvent<SelectionChangeDetail>).detail);\n grid.addEventListener('selection-change', this.listener);\n }\n\n ngOnDestroy(): void {\n const grid = this.elementRef.nativeElement;\n if (this.listener) {\n grid.removeEventListener('selection-change', this.listener);\n this.listener = undefined;\n }\n unregisterFeatureClaim(grid, 'selection');\n unclaimEvent(grid, 'selection-change');\n }\n}\n","/**\n * Selection feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `selection` input on Grid directive.\n * Also exports `injectGridSelection()` for programmatic selection control.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/selection';\n *\n * <tbw-grid [selection]=\"'range'\" />\n * ```\n *\n * @example Using injectGridSelection\n * ```typescript\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({...})\n * export class MyComponent {\n * private selection = injectGridSelection<Employee>();\n *\n * selectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelected() {\n * return this.selection.getSelection();\n * }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport '@toolbox-web/grid/features/selection';\nimport {\n SelectionPlugin,\n type CellRange,\n type SelectionChangeDetail,\n type SelectionResult,\n} from '@toolbox-web/grid/plugins/selection';\nexport { GridSelectionDirective } from './grid-selection.directive';\nexport type { _Augmentation as _SelectionAugmentation } from '@toolbox-web/grid/features/selection';\n\n/**\n * Selection methods returned from injectGridSelection.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n * This ensures it works with lazy-rendered tabs, conditional rendering, etc.\n */\nexport interface SelectionMethods<TRow = unknown> {\n /**\n * Select all rows (row mode) or all cells (range mode).\n */\n selectAll: () => void;\n\n /**\n * Clear all selection.\n */\n clearSelection: () => void;\n\n /**\n * Get the current selection state (imperative, point-in-time snapshot).\n * For reactive selection state, use the `selection` signal instead.\n */\n getSelection: () => SelectionResult | null;\n\n /**\n * Check if a specific cell is selected.\n */\n isCellSelected: (row: number, col: number) => boolean;\n\n /**\n * Set selection ranges programmatically.\n */\n setRanges: (ranges: CellRange[]) => void;\n\n /**\n * Get actual row objects for the current selection (imperative, point-in-time\n * snapshot). Works in all selection modes (row, cell, range) — resolves\n * indices against the grid's processed (sorted/filtered) rows.\n *\n * For reactive selected rows, use the `selectedRows` signal instead.\n */\n getSelectedRows: () => TRow[];\n\n /**\n * Reactive selection state. Updates automatically whenever the selection changes.\n * Null when no SelectionPlugin is active or no selection has been made yet.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selection()?.ranges?.length ?? 0 }} cells selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => (this.selection.selection()?.ranges?.length ?? 0) > 0);\n * ```\n */\n selection: Signal<SelectionResult | null>;\n\n /**\n * Reactive selected row indices (sorted ascending). Updates automatically.\n * Convenience signal for row-mode selection — returns `[]` in cell/range modes\n * or when nothing is selected.\n *\n * **Prefer `selectedRows`** for getting actual row objects — it handles\n * index-to-object resolution correctly regardless of sorting/filtering.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection();\n *\n * // In template:\n * // {{ selection.selectedRowIndices().length }} rows selected\n * ```\n */\n selectedRowIndices: Signal<number[]>;\n\n /**\n * Reactive selected row objects. Updates automatically whenever the selection changes.\n * Works in all selection modes (row, cell, range) — returns the actual row objects\n * from the grid's processed (sorted/filtered) rows.\n *\n * This is the recommended way to get selected rows. Unlike manual index mapping,\n * it correctly resolves rows even when the grid is sorted or filtered.\n *\n * @example\n * ```typescript\n * readonly selection = injectGridSelection<Employee>();\n *\n * // In template:\n * // {{ selection.selectedRows().length }} rows selected\n *\n * // In computed:\n * readonly hasSelection = computed(() => this.selection.selectedRows().length > 0);\n * ```\n */\n selectedRows: Signal<TRow[]>;\n\n /**\n * Signal indicating if grid is ready.\n * The grid is discovered lazily, so this updates when first method call succeeds.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic selection control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization. This ensures it works reliably with:\n * - Lazy-rendered tabs\n * - Conditional rendering (*ngIf)\n * - Dynamic component loading\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/selection';\n * import { injectGridSelection } from '@toolbox-web/grid-angular/features/selection';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"handleSelectAll()\">Select All</button>\n * <tbw-grid [rows]=\"rows\" [selection]=\"'range'\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * selection = injectGridSelection();\n *\n * handleSelectAll() {\n * this.selection.selectAll();\n * }\n *\n * getSelectedRows() {\n * const selection = this.selection.getSelection();\n * if (!selection) return [];\n * // Derive rows from selection.ranges as needed\n * }\n * }\n * ```\n *\n * @param selector - Optional CSS selector to target a specific grid element.\n * Defaults to `'tbw-grid'` (first grid in the component). Use when the\n * component contains multiple grids, e.g. `'tbw-grid.primary'` or `'#my-grid'`.\n */\nexport function injectGridSelection<TRow = unknown>(selector = 'tbw-grid'): SelectionMethods<TRow> {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive selection state\n const selectionSignal = signal<SelectionResult | null>(null);\n const selectedRowIndicesSignal = signal<number[]>([]);\n const selectedRowsSignal = signal<TRow[]>([]);\n\n // Lazy discovery: cached grid reference\n let cachedGrid: DataGridElement<TRow> | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Handle selection-change events from the grid.\n * Updates both reactive signals.\n */\n const onSelectionChange = (detail: unknown): void => {\n const selectionDetail = detail as SelectionChangeDetail;\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n selectedRowIndicesSignal.set(selectionDetail.mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n /**\n * Attach the selection-change event listener to the grid element.\n * Called once when the grid is first discovered.\n */\n const attachListener = (grid: DataGridElement<TRow>): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n const unsub = grid.on('selection-change', onSelectionChange);\n\n destroyRef.onDestroy(() => {\n unsub();\n });\n };\n\n /**\n * Lazily find the grid element. Called on each method invocation.\n * Caches the reference once found and triggers ready() check.\n */\n const getGrid = (): DataGridElement<TRow> | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector(selector) as DataGridElement<TRow> | null;\n if (grid) {\n cachedGrid = grid;\n attachListener(grid);\n // Start ready() check only once\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => {\n if (grid.getPluginByName('selection')) {\n isReady.set(true);\n } else {\n setTimeout(() => isReady.set(true), 0);\n }\n });\n }\n }\n return grid;\n };\n\n const getPlugin = (): SelectionPlugin | undefined => {\n return getGrid()?.getPluginByName('selection') as SelectionPlugin | undefined;\n };\n\n /**\n * Sync reactive signals with the current plugin state.\n * Called once when the grid is first discovered and ready.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n selectionSignal.set(plugin.getSelection());\n const mode = (plugin as any).config?.mode;\n selectedRowIndicesSignal.set(mode === 'row' ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n }\n };\n\n // Discover the grid after the first render so the selection-change\n // listener is attached without requiring a programmatic method call.\n // Uses a MutationObserver as fallback for lazy-rendered tabs, *ngIf,\n // @defer, etc. where the grid may not be in the DOM on first render.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(() => {\n if (grid.getPluginByName('selection')) {\n syncSignals();\n } else {\n setTimeout(syncSignals, 0);\n }\n });\n return;\n }\n\n // Grid not in DOM yet — watch for it to appear.\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(() => {\n if (discovered.getPluginByName('selection')) {\n syncSignals();\n } else {\n setTimeout(syncSignals, 0);\n }\n });\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n selection: selectionSignal.asReadonly(),\n selectedRowIndices: selectedRowIndicesSignal.asReadonly(),\n selectedRows: selectedRowsSignal.asReadonly(),\n\n selectAll: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:selection] SelectionPlugin not found.\\n\\n` +\n ` → Enable selection on the grid:\\n` +\n ` <tbw-grid [selection]=\"'range'\" />`,\n );\n return;\n }\n const grid = getGrid();\n // Cast to any to access protected config\n const mode = (plugin as any).config?.mode;\n\n if (mode === 'row') {\n const rowCount = grid?.rows?.length ?? 0;\n const allIndices = new Set<number>();\n for (let i = 0; i < rowCount; i++) allIndices.add(i);\n (plugin as any).selected = allIndices;\n (plugin as any).requestAfterRender?.();\n } else if (mode === 'range') {\n const rowCount = grid?.rows?.length ?? 0;\n const colCount = (grid as any)?._columns?.length ?? 0;\n if (rowCount > 0 && colCount > 0) {\n plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: rowCount - 1, col: colCount - 1 } }]);\n }\n }\n },\n\n clearSelection: () => {\n getPlugin()?.clearSelection();\n },\n\n getSelection: () => {\n return getPlugin()?.getSelection() ?? null;\n },\n\n isCellSelected: (row: number, col: number) => {\n return getPlugin()?.isCellSelected(row, col) ?? false;\n },\n\n setRanges: (ranges: CellRange[]) => {\n getPlugin()?.setRanges(ranges);\n },\n\n getSelectedRows: () => {\n return getPlugin()?.getSelectedRows<TRow>() ?? [];\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject,
|
|
2
|
+
import { inject, TemplateRef, ElementRef, effect, Directive, contentChild, InjectionToken, Injectable, makeEnvironmentProviders, createComponent, DestroyRef, signal, afterNextRender, computed, EnvironmentInjector, ApplicationRef, ViewContainerRef, untracked, input, output } from '@angular/core';
|
|
3
3
|
import { createPluginFromFeature } from '@toolbox-web/grid/features/registry';
|
|
4
4
|
export { clearFeatureRegistry, createPluginFromFeature, getFeatureFactory, getRegisteredFeatures, isFeatureRegistered, registerFeature } from '@toolbox-web/grid/features/registry';
|
|
5
5
|
import { applyColumnDefaults, normalizeColumns, DataGridElement } from '@toolbox-web/grid';
|
|
@@ -33,6 +33,135 @@ function isComponentClass(value) {
|
|
|
33
33
|
}
|
|
34
34
|
// #endregion
|
|
35
35
|
|
|
36
|
+
const headerRegistry = new Map();
|
|
37
|
+
const headerLabelRegistry = new Map();
|
|
38
|
+
/**
|
|
39
|
+
* Get the `*tbwHeader` template registered for a column element.
|
|
40
|
+
* Used by `AngularGridAdapter.createHeaderRenderer`.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function getHeaderTemplate(columnElement) {
|
|
45
|
+
return headerRegistry.get(columnElement);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the `*tbwHeaderLabel` template registered for a column element.
|
|
49
|
+
* Used by `AngularGridAdapter.createHeaderLabelRenderer`.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
function getHeaderLabelTemplate(columnElement) {
|
|
54
|
+
return headerLabelRegistry.get(columnElement);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Walk up from a structural directive's comment anchor to the owning
|
|
58
|
+
* `<tbw-grid-column>` / `<tbw-grid-type>` element.
|
|
59
|
+
*/
|
|
60
|
+
function findColumnElement(start) {
|
|
61
|
+
let parent = start?.parentElement;
|
|
62
|
+
while (parent && parent.tagName !== 'TBW-GRID-COLUMN' && parent.tagName !== 'TBW-GRID-TYPE') {
|
|
63
|
+
parent = parent.parentElement;
|
|
64
|
+
}
|
|
65
|
+
return parent ?? null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Structural directive that supplies a full header cell template for a column.
|
|
69
|
+
*
|
|
70
|
+
* You own the header content: use `renderSortIcon()` and `renderFilterButton()`
|
|
71
|
+
* from the context to opt back into the built-in affordances. Resize handles
|
|
72
|
+
* are appended automatically for resizable columns.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```html
|
|
76
|
+
* <tbw-grid-column field="salary">
|
|
77
|
+
* <span *tbwHeader="let value; column as column">
|
|
78
|
+
* 💰 {{ value }}
|
|
79
|
+
* </span>
|
|
80
|
+
* </tbw-grid-column>
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @category Directive
|
|
84
|
+
* @since 2.5.0
|
|
85
|
+
*/
|
|
86
|
+
class TbwHeader {
|
|
87
|
+
template = inject((TemplateRef));
|
|
88
|
+
elementRef = inject((ElementRef));
|
|
89
|
+
columnElement = null;
|
|
90
|
+
constructor() {
|
|
91
|
+
effect(() => {
|
|
92
|
+
const parent = findColumnElement(this.elementRef.nativeElement);
|
|
93
|
+
if (parent) {
|
|
94
|
+
this.columnElement = parent;
|
|
95
|
+
headerRegistry.set(parent, this.template);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
ngOnDestroy() {
|
|
100
|
+
if (this.columnElement) {
|
|
101
|
+
headerRegistry.delete(this.columnElement);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Static type guard so `let value` / `column as column` infer correctly. */
|
|
105
|
+
static ngTemplateContextGuard(dir, ctx) {
|
|
106
|
+
void dir;
|
|
107
|
+
void ctx;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TbwHeader, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
111
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: TbwHeader, isStandalone: true, selector: "[tbwHeader]", ngImport: i0 });
|
|
112
|
+
}
|
|
113
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TbwHeader, decorators: [{
|
|
114
|
+
type: Directive,
|
|
115
|
+
args: [{ selector: '[tbwHeader]' }]
|
|
116
|
+
}], ctorParameters: () => [] });
|
|
117
|
+
/**
|
|
118
|
+
* Structural directive that supplies a header *label* template for a column.
|
|
119
|
+
*
|
|
120
|
+
* The grid keeps ownership of sort icons, filter buttons, and resize handles;
|
|
121
|
+
* only the label text is replaced.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```html
|
|
125
|
+
* <tbw-grid-column field="salary">
|
|
126
|
+
* <strong *tbwHeaderLabel="let value">{{ value }}</strong>
|
|
127
|
+
* </tbw-grid-column>
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @category Directive
|
|
131
|
+
* @since 2.5.0
|
|
132
|
+
*/
|
|
133
|
+
class TbwHeaderLabel {
|
|
134
|
+
template = inject((TemplateRef));
|
|
135
|
+
elementRef = inject((ElementRef));
|
|
136
|
+
columnElement = null;
|
|
137
|
+
constructor() {
|
|
138
|
+
effect(() => {
|
|
139
|
+
const parent = findColumnElement(this.elementRef.nativeElement);
|
|
140
|
+
if (parent) {
|
|
141
|
+
this.columnElement = parent;
|
|
142
|
+
headerLabelRegistry.set(parent, this.template);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
ngOnDestroy() {
|
|
147
|
+
if (this.columnElement) {
|
|
148
|
+
headerLabelRegistry.delete(this.columnElement);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Static type guard so `let value` infers correctly. */
|
|
152
|
+
static ngTemplateContextGuard(dir, ctx) {
|
|
153
|
+
void dir;
|
|
154
|
+
void ctx;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TbwHeaderLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
158
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.6", type: TbwHeaderLabel, isStandalone: true, selector: "[tbwHeaderLabel]", ngImport: i0 });
|
|
159
|
+
}
|
|
160
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TbwHeaderLabel, decorators: [{
|
|
161
|
+
type: Directive,
|
|
162
|
+
args: [{ selector: '[tbwHeaderLabel]' }]
|
|
163
|
+
}], ctorParameters: () => [] });
|
|
164
|
+
|
|
36
165
|
// Global registry mapping DOM elements to their templates
|
|
37
166
|
const templateRegistry = new Map();
|
|
38
167
|
/**
|
|
@@ -975,7 +1104,68 @@ class GridAdapter {
|
|
|
975
1104
|
* view template, or a template-based editor resolved by the editing feature).
|
|
976
1105
|
*/
|
|
977
1106
|
canHandle(element) {
|
|
978
|
-
return getAnyViewTemplate(element) !== undefined ||
|
|
1107
|
+
return (getAnyViewTemplate(element) !== undefined ||
|
|
1108
|
+
getHeaderTemplate(element) !== undefined ||
|
|
1109
|
+
getHeaderLabelTemplate(element) !== undefined ||
|
|
1110
|
+
getEditorSpecBridge()?.(element, this) !== undefined);
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Creates a DOM-returning header renderer for a `<tbw-grid-column>` element
|
|
1114
|
+
* that declared a `*tbwHeader` template. Returns undefined when none was
|
|
1115
|
+
* declared, letting the grid fall back to its built-in header.
|
|
1116
|
+
*
|
|
1117
|
+
* Angular counterpart of React's `<GridColumn headerRenderer>` render prop
|
|
1118
|
+
* and Vue's `#header` slot.
|
|
1119
|
+
*/
|
|
1120
|
+
createHeaderRenderer(element) {
|
|
1121
|
+
const template = getHeaderTemplate(element);
|
|
1122
|
+
if (!template)
|
|
1123
|
+
return undefined;
|
|
1124
|
+
return (ctx) => {
|
|
1125
|
+
const context = {
|
|
1126
|
+
$implicit: ctx.value,
|
|
1127
|
+
value: ctx.value,
|
|
1128
|
+
column: ctx.column,
|
|
1129
|
+
sortState: ctx.sortState,
|
|
1130
|
+
filterActive: ctx.filterActive,
|
|
1131
|
+
cellEl: ctx.cellEl,
|
|
1132
|
+
renderSortIcon: ctx.renderSortIcon,
|
|
1133
|
+
renderFilterButton: ctx.renderFilterButton,
|
|
1134
|
+
};
|
|
1135
|
+
return this.renderTemplateToContainer(template, context);
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Creates a DOM-returning header *label* renderer for a `<tbw-grid-column>`
|
|
1140
|
+
* element that declared a `*tbwHeaderLabel` template. Returns undefined when
|
|
1141
|
+
* none was declared.
|
|
1142
|
+
*/
|
|
1143
|
+
createHeaderLabelRenderer(element) {
|
|
1144
|
+
const template = getHeaderLabelTemplate(element);
|
|
1145
|
+
if (!template)
|
|
1146
|
+
return undefined;
|
|
1147
|
+
return (ctx) => {
|
|
1148
|
+
const context = {
|
|
1149
|
+
$implicit: ctx.value,
|
|
1150
|
+
value: ctx.value,
|
|
1151
|
+
column: ctx.column,
|
|
1152
|
+
};
|
|
1153
|
+
return this.renderTemplateToContainer(template, context);
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Create an embedded view from `template` and return a stable
|
|
1158
|
+
* `display: contents` wrapper holding its root nodes. Headers re-render from
|
|
1159
|
+
* scratch on every grid render, so no per-cell caching is needed here.
|
|
1160
|
+
*/
|
|
1161
|
+
renderTemplateToContainer(template, context) {
|
|
1162
|
+
const viewRef = this.viewContainerRef.createEmbeddedView(template, context);
|
|
1163
|
+
this.viewRefs.push(viewRef);
|
|
1164
|
+
viewRef.detectChanges();
|
|
1165
|
+
const container = document.createElement('span');
|
|
1166
|
+
container.style.display = 'contents';
|
|
1167
|
+
syncRootNodes(viewRef, container);
|
|
1168
|
+
return container;
|
|
979
1169
|
}
|
|
980
1170
|
/**
|
|
981
1171
|
* Creates a view renderer function that creates an embedded view
|
|
@@ -1787,9 +1977,12 @@ function injectGrid(selector = 'tbw-grid') {
|
|
|
1787
1977
|
const gridElement = element();
|
|
1788
1978
|
if (!gridElement)
|
|
1789
1979
|
return null;
|
|
1790
|
-
const effectiveConfig = gridElement.getConfig?.();
|
|
1980
|
+
const effectiveConfig = await gridElement.getConfig?.();
|
|
1791
1981
|
return effectiveConfig ?? null;
|
|
1792
1982
|
};
|
|
1983
|
+
const ready = async () => {
|
|
1984
|
+
await element()?.ready?.();
|
|
1985
|
+
};
|
|
1793
1986
|
const forceLayout = async () => {
|
|
1794
1987
|
const gridElement = element();
|
|
1795
1988
|
if (!gridElement)
|
|
@@ -1821,6 +2014,7 @@ function injectGrid(selector = 'tbw-grid') {
|
|
|
1821
2014
|
config,
|
|
1822
2015
|
visibleColumns,
|
|
1823
2016
|
getConfig,
|
|
2017
|
+
ready,
|
|
1824
2018
|
forceLayout,
|
|
1825
2019
|
toggleGroup,
|
|
1826
2020
|
registerStyles,
|
|
@@ -2188,8 +2382,11 @@ class Grid {
|
|
|
2188
2382
|
const angularCfg = userGridConfig;
|
|
2189
2383
|
if (!this.adapter)
|
|
2190
2384
|
return;
|
|
2191
|
-
// Create plugins from feature inputs
|
|
2192
|
-
|
|
2385
|
+
// Create plugins from feature inputs. The manual `plugins` input is an
|
|
2386
|
+
// escape hatch: when present it takes over entirely, matching
|
|
2387
|
+
// `<DataGrid plugins={...}>` (React) and `<TbwGrid :plugins>` (Vue).
|
|
2388
|
+
const manualPlugins = this.plugins();
|
|
2389
|
+
const featurePlugins = manualPlugins ? [] : this.createFeaturePlugins();
|
|
2193
2390
|
// Build core config overrides from individual inputs
|
|
2194
2391
|
const sortableValue = this.sortable();
|
|
2195
2392
|
const filterableValue = this.filterable();
|
|
@@ -2214,14 +2411,18 @@ class Grid {
|
|
|
2214
2411
|
}
|
|
2215
2412
|
// Nothing to do if there's no config input and no feature inputs
|
|
2216
2413
|
const hasFeaturePlugins = featurePlugins.length > 0;
|
|
2414
|
+
// Provided-vs-not, not non-empty: `[plugins]="[]"` is a deliberate "no
|
|
2415
|
+
// plugins" override, and `.length > 0` would early-return and leave a
|
|
2416
|
+
// previously applied set active. Matches React's `if (manualPlugins)`.
|
|
2417
|
+
const hasManualPlugins = manualPlugins !== undefined;
|
|
2217
2418
|
const hasConfigOverrides = Object.keys(coreConfigOverrides).length > 0;
|
|
2218
|
-
if (!angularCfg && !hasFeaturePlugins && !hasConfigOverrides) {
|
|
2419
|
+
if (!angularCfg && !hasFeaturePlugins && !hasManualPlugins && !hasConfigOverrides) {
|
|
2219
2420
|
return;
|
|
2220
2421
|
}
|
|
2221
2422
|
const userConfig = angularCfg || {};
|
|
2222
|
-
// Merge feature-input plugins with the user's own plugins
|
|
2423
|
+
// Merge manual + feature-input plugins with the user's own plugins
|
|
2223
2424
|
const configPlugins = userConfig.plugins || [];
|
|
2224
|
-
const mergedPlugins = [...featurePlugins, ...configPlugins];
|
|
2425
|
+
const mergedPlugins = [...(manualPlugins ?? []), ...featurePlugins, ...configPlugins];
|
|
2225
2426
|
// The interceptor on element.gridConfig (installed in ngOnInit)
|
|
2226
2427
|
// handles converting component classes → functions via processGridConfig,
|
|
2227
2428
|
// so we can pass the raw Angular config through. The interceptor is
|
|
@@ -2229,7 +2430,9 @@ class Grid {
|
|
|
2229
2430
|
grid.gridConfig = {
|
|
2230
2431
|
...userConfig,
|
|
2231
2432
|
...coreConfigOverrides,
|
|
2232
|
-
|
|
2433
|
+
// Only fall back to the user's own list when `plugins` was not provided;
|
|
2434
|
+
// an explicit empty input must win over it.
|
|
2435
|
+
plugins: hasManualPlugins || mergedPlugins.length > 0 ? mergedPlugins : userConfig.plugins,
|
|
2233
2436
|
};
|
|
2234
2437
|
});
|
|
2235
2438
|
// Effect to sync loading state to the grid element
|
|
@@ -2551,6 +2754,26 @@ class Grid {
|
|
|
2551
2754
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2552
2755
|
gridConfig = input(/* @ts-ignore */
|
|
2553
2756
|
...(ngDevMode ? [undefined, { debugName: "gridConfig" }] : /* istanbul ignore next */ []));
|
|
2757
|
+
/**
|
|
2758
|
+
* Manually instantiated plugins (escape hatch for advanced configuration).
|
|
2759
|
+
* When provided, per-feature directive inputs are ignored — only plugins
|
|
2760
|
+
* from this list plus any declared in `gridConfig.plugins` are used.
|
|
2761
|
+
*
|
|
2762
|
+
* @example
|
|
2763
|
+
* ```ts
|
|
2764
|
+
* import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
|
|
2765
|
+
*
|
|
2766
|
+
* plugins = [new SelectionPlugin({ mode: 'range', checkbox: true })];
|
|
2767
|
+
* ```
|
|
2768
|
+
*
|
|
2769
|
+
* ```html
|
|
2770
|
+
* <tbw-grid [rows]="employees()" [plugins]="plugins"></tbw-grid>
|
|
2771
|
+
* ```
|
|
2772
|
+
*
|
|
2773
|
+
* @since 2.5.0
|
|
2774
|
+
*/
|
|
2775
|
+
plugins = input(/* @ts-ignore */
|
|
2776
|
+
...(ngDevMode ? [undefined, { debugName: "plugins" }] : /* istanbul ignore next */ []));
|
|
2554
2777
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
2555
2778
|
// EVENT OUTPUTS - All grid events
|
|
2556
2779
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -2863,12 +3086,12 @@ class Grid {
|
|
|
2863
3086
|
}
|
|
2864
3087
|
}
|
|
2865
3088
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: Grid, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
2866
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: Grid, isStandalone: true, selector: "tbw-grid,[data-tbw-grid]", inputs: { customStyles: { classPropertyName: "customStyles", publicName: "customStyles", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, columnDefaults: { classPropertyName: "columnDefaults", publicName: "columnDefaults", isSignal: true, isRequired: false, transformFunction: null }, fitMode: { classPropertyName: "fitMode", publicName: "fitMode", isSignal: true, isRequired: false, transformFunction: null }, columnInference: { classPropertyName: "columnInference", publicName: "columnInference", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellClick: "cellClick", rowClick: "rowClick", cellActivate: "cellActivate", cellChange: "cellChange", dataChange: "dataChange", sortChange: "sortChange", columnResize: "columnResize", columnResizeReset: "columnResizeReset", columnStateChange: "columnStateChange", tbwScroll: "tbwScroll", render: "render" }, ngImport: i0 });
|
|
3089
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: Grid, isStandalone: true, selector: "tbw-grid,[data-tbw-grid]", inputs: { customStyles: { classPropertyName: "customStyles", publicName: "customStyles", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, columnDefaults: { classPropertyName: "columnDefaults", publicName: "columnDefaults", isSignal: true, isRequired: false, transformFunction: null }, fitMode: { classPropertyName: "fitMode", publicName: "fitMode", isSignal: true, isRequired: false, transformFunction: null }, columnInference: { classPropertyName: "columnInference", publicName: "columnInference", isSignal: true, isRequired: false, transformFunction: null }, gridConfig: { classPropertyName: "gridConfig", publicName: "gridConfig", isSignal: true, isRequired: false, transformFunction: null }, plugins: { classPropertyName: "plugins", publicName: "plugins", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cellClick: "cellClick", rowClick: "rowClick", cellActivate: "cellActivate", cellChange: "cellChange", dataChange: "dataChange", sortChange: "sortChange", columnResize: "columnResize", columnResizeReset: "columnResizeReset", columnStateChange: "columnStateChange", tbwScroll: "tbwScroll", render: "render" }, ngImport: i0 });
|
|
2867
3090
|
}
|
|
2868
3091
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: Grid, decorators: [{
|
|
2869
3092
|
type: Directive,
|
|
2870
3093
|
args: [{ selector: 'tbw-grid,[data-tbw-grid]' }]
|
|
2871
|
-
}], ctorParameters: () => [], propDecorators: { customStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyles", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], columnDefaults: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnDefaults", required: false }] }], fitMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitMode", required: false }] }], columnInference: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnInference", required: false }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", required: false }] }], cellClick: [{ type: i0.Output, args: ["cellClick"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], cellActivate: [{ type: i0.Output, args: ["cellActivate"] }], cellChange: [{ type: i0.Output, args: ["cellChange"] }], dataChange: [{ type: i0.Output, args: ["dataChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnResizeReset: [{ type: i0.Output, args: ["columnResizeReset"] }], columnStateChange: [{ type: i0.Output, args: ["columnStateChange"] }], tbwScroll: [{ type: i0.Output, args: ["tbwScroll"] }], render: [{ type: i0.Output, args: ["render"] }] } });
|
|
3094
|
+
}], ctorParameters: () => [], propDecorators: { customStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyles", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], columnDefaults: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnDefaults", required: false }] }], fitMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitMode", required: false }] }], columnInference: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnInference", required: false }] }], gridConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridConfig", required: false }] }], plugins: [{ type: i0.Input, args: [{ isSignal: true, alias: "plugins", required: false }] }], cellClick: [{ type: i0.Output, args: ["cellClick"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], cellActivate: [{ type: i0.Output, args: ["cellActivate"] }], cellChange: [{ type: i0.Output, args: ["cellChange"] }], dataChange: [{ type: i0.Output, args: ["dataChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnResizeReset: [{ type: i0.Output, args: ["columnResizeReset"] }], columnStateChange: [{ type: i0.Output, args: ["columnStateChange"] }], tbwScroll: [{ type: i0.Output, args: ["tbwScroll"] }], render: [{ type: i0.Output, args: ["render"] }] } });
|
|
2872
3095
|
|
|
2873
3096
|
/**
|
|
2874
3097
|
* Combined provider helper for grid type defaults and icons.
|
|
@@ -2931,5 +3154,5 @@ function provideGrid(options = {}) {
|
|
|
2931
3154
|
* Generated bundle index. Do not edit.
|
|
2932
3155
|
*/
|
|
2933
3156
|
|
|
2934
|
-
export { GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridAdapter, GridColumnView, GridIconRegistry, GridTypeRegistry, TbwGridColumn, TbwGridHeader, TbwGridToolButtons, TbwGridType, TbwRenderer, claimEvent, getFeatureClaim, getFeatureConfigPreprocessor, injectGrid, isComponentClass, isEventClaimed, makeFlushFocusedInput, provideGrid, provideGridIcons, provideGridTypeDefaults, registerDetailRendererBridge, registerEditorMountHook, registerEditorSpecBridge, registerFeatureClaim, registerFeatureConfigPreprocessor, registerFilterPanelTypeDefaultBridge, registerResponsiveCardRendererBridge, registerTemplateBridge, registerToolPanelRendererBridge, runTemplateBridges, unclaimEvent, unregisterFeatureClaim };
|
|
3157
|
+
export { GRID_ICONS, GRID_TYPE_DEFAULTS, Grid, GridAdapter, GridColumnView, GridIconRegistry, GridTypeRegistry, TbwGridColumn, TbwGridHeader, TbwGridToolButtons, TbwGridType, TbwHeader, TbwHeaderLabel, TbwRenderer, claimEvent, getFeatureClaim, getFeatureConfigPreprocessor, injectGrid, isComponentClass, isEventClaimed, makeFlushFocusedInput, provideGrid, provideGridIcons, provideGridTypeDefaults, registerDetailRendererBridge, registerEditorMountHook, registerEditorSpecBridge, registerFeatureClaim, registerFeatureConfigPreprocessor, registerFilterPanelTypeDefaultBridge, registerResponsiveCardRendererBridge, registerTemplateBridge, registerToolPanelRendererBridge, runTemplateBridges, unclaimEvent, unregisterFeatureClaim };
|
|
2935
3158
|
//# sourceMappingURL=toolbox-web-grid-angular.mjs.map
|