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.
@@ -0,0 +1,1958 @@
1
+ import * as ng_hub_ui_utils from 'ng-hub-ui-utils';
2
+ import { HubGridCoords, HubGridRange, HubGridSpan, HubGridWindow } from 'ng-hub-ui-utils';
3
+ import * as _angular_core from '@angular/core';
4
+ import { TemplateRef, ViewContainerRef, EnvironmentProviders, InjectionToken, OnDestroy } from '@angular/core';
5
+
6
+ /**
7
+ * The vocabulary of a spreadsheet: what a column declares, what a cell shows, and what the sheet
8
+ * hands back when something changes.
9
+ *
10
+ * The sheet knows nothing about what the values mean. The owner describes the columns, keeps
11
+ * whatever save state it wants, and does the writing when the sheet reports a change. That split
12
+ * is what lets the same component serve an invoice, a price list and a timesheet.
13
+ */
14
+ /**
15
+ * How a column shows its values and reads back what is typed into it.
16
+ *
17
+ * `select` is the one that changes behaviour rather than formatting: a column with a closed list
18
+ * of answers typed by hand accepts things that are not on the list, and checking afterwards is
19
+ * too late.
20
+ */
21
+ type HubSpreadsheetValueKind = 'text' | 'number' | 'currency' | 'date' | 'boolean' | 'select';
22
+ /** One answer a `select` column offers. */
23
+ interface HubSpreadsheetOption {
24
+ /** What is stored. */
25
+ readonly value: HubSpreadsheetValue;
26
+ /** What is shown, both in the cell and in the list. */
27
+ readonly label: string;
28
+ /** Listed but not choosable — a value being retired, say, that old rows still hold. */
29
+ readonly disabled?: boolean;
30
+ }
31
+ /**
32
+ * Where a cell's save stands.
33
+ *
34
+ * `pending` is a value typed into a sheet that is saved as a whole later: written, not yet sent.
35
+ * `conflict` is somebody else's write landing on a cell this session had already changed.
36
+ */
37
+ type HubSpreadsheetCellState = 'pending' | 'saving' | 'saved' | 'error' | 'conflict';
38
+ /** A value as the sheet carries it; null is an empty cell, not a zero. */
39
+ type HubSpreadsheetValue = string | number | null;
40
+ /** What a single cell shows and allows, worked out by its column from the row. */
41
+ interface HubSpreadsheetCell {
42
+ /** What the cell holds. */
43
+ readonly value: HubSpreadsheetValue;
44
+ /**
45
+ * What to show instead of the value.
46
+ *
47
+ * For a cell whose display differs from its data — a code shown as a name, a number shown
48
+ * with a unit. The clipboard still carries `value`, so a copy round-trips the data and not
49
+ * the decoration.
50
+ */
51
+ readonly text?: string | null;
52
+ /** A second, quieter line under the value. */
53
+ readonly secondary?: string | null;
54
+ /** Whether the cell can be typed into. Read-only when left out. */
55
+ readonly editable?: boolean;
56
+ /** Shown in a quieter tone, for a value that comes from somewhere else. */
57
+ readonly muted?: boolean;
58
+ /** Said when the pointer rests on the cell. */
59
+ readonly title?: string | null;
60
+ }
61
+ /**
62
+ * One column of a sheet.
63
+ *
64
+ * Note the two identities, and that they are not interchangeable. `key` is what everything stored
65
+ * points at — the save state, the structured output, and later the formulas. `header` is what a
66
+ * reader sees and may be rewritten at any time without touching a single stored reference. Making
67
+ * the visible name the key is the mistake Excel made: rename a column there and every formula in
68
+ * the workbook has to be rewritten to match.
69
+ */
70
+ interface HubSpreadsheetColumn<TRow> {
71
+ /** Stable identifier, unique within the sheet. Never shown to the reader. */
72
+ readonly key: string;
73
+ /** The header text, or a translation key when `translate` is set. */
74
+ readonly header: string;
75
+ /** Resolves `header` through the translation system instead of printing it literally. */
76
+ readonly translate?: boolean;
77
+ /** A second, smaller line under the header. */
78
+ readonly subheader?: string | null;
79
+ /** How values are shown and parsed. Text when left out. */
80
+ readonly kind?: HubSpreadsheetValueKind;
81
+ /** Currency code for a `currency` column; the application's own when left out. */
82
+ readonly currency?: string | null;
83
+ /**
84
+ * The answers a `select` column offers, in the order they are listed.
85
+ *
86
+ * A cell shows the matching option's label rather than its stored value, and falls back to
87
+ * the raw value when nothing matches — an old row holding a value that has since been retired
88
+ * has to remain readable, not blank.
89
+ */
90
+ readonly options?: readonly HubSpreadsheetOption[];
91
+ /** Figures go to the end, so they read down the column. */
92
+ readonly align?: 'start' | 'center' | 'end';
93
+ /** A floor for the column's width, as a CSS length. */
94
+ readonly minWidth?: string;
95
+ /**
96
+ * A hint shown in an empty cell of this column while the pointer rests on it.
97
+ *
98
+ * Only on hover, and never in place of a value: a column of hints reads as a column of
99
+ * content, and a reader scanning for what is filled in would have to look twice at every row.
100
+ */
101
+ readonly placeholder?: string;
102
+ /**
103
+ * Keeps the header readable by assistive technology but hides it from sight, for a column
104
+ * whose cells explain themselves and need no name repeated down every row.
105
+ */
106
+ readonly headerHidden?: boolean;
107
+ /**
108
+ * A formula every cell of this column is worked out from, fixed by whoever declared it.
109
+ *
110
+ * The other way round from a formula a reader types: that one is a value the row holds and can
111
+ * be changed like any other, while this one belongs to the column and is the same in every
112
+ * row. A total that is part of how the sheet works — a line total, a running balance — belongs
113
+ * here, where nobody can type over it by accident and no round trip through the data can lose
114
+ * it.
115
+ *
116
+ * The cells of such a column cannot be typed into; their `editable` is ignored, since there is
117
+ * nothing to edit. Needs `formulas` on the sheet, as any formula does.
118
+ */
119
+ readonly formula?: string;
120
+ /** What this column shows for a given row. */
121
+ readonly cell: (row: TRow) => HubSpreadsheetCell;
122
+ }
123
+ /** A cell as the sheet hands it to its owner. */
124
+ interface HubSpreadsheetCellRef<TRow> {
125
+ /** Where it sits. */
126
+ readonly coords: HubGridCoords;
127
+ /** The row it belongs to. */
128
+ readonly row: TRow;
129
+ /** The column it belongs to. */
130
+ readonly column: HubSpreadsheetColumn<TRow>;
131
+ /** What it showed when the event happened. */
132
+ readonly cell: HubSpreadsheetCell;
133
+ }
134
+ /** A value typed or pasted into a cell, already read as a number where the column is one. */
135
+ interface HubSpreadsheetCommit<TRow> extends HubSpreadsheetCellRef<TRow> {
136
+ /** The new value; null when the cell was emptied. */
137
+ readonly value: HubSpreadsheetValue;
138
+ }
139
+ /**
140
+ * A block pasted into the sheet, and what did not fit.
141
+ *
142
+ * The counts matter: a paste that silently drops half its values is the complaint every grid in
143
+ * this field collects, so the owner is told how much fell on read-only cells and how much did not
144
+ * read as a number, and can say so.
145
+ */
146
+ interface HubSpreadsheetPaste<TRow> {
147
+ /** The cells that take a new value. */
148
+ readonly cells: readonly HubSpreadsheetCommit<TRow>[];
149
+ /** The rectangle the paste covered, trimmed to the sheet. */
150
+ readonly range: HubGridRange;
151
+ /** Values that landed on cells that cannot be typed into, or outside the sheet. */
152
+ readonly skipped: number;
153
+ /** Values that did not read as a number in a figure column. */
154
+ readonly rejected: number;
155
+ }
156
+ /**
157
+ * A row of the sheet as plain data, keyed by column alias.
158
+ *
159
+ * This is what the aliases buy: the owner gets `{ price: 12, quantity: 3 }` rather than a grid of
160
+ * coordinates it has to map back by position.
161
+ */
162
+ type HubSpreadsheetRecord = Record<string, HubSpreadsheetValue>;
163
+
164
+ /**
165
+ * Reading a sheet back as plain data, keyed by column alias.
166
+ *
167
+ * The point of the aliases: the owner works with `{ price: 12, units: 3 }` instead of mapping a
168
+ * grid of coordinates back onto its own fields by position, which breaks the moment a column
169
+ * moves.
170
+ *
171
+ * These read the value a column reports, never the text it decides to display, so a cell showing
172
+ * «120 ud.» still yields the number 120.
173
+ */
174
+ /** One row as plain data. */
175
+ declare function spreadsheetRecord<TRow>(row: TRow, columns: readonly HubSpreadsheetColumn<TRow>[]): HubSpreadsheetRecord;
176
+ /** Every row as plain data, in the order the sheet holds them. */
177
+ declare function spreadsheetRecords<TRow>(rows: readonly TRow[], columns: readonly HubSpreadsheetColumn<TRow>[]): HubSpreadsheetRecord[];
178
+ /**
179
+ * The aliases declared more than once, each named a single time.
180
+ *
181
+ * A repeated alias is not a cosmetic problem: the later column silently overwrites the earlier
182
+ * one in every structured read, and once formulas arrive the reference becomes ambiguous. The
183
+ * sheet reports this rather than guessing which column was meant.
184
+ */
185
+ declare function duplicateColumnKeys<TRow>(columns: readonly HubSpreadsheetColumn<TRow>[]): string[];
186
+
187
+ /**
188
+ * Reading and writing the clipboard the way spreadsheets actually use it.
189
+ *
190
+ * Two flavours travel together. `text/plain` is tab-separated and carries what the source
191
+ * *displayed*, formatted in the source's own locale — so a figure copied from Excel in Spain
192
+ * arrives as `1.234,56 €`. `text/html` is where the raw number travels, in an attribute, and is
193
+ * the only place a value can be read back without guessing which convention wrote it.
194
+ *
195
+ * Reading only the plain text is the bug this module exists to avoid.
196
+ */
197
+ /** A cell as it came off the clipboard. */
198
+ interface HubClipboardCell {
199
+ /** What the source displayed. */
200
+ readonly text: string;
201
+ /** The underlying number, when the source declared one. */
202
+ readonly raw?: number;
203
+ }
204
+ /** The two flavours a paste may carry. */
205
+ interface HubClipboardPayload {
206
+ readonly text?: string;
207
+ readonly html?: string;
208
+ }
209
+ /** Both flavours of a copy, ready to be put on the clipboard. */
210
+ interface HubClipboardWrite {
211
+ readonly text: string;
212
+ readonly html: string;
213
+ }
214
+ /**
215
+ * Reads a number out of text written in the reader's convention.
216
+ *
217
+ * Deliberately three-valued: a number, `null` for an empty cell — which is an absence and not a
218
+ * zero — and `undefined` for text that is not a number at all, so a caller can refuse a paste
219
+ * instead of silently writing NaN.
220
+ *
221
+ * @param text The text as typed or as pasted.
222
+ * @param decimalMark Which character separates the decimals for this reader.
223
+ */
224
+ declare function parseDecimal(text: string, decimalMark: ',' | '.'): number | null | undefined;
225
+ /**
226
+ * Parses a pasted block into a table of cells.
227
+ *
228
+ * The plain text decides the shape, because it is the only flavour guaranteed to be there and to
229
+ * be complete. The HTML, when present and parseable, is read alongside it purely to recover raw
230
+ * numbers.
231
+ */
232
+ declare function parseClipboardTable(payload: HubClipboardPayload): HubClipboardCell[][];
233
+ /**
234
+ * Writes a block of values as both flavours.
235
+ *
236
+ * Numbers go into the plain text with a dot and no grouping, which every spreadsheet reads
237
+ * whatever its reader has configured, and into the HTML as `x:num`, which is what lets another
238
+ * instance of this component — or Excel — recover the exact value rather than re-parsing a
239
+ * formatted string.
240
+ */
241
+ declare function serialiseClipboardTable(rows: readonly (readonly HubSpreadsheetValue[])[]): HubClipboardWrite;
242
+
243
+ /**
244
+ * Adding and removing rows and columns while the sheet is running.
245
+ *
246
+ * The sheet never mutates anything itself. It reports what the reader asked for and the owner
247
+ * decides, because only the owner knows whether a line may be deleted, what a new row should
248
+ * start with, or whether any of it should reach a server. What the sheet does offer is the two
249
+ * pieces that are easy to get wrong: allocating an alias for a new column, and finding out what
250
+ * a deletion would leave pointing at nothing.
251
+ */
252
+ /** Which axis a structural change acts on. */
253
+ type HubSpreadsheetAxis = 'row' | 'column';
254
+ /** Where a new row or column goes, relative to the one the reader pointed at. */
255
+ type HubSpreadsheetInsertSide = 'before' | 'after';
256
+ /**
257
+ * Whether a structural change is offered at all.
258
+ *
259
+ * A plain boolean covers the usual case. A predicate covers the rest: a tariff sheet that lets
260
+ * you add lines but never delete the first one, a column set where only the ones the reader
261
+ * created may be removed.
262
+ */
263
+ type HubSpreadsheetPermission<TSubject> = boolean | ((subject: TSubject, index: number) => boolean);
264
+ /** The reader asked to insert. */
265
+ interface HubSpreadsheetInsertRequest {
266
+ readonly axis: HubSpreadsheetAxis;
267
+ /** Where the new one lands, already resolved to an index. */
268
+ readonly at: number;
269
+ /** How many to insert. */
270
+ readonly count: number;
271
+ /** For a column, an alias that is free and has never been used in this sheet. */
272
+ readonly key?: string;
273
+ }
274
+ /** The reader asked to delete. */
275
+ interface HubSpreadsheetDeleteRequest {
276
+ readonly axis: HubSpreadsheetAxis;
277
+ /** The first index to remove. */
278
+ readonly at: number;
279
+ /** How many to remove. */
280
+ readonly count: number;
281
+ /**
282
+ * For a column, the aliases about to disappear.
283
+ *
284
+ * Handed over so the owner can check what still points at them before agreeing — see
285
+ * {@link danglingColumnKeys}.
286
+ */
287
+ readonly keys?: readonly string[];
288
+ }
289
+ /** How a sheet is allowed to change shape. Everything is refused unless turned on. */
290
+ interface HubSpreadsheetStructureOptions<TRow> {
291
+ readonly insertRows?: HubSpreadsheetPermission<TRow | null>;
292
+ readonly deleteRows?: HubSpreadsheetPermission<TRow>;
293
+ readonly insertColumns?: HubSpreadsheetPermission<HubSpreadsheetColumn<TRow> | null>;
294
+ readonly deleteColumns?: HubSpreadsheetPermission<HubSpreadsheetColumn<TRow>>;
295
+ }
296
+ /**
297
+ * Whether a permission lets this particular subject through.
298
+ *
299
+ * Silence means no. A sheet that gains the ability to delete rows because an option was left out
300
+ * is a worse failure than one that refuses until told otherwise.
301
+ *
302
+ * A predicate that throws is treated as a refusal: the owner's guard is broken, and acting on a
303
+ * broken guard is how data gets deleted that should not have been.
304
+ */
305
+ declare function isStructureAllowed<TSubject>(permission: HubSpreadsheetPermission<TSubject> | undefined, subject: TSubject, index: number): boolean;
306
+ /**
307
+ * Where an insertion actually lands.
308
+ *
309
+ * @param reference The row or column the reader pointed at.
310
+ * @param side Whether the new one goes before it or after it.
311
+ * @param length How many there are now.
312
+ * @returns An index between 0 and `length`, inclusive, since appending at the end is legal.
313
+ */
314
+ declare function resolveInsertIndex(reference: number, side: HubSpreadsheetInsertSide, length: number): number;
315
+ /**
316
+ * An alias for a new column that has never belonged to another one.
317
+ *
318
+ * Pass every alias the sheet has ever handed out, the live ones and the retired ones together.
319
+ * That is the whole point: an alias is an identity, so reusing a deleted column's name makes
320
+ * everything that referenced the old column silently resolve to the new one, and the mistake
321
+ * surfaces much later as wrong numbers rather than as an error.
322
+ *
323
+ * The same rule the project already applies to task codes, for the same reason.
324
+ *
325
+ * @param taken Every alias ever used, whether the column still exists or not.
326
+ * @param prefix The family to number within; `col` when not given.
327
+ */
328
+ declare function nextColumnKey(taken: Iterable<string>, prefix?: string): string;
329
+ /**
330
+ * The references that no longer resolve to a column.
331
+ *
332
+ * Give it whatever points at columns — the keys of a saved-state map, the aliases a formula
333
+ * mentions — and it reports which of them have nothing behind them any more. Each one named
334
+ * once, in the order first seen, so a message to the reader lists them the way they were found.
335
+ */
336
+ declare function danglingColumnKeys<TRow>(referenced: Iterable<string>, columns: readonly HubSpreadsheetColumn<TRow>[]): string[];
337
+
338
+ /** What an editor template is handed when it opens. */
339
+ interface HubSpreadsheetEditorContext<TRow> {
340
+ /** The value the cell holds, ready to be edited. */
341
+ readonly $implicit: HubSpreadsheetValue;
342
+ /** The cell as its column reported it. */
343
+ readonly cell: HubSpreadsheetCell;
344
+ /**
345
+ * The character that opened the cell, when it was opened by typing into it.
346
+ *
347
+ * That keystroke is the reader's first, and an editor that ignores it makes them type it
348
+ * again. A picker should put it in its search box; a field, in its value.
349
+ */
350
+ readonly seed: string | undefined;
351
+ /** The row it belongs to. */
352
+ readonly row: TRow;
353
+ /** The column it belongs to. */
354
+ readonly column: HubSpreadsheetColumn<TRow>;
355
+ /** Accepts a new value and closes the editor, moving the cursor down as Enter would. */
356
+ readonly commit: (value: HubSpreadsheetValue) => void;
357
+ /** Closes the editor and keeps what the cell held. */
358
+ readonly cancel: () => void;
359
+ }
360
+ /**
361
+ * Replaces the editor of one column with a template of your own.
362
+ *
363
+ * The sheet ships editors for the kinds it knows — a field for text and figures, a list for a
364
+ * column with options, a date field, a checkbox — and this is how a column gets something else:
365
+ * a `hub-select` with a search box, a picker of your own, anything that can report a value.
366
+ *
367
+ * ```html
368
+ * <hub-spreadsheet [rows]="rows()" [columns]="columns" [rowKey]="rowKey">
369
+ * <ng-template hubSpreadsheetEditor="supplier" let-value let-ctx="commit">
370
+ * <hub-select [options]="suppliers()" [ngModel]="value" (ngModelChange)="ctx($event)" />
371
+ * </ng-template>
372
+ * </hub-spreadsheet>
373
+ * ```
374
+ *
375
+ * The template is responsible for calling `commit` or `cancel`. Until it does, the editor stays
376
+ * open — which is what lets a picker take as many clicks as it needs without the sheet deciding
377
+ * the reader has finished.
378
+ */
379
+ declare class HubSpreadsheetEditorDirective<TRow = unknown> {
380
+ /** The alias of the column this editor belongs to. */
381
+ readonly column: _angular_core.InputSignal<string>;
382
+ /**
383
+ * The same rows the sheet is given, only so the compiler knows what a row is here.
384
+ *
385
+ * Nothing reads it. A template is compiled on its own, with no way of knowing which sheet will
386
+ * mount it, so `let-row` arrives as `unknown` and every use of it needs a cast. Handing the
387
+ * rows over is how Angular's own structural directives solve this, and it costs one binding.
388
+ */
389
+ readonly rows: _angular_core.InputSignal<readonly TRow[]>;
390
+ readonly template: TemplateRef<HubSpreadsheetEditorContext<TRow>>;
391
+ /** Lets Angular narrow `let-` bindings inside the template to the real context type. */
392
+ static ngTemplateContextGuard<TRow>(_directive: HubSpreadsheetEditorDirective<TRow>, _context: unknown): _context is HubSpreadsheetEditorContext<TRow>;
393
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubSpreadsheetEditorDirective<any>, never>;
394
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HubSpreadsheetEditorDirective<any>, "[hubSpreadsheetEditor]", never, { "column": { "alias": "hubSpreadsheetEditor"; "required": true; "isSignal": true; }; "rows": { "alias": "hubSpreadsheetEditorRows"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
395
+ }
396
+
397
+ /** What a cell template is handed while the cell is sitting still. */
398
+ interface HubSpreadsheetCellContext<TRow> {
399
+ /** What the cell holds. */
400
+ readonly $implicit: HubSpreadsheetValue;
401
+ /** The cell as its column reported it. */
402
+ readonly cell: HubSpreadsheetCell;
403
+ /** The row it belongs to. */
404
+ readonly row: TRow;
405
+ /** The column it belongs to. */
406
+ readonly column: HubSpreadsheetColumn<TRow>;
407
+ /** Whether this is the cell the reader is on. */
408
+ readonly active: boolean;
409
+ }
410
+ /**
411
+ * Draws one column's cells yourself: a badge for a state, an avatar beside a name, a row of
412
+ * actions.
413
+ *
414
+ * The sibling of `hubSpreadsheetEditor`, for the other half of a cell's life. That one replaces
415
+ * what appears when the cell is opened; this one replaces what the cell shows while it is not.
416
+ *
417
+ * ```html
418
+ * <hub-spreadsheet [rows]="rows()" [columns]="columns" [rowKey]="rowKey">
419
+ * <ng-template hubSpreadsheetCell="status" let-value>
420
+ * <hub-badge [variant]="value === 'done' ? 'success' : 'warning'">{{ value }}</hub-badge>
421
+ * </ng-template>
422
+ * </hub-spreadsheet>
423
+ * ```
424
+ *
425
+ * Two things stay the sheet's whatever the template draws. The clipboard carries the cell's value,
426
+ * not its decoration, so a copy round-trips as data rather than as a picture of it. And the cell
427
+ * is still the thing the keyboard is on: the template is drawn inside it, not instead of it.
428
+ *
429
+ * A template with something clickable in it joins the tab order of the page, which the grid's own
430
+ * roving tab stop does not manage — put the action behind `hubSpreadsheetEditor` instead when the
431
+ * cell should be entered before it can be used.
432
+ */
433
+ declare class HubSpreadsheetCellDirective<TRow = unknown> {
434
+ /** The alias of the column these cells belong to. */
435
+ readonly column: _angular_core.InputSignal<string>;
436
+ /**
437
+ * The same rows the sheet is given, only so the compiler knows what a row is here.
438
+ *
439
+ * Nothing reads it. A template is compiled on its own, with no way of knowing which sheet will
440
+ * mount it, so `let-row` arrives as `unknown` and every use of it needs a cast. Handing the
441
+ * rows over is how Angular's own structural directives solve this, and it costs one binding:
442
+ *
443
+ * ```html
444
+ * <ng-template hubSpreadsheetCell="actions" [hubSpreadsheetCellRows]="orders()" let-row="row">
445
+ * ```
446
+ */
447
+ readonly rows: _angular_core.InputSignal<readonly TRow[]>;
448
+ readonly template: TemplateRef<HubSpreadsheetCellContext<TRow>>;
449
+ /** Lets Angular narrow `let-` bindings inside the template to the real context type. */
450
+ static ngTemplateContextGuard<TRow>(_directive: HubSpreadsheetCellDirective<TRow>, _context: unknown): _context is HubSpreadsheetCellContext<TRow>;
451
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubSpreadsheetCellDirective<any>, never>;
452
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HubSpreadsheetCellDirective<any>, "[hubSpreadsheetCell]", never, { "column": { "alias": "hubSpreadsheetCell"; "required": true; "isSignal": true; }; "rows": { "alias": "hubSpreadsheetCellRows"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
453
+ }
454
+
455
+ /**
456
+ * What to offer a reader who is in the middle of writing a formula.
457
+ *
458
+ * A formula names columns by an alias that is nowhere on screen — the header shows a title meant
459
+ * for people, not the key the formula needs — so writing one from memory means knowing something
460
+ * the sheet never showed. That is the actual problem: not that the language is hard, but that the
461
+ * words are hidden.
462
+ *
463
+ * Kept as arithmetic over strings, with no DOM and no component, so the awkward parts — where the
464
+ * word being typed starts, what counts as one — can be held still by a test.
465
+ */
466
+ /** One thing the reader can put into the formula. */
467
+ interface HubFormulaSuggestion {
468
+ /** What to show. */
469
+ readonly label: string;
470
+ /** What it is, so the list can say so. */
471
+ readonly kind: 'column' | 'function';
472
+ /** What goes into the draft. */
473
+ readonly insert: string;
474
+ /** A word on what it does, for a function. */
475
+ readonly hint?: string;
476
+ }
477
+ /** What the sheet knows that a formula might want. */
478
+ interface HubFormulaVocabulary {
479
+ /** The column aliases, in the order the columns are in. */
480
+ readonly aliases: readonly string[];
481
+ /** What each alias is called on screen, so the list can show both. */
482
+ readonly titles?: Readonly<Record<string, string>>;
483
+ }
484
+ /** The functions the engine knows, with a word on each. */
485
+ declare const HUB_FORMULA_FUNCTIONS: ReadonlyArray<{
486
+ name: string;
487
+ hint: string;
488
+ }>;
489
+ /** Where the word being typed starts, and what it says so far. */
490
+ interface HubFormulaFragment {
491
+ /** Where in the draft it begins. */
492
+ readonly start: number;
493
+ /** What has been typed of it. */
494
+ readonly text: string;
495
+ /** Whether the reader is inside brackets, which is where a column belongs. */
496
+ readonly inBrackets: boolean;
497
+ }
498
+ /**
499
+ * The word the caret is in the middle of.
500
+ *
501
+ * Inside brackets it is a column being named — `[pri` — and everywhere else it is a function, or
502
+ * the beginning of one. Everything before the last operator, bracket or space is settled and is
503
+ * left alone.
504
+ */
505
+ declare function formulaFragment(draft: string, caret?: number): HubFormulaFragment;
506
+ /**
507
+ * What to offer for a draft, in the order it should be shown.
508
+ *
509
+ * Columns first while inside brackets, functions first outside them, because that is what the
510
+ * reader is asking for by having typed the bracket at all. An empty fragment offers everything:
511
+ * somebody who has just typed `=` is exactly the person who needs the list most.
512
+ *
513
+ * @param draft - The formula as it stands, `=` and all.
514
+ * @param vocabulary - The aliases the sheet has.
515
+ * @param caret - Where the caret is; the end of the draft when not given.
516
+ */
517
+ declare function suggestFormula(draft: string, vocabulary: HubFormulaVocabulary, caret?: number): readonly HubFormulaSuggestion[];
518
+ /**
519
+ * The draft with a suggestion put in, and where the caret should end up.
520
+ *
521
+ * The caret matters more than it looks: dropped at the end of `SUM(` it carries straight on into
522
+ * the brackets, and dropped after `[price]` it is ready for the operator. Leaving it where it was
523
+ * makes the reader move it by hand every single time.
524
+ */
525
+ declare function applySuggestion(draft: string, suggestion: HubFormulaSuggestion, caret?: number): {
526
+ readonly draft: string;
527
+ readonly caret: number;
528
+ };
529
+
530
+ /**
531
+ * Hosting a richer editor inside a cell without depending on the library it comes from.
532
+ *
533
+ * The sheet ships a native list for a column with options, which needs nothing installed and
534
+ * behaves the way the platform does on a phone. A project that has a component library wires this
535
+ * adapter once and every list column upgrades — no template per sheet, and the sheet still imports
536
+ * nothing: the shape is declared here and satisfied structurally, which is the same bargain
537
+ * `ng-hub-ui-paginable` strikes for its table controls.
538
+ *
539
+ * `ng-hub-ui-forms` ships an implementation that fits: `hubFormControlAdapter`.
540
+ */
541
+ /** How the adapter should present the control's name. Mirrors the family's label types. */
542
+ type HubSpreadsheetControlLabelType = 'floating' | 'stacked' | 'horizontal' | 'visually-hidden';
543
+ /** One answer a hosted list offers. */
544
+ interface HubSpreadsheetControlOption {
545
+ value: unknown;
546
+ label: string;
547
+ /**
548
+ * Listed but not choosable, where the adapter honours it.
549
+ *
550
+ * The sheet's own list guarantees this; a hosted control is another library's, and the sheet
551
+ * can only pass the flag on.
552
+ */
553
+ disabled?: boolean;
554
+ }
555
+ /**
556
+ * What the sheet asks for when a cell of a list column opens.
557
+ *
558
+ * Only lists go through the adapter. A text or figure cell keeps the sheet's own editor, which
559
+ * holds the draft, reads the reader's decimal mark and refuses what the column cannot store —
560
+ * none of which a foreign control knows about, and all of which a value-per-keystroke contract
561
+ * would throw away.
562
+ */
563
+ interface HubSpreadsheetControlConfig {
564
+ /** The only kind the sheet hosts today. */
565
+ kind: 'select';
566
+ /** What the cell holds. */
567
+ value: unknown;
568
+ /** The answers, in the order the column declared them. */
569
+ options?: ReadonlyArray<HubSpreadsheetControlOption>;
570
+ /** The column's header, so the control is not left anonymous inside a grid. */
571
+ label?: string;
572
+ /** How to present that name. `visually-hidden` unless the host says otherwise. */
573
+ labelType?: HubSpreadsheetControlLabelType;
574
+ /** The name to fall back on where the adapter ignores {@link label}. */
575
+ ariaLabel?: string;
576
+ /** Shown while the cell is empty. */
577
+ placeholder?: string;
578
+ /** Extra class forwarded to the control. */
579
+ cssClass?: string;
580
+ /** Whether the control offers a search box. True from a cell: a long list is why you wire one. */
581
+ searchable?: boolean;
582
+ /** Whether it offers to clear itself. False from a cell: `Delete` empties a cell. */
583
+ clearable?: boolean;
584
+ /** Whether it opens as it appears. True from a cell, where the click that opened it is spent. */
585
+ autoOpen?: boolean;
586
+ /** A term to start filtered by — the character the reader opened the cell with. */
587
+ searchTerm?: string;
588
+ /**
589
+ * Called when the reader chooses. The sheet commits and moves on, as the native list does.
590
+ *
591
+ * Typed `unknown` rather than the sheet's own value type, and deliberately: a callback is
592
+ * checked the other way round, so a narrower parameter here would refuse the very adapters
593
+ * this contract exists to accept. What comes back is read through the column, which is where
594
+ * the refusing belongs anyway.
595
+ */
596
+ onValueChange: (value: unknown) => void;
597
+ }
598
+ /** Live handle to a control the adapter created. */
599
+ interface HubSpreadsheetControlHandle {
600
+ /** Pushes a value in from outside. */
601
+ setValue(value: unknown): void;
602
+ /** Destroys the control and releases what it holds. */
603
+ destroy(): void;
604
+ }
605
+ /**
606
+ * Optional, structurally typed adapter that renders a cell's list with a richer control.
607
+ *
608
+ * Declared here rather than imported, so the library keeps no dependency at all on whatever
609
+ * provides it. With no adapter registered the sheet draws its native list; with one, every list
610
+ * column upgrades at once.
611
+ */
612
+ interface HubSpreadsheetControlsAdapter {
613
+ /**
614
+ * Creates a control inside `container`.
615
+ *
616
+ * @returns A handle to update or destroy it.
617
+ */
618
+ create(container: ViewContainerRef, config: HubSpreadsheetControlConfig): HubSpreadsheetControlHandle;
619
+ }
620
+
621
+ /**
622
+ * Working out what a reader's merge or unmerge does to a list of blocks.
623
+ *
624
+ * The sheet reports what was asked for and writes nothing, as with rows and columns — the list of
625
+ * blocks belongs to whoever declared it, and is usually saved with the document. These are the two
626
+ * lines of arithmetic that would otherwise be rewritten in every host, and the place where the
627
+ * awkward case lives: a merge swallows the blocks already inside it, and a host that merely
628
+ * appends ends up with two blocks claiming the same cell.
629
+ */
630
+ /** A merge the reader asked for, and what it absorbs. */
631
+ interface HubSpreadsheetMergeRequest {
632
+ /** The block to create. */
633
+ readonly span: HubGridSpan;
634
+ /** The anchors of the blocks it swallows, which have to go. */
635
+ readonly replaced: readonly HubGridCoords[];
636
+ }
637
+ /** The blocks that share a cell with a rectangle, in the order they were declared. */
638
+ declare function spansWithin(spans: readonly HubGridSpan[], range: HubGridRange): readonly HubGridSpan[];
639
+ /**
640
+ * The block a selection would become, and the blocks it swallows.
641
+ *
642
+ * `null` for a selection of one cell that is not already a block: there is nothing to merge, and
643
+ * an entry that does nothing should not be offered.
644
+ */
645
+ declare function mergeRequestFor(spans: readonly HubGridSpan[], range: HubGridRange): HubSpreadsheetMergeRequest | null;
646
+ /**
647
+ * The list of blocks after a merge.
648
+ *
649
+ * The swallowed ones are dropped rather than left behind: two blocks claiming the same cell is a
650
+ * sheet that draws one of them and loses the other, and which one depends on the order they happen
651
+ * to be declared in.
652
+ */
653
+ declare function applySpanMerge(spans: readonly HubGridSpan[], request: HubSpreadsheetMergeRequest): HubGridSpan[];
654
+ /** The list of blocks after the ones anchored at `anchors` are taken apart. */
655
+ declare function applySpanUnmerge(spans: readonly HubGridSpan[], anchors: readonly HubGridCoords[]): HubGridSpan[];
656
+
657
+ /**
658
+ * An editable sheet of cells: typed into, moved through with the keyboard, and pasted into from
659
+ * a spreadsheet.
660
+ *
661
+ * It knows nothing about what the cells mean. The owner describes each column, keeps the save
662
+ * state it cares about, and does the writing when the sheet reports a change. The sheet never
663
+ * mutates the rows it is given, which is what lets the same component serve an invoice, a price
664
+ * list and a timesheet without learning about any of them.
665
+ *
666
+ * Focus lives on the cells themselves, with one tab stop that travels — the arrangement screen
667
+ * readers expect from a grid. Pasting is the exception: a browser only dispatches `paste` to an
668
+ * editable element, so the paste shortcut hands focus to an off-screen field for the length of
669
+ * the event and takes it straight back.
670
+ */
671
+ declare class HubSpreadsheetComponent<TRow> {
672
+ #private;
673
+ /** The rows, as the owner holds them. Never written to. */
674
+ readonly rows: _angular_core.InputSignal<readonly TRow[]>;
675
+ /** What each column shows and allows. */
676
+ readonly columns: _angular_core.InputSignal<readonly HubSpreadsheetColumn<TRow>[]>;
677
+ /** A stable name per row, so a save state follows its row across a reload. */
678
+ readonly rowKey: _angular_core.InputSignal<(row: TRow) => string>;
679
+ /** The save state of each cell, keyed by `rowKey` and column alias joined by a tab. */
680
+ readonly states: _angular_core.InputSignal<Record<string, HubSpreadsheetCellState>>;
681
+ /**
682
+ * A validation message per cell, keyed the same way as {@link states}.
683
+ *
684
+ * Owned by the host, because validation belongs to whoever knows what the data means. Keeping
685
+ * it a plain map rather than tying it to one forms API is what lets the same sheet be driven
686
+ * by Signal Forms, by reactive forms, or by nothing at all.
687
+ */
688
+ readonly errors: _angular_core.InputSignal<Record<string, string>>;
689
+ /** The character this reader types decimals with. */
690
+ readonly decimalMark: _angular_core.InputSignal<"," | ".">;
691
+ /** What an empty sheet says. */
692
+ readonly emptyText: _angular_core.InputSignal<string>;
693
+ /** How many rows Page Up and Page Down travel. */
694
+ readonly pageSize: _angular_core.InputSignal<number>;
695
+ /** Turns off every editor, whatever the individual cells say. */
696
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, unknown>;
697
+ /**
698
+ * How many columns stay pinned against the leading edge while the sheet scrolls sideways.
699
+ *
700
+ * Counted from the start, the way a spreadsheet freezes panes, rather than marked one by one:
701
+ * a set of frozen columns with a gap in it cannot be laid out, so the shape of the input rules
702
+ * the mistake out instead of validating against it.
703
+ */
704
+ readonly frozenColumns: _angular_core.InputSignal<number>;
705
+ /** How many rows stay pinned below the header while the sheet scrolls down. */
706
+ readonly frozenRows: _angular_core.InputSignal<number>;
707
+ /**
708
+ * Cells merged into blocks, each named by its top-left corner.
709
+ *
710
+ * Owned by the host, which is why they come in as an input and why the reader's merging goes
711
+ * back out as a request rather than being done here — see {@link mergeable}. A declared block
712
+ * is treated as one cell everywhere it matters: the cursor lands on the block rather than
713
+ * inside it, a selection that would clip one grows to contain it, and the covered cells are
714
+ * not drawn at all.
715
+ */
716
+ readonly spans: _angular_core.InputSignal<readonly HubGridSpan[]>;
717
+ /** Which structural changes the reader may ask for. Everything is refused by default. */
718
+ readonly structure: _angular_core.InputSignal<HubSpreadsheetStructureOptions<TRow>>;
719
+ /** Editors supplied by the host, one per column alias. */
720
+ protected readonly customEditors: _angular_core.Signal<readonly HubSpreadsheetEditorDirective<TRow>[]>;
721
+ /** Cell templates supplied by the host, for what a column shows while it is not being edited. */
722
+ protected readonly customCells: _angular_core.Signal<readonly HubSpreadsheetCellDirective<TRow>[]>;
723
+ /** Aliases retired by earlier deletions, so a new column never reuses one. */
724
+ readonly retiredColumnKeys: _angular_core.InputSignal<readonly string[]>;
725
+ /** Whether the sheet offers the fill handle at the corner of the selection. */
726
+ readonly fillHandle: _angular_core.InputSignalWithTransform<boolean, unknown>;
727
+ /** Whether undo is available; the sheet only asks, the owner keeps the history. */
728
+ readonly canUndo: _angular_core.InputSignalWithTransform<boolean, unknown>;
729
+ /** Whether redo is available. */
730
+ readonly canRedo: _angular_core.InputSignalWithTransform<boolean, unknown>;
731
+ /** Whether a reader may drag a column's trailing edge to widen or narrow it. */
732
+ readonly resizableColumns: _angular_core.InputSignalWithTransform<boolean, unknown>;
733
+ /** Whether a reader may drag a header to put the column somewhere else. */
734
+ readonly reorderableColumns: _angular_core.InputSignalWithTransform<boolean, unknown>;
735
+ /**
736
+ * The width of each column in pixels, keyed by alias.
737
+ *
738
+ * Two-way, and view state rather than data: the sheet writes it as the reader drags, and the
739
+ * owner may persist it so a layout survives a reload. A column with no entry keeps whatever
740
+ * width its content asks for.
741
+ */
742
+ readonly columnWidths: _angular_core.ModelSignal<Record<string, number>>;
743
+ /** The narrowest a column may be dragged. */
744
+ readonly minColumnWidth: _angular_core.InputSignal<number>;
745
+ /**
746
+ * Whether a right-click offers the menu of structural changes.
747
+ *
748
+ * Off unless asked for, and each entry still has to pass `structure`. A menu that lists what
749
+ * the reader is not allowed to do is worse than no menu.
750
+ */
751
+ readonly contextMenu: _angular_core.InputSignalWithTransform<boolean, unknown>;
752
+ /**
753
+ * Whether the reader may put cells together and take them apart again.
754
+ *
755
+ * Off unless asked for, like every other structural change: a sheet whose shape is part of the
756
+ * document it belongs to should not be reshaped by a stray right-click. The blocks stay the
757
+ * host's — the sheet reports what was asked for through {@link mergeRequested} and
758
+ * {@link unmergeRequested} and writes nothing itself. Offered through the context menu, so it
759
+ * needs {@link contextMenu} to be reachable with the pointer.
760
+ */
761
+ readonly mergeable: _angular_core.InputSignalWithTransform<boolean, unknown>;
762
+ /**
763
+ * Whether a cell whose value starts with `=` is a formula.
764
+ *
765
+ * Off unless asked for, and not as caution for its own sake: a column of text may perfectly
766
+ * well hold `=1+1` as the thing somebody wrote, and a sheet that decides to do arithmetic on
767
+ * it has lost the reader's data.
768
+ *
769
+ * Formulas read columns by their alias — `=[price] * [units]`, `=SUM([total:])` — never by a
770
+ * coordinate, which is what the aliases were for: move a column, rename its header, and every
771
+ * formula still means what it said.
772
+ *
773
+ * The cell shows the answer; the editor shows the formula, so it can be corrected rather than
774
+ * retyped; and the clipboard carries what the cell holds, which is the formula.
775
+ */
776
+ readonly formulas: _angular_core.InputSignalWithTransform<boolean, unknown>;
777
+ /**
778
+ * Whether to draw only the rows the reader can see.
779
+ *
780
+ * Off unless asked for. A sheet of a few hundred rows is better off whole — every row is in the
781
+ * document, so the browser's own find works, printing works, and nothing depends on a
782
+ * measurement. Past a few thousand it stops being a choice: the first paint arrives late and
783
+ * every keystroke pays for cells nobody is looking at.
784
+ *
785
+ * What it costs: the rows out of view are not in the document, so `Ctrl+F` and printing only
786
+ * reach what is drawn, and the rows have to be of one height — see {@link rowHeight}.
787
+ */
788
+ readonly virtual: _angular_core.InputSignalWithTransform<boolean, unknown>;
789
+ /**
790
+ * How tall a row is, in pixels, when {@link virtual} is on.
791
+ *
792
+ * Nought means "measure the first one drawn", which is right whenever the rows are alike. Say
793
+ * it outright for a sheet whose first row is not typical, or one that is built while hidden —
794
+ * a tab that is not open measures every row as nought, and a window worked out from nought is
795
+ * the whole sheet.
796
+ */
797
+ readonly rowHeight: _angular_core.InputSignal<number>;
798
+ /**
799
+ * What opens the editor with the pointer.
800
+ *
801
+ * `double-click` by default, the spreadsheet convention: a click picks the cell and leaves its
802
+ * value where it is. Selecting is the gesture a reader makes most — to copy, to look, to drag
803
+ * out a range — and turning every one of them into an open field puts the value at the mercy
804
+ * of the next keystroke. Typing is still the short way in: with the cell picked, the first
805
+ * character opens the editor and lands in it, so nothing is lost by not opening on the click.
806
+ *
807
+ * `click` opens on the first one, for a sheet that exists to be typed into and where the two
808
+ * gestures per cell are the tax.
809
+ *
810
+ * Either way the editor opens on release and only when the pointer did not travel, so
811
+ * dragging out a range still works — and Shift-clicking still extends rather than edits.
812
+ */
813
+ readonly editOn: _angular_core.InputSignal<"click" | "double-click">;
814
+ /** A cell took a new value. */
815
+ readonly commit: _angular_core.OutputEmitterRef<HubSpreadsheetCommit<TRow>>;
816
+ /** A block was pasted. */
817
+ readonly pasted: _angular_core.OutputEmitterRef<HubSpreadsheetPaste<TRow>>;
818
+ /** Delete was pressed over these cells, the editable ones of the selection. */
819
+ readonly cleared: _angular_core.OutputEmitterRef<HubSpreadsheetCellRef<TRow>[]>;
820
+ /** The selected rectangle changed; null when nothing is selected. */
821
+ readonly selectionChange: _angular_core.OutputEmitterRef<HubGridRange | null>;
822
+ /** The reader asked to add rows or columns. */
823
+ readonly insertRequested: _angular_core.OutputEmitterRef<HubSpreadsheetInsertRequest>;
824
+ /** The reader asked to remove rows or columns. */
825
+ readonly deleteRequested: _angular_core.OutputEmitterRef<HubSpreadsheetDeleteRequest>;
826
+ /**
827
+ * A drag of the fill handle finished.
828
+ *
829
+ * Reported as one event for the whole gesture, so an owner keeping a history can make it one
830
+ * undo step. Filling cell by cell and undoing cell by cell is experienced as a bug.
831
+ */
832
+ readonly filled: _angular_core.OutputEmitterRef<HubSpreadsheetPaste<TRow>>;
833
+ /** The reader asked to undo. The owner holds the history and decides what that means. */
834
+ readonly undoRequested: _angular_core.OutputEmitterRef<void>;
835
+ /** The reader asked to redo. */
836
+ readonly redoRequested: _angular_core.OutputEmitterRef<void>;
837
+ /**
838
+ * The reader dropped a column somewhere else.
839
+ *
840
+ * Reported rather than applied: the column list belongs to the owner, as the rows do. The
841
+ * payload carries the reordered aliases so acting on it is a single assignment.
842
+ */
843
+ readonly columnMoved: _angular_core.OutputEmitterRef<{
844
+ from: number;
845
+ to: number;
846
+ key: string;
847
+ keys: string[];
848
+ }>;
849
+ /**
850
+ * The reader asked to put a selection together into one block.
851
+ *
852
+ * The payload carries the block to create and the anchors of the blocks it swallows, because a
853
+ * host that merely appends the new one ends up with two claiming the same cell — and which of
854
+ * them is drawn depends on the order they were declared in. `applySpanMerge()` does the whole
855
+ * thing in a line.
856
+ */
857
+ readonly mergeRequested: _angular_core.OutputEmitterRef<HubSpreadsheetMergeRequest>;
858
+ /** The reader asked to take apart the blocks their selection touches, named by their anchors. */
859
+ readonly unmergeRequested: _angular_core.OutputEmitterRef<readonly HubGridCoords[]>;
860
+ /** The cell the cursor is on. */
861
+ protected readonly active: _angular_core.WritableSignal<HubGridCoords | null>;
862
+ /** The other corner of the selection; null when only the active cell is selected. */
863
+ protected readonly anchor: _angular_core.WritableSignal<HubGridCoords | null>;
864
+ protected readonly editing: _angular_core.WritableSignal<boolean>;
865
+ protected readonly draft: _angular_core.WritableSignal<string>;
866
+ /**
867
+ * The character that opened the cell, when typing opened it.
868
+ *
869
+ * Kept beside the draft rather than inside it because a supplied editor needs it as it was
870
+ * typed, while the draft has already been turned into whatever the column stores.
871
+ */
872
+ private readonly seed;
873
+ /** The editor holds text that does not read as a value, so it stays open and marked. */
874
+ protected readonly invalid: _angular_core.WritableSignal<boolean>;
875
+ private dragging;
876
+ /** Whether the pointer visited another cell during this press, which makes it a drag. */
877
+ private dragMoved;
878
+ /** The selection a fill drag started from; null when no fill is in progress. */
879
+ protected readonly fillSource: _angular_core.WritableSignal<HubGridRange | null>;
880
+ /** What the fill would cover if released now. */
881
+ protected readonly fillTarget: _angular_core.WritableSignal<HubGridRange | null>;
882
+ /** The column being widened, with where the pointer started and how wide it then was. */
883
+ private resizing;
884
+ /** The column being dragged to another place. */
885
+ protected readonly movingColumn: _angular_core.WritableSignal<number | null>;
886
+ /** Where it would land if dropped now. */
887
+ protected readonly moveTargetColumn: _angular_core.WritableSignal<number | null>;
888
+ /** Where the structural menu sits, in pixels from the sheet's own corner; null when closed. */
889
+ protected readonly menuAt: _angular_core.WritableSignal<{
890
+ x: number;
891
+ y: number;
892
+ } | null>;
893
+ /** Where each frozen column sits, in pixels from the leading edge. */
894
+ protected readonly columnOffsets: _angular_core.WritableSignal<readonly number[]>;
895
+ /** Where each frozen row sits, in pixels from the top of the scroll area. */
896
+ protected readonly rowOffsets: _angular_core.WritableSignal<readonly number[]>;
897
+ /** The merges, indexed once per change rather than scanned on every keystroke. */
898
+ protected readonly spanMap: _angular_core.Signal<ng_hub_ui_utils.HubGridSpanMap>;
899
+ protected readonly bounds: _angular_core.Signal<{
900
+ rows: number;
901
+ cols: number;
902
+ }>;
903
+ /**
904
+ * What the viewport has told us about itself: how far it has scrolled and how tall it is.
905
+ *
906
+ * Nought until the first measurement, which is why {@link gridWindow} draws a first screenful
907
+ * from nothing rather than drawing nothing: the rows it puts in are what the measurement then
908
+ * measures.
909
+ */
910
+ private readonly viewport;
911
+ private readonly scrollTop;
912
+ private readonly viewportHeight;
913
+ /** The height of a row, measured off the first one drawn unless the host declared it. */
914
+ private readonly measuredRowHeight;
915
+ /** How far the viewport has travelled sideways, and how much of it is on screen. */
916
+ private readonly scrollLeft;
917
+ private readonly viewportWidth;
918
+ /** The width of every column, by index, as last measured. */
919
+ private readonly measuredColumnWidths;
920
+ /** The rows to draw: the frozen ones, then the window, or all of them when not virtualising. */
921
+ protected readonly rowWindow: _angular_core.Signal<HubGridWindow>;
922
+ /**
923
+ * The columns to draw, the same way as the rows.
924
+ *
925
+ * Their widths are measured rather than declared, so the first render draws them all — there
926
+ * is nothing to measure otherwise — and every render after that windows them with what the
927
+ * first one learnt. A column the sheet has never drawn has no width, and one unknown width is
928
+ * enough to fall back to drawing the lot: a window is only as honest as the sizes it is built
929
+ * from.
930
+ */
931
+ protected readonly columnWindow: _angular_core.Signal<HubGridWindow>;
932
+ /** The column indices in the document, frozen ones first. */
933
+ protected readonly drawnColumns: _angular_core.Signal<number[]>;
934
+ /**
935
+ * How many cells a row of the sheet actually holds, spacers included.
936
+ *
937
+ * What a spacer row has to span. `bounds().cols` would be wrong whenever the columns are
938
+ * windowed — it counts the columns the sheet has, not the ones in the document — and a colspan
939
+ * that overshoots stretches the table wider than its own header.
940
+ */
941
+ protected readonly drawnCellCount: _angular_core.Signal<number>;
942
+ /** The row indices in the document, frozen ones first. */
943
+ protected readonly drawnRows: _angular_core.Signal<number[]>;
944
+ /** Every cell, worked out once per change of rows or columns rather than on every paint. */
945
+ protected readonly grid: _angular_core.Signal<HubSpreadsheetCell[][]>;
946
+ /** Which suggestion the keyboard is on, or -1 when the reader has not moved into the list. */
947
+ private readonly suggestionAt;
948
+ /**
949
+ * What to offer while a formula is being written, or nothing when one is not.
950
+ *
951
+ * A formula names columns by an alias the sheet never shows — the header carries a title meant
952
+ * for people — so without this, writing one means knowing something that is not on screen.
953
+ */
954
+ protected readonly suggestions: _angular_core.Signal<readonly HubFormulaSuggestion[]>;
955
+ /** Whether the sheet is showing the list of what can go into a formula. */
956
+ protected readonly suggesting: _angular_core.Signal<boolean>;
957
+ /**
958
+ * The one the keyboard is on, which Enter and Tab put in.
959
+ *
960
+ * The first is taken as chosen the moment there is a word being typed, so `=SU` and Tab
961
+ * finishes it — which is what completing means, and what a reader who has seen a spreadsheet
962
+ * expects. Nothing is chosen when there is no word: a bare `=`, or a formula that has just
963
+ * closed a bracket, is a list to look at rather than a completion to accept, and Enter there
964
+ * means commit.
965
+ */
966
+ protected readonly suggestionIndex: _angular_core.Signal<number>;
967
+ /** Whether a formula is being written, which is when the aliases are worth showing. */
968
+ protected readonly writingFormula: _angular_core.Signal<boolean>;
969
+ /** The letters a column answers to in a formula: A, B … Z, AA. */
970
+ protected columnLetter(index: number): string;
971
+ /** Puts a suggestion into the draft and leaves the caret where the next thing is typed. */
972
+ protected useSuggestion(suggestion: HubFormulaSuggestion): void;
973
+ /** Whether a cell is showing a formula that could not be worked out. */
974
+ protected hasFormulaError(row: number, col: number): boolean;
975
+ protected readonly keys: _angular_core.Signal<string[]>;
976
+ protected readonly range: _angular_core.Signal<HubGridRange | null>;
977
+ /**
978
+ * Keeps the cursor where it was when the rows are replaced by a reload after a save, and drops
979
+ * it only once it points at nothing. Typing a value and pressing Enter has to land on the cell
980
+ * below even when the saved figures come back as fresh objects.
981
+ */
982
+ private readonly reconcile;
983
+ /**
984
+ * Keeps the viewport's measurements up to date while virtualising.
985
+ *
986
+ * In the read phase, like the frozen offsets: reading layout while the browser is writing one
987
+ * forces a reflow in the middle of it, which is how a grid loses frames exactly when it is
988
+ * being scrolled.
989
+ *
990
+ * It settles rather than loops. Measuring feeds the window, the window changes what is drawn,
991
+ * and the next pass measures the same numbers — a signal set to the value it already holds
992
+ * tells nobody, so the third pass does not happen. It reads `drawnRows` so that a sheet whose
993
+ * data arrives later is measured when its first row finally exists, which is the common case
994
+ * and the one a single measurement at birth gets wrong.
995
+ */
996
+ private readonly measureViewport;
997
+ /**
998
+ * The width of every column, read off the headers by the index they carry.
999
+ *
1000
+ * By index and not by position, because a windowed header holds the frozen columns and then
1001
+ * the ones in view — so the third header in the document is rarely the third column. A width
1002
+ * that is not on screen keeps whatever was measured last, which is what makes windowing the
1003
+ * columns possible at all: they are measured once, while the sheet is still drawing them all.
1004
+ */
1005
+ private readColumnWidths;
1006
+ /**
1007
+ * Measures the frozen tracks after every render and turns the sizes into offsets.
1008
+ *
1009
+ * Reading layout has to happen in the read phase or it forces a synchronous reflow in the
1010
+ * middle of writing one, which is the classic way a grid loses frames while scrolling.
1011
+ */
1012
+ private readonly measureFrozen;
1013
+ /**
1014
+ * Whether a column is pinned.
1015
+ *
1016
+ * Answered from the input alone, never from the measurement. A cell is taken out of the flow
1017
+ * because the caller asked for it; the measured offset only decides where it lands. Gating
1018
+ * the two on the same signal would leave the pane unpinned for the frame before the first
1019
+ * measurement, and permanently unpinned anywhere the render hooks do not run.
1020
+ */
1021
+ protected isFrozenColumn(col: number): boolean;
1022
+ /** Whether a row is pinned. Same reasoning as {@link isFrozenColumn}. */
1023
+ protected isFrozenRow(row: number): boolean;
1024
+ /** Where a frozen column sits, once measured; null until then, and the stylesheet pins it at zero. */
1025
+ protected columnOffsetOf(col: number): number | null;
1026
+ /** Where a frozen row sits, once measured. */
1027
+ protected rowOffsetOf(row: number): number | null;
1028
+ protected isColumnEdge(col: number): boolean;
1029
+ protected isRowEdge(row: number): boolean;
1030
+ /** Whether a cell is swallowed by a merge, and therefore not rendered. */
1031
+ protected isHidden(row: number, col: number): boolean;
1032
+ /** How many rows a cell occupies; null leaves the attribute off. */
1033
+ protected rowSpanOf(row: number, col: number): number | null;
1034
+ /** How many columns a cell occupies; null leaves the attribute off. */
1035
+ protected colSpanOf(row: number, col: number): number | null;
1036
+ /**
1037
+ * Whether a cell reaches the trailing edge of the sheet, counting the columns it spans.
1038
+ *
1039
+ * Asked rather than guessed with `:last-child`. Once cells can be swallowed by a merge, the
1040
+ * last one drawn in a row is not the one in the last column — in a row where the rightmost
1041
+ * block is covered it is the cell before it, and that is the one that would lose its border.
1042
+ */
1043
+ protected reachesEnd(row: number, col: number): boolean;
1044
+ protected isActive(row: number, col: number): boolean;
1045
+ protected isSelected(row: number, col: number): boolean;
1046
+ protected tabIndexOf(row: number, col: number): number;
1047
+ protected stateOf(row: number, col: number): HubSpreadsheetCellState | undefined;
1048
+ /** The validation message the host attached to a cell, if any. */
1049
+ protected errorOf(row: number, col: number): string | undefined;
1050
+ /** How a cell is addressed in the `states` and `errors` maps. */
1051
+ private cellKey;
1052
+ /**
1053
+ * What a cell shows: its own text when it gives one, otherwise whatever its column makes of
1054
+ * the value — an option's label, a mark for a yes, the value itself for everything else.
1055
+ */
1056
+ protected displayOf(cell: HubSpreadsheetCell, column: HubSpreadsheetColumn<TRow>): string;
1057
+ /**
1058
+ * What a hosted control should be for this cell, or null when there is nothing to host.
1059
+ *
1060
+ * Null covers three cases and they are all deliberate: no adapter registered, a column that is
1061
+ * not a list, and a column whose host supplied its own editor — which is read first and wins,
1062
+ * because a template written for one column is a more specific answer than a rule for all of
1063
+ * them.
1064
+ */
1065
+ protected hostedControl(row: number, col: number): HubSpreadsheetControlConfig | null;
1066
+ /**
1067
+ * Which answer a list shows while it is open.
1068
+ *
1069
+ * Read off the draft rather than off the cell, so a list opened by typing shows the answer that
1070
+ * keystroke reached. The draft holds the stored value until a keystroke moves it, so an
1071
+ * ordinary open still shows what the cell holds.
1072
+ */
1073
+ protected isChosen(option: HubSpreadsheetOption, column: HubSpreadsheetColumn<TRow>): boolean;
1074
+ /** Whether a boolean cell is on, which is what draws its checkbox. */
1075
+ protected isOn(cell: HubSpreadsheetCell): boolean;
1076
+ /**
1077
+ * What a cell should show to say it opens onto something, or null when it opens onto a field.
1078
+ *
1079
+ * Only drawn on the cell the reader is on. A caret down every row of a column would read as
1080
+ * content and make the sheet look like a form, and it is only ever the current cell that is
1081
+ * about to be opened.
1082
+ */
1083
+ protected affordanceOf(column: HubSpreadsheetColumn<TRow>, col: number): 'list' | 'date' | null;
1084
+ /** The template this column's editor should use, if the host supplied one. */
1085
+ protected editorFor(col: number): HubSpreadsheetEditorDirective<TRow> | undefined;
1086
+ /** The template this column draws its cells with, if the host supplied one. */
1087
+ protected cellTemplateFor(col: number): HubSpreadsheetCellDirective<TRow> | undefined;
1088
+ /** The context a cell template is rendered with. */
1089
+ protected cellContext(row: number, col: number): HubSpreadsheetCellContext<TRow>;
1090
+ /** The context a custom editor template is rendered with. */
1091
+ protected editorContext(row: number, col: number): HubSpreadsheetEditorContext<TRow>;
1092
+ /** Accepts a value a custom editor or a built-in list reported, and moves on as Enter would. */
1093
+ protected commitFromEditor(value: HubSpreadsheetValue): void;
1094
+ /**
1095
+ * Turns a boolean cell over without opening an editor.
1096
+ *
1097
+ * A checkbox that needs opening, ticking and confirming is three gestures for one bit. The
1098
+ * keyboard reaches it the same way, since a printable character on such a column toggles too.
1099
+ */
1100
+ protected toggleBoolean(row: number, col: number): void;
1101
+ protected canEdit(row: number, col: number): boolean;
1102
+ protected onKeydown(event: KeyboardEvent): void;
1103
+ protected onMouseDown(event: MouseEvent, row: number, col: number): void;
1104
+ /**
1105
+ * Opens the editor on release, not on press.
1106
+ *
1107
+ * Pressing would settle the question too early: a press that turns into a drag is a range
1108
+ * selection, and one that does not is a request to type. Waiting for the release is what lets
1109
+ * both gestures start the same way.
1110
+ */
1111
+ protected onMouseUp(event: MouseEvent, row: number, col: number): void;
1112
+ protected onMouseEnter(row: number, col: number): void;
1113
+ /** The width a column has been dragged to, or null while it sizes itself to its content. */
1114
+ protected widthOf(col: number): number | null;
1115
+ protected onResizeStart(event: MouseEvent, col: number): void;
1116
+ protected onHeaderMouseDown(event: MouseEvent, col: number): void;
1117
+ protected onHeaderMouseEnter(col: number): void;
1118
+ /** Runs for every pointer move while something is being dragged, and only then. */
1119
+ protected onPointerMove(event: MouseEvent): void;
1120
+ protected endDrag(): void;
1121
+ protected onContextMenu(event: MouseEvent, row: number, col: number): void;
1122
+ protected closeMenu(): void;
1123
+ /**
1124
+ * What the menu offers here, already filtered by what is allowed.
1125
+ *
1126
+ * Built rather than rendered-and-hidden so the menu is never opened empty, and so no entry
1127
+ * appears that would do nothing when clicked.
1128
+ */
1129
+ protected readonly menuEntries: _angular_core.Signal<{
1130
+ id: string;
1131
+ label: string;
1132
+ count: number;
1133
+ }[]>;
1134
+ protected runMenuEntry(id: string): void;
1135
+ /** Reports the block the selection would become, with what it swallows. */
1136
+ private requestMerge;
1137
+ /** Reports the blocks the selection touches, so the host can take them apart. */
1138
+ private requestUnmerge;
1139
+ private allRows;
1140
+ private allColumns;
1141
+ /**
1142
+ * Reads the viewport after it has scrolled, or been resized, or drawn its first rows.
1143
+ *
1144
+ * Signals rather than a stored geometry, so the window is a computed like everything else and
1145
+ * nothing has to remember to recompute it. The row height is measured from the first row in
1146
+ * the document, which is also the only row that is certainly there.
1147
+ */
1148
+ protected onViewportScroll(): void;
1149
+ /** Reports where a dragged column was dropped, if it moved at all. */
1150
+ private commitColumnMove;
1151
+ /** Whether a header is the one being dragged, or the place it would land. */
1152
+ protected isMovingColumn(col: number): boolean;
1153
+ protected isMoveTarget(col: number): boolean;
1154
+ /** Whether the fill handle belongs on this cell: the far corner of the selection. */
1155
+ protected hasFillHandle(row: number, col: number): boolean;
1156
+ /** Whether a cell is inside the area a released fill would write to. */
1157
+ protected isFillPreview(row: number, col: number): boolean;
1158
+ protected onFillHandleDown(event: MouseEvent): void;
1159
+ /** Grows the preview towards wherever the pointer went, on one axis only. */
1160
+ private trackFill;
1161
+ /** Works out what the finished drag writes, reports it, and leaves the selection covering it. */
1162
+ private commitFill;
1163
+ /**
1164
+ * Opens the cell from its own mark, on the first click.
1165
+ *
1166
+ * The mark exists to be pressed, so it does not wait for a second click the way the rest of the
1167
+ * cell does — which is what a spreadsheet does with the list of a cell that has one. The press
1168
+ * is swallowed rather than let through: the cell underneath would otherwise start a range drag,
1169
+ * and the release would land on an editor that was not there when the press began.
1170
+ */
1171
+ protected onAffordanceDown(event: MouseEvent, row: number, col: number): void;
1172
+ protected onDoubleClick(): void;
1173
+ protected onCopy(event: ClipboardEvent, cut: boolean): void;
1174
+ protected onPaste(event: ClipboardEvent): void;
1175
+ /** Asks the owner to add a row beside the cursor, if it allows it. */
1176
+ protected requestInsertRow(side: 'before' | 'after'): void;
1177
+ /** Asks the owner to remove the rows the selection covers, if it allows it. */
1178
+ protected requestDeleteRows(): void;
1179
+ /** Asks the owner to add a column, handing over an alias that has never been used here. */
1180
+ protected requestInsertColumn(side: 'before' | 'after'): void;
1181
+ /** Asks the owner to remove the columns the selection covers, naming the aliases at stake. */
1182
+ protected requestDeleteColumns(): void;
1183
+ protected onDraftInput(event: Event): void;
1184
+ protected onEditorKeydown(event: KeyboardEvent): void;
1185
+ /**
1186
+ * The list of suggestions takes the arrows, and takes Enter only once the reader is in it.
1187
+ *
1188
+ * Enter is the awkward one: it both accepts a suggestion and commits a cell, and which of the
1189
+ * two it means depends on whether the reader has moved into the list. Moving into it is
1190
+ * deliberate — an arrow key — so somebody who types a whole formula and presses Enter commits
1191
+ * it, which is what they meant.
1192
+ *
1193
+ * @returns Whether the key was the list's, and the editor should leave it alone.
1194
+ */
1195
+ private onSuggestionKeydown;
1196
+ /** Leaving the editor keeps what was typed, as clicking away does in a spreadsheet. */
1197
+ protected onEditorBlur(): void;
1198
+ protected trackRow: (index: number) => string;
1199
+ protected trackColumn: (_index: number, column: HubSpreadsheetColumn<TRow>) => string;
1200
+ /** Writes a pasted block into the sheet and reports what fitted and what did not. */
1201
+ private applyPaste;
1202
+ /**
1203
+ * What a pasted cell is worth in this column.
1204
+ *
1205
+ * The raw number from the HTML flavour wins where there is one, because it does not depend on
1206
+ * how the source happened to format it. Only when it is absent is the displayed text parsed,
1207
+ * and then in this reader's own convention.
1208
+ */
1209
+ private read;
1210
+ private startEdit;
1211
+ /** Puts the keyboard in the first thing a supplied editor offers it. */
1212
+ private focusSuppliedEditor;
1213
+ /**
1214
+ * Keeps the characters typed between opening a cell and the editor getting the focus.
1215
+ *
1216
+ * The field does not exist until the view has been drawn, so the focus can only move to it on
1217
+ * the render after the keystroke that opened the cell. Anybody typing quickly — `8`, `0`, `0`
1218
+ * inside one frame — has the first character open the cell and the rest arrive here, at the
1219
+ * grid, with nowhere to go. They used to be dropped: the cell opened on `8` and committed `8`.
1220
+ *
1221
+ * So they are added to the draft, which is what the field shows the moment it appears.
1222
+ */
1223
+ private catchUp;
1224
+ /**
1225
+ * Reads what was typed, reports it if it changed, then moves by `step`.
1226
+ *
1227
+ * @returns false when the text is not a value for this column, leaving the editor open and
1228
+ * marked rather than throwing away what the reader wrote.
1229
+ */
1230
+ private finishEdit;
1231
+ private cancelEdit;
1232
+ /**
1233
+ * The first answer of a list that starts with what was typed, skipping the ones a reader is
1234
+ * not allowed to choose. Matched on the label, because that is what they were reading.
1235
+ */
1236
+ private optionStartingWith;
1237
+ /** What the editor starts with, which is not always what was typed. */
1238
+ private openingText;
1239
+ /** A value as it goes into the editor: numbers in this reader's own convention. */
1240
+ private editableTextOf;
1241
+ private moveTo;
1242
+ private selectRange;
1243
+ private focusActive;
1244
+ /**
1245
+ * Brings the row the cursor is on inside the viewport, when the sheet is the thing that
1246
+ * scrolls and the row might not be drawn.
1247
+ *
1248
+ * Only the arithmetic the browser cannot do for us: `scrollIntoView` needs an element, and a
1249
+ * row that is not in the document has none. The offset is worked out from the row height,
1250
+ * which virtualising already depends on, and the scroller is nudged by the least that brings
1251
+ * the row in — jumping it to the middle would move the sheet under a reader who only pressed
1252
+ * the down arrow.
1253
+ */
1254
+ private scrollRowIntoView;
1255
+ /**
1256
+ * The same for the column the cursor is on, which may be out of the document sideways.
1257
+ *
1258
+ * Measured widths rather than a single size: columns are rarely alike, and adding up the ones
1259
+ * before the cursor is the only way to know where it starts. Nothing to do when a width is
1260
+ * missing, which is also when the sheet is drawing every column anyway.
1261
+ */
1262
+ private scrollColumnIntoView;
1263
+ /** Hands focus to the off-screen field so the browser will dispatch a paste at all. */
1264
+ private focusSink;
1265
+ private refAt;
1266
+ private announceSelection;
1267
+ private announceCleared;
1268
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubSpreadsheetComponent<any>, never>;
1269
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubSpreadsheetComponent<any>, "hub-spreadsheet", never, { "rows": { "alias": "rows"; "required": true; "isSignal": true; }; "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "rowKey": { "alias": "rowKey"; "required": true; "isSignal": true; }; "states": { "alias": "states"; "required": false; "isSignal": true; }; "errors": { "alias": "errors"; "required": false; "isSignal": true; }; "decimalMark": { "alias": "decimalMark"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "frozenColumns": { "alias": "frozenColumns"; "required": false; "isSignal": true; }; "frozenRows": { "alias": "frozenRows"; "required": false; "isSignal": true; }; "spans": { "alias": "spans"; "required": false; "isSignal": true; }; "structure": { "alias": "structure"; "required": false; "isSignal": true; }; "retiredColumnKeys": { "alias": "retiredColumnKeys"; "required": false; "isSignal": true; }; "fillHandle": { "alias": "fillHandle"; "required": false; "isSignal": true; }; "canUndo": { "alias": "canUndo"; "required": false; "isSignal": true; }; "canRedo": { "alias": "canRedo"; "required": false; "isSignal": true; }; "resizableColumns": { "alias": "resizableColumns"; "required": false; "isSignal": true; }; "reorderableColumns": { "alias": "reorderableColumns"; "required": false; "isSignal": true; }; "columnWidths": { "alias": "columnWidths"; "required": false; "isSignal": true; }; "minColumnWidth": { "alias": "minColumnWidth"; "required": false; "isSignal": true; }; "contextMenu": { "alias": "contextMenu"; "required": false; "isSignal": true; }; "mergeable": { "alias": "mergeable"; "required": false; "isSignal": true; }; "formulas": { "alias": "formulas"; "required": false; "isSignal": true; }; "virtual": { "alias": "virtual"; "required": false; "isSignal": true; }; "rowHeight": { "alias": "rowHeight"; "required": false; "isSignal": true; }; "editOn": { "alias": "editOn"; "required": false; "isSignal": true; }; }, { "columnWidths": "columnWidthsChange"; "commit": "commit"; "pasted": "pasted"; "cleared": "cleared"; "selectionChange": "selectionChange"; "insertRequested": "insertRequested"; "deleteRequested": "deleteRequested"; "filled": "filled"; "undoRequested": "undoRequested"; "redoRequested": "redoRequested"; "columnMoved": "columnMoved"; "mergeRequested": "mergeRequested"; "unmergeRequested": "unmergeRequested"; }, ["customEditors", "customCells"], never, true, never>;
1270
+ }
1271
+
1272
+ /** The option a stored value belongs to, or undefined when it belongs to none of them. */
1273
+ declare function optionFor<TRow>(value: HubSpreadsheetValue, column: HubSpreadsheetColumn<TRow>): HubSpreadsheetOption | undefined;
1274
+ /**
1275
+ * What the cell shows for a value.
1276
+ *
1277
+ * A `select` value whose option has been removed is shown as the stored value rather than as
1278
+ * nothing: blanking it would read as data loss, and the reader would have no way to tell what the
1279
+ * row used to say.
1280
+ */
1281
+ declare function displayValue<TRow>(value: HubSpreadsheetValue, column: HubSpreadsheetColumn<TRow>): string;
1282
+ /**
1283
+ * What a typed or pasted string is worth in a column.
1284
+ *
1285
+ * Three answers, as everywhere else here: the value, `null` for an empty cell, and `undefined`
1286
+ * for something this column cannot hold — which lets the caller refuse rather than store rubbish.
1287
+ */
1288
+ declare function parseForColumn<TRow>(text: string, column: HubSpreadsheetColumn<TRow>, decimalMark: ',' | '.'): HubSpreadsheetValue | undefined;
1289
+ /** Whether a stored value counts as yes. */
1290
+ declare function isTruthy(value: HubSpreadsheetValue): boolean;
1291
+
1292
+ /**
1293
+ * The shape of a formula, and of what comes back from running one.
1294
+ *
1295
+ * A formula refers to columns by the alias they were declared with — `[price] * [units]` — and
1296
+ * never by a coordinate. That is the whole reason the aliases exist: a column can be reordered,
1297
+ * renamed in its header, or moved to another position and every formula still means what it said.
1298
+ * Excel made the other choice, and renaming a column there rewrites every formula in the workbook.
1299
+ */
1300
+ /** A value a formula can work with. */
1301
+ type HubFormulaValue = number | string | boolean | null;
1302
+ /** What went wrong, in the terms a reader can be shown. */
1303
+ type HubFormulaErrorCode =
1304
+ /** The text is not a formula: a bracket left open, an operator with nothing after it. */
1305
+ 'syntax'
1306
+ /** A name nothing answers to: a column alias that does not exist, a function that is not one. */
1307
+ | 'name'
1308
+ /** Division by nought. */
1309
+ | 'divide-by-zero'
1310
+ /** A formula that needs its own result to work out its own result. */
1311
+ | 'cycle'
1312
+ /** Arithmetic on something that is not a number, or a function given the wrong kind of thing. */
1313
+ | 'type';
1314
+ /** A formula that could not be run, and why. */
1315
+ interface HubFormulaFailure {
1316
+ readonly ok: false;
1317
+ readonly code: HubFormulaErrorCode;
1318
+ /** What it was that failed — an alias, a function name, the character it stopped at. */
1319
+ readonly at?: string;
1320
+ }
1321
+ /** A formula that ran. */
1322
+ interface HubFormulaSuccess {
1323
+ readonly ok: true;
1324
+ readonly value: HubFormulaValue;
1325
+ }
1326
+ type HubFormulaOutcome = HubFormulaSuccess | HubFormulaFailure;
1327
+ /** What a formula is allowed to ask about the sheet around it. */
1328
+ interface HubFormulaContext {
1329
+ /**
1330
+ * This row's value in a column.
1331
+ *
1332
+ * `undefined` for an alias no column answers to, which is how `#NAME?` is told apart from an
1333
+ * empty cell — a distinction Excel makes and every ad-hoc evaluator forgets.
1334
+ */
1335
+ cell(alias: string): HubFormulaValue | undefined;
1336
+ /** Every row's value in a column, in the order the rows are in. */
1337
+ column(alias: string): readonly HubFormulaValue[] | undefined;
1338
+ /**
1339
+ * The cell at a place, for a formula written with coordinates.
1340
+ *
1341
+ * `undefined` for a place outside the sheet, which is `#REF!` and not an empty cell: a formula
1342
+ * pointing off the edge is a mistake, and one pointing at a blank cell is not.
1343
+ */
1344
+ at?(row: number, col: number): HubFormulaValue | undefined;
1345
+ /** Every cell of a rectangle, row by row. */
1346
+ area?(top: number, left: number, bottom: number, right: number): readonly HubFormulaValue[] | undefined;
1347
+ }
1348
+ /** A parsed formula, ready to be run as many times as the sheet is redrawn. */
1349
+ interface HubFormula {
1350
+ /** The tree to walk. */
1351
+ readonly node: HubFormulaNode;
1352
+ /** Aliases it reads from its own row. */
1353
+ readonly cells: readonly string[];
1354
+ /** Aliases it reads whole columns of. */
1355
+ readonly columns: readonly string[];
1356
+ /** Whether it points at anywhere by coordinate, which is what a change of shape has to move. */
1357
+ readonly coordinates: boolean;
1358
+ }
1359
+ /** One step of a parsed formula. */
1360
+ type HubFormulaNode = {
1361
+ readonly kind: 'number';
1362
+ readonly value: number;
1363
+ } | {
1364
+ readonly kind: 'text';
1365
+ readonly value: string;
1366
+ } | {
1367
+ readonly kind: 'boolean';
1368
+ readonly value: boolean;
1369
+ } | {
1370
+ readonly kind: 'cell';
1371
+ readonly alias: string;
1372
+ } | {
1373
+ readonly kind: 'column';
1374
+ readonly alias: string;
1375
+ } | {
1376
+ readonly kind: 'at';
1377
+ readonly row: number;
1378
+ readonly col: number;
1379
+ } | {
1380
+ readonly kind: 'area';
1381
+ readonly top: number;
1382
+ readonly left: number;
1383
+ readonly bottom: number;
1384
+ readonly right: number;
1385
+ } | {
1386
+ readonly kind: 'unary';
1387
+ readonly operator: '-' | '+';
1388
+ readonly operand: HubFormulaNode;
1389
+ } | {
1390
+ readonly kind: 'binary';
1391
+ readonly operator: '+' | '-' | '*' | '/' | '^' | '&' | '=' | '<>' | '<' | '<=' | '>' | '>=';
1392
+ readonly left: HubFormulaNode;
1393
+ readonly right: HubFormulaNode;
1394
+ } | {
1395
+ readonly kind: 'call';
1396
+ readonly name: string;
1397
+ readonly args: readonly HubFormulaNode[];
1398
+ };
1399
+
1400
+ /**
1401
+ * Turning the text of a formula into something that can be run.
1402
+ *
1403
+ * Hand-written rather than generated, and deliberately: a grammar this small is a page of code,
1404
+ * and a parser generator would be a dependency, a build step and a licence to check for the sake
1405
+ * of it. What it costs is that the precedence is written out by hand — which is also what makes it
1406
+ * readable.
1407
+ *
1408
+ * Precedence, loosest first, as in a spreadsheet:
1409
+ *
1410
+ * comparison = <> < <= > >=
1411
+ * concatenation &
1412
+ * additive + -
1413
+ * multiplicative * /
1414
+ * power ^
1415
+ * unary - +
1416
+ * primary number, text, TRUE/FALSE, [alias], [alias:], NAME(…), ( … )
1417
+ */
1418
+ /** Whether a stored value is a formula rather than a value. */
1419
+ declare function isFormula(value: unknown): value is string;
1420
+ /**
1421
+ * Reads the text of a formula, with or without its leading `=`.
1422
+ *
1423
+ * @returns The parsed formula, or the first thing that did not make sense.
1424
+ */
1425
+ declare function parseFormula(source: string): HubFormula | HubFormulaFailure;
1426
+
1427
+ /**
1428
+ * Runs a formula.
1429
+ *
1430
+ * @param formula - What {@link parseFormula} gave back.
1431
+ * @param context - How to reach the sheet: this row's cells, and whole columns.
1432
+ * @returns The value, or what went wrong.
1433
+ */
1434
+ declare function evaluateFormula(formula: HubFormula, context: HubFormulaContext): HubFormulaOutcome;
1435
+
1436
+ /**
1437
+ * Working out what every cell of a sheet shows once formulas are in it.
1438
+ *
1439
+ * A formula reads other cells, and those may be formulas too, so the answer to one can depend on
1440
+ * the answer to another. Two things follow, and both are here rather than in the component:
1441
+ *
1442
+ * - **It has to be worked out in order.** A cell is not ready until everything it reads is ready.
1443
+ * - **It has to refuse to go round in circles.** A formula that needs its own result — directly,
1444
+ * or through three others — has no answer, and a sheet that tries to find one stops responding.
1445
+ *
1446
+ * One rule follows from the second and is worth saying out loud: a whole-column reference leaves
1447
+ * out the cell that is asking for it. `=SUM([total:])` written in the total row is what everybody
1448
+ * writes and what everybody means, and taken literally it is a sum that needs its own answer.
1449
+ * Reading a *different* column is untouched — a total row that sums the prices sums every price.
1450
+ */
1451
+ /** What a cell came to, and where it came from. */
1452
+ interface HubFormulaCellResult {
1453
+ /** What the cell shows. */
1454
+ readonly value: HubSpreadsheetValue;
1455
+ /** Set when the cell holds a formula that could not be run. */
1456
+ readonly failure?: HubFormulaFailure;
1457
+ }
1458
+ /** Every cell of the sheet, keyed by row index and column alias. */
1459
+ type HubFormulaSheet = ReadonlyArray<Readonly<Record<string, HubFormulaCellResult>>>;
1460
+ /** What a sheet of formulas is worked out from. */
1461
+ interface HubFormulaSheetInput<TRow> {
1462
+ readonly rows: readonly TRow[];
1463
+ readonly columns: readonly HubSpreadsheetColumn<TRow>[];
1464
+ /**
1465
+ * What a cell holds before any formula is run: a value, or the text of a formula.
1466
+ *
1467
+ * By row index rather than by row, because the caller usually has the grid already worked out
1468
+ * and finding a row's place in a list to read it back is the kind of thing that turns a sheet
1469
+ * of ten thousand rows into a sheet that takes a second to draw.
1470
+ */
1471
+ readonly raw: (rowIndex: number, column: HubSpreadsheetColumn<TRow>) => HubSpreadsheetValue;
1472
+ }
1473
+ /**
1474
+ * Runs every formula in a sheet, in whatever order the dependencies demand.
1475
+ *
1476
+ * Worked out lazily and remembered: a cell is run the first time something needs it, and a column
1477
+ * read by twenty rows is not run twenty times. The walk carries the cells it is in the middle of,
1478
+ * which is how a circle is caught the moment it closes rather than by counting to a thousand.
1479
+ *
1480
+ * @returns Every cell of the sheet, ready to be shown.
1481
+ */
1482
+ declare function evaluateSheet<TRow>(input: HubFormulaSheetInput<TRow>): HubFormulaSheet;
1483
+
1484
+ /**
1485
+ * Coordinates: the other way to point at a cell, and what has to happen to them afterwards.
1486
+ *
1487
+ * An alias says which column it means and goes on meaning it whatever happens to the sheet. A
1488
+ * coordinate says *where* the column was when the formula was written, which is only the same
1489
+ * thing until somebody moves it. That is not a reason to refuse coordinates — a spreadsheet
1490
+ * without `B2` is not a spreadsheet — it is a reason to move them with the sheet, which is what
1491
+ * every spreadsheet does and what these functions are for.
1492
+ */
1493
+ /** The letters a column is known by: A, B … Z, AA, AB … */
1494
+ declare function columnLabel(index: number): string;
1495
+ /** Which column a run of letters means, or -1 when it means none. */
1496
+ declare function columnIndexOf(label: string): number;
1497
+ /** What a coordinate points at, counting from nought. */
1498
+ interface HubFormulaCoordinate {
1499
+ readonly row: number;
1500
+ readonly col: number;
1501
+ }
1502
+ /** Reads `B3` as a place, or nothing when it is not one. */
1503
+ declare function parseCoordinate(text: string): HubFormulaCoordinate | null;
1504
+ /** Writes a place back as `B3`, which is what a formula shows. */
1505
+ declare function formatCoordinate(coordinate: HubFormulaCoordinate): string;
1506
+ /** What a reference has become after the sheet changed shape. */
1507
+ type Moved = HubFormulaCoordinate | 'gone';
1508
+ /** How the sheet changed shape. */
1509
+ type HubFormulaShapeChange = {
1510
+ readonly kind: 'move-column';
1511
+ readonly from: number;
1512
+ readonly to: number;
1513
+ }
1514
+ /** A positive count inserts, a negative one deletes. */
1515
+ | {
1516
+ readonly kind: 'columns';
1517
+ readonly at: number;
1518
+ readonly count: number;
1519
+ } | {
1520
+ readonly kind: 'rows';
1521
+ readonly at: number;
1522
+ readonly count: number;
1523
+ };
1524
+ /** Where a coordinate ends up after a change, or that it no longer points at anything. */
1525
+ declare function moveCoordinate(coordinate: HubFormulaCoordinate, change: HubFormulaShapeChange): Moved;
1526
+ /**
1527
+ * Rewrites the coordinates in a formula so they point at the same data as before.
1528
+ *
1529
+ * Text, not a parsed tree, and deliberately: what a reader typed is what is stored, so what comes
1530
+ * back has to be something they would recognise as their own formula with a letter changed. A
1531
+ * reference to something that has been deleted becomes `#REF!`, which is what a spreadsheet does
1532
+ * and is honest — the alternative is a formula that quietly reads its neighbour.
1533
+ *
1534
+ * @param formula - The text of the formula, `=` and all.
1535
+ * @param change - What happened to the sheet.
1536
+ * @returns The formula with its coordinates moved.
1537
+ */
1538
+ declare function rewriteCoordinates(formula: string, change: HubFormulaShapeChange): string;
1539
+ /**
1540
+ * The rows again, with every formula in them moved to match the sheet's new shape.
1541
+ *
1542
+ * The sheet reports what the reader asked for and writes nothing, as it does with everything else,
1543
+ * so this is the line the host writes in its own handler:
1544
+ *
1545
+ * ```ts
1546
+ * protected onColumnMoved(move: { from: number; to: number; keys: string[] }): void {
1547
+ * this.order.set(move.keys);
1548
+ * this.rows.update((rows) => rewriteRowFormulas(rows, { kind: 'move-column', from: move.from, to: move.to }));
1549
+ * }
1550
+ * ```
1551
+ *
1552
+ * Every own property that holds a formula is rewritten, which covers the usual case of formulas
1553
+ * living in the row's own fields. A host that keeps them somewhere else — a map beside the data, a
1554
+ * column that declares its own — walks that itself with {@link rewriteCoordinates}.
1555
+ *
1556
+ * @param rows - The rows as they are.
1557
+ * @param change - What happened to the sheet.
1558
+ * @returns New rows; the ones with nothing to rewrite are the very same objects, so a sheet that
1559
+ * tracks by identity redraws only what moved.
1560
+ */
1561
+ declare function rewriteRowFormulas<TRow extends object>(rows: readonly TRow[], change: HubFormulaShapeChange): TRow[];
1562
+
1563
+ /** A file read back: its headers, if it had any, and its rows as they were written. */
1564
+ interface HubSheetTable {
1565
+ readonly headers: readonly string[] | null;
1566
+ readonly rows: readonly (readonly string[])[];
1567
+ }
1568
+ /**
1569
+ * Turns a file's rows into records keyed by column alias, matching its headers to the sheet.
1570
+ *
1571
+ * Matched on the header first and the alias second, both ignoring case and surrounding space: a
1572
+ * file that came from this sheet carries the headers, and one written by hand often carries the
1573
+ * aliases. A column the file has nothing for is left out rather than filled with an empty string,
1574
+ * so a partial file updates what it names and leaves the rest alone.
1575
+ *
1576
+ * Shared by every importer, because the matching is the part that has to behave the same whatever
1577
+ * the file was: somebody who fixes a CSV by renaming a heading expects the same file to work when
1578
+ * they save it as a workbook instead.
1579
+ *
1580
+ * @returns One record per row of the file, keyed by column alias.
1581
+ */
1582
+ declare function tableToRecords<TRow>(table: HubSheetTable, columns: readonly HubSpreadsheetColumn<TRow>[]): Array<Record<string, string>>;
1583
+
1584
+ /**
1585
+ * A sheet as a CSV file, and back.
1586
+ *
1587
+ * Two things decide whether a CSV is any use, and both are here rather than left to the caller.
1588
+ *
1589
+ * The **separator** is not always a comma: where the comma is the decimal mark — most of Europe —
1590
+ * a spreadsheet writes and expects semicolons, and a file written with the wrong one opens as a
1591
+ * single column of text. So it is chosen from the decimal mark when nobody says otherwise, and
1592
+ * sniffed when reading rather than assumed.
1593
+ *
1594
+ * The **values** are what the reader sees, not what the row holds: a select column exports the
1595
+ * label somebody chose, and a formula exports the number it came to. A CSV is what gets sent to
1596
+ * somebody who does not have the application, and a column of `pending` codes is of no use to
1597
+ * them. `values: 'stored'` is there for the round trip that is meant to come back.
1598
+ */
1599
+ interface HubCsvOptions {
1600
+ /** What separates the cells. Chosen from the decimal mark when not given. */
1601
+ readonly delimiter?: ',' | ';' | '\t';
1602
+ /** Whether to write the column headers as the first line. On unless turned off. */
1603
+ readonly headers?: boolean;
1604
+ /** What to write: what the cell shows, or what the row holds. */
1605
+ readonly values?: 'shown' | 'stored';
1606
+ /** How this reader writes decimals, which decides the separator and the figures. */
1607
+ readonly decimalMark?: ',' | '.';
1608
+ /** What ends a line. CRLF, because that is what a spreadsheet on Windows expects. */
1609
+ readonly newline?: '\n' | '\r\n';
1610
+ /**
1611
+ * Whether to work the formulas out before writing. On unless turned off, and never done for
1612
+ * `values: 'stored'`, where the formula itself is the thing worth keeping.
1613
+ */
1614
+ readonly formulas?: boolean;
1615
+ }
1616
+ /**
1617
+ * Writes a sheet as CSV.
1618
+ *
1619
+ * @param rows - The rows, as the sheet has them.
1620
+ * @param columns - The columns, which decide the order and the headers.
1621
+ * @param options - How to write it.
1622
+ * @returns The file's contents.
1623
+ */
1624
+ declare function sheetToCsv<TRow>(rows: readonly TRow[], columns: readonly HubSpreadsheetColumn<TRow>[], options?: HubCsvOptions): string;
1625
+ /**
1626
+ * Which separator a file is written with.
1627
+ *
1628
+ * Counted on the first line rather than guessed from the file's name: an exporter's idea of CSV
1629
+ * and the reader's locale often disagree, and the line that holds the headers is the one that
1630
+ * shows it most clearly.
1631
+ */
1632
+ declare function sniffDelimiter(text: string): ',' | ';' | '\t';
1633
+ /**
1634
+ * Reads a CSV file.
1635
+ *
1636
+ * @param text - The file's contents.
1637
+ * @param options - How to read it; the separator is sniffed when not given.
1638
+ */
1639
+ declare function csvToTable(text: string, options?: HubCsvOptions): HubSheetTable;
1640
+ /**
1641
+ * Reads a CSV file into rows keyed by column alias, matching its headers to the sheet's columns.
1642
+ *
1643
+ * @returns One record per row of the file, keyed by column alias.
1644
+ */
1645
+ declare function csvToRecords<TRow>(text: string, columns: readonly HubSpreadsheetColumn<TRow>[], options?: HubCsvOptions): Array<Record<string, string>>;
1646
+ /**
1647
+ * Hands the reader a CSV file to save.
1648
+ *
1649
+ * @param filename - What to call it.
1650
+ * @param text - What goes in it.
1651
+ * @param type - Its media type.
1652
+ */
1653
+ declare function downloadText(filename: string, text: string, type?: string): void;
1654
+
1655
+ /**
1656
+ * A sheet as a real `.xlsx`, written by hand.
1657
+ *
1658
+ * CSV loses everything except the characters: a figure becomes text the moment a locale disagrees
1659
+ * about the decimal mark, a date is whatever the reader's machine decides it is, and nothing knows
1660
+ * a column of prices from a column of postcodes. That is why the export people actually ask for is
1661
+ * this one.
1662
+ *
1663
+ * What comes out is a small, honest workbook: one sheet, a header row in bold, numbers as numbers
1664
+ * and dates as dates. No charts, no merges, no column widths — anybody who needs those has a
1665
+ * workbook of their own to paste into, and every one of them costs another part of a format that
1666
+ * is already the largest thing in this library.
1667
+ *
1668
+ * The one thing worth knowing about the file: its parts are **stored** rather than compressed, so
1669
+ * it is a few times larger than what Excel writes. That is the whole price of not depending on a
1670
+ * compression library, and no reader can tell the difference.
1671
+ */
1672
+ interface HubXlsxOptions {
1673
+ /** The sheet's name in the workbook. `Sheet1` when not given. */
1674
+ readonly sheetName?: string;
1675
+ /** Whether to write the column headers as the first row. On unless turned off. */
1676
+ readonly headers?: boolean;
1677
+ /** What to write: what the cell shows, or what the row holds. */
1678
+ readonly values?: 'shown' | 'stored';
1679
+ /** Whether to work the formulas out first. On unless turned off. */
1680
+ readonly formulas?: boolean;
1681
+ }
1682
+ /** The media type a browser and Excel both recognise. */
1683
+ declare const XLSX_MEDIA_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
1684
+ /**
1685
+ * Writes a sheet as an xlsx workbook.
1686
+ *
1687
+ * @param rows - The rows, as the sheet has them.
1688
+ * @param columns - The columns, which decide the order and the headers.
1689
+ * @param options - How to write it.
1690
+ * @returns The bytes of the file.
1691
+ */
1692
+ declare function sheetToXlsx<TRow>(rows: readonly TRow[], columns: readonly HubSpreadsheetColumn<TRow>[], options?: HubXlsxOptions): Uint8Array;
1693
+ /**
1694
+ * Hands the reader an xlsx file to save.
1695
+ *
1696
+ * @param filename - What to call it; `.xlsx` is added when missing.
1697
+ * @param bytes - The workbook, from `sheetToXlsx`.
1698
+ */
1699
+ declare function downloadXlsx(filename: string, bytes: Uint8Array): void;
1700
+
1701
+ /**
1702
+ * Reading an xlsx workbook.
1703
+ *
1704
+ * Only what a sheet of data needs: the first worksheet's cells, as text, with the headers if it has
1705
+ * them. Not the charts, not the pictures, not the pivot tables — a file with those in it still
1706
+ * reads, they are simply parts nobody opens here.
1707
+ *
1708
+ * Two things a workbook does that a CSV does not, and both have to be undone or the import is
1709
+ * wrong. A **shared string** is written once and pointed at from every cell that holds it, so a
1710
+ * column of the same word arrives as a column of numbers unless the table is read. And a **date**
1711
+ * is a count of days wearing a number format, so it arrives as `45306` unless the styles are read
1712
+ * to know which columns were dates.
1713
+ *
1714
+ * The XML is read with patterns rather than a parser. It is the part of this file to be suspicious
1715
+ * of, and it is a deliberate choice: these parts are machine-written, their shape is fixed by the
1716
+ * format, and `DOMParser` does not exist where the tests run. What it means in practice is that
1717
+ * anything beyond these few elements is not understood — and a workbook is not a document to be
1718
+ * edited here, only a file to be read once.
1719
+ */
1720
+ /** How a workbook is read. */
1721
+ interface HubXlsxReadOptions {
1722
+ /** Whether the first row holds the headers. On unless turned off. */
1723
+ readonly headers?: boolean;
1724
+ /** Which sheet to read, by name. The first one when not given. */
1725
+ readonly sheetName?: string;
1726
+ }
1727
+ /**
1728
+ * Reads a workbook's first sheet as a table of text.
1729
+ *
1730
+ * @param bytes - The whole file, as it was read or dropped.
1731
+ * @param options - How to read it.
1732
+ * @returns The headers, if it has them, and every row.
1733
+ * @throws When the file is not a workbook at all.
1734
+ */
1735
+ declare function xlsxToTable(bytes: Uint8Array, options?: HubXlsxReadOptions): Promise<HubSheetTable>;
1736
+ /**
1737
+ * Reads a workbook into rows keyed by column alias, matching its headers to the sheet's columns.
1738
+ *
1739
+ * @returns One record per row of the file, keyed by column alias.
1740
+ */
1741
+ declare function xlsxToRecords<TRow>(bytes: Uint8Array, columns: readonly HubSpreadsheetColumn<TRow>[], options?: HubXlsxReadOptions): Promise<Array<Record<string, string>>>;
1742
+
1743
+ /** How a sheet's values are read for a file. */
1744
+ interface HubSheetValuesOptions {
1745
+ /**
1746
+ * Whether to work the formulas out first.
1747
+ *
1748
+ * On for a file somebody else is going to read: a column of `=SUM([total:])` is of no use to
1749
+ * them. Off for a round trip that is meant to come back with its formulas intact.
1750
+ */
1751
+ readonly formulas?: boolean;
1752
+ }
1753
+ /**
1754
+ * What every cell of a sheet holds, with the formulas run if asked.
1755
+ *
1756
+ * Shared by the exporters because both need the same answer and neither should be the one deciding
1757
+ * it: what a cell shows is the sheet's business, and a second copy of that decision inside the CSV
1758
+ * writer would drift from the component the first time a formula grows a new case.
1759
+ *
1760
+ * A formula that cannot be worked out comes out as its spreadsheet spelling — `#DIV/0!` and the
1761
+ * rest — which is what Excel writes in the same situation and what the reader of the file expects
1762
+ * to see.
1763
+ */
1764
+ declare function sheetValues<TRow>(rows: readonly TRow[], columns: readonly HubSpreadsheetColumn<TRow>[], options?: HubSheetValuesOptions): HubSpreadsheetValue[][];
1765
+
1766
+ /**
1767
+ * What a cell shows for a formula that could not be worked out.
1768
+ *
1769
+ * The spreadsheet spellings rather than sentences: they fit in a cell, they are the same in every
1770
+ * language, and anybody who has used a spreadsheet already knows what `#DIV/0!` means. They also
1771
+ * travel — a file exported with `#DIV/0!` in a cell says the same thing to whoever opens it in
1772
+ * Excel as it did here.
1773
+ */
1774
+ declare const FORMULA_ERROR_TEXT: Record<HubFormulaFailure['code'], string>;
1775
+ /** Every spelling, for telling an error apart from something a reader typed. */
1776
+ declare const FORMULA_ERRORS: string[];
1777
+ /** What to show for a failure. */
1778
+ declare function formulaErrorText(failure: HubFormulaFailure): string;
1779
+
1780
+ /**
1781
+ * Registers the control adapter a sheet opens its list cells with.
1782
+ *
1783
+ * ```ts
1784
+ * import { provideHubSpreadsheetControls } from 'ng-hub-ui-spreadsheet';
1785
+ * import { hubFormControlAdapter } from 'ng-hub-ui-forms';
1786
+ *
1787
+ * providers: [provideHubSpreadsheetControls(hubFormControlAdapter)];
1788
+ * ```
1789
+ *
1790
+ * Every list column then opens with that control, with no template written per sheet. A single
1791
+ * column that needs something else still overrides it with `hubSpreadsheetEditor`, which wins.
1792
+ *
1793
+ * @param adapter - The implementation to use, e.g. `hubFormControlAdapter` from `ng-hub-ui-forms`.
1794
+ * @returns Providers to add to the application, or to one component.
1795
+ */
1796
+ declare function provideHubSpreadsheetControls(adapter: HubSpreadsheetControlsAdapter): EnvironmentProviders;
1797
+
1798
+ /**
1799
+ * The optional adapter a sheet renders its list cells through.
1800
+ *
1801
+ * Injected with `{ optional: true }`: absent means "draw the native list", which is what the
1802
+ * library does on its own. Register it with {@link provideHubSpreadsheetControls}.
1803
+ */
1804
+ declare const HUB_SPREADSHEET_CONTROLS: InjectionToken<HubSpreadsheetControlsAdapter>;
1805
+
1806
+ /**
1807
+ * Renders a cell's editor through the optional {@link HUB_SPREADSHEET_CONTROLS} adapter.
1808
+ *
1809
+ * Placed on an `<ng-container>` in the branch that only runs when an adapter is registered; the
1810
+ * native list stays in the branch beside it as the answer that needs nothing installed. The
1811
+ * control is created as a sibling of the anchor and torn down with it.
1812
+ *
1813
+ * The config is read once, when the editor opens. A cell editor lives for one decision — it opens,
1814
+ * the reader chooses, it closes — so there is nothing to keep in sync afterwards, and the value
1815
+ * arriving from elsewhere mid-edit is the case the sheet already refuses.
1816
+ */
1817
+ declare class HubSpreadsheetControlDirective implements OnDestroy {
1818
+ /** What to build. */
1819
+ readonly config: _angular_core.InputSignal<HubSpreadsheetControlConfig>;
1820
+ private readonly vcr;
1821
+ private readonly adapter;
1822
+ private handle;
1823
+ constructor();
1824
+ ngOnDestroy(): void;
1825
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubSpreadsheetControlDirective, never>;
1826
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HubSpreadsheetControlDirective, "[hubSpreadsheetControl]", never, { "config": { "alias": "hubSpreadsheetControl"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
1827
+ }
1828
+
1829
+ /** English dictionary for the spreadsheet's own labels. */
1830
+ declare const locale$7: {
1831
+ lang: string;
1832
+ data: {
1833
+ INSERT_ROW_ABOVE: string;
1834
+ INSERT_ROW_BELOW: string;
1835
+ DELETE_ROWS: string;
1836
+ INSERT_COLUMN_BEFORE: string;
1837
+ INSERT_COLUMN_AFTER: string;
1838
+ DELETE_COLUMNS: string;
1839
+ MERGE_CELLS: string;
1840
+ UNMERGE_CELLS: string;
1841
+ ROW_ACTIONS: string;
1842
+ };
1843
+ };
1844
+
1845
+ /** Spanish dictionary for the spreadsheet's own labels. */
1846
+ declare const locale$6: {
1847
+ lang: string;
1848
+ data: {
1849
+ INSERT_ROW_ABOVE: string;
1850
+ INSERT_ROW_BELOW: string;
1851
+ DELETE_ROWS: string;
1852
+ INSERT_COLUMN_BEFORE: string;
1853
+ INSERT_COLUMN_AFTER: string;
1854
+ DELETE_COLUMNS: string;
1855
+ MERGE_CELLS: string;
1856
+ UNMERGE_CELLS: string;
1857
+ ROW_ACTIONS: string;
1858
+ };
1859
+ };
1860
+
1861
+ /** French dictionary for the spreadsheet's own labels. */
1862
+ declare const locale$5: {
1863
+ lang: string;
1864
+ data: {
1865
+ INSERT_ROW_ABOVE: string;
1866
+ INSERT_ROW_BELOW: string;
1867
+ DELETE_ROWS: string;
1868
+ INSERT_COLUMN_BEFORE: string;
1869
+ INSERT_COLUMN_AFTER: string;
1870
+ DELETE_COLUMNS: string;
1871
+ MERGE_CELLS: string;
1872
+ UNMERGE_CELLS: string;
1873
+ ROW_ACTIONS: string;
1874
+ };
1875
+ };
1876
+
1877
+ /** German dictionary for the spreadsheet's own labels. */
1878
+ declare const locale$4: {
1879
+ lang: string;
1880
+ data: {
1881
+ INSERT_ROW_ABOVE: string;
1882
+ INSERT_ROW_BELOW: string;
1883
+ DELETE_ROWS: string;
1884
+ INSERT_COLUMN_BEFORE: string;
1885
+ INSERT_COLUMN_AFTER: string;
1886
+ DELETE_COLUMNS: string;
1887
+ MERGE_CELLS: string;
1888
+ UNMERGE_CELLS: string;
1889
+ ROW_ACTIONS: string;
1890
+ };
1891
+ };
1892
+
1893
+ /** Russian dictionary for the spreadsheet's own labels. */
1894
+ declare const locale$3: {
1895
+ lang: string;
1896
+ data: {
1897
+ INSERT_ROW_ABOVE: string;
1898
+ INSERT_ROW_BELOW: string;
1899
+ DELETE_ROWS: string;
1900
+ INSERT_COLUMN_BEFORE: string;
1901
+ INSERT_COLUMN_AFTER: string;
1902
+ DELETE_COLUMNS: string;
1903
+ MERGE_CELLS: string;
1904
+ UNMERGE_CELLS: string;
1905
+ ROW_ACTIONS: string;
1906
+ };
1907
+ };
1908
+
1909
+ /** Japanese dictionary for the spreadsheet's own labels. */
1910
+ declare const locale$2: {
1911
+ lang: string;
1912
+ data: {
1913
+ INSERT_ROW_ABOVE: string;
1914
+ INSERT_ROW_BELOW: string;
1915
+ DELETE_ROWS: string;
1916
+ INSERT_COLUMN_BEFORE: string;
1917
+ INSERT_COLUMN_AFTER: string;
1918
+ DELETE_COLUMNS: string;
1919
+ MERGE_CELLS: string;
1920
+ UNMERGE_CELLS: string;
1921
+ ROW_ACTIONS: string;
1922
+ };
1923
+ };
1924
+
1925
+ /** Chinese dictionary for the spreadsheet's own labels. */
1926
+ declare const locale$1: {
1927
+ lang: string;
1928
+ data: {
1929
+ INSERT_ROW_ABOVE: string;
1930
+ INSERT_ROW_BELOW: string;
1931
+ DELETE_ROWS: string;
1932
+ INSERT_COLUMN_BEFORE: string;
1933
+ INSERT_COLUMN_AFTER: string;
1934
+ DELETE_COLUMNS: string;
1935
+ MERGE_CELLS: string;
1936
+ UNMERGE_CELLS: string;
1937
+ ROW_ACTIONS: string;
1938
+ };
1939
+ };
1940
+
1941
+ /** Arabic dictionary for the spreadsheet's own labels. */
1942
+ declare const locale: {
1943
+ lang: string;
1944
+ data: {
1945
+ INSERT_ROW_ABOVE: string;
1946
+ INSERT_ROW_BELOW: string;
1947
+ DELETE_ROWS: string;
1948
+ INSERT_COLUMN_BEFORE: string;
1949
+ INSERT_COLUMN_AFTER: string;
1950
+ DELETE_COLUMNS: string;
1951
+ MERGE_CELLS: string;
1952
+ UNMERGE_CELLS: string;
1953
+ ROW_ACTIONS: string;
1954
+ };
1955
+ };
1956
+
1957
+ export { FORMULA_ERRORS, FORMULA_ERROR_TEXT, HUB_FORMULA_FUNCTIONS, HUB_SPREADSHEET_CONTROLS, HubSpreadsheetCellDirective, HubSpreadsheetComponent, HubSpreadsheetControlDirective, HubSpreadsheetEditorDirective, XLSX_MEDIA_TYPE, applySpanMerge, applySpanUnmerge, applySuggestion, locale as arSpreadsheetLocale, columnIndexOf, columnLabel, csvToRecords, csvToTable, danglingColumnKeys, locale$4 as deSpreadsheetLocale, displayValue, downloadText, downloadXlsx, duplicateColumnKeys, locale$7 as enSpreadsheetLocale, locale$6 as esSpreadsheetLocale, evaluateFormula, evaluateSheet, formatCoordinate, formulaErrorText, formulaFragment, locale$5 as frSpreadsheetLocale, isFormula, isStructureAllowed, isTruthy, locale$2 as jaSpreadsheetLocale, mergeRequestFor, moveCoordinate, nextColumnKey, optionFor, parseClipboardTable, parseCoordinate, parseDecimal, parseForColumn, parseFormula, provideHubSpreadsheetControls, resolveInsertIndex, rewriteCoordinates, rewriteRowFormulas, locale$3 as ruSpreadsheetLocale, serialiseClipboardTable, sheetToCsv, sheetToXlsx, sheetValues, sniffDelimiter, spansWithin, spreadsheetRecord, spreadsheetRecords, suggestFormula, tableToRecords, xlsxToRecords, xlsxToTable, locale$1 as zhSpreadsheetLocale };
1958
+ export type { HubClipboardCell, HubClipboardPayload, HubClipboardWrite, HubCsvOptions, HubFormula, HubFormulaCellResult, HubFormulaContext, HubFormulaCoordinate, HubFormulaErrorCode, HubFormulaFailure, HubFormulaFragment, HubFormulaNode, HubFormulaOutcome, HubFormulaShapeChange, HubFormulaSheet, HubFormulaSheetInput, HubFormulaSuccess, HubFormulaSuggestion, HubFormulaValue, HubFormulaVocabulary, HubSheetTable, HubSheetValuesOptions, HubSpreadsheetAxis, HubSpreadsheetCell, HubSpreadsheetCellContext, HubSpreadsheetCellRef, HubSpreadsheetCellState, HubSpreadsheetColumn, HubSpreadsheetCommit, HubSpreadsheetControlConfig, HubSpreadsheetControlHandle, HubSpreadsheetControlLabelType, HubSpreadsheetControlOption, HubSpreadsheetControlsAdapter, HubSpreadsheetDeleteRequest, HubSpreadsheetEditorContext, HubSpreadsheetInsertRequest, HubSpreadsheetInsertSide, HubSpreadsheetMergeRequest, HubSpreadsheetOption, HubSpreadsheetPaste, HubSpreadsheetPermission, HubSpreadsheetRecord, HubSpreadsheetStructureOptions, HubSpreadsheetValue, HubSpreadsheetValueKind, HubXlsxOptions, HubXlsxReadOptions };