@toolbox-web/grid-angular 2.6.0 → 2.6.2
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/README.md +2 -0
- package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs +14 -6
- package/fesm2022/toolbox-web-grid-angular-features-grouping-rows.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs +39 -20
- package/fesm2022/toolbox-web-grid-angular-features-master-detail.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +15 -36
- package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +2 -2
- package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -1
- package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts +10 -3
- package/types/toolbox-web-grid-angular-features-grouping-rows.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular-features-master-detail.d.ts +12 -4
- package/types/toolbox-web-grid-angular-features-master-detail.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular-features-selection.d.ts +3 -1
- package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +3 -1
- package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -1
- package/types/toolbox-web-grid-angular.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1204,11 +1204,13 @@ export class TextFilterComponent extends BaseFilterPanel {
|
|
|
1204
1204
|
| `selectionChange` | `OutputEmitterRef<SelectionChangeDetail>` | Selection changed |
|
|
1205
1205
|
| `rowMove` | `OutputEmitterRef<RowMoveDetail>` | Row moved (drag & drop) |
|
|
1206
1206
|
| `groupToggle` | `OutputEmitterRef<GroupToggleDetail>` | Group expanded/collapsed |
|
|
1207
|
+
| `groupLoadError` | `OutputEmitterRef<GroupLoadErrorDetail>` | Server group rows failed |
|
|
1207
1208
|
| `treeExpand` | `OutputEmitterRef<TreeExpandDetail>` | Tree node expanded/collapsed |
|
|
1208
1209
|
| `treeLoadStart` | `OutputEmitterRef<TreeLoadStartDetail>` | Lazy child loading started |
|
|
1209
1210
|
| `treeLoadEnd` | `OutputEmitterRef<TreeLoadEndDetail>` | Lazy child loading finished |
|
|
1210
1211
|
| `treeLoadError` | `OutputEmitterRef<TreeLoadErrorDetail>` | Lazy child loading failed |
|
|
1211
1212
|
| `detailExpand` | `OutputEmitterRef<DetailExpandDetail>` | Detail panel toggled |
|
|
1213
|
+
| `detailLoadError` | `OutputEmitterRef<DetailLoadErrorDetail>` | Server detail data failed |
|
|
1212
1214
|
| `responsiveChange` | `OutputEmitterRef<ResponsiveChangeDetail>` | Responsive mode changed |
|
|
1213
1215
|
| `copy` | `OutputEmitterRef<CopyDetail>` | Data copied to clipboard |
|
|
1214
1216
|
| `paste` | `OutputEmitterRef<PasteDetail>` | Data pasted from clipboard |
|
|
@@ -5,13 +5,13 @@ import { inject, ElementRef, input, output, Directive } from '@angular/core';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* `GridGroupingRowsDirective` — owns `[groupingRows]` and the group
|
|
8
|
-
* toggle/expand/collapse outputs on `<tbw-grid>`. See
|
|
8
|
+
* toggle/expand/collapse/load-error outputs on `<tbw-grid>`. See
|
|
9
9
|
* `GridFilteringDirective` for the full rationale.
|
|
10
10
|
*
|
|
11
11
|
* @category Directive
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
|
-
* Owns the binding(s) `[groupingRows], [groupToggle], [groupExpand], [groupCollapse]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.
|
|
14
|
+
* Owns the binding(s) `[groupingRows], [groupToggle], [groupExpand], [groupCollapse], [groupLoadError]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.
|
|
15
15
|
*
|
|
16
16
|
* @category Directive
|
|
17
17
|
*/
|
|
@@ -22,8 +22,15 @@ class GridGroupingRowsDirective {
|
|
|
22
22
|
groupToggle = output();
|
|
23
23
|
groupExpand = output();
|
|
24
24
|
groupCollapse = output();
|
|
25
|
+
/**
|
|
26
|
+
* Fired when loading a pre-defined group's rows through the ServerSide data
|
|
27
|
+
* source (`getChildRows()`) fails. Re-expanding the group retries the fetch.
|
|
28
|
+
*
|
|
29
|
+
* @since 2.7.0
|
|
30
|
+
*/
|
|
31
|
+
groupLoadError = output();
|
|
25
32
|
listeners = new Map();
|
|
26
|
-
static EVENTS = ['group-toggle', 'group-expand', 'group-collapse'];
|
|
33
|
+
static EVENTS = ['group-toggle', 'group-expand', 'group-collapse', 'group-load-error'];
|
|
27
34
|
constructor() {
|
|
28
35
|
const grid = this.elementRef.nativeElement;
|
|
29
36
|
registerFeatureClaim(grid, 'groupingRows', () => this.groupingRows());
|
|
@@ -40,6 +47,7 @@ class GridGroupingRowsDirective {
|
|
|
40
47
|
wire('group-toggle', this.groupToggle);
|
|
41
48
|
wire('group-expand', this.groupExpand);
|
|
42
49
|
wire('group-collapse', this.groupCollapse);
|
|
50
|
+
wire('group-load-error', this.groupLoadError);
|
|
43
51
|
}
|
|
44
52
|
ngOnDestroy() {
|
|
45
53
|
const grid = this.elementRef.nativeElement;
|
|
@@ -51,15 +59,15 @@ class GridGroupingRowsDirective {
|
|
|
51
59
|
unclaimEvent(grid, ev);
|
|
52
60
|
}
|
|
53
61
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: GridGroupingRowsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
54
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: GridGroupingRowsDirective, isStandalone: true, selector: "tbw-grid[groupingRows], tbw-grid[groupToggle], tbw-grid[groupExpand], tbw-grid[groupCollapse]", inputs: { groupingRows: { classPropertyName: "groupingRows", publicName: "groupingRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { groupToggle: "groupToggle", groupExpand: "groupExpand", groupCollapse: "groupCollapse" }, ngImport: i0 });
|
|
62
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: GridGroupingRowsDirective, isStandalone: true, selector: "tbw-grid[groupingRows], tbw-grid[groupToggle], tbw-grid[groupExpand], tbw-grid[groupCollapse], tbw-grid[groupLoadError]", inputs: { groupingRows: { classPropertyName: "groupingRows", publicName: "groupingRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { groupToggle: "groupToggle", groupExpand: "groupExpand", groupCollapse: "groupCollapse", groupLoadError: "groupLoadError" }, ngImport: i0 });
|
|
55
63
|
}
|
|
56
64
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: GridGroupingRowsDirective, decorators: [{
|
|
57
65
|
type: Directive,
|
|
58
66
|
args: [{
|
|
59
|
-
selector: 'tbw-grid[groupingRows], tbw-grid[groupToggle], tbw-grid[groupExpand], tbw-grid[groupCollapse]',
|
|
67
|
+
selector: 'tbw-grid[groupingRows], tbw-grid[groupToggle], tbw-grid[groupExpand], tbw-grid[groupCollapse], tbw-grid[groupLoadError]',
|
|
60
68
|
standalone: true,
|
|
61
69
|
}]
|
|
62
|
-
}], ctorParameters: () => [], propDecorators: { groupingRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupingRows", required: false }] }], groupToggle: [{ type: i0.Output, args: ["groupToggle"] }], groupExpand: [{ type: i0.Output, args: ["groupExpand"] }], groupCollapse: [{ type: i0.Output, args: ["groupCollapse"] }] } });
|
|
70
|
+
}], ctorParameters: () => [], propDecorators: { groupingRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupingRows", required: false }] }], groupToggle: [{ type: i0.Output, args: ["groupToggle"] }], groupExpand: [{ type: i0.Output, args: ["groupExpand"] }], groupCollapse: [{ type: i0.Output, args: ["groupCollapse"] }], groupLoadError: [{ type: i0.Output, args: ["groupLoadError"] }] } });
|
|
63
71
|
|
|
64
72
|
/**
|
|
65
73
|
* Row grouping feature for @toolbox-web/grid-angular
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-grouping-rows.mjs","sources":["../../../../libs/grid-angular/features/grouping-rows/src/grid-grouping-rows.directive.ts","../../../../libs/grid-angular/features/grouping-rows/src/index.ts","../../../../libs/grid-angular/features/grouping-rows/src/toolbox-web-grid-angular-features-grouping-rows.ts"],"sourcesContent":["/**\n * `GridGroupingRowsDirective` — owns `[groupingRows]` and the group\n * toggle/expand/collapse outputs on `<tbw-grid>`. See\n * `GridFilteringDirective` for the full rationale.\n *\n * @category Directive\n */\nimport { Directive, ElementRef, inject, input, OnDestroy, OnInit, output, type Type } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { claimEvent, registerFeatureClaim, unclaimEvent, unregisterFeatureClaim } from '@toolbox-web/grid-angular';\nimport type {\n GroupingRowsConfig as CoreGroupingRowsConfig,\n GroupCollapseDetail,\n GroupExpandDetail,\n GroupRowRenderParams,\n GroupToggleDetail,\n} from '@toolbox-web/grid/plugins/grouping-rows';\n\n/**\n * Grouping-rows config widened to accept an Angular component class as\n * `groupRowRenderer`.\n *\n * Component instances receive the {@link GroupRowRenderParams} fields as\n * inputs (`key`, `value`, `depth`, `rows`, `expanded`, `toggleExpand`).\n *\n * @since 1.7.0\n */\nexport type GroupingRowsConfig = Omit<CoreGroupingRowsConfig, 'groupRowRenderer'> & {\n groupRowRenderer?: CoreGroupingRowsConfig['groupRowRenderer'] | Type<unknown>;\n};\n\n/**\n * Owns the binding(s) `[groupingRows], [groupToggle], [groupExpand], [groupCollapse]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.\n *\n * @category Directive\n */\n@Directive({\n selector
|
|
1
|
+
{"version":3,"file":"toolbox-web-grid-angular-features-grouping-rows.mjs","sources":["../../../../libs/grid-angular/features/grouping-rows/src/grid-grouping-rows.directive.ts","../../../../libs/grid-angular/features/grouping-rows/src/index.ts","../../../../libs/grid-angular/features/grouping-rows/src/toolbox-web-grid-angular-features-grouping-rows.ts"],"sourcesContent":["/**\n * `GridGroupingRowsDirective` — owns `[groupingRows]` and the group\n * toggle/expand/collapse/load-error outputs on `<tbw-grid>`. See\n * `GridFilteringDirective` for the full rationale.\n *\n * @category Directive\n */\nimport { Directive, ElementRef, inject, input, OnDestroy, OnInit, output, type Type } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { claimEvent, registerFeatureClaim, unclaimEvent, unregisterFeatureClaim } from '@toolbox-web/grid-angular';\nimport type {\n GroupingRowsConfig as CoreGroupingRowsConfig,\n GroupCollapseDetail,\n GroupExpandDetail,\n GroupLoadErrorDetail,\n GroupRowRenderParams,\n GroupToggleDetail,\n} from '@toolbox-web/grid/plugins/grouping-rows';\n\n/**\n * Grouping-rows config widened to accept an Angular component class as\n * `groupRowRenderer`.\n *\n * Component instances receive the {@link GroupRowRenderParams} fields as\n * inputs (`key`, `value`, `depth`, `rows`, `expanded`, `toggleExpand`).\n *\n * @since 1.7.0\n */\nexport type GroupingRowsConfig = Omit<CoreGroupingRowsConfig, 'groupRowRenderer'> & {\n groupRowRenderer?: CoreGroupingRowsConfig['groupRowRenderer'] | Type<unknown>;\n};\n\n/**\n * Owns the binding(s) `[groupingRows], [groupToggle], [groupExpand], [groupCollapse], [groupLoadError]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.\n *\n * @category Directive\n */\n@Directive({\n selector:\n 'tbw-grid[groupingRows], tbw-grid[groupToggle], tbw-grid[groupExpand], tbw-grid[groupCollapse], tbw-grid[groupLoadError]',\n standalone: true,\n})\nexport class GridGroupingRowsDirective implements OnInit, OnDestroy {\n private readonly elementRef = inject(ElementRef<DataGridElement>);\n\n readonly groupingRows = input<GroupingRowsConfig>();\n readonly groupToggle = output<GroupToggleDetail>();\n readonly groupExpand = output<GroupExpandDetail>();\n readonly groupCollapse = output<GroupCollapseDetail>();\n /**\n * Fired when loading a pre-defined group's rows through the ServerSide data\n * source (`getChildRows()`) fails. Re-expanding the group retries the fetch.\n *\n * @since 2.7.0\n */\n readonly groupLoadError = output<GroupLoadErrorDetail>();\n\n private readonly listeners = new Map<string, (e: Event) => void>();\n private static readonly EVENTS = ['group-toggle', 'group-expand', 'group-collapse', 'group-load-error'] as const;\n\n constructor() {\n const grid = this.elementRef.nativeElement;\n registerFeatureClaim(grid, 'groupingRows', () => this.groupingRows());\n for (const ev of GridGroupingRowsDirective.EVENTS) claimEvent(grid, ev);\n }\n\n ngOnInit(): void {\n const grid = this.elementRef.nativeElement;\n const wire = <T>(name: string, out: { emit: (v: T) => void }): void => {\n const l = (e: Event): void => out.emit((e as CustomEvent<T>).detail);\n grid.addEventListener(name, l);\n this.listeners.set(name, l);\n };\n wire<GroupToggleDetail>('group-toggle', this.groupToggle);\n wire<GroupExpandDetail>('group-expand', this.groupExpand);\n wire<GroupCollapseDetail>('group-collapse', this.groupCollapse);\n wire<GroupLoadErrorDetail>('group-load-error', this.groupLoadError);\n }\n\n ngOnDestroy(): void {\n const grid = this.elementRef.nativeElement;\n for (const [name, l] of this.listeners) grid.removeEventListener(name, l);\n this.listeners.clear();\n unregisterFeatureClaim(grid, 'groupingRows');\n for (const ev of GridGroupingRowsDirective.EVENTS) unclaimEvent(grid, ev);\n }\n}\n","/**\n * Row grouping feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `groupingRows` input on Grid directive.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/grouping-rows';\n *\n * <tbw-grid [groupingRows]=\"{ groupBy: ['department'] }\" />\n * ```\n *\n * @packageDocumentation\n */\n\nimport { isComponentClass, registerFeatureConfigPreprocessor } from '@toolbox-web/grid-angular';\nimport '@toolbox-web/grid/features/grouping-rows';\nimport type {\n GroupingRowsConfig as CoreGroupingRowsConfig,\n GroupRowRenderParams,\n} from '@toolbox-web/grid/plugins/grouping-rows';\nexport { GridGroupingRowsDirective, type GroupingRowsConfig } from './grid-grouping-rows.directive';\nexport type { _Augmentation as _GroupingRowsAugmentation } from '@toolbox-web/grid/features/grouping-rows';\n\n// Bridge any Angular component classes embedded in the user-supplied config\n// (e.g. group-row renderer) to plain renderer functions before the core\n// plugin factory consumes the config.\nregisterFeatureConfigPreprocessor('groupingRows', (config, adapter) => {\n if (!config || typeof config !== 'object') return config;\n const cfg = config as CoreGroupingRowsConfig;\n if (cfg.groupRowRenderer && isComponentClass(cfg.groupRowRenderer)) {\n const mount = adapter.mountComponentRenderer<GroupRowRenderParams>(cfg.groupRowRenderer, (p) => ({\n key: p.key,\n value: p.value,\n depth: p.depth,\n rows: p.rows,\n expanded: p.expanded,\n toggleExpand: p.toggleExpand,\n }));\n return { ...cfg, groupRowRenderer: (params: GroupRowRenderParams) => mount(params).hostElement };\n }\n return cfg;\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -102,8 +102,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
102
102
|
}], propDecorators: { showExpandColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExpandColumn", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], template: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* `GridMasterDetailDirective` — owns `[masterDetail]
|
|
106
|
-
* on `<tbw-grid>`. See `GridFilteringDirective` for the
|
|
105
|
+
* `GridMasterDetailDirective` — owns `[masterDetail]`, `(detailExpand)` and
|
|
106
|
+
* `(detailLoadError)` on `<tbw-grid>`. See `GridFilteringDirective` for the
|
|
107
|
+
* full rationale.
|
|
107
108
|
*
|
|
108
109
|
* Note: this directive is independent of the higher-level
|
|
109
110
|
* `<tbw-grid-detail>` template element + `GridDetailView` wrapper, which
|
|
@@ -113,7 +114,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
113
114
|
* @category Directive
|
|
114
115
|
*/
|
|
115
116
|
/**
|
|
116
|
-
* Owns the binding(s) `[masterDetail], [detailExpand]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.
|
|
117
|
+
* Owns the binding(s) `[masterDetail], [detailExpand], [detailLoadError]` on `<tbw-grid>` for the matching feature plugin. See `GridFilteringDirective` for the full rationale.
|
|
117
118
|
*
|
|
118
119
|
* @category Directive
|
|
119
120
|
*/
|
|
@@ -122,36 +123,50 @@ class GridMasterDetailDirective {
|
|
|
122
123
|
masterDetail = input(/* @ts-ignore */
|
|
123
124
|
...(ngDevMode ? [undefined, { debugName: "masterDetail" }] : /* istanbul ignore next */ []));
|
|
124
125
|
detailExpand = output();
|
|
125
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Fired when loading detail data through the ServerSide data source
|
|
128
|
+
* (`getChildRows()`) fails. Re-expanding the row retries the fetch.
|
|
129
|
+
*
|
|
130
|
+
* @since 2.7.0
|
|
131
|
+
*/
|
|
132
|
+
detailLoadError = output();
|
|
133
|
+
listeners = new Map();
|
|
134
|
+
static EVENTS = ['detail-expand', 'detail-load-error'];
|
|
126
135
|
constructor() {
|
|
127
136
|
const grid = this.elementRef.nativeElement;
|
|
128
137
|
registerFeatureClaim(grid, 'masterDetail', () => this.masterDetail());
|
|
129
|
-
|
|
138
|
+
for (const ev of GridMasterDetailDirective.EVENTS)
|
|
139
|
+
claimEvent(grid, ev);
|
|
130
140
|
}
|
|
131
141
|
ngOnInit() {
|
|
132
142
|
const grid = this.elementRef.nativeElement;
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
const wire = (name, out) => {
|
|
144
|
+
const l = (e) => out.emit(e.detail);
|
|
145
|
+
grid.addEventListener(name, l);
|
|
146
|
+
this.listeners.set(name, l);
|
|
147
|
+
};
|
|
148
|
+
wire('detail-expand', this.detailExpand);
|
|
149
|
+
wire('detail-load-error', this.detailLoadError);
|
|
135
150
|
}
|
|
136
151
|
ngOnDestroy() {
|
|
137
152
|
const grid = this.elementRef.nativeElement;
|
|
138
|
-
|
|
139
|
-
grid.removeEventListener(
|
|
140
|
-
|
|
141
|
-
}
|
|
153
|
+
for (const [name, l] of this.listeners)
|
|
154
|
+
grid.removeEventListener(name, l);
|
|
155
|
+
this.listeners.clear();
|
|
142
156
|
unregisterFeatureClaim(grid, 'masterDetail');
|
|
143
|
-
|
|
157
|
+
for (const ev of GridMasterDetailDirective.EVENTS)
|
|
158
|
+
unclaimEvent(grid, ev);
|
|
144
159
|
}
|
|
145
160
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: GridMasterDetailDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
146
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: GridMasterDetailDirective, isStandalone: true, selector: "tbw-grid[masterDetail], tbw-grid[detailExpand]", inputs: { masterDetail: { classPropertyName: "masterDetail", publicName: "masterDetail", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { detailExpand: "detailExpand" }, ngImport: i0 });
|
|
161
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: GridMasterDetailDirective, isStandalone: true, selector: "tbw-grid[masterDetail], tbw-grid[detailExpand], tbw-grid[detailLoadError]", inputs: { masterDetail: { classPropertyName: "masterDetail", publicName: "masterDetail", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { detailExpand: "detailExpand", detailLoadError: "detailLoadError" }, ngImport: i0 });
|
|
147
162
|
}
|
|
148
163
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: GridMasterDetailDirective, decorators: [{
|
|
149
164
|
type: Directive,
|
|
150
165
|
args: [{
|
|
151
|
-
selector: 'tbw-grid[masterDetail], tbw-grid[detailExpand]',
|
|
166
|
+
selector: 'tbw-grid[masterDetail], tbw-grid[detailExpand], tbw-grid[detailLoadError]',
|
|
152
167
|
standalone: true,
|
|
153
168
|
}]
|
|
154
|
-
}], ctorParameters: () => [], propDecorators: { masterDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "masterDetail", required: false }] }], detailExpand: [{ type: i0.Output, args: ["detailExpand"] }] } });
|
|
169
|
+
}], ctorParameters: () => [], propDecorators: { masterDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "masterDetail", required: false }] }], detailExpand: [{ type: i0.Output, args: ["detailExpand"] }], detailLoadError: [{ type: i0.Output, args: ["detailLoadError"] }] } });
|
|
155
170
|
|
|
156
171
|
/**
|
|
157
172
|
* Master-detail feature for @toolbox-web/grid-angular
|
|
@@ -243,14 +258,18 @@ registerFeatureConfigPreprocessor('masterDetail', (config, adapter) => {
|
|
|
243
258
|
row: ctx.row,
|
|
244
259
|
rowIndex: ctx.rowIndex,
|
|
245
260
|
}));
|
|
261
|
+
// Cached per row so re-renders reuse the instance, but only while it is alive:
|
|
262
|
+
// MasterDetailPlugin calls `adapter.unmount()` (destroying the ComponentRef)
|
|
263
|
+
// when a detail panel is collapsed, scrolled out, or re-rendered after async
|
|
264
|
+
// data/error, and the next render must mount a fresh component.
|
|
246
265
|
const cached = new Map();
|
|
247
266
|
const detailRenderer = (row, rowIndex) => {
|
|
248
267
|
const existing = cached.get(row);
|
|
249
|
-
if (existing)
|
|
250
|
-
return existing;
|
|
251
|
-
const
|
|
252
|
-
cached.set(row,
|
|
253
|
-
return hostElement;
|
|
268
|
+
if (existing && !existing.componentRef.hostView.destroyed)
|
|
269
|
+
return existing.hostElement;
|
|
270
|
+
const mounted = mount({ row, rowIndex });
|
|
271
|
+
cached.set(row, mounted);
|
|
272
|
+
return mounted.hostElement;
|
|
254
273
|
};
|
|
255
274
|
return { ...cfg, detailRenderer };
|
|
256
275
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbox-web-grid-angular-features-master-detail.mjs","sources":["../../../../libs/grid-angular/features/master-detail/src/grid-detail-view.directive.ts","../../../../libs/grid-angular/features/master-detail/src/grid-master-detail.directive.ts","../../../../libs/grid-angular/features/master-detail/src/index.ts","../../../../libs/grid-angular/features/master-detail/src/toolbox-web-grid-angular-features-master-detail.ts"],"sourcesContent":["import { contentChild, Directive, effect, ElementRef, inject, input, TemplateRef } from '@angular/core';\nimport type { ExpandCollapseAnimation } from '@toolbox-web/grid';\n\n/**\n * Context object passed to the detail renderer template.\n * Contains the row data for the expanded detail view.\n * @since 0.1.0\n */\nexport interface GridDetailContext<TRow = unknown> {\n /** The row data (implicit binding for let-row) */\n $implicit: TRow;\n /** The row data (explicit binding) */\n row: TRow;\n}\n\n// Global registry mapping DOM elements to their templates\nconst detailTemplateRegistry = new Map<HTMLElement, TemplateRef<GridDetailContext>>();\n\n/**\n * Gets the detail template registered for a given grid element.\n * Used by AngularGridAdapter to retrieve templates at render time.\n * @since 0.1.0\n */\nexport function getDetailTemplate(gridElement: HTMLElement): TemplateRef<GridDetailContext> | undefined {\n // Look for tbw-grid-detail child and get its template\n const detailElement = gridElement.querySelector('tbw-grid-detail');\n if (detailElement) {\n return detailTemplateRegistry.get(detailElement as HTMLElement);\n }\n return undefined;\n}\n\n/**\n * Directive that captures an `<ng-template>` for use as a master-detail row renderer.\n *\n * This enables declarative Angular component usage for expandable detail rows\n * that appear below the main row when expanded.\n *\n * ## Usage\n *\n * ```html\n * <tbw-grid [rows]=\"rows\" [gridConfig]=\"config\">\n * <tbw-grid-detail [showExpandColumn]=\"true\" animation=\"slide\">\n * <ng-template let-row>\n * <app-detail-panel [employee]=\"row\" />\n * </ng-template>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * The template context provides:\n * - `$implicit` / `row`: The full row data object\n *\n * Import the directive from the master-detail feature entry:\n *\n * ```typescript\n * import { GridDetailView } from '@toolbox-web/grid-angular/features/master-detail';\n *\n * @Component({\n * imports: [GridDetailView],\n * // ...\n * })\n * ```\n *\n * @example\n * ```html\n * <tbw-grid [rows]=\"rows\" [gridConfig]=\"config\">\n * <tbw-grid-detail [showExpandColumn]=\"true\" animation=\"slide\">\n * <ng-template let-row>\n * <app-detail-panel [employee]=\"row\" />\n * </ng-template>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * @category Directive\n * @since 0.1.0\n */\n@Directive({ selector: 'tbw-grid-detail' })\nexport class GridDetailView {\n private elementRef = inject(ElementRef<HTMLElement>);\n\n /** Whether to show the expand/collapse column. Default: true */\n showExpandColumn = input<boolean>(true);\n\n /** Animation style for expand/collapse. Default: 'slide' */\n animation = input<ExpandCollapseAnimation>('slide');\n\n /**\n * Query for the ng-template content child.\n */\n template = contentChild(TemplateRef<GridDetailContext>);\n\n /** Effect that triggers when the template is available */\n private onTemplateReceived = effect(() => {\n const template = this.template();\n if (template) {\n // Register the template for this element\n detailTemplateRegistry.set(this.elementRef.nativeElement, template);\n }\n });\n\n /**\n * Static type guard for template context.\n * Enables type inference in templates.\n */\n static ngTemplateContextGuard(dir: GridDetailView, ctx: unknown): ctx is GridDetailContext {\n return true;\n }\n}\n","/**\n * `GridMasterDetailDirective` — owns `[masterDetail]` and `(detailExpand)`\n * on `<tbw-grid>`. See `GridFilteringDirective` for the full rationale.\n *\n * Note: this directive is independent of the higher-level\n * `<tbw-grid-detail>` template element + `GridDetailView` wrapper, which\n * uses a different bridging mechanism (template registration). Apps using\n * the template form do not need this directive.\n *\n * @category Directive\n */\nimport { Directive, ElementRef, inject, input, OnDestroy, OnInit, output, type Type } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { claimEvent, registerFeatureClaim, unclaimEvent, unregisterFeatureClaim } from '@toolbox-web/grid-angular';\nimport type {\n MasterDetailConfig as CoreMasterDetailConfig,\n DetailExpandDetail,\n} from '@toolbox-web/grid/plugins/master-detail';\n\n/**\n * Angular-specific master-detail config that allows an Angular component class\n * as `detailRenderer`.\n *\n * Extends the core `MasterDetailConfig` to accept a `Type<unknown>` for\n * `detailRenderer` in addition to the vanilla\n * `(row, rowIndex) => HTMLElement | string` signature. Bridging to vanilla\n * DOM is handled by the side-effect import\n * `@toolbox-web/grid-angular/features/master-detail`.\n *\n * Re-exported from the feature entry under the same name as the core type\n * so Angular consumers see a single canonical `MasterDetailConfig` from\n * `@toolbox-web/grid-angular/features/master-detail`.\n *\n * @since 1.7.1\n */\nexport type MasterDetailConfig = Omit<CoreMasterDetailConfig, 'detailRenderer'> & {\n detailRenderer?: CoreMasterDetailConfig['detailRenderer'] | Type<unknown>;\n};\n\n/**\n * Owns the binding(s) `[masterDetail], [detailExpand]` 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[masterDetail], tbw-grid[detailExpand]',\n standalone: true,\n})\nexport class GridMasterDetailDirective implements OnInit, OnDestroy {\n private readonly elementRef = inject(ElementRef<DataGridElement>);\n\n readonly masterDetail = input<MasterDetailConfig>();\n readonly detailExpand = output<DetailExpandDetail>();\n\n private listener?: (e: Event) => void;\n\n constructor() {\n const grid = this.elementRef.nativeElement;\n registerFeatureClaim(grid, 'masterDetail', () => this.masterDetail());\n claimEvent(grid, 'detail-expand');\n }\n\n ngOnInit(): void {\n const grid = this.elementRef.nativeElement;\n this.listener = (e: Event): void => this.detailExpand.emit((e as CustomEvent<DetailExpandDetail>).detail);\n grid.addEventListener('detail-expand', this.listener);\n }\n\n ngOnDestroy(): void {\n const grid = this.elementRef.nativeElement;\n if (this.listener) {\n grid.removeEventListener('detail-expand', this.listener);\n this.listener = undefined;\n }\n unregisterFeatureClaim(grid, 'masterDetail');\n unclaimEvent(grid, 'detail-expand');\n }\n}\n","/**\n * Master-detail feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `[masterDetail]` input on the `Grid`\n * directive AND to wire `<tbw-grid-detail>` Angular templates into the\n * MasterDetailPlugin's `detailRenderer`.\n *\n * @example\n * ```typescript\n * // In your bootstrap (e.g. main.ts or app.component.ts):\n * import '@toolbox-web/grid-angular/features/master-detail';\n * ```\n *\n * ```html\n * <tbw-grid [masterDetail]=\"{ showExpandColumn: true }\">\n * <tbw-grid-detail>\n * <ng-template let-row>...</ng-template>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { Type } from '@angular/core';\nimport {\n isComponentClass,\n registerDetailRendererBridge,\n registerFeatureConfigPreprocessor,\n registerTemplateBridge,\n type GridAdapter,\n} from '@toolbox-web/grid-angular';\nimport '@toolbox-web/grid/features/master-detail';\nimport { getDetailTemplate, type GridDetailContext } from './grid-detail-view.directive';\nimport type { MasterDetailConfig } from './grid-master-detail.directive';\nexport { GridMasterDetailDirective } from './grid-master-detail.directive';\nexport type { MasterDetailConfig } from './grid-master-detail.directive';\nexport type { _Augmentation as _MasterDetailAugmentation } from '@toolbox-web/grid/features/master-detail';\n\n// ---------------------------------------------------------------------------\n// Master-detail-owned directive + helpers. These physically live in this\n// secondary entry (the runtime behaviour lives here too).\n// ---------------------------------------------------------------------------\nexport { getDetailTemplate, GridDetailView } from './grid-detail-view.directive';\nexport type { GridDetailContext } from './grid-detail-view.directive';\n\n/**\n * Subset of `MasterDetailPlugin` we touch from the bridge. Avoids importing\n * the plugin class itself (would defeat tree-shaking and re-introduce the\n * coupling we just removed from the core directive).\n */\ninterface MasterDetailPluginLike {\n refreshDetailRenderer?: () => void;\n}\n\n// Install the row renderer bridge on the adapter. This is what\n// `adapter.createDetailRenderer(grid)` and `adapter.parseDetailElement(el)`\n// delegate to. Without this import, both methods return undefined.\nregisterDetailRendererBridge(<TRow = unknown>(gridElement: HTMLElement, adapter: GridAdapter) => {\n // Type inferred from `getDetailTemplate` (same package instance as `adapter`),\n // not from a local `import type { TemplateRef } from '@angular/core'` — the\n // latter resolves through Bun's `.bun/` cache during ng-packagr build and\n // produces a brand-incompatible TemplateRef vs the built adapter `.d.ts`.\n const template = getDetailTemplate(gridElement);\n if (!template) return undefined;\n\n return (row: TRow) => {\n const context: GridDetailContext<TRow> = { $implicit: row, row };\n const viewRef = adapter.createTrackedEmbeddedView(template, context);\n const container = document.createElement('div');\n viewRef.rootNodes.forEach((node: Node) => container.appendChild(node));\n return container;\n };\n});\n\n// Wire <tbw-grid-detail> Angular templates into the MasterDetailPlugin once\n// content templates are registered. The plugin's `parseLightDomDetail()`\n// already calls back into the adapter via `grid.__frameworkAdapter.parseDetailElement`\n// (which the `Grid` directive set in `ngOnInit`), so all we need to do here\n// is trigger a refresh once Angular's content templates are available.\nregisterTemplateBridge(({ grid }) => {\n // Only act when the user actually placed a <tbw-grid-detail> in light DOM.\n const detailElement = grid.querySelector('tbw-grid-detail');\n if (!detailElement) return;\n\n // The plugin must already have been attached (via [masterDetail] input,\n // gridConfig.features.masterDetail, or gridConfig.plugins). Use the runtime\n // lookup so we don't miss plugins instantiated from `gridConfig.features`,\n // which never appear in `gridConfig.plugins`.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const existingPlugin = (grid as any).getPluginByName?.('masterDetail') as MasterDetailPluginLike | undefined;\n\n if (!existingPlugin) {\n console.warn(\n '[tbw-grid-angular] <tbw-grid-detail> found but MasterDetailPlugin is not configured.\\n' +\n 'Add the [masterDetail] input, set gridConfig.features.masterDetail, or include\\n' +\n 'MasterDetailPlugin in gridConfig.plugins:\\n\\n' +\n ' <tbw-grid [masterDetail]=\"{ showExpandColumn: true }\">...</tbw-grid>\\n\\n' +\n 'or:\\n\\n' +\n ' gridConfig = { features: { masterDetail: { showExpandColumn: true } } };',\n );\n return;\n }\n\n existingPlugin.refreshDetailRenderer?.();\n});\n\n/**\n * Bridge any Angular component class used as `masterDetail.detailRenderer` to\n * a plain `(row, rowIndex) => HTMLElement` function before the core plugin\n * factory consumes the config. Without this preprocessor, a component class\n * passed as `detailRenderer` would be invoked as a function at render time\n * and crash. Light-DOM `<tbw-grid-detail>` templates continue to win via\n * `parseLightDomDetail` inside the plugin.\n *\n * @since 1.7.1\n */\nregisterFeatureConfigPreprocessor('masterDetail', (config, adapter) => {\n if (!config || typeof config !== 'object') return config;\n const cfg = config as MasterDetailConfig;\n if (!isComponentClass(cfg.detailRenderer)) return config;\n\n const componentClass = cfg.detailRenderer as Type<unknown>;\n const mount = adapter.mountComponentRenderer<{ row: unknown; rowIndex: number }>(componentClass, (ctx) => ({\n row: ctx.row,\n rowIndex: ctx.rowIndex,\n }));\n const cached = new Map<unknown, HTMLElement>();\n const detailRenderer: MasterDetailConfig['detailRenderer'] = (row, rowIndex) => {\n const existing = cached.get(row);\n if (existing) return existing;\n const { hostElement } = mount({ row, rowIndex });\n cached.set(row, hostElement);\n return hostElement;\n };\n return { ...cfg, detailRenderer };\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-master-detail.mjs","sources":["../../../../libs/grid-angular/features/master-detail/src/grid-detail-view.directive.ts","../../../../libs/grid-angular/features/master-detail/src/grid-master-detail.directive.ts","../../../../libs/grid-angular/features/master-detail/src/index.ts","../../../../libs/grid-angular/features/master-detail/src/toolbox-web-grid-angular-features-master-detail.ts"],"sourcesContent":["import { contentChild, Directive, effect, ElementRef, inject, input, TemplateRef } from '@angular/core';\nimport type { ExpandCollapseAnimation } from '@toolbox-web/grid';\n\n/**\n * Context object passed to the detail renderer template.\n * Contains the row data for the expanded detail view.\n * @since 0.1.0\n */\nexport interface GridDetailContext<TRow = unknown> {\n /** The row data (implicit binding for let-row) */\n $implicit: TRow;\n /** The row data (explicit binding) */\n row: TRow;\n}\n\n// Global registry mapping DOM elements to their templates\nconst detailTemplateRegistry = new Map<HTMLElement, TemplateRef<GridDetailContext>>();\n\n/**\n * Gets the detail template registered for a given grid element.\n * Used by AngularGridAdapter to retrieve templates at render time.\n * @since 0.1.0\n */\nexport function getDetailTemplate(gridElement: HTMLElement): TemplateRef<GridDetailContext> | undefined {\n // Look for tbw-grid-detail child and get its template\n const detailElement = gridElement.querySelector('tbw-grid-detail');\n if (detailElement) {\n return detailTemplateRegistry.get(detailElement as HTMLElement);\n }\n return undefined;\n}\n\n/**\n * Directive that captures an `<ng-template>` for use as a master-detail row renderer.\n *\n * This enables declarative Angular component usage for expandable detail rows\n * that appear below the main row when expanded.\n *\n * ## Usage\n *\n * ```html\n * <tbw-grid [rows]=\"rows\" [gridConfig]=\"config\">\n * <tbw-grid-detail [showExpandColumn]=\"true\" animation=\"slide\">\n * <ng-template let-row>\n * <app-detail-panel [employee]=\"row\" />\n * </ng-template>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * The template context provides:\n * - `$implicit` / `row`: The full row data object\n *\n * Import the directive from the master-detail feature entry:\n *\n * ```typescript\n * import { GridDetailView } from '@toolbox-web/grid-angular/features/master-detail';\n *\n * @Component({\n * imports: [GridDetailView],\n * // ...\n * })\n * ```\n *\n * @example\n * ```html\n * <tbw-grid [rows]=\"rows\" [gridConfig]=\"config\">\n * <tbw-grid-detail [showExpandColumn]=\"true\" animation=\"slide\">\n * <ng-template let-row>\n * <app-detail-panel [employee]=\"row\" />\n * </ng-template>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * @category Directive\n * @since 0.1.0\n */\n@Directive({ selector: 'tbw-grid-detail' })\nexport class GridDetailView {\n private elementRef = inject(ElementRef<HTMLElement>);\n\n /** Whether to show the expand/collapse column. Default: true */\n showExpandColumn = input<boolean>(true);\n\n /** Animation style for expand/collapse. Default: 'slide' */\n animation = input<ExpandCollapseAnimation>('slide');\n\n /**\n * Query for the ng-template content child.\n */\n template = contentChild(TemplateRef<GridDetailContext>);\n\n /** Effect that triggers when the template is available */\n private onTemplateReceived = effect(() => {\n const template = this.template();\n if (template) {\n // Register the template for this element\n detailTemplateRegistry.set(this.elementRef.nativeElement, template);\n }\n });\n\n /**\n * Static type guard for template context.\n * Enables type inference in templates.\n */\n static ngTemplateContextGuard(dir: GridDetailView, ctx: unknown): ctx is GridDetailContext {\n return true;\n }\n}\n","/**\n * `GridMasterDetailDirective` — owns `[masterDetail]`, `(detailExpand)` and\n * `(detailLoadError)` on `<tbw-grid>`. See `GridFilteringDirective` for the\n * full rationale.\n *\n * Note: this directive is independent of the higher-level\n * `<tbw-grid-detail>` template element + `GridDetailView` wrapper, which\n * uses a different bridging mechanism (template registration). Apps using\n * the template form do not need this directive.\n *\n * @category Directive\n */\nimport { Directive, ElementRef, inject, input, OnDestroy, OnInit, output, type Type } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { claimEvent, registerFeatureClaim, unclaimEvent, unregisterFeatureClaim } from '@toolbox-web/grid-angular';\nimport type {\n MasterDetailConfig as CoreMasterDetailConfig,\n DetailExpandDetail,\n DetailLoadErrorDetail,\n} from '@toolbox-web/grid/plugins/master-detail';\n\n/**\n * Angular-specific master-detail config that allows an Angular component class\n * as `detailRenderer`.\n *\n * Extends the core `MasterDetailConfig` to accept a `Type<unknown>` for\n * `detailRenderer` in addition to the vanilla\n * `(row, rowIndex) => HTMLElement | string` signature. Bridging to vanilla\n * DOM is handled by the side-effect import\n * `@toolbox-web/grid-angular/features/master-detail`.\n *\n * Re-exported from the feature entry under the same name as the core type\n * so Angular consumers see a single canonical `MasterDetailConfig` from\n * `@toolbox-web/grid-angular/features/master-detail`.\n *\n * @since 1.7.1\n */\nexport type MasterDetailConfig = Omit<CoreMasterDetailConfig, 'detailRenderer'> & {\n detailRenderer?: CoreMasterDetailConfig['detailRenderer'] | Type<unknown>;\n};\n\n/**\n * Owns the binding(s) `[masterDetail], [detailExpand], [detailLoadError]` 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[masterDetail], tbw-grid[detailExpand], tbw-grid[detailLoadError]',\n standalone: true,\n})\nexport class GridMasterDetailDirective implements OnInit, OnDestroy {\n private readonly elementRef = inject(ElementRef<DataGridElement>);\n\n readonly masterDetail = input<MasterDetailConfig>();\n readonly detailExpand = output<DetailExpandDetail>();\n /**\n * Fired when loading detail data through the ServerSide data source\n * (`getChildRows()`) fails. Re-expanding the row retries the fetch.\n *\n * @since 2.7.0\n */\n readonly detailLoadError = output<DetailLoadErrorDetail>();\n\n private readonly listeners = new Map<string, (e: Event) => void>();\n private static readonly EVENTS = ['detail-expand', 'detail-load-error'] as const;\n\n constructor() {\n const grid = this.elementRef.nativeElement;\n registerFeatureClaim(grid, 'masterDetail', () => this.masterDetail());\n for (const ev of GridMasterDetailDirective.EVENTS) claimEvent(grid, ev);\n }\n\n ngOnInit(): void {\n const grid = this.elementRef.nativeElement;\n const wire = <T>(name: string, out: { emit: (v: T) => void }): void => {\n const l = (e: Event): void => out.emit((e as CustomEvent<T>).detail);\n grid.addEventListener(name, l);\n this.listeners.set(name, l);\n };\n wire<DetailExpandDetail>('detail-expand', this.detailExpand);\n wire<DetailLoadErrorDetail>('detail-load-error', this.detailLoadError);\n }\n\n ngOnDestroy(): void {\n const grid = this.elementRef.nativeElement;\n for (const [name, l] of this.listeners) grid.removeEventListener(name, l);\n this.listeners.clear();\n unregisterFeatureClaim(grid, 'masterDetail');\n for (const ev of GridMasterDetailDirective.EVENTS) unclaimEvent(grid, ev);\n }\n}\n","/**\n * Master-detail feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `[masterDetail]` input on the `Grid`\n * directive AND to wire `<tbw-grid-detail>` Angular templates into the\n * MasterDetailPlugin's `detailRenderer`.\n *\n * @example\n * ```typescript\n * // In your bootstrap (e.g. main.ts or app.component.ts):\n * import '@toolbox-web/grid-angular/features/master-detail';\n * ```\n *\n * ```html\n * <tbw-grid [masterDetail]=\"{ showExpandColumn: true }\">\n * <tbw-grid-detail>\n * <ng-template let-row>...</ng-template>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { Type } from '@angular/core';\nimport {\n isComponentClass,\n registerDetailRendererBridge,\n registerFeatureConfigPreprocessor,\n registerTemplateBridge,\n type GridAdapter,\n} from '@toolbox-web/grid-angular';\nimport '@toolbox-web/grid/features/master-detail';\nimport { getDetailTemplate, type GridDetailContext } from './grid-detail-view.directive';\nimport type { MasterDetailConfig } from './grid-master-detail.directive';\nexport { GridMasterDetailDirective } from './grid-master-detail.directive';\nexport type { MasterDetailConfig } from './grid-master-detail.directive';\nexport type { _Augmentation as _MasterDetailAugmentation } from '@toolbox-web/grid/features/master-detail';\n\n// ---------------------------------------------------------------------------\n// Master-detail-owned directive + helpers. These physically live in this\n// secondary entry (the runtime behaviour lives here too).\n// ---------------------------------------------------------------------------\nexport { getDetailTemplate, GridDetailView } from './grid-detail-view.directive';\nexport type { GridDetailContext } from './grid-detail-view.directive';\n\n/**\n * Subset of `MasterDetailPlugin` we touch from the bridge. Avoids importing\n * the plugin class itself (would defeat tree-shaking and re-introduce the\n * coupling we just removed from the core directive).\n */\ninterface MasterDetailPluginLike {\n refreshDetailRenderer?: () => void;\n}\n\n// Install the row renderer bridge on the adapter. This is what\n// `adapter.createDetailRenderer(grid)` and `adapter.parseDetailElement(el)`\n// delegate to. Without this import, both methods return undefined.\nregisterDetailRendererBridge(<TRow = unknown>(gridElement: HTMLElement, adapter: GridAdapter) => {\n // Type inferred from `getDetailTemplate` (same package instance as `adapter`),\n // not from a local `import type { TemplateRef } from '@angular/core'` — the\n // latter resolves through Bun's `.bun/` cache during ng-packagr build and\n // produces a brand-incompatible TemplateRef vs the built adapter `.d.ts`.\n const template = getDetailTemplate(gridElement);\n if (!template) return undefined;\n\n return (row: TRow) => {\n const context: GridDetailContext<TRow> = { $implicit: row, row };\n const viewRef = adapter.createTrackedEmbeddedView(template, context);\n const container = document.createElement('div');\n viewRef.rootNodes.forEach((node: Node) => container.appendChild(node));\n return container;\n };\n});\n\n// Wire <tbw-grid-detail> Angular templates into the MasterDetailPlugin once\n// content templates are registered. The plugin's `parseLightDomDetail()`\n// already calls back into the adapter via `grid.__frameworkAdapter.parseDetailElement`\n// (which the `Grid` directive set in `ngOnInit`), so all we need to do here\n// is trigger a refresh once Angular's content templates are available.\nregisterTemplateBridge(({ grid }) => {\n // Only act when the user actually placed a <tbw-grid-detail> in light DOM.\n const detailElement = grid.querySelector('tbw-grid-detail');\n if (!detailElement) return;\n\n // The plugin must already have been attached (via [masterDetail] input,\n // gridConfig.features.masterDetail, or gridConfig.plugins). Use the runtime\n // lookup so we don't miss plugins instantiated from `gridConfig.features`,\n // which never appear in `gridConfig.plugins`.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const existingPlugin = (grid as any).getPluginByName?.('masterDetail') as MasterDetailPluginLike | undefined;\n\n if (!existingPlugin) {\n console.warn(\n '[tbw-grid-angular] <tbw-grid-detail> found but MasterDetailPlugin is not configured.\\n' +\n 'Add the [masterDetail] input, set gridConfig.features.masterDetail, or include\\n' +\n 'MasterDetailPlugin in gridConfig.plugins:\\n\\n' +\n ' <tbw-grid [masterDetail]=\"{ showExpandColumn: true }\">...</tbw-grid>\\n\\n' +\n 'or:\\n\\n' +\n ' gridConfig = { features: { masterDetail: { showExpandColumn: true } } };',\n );\n return;\n }\n\n existingPlugin.refreshDetailRenderer?.();\n});\n\n/**\n * Bridge any Angular component class used as `masterDetail.detailRenderer` to\n * a plain `(row, rowIndex) => HTMLElement` function before the core plugin\n * factory consumes the config. Without this preprocessor, a component class\n * passed as `detailRenderer` would be invoked as a function at render time\n * and crash. Light-DOM `<tbw-grid-detail>` templates continue to win via\n * `parseLightDomDetail` inside the plugin.\n *\n * @since 1.7.1\n */\nregisterFeatureConfigPreprocessor('masterDetail', (config, adapter) => {\n if (!config || typeof config !== 'object') return config;\n const cfg = config as MasterDetailConfig;\n if (!isComponentClass(cfg.detailRenderer)) return config;\n\n const componentClass = cfg.detailRenderer as Type<unknown>;\n const mount = adapter.mountComponentRenderer<{ row: unknown; rowIndex: number }>(componentClass, (ctx) => ({\n row: ctx.row,\n rowIndex: ctx.rowIndex,\n }));\n // Cached per row so re-renders reuse the instance, but only while it is alive:\n // MasterDetailPlugin calls `adapter.unmount()` (destroying the ComponentRef)\n // when a detail panel is collapsed, scrolled out, or re-rendered after async\n // data/error, and the next render must mount a fresh component.\n const cached = new Map<unknown, ReturnType<typeof mount>>();\n const detailRenderer: MasterDetailConfig['detailRenderer'] = (row, rowIndex) => {\n const existing = cached.get(row);\n if (existing && !existing.componentRef.hostView.destroyed) return existing.hostElement;\n const mounted = mount({ row, rowIndex });\n cached.set(row, mounted);\n return mounted.hostElement;\n };\n return { ...cfg, detailRenderer };\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -145,17 +145,21 @@ function injectGridSelection(selector = 'tbw-grid') {
|
|
|
145
145
|
let readyPromiseStarted = false;
|
|
146
146
|
let listenerAttached = false;
|
|
147
147
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
148
|
+
* Refresh the reactive signals from the plugin. `mode` may be a single mode
|
|
149
|
+
* or an array (multi-mode selection); falls back to the plugin's config.
|
|
150
150
|
*/
|
|
151
|
-
const
|
|
152
|
-
const selectionDetail = detail;
|
|
151
|
+
const sync = (mode) => {
|
|
153
152
|
const plugin = getPlugin();
|
|
154
|
-
if (plugin)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
if (!plugin)
|
|
154
|
+
return;
|
|
155
|
+
const resolvedMode = mode ?? plugin.config?.mode;
|
|
156
|
+
const isRowMode = Array.isArray(resolvedMode) ? resolvedMode.includes('row') : resolvedMode === 'row';
|
|
157
|
+
selectionSignal.set(plugin.getSelection());
|
|
158
|
+
selectedRowIndicesSignal.set(isRowMode ? plugin.getSelectedRowIndices() : []);
|
|
159
|
+
selectedRowsSignal.set(plugin.getSelectedRows());
|
|
160
|
+
};
|
|
161
|
+
const onSelectionChange = (detail) => {
|
|
162
|
+
sync(detail.mode);
|
|
159
163
|
};
|
|
160
164
|
/**
|
|
161
165
|
* Attach the selection-change event listener to the grid element.
|
|
@@ -203,15 +207,7 @@ function injectGridSelection(selector = 'tbw-grid') {
|
|
|
203
207
|
* Sync reactive signals with the current plugin state.
|
|
204
208
|
* Called once when the grid is first discovered and ready.
|
|
205
209
|
*/
|
|
206
|
-
const syncSignals = () =>
|
|
207
|
-
const plugin = getPlugin();
|
|
208
|
-
if (plugin) {
|
|
209
|
-
selectionSignal.set(plugin.getSelection());
|
|
210
|
-
const mode = plugin.config?.mode;
|
|
211
|
-
selectedRowIndicesSignal.set(mode === 'row' ? plugin.getSelectedRowIndices() : []);
|
|
212
|
-
selectedRowsSignal.set(plugin.getSelectedRows());
|
|
213
|
-
}
|
|
214
|
-
};
|
|
210
|
+
const syncSignals = () => sync();
|
|
215
211
|
// Discover the grid after the first render so the selection-change
|
|
216
212
|
// listener is attached without requiring a programmatic method call.
|
|
217
213
|
// Uses a MutationObserver as fallback for lazy-rendered tabs, *ngIf,
|
|
@@ -261,24 +257,7 @@ function injectGridSelection(selector = 'tbw-grid') {
|
|
|
261
257
|
` <tbw-grid [selection]="'range'" />`);
|
|
262
258
|
return;
|
|
263
259
|
}
|
|
264
|
-
|
|
265
|
-
// Cast to any to access protected config
|
|
266
|
-
const mode = plugin.config?.mode;
|
|
267
|
-
if (mode === 'row') {
|
|
268
|
-
const rowCount = grid?.rows?.length ?? 0;
|
|
269
|
-
const allIndices = new Set();
|
|
270
|
-
for (let i = 0; i < rowCount; i++)
|
|
271
|
-
allIndices.add(i);
|
|
272
|
-
plugin.selected = allIndices;
|
|
273
|
-
plugin.requestAfterRender?.();
|
|
274
|
-
}
|
|
275
|
-
else if (mode === 'range') {
|
|
276
|
-
const rowCount = grid?.rows?.length ?? 0;
|
|
277
|
-
const colCount = grid?._columns?.length ?? 0;
|
|
278
|
-
if (rowCount > 0 && colCount > 0) {
|
|
279
|
-
plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: rowCount - 1, col: colCount - 1 } }]);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
260
|
+
plugin.selectAll();
|
|
282
261
|
},
|
|
283
262
|
clearSelection: () => {
|
|
284
263
|
getPlugin()?.clearSelection();
|
|
@@ -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 * 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
|
+
{"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 selectable rows (row mode) or one range over every row and all\n * non-hidden columns (range mode).\n * Array modes use their row/range axis.\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 * Refresh the reactive signals from the plugin. `mode` may be a single mode\n * or an array (multi-mode selection); falls back to the plugin's config.\n */\n const sync = (mode?: SelectionChangeDetail['mode']): void => {\n const plugin = getPlugin();\n if (!plugin) return;\n const resolvedMode = mode ?? ((plugin as any).config?.mode as SelectionChangeDetail['mode'] | undefined);\n const isRowMode = Array.isArray(resolvedMode) ? resolvedMode.includes('row') : resolvedMode === 'row';\n selectionSignal.set(plugin.getSelection());\n selectedRowIndicesSignal.set(isRowMode ? plugin.getSelectedRowIndices() : []);\n selectedRowsSignal.set(plugin.getSelectedRows<TRow>());\n };\n\n const onSelectionChange = (detail: unknown): void => {\n sync((detail as SelectionChangeDetail).mode);\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 => sync();\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 plugin.selectAll();\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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"}
|
|
@@ -254,7 +254,7 @@ function injectGridUndoRedo(selector = 'tbw-grid') {
|
|
|
254
254
|
},
|
|
255
255
|
getUndoStack: () => getPlugin()?.getUndoStack() ?? [],
|
|
256
256
|
getRedoStack: () => getPlugin()?.getRedoStack() ?? [],
|
|
257
|
-
recordEdit: (rowIndex, field, oldValue, newValue) => {
|
|
257
|
+
recordEdit: (rowIndex, field, oldValue, newValue, rowId) => {
|
|
258
258
|
const plugin = getPlugin();
|
|
259
259
|
if (!plugin) {
|
|
260
260
|
console.warn(`[tbw-grid:undoRedo] UndoRedoPlugin not found.\n\n` +
|
|
@@ -262,7 +262,7 @@ function injectGridUndoRedo(selector = 'tbw-grid') {
|
|
|
262
262
|
` <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />`);
|
|
263
263
|
return;
|
|
264
264
|
}
|
|
265
|
-
plugin.recordEdit(rowIndex, field, oldValue, newValue);
|
|
265
|
+
plugin.recordEdit(rowIndex, field, oldValue, newValue, rowId);
|
|
266
266
|
syncSignals();
|
|
267
267
|
},
|
|
268
268
|
beginTransaction: () => {
|