@toolbox-web/grid-angular 2.4.0 → 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 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
  [![npm](https://img.shields.io/npm/v/@toolbox-web/grid-angular.svg)](https://www.npmjs.com/package/@toolbox-web/grid-angular)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
5
5
  [![GitHub Sponsors](https://img.shields.io/badge/Sponsor-❤-ea4aaa?logo=github)](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 |
@@ -1605,6 +1605,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
1605
1605
  type: Directive
1606
1606
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], commit: [{ type: i0.Output, args: ["commit"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
1607
1607
 
1608
+ /**
1609
+ * Resolves what a CVA editor should display.
1610
+ *
1611
+ * Precedence: a value this editor committed wins over the grid value (which
1612
+ * goes stale after a commit — see `BaseGridEditorCVA.commitBoth`), which in
1613
+ * turn wins over the value written by a form control.
1614
+ *
1615
+ * Exported for unit testing only — not re-exported from the package entry point.
1616
+ *
1617
+ * @internal
1618
+ */
1619
+ function resolveEditorDisplayValue(committed, currentValue, cvaValue) {
1620
+ if (committed)
1621
+ return committed.value;
1622
+ return currentValue ?? cvaValue;
1623
+ }
1608
1624
  /**
1609
1625
  * Base class for grid editors that also work as Angular form controls.
1610
1626
  *
@@ -1617,7 +1633,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
1617
1633
  * |--------|---------|
1618
1634
  * | `cvaValue` | Signal holding the value written by the form control |
1619
1635
  * | `disabledState` | Signal tracking `setDisabledState` calls |
1620
- * | `displayValue` | Computed that prefers grid value (`currentValue`) and falls back to `cvaValue` |
1636
+ * | `displayValue` | Computed that prefers the last `commitBoth` value, then the grid value (`currentValue`), then `cvaValue` |
1621
1637
  * | `commitBoth(v)` | Commits via both CVA `onChange` and grid `commitValue` |
1622
1638
  * | `writeValue` / `registerOn*` / `setDisabledState` | Full CVA implementation |
1623
1639
  *
@@ -1679,18 +1695,30 @@ class BaseGridEditorCVA extends BaseGridEditor {
1679
1695
  */
1680
1696
  disabledState = signal(false, /* @ts-ignore */
1681
1697
  ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
1698
+ /**
1699
+ * Value most recently passed to {@link commitBoth}, wrapped so a committed
1700
+ * `null` stays distinguishable from "nothing committed yet".
1701
+ *
1702
+ * The grid never pushes a cell's own committed value back to the editor that
1703
+ * produced it — `EditingPlugin` skips its editor value callbacks when
1704
+ * `source === 'user'`. In row edit mode every cell editor stays mounted after
1705
+ * a commit, so the `value` input goes stale for the rest of the session.
1706
+ * Editors that normalise or derive what they commit (e.g. a day number to a
1707
+ * full date) would otherwise keep rendering the pre-commit value.
1708
+ */
1709
+ _committed = signal(null, /* @ts-ignore */
1710
+ ...(ngDevMode ? [{ debugName: "_committed" }] : /* istanbul ignore next */ []));
1682
1711
  /**
1683
1712
  * Resolved display value.
1684
1713
  *
1685
- * Prefers `currentValue()` (grid context — from `control.value` or `value` input)
1714
+ * Prefers a value committed by this editor (see {@link commitBoth}), then
1715
+ * `currentValue()` (grid context — from `control.value` or `value` input),
1686
1716
  * and falls back to `cvaValue()` (standalone form context — from `writeValue`).
1687
1717
  *
1688
1718
  * Use this in your template instead of reading `currentValue()` directly
1689
1719
  * so the component works in both grid and standalone form contexts.
1690
1720
  */
1691
- displayValue = computed(() => {
1692
- return this.currentValue() ?? this.cvaValue();
1693
- }, /* @ts-ignore */
1721
+ displayValue = computed(() => resolveEditorDisplayValue(this._committed(), this.currentValue(), this.cvaValue()), /* @ts-ignore */
1694
1722
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
1695
1723
  // ============================================================================
1696
1724
  // ControlValueAccessor Implementation
@@ -1699,8 +1727,19 @@ class BaseGridEditorCVA extends BaseGridEditor {
1699
1727
  * Called by Angular forms when the form control value changes programmatically.
1700
1728
  */
1701
1729
  writeValue(value) {
1730
+ this._committed.set(null);
1702
1731
  this.cvaValue.set(value);
1703
1732
  }
1733
+ /**
1734
+ * Discards the value committed by this editor so a genuinely external value
1735
+ * (cascade, undo/redo, cell cancel) wins.
1736
+ *
1737
+ * Subclasses that override this **must** call `super.onExternalValueChange()`.
1738
+ */
1739
+ onExternalValueChange(newVal) {
1740
+ this._committed.set(null);
1741
+ super.onExternalValueChange(newVal);
1742
+ }
1704
1743
  /**
1705
1744
  * Called by Angular forms to register a change callback.
1706
1745
  */
@@ -1734,6 +1773,8 @@ class BaseGridEditorCVA extends BaseGridEditor {
1734
1773
  * @param value - The new value to commit
1735
1774
  */
1736
1775
  commitBoth(value) {
1776
+ // Keep the committed value visible: the grid will not push it back to us.
1777
+ this._committed.set({ value });
1737
1778
  // Update CVA
1738
1779
  this.cvaValue.set(value);
1739
1780
  this._onChange(value);