ng-hub-ui-spreadsheet 22.0.0 → 22.1.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/README.md +37 -16
- package/fesm2022/ng-hub-ui-spreadsheet-signals.mjs +1 -1
- package/fesm2022/ng-hub-ui-spreadsheet-signals.mjs.map +1 -1
- package/fesm2022/ng-hub-ui-spreadsheet.mjs +514 -24
- package/fesm2022/ng-hub-ui-spreadsheet.mjs.map +1 -1
- package/package.json +11 -8
- package/styles/index.scss +11 -0
- package/styles/mixins/_spreadsheet-theme.scss +333 -0
- package/types/ng-hub-ui-spreadsheet.d.ts +199 -1
package/README.md
CHANGED
|
@@ -77,14 +77,14 @@ That split is deliberate. It is what lets the same component serve an invoice, a
|
|
|
77
77
|
|
|
78
78
|
- **Two identities per column.** A stable alias that everything stored points at, and a visible header you can rewrite whenever you like without breaking a single reference.
|
|
79
79
|
- **Excel's editing keyboard**, down to the details: typing replaces, `F2` keeps, `Enter` commits and drops, `Tab` commits and advances, `Escape` reverts.
|
|
80
|
-
- **Rectangular selection** by shift-arrow, shift-click or dragging.
|
|
80
|
+
- **Rectangular selection** by shift-arrow, shift-click or dragging, and **more than one block at a time** with `Ctrl` held. `Delete` empties every block; a copy happens when the blocks line up and is refused when they do not, exactly as a spreadsheet refuses it.
|
|
81
81
|
- **A clipboard that actually round-trips with Excel.** Both flavours written, both read, with the raw number recovered from the HTML so a figure copied in a locale that writes `1.234,56` arrives as `1234.56`.
|
|
82
82
|
- **Frozen panes**, counted from the edge the way Excel freezes them.
|
|
83
83
|
- **Five save states per cell**, drawn as a bar rather than a badge.
|
|
84
84
|
- **Structured output by alias**: `{ price: 12, units: 3 }`, not a grid of coordinates.
|
|
85
85
|
- **Real grid semantics** — `role="grid"`, row and column indices, `aria-selected`, a roving tab stop — so a screen reader can navigate it.
|
|
86
86
|
- **Input-method safe.** The `Enter` that confirms a Chinese, Japanese or Korean character does not commit the cell.
|
|
87
|
-
- **
|
|
87
|
+
- **67 CSS variables** that inherit from `--hub-table-*` before falling back to the design system.
|
|
88
88
|
- **Out to a real `.xlsx` and back**, with figures as figures and dates as dates — and to CSV, with the separator the reader's locale expects. The zip and the XML are written here, so exporting brings no dependency.
|
|
89
89
|
- **No `@angular/cdk`.** The grid primitives are in `ng-hub-ui-utils`.
|
|
90
90
|
|
|
@@ -289,6 +289,7 @@ danglingColumnKeys(Object.keys(this.savedState), this.columns); // ['discount']
|
|
|
289
289
|
| `readonly` | `boolean` | `false` | Turns off every editor, whatever the cells say. |
|
|
290
290
|
| `formulas` | `boolean` | `false` | Reads a cell that starts with `=` as a formula. See below. |
|
|
291
291
|
| `editOn` | `'click' \| 'double-click'` | `'double-click'` | What opens the editor with the pointer. Typing opens it either way. |
|
|
292
|
+
| `direction` | `'auto' \| 'ltr' \| 'rtl'` | `'auto'` | Which way the sheet runs. `auto` follows the page; the other two force it. |
|
|
292
293
|
| `frozenColumns` | `number` | `0` | How many columns stay pinned to the leading edge. |
|
|
293
294
|
| `frozenRows` | `number` | `0` | How many rows stay pinned below the header. |
|
|
294
295
|
| `virtual` | `boolean` | `false` | Draws only the rows in view. Gives the sheet a height; see below. |
|
|
@@ -298,6 +299,7 @@ danglingColumnKeys(Object.keys(this.savedState), this.columns); // ['discount']
|
|
|
298
299
|
| `retiredColumnKeys` | `readonly string[]` | `[]` | Aliases of deleted columns, so a new one never reuses them. |
|
|
299
300
|
| `contextMenu` | `boolean` | `false` | Offers the structural changes on right-click. |
|
|
300
301
|
| `mergeable` | `boolean` | `false` | Offers putting cells together, and taking them apart, in that menu. |
|
|
302
|
+
| `disjointSelection` | `boolean` | `true` | Whether `Ctrl`-clicking adds a second block instead of starting a new selection. |
|
|
301
303
|
| `fillHandle` | `boolean` | `false` | Draws the grip at the corner of the selection. |
|
|
302
304
|
| `canUndo` | `boolean` | `false` | Whether `Ctrl+Z` has anything to ask for. The history belongs to the host. |
|
|
303
305
|
| `canRedo` | `boolean` | `false` | The same for `Ctrl+Shift+Z` and `Ctrl+Y`. |
|
|
@@ -308,20 +310,21 @@ danglingColumnKeys(Object.keys(this.savedState), this.columns); // ['discount']
|
|
|
308
310
|
|
|
309
311
|
### Outputs
|
|
310
312
|
|
|
311
|
-
| Output
|
|
312
|
-
|
|
|
313
|
-
| `commit`
|
|
314
|
-
| `pasted`
|
|
315
|
-
| `filled`
|
|
316
|
-
| `cleared`
|
|
317
|
-
| `selectionChange`
|
|
318
|
-
| `
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
313
|
+
| Output | Payload | Fires when |
|
|
314
|
+
| ----------------------- | ------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
315
|
+
| `commit` | `HubSpreadsheetCommit<TRow>` | A cell took a new value, and it differs from the old one. |
|
|
316
|
+
| `pasted` | `HubSpreadsheetPaste<TRow>` | A block was pasted. |
|
|
317
|
+
| `filled` | `HubSpreadsheetPaste<TRow>` | The fill handle was dragged and released. |
|
|
318
|
+
| `cleared` | `HubSpreadsheetCellRef<TRow>[]` | `Delete` was pressed over a selection. |
|
|
319
|
+
| `selectionChange` | `HubGridRange \| null` | The selected rectangle changed. |
|
|
320
|
+
| `selectionRangesChange` | `readonly HubGridRange[]` | Every rectangle of the selection, whenever it changes. One entry unless blocks were held with `Ctrl`. |
|
|
321
|
+
| `insertRequested` | `HubSpreadsheetInsertRequest` | The reader asked to add rows or columns. |
|
|
322
|
+
| `deleteRequested` | `HubSpreadsheetDeleteRequest` | The reader asked to remove rows or columns. |
|
|
323
|
+
| `undoRequested` | `void` | `Ctrl+Z`. The sheet undoes nothing itself. |
|
|
324
|
+
| `redoRequested` | `void` | `Ctrl+Shift+Z` or `Ctrl+Y`. |
|
|
325
|
+
| `columnMoved` | `{ from, to, key, keys }` | A header was dropped. `keys` arrives already reordered. |
|
|
326
|
+
| `mergeRequested` | `HubSpreadsheetMergeRequest` | The reader asked to put a selection together. Carries what it swallows. |
|
|
327
|
+
| `unmergeRequested` | `readonly HubGridCoords[]` | The reader asked to take apart the blocks their selection touches. |
|
|
325
328
|
|
|
326
329
|
### Helpers
|
|
327
330
|
|
|
@@ -543,6 +546,24 @@ Theme it by setting tokens on any ancestor:
|
|
|
543
546
|
|
|
544
547
|
Each value falls through `--hub-table-*` before `--hub-sys-*`, so a project that has themed its tables with `ng-hub-ui-paginable` gets its sheets dressed to match without setting anything.
|
|
545
548
|
|
|
549
|
+
Or in one include, which is the same thing said in Sass:
|
|
550
|
+
|
|
551
|
+
```scss
|
|
552
|
+
@use 'ng-hub-ui-spreadsheet/styles' as sheet;
|
|
553
|
+
|
|
554
|
+
.invoice-lines {
|
|
555
|
+
@include sheet.hub-spreadsheet-theme(
|
|
556
|
+
$cursor-color: #6f42c1,
|
|
557
|
+
$cell-padding-y: 0.125rem,
|
|
558
|
+
$cell-line-height: 1.2,
|
|
559
|
+
$max-block-size: 60vh
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Every parameter is optional and only what you pass is emitted, so the rest keep falling through the
|
|
565
|
+
chain. Reach for it for what a table theme does not already cover, rather than to restate it.
|
|
566
|
+
|
|
546
567
|
Full catalogue: [`docs/css-variables-reference.md`](./docs/css-variables-reference.md).
|
|
547
568
|
|
|
548
569
|
## ♿ Accessibility
|
|
@@ -120,7 +120,7 @@ class HubSpreadsheetFieldComponent {
|
|
|
120
120
|
(cleared)="onCleared($event)"
|
|
121
121
|
(selectionChange)="selectionChange.emit($event)"
|
|
122
122
|
/>
|
|
123
|
-
`, isInline: true, dependencies: [{ kind: "component", type: HubSpreadsheetComponent, selector: "hub-spreadsheet", inputs: ["rows", "columns", "rowKey", "states", "errors", "decimalMark", "emptyText", "pageSize", "readonly", "frozenColumns", "frozenRows", "spans", "structure", "retiredColumnKeys", "fillHandle", "canUndo", "canRedo", "resizableColumns", "reorderableColumns", "columnWidths", "minColumnWidth", "contextMenu", "mergeable", "formulas", "virtual", "rowHeight", "editOn"], outputs: ["columnWidthsChange", "commit", "pasted", "cleared", "selectionChange", "insertRequested", "deleteRequested", "filled", "undoRequested", "redoRequested", "columnMoved", "mergeRequested", "unmergeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
123
|
+
`, isInline: true, dependencies: [{ kind: "component", type: HubSpreadsheetComponent, selector: "hub-spreadsheet", inputs: ["rows", "columns", "rowKey", "states", "errors", "decimalMark", "emptyText", "pageSize", "readonly", "frozenColumns", "frozenRows", "spans", "structure", "retiredColumnKeys", "direction", "disjointSelection", "fillHandle", "canUndo", "canRedo", "resizableColumns", "reorderableColumns", "columnWidths", "minColumnWidth", "contextMenu", "mergeable", "formulas", "virtual", "rowHeight", "editOn"], outputs: ["columnWidthsChange", "commit", "pasted", "cleared", "selectionChange", "insertRequested", "deleteRequested", "filled", "undoRequested", "redoRequested", "columnMoved", "mergeRequested", "selectionRangesChange", "unmergeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
124
124
|
}
|
|
125
125
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubSpreadsheetFieldComponent, decorators: [{
|
|
126
126
|
type: Component,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ng-hub-ui-spreadsheet-signals.mjs","sources":["../../../projects/spreadsheet/signals/src/lib/spreadsheet-field.component.ts","../../../projects/spreadsheet/signals/src/lib/spreadsheet-field-errors.ts","../../../projects/spreadsheet/signals/src/public-api.ts","../../../projects/spreadsheet/signals/src/ng-hub-ui-spreadsheet-signals.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, booleanAttribute, computed, input, model, output } from '@angular/core';\nimport { FormValueControl, ValidationError, WithOptionalFieldTree } from '@angular/forms/signals';\nimport { HubGridRange, HubGridSpan } from 'ng-hub-ui-utils';\nimport {\n\tHubSpreadsheetCellRef,\n\tHubSpreadsheetCellState,\n\tHubSpreadsheetColumn,\n\tHubSpreadsheetCommit,\n\tHubSpreadsheetComponent,\n\tHubSpreadsheetPaste\n} from 'ng-hub-ui-spreadsheet';\n\n/**\n * A sheet that is a form control: its value is the rows.\n *\n * Bind it with the `FormField` directive and the rows become the field's value, written back on\n * every edit, paste and clearing. Because the contract is Angular's own\n * {@link FormValueControl}, the same component also works inside reactive and template-driven\n * forms — Angular's migration guide says as much, and forbids implementing the old\n * `ControlValueAccessor` alongside it.\n *\n * **What this shape cannot do, and why it is not the only one.** A control bound to a collection\n * receives that field's own errors and never its descendants'. A rule declared with `applyEach`\n * on the rows therefore produces errors this component will not see, and per-cell validation is\n * the whole point of an editable sheet. When you need it, keep `<hub-spreadsheet>` bound to your\n * own rows and feed its `errors` input — `spreadsheetFieldErrors()` maps a field tree onto the\n * map it expects.\n */\n@Component({\n\tselector: 'hub-spreadsheet-field',\n\tstandalone: true,\n\timports: [HubSpreadsheetComponent],\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplate: `\n\t\t<hub-spreadsheet\n\t\t\t[rows]=\"value()\"\n\t\t\t[columns]=\"columns()\"\n\t\t\t[rowKey]=\"rowKey()\"\n\t\t\t[states]=\"states()\"\n\t\t\t[errors]=\"cellErrors()\"\n\t\t\t[decimalMark]=\"decimalMark()\"\n\t\t\t[emptyText]=\"emptyText()\"\n\t\t\t[pageSize]=\"pageSize()\"\n\t\t\t[readonly]=\"readonly() || disabled()\"\n\t\t\t[frozenColumns]=\"frozenColumns()\"\n\t\t\t[frozenRows]=\"frozenRows()\"\n\t\t\t[spans]=\"spans()\"\n\t\t\t(commit)=\"onCommit($event)\"\n\t\t\t(pasted)=\"onPaste($event)\"\n\t\t\t(cleared)=\"onCleared($event)\"\n\t\t\t(selectionChange)=\"selectionChange.emit($event)\"\n\t\t/>\n\t`\n})\nexport class HubSpreadsheetFieldComponent<TRow extends object> implements FormValueControl<TRow[]> {\n\t/** The rows, as the field's value. Written back whenever the reader changes a cell. */\n\treadonly value = model<TRow[]>([]);\n\n\t/** What each column shows and allows. */\n\treadonly columns = input.required<readonly HubSpreadsheetColumn<TRow>[]>();\n\t/** A stable name per row. */\n\treadonly rowKey = input.required<(row: TRow) => string>();\n\t/** The save state of each cell. */\n\treadonly states = input<Record<string, HubSpreadsheetCellState>>({});\n\t/** The character this reader types decimals with. */\n\treadonly decimalMark = input<',' | '.'>(',');\n\t/** What an empty sheet says. */\n\treadonly emptyText = input('');\n\t/** How many rows Page Up and Page Down travel. */\n\treadonly pageSize = input(10);\n\t/** Turns off every editor, whatever the cells say. */\n\treadonly readonly = input(false, { transform: booleanAttribute });\n\t/** How many columns stay pinned to the leading edge. */\n\treadonly frozenColumns = input(0);\n\t/** How many rows stay pinned below the header. */\n\treadonly frozenRows = input(0);\n\t/** Merged blocks. */\n\treadonly spans = input<readonly HubGridSpan[]>([]);\n\n\t/**\n\t * Bound by the `FormField` directive. Only the array field's own errors arrive here, which is\n\t * why they are shown as a whole rather than attributed to a cell.\n\t */\n\treadonly errors = input<readonly ValidationError.WithOptionalFieldTree[]>([]);\n\t/** Bound by the `FormField` directive; disables every editor. */\n\treadonly disabled = input(false, { transform: booleanAttribute });\n\n\t/** The selected rectangle changed. */\n\treadonly selectionChange = output<HubGridRange | null>();\n\n\t/**\n\t * The sheet's own per-cell error map.\n\t *\n\t * Empty here on purpose: this shape has no per-cell errors to give. It exists so the inner\n\t * component's input is always bound to something, and so a future release can fill it without\n\t * changing the template.\n\t */\n\tprotected readonly cellErrors = computed<Record<string, string>>(() => ({}));\n\n\tprotected onCommit(change: HubSpreadsheetCommit<TRow>): void {\n\t\tthis.write(change.coords.row, change.column.key, change.value);\n\t}\n\n\tprotected onPaste(paste: HubSpreadsheetPaste<TRow>): void {\n\t\tif (!paste.cells.length) {\n\t\t\treturn;\n\t\t}\n\n\t\t// One update for the whole block rather than one per cell: a paste is a single change as\n\t\t// far as the field is concerned, and writing it cell by cell would fire the form's\n\t\t// validation hundreds of times for one gesture.\n\t\tthis.value.update((rows) => {\n\t\t\tconst next = [...rows];\n\n\t\t\tfor (const change of paste.cells) {\n\t\t\t\tnext[change.coords.row] = { ...next[change.coords.row], [change.column.key]: change.value };\n\t\t\t}\n\n\t\t\treturn next;\n\t\t});\n\t}\n\n\tprotected onCleared(cells: readonly HubSpreadsheetCellRef<TRow>[]): void {\n\t\tthis.value.update((rows) => {\n\t\t\tconst next = [...rows];\n\n\t\t\tfor (const ref of cells) {\n\t\t\t\tnext[ref.coords.row] = { ...next[ref.coords.row], [ref.column.key]: null };\n\t\t\t}\n\n\t\t\treturn next;\n\t\t});\n\t}\n\n\tprivate write(row: number, key: string, value: unknown): void {\n\t\tthis.value.update((rows) =>\n\t\t\trows.map((candidate, index) => (index === row ? { ...candidate, [key]: value } : candidate))\n\t\t);\n\t}\n}\n\n/** Re-exported so a consumer can type a handler without importing from two packages. */\nexport type { WithOptionalFieldTree };\n","import { FieldTree } from '@angular/forms/signals';\nimport { HubSpreadsheetColumn } from 'ng-hub-ui-spreadsheet';\n\n/**\n * Turns a field tree over rows into the per-cell error map `<hub-spreadsheet>` reads.\n *\n * This is the piece that makes per-cell validation work, and it exists because of a limit in the\n * framework rather than a preference: a control bound to a collection is handed that field's own\n * errors and never its descendants'. Rules declared with `applyEach` therefore land on the row\n * fields, one level below anything the sheet could see for itself.\n *\n * So the host keeps the field tree, and this walks it: for each row and each column, it reads\n * that field's errors and writes the first message under the key the sheet addresses cells by.\n *\n * ```ts\n * readonly form = form(this.lines, (path) => {\n * applyEach(path, (line) => {\n * min(line.units, 1, { message: 'At least one unit' });\n * });\n * });\n *\n * protected readonly cellErrors = computed(() =>\n * spreadsheetFieldErrors(this.form, this.lines(), this.columns, (row) => row.id)\n * );\n * ```\n *\n * @param form The field tree over the rows.\n * @param rows The rows themselves, in the same order.\n * @param columns The sheet's columns; only aliases that name a field are looked up.\n * @param rowKey How the sheet names a row, so the keys line up with `states`.\n * @param touchedOnly Whether to report only fields the reader has visited. True by default: an\n * untouched form that paints itself red the moment it renders is a form nobody trusts.\n */\nexport function spreadsheetFieldErrors<TRow extends object>(\n\tform: FieldTree<TRow[]>,\n\trows: readonly TRow[],\n\tcolumns: readonly HubSpreadsheetColumn<TRow>[],\n\trowKey: (row: TRow) => string,\n\ttouchedOnly = true\n): Record<string, string> {\n\tconst errors: Record<string, string> = {};\n\tconst tree = form as unknown as ArrayLike<Record<string, unknown>>;\n\n\trows.forEach((row, index) => {\n\t\tconst rowField = tree[index];\n\n\t\tif (!rowField) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const column of columns) {\n\t\t\tconst message = firstMessage(rowField[column.key], touchedOnly);\n\n\t\t\tif (message) {\n\t\t\t\terrors[`${rowKey(row)}\\t${column.key}`] = message;\n\t\t\t}\n\t\t}\n\t});\n\n\treturn errors;\n}\n\n/**\n * The first message a field has to offer, or nothing.\n *\n * Read defensively. A column alias need not name a field at all — a calculated total has no\n * counterpart in the model — and reaching into a field tree for a key that is not there must\n * report \"no error\" rather than throw in the middle of a render.\n */\nfunction firstMessage(field: unknown, touchedOnly: boolean): string | undefined {\n\tif (typeof field !== 'function') {\n\t\treturn undefined;\n\t}\n\n\ttry {\n\t\tconst state = (field as () => Record<string, () => unknown>)();\n\n\t\tif (touchedOnly && state['touched'] && state['touched']() !== true) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst list = state['errors']?.() as ReadonlyArray<{ message?: string; kind?: string }> | undefined;\n\t\tconst first = list?.[0];\n\n\t\treturn first?.message ?? first?.kind;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Marks the field behind a cell as visited.\n *\n * Errors are held back until a cell has been touched, and a sheet has no way to report that on\n * its own: the `FormField` directive learns it from a control's `touch` output, and a sheet\n * driven through `errors` is not bound to a field at all. So the host says it, from the\n * `commit`, `pasted` and `cleared` handlers it already writes.\n *\n * Without this call, `spreadsheetFieldErrors()` in its default mode reports nothing and no cell\n * is ever marked — which looks exactly like validation that does not work.\n *\n * ```ts\n * protected onCommit(change: HubSpreadsheetCommit<Line>): void {\n * this.lines.update(...);\n * touchSpreadsheetField(this.orderForm, change.coords.row, change.column.key);\n * }\n * ```\n *\n * @param form The field tree over the rows.\n * @param row The row index of the cell.\n * @param columnKey The column's alias.\n */\nexport function touchSpreadsheetField<TRow extends object>(form: FieldTree<TRow[]>, row: number, columnKey: string): void {\n\tconst field = (form as unknown as ArrayLike<Record<string, unknown>>)[row]?.[columnKey];\n\n\tif (typeof field !== 'function') {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst state = (field as () => { markAsTouched?: () => void })();\n\n\t\tstate.markAsTouched?.();\n\t} catch {\n\t\t// A column alias that names no field. Nothing to mark, and nothing to report.\n\t}\n}\n","/*\n * Public API Surface of ng-hub-ui-spreadsheet/signals\n *\n * Opt-in Angular Signal Forms (`@angular/forms/signals`) integration. Importing this entry\n * point is the only place that pulls in that API; the core `ng-hub-ui-spreadsheet` package\n * never touches it, so a project that uses neither forms system pays nothing for it.\n *\n * Worth knowing before you choose a shape: a control whose value is a collection receives only\n * that field's own errors, never its descendants'. `<hub-spreadsheet-field>` is therefore the\n * convenience, not the whole story — for per-cell validation, keep the sheet bound to your own\n * rows and feed its `errors` input from the field tree. `spreadsheetFieldErrors()` does that\n * mapping for you.\n */\n\nexport { HubSpreadsheetFieldComponent } from './lib/spreadsheet-field.component';\nexport { spreadsheetFieldErrors, touchSpreadsheetField } from './lib/spreadsheet-field-errors';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ng-hub-ui-spreadsheet-signals.mjs","sources":["../../../projects/spreadsheet/signals/src/lib/spreadsheet-field.component.ts","../../../projects/spreadsheet/signals/src/lib/spreadsheet-field-errors.ts","../../../projects/spreadsheet/signals/src/public-api.ts","../../../projects/spreadsheet/signals/src/ng-hub-ui-spreadsheet-signals.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, booleanAttribute, computed, input, model, output } from '@angular/core';\nimport { FormValueControl, ValidationError, WithOptionalFieldTree } from '@angular/forms/signals';\nimport { HubGridRange, HubGridSpan } from 'ng-hub-ui-utils';\nimport {\n\tHubSpreadsheetCellRef,\n\tHubSpreadsheetCellState,\n\tHubSpreadsheetColumn,\n\tHubSpreadsheetCommit,\n\tHubSpreadsheetComponent,\n\tHubSpreadsheetPaste\n} from 'ng-hub-ui-spreadsheet';\n\n/**\n * A sheet that is a form control: its value is the rows.\n *\n * Bind it with the `FormField` directive and the rows become the field's value, written back on\n * every edit, paste and clearing. Because the contract is Angular's own\n * {@link FormValueControl}, the same component also works inside reactive and template-driven\n * forms — Angular's migration guide says as much, and forbids implementing the old\n * `ControlValueAccessor` alongside it.\n *\n * **What this shape cannot do, and why it is not the only one.** A control bound to a collection\n * receives that field's own errors and never its descendants'. A rule declared with `applyEach`\n * on the rows therefore produces errors this component will not see, and per-cell validation is\n * the whole point of an editable sheet. When you need it, keep `<hub-spreadsheet>` bound to your\n * own rows and feed its `errors` input — `spreadsheetFieldErrors()` maps a field tree onto the\n * map it expects.\n */\n@Component({\n\tselector: 'hub-spreadsheet-field',\n\tstandalone: true,\n\timports: [HubSpreadsheetComponent],\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplate: `\n\t\t<hub-spreadsheet\n\t\t\t[rows]=\"value()\"\n\t\t\t[columns]=\"columns()\"\n\t\t\t[rowKey]=\"rowKey()\"\n\t\t\t[states]=\"states()\"\n\t\t\t[errors]=\"cellErrors()\"\n\t\t\t[decimalMark]=\"decimalMark()\"\n\t\t\t[emptyText]=\"emptyText()\"\n\t\t\t[pageSize]=\"pageSize()\"\n\t\t\t[readonly]=\"readonly() || disabled()\"\n\t\t\t[frozenColumns]=\"frozenColumns()\"\n\t\t\t[frozenRows]=\"frozenRows()\"\n\t\t\t[spans]=\"spans()\"\n\t\t\t(commit)=\"onCommit($event)\"\n\t\t\t(pasted)=\"onPaste($event)\"\n\t\t\t(cleared)=\"onCleared($event)\"\n\t\t\t(selectionChange)=\"selectionChange.emit($event)\"\n\t\t/>\n\t`\n})\nexport class HubSpreadsheetFieldComponent<TRow extends object> implements FormValueControl<TRow[]> {\n\t/** The rows, as the field's value. Written back whenever the reader changes a cell. */\n\treadonly value = model<TRow[]>([]);\n\n\t/** What each column shows and allows. */\n\treadonly columns = input.required<readonly HubSpreadsheetColumn<TRow>[]>();\n\t/** A stable name per row. */\n\treadonly rowKey = input.required<(row: TRow) => string>();\n\t/** The save state of each cell. */\n\treadonly states = input<Record<string, HubSpreadsheetCellState>>({});\n\t/** The character this reader types decimals with. */\n\treadonly decimalMark = input<',' | '.'>(',');\n\t/** What an empty sheet says. */\n\treadonly emptyText = input('');\n\t/** How many rows Page Up and Page Down travel. */\n\treadonly pageSize = input(10);\n\t/** Turns off every editor, whatever the cells say. */\n\treadonly readonly = input(false, { transform: booleanAttribute });\n\t/** How many columns stay pinned to the leading edge. */\n\treadonly frozenColumns = input(0);\n\t/** How many rows stay pinned below the header. */\n\treadonly frozenRows = input(0);\n\t/** Merged blocks. */\n\treadonly spans = input<readonly HubGridSpan[]>([]);\n\n\t/**\n\t * Bound by the `FormField` directive. Only the array field's own errors arrive here, which is\n\t * why they are shown as a whole rather than attributed to a cell.\n\t */\n\treadonly errors = input<readonly ValidationError.WithOptionalFieldTree[]>([]);\n\t/** Bound by the `FormField` directive; disables every editor. */\n\treadonly disabled = input(false, { transform: booleanAttribute });\n\n\t/** The selected rectangle changed. */\n\treadonly selectionChange = output<HubGridRange | null>();\n\n\t/**\n\t * The sheet's own per-cell error map.\n\t *\n\t * Empty here on purpose: this shape has no per-cell errors to give. It exists so the inner\n\t * component's input is always bound to something, and so a future release can fill it without\n\t * changing the template.\n\t */\n\tprotected readonly cellErrors = computed<Record<string, string>>(() => ({}));\n\n\tprotected onCommit(change: HubSpreadsheetCommit<TRow>): void {\n\t\tthis.write(change.coords.row, change.column.key, change.value);\n\t}\n\n\tprotected onPaste(paste: HubSpreadsheetPaste<TRow>): void {\n\t\tif (!paste.cells.length) {\n\t\t\treturn;\n\t\t}\n\n\t\t// One update for the whole block rather than one per cell: a paste is a single change as\n\t\t// far as the field is concerned, and writing it cell by cell would fire the form's\n\t\t// validation hundreds of times for one gesture.\n\t\tthis.value.update((rows) => {\n\t\t\tconst next = [...rows];\n\n\t\t\tfor (const change of paste.cells) {\n\t\t\t\tnext[change.coords.row] = { ...next[change.coords.row], [change.column.key]: change.value };\n\t\t\t}\n\n\t\t\treturn next;\n\t\t});\n\t}\n\n\tprotected onCleared(cells: readonly HubSpreadsheetCellRef<TRow>[]): void {\n\t\tthis.value.update((rows) => {\n\t\t\tconst next = [...rows];\n\n\t\t\tfor (const ref of cells) {\n\t\t\t\tnext[ref.coords.row] = { ...next[ref.coords.row], [ref.column.key]: null };\n\t\t\t}\n\n\t\t\treturn next;\n\t\t});\n\t}\n\n\tprivate write(row: number, key: string, value: unknown): void {\n\t\tthis.value.update((rows) =>\n\t\t\trows.map((candidate, index) => (index === row ? { ...candidate, [key]: value } : candidate))\n\t\t);\n\t}\n}\n\n/** Re-exported so a consumer can type a handler without importing from two packages. */\nexport type { WithOptionalFieldTree };\n","import { FieldTree } from '@angular/forms/signals';\nimport { HubSpreadsheetColumn } from 'ng-hub-ui-spreadsheet';\n\n/**\n * Turns a field tree over rows into the per-cell error map `<hub-spreadsheet>` reads.\n *\n * This is the piece that makes per-cell validation work, and it exists because of a limit in the\n * framework rather than a preference: a control bound to a collection is handed that field's own\n * errors and never its descendants'. Rules declared with `applyEach` therefore land on the row\n * fields, one level below anything the sheet could see for itself.\n *\n * So the host keeps the field tree, and this walks it: for each row and each column, it reads\n * that field's errors and writes the first message under the key the sheet addresses cells by.\n *\n * ```ts\n * readonly form = form(this.lines, (path) => {\n * applyEach(path, (line) => {\n * min(line.units, 1, { message: 'At least one unit' });\n * });\n * });\n *\n * protected readonly cellErrors = computed(() =>\n * spreadsheetFieldErrors(this.form, this.lines(), this.columns, (row) => row.id)\n * );\n * ```\n *\n * @param form The field tree over the rows.\n * @param rows The rows themselves, in the same order.\n * @param columns The sheet's columns; only aliases that name a field are looked up.\n * @param rowKey How the sheet names a row, so the keys line up with `states`.\n * @param touchedOnly Whether to report only fields the reader has visited. True by default: an\n * untouched form that paints itself red the moment it renders is a form nobody trusts.\n */\nexport function spreadsheetFieldErrors<TRow extends object>(\n\tform: FieldTree<TRow[]>,\n\trows: readonly TRow[],\n\tcolumns: readonly HubSpreadsheetColumn<TRow>[],\n\trowKey: (row: TRow) => string,\n\ttouchedOnly = true\n): Record<string, string> {\n\tconst errors: Record<string, string> = {};\n\tconst tree = form as unknown as ArrayLike<Record<string, unknown>>;\n\n\trows.forEach((row, index) => {\n\t\tconst rowField = tree[index];\n\n\t\tif (!rowField) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const column of columns) {\n\t\t\tconst message = firstMessage(rowField[column.key], touchedOnly);\n\n\t\t\tif (message) {\n\t\t\t\terrors[`${rowKey(row)}\\t${column.key}`] = message;\n\t\t\t}\n\t\t}\n\t});\n\n\treturn errors;\n}\n\n/**\n * The first message a field has to offer, or nothing.\n *\n * Read defensively. A column alias need not name a field at all — a calculated total has no\n * counterpart in the model — and reaching into a field tree for a key that is not there must\n * report \"no error\" rather than throw in the middle of a render.\n */\nfunction firstMessage(field: unknown, touchedOnly: boolean): string | undefined {\n\tif (typeof field !== 'function') {\n\t\treturn undefined;\n\t}\n\n\ttry {\n\t\tconst state = (field as () => Record<string, () => unknown>)();\n\n\t\tif (touchedOnly && state['touched'] && state['touched']() !== true) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst list = state['errors']?.() as ReadonlyArray<{ message?: string; kind?: string }> | undefined;\n\t\tconst first = list?.[0];\n\n\t\treturn first?.message ?? first?.kind;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Marks the field behind a cell as visited.\n *\n * Errors are held back until a cell has been touched, and a sheet has no way to report that on\n * its own: the `FormField` directive learns it from a control's `touch` output, and a sheet\n * driven through `errors` is not bound to a field at all. So the host says it, from the\n * `commit`, `pasted` and `cleared` handlers it already writes.\n *\n * Without this call, `spreadsheetFieldErrors()` in its default mode reports nothing and no cell\n * is ever marked — which looks exactly like validation that does not work.\n *\n * ```ts\n * protected onCommit(change: HubSpreadsheetCommit<Line>): void {\n * this.lines.update(...);\n * touchSpreadsheetField(this.orderForm, change.coords.row, change.column.key);\n * }\n * ```\n *\n * @param form The field tree over the rows.\n * @param row The row index of the cell.\n * @param columnKey The column's alias.\n */\nexport function touchSpreadsheetField<TRow extends object>(form: FieldTree<TRow[]>, row: number, columnKey: string): void {\n\tconst field = (form as unknown as ArrayLike<Record<string, unknown>>)[row]?.[columnKey];\n\n\tif (typeof field !== 'function') {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst state = (field as () => { markAsTouched?: () => void })();\n\n\t\tstate.markAsTouched?.();\n\t} catch {\n\t\t// A column alias that names no field. Nothing to mark, and nothing to report.\n\t}\n}\n","/*\n * Public API Surface of ng-hub-ui-spreadsheet/signals\n *\n * Opt-in Angular Signal Forms (`@angular/forms/signals`) integration. Importing this entry\n * point is the only place that pulls in that API; the core `ng-hub-ui-spreadsheet` package\n * never touches it, so a project that uses neither forms system pays nothing for it.\n *\n * Worth knowing before you choose a shape: a control whose value is a collection receives only\n * that field's own errors, never its descendants'. `<hub-spreadsheet-field>` is therefore the\n * convenience, not the whole story — for per-cell validation, keep the sheet bound to your own\n * rows and feed its `errors` input from the field tree. `spreadsheetFieldErrors()` does that\n * mapping for you.\n */\n\nexport { HubSpreadsheetFieldComponent } from './lib/spreadsheet-field.component';\nexport { spreadsheetFieldErrors, touchSpreadsheetField } from './lib/spreadsheet-field-errors';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|