ng-hub-ui-spreadsheet 22.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +571 -0
- package/fesm2022/ng-hub-ui-spreadsheet-signals.mjs +281 -0
- package/fesm2022/ng-hub-ui-spreadsheet-signals.mjs.map +1 -0
- package/fesm2022/ng-hub-ui-spreadsheet.mjs +4598 -0
- package/fesm2022/ng-hub-ui-spreadsheet.mjs.map +1 -0
- package/package.json +67 -0
- package/types/ng-hub-ui-spreadsheet-signals.d.ts +125 -0
- package/types/ng-hub-ui-spreadsheet.d.ts +1958 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { model, input, booleanAttribute, output, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { HubSpreadsheetComponent } from 'ng-hub-ui-spreadsheet';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A sheet that is a form control: its value is the rows.
|
|
7
|
+
*
|
|
8
|
+
* Bind it with the `FormField` directive and the rows become the field's value, written back on
|
|
9
|
+
* every edit, paste and clearing. Because the contract is Angular's own
|
|
10
|
+
* {@link FormValueControl}, the same component also works inside reactive and template-driven
|
|
11
|
+
* forms — Angular's migration guide says as much, and forbids implementing the old
|
|
12
|
+
* `ControlValueAccessor` alongside it.
|
|
13
|
+
*
|
|
14
|
+
* **What this shape cannot do, and why it is not the only one.** A control bound to a collection
|
|
15
|
+
* receives that field's own errors and never its descendants'. A rule declared with `applyEach`
|
|
16
|
+
* on the rows therefore produces errors this component will not see, and per-cell validation is
|
|
17
|
+
* the whole point of an editable sheet. When you need it, keep `<hub-spreadsheet>` bound to your
|
|
18
|
+
* own rows and feed its `errors` input — `spreadsheetFieldErrors()` maps a field tree onto the
|
|
19
|
+
* map it expects.
|
|
20
|
+
*/
|
|
21
|
+
class HubSpreadsheetFieldComponent {
|
|
22
|
+
/** The rows, as the field's value. Written back whenever the reader changes a cell. */
|
|
23
|
+
value = model([], /* @ts-ignore */
|
|
24
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
25
|
+
/** What each column shows and allows. */
|
|
26
|
+
columns = input.required(/* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
28
|
+
/** A stable name per row. */
|
|
29
|
+
rowKey = input.required(/* @ts-ignore */
|
|
30
|
+
...(ngDevMode ? [{ debugName: "rowKey" }] : /* istanbul ignore next */ []));
|
|
31
|
+
/** The save state of each cell. */
|
|
32
|
+
states = input({}, /* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [{ debugName: "states" }] : /* istanbul ignore next */ []));
|
|
34
|
+
/** The character this reader types decimals with. */
|
|
35
|
+
decimalMark = input(',', /* @ts-ignore */
|
|
36
|
+
...(ngDevMode ? [{ debugName: "decimalMark" }] : /* istanbul ignore next */ []));
|
|
37
|
+
/** What an empty sheet says. */
|
|
38
|
+
emptyText = input('', /* @ts-ignore */
|
|
39
|
+
...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
40
|
+
/** How many rows Page Up and Page Down travel. */
|
|
41
|
+
pageSize = input(10, /* @ts-ignore */
|
|
42
|
+
...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
|
|
43
|
+
/** Turns off every editor, whatever the cells say. */
|
|
44
|
+
readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
45
|
+
/** How many columns stay pinned to the leading edge. */
|
|
46
|
+
frozenColumns = input(0, /* @ts-ignore */
|
|
47
|
+
...(ngDevMode ? [{ debugName: "frozenColumns" }] : /* istanbul ignore next */ []));
|
|
48
|
+
/** How many rows stay pinned below the header. */
|
|
49
|
+
frozenRows = input(0, /* @ts-ignore */
|
|
50
|
+
...(ngDevMode ? [{ debugName: "frozenRows" }] : /* istanbul ignore next */ []));
|
|
51
|
+
/** Merged blocks. */
|
|
52
|
+
spans = input([], /* @ts-ignore */
|
|
53
|
+
...(ngDevMode ? [{ debugName: "spans" }] : /* istanbul ignore next */ []));
|
|
54
|
+
/**
|
|
55
|
+
* Bound by the `FormField` directive. Only the array field's own errors arrive here, which is
|
|
56
|
+
* why they are shown as a whole rather than attributed to a cell.
|
|
57
|
+
*/
|
|
58
|
+
errors = input([], /* @ts-ignore */
|
|
59
|
+
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
60
|
+
/** Bound by the `FormField` directive; disables every editor. */
|
|
61
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
62
|
+
/** The selected rectangle changed. */
|
|
63
|
+
selectionChange = output();
|
|
64
|
+
/**
|
|
65
|
+
* The sheet's own per-cell error map.
|
|
66
|
+
*
|
|
67
|
+
* Empty here on purpose: this shape has no per-cell errors to give. It exists so the inner
|
|
68
|
+
* component's input is always bound to something, and so a future release can fill it without
|
|
69
|
+
* changing the template.
|
|
70
|
+
*/
|
|
71
|
+
cellErrors = computed(() => ({}), /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "cellErrors" }] : /* istanbul ignore next */ []));
|
|
73
|
+
onCommit(change) {
|
|
74
|
+
this.write(change.coords.row, change.column.key, change.value);
|
|
75
|
+
}
|
|
76
|
+
onPaste(paste) {
|
|
77
|
+
if (!paste.cells.length) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// One update for the whole block rather than one per cell: a paste is a single change as
|
|
81
|
+
// far as the field is concerned, and writing it cell by cell would fire the form's
|
|
82
|
+
// validation hundreds of times for one gesture.
|
|
83
|
+
this.value.update((rows) => {
|
|
84
|
+
const next = [...rows];
|
|
85
|
+
for (const change of paste.cells) {
|
|
86
|
+
next[change.coords.row] = { ...next[change.coords.row], [change.column.key]: change.value };
|
|
87
|
+
}
|
|
88
|
+
return next;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
onCleared(cells) {
|
|
92
|
+
this.value.update((rows) => {
|
|
93
|
+
const next = [...rows];
|
|
94
|
+
for (const ref of cells) {
|
|
95
|
+
next[ref.coords.row] = { ...next[ref.coords.row], [ref.column.key]: null };
|
|
96
|
+
}
|
|
97
|
+
return next;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
write(row, key, value) {
|
|
101
|
+
this.value.update((rows) => rows.map((candidate, index) => (index === row ? { ...candidate, [key]: value } : candidate)));
|
|
102
|
+
}
|
|
103
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubSpreadsheetFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
104
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: HubSpreadsheetFieldComponent, isStandalone: true, selector: "hub-spreadsheet-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: true, transformFunction: null }, states: { classPropertyName: "states", publicName: "states", isSignal: true, isRequired: false, transformFunction: null }, decimalMark: { classPropertyName: "decimalMark", publicName: "decimalMark", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, frozenColumns: { classPropertyName: "frozenColumns", publicName: "frozenColumns", isSignal: true, isRequired: false, transformFunction: null }, frozenRows: { classPropertyName: "frozenRows", publicName: "frozenRows", isSignal: true, isRequired: false, transformFunction: null }, spans: { classPropertyName: "spans", publicName: "spans", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", selectionChange: "selectionChange" }, ngImport: i0, template: `
|
|
105
|
+
<hub-spreadsheet
|
|
106
|
+
[rows]="value()"
|
|
107
|
+
[columns]="columns()"
|
|
108
|
+
[rowKey]="rowKey()"
|
|
109
|
+
[states]="states()"
|
|
110
|
+
[errors]="cellErrors()"
|
|
111
|
+
[decimalMark]="decimalMark()"
|
|
112
|
+
[emptyText]="emptyText()"
|
|
113
|
+
[pageSize]="pageSize()"
|
|
114
|
+
[readonly]="readonly() || disabled()"
|
|
115
|
+
[frozenColumns]="frozenColumns()"
|
|
116
|
+
[frozenRows]="frozenRows()"
|
|
117
|
+
[spans]="spans()"
|
|
118
|
+
(commit)="onCommit($event)"
|
|
119
|
+
(pasted)="onPaste($event)"
|
|
120
|
+
(cleared)="onCleared($event)"
|
|
121
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
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 });
|
|
124
|
+
}
|
|
125
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubSpreadsheetFieldComponent, decorators: [{
|
|
126
|
+
type: Component,
|
|
127
|
+
args: [{
|
|
128
|
+
selector: 'hub-spreadsheet-field',
|
|
129
|
+
standalone: true,
|
|
130
|
+
imports: [HubSpreadsheetComponent],
|
|
131
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
132
|
+
template: `
|
|
133
|
+
<hub-spreadsheet
|
|
134
|
+
[rows]="value()"
|
|
135
|
+
[columns]="columns()"
|
|
136
|
+
[rowKey]="rowKey()"
|
|
137
|
+
[states]="states()"
|
|
138
|
+
[errors]="cellErrors()"
|
|
139
|
+
[decimalMark]="decimalMark()"
|
|
140
|
+
[emptyText]="emptyText()"
|
|
141
|
+
[pageSize]="pageSize()"
|
|
142
|
+
[readonly]="readonly() || disabled()"
|
|
143
|
+
[frozenColumns]="frozenColumns()"
|
|
144
|
+
[frozenRows]="frozenRows()"
|
|
145
|
+
[spans]="spans()"
|
|
146
|
+
(commit)="onCommit($event)"
|
|
147
|
+
(pasted)="onPaste($event)"
|
|
148
|
+
(cleared)="onCleared($event)"
|
|
149
|
+
(selectionChange)="selectionChange.emit($event)"
|
|
150
|
+
/>
|
|
151
|
+
`
|
|
152
|
+
}]
|
|
153
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: true }] }], states: [{ type: i0.Input, args: [{ isSignal: true, alias: "states", required: false }] }], decimalMark: [{ type: i0.Input, args: [{ isSignal: true, alias: "decimalMark", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], frozenColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenColumns", required: false }] }], frozenRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenRows", required: false }] }], spans: [{ type: i0.Input, args: [{ isSignal: true, alias: "spans", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Turns a field tree over rows into the per-cell error map `<hub-spreadsheet>` reads.
|
|
157
|
+
*
|
|
158
|
+
* This is the piece that makes per-cell validation work, and it exists because of a limit in the
|
|
159
|
+
* framework rather than a preference: a control bound to a collection is handed that field's own
|
|
160
|
+
* errors and never its descendants'. Rules declared with `applyEach` therefore land on the row
|
|
161
|
+
* fields, one level below anything the sheet could see for itself.
|
|
162
|
+
*
|
|
163
|
+
* So the host keeps the field tree, and this walks it: for each row and each column, it reads
|
|
164
|
+
* that field's errors and writes the first message under the key the sheet addresses cells by.
|
|
165
|
+
*
|
|
166
|
+
* ```ts
|
|
167
|
+
* readonly form = form(this.lines, (path) => {
|
|
168
|
+
* applyEach(path, (line) => {
|
|
169
|
+
* min(line.units, 1, { message: 'At least one unit' });
|
|
170
|
+
* });
|
|
171
|
+
* });
|
|
172
|
+
*
|
|
173
|
+
* protected readonly cellErrors = computed(() =>
|
|
174
|
+
* spreadsheetFieldErrors(this.form, this.lines(), this.columns, (row) => row.id)
|
|
175
|
+
* );
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* @param form The field tree over the rows.
|
|
179
|
+
* @param rows The rows themselves, in the same order.
|
|
180
|
+
* @param columns The sheet's columns; only aliases that name a field are looked up.
|
|
181
|
+
* @param rowKey How the sheet names a row, so the keys line up with `states`.
|
|
182
|
+
* @param touchedOnly Whether to report only fields the reader has visited. True by default: an
|
|
183
|
+
* untouched form that paints itself red the moment it renders is a form nobody trusts.
|
|
184
|
+
*/
|
|
185
|
+
function spreadsheetFieldErrors(form, rows, columns, rowKey, touchedOnly = true) {
|
|
186
|
+
const errors = {};
|
|
187
|
+
const tree = form;
|
|
188
|
+
rows.forEach((row, index) => {
|
|
189
|
+
const rowField = tree[index];
|
|
190
|
+
if (!rowField) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
for (const column of columns) {
|
|
194
|
+
const message = firstMessage(rowField[column.key], touchedOnly);
|
|
195
|
+
if (message) {
|
|
196
|
+
errors[`${rowKey(row)}\t${column.key}`] = message;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return errors;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The first message a field has to offer, or nothing.
|
|
204
|
+
*
|
|
205
|
+
* Read defensively. A column alias need not name a field at all — a calculated total has no
|
|
206
|
+
* counterpart in the model — and reaching into a field tree for a key that is not there must
|
|
207
|
+
* report "no error" rather than throw in the middle of a render.
|
|
208
|
+
*/
|
|
209
|
+
function firstMessage(field, touchedOnly) {
|
|
210
|
+
if (typeof field !== 'function') {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const state = field();
|
|
215
|
+
if (touchedOnly && state['touched'] && state['touched']() !== true) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
const list = state['errors']?.();
|
|
219
|
+
const first = list?.[0];
|
|
220
|
+
return first?.message ?? first?.kind;
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Marks the field behind a cell as visited.
|
|
228
|
+
*
|
|
229
|
+
* Errors are held back until a cell has been touched, and a sheet has no way to report that on
|
|
230
|
+
* its own: the `FormField` directive learns it from a control's `touch` output, and a sheet
|
|
231
|
+
* driven through `errors` is not bound to a field at all. So the host says it, from the
|
|
232
|
+
* `commit`, `pasted` and `cleared` handlers it already writes.
|
|
233
|
+
*
|
|
234
|
+
* Without this call, `spreadsheetFieldErrors()` in its default mode reports nothing and no cell
|
|
235
|
+
* is ever marked — which looks exactly like validation that does not work.
|
|
236
|
+
*
|
|
237
|
+
* ```ts
|
|
238
|
+
* protected onCommit(change: HubSpreadsheetCommit<Line>): void {
|
|
239
|
+
* this.lines.update(...);
|
|
240
|
+
* touchSpreadsheetField(this.orderForm, change.coords.row, change.column.key);
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*
|
|
244
|
+
* @param form The field tree over the rows.
|
|
245
|
+
* @param row The row index of the cell.
|
|
246
|
+
* @param columnKey The column's alias.
|
|
247
|
+
*/
|
|
248
|
+
function touchSpreadsheetField(form, row, columnKey) {
|
|
249
|
+
const field = form[row]?.[columnKey];
|
|
250
|
+
if (typeof field !== 'function') {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const state = field();
|
|
255
|
+
state.markAsTouched?.();
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// A column alias that names no field. Nothing to mark, and nothing to report.
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/*
|
|
263
|
+
* Public API Surface of ng-hub-ui-spreadsheet/signals
|
|
264
|
+
*
|
|
265
|
+
* Opt-in Angular Signal Forms (`@angular/forms/signals`) integration. Importing this entry
|
|
266
|
+
* point is the only place that pulls in that API; the core `ng-hub-ui-spreadsheet` package
|
|
267
|
+
* never touches it, so a project that uses neither forms system pays nothing for it.
|
|
268
|
+
*
|
|
269
|
+
* Worth knowing before you choose a shape: a control whose value is a collection receives only
|
|
270
|
+
* that field's own errors, never its descendants'. `<hub-spreadsheet-field>` is therefore the
|
|
271
|
+
* convenience, not the whole story — for per-cell validation, keep the sheet bound to your own
|
|
272
|
+
* rows and feed its `errors` input from the field tree. `spreadsheetFieldErrors()` does that
|
|
273
|
+
* mapping for you.
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Generated bundle index. Do not edit.
|
|
278
|
+
*/
|
|
279
|
+
|
|
280
|
+
export { HubSpreadsheetFieldComponent, spreadsheetFieldErrors, touchSpreadsheetField };
|
|
281
|
+
//# sourceMappingURL=ng-hub-ui-spreadsheet-signals.mjs.map
|
|
@@ -0,0 +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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"}
|