ng-hub-ui-spreadsheet 22.0.0 → 22.4.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.
@@ -1,7 +1,7 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { input, inject, TemplateRef, Directive, InjectionToken, ViewContainerRef, afterNextRender, ElementRef, Injector, booleanAttribute, contentChildren, model, output, signal, computed, viewChild, effect, untracked, afterRenderEffect, ChangeDetectionStrategy, Component, makeEnvironmentProviders } from '@angular/core';
4
- import { buildSpanMap, gridWindow, growWindowToSpans, clampGridRange, gridRangeBetween, coversRange, isCovered, spanAt, isWithinGridRange, gridCellTabIndex, moveGridFocus, resolveGridIntent, resolveGridEdge, anchorOf, gridRangeCells, TranslatePipe, HUB_TRANSLATION_PREFIX } from 'ng-hub-ui-utils';
3
+ import { input, inject, TemplateRef, Directive, InjectionToken, ViewContainerRef, afterNextRender, ElementRef, Injector, DestroyRef, computed, signal, afterRenderEffect, booleanAttribute, contentChildren, model, output, viewChild, effect, untracked, ChangeDetectionStrategy, Component, makeEnvironmentProviders } from '@angular/core';
4
+ import { buildSpanMap, gridWindow, growWindowToSpans, gridRangeBetween, clampGridRange, coversRange, clampGridSelection, addGridRange, isCovered, spanAt, isWithinGridSelection, isWithinGridRange, gridCellTabIndex, moveGridFocus, resolveGridIntent, resolveGridEdge, gridSelectionTable, anchorOf, gridSelectionCells, TranslatePipe, HUB_TRANSLATION_PREFIX } from 'ng-hub-ui-utils';
5
5
 
6
6
  /**
7
7
  * Reading a sheet back as plain data, keyed by column alias.
@@ -522,13 +522,23 @@ class HubSpreadsheetCellDirective {
522
522
  * ```
523
523
  */
524
524
  rows = input([], { ...(ngDevMode ? { debugName: "rows" } : /* istanbul ignore next */ {}), alias: 'hubSpreadsheetCellRows' });
525
+ /**
526
+ * The accessible name of the way in, when the cells of this column open onto something rather
527
+ * than being typed into.
528
+ *
529
+ * Given, the sheet keeps `Enter` for the cell instead of opening an editor the cell has not
530
+ * got, and reports the press through `opened`; the template draws the way in itself — a link,
531
+ * a button — and calls `open()` from its context. Left out, the cell is only read or typed
532
+ * into, and `Enter` does what the sheet would have done on its own.
533
+ */
534
+ action = input('', { ...(ngDevMode ? { debugName: "action" } : /* istanbul ignore next */ {}), alias: 'hubSpreadsheetCellAction' });
525
535
  template = inject(TemplateRef);
526
536
  /** Lets Angular narrow `let-` bindings inside the template to the real context type. */
527
537
  static ngTemplateContextGuard(_directive, _context) {
528
538
  return true;
529
539
  }
530
540
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubSpreadsheetCellDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
531
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubSpreadsheetCellDirective, isStandalone: true, selector: "[hubSpreadsheetCell]", inputs: { column: { classPropertyName: "column", publicName: "hubSpreadsheetCell", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "hubSpreadsheetCellRows", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
541
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubSpreadsheetCellDirective, isStandalone: true, selector: "[hubSpreadsheetCell]", inputs: { column: { classPropertyName: "column", publicName: "hubSpreadsheetCell", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "hubSpreadsheetCellRows", isSignal: true, isRequired: false, transformFunction: null }, action: { classPropertyName: "action", publicName: "hubSpreadsheetCellAction", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
532
542
  }
533
543
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubSpreadsheetCellDirective, decorators: [{
534
544
  type: Directive,
@@ -536,7 +546,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
536
546
  selector: '[hubSpreadsheetCell]',
537
547
  standalone: true
538
548
  }]
539
- }], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubSpreadsheetCell", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubSpreadsheetCellRows", required: false }] }] } });
549
+ }], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubSpreadsheetCell", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubSpreadsheetCellRows", required: false }] }], action: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubSpreadsheetCellAction", required: false }] }] } });
540
550
 
541
551
  /**
542
552
  * Resizing and reordering columns, as arithmetic on their own.
@@ -1789,6 +1799,143 @@ function applySuggestion(draft, suggestion, caret = draft.length) {
1789
1799
  return { draft: before + insert + trailing, caret: before.length + insert.length };
1790
1800
  }
1791
1801
 
1802
+ const OPERATORS = ['<>', '<=', '>=', '+', '-', '*', '/', '^', '&', '=', '<', '>'];
1803
+ const NUMBER = /^[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?/;
1804
+ const NAME = /^[A-Za-z_][A-Za-z0-9_.]*/;
1805
+ /** The next character that is not a space, from a position. */
1806
+ function nextSignificant(source, from) {
1807
+ let index = from;
1808
+ while (index < source.length && /\s/.test(source[index])) {
1809
+ index++;
1810
+ }
1811
+ return index;
1812
+ }
1813
+ /**
1814
+ * Names every piece of a formula.
1815
+ *
1816
+ * @param source - The text, `=` and all, as the reader is typing it.
1817
+ * @returns The tokens in order, which joined back up are `source` again.
1818
+ */
1819
+ function tokenizeFormula(source) {
1820
+ const tokens = [];
1821
+ let index = 0;
1822
+ let leading = true;
1823
+ while (index < source.length) {
1824
+ const char = source[index];
1825
+ if (/\s/.test(char)) {
1826
+ const end = nextSignificant(source, index);
1827
+ tokens.push({ kind: 'space', text: source.slice(index, end) });
1828
+ index = end;
1829
+ continue;
1830
+ }
1831
+ // Only the first `=` opens the formula; every other one is a comparison.
1832
+ if (char === '=' && leading) {
1833
+ tokens.push({ kind: 'equals', text: '=' });
1834
+ index++;
1835
+ leading = false;
1836
+ continue;
1837
+ }
1838
+ leading = false;
1839
+ // A column, brackets and all. An unclosed one is left plain rather than guessed at.
1840
+ if (char === '[') {
1841
+ const end = source.indexOf(']', index);
1842
+ if (end < 0) {
1843
+ tokens.push({ kind: 'plain', text: source.slice(index) });
1844
+ break;
1845
+ }
1846
+ tokens.push({ kind: 'column', text: source.slice(index, end + 1) });
1847
+ index = end + 1;
1848
+ continue;
1849
+ }
1850
+ // A quoted string, doubled quotes and all. Unterminated stays plain.
1851
+ if (char === '"') {
1852
+ let end = index + 1;
1853
+ let closed = false;
1854
+ while (end < source.length) {
1855
+ if (source[end] === '"') {
1856
+ if (source[end + 1] === '"') {
1857
+ end += 2;
1858
+ continue;
1859
+ }
1860
+ end++;
1861
+ closed = true;
1862
+ break;
1863
+ }
1864
+ end++;
1865
+ }
1866
+ tokens.push({ kind: closed ? 'text' : 'plain', text: source.slice(index, end) });
1867
+ index = end;
1868
+ continue;
1869
+ }
1870
+ if (/[0-9.]/.test(char)) {
1871
+ const match = NUMBER.exec(source.slice(index));
1872
+ if (!match) {
1873
+ tokens.push({ kind: 'plain', text: char });
1874
+ index++;
1875
+ continue;
1876
+ }
1877
+ tokens.push({ kind: 'number', text: match[0] });
1878
+ index += match[0].length;
1879
+ continue;
1880
+ }
1881
+ if (/[A-Za-z_]/.test(char)) {
1882
+ const match = NAME.exec(source.slice(index));
1883
+ const name = match[0];
1884
+ const upper = name.toUpperCase();
1885
+ if (upper === 'TRUE' || upper === 'FALSE') {
1886
+ tokens.push({ kind: 'boolean', text: name });
1887
+ index += name.length;
1888
+ continue;
1889
+ }
1890
+ if (parseCoordinate(name)) {
1891
+ // A rectangle reads as one reference, not as two places with a colon between them.
1892
+ const colon = nextSignificant(source, index + name.length);
1893
+ if (source[colon] === ':') {
1894
+ const far = nextSignificant(source, colon + 1);
1895
+ const next = NAME.exec(source.slice(far));
1896
+ if (next && parseCoordinate(next[0])) {
1897
+ const end = far + next[0].length;
1898
+ tokens.push({ kind: 'coordinate', text: source.slice(index, end) });
1899
+ index = end;
1900
+ continue;
1901
+ }
1902
+ }
1903
+ tokens.push({ kind: 'coordinate', text: name });
1904
+ index += name.length;
1905
+ continue;
1906
+ }
1907
+ // A function is a name that opens a bracket, however much space is between them.
1908
+ if (source[nextSignificant(source, index + name.length)] === '(') {
1909
+ tokens.push({ kind: 'function', text: name });
1910
+ index += name.length;
1911
+ continue;
1912
+ }
1913
+ tokens.push({ kind: 'plain', text: name });
1914
+ index += name.length;
1915
+ continue;
1916
+ }
1917
+ const operator = OPERATORS.find((candidate) => source.startsWith(candidate, index));
1918
+ if (operator) {
1919
+ tokens.push({ kind: 'operator', text: operator });
1920
+ index += operator.length;
1921
+ continue;
1922
+ }
1923
+ if (char === '(' || char === ')') {
1924
+ tokens.push({ kind: 'paren', text: char });
1925
+ index++;
1926
+ continue;
1927
+ }
1928
+ if (char === ',' || char === ';' || char === ':') {
1929
+ tokens.push({ kind: 'separator', text: char });
1930
+ index++;
1931
+ continue;
1932
+ }
1933
+ tokens.push({ kind: 'plain', text: char });
1934
+ index++;
1935
+ }
1936
+ return tokens;
1937
+ }
1938
+
1792
1939
  /**
1793
1940
  * The optional adapter a sheet renders its list cells through.
1794
1941
  *
@@ -1889,6 +2036,59 @@ function applySpanUnmerge(spans, anchors) {
1889
2036
  return spans.filter((span) => !anchors.some((anchor) => sameCell(anchor, span)));
1890
2037
  }
1891
2038
 
2039
+ /**
2040
+ * Every group an axis makes, deepest level last.
2041
+ *
2042
+ * @param levels The level of each member; `0` or a hole outside every fold.
2043
+ * @param identify Names a member for the group's id, usually its stable key.
2044
+ * @returns One group per run, at each level from 1 to the deepest the axis carries.
2045
+ */
2046
+ function outlineGroups(levels, identify) {
2047
+ const deepest = levels.reduce((deepest, level) => Math.max(deepest, level || 0), 0);
2048
+ const groups = [];
2049
+ for (let level = 1; level <= deepest; level++) {
2050
+ let start = -1;
2051
+ for (let index = 0; index <= levels.length; index++) {
2052
+ const inside = index < levels.length && (levels[index] || 0) >= level;
2053
+ if (inside && start === -1) {
2054
+ start = index;
2055
+ }
2056
+ if (!inside && start !== -1) {
2057
+ groups.push({ id: `${level}:${identify(start)}`, level, start, end: index - 1 });
2058
+ start = -1;
2059
+ }
2060
+ }
2061
+ }
2062
+ return groups;
2063
+ }
2064
+ /**
2065
+ * The members a set of folded groups takes out of the axis.
2066
+ *
2067
+ * A member is hidden when any group that encloses it — one whose level is no deeper than its own
2068
+ * and whose run contains it — is folded. Folding an outer group therefore takes its inner ones with
2069
+ * it, which is what folding a level in Excel does.
2070
+ *
2071
+ * @param levels The level of each member.
2072
+ * @param groups Every group the axis makes, as {@link outlineGroups} returns them.
2073
+ * @param collapsed The ids of the folded groups.
2074
+ * @returns The indices not to draw, in order.
2075
+ */
2076
+ function collapsedMembers(levels, groups, collapsed) {
2077
+ const folded = new Set(collapsed);
2078
+ const hidden = new Set();
2079
+ for (const group of groups) {
2080
+ if (!folded.has(group.id)) {
2081
+ continue;
2082
+ }
2083
+ for (let index = group.start; index <= group.end; index++) {
2084
+ if ((levels[index] || 0) >= group.level) {
2085
+ hidden.add(index);
2086
+ }
2087
+ }
2088
+ }
2089
+ return [...hidden].sort((a, b) => a - b);
2090
+ }
2091
+
1892
2092
  /**
1893
2093
  * The height a row is assumed to have on the very first frame of a virtualised sheet, in pixels.
1894
2094
  *
@@ -1915,6 +2115,50 @@ const FIRST_PASS_ROW_HEIGHT = 32;
1915
2115
  class HubSpreadsheetComponent {
1916
2116
  #host = inject(ElementRef);
1917
2117
  #injector = inject(Injector);
2118
+ #destroyRef = inject(DestroyRef);
2119
+ constructor() {
2120
+ afterNextRender(() => {
2121
+ // The list of suggestions hangs in the window rather than in the sheet, which is how
2122
+ // it escapes every scroll area and every `overflow: hidden` between the cell and the
2123
+ // page. The price is that it no longer travels with the sheet, so anything that
2124
+ // scrolls — or a resized window — puts it back on its cell. Scroll is caught in the
2125
+ // capture phase, because the thing that scrolls the page is rarely the window.
2126
+ const replace = () => {
2127
+ if (this.editing()) {
2128
+ this.placeSuggestions();
2129
+ }
2130
+ };
2131
+ const options = { capture: true, passive: true };
2132
+ document.addEventListener('scroll', replace, options);
2133
+ window.addEventListener('resize', replace, options);
2134
+ this.#destroyRef.onDestroy(() => {
2135
+ document.removeEventListener('scroll', replace, options);
2136
+ window.removeEventListener('resize', replace, options);
2137
+ });
2138
+ }, { injector: this.#injector });
2139
+ }
2140
+ /** The `dir` to put on the host: nothing when the page is left to decide, which is `'auto'`. */
2141
+ forcedDirection = computed(() => (this.direction() === 'auto' ? null : this.direction()), /* @ts-ignore */
2142
+ ...(ngDevMode ? [{ debugName: "forcedDirection" }] : /* istanbul ignore next */ []));
2143
+ /**
2144
+ * Which way the sheet actually runs, once the page has had its say.
2145
+ *
2146
+ * `'auto'` is answered by the element itself, because `dir` is inherited and only the element
2147
+ * knows what it inherited. Read after render: before it, the attribute this very binding sets is
2148
+ * not on the element yet, so the answer would be the page's and not the sheet's.
2149
+ */
2150
+ resolvedDirection = signal('ltr', /* @ts-ignore */
2151
+ ...(ngDevMode ? [{ debugName: "resolvedDirection" }] : /* istanbul ignore next */ []));
2152
+ readDirection = afterRenderEffect({
2153
+ read: () => {
2154
+ const preference = this.direction();
2155
+ const computed = getComputedStyle(this.#host.nativeElement).direction === 'rtl' ? 'rtl' : 'ltr';
2156
+ this.resolvedDirection.set(preference === 'auto' ? computed : preference);
2157
+ }
2158
+ });
2159
+ /** Whether the sheet runs right to left, which is what the menu and the list have to know. */
2160
+ rtl = computed(() => this.resolvedDirection() === 'rtl', /* @ts-ignore */
2161
+ ...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
1918
2162
  /** The rows, as the owner holds them. Never written to. */
1919
2163
  rows = input.required(/* @ts-ignore */
1920
2164
  ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
@@ -1982,6 +2226,25 @@ class HubSpreadsheetComponent {
1982
2226
  /** Aliases retired by earlier deletions, so a new column never reuses one. */
1983
2227
  retiredColumnKeys = input([], /* @ts-ignore */
1984
2228
  ...(ngDevMode ? [{ debugName: "retiredColumnKeys" }] : /* istanbul ignore next */ []));
2229
+ /**
2230
+ * Which way the sheet runs.
2231
+ *
2232
+ * `'auto'` follows whatever the page says, which is the only honest default: a sheet dropped
2233
+ * into a right-to-left article on a left-to-right page has to read the way the article does,
2234
+ * and a `dir` is inherited. `'ltr'` and `'rtl'` force it for a host that needs the sheet to run
2235
+ * against the page around it.
2236
+ */
2237
+ direction = input('auto', /* @ts-ignore */
2238
+ ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
2239
+ /**
2240
+ * Whether `Ctrl`-clicking (`Cmd` on a Mac) adds a second rectangle to the selection instead of
2241
+ * starting a new one.
2242
+ *
2243
+ * On, because it is what a spreadsheet does and because the gesture is otherwise spent: a plain
2244
+ * click already selects, so `Ctrl` had nothing to say. Off for a host that wants the modifier
2245
+ * for something of its own.
2246
+ */
2247
+ disjointSelection = input(true, { ...(ngDevMode ? { debugName: "disjointSelection" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1985
2248
  /** Whether the sheet offers the fill handle at the corner of the selection. */
1986
2249
  fillHandle = input(false, { ...(ngDevMode ? { debugName: "fillHandle" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1987
2250
  /** Whether undo is available; the sheet only asks, the owner keeps the history. */
@@ -2075,10 +2338,83 @@ class HubSpreadsheetComponent {
2075
2338
  */
2076
2339
  editOn = input('double-click', /* @ts-ignore */
2077
2340
  ...(ngDevMode ? [{ debugName: "editOn" }] : /* istanbul ignore next */ []));
2341
+ /**
2342
+ * A template drawn inside a row that is open, under the row itself.
2343
+ *
2344
+ * What a row holds behind its figures, laid out where the row is: a detail panel, the lines of
2345
+ * an order, a breakdown. In place and not in a dialog over it, so the open row can still be
2346
+ * read against the one above. The row opens when a cell reports an `action` or when the owner
2347
+ * writes {@link expandedRow} itself, and the template is handed the row, its index and a way
2348
+ * to close it.
2349
+ */
2350
+ expansion = input(null, /* @ts-ignore */
2351
+ ...(ngDevMode ? [{ debugName: "expansion" }] : /* istanbul ignore next */ []));
2352
+ /**
2353
+ * Which row is open, by its key, or null when none is.
2354
+ *
2355
+ * Two-way, so the owner can open a row from its own code — a filter that reveals a match, a
2356
+ * deep link — and hear about it when the reader opens or closes one. One row at a time: two
2357
+ * open rows would push each other off the screen and turn the sheet into a list of dialogs.
2358
+ */
2359
+ expandedRow = model(null, /* @ts-ignore */
2360
+ ...(ngDevMode ? [{ debugName: "expandedRow" }] : /* istanbul ignore next */ []));
2361
+ /**
2362
+ * PROTOTYPE — draw a floating toggle per row to open and close its expansion.
2363
+ *
2364
+ * Off unless asked for, so the sheet a host already has is untouched. The toggle is drawn by
2365
+ * the sheet and floats over the frame's leading edge, rather than being a control the owner
2366
+ * has to place.
2367
+ */
2368
+ expandToggle = input(false, /* @ts-ignore */
2369
+ ...(ngDevMode ? [{ debugName: "expandToggle" }] : /* istanbul ignore next */ []));
2370
+ /** The accessible name of that toggle, the owner's string — the sheet keeps no dictionary. */
2371
+ expandToggleLabel = input('', /* @ts-ignore */
2372
+ ...(ngDevMode ? [{ debugName: "expandToggleLabel" }] : /* istanbul ignore next */ []));
2373
+ /**
2374
+ * Which column groups are folded, by group id.
2375
+ *
2376
+ * Two-way, so the owner can fold a group from its own code and hear it when the reader folds
2377
+ * one. The ids are the outline the columns' own `level`s make — see {@link columnGroups} — and a
2378
+ * sheet whose columns carry no level has no groups at all.
2379
+ */
2380
+ collapsedColumns = model([], /* @ts-ignore */
2381
+ ...(ngDevMode ? [{ debugName: "collapsedColumns" }] : /* istanbul ignore next */ []));
2382
+ /** The accessible name of a column group's toggle, the owner's string. */
2383
+ columnToggleLabel = input('', /* @ts-ignore */
2384
+ ...(ngDevMode ? [{ debugName: "columnToggleLabel" }] : /* istanbul ignore next */ []));
2385
+ /**
2386
+ * How deep a row sits in an outline: `1` inside one fold, `2` inside two, and `0` or nothing
2387
+ * outside every fold.
2388
+ *
2389
+ * An accessor rather than a field, because the rows are the owner's data and the sheet does not
2390
+ * get to add one.
2391
+ */
2392
+ rowLevel = input(() => 0, /* @ts-ignore */
2393
+ ...(ngDevMode ? [{ debugName: "rowLevel" }] : /* istanbul ignore next */ []));
2394
+ /**
2395
+ * Which row groups are folded, by group id.
2396
+ *
2397
+ * Two-way, as {@link collapsedColumns} is, and the ids are the outline the rows' own levels
2398
+ * make — see {@link rowGroups}.
2399
+ */
2400
+ collapsedRows = model([], /* @ts-ignore */
2401
+ ...(ngDevMode ? [{ debugName: "collapsedRows" }] : /* istanbul ignore next */ []));
2402
+ /** The accessible name of a row group's toggle, the owner's string. */
2403
+ rowToggleLabel = input('', /* @ts-ignore */
2404
+ ...(ngDevMode ? [{ debugName: "rowToggleLabel" }] : /* istanbul ignore next */ []));
2078
2405
  /** A cell took a new value. */
2079
2406
  commit = output();
2080
2407
  /** A block was pasted. */
2081
2408
  pasted = output();
2409
+ /**
2410
+ * The reader asked to open what a cell points at.
2411
+ *
2412
+ * Raised by a cell that declares an `action`, with the pointer or with `Enter`. The sheet
2413
+ * reports and does no more: what a cell opens — a panel, a page, a dialog of the owner's — is
2414
+ * the owner's to open. A row that carries an expansion opens through {@link expandedRow}
2415
+ * instead, which is the sheet's own.
2416
+ */
2417
+ opened = output();
2082
2418
  /** Delete was pressed over these cells, the editable ones of the selection. */
2083
2419
  cleared = output();
2084
2420
  /** The selected rectangle changed; null when nothing is selected. */
@@ -2114,6 +2450,13 @@ class HubSpreadsheetComponent {
2114
2450
  * thing in a line.
2115
2451
  */
2116
2452
  mergeRequested = output();
2453
+ /**
2454
+ * Every rectangle of the selection, whenever it changes.
2455
+ *
2456
+ * Beside {@link selectionChange}, which keeps reporting the one the cursor is in: a host that
2457
+ * shows "sum of the selection" needs all of them, and one that shows "you are in B4" does not.
2458
+ */
2459
+ selectionRangesChange = output();
2117
2460
  /** The reader asked to take apart the blocks their selection touches, named by their anchors. */
2118
2461
  unmergeRequested = output();
2119
2462
  /** The cell the cursor is on. */
@@ -2122,6 +2465,25 @@ class HubSpreadsheetComponent {
2122
2465
  /** The other corner of the selection; null when only the active cell is selected. */
2123
2466
  anchor = signal(null, /* @ts-ignore */
2124
2467
  ...(ngDevMode ? [{ debugName: "anchor" }] : /* istanbul ignore next */ []));
2468
+ /**
2469
+ * The rectangles picked before the one being drawn.
2470
+ *
2471
+ * Held apart from the anchor and the cursor rather than as a list including them, because the
2472
+ * live one has to keep growing under the pointer while the others stay exactly as they were.
2473
+ */
2474
+ extraRanges = signal([], /* @ts-ignore */
2475
+ ...(ngDevMode ? [{ debugName: "extraRanges" }] : /* istanbul ignore next */ []));
2476
+ /**
2477
+ * The cells a pointing gesture is over, while a formula is being written.
2478
+ *
2479
+ * Kept apart from the selection: pointing picks a reference to put into the text, and moving the
2480
+ * reader's own selection out from under them to do it would be a different gesture wearing the
2481
+ * same click.
2482
+ */
2483
+ pointAnchor = signal(null, /* @ts-ignore */
2484
+ ...(ngDevMode ? [{ debugName: "pointAnchor" }] : /* istanbul ignore next */ []));
2485
+ pointTarget = signal(null, /* @ts-ignore */
2486
+ ...(ngDevMode ? [{ debugName: "pointTarget" }] : /* istanbul ignore next */ []));
2125
2487
  editing = signal(false, /* @ts-ignore */
2126
2488
  ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
2127
2489
  draft = signal('', /* @ts-ignore */
@@ -2187,6 +2549,9 @@ class HubSpreadsheetComponent {
2187
2549
  */
2188
2550
  viewport = viewChild('viewport', /* @ts-ignore */
2189
2551
  ...(ngDevMode ? [{ debugName: "viewport" }] : /* istanbul ignore next */ []));
2552
+ /** The coloured copy of the draft drawn behind the editor while a formula is being written. */
2553
+ tokens = viewChild('tokens', /* @ts-ignore */
2554
+ ...(ngDevMode ? [{ debugName: "tokens" }] : /* istanbul ignore next */ []));
2190
2555
  scrollTop = signal(0, /* @ts-ignore */
2191
2556
  ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
2192
2557
  viewportHeight = signal(0, /* @ts-ignore */
@@ -2206,7 +2571,7 @@ class HubSpreadsheetComponent {
2206
2571
  rowWindow = computed(() => {
2207
2572
  const total = this.rows().length;
2208
2573
  const frozen = Math.min(this.frozenRows(), total);
2209
- if (!this.virtual()) {
2574
+ if (!this.virtual() || this.hiddenRows().size) {
2210
2575
  return { start: frozen, end: total - 1, before: 0, after: 0 };
2211
2576
  }
2212
2577
  // Nothing measured and nothing declared, which is only ever the first frame: a rough height
@@ -2237,7 +2602,7 @@ class HubSpreadsheetComponent {
2237
2602
  const total = this.columns().length;
2238
2603
  const frozen = Math.min(this.frozenColumns(), total);
2239
2604
  const everything = { start: frozen, end: total - 1, before: 0, after: 0 };
2240
- if (!this.virtual()) {
2605
+ if (!this.virtual() || this.hiddenColumns().size) {
2241
2606
  return everything;
2242
2607
  }
2243
2608
  const widths = this.measuredColumnWidths();
@@ -2253,17 +2618,56 @@ class HubSpreadsheetComponent {
2253
2618
  return growWindowToSpans(window, this.spans(), 'col', widths, { pinned: frozen });
2254
2619
  }, /* @ts-ignore */
2255
2620
  ...(ngDevMode ? [{ debugName: "columnWindow" }] : /* istanbul ignore next */ []));
2256
- /** The column indices in the document, frozen ones first. */
2621
+ /** The outline the columns' levels make: one group per run, at each level. */
2622
+ columnGroups = computed(() => outlineGroups(this.columns().map((column) => column.level ?? 0), (index) => this.columns()[index]?.key ?? String(index)), /* @ts-ignore */
2623
+ ...(ngDevMode ? [{ debugName: "columnGroups" }] : /* istanbul ignore next */ []));
2624
+ /** The columns a folded group takes out, by index. */
2625
+ hiddenColumns = computed(() => new Set(collapsedMembers(this.columns().map((column) => column.level ?? 0), this.columnGroups(), this.collapsedColumns())), /* @ts-ignore */
2626
+ ...(ngDevMode ? [{ debugName: "hiddenColumns" }] : /* istanbul ignore next */ []));
2627
+ /** The outline the rows' levels make: one group per run, at each level. */
2628
+ rowGroups = computed(() => outlineGroups(this.rows().map((row) => this.rowLevel()(row) ?? 0), (index) => this.keys()[index]), /* @ts-ignore */
2629
+ ...(ngDevMode ? [{ debugName: "rowGroups" }] : /* istanbul ignore next */ []));
2630
+ /** The rows a folded group takes out, by index. */
2631
+ hiddenRows = computed(() => new Set(collapsedMembers(this.rows().map((row) => this.rowLevel()(row) ?? 0), this.rowGroups(), this.collapsedRows())), /* @ts-ignore */
2632
+ ...(ngDevMode ? [{ debugName: "hiddenRows" }] : /* istanbul ignore next */ []));
2633
+ /** The columns that belong to a group, so the run can be tinted while it is out. */
2634
+ groupedColumns = computed(() => {
2635
+ const grouped = new Set();
2636
+ for (const group of this.columnGroups()) {
2637
+ for (let index = group.start; index <= group.end; index++) {
2638
+ grouped.add(index);
2639
+ }
2640
+ }
2641
+ return grouped;
2642
+ }, /* @ts-ignore */
2643
+ ...(ngDevMode ? [{ debugName: "groupedColumns" }] : /* istanbul ignore next */ []));
2644
+ /** The rows that belong to a group. */
2645
+ groupedRows = computed(() => {
2646
+ const grouped = new Set();
2647
+ for (const group of this.rowGroups()) {
2648
+ for (let index = group.start; index <= group.end; index++) {
2649
+ grouped.add(index);
2650
+ }
2651
+ }
2652
+ return grouped;
2653
+ }, /* @ts-ignore */
2654
+ ...(ngDevMode ? [{ debugName: "groupedRows" }] : /* istanbul ignore next */ []));
2655
+ /** The column indices in the document: frozen ones first, folded ones out. */
2257
2656
  drawnColumns = computed(() => {
2258
2657
  const total = this.columns().length;
2259
2658
  const frozen = Math.min(this.frozenColumns(), total);
2260
2659
  const window = this.columnWindow();
2660
+ const hidden = this.hiddenColumns();
2261
2661
  const drawn = [];
2262
2662
  for (let index = 0; index < frozen; index++) {
2263
- drawn.push(index);
2663
+ if (!hidden.has(index)) {
2664
+ drawn.push(index);
2665
+ }
2264
2666
  }
2265
2667
  for (let index = window.start; index <= window.end; index++) {
2266
- drawn.push(index);
2668
+ if (!hidden.has(index)) {
2669
+ drawn.push(index);
2670
+ }
2267
2671
  }
2268
2672
  return drawn;
2269
2673
  }, /* @ts-ignore */
@@ -2280,17 +2684,22 @@ class HubSpreadsheetComponent {
2280
2684
  return this.drawnColumns().length + (window.before > 0 ? 1 : 0) + (window.after > 0 ? 1 : 0);
2281
2685
  }, /* @ts-ignore */
2282
2686
  ...(ngDevMode ? [{ debugName: "drawnCellCount" }] : /* istanbul ignore next */ []));
2283
- /** The row indices in the document, frozen ones first. */
2687
+ /** The row indices in the document: frozen ones first, folded ones out. */
2284
2688
  drawnRows = computed(() => {
2285
2689
  const total = this.rows().length;
2286
2690
  const frozen = Math.min(this.frozenRows(), total);
2287
2691
  const window = this.rowWindow();
2692
+ const hidden = this.hiddenRows();
2288
2693
  const drawn = [];
2289
2694
  for (let index = 0; index < frozen; index++) {
2290
- drawn.push(index);
2695
+ if (!hidden.has(index)) {
2696
+ drawn.push(index);
2697
+ }
2291
2698
  }
2292
2699
  for (let index = window.start; index <= window.end; index++) {
2293
- drawn.push(index);
2700
+ if (!hidden.has(index)) {
2701
+ drawn.push(index);
2702
+ }
2294
2703
  }
2295
2704
  return drawn;
2296
2705
  }, /* @ts-ignore */
@@ -2327,6 +2736,35 @@ class HubSpreadsheetComponent {
2327
2736
  /** Which suggestion the keyboard is on, or -1 when the reader has not moved into the list. */
2328
2737
  suggestionAt = signal(-1, /* @ts-ignore */
2329
2738
  ...(ngDevMode ? [{ debugName: "suggestionAt" }] : /* istanbul ignore next */ []));
2739
+ /**
2740
+ * Where the list of suggestions is anchored, in the host's own coordinates.
2741
+ *
2742
+ * The list used to be drawn inside the cell, and the scroll area — which is what makes the
2743
+ * sheet scroll — clipped it: on the last row it fell below the viewport and was never seen,
2744
+ * which is exactly the row a total is written in. It hangs off the host now, like the
2745
+ * structural menu, where no scroll area can cut it. `top` opens below the cell and `bottom`
2746
+ * above it, only one of the two ever being set; both null until the sheet has been measured.
2747
+ */
2748
+ suggestionsAt = signal(null, /* @ts-ignore */
2749
+ ...(ngDevMode ? [{ debugName: "suggestionsAt" }] : /* istanbul ignore next */ []));
2750
+ /**
2751
+ * Whether the cell being edited opens the plain field, which is the only editor the list of
2752
+ * suggestions belongs to.
2753
+ *
2754
+ * A list, a calendar or a host-supplied editor owns its own keystrokes, and the formula
2755
+ * language is not what is being typed there. While the list was drawn inside the field's own
2756
+ * branch that was true by construction; lifted to the host it has to be said.
2757
+ */
2758
+ editingField = computed(() => {
2759
+ const active = this.active();
2760
+ if (!active || !this.editing()) {
2761
+ return false;
2762
+ }
2763
+ return (!this.editorFor(active.col) &&
2764
+ !this.hostedControl(active.row, active.col) &&
2765
+ this.columns()[active.col]?.kind !== 'select');
2766
+ }, /* @ts-ignore */
2767
+ ...(ngDevMode ? [{ debugName: "editingField" }] : /* istanbul ignore next */ []));
2330
2768
  /**
2331
2769
  * What to offer while a formula is being written, or nothing when one is not.
2332
2770
  *
@@ -2334,7 +2772,7 @@ class HubSpreadsheetComponent {
2334
2772
  * for people — so without this, writing one means knowing something that is not on screen.
2335
2773
  */
2336
2774
  suggestions = computed(() => {
2337
- if (!this.formulas() || !this.editing()) {
2775
+ if (!this.formulas() || !this.editingField()) {
2338
2776
  return [];
2339
2777
  }
2340
2778
  const columns = this.columns();
@@ -2367,23 +2805,401 @@ class HubSpreadsheetComponent {
2367
2805
  /** Whether a formula is being written, which is when the aliases are worth showing. */
2368
2806
  writingFormula = computed(() => this.formulas() && this.editing() && isFormula(this.draft()), /* @ts-ignore */
2369
2807
  ...(ngDevMode ? [{ debugName: "writingFormula" }] : /* istanbul ignore next */ []));
2808
+ /**
2809
+ * The draft split into the pieces a formula is made of, so they can be drawn apart.
2810
+ *
2811
+ * Read off the raw draft rather than off the value that is shown, because this is what the
2812
+ * reader is typing: a half-written `=SUM(` still has to say those three things are what they
2813
+ * are, or the colour would only arrive once the formula was already right.
2814
+ */
2815
+ draftTokens = computed(() => tokenizeFormula(this.draft()), /* @ts-ignore */
2816
+ ...(ngDevMode ? [{ debugName: "draftTokens" }] : /* istanbul ignore next */ []));
2817
+ /**
2818
+ * Keeps the coloured copy under the field lined up with the field's own text.
2819
+ *
2820
+ * The field scrolls sideways once the formula is longer than the cell, and the copy has no way
2821
+ * to know it did — so it is told, on every scroll and on every keystroke that moves the caret.
2822
+ */
2823
+ syncTokens(event) {
2824
+ const overlay = this.tokens()?.nativeElement;
2825
+ const field = event.target;
2826
+ if (overlay && field) {
2827
+ overlay.scrollLeft = field.scrollLeft;
2828
+ }
2829
+ }
2830
+ /**
2831
+ * Where the coordinate badges go, in the host's own coordinates, while a formula is written.
2832
+ *
2833
+ * The badges live outside the sheet's frame rather than in its cells — so the frame stays
2834
+ * between them and the table — which also means nothing lines them up for us: every position is
2835
+ * measured off the drawn table. `start` is from the table's leading edge (either one: the sheet
2836
+ * may run the other way), `top` from its top.
2837
+ */
2838
+ coordColumns = signal([], /* @ts-ignore */
2839
+ ...(ngDevMode ? [{ debugName: "coordColumns" }] : /* istanbul ignore next */ []));
2840
+ coordRows = signal([], /* @ts-ignore */
2841
+ ...(ngDevMode ? [{ debugName: "coordRows" }] : /* istanbul ignore next */ []));
2842
+ measureCoords = afterRenderEffect({
2843
+ read: () => {
2844
+ if (!this.writingFormula()) {
2845
+ if (this.coordColumns().length) {
2846
+ this.coordColumns.set([]);
2847
+ }
2848
+ if (this.coordRows().length) {
2849
+ this.coordRows.set([]);
2850
+ }
2851
+ return;
2852
+ }
2853
+ const host = this.#host.nativeElement;
2854
+ const frame = host.getBoundingClientRect();
2855
+ const rtl = this.rtl();
2856
+ const columns = this.columns();
2857
+ const drawn = this.drawnRows();
2858
+ this.coordColumns.set([...host.querySelectorAll('.hub-spreadsheet__header[data-header]')].map((cell) => {
2859
+ const box = cell.getBoundingClientRect();
2860
+ const index = Number(cell.dataset['header']);
2861
+ return {
2862
+ start: Math.round(rtl ? frame.right - box.right : box.left - frame.left),
2863
+ width: Math.round(box.width),
2864
+ letter: columnLabel(index),
2865
+ alias: columns[index]?.key ?? ''
2866
+ };
2867
+ }));
2868
+ this.coordRows.set([...host.querySelectorAll('.hub-spreadsheet__row')].map((row, position) => {
2869
+ const box = row.getBoundingClientRect();
2870
+ return {
2871
+ top: Math.round(box.top - frame.top),
2872
+ height: Math.round(box.height),
2873
+ number: (drawn[position] ?? position) + 1
2874
+ };
2875
+ }));
2876
+ }
2877
+ });
2878
+ /**
2879
+ * PROTOTYPE — where each row's floating toggle goes, measured the way the coordinates are.
2880
+ *
2881
+ * Against the host's frame rather than inside a cell, so the toggle floats over the edge of the
2882
+ * sheet instead of travelling with the cells, and re-measured as the sheet scrolls because the
2883
+ * effect reads the scroll signals.
2884
+ */
2885
+ expansionToggles = signal([], /* @ts-ignore */
2886
+ ...(ngDevMode ? [{ debugName: "expansionToggles" }] : /* istanbul ignore next */ []));
2887
+ measureExpansionToggles = afterRenderEffect({
2888
+ read: () => {
2889
+ // Follow the sheet: reading the scroll puts the layer back on every scroll, the way the
2890
+ // frame itself moves.
2891
+ this.scrollTop();
2892
+ this.scrollLeft();
2893
+ if (!this.expandToggle() || !this.expansion()) {
2894
+ if (this.expansionToggles().length) {
2895
+ this.expansionToggles.set([]);
2896
+ }
2897
+ return;
2898
+ }
2899
+ const host = this.#host.nativeElement;
2900
+ const frame = host.getBoundingClientRect();
2901
+ const drawn = this.drawnRows();
2902
+ this.expansionToggles.set([...host.querySelectorAll('.hub-spreadsheet__row')].map((row, position) => {
2903
+ const box = row.getBoundingClientRect();
2904
+ const index = drawn[position] ?? position;
2905
+ return {
2906
+ top: Math.round(box.top - frame.top),
2907
+ height: Math.round(box.height),
2908
+ key: this.keys()[index],
2909
+ open: this.isExpanded(index)
2910
+ };
2911
+ }));
2912
+ }
2913
+ });
2914
+ /** Opens the row the toggle belongs to, or closes it when it was already the open one. */
2915
+ toggleByKey(key) {
2916
+ this.expandedRow.set(this.expandedRow() === key ? null : key);
2917
+ }
2918
+ /**
2919
+ * Where each column group's toggle goes, measured off the headers it folds.
2920
+ *
2921
+ * At the trailing edge of the group, or — when the group is folded and has no header of its own
2922
+ * — at the trailing edge of the column before it, so the way back in stays where the group was.
2923
+ */
2924
+ columnToggles = signal([], /* @ts-ignore */
2925
+ ...(ngDevMode ? [{ debugName: "columnToggles" }] : /* istanbul ignore next */ []));
2926
+ measureColumnToggles = afterRenderEffect({
2927
+ read: () => {
2928
+ this.scrollTop();
2929
+ this.scrollLeft();
2930
+ const groups = this.columnGroups();
2931
+ if (!groups.length) {
2932
+ if (this.columnToggles().length) {
2933
+ this.columnToggles.set([]);
2934
+ }
2935
+ return;
2936
+ }
2937
+ const host = this.#host.nativeElement;
2938
+ const frame = host.getBoundingClientRect();
2939
+ const rtl = this.rtl();
2940
+ const folded = new Set(this.collapsedColumns());
2941
+ const headers = [...host.querySelectorAll('.hub-spreadsheet__header[data-header]')];
2942
+ const boxAt = (index) => {
2943
+ const cell = headers.find((one) => Number(one.dataset['header']) === index);
2944
+ return cell ? cell.getBoundingClientRect() : null;
2945
+ };
2946
+ const leadingAt = (index) => {
2947
+ const box = boxAt(index);
2948
+ return box ? Math.round(rtl ? frame.right - box.right : box.left - frame.left) : null;
2949
+ };
2950
+ const trailingAt = (index) => {
2951
+ if (index < 0) {
2952
+ return 0;
2953
+ }
2954
+ const box = boxAt(index);
2955
+ return box ? Math.round(rtl ? frame.right - box.left : box.right - frame.left) : null;
2956
+ };
2957
+ // A folded run is not drawn, so its edge is the one it left behind: the leading edge of
2958
+ // the column that follows it, or the trailing edge of the one before when it was last.
2959
+ const drawn = this.drawnColumns();
2960
+ const boundaryOf = (group) => {
2961
+ const after = drawn.find((index) => index > group.end);
2962
+ if (after !== undefined) {
2963
+ return leadingAt(after);
2964
+ }
2965
+ const before = [...drawn].reverse().find((index) => index < group.start);
2966
+ return before !== undefined ? trailingAt(before) : 0;
2967
+ };
2968
+ this.columnToggles.set(groups
2969
+ .map((group) => {
2970
+ const open = !folded.has(group.id);
2971
+ const toggle = open ? trailingAt(group.end) : boundaryOf(group);
2972
+ return toggle === null ? null : { id: group.id, toggle, open };
2973
+ })
2974
+ .filter((one) => one !== null));
2975
+ }
2976
+ });
2977
+ /** Folds a column group away, or brings it back when it was folded. */
2978
+ toggleColumnGroup(id) {
2979
+ this.animateFold(() => this.collapsedColumns.update((collapsed) => collapsed.includes(id) ? collapsed.filter((one) => one !== id) : [...collapsed, id]));
2980
+ }
2981
+ /**
2982
+ * Runs a fold with the browser's own transition around it.
2983
+ *
2984
+ * The sheet draws one state or the other and nothing between: a folded group is simply not in
2985
+ * the document, so there is no width to animate. What it asks the browser for instead is a cross
2986
+ * -fade between the two states, through the View Transition API where it exists — and does the
2987
+ * fold with no transition where it does not, rather than leaving it undone.
2988
+ */
2989
+ animateFold(update) {
2990
+ const doc = document;
2991
+ if (typeof doc.startViewTransition === 'function') {
2992
+ doc.startViewTransition(update);
2993
+ }
2994
+ else {
2995
+ update();
2996
+ }
2997
+ }
2998
+ /**
2999
+ * Where each row group's toggle goes, measured off the rows it folds.
3000
+ *
3001
+ * At the vertical middle of the row whose fold it is, at the frame's leading edge — a handle on
3002
+ * the heading, not a cell's control.
3003
+ */
3004
+ rowGroupToggles = signal([], /* @ts-ignore */
3005
+ ...(ngDevMode ? [{ debugName: "rowGroupToggles" }] : /* istanbul ignore next */ []));
3006
+ measureRowGroupToggles = afterRenderEffect({
3007
+ read: () => {
3008
+ this.scrollTop();
3009
+ this.scrollLeft();
3010
+ const groups = this.rowGroups();
3011
+ if (!groups.length) {
3012
+ if (this.rowGroupToggles().length) {
3013
+ this.rowGroupToggles.set([]);
3014
+ }
3015
+ return;
3016
+ }
3017
+ const host = this.#host.nativeElement;
3018
+ const frame = host.getBoundingClientRect();
3019
+ const rows = [...host.querySelectorAll('.hub-spreadsheet__row')];
3020
+ const drawn = this.drawnRows();
3021
+ const folded = new Set(this.collapsedRows());
3022
+ const boxAt = (index) => {
3023
+ const position = drawn.indexOf(index);
3024
+ const row = position === -1 ? null : rows[position];
3025
+ return row ? row.getBoundingClientRect() : null;
3026
+ };
3027
+ // A folded run is not drawn, so its edge is the one it left behind: the top of the row
3028
+ // that follows it, or the bottom of the one before when it was last.
3029
+ const boundaryOf = (group) => {
3030
+ const after = drawn.find((index) => index > group.end);
3031
+ const box = after !== undefined ? boxAt(after) : null;
3032
+ if (box) {
3033
+ return Math.round(box.top - frame.top);
3034
+ }
3035
+ const before = [...drawn].reverse().find((index) => index < group.start);
3036
+ const last = before !== undefined ? boxAt(before) : null;
3037
+ return last ? Math.round(last.bottom - frame.top) : 0;
3038
+ };
3039
+ this.rowGroupToggles.set(groups
3040
+ .map((group) => {
3041
+ const open = !folded.has(group.id);
3042
+ if (open) {
3043
+ const box = boxAt(group.end);
3044
+ return box === null
3045
+ ? null
3046
+ : { id: group.id, toggle: Math.round(box.top - frame.top + box.height / 2), open };
3047
+ }
3048
+ return { id: group.id, toggle: boundaryOf(group), open };
3049
+ })
3050
+ .filter((one) => one !== null));
3051
+ }
3052
+ });
3053
+ /** Folds a row group away, or brings it back when it was folded. */
3054
+ toggleRowGroup(id) {
3055
+ this.animateFold(() => this.collapsedRows.update((collapsed) => collapsed.includes(id) ? collapsed.filter((one) => one !== id) : [...collapsed, id]));
3056
+ }
3057
+ /**
3058
+ * Puts the list of suggestions back on its cell once the frame has shifted for a formula.
3059
+ *
3060
+ * The list is placed from the cell's own rectangle, and writing the first `=` moves every cell
3061
+ * down and in to make room for the coordinates — so the place the list was given when the cell
3062
+ * opened is a place the cell has since left.
3063
+ */
3064
+ placeSuggestionsOnRender = afterRenderEffect({
3065
+ read: () => {
3066
+ if (this.writingFormula() && this.suggesting()) {
3067
+ this.placeSuggestions();
3068
+ }
3069
+ }
3070
+ });
2370
3071
  /** The letters a column answers to in a formula: A, B … Z, AA. */
2371
3072
  columnLetter(index) {
2372
3073
  return columnLabel(index);
2373
3074
  }
3075
+ /**
3076
+ * Writes a column's alias into the formula being written, from its header badge.
3077
+ *
3078
+ * Nothing when no formula is being written: the badge also shows outside one, as a reading of
3079
+ * what the column is called, and a click there has nothing to write into.
3080
+ */
3081
+ insertAlias(alias) {
3082
+ if (this.writingFormula()) {
3083
+ this.useSuggestion({ label: alias, kind: 'column', insert: `[${alias}]` });
3084
+ }
3085
+ }
2374
3086
  /** Puts a suggestion into the draft and leaves the caret where the next thing is typed. */
2375
3087
  useSuggestion(suggestion) {
2376
- const editor = this.#host.nativeElement.querySelector('input.hub-spreadsheet__editor');
3088
+ const editor = this.editorField();
2377
3089
  const caret = editor?.selectionStart ?? this.draft().length;
2378
3090
  const applied = applySuggestion(this.draft(), suggestion, caret);
2379
- this.draft.set(applied.draft);
3091
+ this.writeDraft(applied.draft, applied.caret);
3092
+ }
3093
+ /** The field a formula is being written in, when there is one. */
3094
+ editorField() {
3095
+ return this.#host.nativeElement.querySelector('input.hub-spreadsheet__editor');
3096
+ }
3097
+ /**
3098
+ * Puts text into the draft at the caret and leaves it there, ready for what is typed next.
3099
+ *
3100
+ * The caret matters as much as the text: dropped after an inserted reference it is ready for
3101
+ * the operator or the next argument, and left where it was the reader has to move it by hand
3102
+ * every single time.
3103
+ */
3104
+ writeDraft(text, caret) {
3105
+ const editor = this.editorField();
3106
+ this.draft.set(text);
2380
3107
  this.suggestionAt.set(-1);
2381
3108
  if (editor) {
2382
- editor.value = applied.draft;
3109
+ editor.value = text;
2383
3110
  editor.focus();
2384
- editor.setSelectionRange(applied.caret, applied.caret);
3111
+ editor.setSelectionRange(caret, caret);
2385
3112
  }
2386
3113
  }
3114
+ /** Starts pointing at cells to put their reference into the formula being written. */
3115
+ beginPointing(row, col) {
3116
+ this.pointAnchor.set({ row, col });
3117
+ this.pointTarget.set({ row, col });
3118
+ }
3119
+ /** Gives up on a pointing gesture without writing anything. */
3120
+ cancelPointing() {
3121
+ this.pointAnchor.set(null);
3122
+ this.pointTarget.set(null);
3123
+ }
3124
+ /**
3125
+ * Writes the pointed rectangle into the formula as a reference and ends the gesture.
3126
+ *
3127
+ * A single cell goes in as `B3`; more than one as `B3:D7`, the way a spreadsheet names a
3128
+ * rectangle. Coordinates rather than aliases, because that is what pointing at cells means:
3129
+ * an alias names a whole column, and there is no alias for one cell or for a patch of them.
3130
+ */
3131
+ commitPointing() {
3132
+ const anchor = this.pointAnchor();
3133
+ const target = this.pointTarget();
3134
+ this.cancelPointing();
3135
+ if (!anchor || !target) {
3136
+ return;
3137
+ }
3138
+ const range = gridRangeBetween(anchor, target);
3139
+ const near = formatCoordinate({ row: range.top, col: range.left });
3140
+ const far = formatCoordinate({ row: range.bottom, col: range.right });
3141
+ const reference = near === far ? near : `${near}:${far}`;
3142
+ const editor = this.editorField();
3143
+ const caret = editor?.selectionStart ?? this.draft().length;
3144
+ this.writeDraft(this.draft().slice(0, caret) + reference + this.draft().slice(caret), caret + reference.length);
3145
+ }
3146
+ /**
3147
+ * Works out where the list of suggestions belongs, against the cell being edited.
3148
+ *
3149
+ * The list is `fixed`, so these are window coordinates read straight off the cell's own
3150
+ * rectangle — which is also the only honest way to place it with frozen panes and
3151
+ * virtualisation, where the drawn position and the scroll offset are not the same number. It
3152
+ * hangs just under the cell, pulled in only as far as keeps it on screen, and it flips above
3153
+ * the cell when the window has no room below — so the last row of a sheet that reaches the
3154
+ * foot of the page can still be written in.
3155
+ */
3156
+ placeSuggestions() {
3157
+ const active = this.active();
3158
+ if (!active) {
3159
+ this.suggestionsAt.set(null);
3160
+ return;
3161
+ }
3162
+ const cell = this.#host.nativeElement
3163
+ .querySelector(`[data-cell="${active.row}-${active.col}"]`)
3164
+ ?.getBoundingClientRect();
3165
+ // A sheet that has not been laid out — a hidden panel, a closed tab, a test with no layout —
3166
+ // has nothing to measure. The list keeps where it was rather than jumping to a corner, and
3167
+ // the next placement gets it right.
3168
+ if (!cell || (cell.width === 0 && cell.height === 0)) {
3169
+ return;
3170
+ }
3171
+ // The width the list asks for, and how far it can go before running off the window's edge.
3172
+ // Measured from the edge the sheet starts at — the left when it reads left to right, the
3173
+ // right when it does not — because the list is placed by its inline start, whichever that is.
3174
+ const wanted = 224;
3175
+ const width = Math.min(wanted, window.innerWidth);
3176
+ const fromStart = this.rtl() ? window.innerWidth - cell.right : cell.left;
3177
+ const x = Math.max(0, Math.min(fromStart, window.innerWidth - width));
3178
+ // Below is the default; above only when below is the shorter side, so the list does not
3179
+ // cover the rows the reader is writing between.
3180
+ const roomBelow = window.innerHeight - cell.bottom;
3181
+ const above = roomBelow < wanted && cell.top > roomBelow;
3182
+ this.suggestionsAt.set({
3183
+ x,
3184
+ top: above ? null : cell.bottom,
3185
+ bottom: above ? window.innerHeight - cell.top : null,
3186
+ maxWidth: Math.max(width, window.innerWidth - x)
3187
+ });
3188
+ }
3189
+ /**
3190
+ * Whether a cell is worked out from a formula rather than typed.
3191
+ *
3192
+ * Two ways to be one: the row holds the text of a formula, or the column declares one and every
3193
+ * cell of it answers with it. Only when the sheet reads formulas at all — with that off, a
3194
+ * leading `=` is just the character somebody typed, and marking it would be a lie.
3195
+ */
3196
+ isFormulaCell(row, col) {
3197
+ if (!this.formulas()) {
3198
+ return false;
3199
+ }
3200
+ const cell = this.grid()[row]?.[col];
3201
+ return !!cell && (isFormula(cell.value) || !!this.columns()[col]?.formula);
3202
+ }
2387
3203
  /** Whether a cell is showing a formula that could not be worked out. */
2388
3204
  hasFormulaError(row, col) {
2389
3205
  const cell = this.grid()[row]?.[col];
@@ -2406,6 +3222,22 @@ class HubSpreadsheetComponent {
2406
3222
  return base ? clampGridRange(coversRange(this.spanMap(), base), this.bounds()) : null;
2407
3223
  }, /* @ts-ignore */
2408
3224
  ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
3225
+ /**
3226
+ * Every rectangle the reader has picked: the ones held with `Ctrl`, and the live one last.
3227
+ *
3228
+ * Trimmed to the grid on every read for the same reason one rectangle is — rows come and go
3229
+ * under a live selection, and a stale rectangle would be cleared or copied against cells that
3230
+ * are not there any more.
3231
+ */
3232
+ selection = computed(() => {
3233
+ const live = this.range();
3234
+ const kept = clampGridSelection(this.extraRanges(), this.bounds());
3235
+ return live ? addGridRange(kept, live) : kept;
3236
+ }, /* @ts-ignore */
3237
+ ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
3238
+ /** Whether the reader has picked more than one rectangle. */
3239
+ disjoint = computed(() => this.selection().length > 1, /* @ts-ignore */
3240
+ ...(ngDevMode ? [{ debugName: "disjoint" }] : /* istanbul ignore next */ []));
2409
3241
  /**
2410
3242
  * Keeps the cursor where it was when the rows are replaced by a reload after a save, and drops
2411
3243
  * it only once it points at nothing. Typing a value and pressing Enter has to land on the cell
@@ -2418,6 +3250,7 @@ class HubSpreadsheetComponent {
2418
3250
  if (active && (active.row >= rows || active.col >= cols)) {
2419
3251
  this.active.set(null);
2420
3252
  this.anchor.set(null);
3253
+ this.extraRanges.set([]);
2421
3254
  this.editing.set(false);
2422
3255
  }
2423
3256
  });
@@ -2557,7 +3390,26 @@ class HubSpreadsheetComponent {
2557
3390
  return !!active && active.row === row && active.col === col;
2558
3391
  }
2559
3392
  isSelected(row, col) {
2560
- return isWithinGridRange({ row, col }, this.range());
3393
+ return isWithinGridSelection({ row, col }, this.selection());
3394
+ }
3395
+ /** Whether a press is being dragged out to point a reference into the formula being written. */
3396
+ pointing = computed(() => !!this.pointAnchor(), /* @ts-ignore */
3397
+ ...(ngDevMode ? [{ debugName: "pointing" }] : /* istanbul ignore next */ []));
3398
+ /**
3399
+ * The rectangle a pointing gesture currently covers, or null when none is in progress.
3400
+ *
3401
+ * Drawn on the cells rather than kept out of sight, because the whole point of pointing is to
3402
+ * see what is about to go into the formula before letting go of it.
3403
+ */
3404
+ pointedRange = computed(() => {
3405
+ const anchor = this.pointAnchor();
3406
+ const target = this.pointTarget();
3407
+ return anchor && target ? gridRangeBetween(anchor, target) : null;
3408
+ }, /* @ts-ignore */
3409
+ ...(ngDevMode ? [{ debugName: "pointedRange" }] : /* istanbul ignore next */ []));
3410
+ isPointed(row, col) {
3411
+ const range = this.pointedRange();
3412
+ return !!range && isWithinGridRange({ row, col }, range);
2561
3413
  }
2562
3414
  tabIndexOf(row, col) {
2563
3415
  return gridCellTabIndex({ row, col }, this.active(), 'roving');
@@ -2660,9 +3512,51 @@ class HubSpreadsheetComponent {
2660
3512
  cell,
2661
3513
  row: this.rows()[row],
2662
3514
  column: this.columns()[col],
2663
- active: this.isActive(row, col)
3515
+ active: this.isActive(row, col),
3516
+ open: () => this.openCell(row, col)
2664
3517
  };
2665
3518
  }
3519
+ /** Whether a row is the one open. */
3520
+ isExpanded(row) {
3521
+ return this.expandedRow() !== null && this.expandedRow() === this.keys()[row];
3522
+ }
3523
+ /** The context a row's expansion template is rendered with. */
3524
+ expansionContext(row) {
3525
+ return {
3526
+ $implicit: this.rows()[row],
3527
+ row: this.rows()[row],
3528
+ index: row,
3529
+ key: this.keys()[row],
3530
+ close: () => this.closeExpansion()
3531
+ };
3532
+ }
3533
+ /** Opens a row, or closes it when it was already the open one. */
3534
+ toggleExpansion(row) {
3535
+ this.expandedRow.set(this.isExpanded(row) ? null : this.keys()[row]);
3536
+ }
3537
+ /** Closes whatever row is open. */
3538
+ closeExpansion() {
3539
+ this.expandedRow.set(null);
3540
+ }
3541
+ /**
3542
+ * Whether this column's cells open onto something rather than being typed into.
3543
+ *
3544
+ * A cell template says so with `hubSpreadsheetCellAction`, and only the template does: the
3545
+ * sheet draws nothing itself, but it has to know, because it is what keeps `Enter` for the
3546
+ * cell instead of opening an editor the cell has not got.
3547
+ */
3548
+ cellOpens(col) {
3549
+ return !!this.cellTemplateFor(col)?.action();
3550
+ }
3551
+ /** Reports that the reader asked to open what a cell points at. */
3552
+ openCell(row, col) {
3553
+ this.opened.emit({
3554
+ coords: { row, col },
3555
+ row: this.rows()[row],
3556
+ column: this.columns()[col],
3557
+ cell: this.grid()[row][col]
3558
+ });
3559
+ }
2666
3560
  /** The context a custom editor template is rendered with. */
2667
3561
  editorContext(row, col) {
2668
3562
  const cell = this.grid()[row][col];
@@ -2720,6 +3614,22 @@ class HubSpreadsheetComponent {
2720
3614
  }
2721
3615
  return !this.readonly() && !!this.grid()[row]?.[col]?.editable;
2722
3616
  }
3617
+ /**
3618
+ * Opens the cell the `fx` marks for writing, so the formula is edited where it is read.
3619
+ *
3620
+ * Nothing on a cell that refuses to open: a column that carries its own formula answers the same
3621
+ * in every row and has nothing to type over, and its mark says so by not taking the pointer.
3622
+ */
3623
+ editFormula(row, col, event) {
3624
+ // The mark sits inside the cell, so the press must not reach the cell and be taken for a pick.
3625
+ event.preventDefault();
3626
+ event.stopPropagation();
3627
+ if (!this.canEdit(row, col)) {
3628
+ return;
3629
+ }
3630
+ this.moveTo({ row, col }, false);
3631
+ this.startEdit(undefined);
3632
+ }
2723
3633
  onKeydown(event) {
2724
3634
  const active = this.active();
2725
3635
  if (!active) {
@@ -2754,6 +3664,13 @@ class HubSpreadsheetComponent {
2754
3664
  this.moveTo(resolveGridEdge(active, intent.edge, this.bounds()), intent.extend);
2755
3665
  break;
2756
3666
  case 'edit':
3667
+ // A cell that opens onto something, with no field to write in, answers Enter with
3668
+ // that rather than with an editor it does not have. Typing a character is left
3669
+ // alone: such a cell holds no value to write over.
3670
+ if (!intent.seed && !this.canEdit(active.row, active.col) && this.cellOpens(active.col)) {
3671
+ this.openCell(active.row, active.col);
3672
+ break;
3673
+ }
2757
3674
  this.startEdit(intent.seed);
2758
3675
  break;
2759
3676
  case 'clear':
@@ -2785,7 +3702,19 @@ class HubSpreadsheetComponent {
2785
3702
  }
2786
3703
  }
2787
3704
  onMouseDown(event, row, col) {
2788
- if (event.button !== 0 || (this.editing() && this.isActive(row, col))) {
3705
+ if (event.button !== 0) {
3706
+ return;
3707
+ }
3708
+ // While a formula is being written, a press on another cell points at it instead of
3709
+ // committing the text: that is how a reference gets into a formula without typing its
3710
+ // address. The press is swallowed so the field keeps the focus and the caret it was about
3711
+ // to lose — which is also what lets the reader drag out a rectangle.
3712
+ if (this.editing() && this.writingFormula() && !this.isActive(row, col)) {
3713
+ event.preventDefault();
3714
+ this.beginPointing(row, col);
3715
+ return;
3716
+ }
3717
+ if (this.editing() && this.isActive(row, col)) {
2789
3718
  return;
2790
3719
  }
2791
3720
  if (this.editing()) {
@@ -2793,7 +3722,17 @@ class HubSpreadsheetComponent {
2793
3722
  }
2794
3723
  this.dragging = true;
2795
3724
  this.dragMoved = false;
2796
- this.moveTo({ row, col }, event.shiftKey);
3725
+ // `Ctrl` (or `Cmd`) puts the rectangle that was being drawn away and starts another, so the
3726
+ // reader can pick blocks that have nothing to do with each other. Shift still extends the
3727
+ // live one, and holding both extends it without losing the rest.
3728
+ const adding = this.disjointSelection() && (event.ctrlKey || event.metaKey);
3729
+ if (adding && !event.shiftKey) {
3730
+ const live = this.range();
3731
+ if (live) {
3732
+ this.extraRanges.update((ranges) => addGridRange(ranges, live));
3733
+ }
3734
+ }
3735
+ this.moveTo({ row, col }, event.shiftKey, adding);
2797
3736
  }
2798
3737
  /**
2799
3738
  * Opens the editor on release, not on press.
@@ -2814,6 +3753,11 @@ class HubSpreadsheetComponent {
2814
3753
  this.startEdit(undefined);
2815
3754
  }
2816
3755
  onMouseEnter(row, col) {
3756
+ // Pointing grows its own rectangle, and the reader's selection is left exactly where it was.
3757
+ if (this.pointing()) {
3758
+ this.pointTarget.set({ row, col });
3759
+ return;
3760
+ }
2817
3761
  if (this.dragging && !this.isActive(row, col)) {
2818
3762
  this.dragMoved = true;
2819
3763
  }
@@ -2872,8 +3816,15 @@ class HubSpreadsheetComponent {
2872
3816
  }
2873
3817
  }
2874
3818
  endDrag() {
3819
+ const pointing = this.pointing();
2875
3820
  this.dragging = false;
2876
3821
  this.resizing = null;
3822
+ // A pointing gesture is not a drag: releasing it writes the reference rather than finishing
3823
+ // a column move or a fill, neither of which was ever started.
3824
+ if (pointing) {
3825
+ this.commitPointing();
3826
+ return;
3827
+ }
2877
3828
  this.commitColumnMove();
2878
3829
  if (this.fillSource()) {
2879
3830
  this.commitFill();
@@ -2894,7 +3845,9 @@ class HubSpreadsheetComponent {
2894
3845
  }
2895
3846
  event.preventDefault();
2896
3847
  const host = this.#host.nativeElement.getBoundingClientRect();
2897
- this.menuAt.set({ x: event.clientX - host.left, y: event.clientY - host.top });
3848
+ // Placed by its inline start, so measured from the edge the sheet starts at.
3849
+ const x = this.rtl() ? host.right - event.clientX : event.clientX - host.left;
3850
+ this.menuAt.set({ x, y: event.clientY - host.top });
2898
3851
  }
2899
3852
  closeMenu() {
2900
3853
  this.menuAt.set(null);
@@ -3026,6 +3979,10 @@ class HubSpreadsheetComponent {
3026
3979
  this.viewportHeight.set(viewport.clientHeight);
3027
3980
  this.scrollLeft.set(viewport.scrollLeft);
3028
3981
  this.viewportWidth.set(viewport.clientWidth);
3982
+ // The cell being edited has moved with the scroll, and so has the list hanging off it.
3983
+ if (this.editing()) {
3984
+ this.placeSuggestions();
3985
+ }
3029
3986
  if (!this.rowHeight()) {
3030
3987
  const row = viewport.querySelector('.hub-spreadsheet__row');
3031
3988
  const height = row?.getBoundingClientRect().height ?? 0;
@@ -3058,7 +4015,9 @@ class HubSpreadsheetComponent {
3058
4015
  /** Whether the fill handle belongs on this cell: the far corner of the selection. */
3059
4016
  hasFillHandle(row, col) {
3060
4017
  const range = this.range();
3061
- return this.fillHandle() && !this.readonly() && !!range && row === range.bottom && col === range.right;
4018
+ // Never on a disjoint selection: a fill continues one rectangle, and which of several it
4019
+ // would continue is a question with no answer. A spreadsheet drops the handle too.
4020
+ return (this.fillHandle() && !this.readonly() && !this.disjoint() && !!range && row === range.bottom && col === range.right);
3062
4021
  }
3063
4022
  /** Whether a cell is inside the area a released fill would write to. */
3064
4023
  isFillPreview(row, col) {
@@ -3165,15 +4124,18 @@ class HubSpreadsheetComponent {
3165
4124
  this.startEdit(undefined);
3166
4125
  }
3167
4126
  onCopy(event, cut) {
3168
- const range = this.range();
3169
- if (!range || this.editing() || !event.clipboardData) {
4127
+ // Several rectangles only make a table when they line up — all on the same columns, or all
4128
+ // on the same rows. Anything else has no honest shape to write, so nothing is written and
4129
+ // the clipboard keeps what it had, which is what Excel does when it refuses the command.
4130
+ const table = gridSelectionTable(this.selection());
4131
+ if (!table || this.editing() || !event.clipboardData) {
3170
4132
  return;
3171
4133
  }
3172
4134
  const grid = this.grid();
3173
4135
  const values = [];
3174
- for (let row = range.top; row <= range.bottom; row++) {
4136
+ for (const row of table.rows) {
3175
4137
  const line = [];
3176
- for (let col = range.left; col <= range.right; col++) {
4138
+ for (const col of table.cols) {
3177
4139
  line.push(grid[row][col].value);
3178
4140
  }
3179
4141
  values.push(line);
@@ -3270,6 +4232,9 @@ class HubSpreadsheetComponent {
3270
4232
  this.suggestionAt.set(-1);
3271
4233
  this.draft.set(event.target.value);
3272
4234
  this.invalid.set(false);
4235
+ // The copy behind has just changed length, and may have been re-anchored; keep it where the
4236
+ // field is rather than at the start of the line.
4237
+ this.syncTokens(event);
3273
4238
  }
3274
4239
  onEditorKeydown(event) {
3275
4240
  // A composition is in flight: the Enter that picks a candidate is not the Enter that
@@ -3298,7 +4263,14 @@ class HubSpreadsheetComponent {
3298
4263
  case 'Escape':
3299
4264
  event.preventDefault();
3300
4265
  event.stopPropagation();
3301
- this.cancelEdit();
4266
+ // A pointing gesture in flight is what Escape gives up first: the formula stays as it
4267
+ // was, and the reader is still writing it.
4268
+ if (this.pointing()) {
4269
+ this.cancelPointing();
4270
+ }
4271
+ else {
4272
+ this.cancelEdit();
4273
+ }
3302
4274
  break;
3303
4275
  }
3304
4276
  }
@@ -3448,6 +4420,9 @@ class HubSpreadsheetComponent {
3448
4420
  this.draft.set(this.openingText(cell, kind, seed, opener));
3449
4421
  this.invalid.set(false);
3450
4422
  this.editing.set(true);
4423
+ // The cell is already drawn — it is the one that was clicked — so its rectangle can be read
4424
+ // now, and the list is anchored before the list itself is ever rendered.
4425
+ this.placeSuggestions();
3451
4426
  afterNextRender(() => {
3452
4427
  const editor = this.#host.nativeElement.querySelector('.hub-spreadsheet__editor');
3453
4428
  if (!editor) {
@@ -3577,7 +4552,7 @@ class HubSpreadsheetComponent {
3577
4552
  }
3578
4553
  return typeof cell.value === 'number' ? String(cell.value).replace('.', this.decimalMark()) : String(cell.value);
3579
4554
  }
3580
- moveTo(rawTarget, extend) {
4555
+ moveTo(rawTarget, extend, keepExtras = false) {
3581
4556
  // Arithmetic runs on plain coordinates; landing on a covered cell then resolves to the
3582
4557
  // block that owns it, so the cursor is never on something that is not drawn.
3583
4558
  const target = anchorOf(this.spanMap(), rawTarget);
@@ -3587,11 +4562,17 @@ class HubSpreadsheetComponent {
3587
4562
  else {
3588
4563
  this.anchor.set(null);
3589
4564
  }
4565
+ // Any ordinary move starts the selection again, which is what every spreadsheet does: the
4566
+ // rectangles held with `Ctrl` last until the reader clicks or walks somewhere plainly.
4567
+ if (!keepExtras) {
4568
+ this.extraRanges.set([]);
4569
+ }
3590
4570
  this.active.set(target);
3591
4571
  this.announceSelection();
3592
4572
  this.focusActive();
3593
4573
  }
3594
4574
  selectRange(range) {
4575
+ this.extraRanges.set([]);
3595
4576
  this.anchor.set({ row: range.top, col: range.left });
3596
4577
  this.active.set({ row: range.bottom, col: range.right });
3597
4578
  this.announceSelection();
@@ -3657,6 +4638,12 @@ class HubSpreadsheetComponent {
3657
4638
  * missing, which is also when the sheet is drawing every column anyway.
3658
4639
  */
3659
4640
  scrollColumnIntoView(viewport, col) {
4641
+ // Right to left, the scroll offset runs the other way and the arithmetic below would nudge
4642
+ // the sheet in the wrong direction. The browser's own focus scroll brings the drawn cell
4643
+ // into view; reaching a column that is not drawn yet is left alone rather than guessed at.
4644
+ if (this.rtl()) {
4645
+ return;
4646
+ }
3660
4647
  const widths = this.measuredColumnWidths();
3661
4648
  const frozen = Math.min(this.frozenColumns(), this.columns().length);
3662
4649
  if (widths.length !== this.columns().length || widths.some((width) => !(width > 0))) {
@@ -3698,13 +4685,14 @@ class HubSpreadsheetComponent {
3698
4685
  }
3699
4686
  announceSelection() {
3700
4687
  this.selectionChange.emit(this.range());
4688
+ this.selectionRangesChange.emit(this.selection());
3701
4689
  }
3702
4690
  announceCleared() {
3703
- const range = this.range();
3704
- if (!range || this.readonly()) {
4691
+ const ranges = this.selection();
4692
+ if (!ranges.length || this.readonly()) {
3705
4693
  return;
3706
4694
  }
3707
- const cells = gridRangeCells(range)
4695
+ const cells = gridSelectionCells(ranges)
3708
4696
  .filter(({ row, col }) => this.canEdit(row, col) && this.grid()[row][col].value !== null)
3709
4697
  .map((coords) => this.refAt(coords));
3710
4698
  if (cells.length) {
@@ -3712,7 +4700,7 @@ class HubSpreadsheetComponent {
3712
4700
  }
3713
4701
  }
3714
4702
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubSpreadsheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3715
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubSpreadsheetComponent, isStandalone: true, selector: "hub-spreadsheet", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: true, transformFunction: null }, states: { classPropertyName: "states", publicName: "states", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, decimalMark: { classPropertyName: "decimalMark", publicName: "decimalMark", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, frozenColumns: { classPropertyName: "frozenColumns", publicName: "frozenColumns", isSignal: true, isRequired: false, transformFunction: null }, frozenRows: { classPropertyName: "frozenRows", publicName: "frozenRows", isSignal: true, isRequired: false, transformFunction: null }, spans: { classPropertyName: "spans", publicName: "spans", isSignal: true, isRequired: false, transformFunction: null }, structure: { classPropertyName: "structure", publicName: "structure", isSignal: true, isRequired: false, transformFunction: null }, retiredColumnKeys: { classPropertyName: "retiredColumnKeys", publicName: "retiredColumnKeys", isSignal: true, isRequired: false, transformFunction: null }, fillHandle: { classPropertyName: "fillHandle", publicName: "fillHandle", isSignal: true, isRequired: false, transformFunction: null }, canUndo: { classPropertyName: "canUndo", publicName: "canUndo", isSignal: true, isRequired: false, transformFunction: null }, canRedo: { classPropertyName: "canRedo", publicName: "canRedo", isSignal: true, isRequired: false, transformFunction: null }, resizableColumns: { classPropertyName: "resizableColumns", publicName: "resizableColumns", isSignal: true, isRequired: false, transformFunction: null }, reorderableColumns: { classPropertyName: "reorderableColumns", publicName: "reorderableColumns", isSignal: true, isRequired: false, transformFunction: null }, columnWidths: { classPropertyName: "columnWidths", publicName: "columnWidths", isSignal: true, isRequired: false, transformFunction: null }, minColumnWidth: { classPropertyName: "minColumnWidth", publicName: "minColumnWidth", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null }, mergeable: { classPropertyName: "mergeable", publicName: "mergeable", isSignal: true, isRequired: false, transformFunction: null }, formulas: { classPropertyName: "formulas", publicName: "formulas", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, editOn: { classPropertyName: "editOn", publicName: "editOn", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { 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" }, host: { attributes: { "ngSkipHydration": "true" }, listeners: { "document:mouseup": "endDrag()", "document:mousemove": "onPointerMove($event)", "document:mousedown": "closeMenu()", "document:keydown.escape": "closeMenu()" }, properties: { "class.hub-spreadsheet--readonly": "readonly()", "class.hub-spreadsheet--editing": "editing()", "class.hub-spreadsheet--virtual": "virtual()" }, classAttribute: "hub-spreadsheet" }, providers: [{ provide: HUB_TRANSLATION_PREFIX, useValue: 'HUBUI.SPREADSHEET' }], queries: [{ propertyName: "customEditors", predicate: HubSpreadsheetEditorDirective, isSignal: true }, { propertyName: "customCells", predicate: HubSpreadsheetCellDirective, isSignal: true }], viewQueries: [{ propertyName: "viewport", first: true, predicate: ["viewport"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n\tThe off-screen field exists for one reason: a browser only dispatches `paste` to something\n\teditable. The paste shortcut parks the focus here for the length of the event and the cell\n\ttakes it straight back, so the reader never notices and the grid keeps its own focus model.\n-->\n<textarea class=\"hub-spreadsheet__sink\" tabindex=\"-1\" aria-hidden=\"true\" readonly (paste)=\"onPaste($event)\"></textarea>\n\n<div class=\"hub-spreadsheet__viewport\" #viewport (scroll)=\"onViewportScroll()\">\n\t<table\n\t\tclass=\"hub-spreadsheet__table\"\n\t\trole=\"grid\"\n\t\t[attr.aria-rowcount]=\"bounds().rows\"\n\t\t[attr.aria-colcount]=\"bounds().cols\"\n\t\t[attr.aria-readonly]=\"readonly() || null\"\n\t\t(keydown)=\"onKeydown($event)\"\n\t\t(copy)=\"onCopy($event, false)\"\n\t\t(cut)=\"onCopy($event, true)\"\n\t>\n\t\t<thead class=\"hub-spreadsheet__head\">\n\t\t\t<tr role=\"row\">\n\t\t\t\t<!-- Holds the width of the columns that are not drawn, so the sheet is as wide as it\n\t\t\t\tsays it is and the ones that are drawn land where they belong. -->\n\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t<!-- A minimum, not a width: in a table that shares its space out, a width is a\n\t\t\t\t\tsuggestion the algorithm is free to ignore, and an empty cell asked for three\n\t\t\t\t\tthousand pixels came out two wide. -->\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\n\t\t\t\t@for (columnIndex of drawnColumns(); track trackColumn(columnIndex, columns()[columnIndex])) {\n\t\t\t\t\t@let column = columns()[columnIndex];\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__header\"\n\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen]=\"isFrozenColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen-edge]=\"isColumnEdge(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(columnIndex)\"\n\t\t\t\t\t\t[style.min-width]=\"column.minWidth\"\n\t\t\t\t\t\t[style.width.px]=\"widthOf(columnIndex)\"\n\t\t\t\t\t\t[attr.data-header]=\"columnIndex\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end-of-sheet]=\"columnIndex === bounds().cols - 1\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--moving]=\"isMovingColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--move-target]=\"isMoveTarget(columnIndex)\"\n\t\t\t\t\t\t(mousedown)=\"onHeaderMouseDown($event, columnIndex)\"\n\t\t\t\t\t\t(mouseenter)=\"onHeaderMouseEnter(columnIndex)\"\n\t\t\t\t\t\t[attr.aria-colindex]=\"columnIndex + 1\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<span [class.hub-spreadsheet__header-text--hidden]=\"column.headerHidden\">\n\t\t\t\t\t\t\t{{ column.header }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<!-- The name a formula calls this column, which is not the name on the header.\n\t\t\t\t\tShown only while one is being written, and clickable, because the alternative is\n\t\t\t\t\tknowing it by heart. -->\n\t\t\t\t\t\t@if (writingFormula()) {\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__alias\"\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t\t\t(mousedown)=\"\n\t\t\t\t\t\t\t\t\t$event.preventDefault();\n\t\t\t\t\t\t\t\t\tuseSuggestion({ label: column.key, kind: 'column', insert: '[' + column.key + ']' })\n\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__alias-letter\">{{ columnLetter(columnIndex) }}</span>\n\t\t\t\t\t\t\t\t{{ column.key }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@if (column.subheader) {\n\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__subheader\">{{ column.subheader }}</small>\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@if (resizableColumns()) {\n\t\t\t\t\t\t\t<!-- Sits inside the header's trailing edge. Hidden from assistive technology:\n\t\t\t\t\t\t\ta width is a matter of taste, and a screen reader has no use for a drag\n\t\t\t\t\t\t\ttarget it cannot aim at. -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__resize-grip\"\n\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onResizeStart($event, columnIndex)\"\n\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t</th>\n\t\t\t\t}\n\n\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\t\t\t</tr>\n\t\t</thead>\n\n\t\t<tbody class=\"hub-spreadsheet__body\">\n\t\t\t<!-- The rows above the window are not in the document; this holds their height, so the\n\t\t\tscrollbar still says how long the sheet is and the rows that are drawn land where they\n\t\t\tbelong. Hidden from assistive technology, which reads `aria-rowindex` instead. -->\n\t\t\t@if (rowWindow().before > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().before\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\n\t\t\t@for (rowIndex of drawnRows(); track trackRow(rowIndex)) {\n\t\t\t\t<tr\n\t\t\t\t\tclass=\"hub-spreadsheet__row\"\n\t\t\t\t\trole=\"row\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t[attr.aria-rowindex]=\"rowIndex + 1\"\n\t\t\t\t>\n\t\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\n\t\t\t\t\t@for (colIndex of drawnColumns(); track trackColumn(colIndex, columns()[colIndex])) {\n\t\t\t\t\t\t@let column = columns()[colIndex];\n\t\t\t\t\t\t<!-- A cell swallowed by a merge is not rendered at all: the anchor's rowspan and\n\t\t\t\t\t\tcolspan already occupy its place, and drawing it would push the row out of shape. -->\n\t\t\t\t\t\t@if (!isHidden(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t@let cell = grid()[rowIndex][colIndex];\n\t\t\t\t\t\t\t@let state = stateOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let error = errorOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let editable = canEdit(rowIndex, colIndex);\n\t\t\t\t\t\t\t<!-- Worked out once per cell, and only for the one that is open: the config a\n\t\t\t\t\t\tregistered control adapter is asked for, or null when there is nothing to host. -->\n\t\t\t\t\t\t\t@let hosted = editing() && isActive(rowIndex, colIndex) ? hostedControl(rowIndex, colIndex) : null;\n\n\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__cell\"\n\t\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\t\t[attr.data-cell]=\"rowIndex + '-' + colIndex\"\n\t\t\t\t\t\t\t\t[attr.tabindex]=\"tabIndexOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colindex]=\"colIndex + 1\"\n\t\t\t\t\t\t\t\t[attr.rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-readonly]=\"!editable || null\"\n\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"(isActive(rowIndex, colIndex) && invalid()) || error ? true : null\"\n\t\t\t\t\t\t\t\t[attr.aria-busy]=\"state === 'saving' || null\"\n\t\t\t\t\t\t\t\t[attr.title]=\"error ?? cell.title\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--active]=\"isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--editing]=\"editing() && isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end-of-sheet]=\"reachesEnd(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--filling]=\"isFillPreview(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--readonly]=\"!editable\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--muted]=\"cell.muted\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col]=\"isFrozenColumn(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col-edge]=\"isColumnEdge(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(colIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-block-start.px]=\"rowOffsetOf(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--invalid]=\"\n\t\t\t\t\t\t\t\t\t(isActive(rowIndex, colIndex) && invalid()) ||\n\t\t\t\t\t\t\t\t\t!!error ||\n\t\t\t\t\t\t\t\t\thasFormulaError(rowIndex, colIndex)\n\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t\t[attr.data-state]=\"state\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onMouseDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseenter)=\"onMouseEnter(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseup)=\"onMouseUp($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(dblclick)=\"onDoubleClick()\"\n\t\t\t\t\t\t\t\t(contextmenu)=\"onContextMenu($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t@if (editing() && isActive(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t@if (editorFor(colIndex); as custom) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host supplied an editor for this column. It reports a value through\n\t\t\t\t\t\t\t\t\t`commit`, or walks away through `cancel`; until it does one of the two the\n\t\t\t\t\t\t\t\t\teditor stays open, so a picker may take as many clicks as it needs. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"custom.template\"\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"editorContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (hosted) {\n\t\t\t\t\t\t\t\t\t\t<!-- A control adapter is registered and this column is a list, so the cell\n\t\t\t\t\t\t\t\t\topens with whatever the project wired instead of the native list. The\n\t\t\t\t\t\t\t\t\twrapper is the supplied editor's, because from the sheet's side this is\n\t\t\t\t\t\t\t\t\tone: something it did not build, filling the cell. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container [hubSpreadsheetControl]=\"hosted\" />\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (column.kind === 'select') {\n\t\t\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor hub-spreadsheet__editor--select\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"commitFromEditor($any($event.target).value || null)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<option value=\"\" [selected]=\"cell.value === null\"></option>\n\t\t\t\t\t\t\t\t\t\t\t@for (option of column.options ?? []; track option.value) {\n\t\t\t\t\t\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\t\t\t\t\t\t[value]=\"option.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[selected]=\"isChosen(option, column)\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"option.disabled && option.value !== cell.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t@if (suggesting()) {\n\t\t\t\t\t\t\t\t\t\t\t<!-- What can go into the formula, because the aliases it needs are nowhere\n\t\t\t\t\t\t\t\t\t\telse on screen. Hidden from assistive technology: the field owns the\n\t\t\t\t\t\t\t\t\t\tconversation, and a list that announced itself over every keystroke\n\t\t\t\t\t\t\t\t\t\twould talk across it. -->\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__suggestions\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t@for (suggestion of suggestions(); track suggestion.label; let index = $index) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__suggestion\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__suggestion--on]=\"index === suggestionIndex()\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"$event.preventDefault(); useSuggestion(suggestion)\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__suggestion-name\">{{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsuggestion.label\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@if (suggestion.hint) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__suggestion-hint\">{{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsuggestion.hint\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor\"\n\t\t\t\t\t\t\t\t\t\t\t[type]=\"column.kind === 'date' ? 'date' : 'text'\"\n\t\t\t\t\t\t\t\t\t\t\t[value]=\"draft()\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"invalid() || null\"\n\t\t\t\t\t\t\t\t\t\t\t(input)=\"onDraftInput($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t@if (column.kind === 'boolean') {\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__check\"\n\t\t\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\t\t\t[checked]=\"isOn(cell)\"\n\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"!editable\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"toggleBoolean(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else if (cellTemplateFor(colIndex); as own) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host draws this column's cells. The value still travels to the\n\t\t\t\t\t\t\t\t\tclipboard rather than the drawing, and the cell is still what the\n\t\t\t\t\t\t\t\t\tkeyboard is on: the template is inside it, not instead of it. -->\n\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"own.template\"\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"cellContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t<!-- The row's number, so a coordinate can be read off the sheet instead of\n\t\t\t\t\t\t\t\t\tcounted. Only while a formula is being written, and only in the first\n\t\t\t\t\t\t\t\t\tcolumn drawn, which is where a spreadsheet puts it. -->\n\t\t\t\t\t\t\t\t\t\t@if (writingFormula() && colIndex === drawnColumns()[0]) {\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__rownum\" aria-hidden=\"true\">{{ rowIndex + 1 }}</span>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__value\">{{ displayOf(cell, column) }}</span>\n\n\t\t\t\t\t\t\t\t\t\t@if (column.placeholder && !displayOf(cell, column)) {\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__placeholder\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t{{ column.placeholder }}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<!-- Says the cell opens onto a list or a calendar rather than a field, and\n\t\t\t\t\t\t\t\t\topens it on one click \u2014 as a spreadsheet drops the list of a cell that\n\t\t\t\t\t\t\t\t\thas one. Shown on the cell the reader is on and on the one under the\n\t\t\t\t\t\t\t\t\tpointer. Hidden from assistive technology: the keyboard opens the same\n\t\t\t\t\t\t\t\t\tcell with Alt and the down arrow, and reads the editor itself. -->\n\t\t\t\t\t\t\t\t\t\t@if (editable && affordanceOf(column, colIndex); as affordance) {\n\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__affordance\"\n\t\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__affordance--date]=\"affordance === 'date'\"\n\t\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onAffordanceDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (cell.secondary) {\n\t\t\t\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__secondary\">{{ cell.secondary }}</small>\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (hasFillHandle(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t\t<!-- Drawn inside the corner cell so it travels with the selection. Hidden from\n\t\t\t\t\t\t\t\t\tassistive technology: filling is available from the keyboard through the\n\t\t\t\t\t\t\t\t\tclipboard, and a screen reader has no use for a drag target. -->\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__fill-handle\"\n\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onFillHandleDown($event)\"\n\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\t\t\t\t</tr>\n\t\t\t} @empty {\n\t\t\t\t<tr class=\"hub-spreadsheet__row hub-spreadsheet__row--empty\" role=\"row\">\n\t\t\t\t\t<td class=\"hub-spreadsheet__empty\" role=\"gridcell\" [attr.colspan]=\"bounds().cols || 1\">\n\t\t\t\t\t\t{{ emptyText() }}\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t\t@if (rowWindow().after > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().after\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t</tbody>\n\t</table>\n</div>\n\n@if (menuAt(); as at) {\n\t<!-- Positioned against the host, which is the sheet's own corner, so the menu travels with a\n\tscrolled page and never has to be repositioned on scroll. -->\n\t<div\n\t\tclass=\"hub-spreadsheet__menu\"\n\t\trole=\"menu\"\n\t\t[attr.aria-label]=\"'ROW_ACTIONS' | translate\"\n\t\t[style.inset-inline-start.px]=\"at.x\"\n\t\t[style.inset-block-start.px]=\"at.y\"\n\t\t(mousedown)=\"$event.stopPropagation()\"\n\t>\n\t\t@for (entry of menuEntries(); track entry.id) {\n\t\t\t<button class=\"hub-spreadsheet__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"runMenuEntry(entry.id)\">\n\t\t\t\t{{ entry.label | translate: { count: entry.count } }}\n\t\t\t</button>\n\t\t}\n\t</div>\n}\n", styles: ["@charset \"UTF-8\";:host{position:relative;--hub-spreadsheet-bg: var(--hub-table-bg, var(--hub-sys-color-surface, #fff));--hub-spreadsheet-color: var(--hub-table-color, var(--hub-sys-color-text, #212529));--hub-spreadsheet-border-color: var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6));--hub-spreadsheet-border-width: var(--hub-table-border-width, 1px);--hub-spreadsheet-border-radius: var(--hub-table-border-radius, var(--hub-sys-radius-lg, .5rem));--hub-spreadsheet-cell-padding-x: var(--hub-table-cell-padding-x, .6rem);--hub-spreadsheet-cell-padding-y: var(--hub-table-cell-padding-y, .4rem);--hub-spreadsheet-cell-line-height: 1.5em;--hub-spreadsheet-header-bg: var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa));--hub-spreadsheet-header-color: var(--hub-sys-text-muted, #6a737b);--hub-spreadsheet-header-font-size: .8125rem;--hub-spreadsheet-header-font-weight: 600;--hub-spreadsheet-cursor-color: var(--hub-sys-color-primary, #0d6efd);--hub-spreadsheet-cursor-width: 2px;--hub-spreadsheet-selection-bg: color-mix(in srgb, var(--hub-spreadsheet-cursor-color) 12%, transparent);--hub-spreadsheet-readonly-bg: color-mix(in srgb, var(--hub-spreadsheet-header-bg) 55%, var(--hub-spreadsheet-bg));--hub-spreadsheet-affordance-color: var(--hub-select-arrow-color, var(--hub-sys-text-muted, #6a737b));--hub-spreadsheet-affordance-size: var(--hub-select-arrow-size, 5px);--hub-spreadsheet-affordance-gap: var(--hub-select-arrow-gap, .5rem);--hub-spreadsheet-affordance-field-border: var(--hub-input-border-width, 1px);--hub-spreadsheet-placeholder-color: var(--hub-sys-text-muted, #6a737b);--hub-spreadsheet-placeholder-opacity: .65;--hub-spreadsheet-placeholder-transition: var(--hub-sys-transition-fast, all .15s ease-in-out);--hub-spreadsheet-readonly-color: var(--hub-sys-text-muted, #6a737b);--hub-spreadsheet-editor-bg: var(--hub-spreadsheet-bg);--hub-spreadsheet-editor-color: var(--hub-spreadsheet-color);--hub-spreadsheet-editor-field-bg: transparent;--hub-spreadsheet-editor-field-border-color: transparent;--hub-spreadsheet-editor-field-border-radius: 0;--hub-spreadsheet-editor-field-focus-shadow: none;--hub-spreadsheet-state-pending-color: var(--hub-sys-color-warning, #cc8a00);--hub-spreadsheet-state-saving-color: var(--hub-sys-color-info, #0a7ea4);--hub-spreadsheet-state-saved-color: var(--hub-sys-color-success, #1a7f37);--hub-spreadsheet-state-error-color: var(--hub-sys-color-danger, #b3261e);--hub-spreadsheet-state-conflict-color: var(--hub-sys-color-danger, #b3261e);--hub-spreadsheet-state-bar-width: 3px;--hub-spreadsheet-suggestions-bg: var(--hub-spreadsheet-menu-bg);--hub-spreadsheet-suggestions-border-color: var(--hub-spreadsheet-menu-border-color);--hub-spreadsheet-suggestion-on-bg: var(--hub-spreadsheet-selection-bg);--hub-spreadsheet-alias-bg: color-mix(in srgb, var(--hub-spreadsheet-cursor-color) 12%, transparent);--hub-spreadsheet-alias-color: var(--hub-spreadsheet-cursor-color);--hub-spreadsheet-menu-bg: var(--hub-sys-color-surface, #fff);--hub-spreadsheet-menu-color: var(--hub-spreadsheet-color);--hub-spreadsheet-menu-border-color: var(--hub-spreadsheet-border-color);--hub-spreadsheet-menu-radius: var(--hub-sys-radius-sm, .25rem);--hub-spreadsheet-menu-shadow: 0 6px 20px rgb(0 0 0 / 18%);--hub-spreadsheet-menu-item-padding-x: .85rem;--hub-spreadsheet-menu-item-padding-y: .4rem;--hub-spreadsheet-menu-item-bg-hover: var(--hub-spreadsheet-selection-bg);--hub-spreadsheet-resize-grip-width: 5px;--hub-spreadsheet-resize-grip-color: var(--hub-spreadsheet-cursor-color);--hub-spreadsheet-moving-opacity: .5;--hub-spreadsheet-fill-handle-size: 7px;--hub-spreadsheet-fill-handle-color: var(--hub-spreadsheet-cursor-color);--hub-spreadsheet-fill-preview-bg: color-mix(in srgb, var(--hub-spreadsheet-cursor-color) 6%, transparent);--hub-spreadsheet-invalid-color: var(--hub-sys-color-danger, #b3261e);--hub-spreadsheet-invalid-border-width: 1px;--hub-spreadsheet-invalid-bg: color-mix(in srgb, var(--hub-spreadsheet-invalid-color) 10%, transparent);--hub-spreadsheet-max-block-size: var(--hub-table-container-max-block-size, none);--hub-spreadsheet-frozen-divider-color: var(--hub-spreadsheet-border-color, var(--hub-sys-border-color-default, #dee2e6));--hub-spreadsheet-frozen-divider-width: 2px;display:block;color:var(--hub-spreadsheet-color)}.hub-spreadsheet__sink{position:absolute;inline-size:1px;block-size:1px;padding:0;border:0;opacity:0;pointer-events:none;inset-block-start:0;inset-inline-start:0}:host(.hub-spreadsheet--virtual){--hub-spreadsheet-max-block-size: var(--hub-table-container-max-block-size, 24rem)}.hub-spreadsheet__viewport{position:relative;overflow:auto;max-block-size:var(--hub-spreadsheet-max-block-size);border:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-border-color);border-radius:var(--hub-spreadsheet-border-radius);background:var(--hub-spreadsheet-bg)}.hub-spreadsheet__table{inline-size:100%;border-collapse:separate;border-spacing:0;font-variant-numeric:tabular-nums}.hub-spreadsheet__header,.hub-spreadsheet__cell{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-border-width);--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-border-width);padding:var(--hub-spreadsheet-cell-padding-y) var(--hub-spreadsheet-cell-padding-x);border-inline-end:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-border-color);border-block-end:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-border-color);background:var(--hub-spreadsheet-bg);white-space:nowrap;text-align:start}.hub-spreadsheet__header--end-of-sheet,.hub-spreadsheet__cell--end-of-sheet{--hub-spreadsheet-cell-edge-inline: 0px;border-inline-end:0}.hub-spreadsheet__header{position:sticky;isolation:isolate;inset-block-start:0;z-index:2;background:var(--hub-spreadsheet-header-bg);color:var(--hub-spreadsheet-header-color);font-size:var(--hub-spreadsheet-header-font-size);font-weight:var(--hub-spreadsheet-header-font-weight)}.hub-spreadsheet__header--moving{opacity:var(--hub-spreadsheet-moving-opacity)}.hub-spreadsheet__header--move-target{outline:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);outline-offset:calc(var(--hub-spreadsheet-cursor-width) * -1)}.hub-spreadsheet__header--frozen{inset-inline-start:0;z-index:4}.hub-spreadsheet__header--frozen-edge{border-inline-end:var(--hub-spreadsheet-frozen-divider-width) solid var(--hub-spreadsheet-frozen-divider-color)}.hub-spreadsheet__header--end{text-align:end}.hub-spreadsheet__header--center{text-align:center}.hub-spreadsheet__header-text--hidden{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.hub-spreadsheet__subheader{display:block;font-weight:400;opacity:.8}.hub-spreadsheet__cell{position:relative;cursor:cell;-webkit-user-select:none;user-select:none}.hub-spreadsheet__cell--end{text-align:end}.hub-spreadsheet__cell--center{text-align:center}.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--frozen-row{position:sticky;z-index:1}.hub-spreadsheet__cell--frozen-col{inset-inline-start:0}.hub-spreadsheet__cell--frozen-col.hub-spreadsheet__cell--frozen-row{z-index:2}.hub-spreadsheet__cell--frozen-col-edge{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-frozen-divider-width);border-inline-end:var(--hub-spreadsheet-frozen-divider-width) solid var(--hub-spreadsheet-frozen-divider-color)}.hub-spreadsheet__cell--frozen-row-edge{--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-frozen-divider-width);border-block-end:var(--hub-spreadsheet-frozen-divider-width) solid var(--hub-spreadsheet-frozen-divider-color)}.hub-spreadsheet__cell--filling{background-image:linear-gradient(var(--hub-spreadsheet-fill-preview-bg),var(--hub-spreadsheet-fill-preview-bg))}.hub-spreadsheet__cell--selected{background-image:linear-gradient(var(--hub-spreadsheet-selection-bg),var(--hub-spreadsheet-selection-bg))}.hub-spreadsheet__cell--readonly{background-color:var(--hub-spreadsheet-readonly-bg);color:var(--hub-spreadsheet-readonly-color);cursor:default}.hub-spreadsheet__cell--muted{color:var(--hub-spreadsheet-readonly-color)}.hub-spreadsheet__cell--invalid{background-image:linear-gradient(var(--hub-spreadsheet-invalid-bg),var(--hub-spreadsheet-invalid-bg));color:var(--hub-spreadsheet-invalid-color);box-shadow:inset 0 0 0 var(--hub-spreadsheet-invalid-border-width) var(--hub-spreadsheet-invalid-color)}.hub-spreadsheet__cell--active{outline:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);outline-offset:calc(var(--hub-spreadsheet-cursor-width) * -1);z-index:2}.hub-spreadsheet__cell--editing{block-size:calc(var(--hub-spreadsheet-cell-line-height) + var(--hub-spreadsheet-cell-padding-y) * 2 + var(--hub-spreadsheet-cell-edge-block))}.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-row{z-index:3}.hub-spreadsheet__cell[data-state]:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;inline-size:var(--hub-spreadsheet-state-bar-width)}.hub-spreadsheet__cell[data-state=pending]:before{background:var(--hub-spreadsheet-state-pending-color)}.hub-spreadsheet__cell[data-state=saving]:before{background:var(--hub-spreadsheet-state-saving-color)}.hub-spreadsheet__cell[data-state=saved]:before{background:var(--hub-spreadsheet-state-saved-color)}.hub-spreadsheet__cell[data-state=error]:before{background:var(--hub-spreadsheet-state-error-color)}.hub-spreadsheet__cell[data-state=conflict]:before{background:repeating-linear-gradient(45deg,var(--hub-spreadsheet-state-conflict-color) 0 3px,transparent 3px 6px)}.hub-spreadsheet__editor--select{padding-inline:calc(var(--hub-spreadsheet-cell-padding-x) - 2px);appearance:auto}.hub-spreadsheet__custom-editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);display:grid;background:var(--hub-spreadsheet-editor-bg);outline:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);outline-offset:calc(var(--hub-spreadsheet-cursor-width) * -1);z-index:3;--hub-input-bg: var(--hub-spreadsheet-editor-field-bg);--hub-input-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-input-border-radius: var(--hub-spreadsheet-editor-field-border-radius);--hub-input-focus-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-input-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow);--hub-input-padding-x: var(--hub-spreadsheet-cell-padding-x);--hub-select-bg: var(--hub-spreadsheet-editor-field-bg);--hub-select-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-select-border-radius: var(--hub-spreadsheet-editor-field-border-radius);--hub-select-focus-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-select-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow);--hub-select-padding-x: var(--hub-spreadsheet-cell-padding-x)}.hub-spreadsheet__spacer>td{padding:0;border:0;background:var(--hub-spreadsheet-bg)}.hub-spreadsheet__affordance{position:absolute;inset-block:0;inset-inline-end:calc(var(--hub-spreadsheet-affordance-field-border) - var(--hub-spreadsheet-cell-edge-inline));display:flex;align-items:center;padding-inline:var(--hub-spreadsheet-affordance-gap);cursor:pointer;opacity:0}.hub-spreadsheet__cell:hover>.hub-spreadsheet__affordance,.hub-spreadsheet__cell--active>.hub-spreadsheet__affordance{opacity:1}.hub-spreadsheet__affordance:after{content:\"\";inline-size:0;block-size:0;border-style:solid;border-width:var(--hub-spreadsheet-affordance-size) var(--hub-spreadsheet-affordance-size) calc(var(--hub-spreadsheet-affordance-size) / 2);border-color:var(--hub-spreadsheet-affordance-color) transparent transparent}.hub-spreadsheet__affordance--date:after{inline-size:calc(var(--hub-spreadsheet-affordance-size) * 2);block-size:calc(var(--hub-spreadsheet-affordance-size) * 1.8);border:1px solid var(--hub-spreadsheet-affordance-color);border-block-start-width:3px;border-radius:2px}.hub-spreadsheet__placeholder{position:absolute;inset-block:0;inset-inline:var(--hub-spreadsheet-cell-padding-x);display:flex;align-items:center;color:var(--hub-spreadsheet-placeholder-color);opacity:0;pointer-events:none;transition:var(--hub-spreadsheet-placeholder-transition)}.hub-spreadsheet__cell:hover>.hub-spreadsheet__placeholder{opacity:var(--hub-spreadsheet-placeholder-opacity)}.hub-spreadsheet__check{inline-size:1em;block-size:1em;accent-color:var(--hub-spreadsheet-cursor-color);cursor:pointer}.hub-spreadsheet__check:disabled{cursor:default}.hub-spreadsheet__resize-grip{position:absolute;inset-block:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-resize-grip-width);cursor:col-resize;background:transparent;z-index:5}.hub-spreadsheet__resize-grip:hover,.hub-spreadsheet__resize-grip:active{background:var(--hub-spreadsheet-resize-grip-color)}.hub-spreadsheet__fill-handle{position:absolute;inset-block-end:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-fill-handle-size);block-size:var(--hub-spreadsheet-fill-handle-size);background:var(--hub-spreadsheet-fill-handle-color);cursor:crosshair;z-index:4}.hub-spreadsheet__secondary{display:block;opacity:.75}.hub-spreadsheet__editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);padding:var(--hub-spreadsheet-cell-padding-y) var(--hub-spreadsheet-cell-padding-x);border:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);border-radius:0;background:var(--hub-spreadsheet-editor-bg);color:var(--hub-spreadsheet-editor-color);font:inherit;font-variant-numeric:tabular-nums;text-align:inherit;outline:none;z-index:2}.hub-spreadsheet__editor[aria-invalid=true]{border-color:var(--hub-spreadsheet-invalid-color)}.hub-spreadsheet__empty{padding-block:calc(var(--hub-spreadsheet-cell-padding-y) * 4);color:var(--hub-spreadsheet-readonly-color);text-align:center;cursor:default}.hub-spreadsheet__menu{position:absolute;z-index:10;min-inline-size:12rem;padding-block:.25rem;border:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-menu-border-color);border-radius:var(--hub-spreadsheet-menu-radius);background:var(--hub-spreadsheet-menu-bg);color:var(--hub-spreadsheet-menu-color);box-shadow:var(--hub-spreadsheet-menu-shadow)}.hub-spreadsheet__menu-item{display:block;inline-size:100%;padding:var(--hub-spreadsheet-menu-item-padding-y) var(--hub-spreadsheet-menu-item-padding-x);border:0;background:transparent;color:inherit;font:inherit;text-align:start;white-space:nowrap;cursor:pointer}.hub-spreadsheet__menu-item:hover,.hub-spreadsheet__menu-item:focus-visible{background:var(--hub-spreadsheet-menu-item-bg-hover)}.hub-spreadsheet__suggestions{position:absolute;inset-block-start:100%;inset-inline-start:0;z-index:12;display:flex;flex-direction:column;min-inline-size:14rem;max-block-size:14rem;overflow-y:auto;border:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-suggestions-border-color);border-radius:var(--hub-spreadsheet-border-radius);background:var(--hub-spreadsheet-suggestions-bg);box-shadow:var(--hub-spreadsheet-menu-shadow)}.hub-spreadsheet__suggestion{display:flex;gap:.5rem;align-items:baseline;justify-content:space-between;padding:var(--hub-spreadsheet-cell-padding-y) var(--hub-spreadsheet-cell-padding-x);border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.hub-spreadsheet__suggestion:hover,.hub-spreadsheet__suggestion--on{background:var(--hub-spreadsheet-suggestion-on-bg)}.hub-spreadsheet__suggestion-name{font-variant-numeric:tabular-nums}.hub-spreadsheet__suggestion-hint{color:var(--hub-spreadsheet-readonly-color);font-size:var(--hub-spreadsheet-header-font-size)}.hub-spreadsheet__alias{margin-inline-start:.4rem;padding:0 .3rem;border:0;border-radius:.25rem;background:var(--hub-spreadsheet-alias-bg);color:var(--hub-spreadsheet-alias-color);font:inherit;font-size:var(--hub-spreadsheet-header-font-size);font-weight:400;cursor:pointer}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: HubSpreadsheetControlDirective, selector: "[hubSpreadsheetControl]", inputs: ["hubSpreadsheetControl"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4703
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubSpreadsheetComponent, isStandalone: true, selector: "hub-spreadsheet", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: true, transformFunction: null }, states: { classPropertyName: "states", publicName: "states", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, decimalMark: { classPropertyName: "decimalMark", publicName: "decimalMark", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, frozenColumns: { classPropertyName: "frozenColumns", publicName: "frozenColumns", isSignal: true, isRequired: false, transformFunction: null }, frozenRows: { classPropertyName: "frozenRows", publicName: "frozenRows", isSignal: true, isRequired: false, transformFunction: null }, spans: { classPropertyName: "spans", publicName: "spans", isSignal: true, isRequired: false, transformFunction: null }, structure: { classPropertyName: "structure", publicName: "structure", isSignal: true, isRequired: false, transformFunction: null }, retiredColumnKeys: { classPropertyName: "retiredColumnKeys", publicName: "retiredColumnKeys", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, disjointSelection: { classPropertyName: "disjointSelection", publicName: "disjointSelection", isSignal: true, isRequired: false, transformFunction: null }, fillHandle: { classPropertyName: "fillHandle", publicName: "fillHandle", isSignal: true, isRequired: false, transformFunction: null }, canUndo: { classPropertyName: "canUndo", publicName: "canUndo", isSignal: true, isRequired: false, transformFunction: null }, canRedo: { classPropertyName: "canRedo", publicName: "canRedo", isSignal: true, isRequired: false, transformFunction: null }, resizableColumns: { classPropertyName: "resizableColumns", publicName: "resizableColumns", isSignal: true, isRequired: false, transformFunction: null }, reorderableColumns: { classPropertyName: "reorderableColumns", publicName: "reorderableColumns", isSignal: true, isRequired: false, transformFunction: null }, columnWidths: { classPropertyName: "columnWidths", publicName: "columnWidths", isSignal: true, isRequired: false, transformFunction: null }, minColumnWidth: { classPropertyName: "minColumnWidth", publicName: "minColumnWidth", isSignal: true, isRequired: false, transformFunction: null }, contextMenu: { classPropertyName: "contextMenu", publicName: "contextMenu", isSignal: true, isRequired: false, transformFunction: null }, mergeable: { classPropertyName: "mergeable", publicName: "mergeable", isSignal: true, isRequired: false, transformFunction: null }, formulas: { classPropertyName: "formulas", publicName: "formulas", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, editOn: { classPropertyName: "editOn", publicName: "editOn", isSignal: true, isRequired: false, transformFunction: null }, expansion: { classPropertyName: "expansion", publicName: "expansion", isSignal: true, isRequired: false, transformFunction: null }, expandedRow: { classPropertyName: "expandedRow", publicName: "expandedRow", isSignal: true, isRequired: false, transformFunction: null }, expandToggle: { classPropertyName: "expandToggle", publicName: "expandToggle", isSignal: true, isRequired: false, transformFunction: null }, expandToggleLabel: { classPropertyName: "expandToggleLabel", publicName: "expandToggleLabel", isSignal: true, isRequired: false, transformFunction: null }, collapsedColumns: { classPropertyName: "collapsedColumns", publicName: "collapsedColumns", isSignal: true, isRequired: false, transformFunction: null }, columnToggleLabel: { classPropertyName: "columnToggleLabel", publicName: "columnToggleLabel", isSignal: true, isRequired: false, transformFunction: null }, rowLevel: { classPropertyName: "rowLevel", publicName: "rowLevel", isSignal: true, isRequired: false, transformFunction: null }, collapsedRows: { classPropertyName: "collapsedRows", publicName: "collapsedRows", isSignal: true, isRequired: false, transformFunction: null }, rowToggleLabel: { classPropertyName: "rowToggleLabel", publicName: "rowToggleLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { columnWidths: "columnWidthsChange", expandedRow: "expandedRowChange", collapsedColumns: "collapsedColumnsChange", collapsedRows: "collapsedRowsChange", commit: "commit", pasted: "pasted", opened: "opened", cleared: "cleared", selectionChange: "selectionChange", insertRequested: "insertRequested", deleteRequested: "deleteRequested", filled: "filled", undoRequested: "undoRequested", redoRequested: "redoRequested", columnMoved: "columnMoved", mergeRequested: "mergeRequested", selectionRangesChange: "selectionRangesChange", unmergeRequested: "unmergeRequested" }, host: { attributes: { "ngSkipHydration": "true" }, listeners: { "document:mouseup": "endDrag()", "document:mousemove": "onPointerMove($event)", "document:mousedown": "closeMenu()", "document:keydown.escape": "closeMenu()" }, properties: { "attr.dir": "forcedDirection()", "class.hub-spreadsheet--readonly": "readonly()", "class.hub-spreadsheet--editing": "editing()", "class.hub-spreadsheet--formula": "writingFormula()", "class.hub-spreadsheet--virtual": "virtual()", "class.hub-spreadsheet--rtl": "rtl()" }, classAttribute: "hub-spreadsheet" }, providers: [{ provide: HUB_TRANSLATION_PREFIX, useValue: 'HUBUI.SPREADSHEET' }], queries: [{ propertyName: "customEditors", predicate: HubSpreadsheetEditorDirective, isSignal: true }, { propertyName: "customCells", predicate: HubSpreadsheetCellDirective, isSignal: true }], viewQueries: [{ propertyName: "viewport", first: true, predicate: ["viewport"], descendants: true, isSignal: true }, { propertyName: "tokens", first: true, predicate: ["tokens"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n\tThe off-screen field exists for one reason: a browser only dispatches `paste` to something\n\teditable. The paste shortcut parks the focus here for the length of the event and the cell\n\ttakes it straight back, so the reader never notices and the grid keeps its own focus model.\n-->\n<textarea class=\"hub-spreadsheet__sink\" tabindex=\"-1\" aria-hidden=\"true\" readonly (paste)=\"onPaste($event)\"></textarea>\n\n<div class=\"hub-spreadsheet__viewport\" #viewport (scroll)=\"onViewportScroll()\">\n\t<table\n\t\tclass=\"hub-spreadsheet__table\"\n\t\trole=\"grid\"\n\t\t[attr.aria-rowcount]=\"bounds().rows\"\n\t\t[attr.aria-colcount]=\"bounds().cols\"\n\t\t[attr.aria-readonly]=\"readonly() || null\"\n\t\t(keydown)=\"onKeydown($event)\"\n\t\t(copy)=\"onCopy($event, false)\"\n\t\t(cut)=\"onCopy($event, true)\"\n\t>\n\t\t<thead class=\"hub-spreadsheet__head\">\n\t\t\t<tr role=\"row\">\n\t\t\t\t<!-- Holds the width of the columns that are not drawn, so the sheet is as wide as it\n\t\t\t\tsays it is and the ones that are drawn land where they belong. -->\n\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t<!-- A minimum, not a width: in a table that shares its space out, a width is a\n\t\t\t\t\tsuggestion the algorithm is free to ignore, and an empty cell asked for three\n\t\t\t\t\tthousand pixels came out two wide. -->\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\n\t\t\t\t@for (columnIndex of drawnColumns(); track trackColumn(columnIndex, columns()[columnIndex])) {\n\t\t\t\t\t@let column = columns()[columnIndex];\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__header\"\n\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen]=\"isFrozenColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen-edge]=\"isColumnEdge(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t[class.hub-spreadsheet__grouped]=\"groupedColumns().has(columnIndex)\"\n\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(columnIndex)\"\n\t\t\t\t\t\t[style.min-width]=\"column.minWidth\"\n\t\t\t\t\t\t[style.width.px]=\"widthOf(columnIndex)\"\n\t\t\t\t\t\t[attr.data-header]=\"columnIndex\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end-of-sheet]=\"columnIndex === bounds().cols - 1\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--moving]=\"isMovingColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--move-target]=\"isMoveTarget(columnIndex)\"\n\t\t\t\t\t\t(mousedown)=\"onHeaderMouseDown($event, columnIndex)\"\n\t\t\t\t\t\t(mouseenter)=\"onHeaderMouseEnter(columnIndex)\"\n\t\t\t\t\t\t[attr.aria-colindex]=\"columnIndex + 1\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<span [class.hub-spreadsheet__header-text--hidden]=\"column.headerHidden\">\n\t\t\t\t\t\t\t{{ column.header }}\n\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t@if (column.subheader) {\n\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__subheader\">{{ column.subheader }}</small>\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@if (resizableColumns()) {\n\t\t\t\t\t\t\t<!-- Sits inside the header's trailing edge. Hidden from assistive technology:\n\t\t\t\t\t\t\ta width is a matter of taste, and a screen reader has no use for a drag\n\t\t\t\t\t\t\ttarget it cannot aim at. -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__resize-grip\"\n\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onResizeStart($event, columnIndex)\"\n\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t</th>\n\t\t\t\t}\n\n\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\t\t\t</tr>\n\t\t</thead>\n\n\t\t<tbody class=\"hub-spreadsheet__body\">\n\t\t\t<!-- The rows above the window are not in the document; this holds their height, so the\n\t\t\tscrollbar still says how long the sheet is and the rows that are drawn land where they\n\t\t\tbelong. Hidden from assistive technology, which reads `aria-rowindex` instead. -->\n\t\t\t@if (rowWindow().before > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().before\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\n\t\t\t@for (rowIndex of drawnRows(); track trackRow(rowIndex)) {\n\t\t\t\t<tr\n\t\t\t\t\tclass=\"hub-spreadsheet__row\"\n\t\t\t\t\trole=\"row\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t[attr.aria-rowindex]=\"rowIndex + 1\"\n\t\t\t\t>\n\t\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\n\t\t\t\t\t@for (colIndex of drawnColumns(); track trackColumn(colIndex, columns()[colIndex])) {\n\t\t\t\t\t\t@let column = columns()[colIndex];\n\t\t\t\t\t\t<!-- A cell swallowed by a merge is not rendered at all: the anchor's rowspan and\n\t\t\t\t\t\tcolspan already occupy its place, and drawing it would push the row out of shape. -->\n\t\t\t\t\t\t@if (!isHidden(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t@let cell = grid()[rowIndex][colIndex];\n\t\t\t\t\t\t\t@let state = stateOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let error = errorOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let editable = canEdit(rowIndex, colIndex);\n\t\t\t\t\t\t\t<!-- Worked out once per cell, and only for the one that is open: the config a\n\t\t\t\t\t\tregistered control adapter is asked for, or null when there is nothing to host. -->\n\t\t\t\t\t\t\t@let hosted = editing() && isActive(rowIndex, colIndex) ? hostedControl(rowIndex, colIndex) : null;\n\n\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__cell\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__grouped]=\"groupedColumns().has(colIndex) || groupedRows().has(rowIndex)\"\n\t\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\t\t[attr.data-cell]=\"rowIndex + '-' + colIndex\"\n\t\t\t\t\t\t\t\t[attr.tabindex]=\"tabIndexOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colindex]=\"colIndex + 1\"\n\t\t\t\t\t\t\t\t[attr.rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-readonly]=\"!editable || null\"\n\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"(isActive(rowIndex, colIndex) && invalid()) || error ? true : null\"\n\t\t\t\t\t\t\t\t[attr.aria-busy]=\"state === 'saving' || null\"\n\t\t\t\t\t\t\t\t[attr.title]=\"error ?? cell.title\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--active]=\"isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--editing]=\"editing() && isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--pointed]=\"isPointed(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end-of-sheet]=\"reachesEnd(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--filling]=\"isFillPreview(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--readonly]=\"!editable\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--muted]=\"cell.muted\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col]=\"isFrozenColumn(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col-edge]=\"isColumnEdge(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(colIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-block-start.px]=\"rowOffsetOf(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--invalid]=\"\n\t\t\t\t\t\t\t\t\t(isActive(rowIndex, colIndex) && invalid()) ||\n\t\t\t\t\t\t\t\t\t!!error ||\n\t\t\t\t\t\t\t\t\thasFormulaError(rowIndex, colIndex)\n\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t\t[attr.data-state]=\"state\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onMouseDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseenter)=\"onMouseEnter(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseup)=\"onMouseUp($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(dblclick)=\"onDoubleClick()\"\n\t\t\t\t\t\t\t\t(contextmenu)=\"onContextMenu($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t@if (editing() && isActive(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t@if (editorFor(colIndex); as custom) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host supplied an editor for this column. It reports a value through\n\t\t\t\t\t\t\t\t\t`commit`, or walks away through `cancel`; until it does one of the two the\n\t\t\t\t\t\t\t\t\teditor stays open, so a picker may take as many clicks as it needs. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"custom.template\"\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"editorContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (hosted) {\n\t\t\t\t\t\t\t\t\t\t<!-- A control adapter is registered and this column is a list, so the cell\n\t\t\t\t\t\t\t\t\topens with whatever the project wired instead of the native list. The\n\t\t\t\t\t\t\t\t\twrapper is the supplied editor's, because from the sheet's side this is\n\t\t\t\t\t\t\t\t\tone: something it did not build, filling the cell. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container [hubSpreadsheetControl]=\"hosted\" />\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (column.kind === 'select') {\n\t\t\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor hub-spreadsheet__editor--select\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"commitFromEditor($any($event.target).value || null)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<option value=\"\" [selected]=\"cell.value === null\"></option>\n\t\t\t\t\t\t\t\t\t\t\t@for (option of column.options ?? []; track option.value) {\n\t\t\t\t\t\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\t\t\t\t\t\t[value]=\"option.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[selected]=\"isChosen(option, column)\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"option.disabled && option.value !== cell.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t@if (writingFormula()) {\n\t\t\t\t\t\t\t\t\t\t\t<!-- The draft again, in colour, drawn behind the field \u2014 which keeps the\n\t\t\t\t\t\t\t\t\t\ttext transparent so this is what the reader sees. Named by kind so a\n\t\t\t\t\t\t\t\t\t\tfunction, a column alias and a place are told apart at a glance. Hidden\n\t\t\t\t\t\t\t\t\t\tfrom assistive technology: it is a second copy of the field's own text. -->\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__tokens\" aria-hidden=\"true\" #tokens>\n\t\t\t\t\t\t\t\t\t\t\t\t@for (token of draftTokens(); track $index) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t[class]=\"'hub-spreadsheet__token hub-spreadsheet__token--' + token.kind\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>{{ token.text }}</span\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor\"\n\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__editor--coloured]=\"writingFormula()\"\n\t\t\t\t\t\t\t\t\t\t\t[type]=\"column.kind === 'date' ? 'date' : 'text'\"\n\t\t\t\t\t\t\t\t\t\t\t[value]=\"draft()\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"invalid() || null\"\n\t\t\t\t\t\t\t\t\t\t\t(input)=\"onDraftInput($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(scroll)=\"syncTokens($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t@if (column.kind === 'boolean') {\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__check\"\n\t\t\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\t\t\t[checked]=\"isOn(cell)\"\n\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"!editable\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"toggleBoolean(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else if (cellTemplateFor(colIndex); as own) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host draws this column's cells. The value still travels to the\n\t\t\t\t\t\t\t\t\tclipboard rather than the drawing, and the cell is still what the\n\t\t\t\t\t\t\t\t\tkeyboard is on: the template is inside it, not instead of it. -->\n\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"own.template\"\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"cellContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__value\">{{ displayOf(cell, column) }}</span>\n\n\t\t\t\t\t\t\t\t\t\t@if (column.placeholder && !displayOf(cell, column)) {\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__placeholder\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t{{ column.placeholder }}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<!-- Says the cell opens onto a list or a calendar rather than a field, and\n\t\t\t\t\t\t\t\t\topens it on one click \u2014 as a spreadsheet drops the list of a cell that\n\t\t\t\t\t\t\t\t\thas one. Shown on the cell the reader is on and on the one under the\n\t\t\t\t\t\t\t\t\tpointer. Hidden from assistive technology: the keyboard opens the same\n\t\t\t\t\t\t\t\t\tcell with Alt and the down arrow, and reads the editor itself. -->\n\t\t\t\t\t\t\t\t\t\t@if (editable && affordanceOf(column, colIndex); as affordance) {\n\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__affordance\"\n\t\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__affordance--date]=\"affordance === 'date'\"\n\t\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onAffordanceDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (cell.secondary) {\n\t\t\t\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__secondary\">{{ cell.secondary }}</small>\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (hasFillHandle(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t\t<!-- Drawn inside the corner cell so it travels with the selection. Hidden from\n\t\t\t\t\t\t\t\t\tassistive technology: filling is available from the keyboard through the\n\t\t\t\t\t\t\t\t\tclipboard, and a screen reader has no use for a drag target. -->\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__fill-handle\"\n\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onFillHandleDown($event)\"\n\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t<!-- A cell worked out from a formula rather than typed, marked with a small `fx`\n\t\t\t\t\t\t\t\tin its corner. Shown on the cell under the pointer only: a mark on every formula\n\t\t\t\t\t\t\t\tcell at once would read as content. Pressing it opens the cell for writing, if the\n\t\t\t\t\t\t\t\tformula can be edited at all. Hidden from assistive technology \u2014 the value is what\n\t\t\t\t\t\t\t\tmatters, and the marker says nothing a reader needs. -->\n\t\t\t\t\t\t\t\t@if (isFormulaCell(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__fx\"\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__fx--editable]=\"canEdit(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t(mousedown)=\"editFormula(rowIndex, colIndex, $event)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\t\t\t\t</tr>\n\n\t\t\t\t<!-- What the row holds behind its figures, opened in place. A dialog used to be the\n\t\t\t\tonly shape for this and it is the wrong one: it hides the row it is about and cannot\n\t\t\t\tbe read against the one above. -->\n\t\t\t\t@if (expansion() && isExpanded(rowIndex)) {\n\t\t\t\t\t<tr class=\"hub-spreadsheet__expansion\" role=\"row\">\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__expansion-cell\" role=\"gridcell\" [attr.colspan]=\"drawnCellCount()\">\n\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"expansion()!\"\n\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"expansionContext(rowIndex)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t}\n\t\t\t} @empty {\n\t\t\t\t<tr class=\"hub-spreadsheet__row hub-spreadsheet__row--empty\" role=\"row\">\n\t\t\t\t\t<td class=\"hub-spreadsheet__empty\" role=\"gridcell\" [attr.colspan]=\"bounds().cols || 1\">\n\t\t\t\t\t\t{{ emptyText() }}\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t\t@if (rowWindow().after > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().after\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t</tbody>\n\t</table>\n</div>\n\n<!-- The coordinates, outside the sheet's frame while a formula is written: a column's letter and the\nalias a formula calls it by over the table, the row's number down its leading edge. The viewport\nshifts to make room, so the frame stays between these and the table. Measured off the drawn table\n(the grid already names every cell by its indices, so these are decoration), and the column badge is\nclickable to write its alias into the formula. -->\n@if (writingFormula()) {\n\t<div class=\"hub-spreadsheet__coords\" aria-hidden=\"true\">\n\t\t@for (column of coordColumns(); track $index) {\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__coord hub-spreadsheet__coord--column\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[style.--coord-start]=\"column.start + 'px'\"\n\t\t\t\t[style.--coord-width]=\"column.width + 'px'\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); insertAlias(column.alias)\"\n\t\t\t>\n\t\t\t\t<span class=\"hub-spreadsheet__coord-letter\">{{ column.letter }}</span>\n\t\t\t\t<span class=\"hub-spreadsheet__coord-alias\">{{ column.alias }}</span>\n\t\t\t</button>\n\t\t}\n\n\t\t@for (row of coordRows(); track $index) {\n\t\t\t<span class=\"hub-spreadsheet__coord-row\" [style.inset-block-start.px]=\"row.top\" [style.block-size.px]=\"row.height\">\n\t\t\t\t<span class=\"hub-spreadsheet__coord hub-spreadsheet__coord--row\">{{ row.number }}</span>\n\t\t\t</span>\n\t\t}\n\t</div>\n}\n\n@if (expandToggle() && expansion()) {\n\t<!-- PROTOTYPE: a floating toggle per row, over the frame's leading edge, that opens and closes\n\tthe row's expansion. Its place is measured against the frame and put back on every scroll, so it\n\tstays where its row is rather than travelling with the cells. -->\n\t<div class=\"hub-spreadsheet__toggles\">\n\t\t@for (toggle of expansionToggles(); track toggle.key) {\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__toggle hub-spreadsheet__toggle--start\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-block-start.px]=\"toggle.top\"\n\t\t\t\t[style.block-size.px]=\"toggle.height\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"expandToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleByKey(toggle.key)\"\n\t\t\t></button>\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__toggle hub-spreadsheet__toggle--end\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-block-start.px]=\"toggle.top\"\n\t\t\t\t[style.block-size.px]=\"toggle.height\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"expandToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleByKey(toggle.key)\"\n\t\t\t></button>\n\t\t}\n\t</div>\n}\n\n@if (rowGroupToggles().length) {\n\t<!-- The row groups: a line across the sheet where a run is folded away, and a button at the\n\tmiddle of the row whose fold it is. -->\n\t<div class=\"hub-spreadsheet__row-toggles\">\n\t\t@for (toggle of rowGroupToggles(); track toggle.id) {\n\t\t\t@if (!toggle.open) {\n\t\t\t\t<span\n\t\t\t\t\tclass=\"hub-spreadsheet__fold hub-spreadsheet__fold--row\"\n\t\t\t\t\t[style.inset-block-start.px]=\"toggle.toggle\"\n\t\t\t\t></span>\n\t\t\t}\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__row-toggle\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__row-toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-block-start.px]=\"toggle.toggle\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"rowToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleRowGroup(toggle.id)\"\n\t\t\t></button>\n\t\t}\n\t</div>\n}\n\n@if (columnToggles().length) {\n\t<!-- The column groups: a bracket over the headers across the run it folds \u2014 a coloured mark\n\twider than zero once the run is folded away \u2014 and a button at the bracket's end. -->\n\t<div class=\"hub-spreadsheet__column-toggles\">\n\t\t@for (toggle of columnToggles(); track toggle.id) {\n\t\t\t@if (!toggle.open) {\n\t\t\t\t<span class=\"hub-spreadsheet__fold\" [style.inset-inline-start.px]=\"toggle.toggle\"></span>\n\t\t\t}\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__column-toggle\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__column-toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-inline-start.px]=\"toggle.toggle\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"columnToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleColumnGroup(toggle.id)\"\n\t\t\t></button>\n\t\t}\n\t</div>\n}\n\n@if (suggesting()) {\n\t<!-- What can go into the formula being written. Anchored to the host, never to the cell: drawn\n\tinside the cell the scroll area cut it, and on the last row \u2014 the row a total is written in \u2014\n\tit was never seen at all. The position is the component's; these are the looks. Hidden from\n\tassistive technology: the field owns the conversation. -->\n\t<div\n\t\tclass=\"hub-spreadsheet__suggestions\"\n\t\trole=\"presentation\"\n\t\taria-hidden=\"true\"\n\t\t[style.inset-inline-start.px]=\"suggestionsAt()?.x\"\n\t\t[style.inset-block-start.px]=\"suggestionsAt()?.top\"\n\t\t[style.inset-block-end.px]=\"suggestionsAt()?.bottom\"\n\t\t[style.max-inline-size.px]=\"suggestionsAt()?.maxWidth\"\n\t>\n\t\t@for (suggestion of suggestions(); track suggestion.label; let index = $index) {\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__suggestion\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__suggestion--on]=\"index === suggestionIndex()\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); useSuggestion(suggestion)\"\n\t\t\t>\n\t\t\t\t<span class=\"hub-spreadsheet__suggestion-name\">{{ suggestion.label }}</span>\n\t\t\t\t@if (suggestion.hint) {\n\t\t\t\t\t<span class=\"hub-spreadsheet__suggestion-hint\">{{ suggestion.hint }}</span>\n\t\t\t\t}\n\t\t\t</button>\n\t\t}\n\t</div>\n}\n\n@if (menuAt(); as at) {\n\t<!-- Positioned against the host, which is the sheet's own corner, so the menu travels with a\n\tscrolled page and never has to be repositioned on scroll. -->\n\t<div\n\t\tclass=\"hub-spreadsheet__menu\"\n\t\trole=\"menu\"\n\t\t[attr.aria-label]=\"'ROW_ACTIONS' | translate\"\n\t\t[style.inset-inline-start.px]=\"at.x\"\n\t\t[style.inset-block-start.px]=\"at.y\"\n\t\t(mousedown)=\"$event.stopPropagation()\"\n\t>\n\t\t@for (entry of menuEntries(); track entry.id) {\n\t\t\t<button class=\"hub-spreadsheet__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"runMenuEntry(entry.id)\">\n\t\t\t\t{{ entry.label | translate: { count: entry.count } }}\n\t\t\t</button>\n\t\t}\n\t</div>\n}\n", styles: ["@charset \"UTF-8\";:host{position:relative;display:block;color:var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529)))}.hub-spreadsheet__sink{position:absolute;inline-size:1px;block-size:1px;padding:0;border:0;opacity:0;pointer-events:none;inset-block-start:0;inset-inline-start:0}:host(.hub-spreadsheet--virtual) .hub-spreadsheet__viewport{max-block-size:var(--hub-spreadsheet-max-block-size, var(--hub-table-container-max-block-size, 24rem))}.hub-spreadsheet__viewport{position:relative;overflow:auto;max-block-size:var(--hub-spreadsheet-max-block-size, var(--hub-table-container-max-block-size, none));border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));border-radius:var(--hub-spreadsheet-border-radius, var(--hub-table-border-radius, var(--hub-sys-radius-lg, .5rem)));background:var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))}.hub-spreadsheet__table{inline-size:100%;border-collapse:separate;border-spacing:0;font-variant-numeric:tabular-nums}.hub-spreadsheet__header,.hub-spreadsheet__cell{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px));--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px));padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border-inline-end:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));border-block-end:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));background:var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)));white-space:nowrap;text-align:start}.hub-spreadsheet__header--end-of-sheet,.hub-spreadsheet__cell--end-of-sheet{--hub-spreadsheet-cell-edge-inline: 0px;border-inline-end:0}.hub-spreadsheet__header{position:sticky;isolation:isolate;inset-block-start:0;z-index:2;background:var(--hub-spreadsheet-header-bg, var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa)));color:var(--hub-spreadsheet-header-color, var(--hub-sys-text-muted, #6a737b));font-size:var(--hub-spreadsheet-header-font-size, .8125rem);font-weight:var(--hub-spreadsheet-header-font-weight, 600)}.hub-spreadsheet__header--moving{opacity:var(--hub-spreadsheet-moving-opacity, .5)}.hub-spreadsheet__header--move-target{outline:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));outline-offset:calc(var(--hub-spreadsheet-cursor-width, 2px) * -1)}.hub-spreadsheet__header--frozen{inset-inline-start:0;z-index:4}.hub-spreadsheet__header--frozen-edge{border-inline-end:var(--hub-spreadsheet-frozen-divider-width, 2px) solid var(--hub-spreadsheet-frozen-divider-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))))}.hub-spreadsheet__header--end{text-align:end}.hub-spreadsheet__header--center{text-align:center}:host(.hub-spreadsheet--formula){padding-block-start:2.8rem;padding-inline-start:2.6rem}.hub-spreadsheet__coords{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__coord-row{position:absolute;inset-inline-start:.45rem;display:flex;align-items:center;inline-size:calc(2.6rem - .45rem * 2)}.hub-spreadsheet__coord{position:absolute;display:inline-flex;gap:.3rem;align-items:center;justify-content:center;block-size:1.9rem;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:.4rem;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529)));box-shadow:0 1px 2px #0000001f;font:inherit;font-size:var(--hub-spreadsheet-header-font-size, .8125rem);font-weight:400;font-variant-numeric:tabular-nums;line-height:1;white-space:nowrap;pointer-events:auto;animation:hub-spreadsheet-coord-in .18s ease both}.hub-spreadsheet__coord--column{inset-block-start:.45rem;inset-inline-start:calc(var(--coord-start, 0px) + .45rem);inline-size:calc(var(--coord-width, 0px) - .45rem * 2);cursor:default}.hub-spreadsheet__coord--row{position:relative;inline-size:100%}.hub-spreadsheet__toggles{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__toggle{position:absolute;display:flex;align-items:center;justify-content:center;inline-size:1rem;padding:0;border:0;background:none;color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));cursor:pointer;opacity:.5;transition:opacity .15s ease;pointer-events:auto}.hub-spreadsheet__toggle:hover,.hub-spreadsheet__toggle--open{opacity:1}.hub-spreadsheet__toggle:before{content:\"\";inline-size:.85rem;block-size:.85rem;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:50%;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));box-shadow:0 1px 2px #0000001f}.hub-spreadsheet__toggle:after{content:\"\";position:absolute;inset-block-start:50%;inset-inline-start:50%;inline-size:.26rem;block-size:.26rem;border-inline-end:1.5px solid currentColor;border-block-end:1.5px solid currentColor;transform:translate(-58%,-42%) rotate(45deg);transition:transform .15s ease}.hub-spreadsheet__toggle--open:after{transform:translate(-58%,-42%) rotate(-135deg)}.hub-spreadsheet__toggle--start{inset-inline-start:0;margin-inline-start:-.55rem}.hub-spreadsheet__toggle--end{inset-inline-end:0;margin-inline-end:-.55rem}.hub-spreadsheet__column-toggles{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__column-toggle{position:absolute;inset-block-start:-.55rem;display:flex;align-items:center;justify-content:center;inline-size:1rem;block-size:1rem;margin-inline-start:-.5rem;padding:0;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:50%;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));box-shadow:0 1px 2px #0000001f;cursor:pointer;transition:background .15s ease;pointer-events:auto}.hub-spreadsheet__column-toggle:hover{background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 18%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))))}.hub-spreadsheet__column-toggle:before,.hub-spreadsheet__column-toggle:after{content:\"\";position:absolute;background:currentColor}.hub-spreadsheet__column-toggle:before{inline-size:.5rem;block-size:1.5px}.hub-spreadsheet__column-toggle:after{inline-size:1.5px;block-size:.5rem}.hub-spreadsheet__column-toggle--open:after{display:none}.hub-spreadsheet__fold{position:absolute;inset-block:0;inline-size:2px;background:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));opacity:.6;pointer-events:none}.hub-spreadsheet__fold--row{inset-block:auto;inset-inline:0;block-size:2px;inline-size:auto}.hub-spreadsheet__cell.hub-spreadsheet__grouped,.hub-spreadsheet__header.hub-spreadsheet__grouped{background-color:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 14%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))))}.hub-spreadsheet__row-toggles{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__row-toggle{position:absolute;inset-inline-start:0;display:flex;align-items:center;justify-content:center;inline-size:1rem;block-size:1rem;margin-inline-start:-.5rem;transform:translateY(-50%);padding:0;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:50%;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));box-shadow:0 1px 2px #0000001f;cursor:pointer;transition:background .15s ease;pointer-events:auto}.hub-spreadsheet__row-toggle:hover{background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 18%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))))}.hub-spreadsheet__row-toggle:before,.hub-spreadsheet__row-toggle:after{content:\"\";position:absolute;background:currentColor}.hub-spreadsheet__row-toggle:before{inline-size:.5rem;block-size:1.5px}.hub-spreadsheet__row-toggle:after{inline-size:1.5px;block-size:.5rem}.hub-spreadsheet__row-toggle--open:after{display:none}.hub-spreadsheet__coord-letter{font-weight:600}.hub-spreadsheet__coord-alias{padding:0 .25rem;border-radius:.25rem;background:var(--hub-spreadsheet-alias-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent));overflow:hidden;text-overflow:ellipsis}@keyframes hub-spreadsheet-coord-in{0%{opacity:0;transform:translateY(.25rem) scale(.85)}to{opacity:1;transform:none}}.hub-spreadsheet__header-text--hidden{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.hub-spreadsheet__subheader{display:block;font-weight:400;opacity:.8}.hub-spreadsheet__cell{position:relative;cursor:cell;-webkit-user-select:none;user-select:none}.hub-spreadsheet__cell--end{text-align:end}.hub-spreadsheet__cell--center{text-align:center}.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--frozen-row{position:sticky;z-index:1}.hub-spreadsheet__cell--frozen-col{inset-inline-start:0}.hub-spreadsheet__cell--frozen-col.hub-spreadsheet__cell--frozen-row{z-index:2}.hub-spreadsheet__cell--frozen-col-edge{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-frozen-divider-width, 2px);border-inline-end:var(--hub-spreadsheet-frozen-divider-width, 2px) solid var(--hub-spreadsheet-frozen-divider-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))))}.hub-spreadsheet__cell--frozen-row-edge{--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-frozen-divider-width, 2px);border-block-end:var(--hub-spreadsheet-frozen-divider-width, 2px) solid var(--hub-spreadsheet-frozen-divider-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))))}.hub-spreadsheet__cell--filling{background-image:linear-gradient(var(--hub-spreadsheet-fill-preview-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 6%, transparent)),var(--hub-spreadsheet-fill-preview-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 6%, transparent)))}.hub-spreadsheet__cell--selected{background-image:linear-gradient(var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)),var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)))}.hub-spreadsheet__cell--pointed{background-image:linear-gradient(var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)),var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)));box-shadow:inset 0 0 0 var(--hub-spreadsheet-cursor-width, 2px) var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd))}.hub-spreadsheet__cell--readonly{background-color:var(--hub-spreadsheet-readonly-bg, color-mix(in srgb, var(--hub-spreadsheet-header-bg, var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa))) 55%, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))));color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b));cursor:default}.hub-spreadsheet__cell--muted{color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b))}.hub-spreadsheet__cell--invalid{background-image:linear-gradient(var(--hub-spreadsheet-invalid-bg, color-mix(in srgb, var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e)) 10%, transparent)),var(--hub-spreadsheet-invalid-bg, color-mix(in srgb, var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e)) 10%, transparent)));color:var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e));box-shadow:inset 0 0 0 var(--hub-spreadsheet-invalid-border-width, 1px) var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e))}.hub-spreadsheet__cell--active{outline:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));outline-offset:calc(var(--hub-spreadsheet-cursor-width, 2px) * -1);z-index:2}.hub-spreadsheet__cell--editing{block-size:calc(var(--hub-spreadsheet-cell-line-height, 1.5em) + var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) * 2 + var(--hub-spreadsheet-cell-edge-block))}.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-row{z-index:3}.hub-spreadsheet__cell[data-state]:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;inline-size:var(--hub-spreadsheet-state-bar-width, 3px)}.hub-spreadsheet__cell[data-state=pending]:before{background:var(--hub-spreadsheet-state-pending-color, var(--hub-sys-color-warning, #cc8a00))}.hub-spreadsheet__cell[data-state=saving]:before{background:var(--hub-spreadsheet-state-saving-color, var(--hub-sys-color-info, #0a7ea4))}.hub-spreadsheet__cell[data-state=saved]:before{background:var(--hub-spreadsheet-state-saved-color, var(--hub-sys-color-success, #1a7f37))}.hub-spreadsheet__cell[data-state=error]:before{background:var(--hub-spreadsheet-state-error-color, var(--hub-sys-color-danger, #b3261e))}.hub-spreadsheet__cell[data-state=conflict]:before{background:repeating-linear-gradient(45deg,var(--hub-spreadsheet-state-conflict-color, var(--hub-sys-color-danger, #b3261e)) 0 3px,transparent 3px 6px)}.hub-spreadsheet__editor--select{padding-inline:calc(var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem)) - 2px);appearance:auto}.hub-spreadsheet__custom-editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);display:grid;background:var(--hub-spreadsheet-editor-bg, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));outline:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));outline-offset:calc(var(--hub-spreadsheet-cursor-width, 2px) * -1);z-index:3;--hub-input-bg: var(--hub-spreadsheet-editor-field-bg, transparent);--hub-input-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-input-border-radius: var(--hub-spreadsheet-editor-field-border-radius, 0);--hub-input-focus-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-input-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow, none);--hub-input-padding-x: var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));--hub-select-bg: var(--hub-spreadsheet-editor-field-bg, transparent);--hub-select-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-select-border-radius: var(--hub-spreadsheet-editor-field-border-radius, 0);--hub-select-focus-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-select-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow, none);--hub-select-padding-x: var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem))}.hub-spreadsheet__spacer>td{padding:0;border:0;background:var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))}.hub-spreadsheet__affordance{position:absolute;inset-block:0;inset-inline-end:calc(var(--hub-spreadsheet-affordance-field-border, var(--hub-input-border-width, 1px)) - var(--hub-spreadsheet-cell-edge-inline));display:flex;align-items:center;padding-inline:var(--hub-spreadsheet-affordance-gap, var(--hub-select-arrow-gap, .5rem));cursor:pointer;opacity:0}.hub-spreadsheet__cell:hover>.hub-spreadsheet__affordance,.hub-spreadsheet__cell--active>.hub-spreadsheet__affordance{opacity:1}.hub-spreadsheet__affordance:after{content:\"\";inline-size:0;block-size:0;border-style:solid;border-width:var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) calc(var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) / 2);border-color:var(--hub-spreadsheet-affordance-color, var(--hub-select-arrow-color, var(--hub-sys-text-muted, #6a737b))) transparent transparent}.hub-spreadsheet__affordance--date:after{inline-size:calc(var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) * 2);block-size:calc(var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) * 1.8);border:1px solid var(--hub-spreadsheet-affordance-color, var(--hub-select-arrow-color, var(--hub-sys-text-muted, #6a737b)));border-block-start-width:3px;border-radius:2px}.hub-spreadsheet__placeholder{position:absolute;inset-block:0;inset-inline:var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));display:flex;align-items:center;color:var(--hub-spreadsheet-placeholder-color, var(--hub-sys-text-muted, #6a737b));opacity:0;pointer-events:none;transition:var(--hub-spreadsheet-placeholder-transition, var(--hub-sys-transition-fast, all .15s ease-in-out))}.hub-spreadsheet__cell:hover>.hub-spreadsheet__placeholder{opacity:var(--hub-spreadsheet-placeholder-opacity, .65)}.hub-spreadsheet__check{inline-size:1em;block-size:1em;accent-color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));cursor:pointer}.hub-spreadsheet__check:disabled{cursor:default}.hub-spreadsheet__resize-grip{position:absolute;inset-block:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-resize-grip-width, 5px);cursor:col-resize;background:transparent;z-index:5}.hub-spreadsheet__resize-grip:hover,.hub-spreadsheet__resize-grip:active{background:var(--hub-spreadsheet-resize-grip-color, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)))}.hub-spreadsheet__fill-handle{position:absolute;inset-block-end:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-fill-handle-size, 7px);block-size:var(--hub-spreadsheet-fill-handle-size, 7px);background:var(--hub-spreadsheet-fill-handle-color, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)));cursor:crosshair;z-index:4}.hub-spreadsheet__secondary{display:block;opacity:.75}.hub-spreadsheet__expansion-cell{padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border-block-end:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));background:var(--hub-spreadsheet-header-bg, var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa)));white-space:normal;vertical-align:top}.hub-spreadsheet__fx{position:absolute;inset-block-start:2px;inset-inline-end:2px;z-index:1;padding:0 .3rem;border:0;border-radius:.3rem;background:var(--hub-spreadsheet-formula-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 14%, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))));color:var(--hub-spreadsheet-formula-color, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)));font-size:.5625rem;font-style:italic;font-weight:600;line-height:1.4;opacity:0;pointer-events:none;transition:opacity .12s ease}.hub-spreadsheet__fx:before{content:\"fx\"}.hub-spreadsheet__cell:hover>.hub-spreadsheet__fx{opacity:1}.hub-spreadsheet__fx--editable{cursor:pointer;pointer-events:auto}.hub-spreadsheet__editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));border-radius:0;background:var(--hub-spreadsheet-editor-bg, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-editor-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))));font:inherit;font-variant-numeric:tabular-nums;text-align:inherit;outline:none;z-index:2}.hub-spreadsheet__editor[aria-invalid=true]{border-color:var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e))}.hub-spreadsheet__tokens{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border:var(--hub-spreadsheet-cursor-width, 2px) solid transparent;border-radius:0;background:var(--hub-spreadsheet-editor-bg, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-editor-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))));font:inherit;font-variant-numeric:tabular-nums;text-align:inherit;white-space:pre;overflow:hidden;pointer-events:none;z-index:1}.hub-spreadsheet__editor--coloured{background:transparent;color:transparent;caret-color:var(--hub-spreadsheet-editor-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))))}.hub-spreadsheet__token{border-radius:3px}.hub-spreadsheet__token--function{color:var(--hub-spreadsheet-token-function-color, var(--hub-sys-color-primary, #0d6efd));background:color-mix(in srgb,var(--hub-spreadsheet-token-function-color, var(--hub-sys-color-primary, #0d6efd)) 14%,transparent)}.hub-spreadsheet__token--column{color:var(--hub-spreadsheet-token-column-color, var(--hub-sys-color-info, #0a7ea4));background:color-mix(in srgb,var(--hub-spreadsheet-token-column-color, var(--hub-sys-color-info, #0a7ea4)) 14%,transparent)}.hub-spreadsheet__token--coordinate{color:var(--hub-spreadsheet-token-coordinate-color, var(--hub-sys-color-success, #1a7f37));background:color-mix(in srgb,var(--hub-spreadsheet-token-coordinate-color, var(--hub-sys-color-success, #1a7f37)) 14%,transparent)}.hub-spreadsheet__token--text{color:var(--hub-spreadsheet-token-text-color, var(--hub-sys-color-danger, #b3261e));background:color-mix(in srgb,var(--hub-spreadsheet-token-text-color, var(--hub-sys-color-danger, #b3261e)) 12%,transparent)}.hub-spreadsheet__token--operator,.hub-spreadsheet__token--equals,.hub-spreadsheet__token--paren,.hub-spreadsheet__token--separator{color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b))}.hub-spreadsheet__empty{padding-block:calc(var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) * 4);color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b));text-align:center;cursor:default}.hub-spreadsheet__menu{position:absolute;z-index:10;min-inline-size:12rem;padding-block:.25rem;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-menu-border-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))));border-radius:var(--hub-spreadsheet-menu-radius, var(--hub-sys-radius-sm, .25rem));background:var(--hub-spreadsheet-menu-bg, var(--hub-sys-color-surface, #fff));color:var(--hub-spreadsheet-menu-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))));box-shadow:var(--hub-spreadsheet-menu-shadow, 0 6px 20px rgba(0, 0, 0, .18))}.hub-spreadsheet__menu-item{display:block;inline-size:100%;padding:var(--hub-spreadsheet-menu-item-padding-y, .4rem) var(--hub-spreadsheet-menu-item-padding-x, .85rem);border:0;background:transparent;color:inherit;font:inherit;text-align:start;white-space:nowrap;cursor:pointer}.hub-spreadsheet__menu-item:hover,.hub-spreadsheet__menu-item:focus-visible{background:var(--hub-spreadsheet-menu-item-bg-hover, var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)))}.hub-spreadsheet__suggestions{position:fixed;z-index:1000;display:flex;flex-direction:column;min-inline-size:14rem;max-block-size:14rem;overflow-y:auto;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-suggestions-border-color, var(--hub-spreadsheet-menu-border-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)))));border-radius:var(--hub-spreadsheet-border-radius, var(--hub-table-border-radius, var(--hub-sys-radius-lg, .5rem)));background:var(--hub-spreadsheet-suggestions-bg, var(--hub-spreadsheet-menu-bg, var(--hub-sys-color-surface, #fff)));box-shadow:var(--hub-spreadsheet-menu-shadow, 0 6px 20px rgba(0, 0, 0, .18))}.hub-spreadsheet__suggestion{display:flex;gap:.5rem;align-items:baseline;justify-content:space-between;padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.hub-spreadsheet__suggestion:hover,.hub-spreadsheet__suggestion--on{background:var(--hub-spreadsheet-suggestion-on-bg, var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)))}.hub-spreadsheet__suggestion-name{font-variant-numeric:tabular-nums}.hub-spreadsheet__suggestion-hint{color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b));font-size:var(--hub-spreadsheet-header-font-size, .8125rem)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: HubSpreadsheetControlDirective, selector: "[hubSpreadsheetControl]", inputs: ["hubSpreadsheetControl"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3716
4704
  }
3717
4705
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubSpreadsheetComponent, decorators: [{
3718
4706
  type: Component,
@@ -3731,15 +4719,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
3731
4719
  */
3732
4720
  ngSkipHydration: 'true',
3733
4721
  class: 'hub-spreadsheet',
4722
+ '[attr.dir]': 'forcedDirection()',
3734
4723
  '[class.hub-spreadsheet--readonly]': 'readonly()',
3735
4724
  '[class.hub-spreadsheet--editing]': 'editing()',
4725
+ '[class.hub-spreadsheet--formula]': 'writingFormula()',
3736
4726
  '[class.hub-spreadsheet--virtual]': 'virtual()',
4727
+ '[class.hub-spreadsheet--rtl]': 'rtl()',
3737
4728
  '(document:mouseup)': 'endDrag()',
3738
4729
  '(document:mousemove)': 'onPointerMove($event)',
3739
4730
  '(document:mousedown)': 'closeMenu()',
3740
4731
  '(document:keydown.escape)': 'closeMenu()'
3741
- }, template: "<!--\n\tThe off-screen field exists for one reason: a browser only dispatches `paste` to something\n\teditable. The paste shortcut parks the focus here for the length of the event and the cell\n\ttakes it straight back, so the reader never notices and the grid keeps its own focus model.\n-->\n<textarea class=\"hub-spreadsheet__sink\" tabindex=\"-1\" aria-hidden=\"true\" readonly (paste)=\"onPaste($event)\"></textarea>\n\n<div class=\"hub-spreadsheet__viewport\" #viewport (scroll)=\"onViewportScroll()\">\n\t<table\n\t\tclass=\"hub-spreadsheet__table\"\n\t\trole=\"grid\"\n\t\t[attr.aria-rowcount]=\"bounds().rows\"\n\t\t[attr.aria-colcount]=\"bounds().cols\"\n\t\t[attr.aria-readonly]=\"readonly() || null\"\n\t\t(keydown)=\"onKeydown($event)\"\n\t\t(copy)=\"onCopy($event, false)\"\n\t\t(cut)=\"onCopy($event, true)\"\n\t>\n\t\t<thead class=\"hub-spreadsheet__head\">\n\t\t\t<tr role=\"row\">\n\t\t\t\t<!-- Holds the width of the columns that are not drawn, so the sheet is as wide as it\n\t\t\t\tsays it is and the ones that are drawn land where they belong. -->\n\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t<!-- A minimum, not a width: in a table that shares its space out, a width is a\n\t\t\t\t\tsuggestion the algorithm is free to ignore, and an empty cell asked for three\n\t\t\t\t\tthousand pixels came out two wide. -->\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\n\t\t\t\t@for (columnIndex of drawnColumns(); track trackColumn(columnIndex, columns()[columnIndex])) {\n\t\t\t\t\t@let column = columns()[columnIndex];\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__header\"\n\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen]=\"isFrozenColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen-edge]=\"isColumnEdge(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(columnIndex)\"\n\t\t\t\t\t\t[style.min-width]=\"column.minWidth\"\n\t\t\t\t\t\t[style.width.px]=\"widthOf(columnIndex)\"\n\t\t\t\t\t\t[attr.data-header]=\"columnIndex\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end-of-sheet]=\"columnIndex === bounds().cols - 1\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--moving]=\"isMovingColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--move-target]=\"isMoveTarget(columnIndex)\"\n\t\t\t\t\t\t(mousedown)=\"onHeaderMouseDown($event, columnIndex)\"\n\t\t\t\t\t\t(mouseenter)=\"onHeaderMouseEnter(columnIndex)\"\n\t\t\t\t\t\t[attr.aria-colindex]=\"columnIndex + 1\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<span [class.hub-spreadsheet__header-text--hidden]=\"column.headerHidden\">\n\t\t\t\t\t\t\t{{ column.header }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<!-- The name a formula calls this column, which is not the name on the header.\n\t\t\t\t\tShown only while one is being written, and clickable, because the alternative is\n\t\t\t\t\tknowing it by heart. -->\n\t\t\t\t\t\t@if (writingFormula()) {\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__alias\"\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t\t\t(mousedown)=\"\n\t\t\t\t\t\t\t\t\t$event.preventDefault();\n\t\t\t\t\t\t\t\t\tuseSuggestion({ label: column.key, kind: 'column', insert: '[' + column.key + ']' })\n\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__alias-letter\">{{ columnLetter(columnIndex) }}</span>\n\t\t\t\t\t\t\t\t{{ column.key }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@if (column.subheader) {\n\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__subheader\">{{ column.subheader }}</small>\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@if (resizableColumns()) {\n\t\t\t\t\t\t\t<!-- Sits inside the header's trailing edge. Hidden from assistive technology:\n\t\t\t\t\t\t\ta width is a matter of taste, and a screen reader has no use for a drag\n\t\t\t\t\t\t\ttarget it cannot aim at. -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__resize-grip\"\n\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onResizeStart($event, columnIndex)\"\n\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t</th>\n\t\t\t\t}\n\n\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\t\t\t</tr>\n\t\t</thead>\n\n\t\t<tbody class=\"hub-spreadsheet__body\">\n\t\t\t<!-- The rows above the window are not in the document; this holds their height, so the\n\t\t\tscrollbar still says how long the sheet is and the rows that are drawn land where they\n\t\t\tbelong. Hidden from assistive technology, which reads `aria-rowindex` instead. -->\n\t\t\t@if (rowWindow().before > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().before\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\n\t\t\t@for (rowIndex of drawnRows(); track trackRow(rowIndex)) {\n\t\t\t\t<tr\n\t\t\t\t\tclass=\"hub-spreadsheet__row\"\n\t\t\t\t\trole=\"row\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t[attr.aria-rowindex]=\"rowIndex + 1\"\n\t\t\t\t>\n\t\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\n\t\t\t\t\t@for (colIndex of drawnColumns(); track trackColumn(colIndex, columns()[colIndex])) {\n\t\t\t\t\t\t@let column = columns()[colIndex];\n\t\t\t\t\t\t<!-- A cell swallowed by a merge is not rendered at all: the anchor's rowspan and\n\t\t\t\t\t\tcolspan already occupy its place, and drawing it would push the row out of shape. -->\n\t\t\t\t\t\t@if (!isHidden(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t@let cell = grid()[rowIndex][colIndex];\n\t\t\t\t\t\t\t@let state = stateOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let error = errorOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let editable = canEdit(rowIndex, colIndex);\n\t\t\t\t\t\t\t<!-- Worked out once per cell, and only for the one that is open: the config a\n\t\t\t\t\t\tregistered control adapter is asked for, or null when there is nothing to host. -->\n\t\t\t\t\t\t\t@let hosted = editing() && isActive(rowIndex, colIndex) ? hostedControl(rowIndex, colIndex) : null;\n\n\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__cell\"\n\t\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\t\t[attr.data-cell]=\"rowIndex + '-' + colIndex\"\n\t\t\t\t\t\t\t\t[attr.tabindex]=\"tabIndexOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colindex]=\"colIndex + 1\"\n\t\t\t\t\t\t\t\t[attr.rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-readonly]=\"!editable || null\"\n\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"(isActive(rowIndex, colIndex) && invalid()) || error ? true : null\"\n\t\t\t\t\t\t\t\t[attr.aria-busy]=\"state === 'saving' || null\"\n\t\t\t\t\t\t\t\t[attr.title]=\"error ?? cell.title\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--active]=\"isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--editing]=\"editing() && isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end-of-sheet]=\"reachesEnd(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--filling]=\"isFillPreview(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--readonly]=\"!editable\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--muted]=\"cell.muted\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col]=\"isFrozenColumn(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col-edge]=\"isColumnEdge(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(colIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-block-start.px]=\"rowOffsetOf(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--invalid]=\"\n\t\t\t\t\t\t\t\t\t(isActive(rowIndex, colIndex) && invalid()) ||\n\t\t\t\t\t\t\t\t\t!!error ||\n\t\t\t\t\t\t\t\t\thasFormulaError(rowIndex, colIndex)\n\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t\t[attr.data-state]=\"state\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onMouseDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseenter)=\"onMouseEnter(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseup)=\"onMouseUp($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(dblclick)=\"onDoubleClick()\"\n\t\t\t\t\t\t\t\t(contextmenu)=\"onContextMenu($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t@if (editing() && isActive(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t@if (editorFor(colIndex); as custom) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host supplied an editor for this column. It reports a value through\n\t\t\t\t\t\t\t\t\t`commit`, or walks away through `cancel`; until it does one of the two the\n\t\t\t\t\t\t\t\t\teditor stays open, so a picker may take as many clicks as it needs. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"custom.template\"\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"editorContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (hosted) {\n\t\t\t\t\t\t\t\t\t\t<!-- A control adapter is registered and this column is a list, so the cell\n\t\t\t\t\t\t\t\t\topens with whatever the project wired instead of the native list. The\n\t\t\t\t\t\t\t\t\twrapper is the supplied editor's, because from the sheet's side this is\n\t\t\t\t\t\t\t\t\tone: something it did not build, filling the cell. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container [hubSpreadsheetControl]=\"hosted\" />\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (column.kind === 'select') {\n\t\t\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor hub-spreadsheet__editor--select\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"commitFromEditor($any($event.target).value || null)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<option value=\"\" [selected]=\"cell.value === null\"></option>\n\t\t\t\t\t\t\t\t\t\t\t@for (option of column.options ?? []; track option.value) {\n\t\t\t\t\t\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\t\t\t\t\t\t[value]=\"option.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[selected]=\"isChosen(option, column)\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"option.disabled && option.value !== cell.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t@if (suggesting()) {\n\t\t\t\t\t\t\t\t\t\t\t<!-- What can go into the formula, because the aliases it needs are nowhere\n\t\t\t\t\t\t\t\t\t\telse on screen. Hidden from assistive technology: the field owns the\n\t\t\t\t\t\t\t\t\t\tconversation, and a list that announced itself over every keystroke\n\t\t\t\t\t\t\t\t\t\twould talk across it. -->\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__suggestions\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t@for (suggestion of suggestions(); track suggestion.label; let index = $index) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__suggestion\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__suggestion--on]=\"index === suggestionIndex()\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"$event.preventDefault(); useSuggestion(suggestion)\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__suggestion-name\">{{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsuggestion.label\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@if (suggestion.hint) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__suggestion-hint\">{{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsuggestion.hint\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor\"\n\t\t\t\t\t\t\t\t\t\t\t[type]=\"column.kind === 'date' ? 'date' : 'text'\"\n\t\t\t\t\t\t\t\t\t\t\t[value]=\"draft()\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"invalid() || null\"\n\t\t\t\t\t\t\t\t\t\t\t(input)=\"onDraftInput($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t@if (column.kind === 'boolean') {\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__check\"\n\t\t\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\t\t\t[checked]=\"isOn(cell)\"\n\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"!editable\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"toggleBoolean(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else if (cellTemplateFor(colIndex); as own) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host draws this column's cells. The value still travels to the\n\t\t\t\t\t\t\t\t\tclipboard rather than the drawing, and the cell is still what the\n\t\t\t\t\t\t\t\t\tkeyboard is on: the template is inside it, not instead of it. -->\n\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"own.template\"\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"cellContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t<!-- The row's number, so a coordinate can be read off the sheet instead of\n\t\t\t\t\t\t\t\t\tcounted. Only while a formula is being written, and only in the first\n\t\t\t\t\t\t\t\t\tcolumn drawn, which is where a spreadsheet puts it. -->\n\t\t\t\t\t\t\t\t\t\t@if (writingFormula() && colIndex === drawnColumns()[0]) {\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__rownum\" aria-hidden=\"true\">{{ rowIndex + 1 }}</span>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__value\">{{ displayOf(cell, column) }}</span>\n\n\t\t\t\t\t\t\t\t\t\t@if (column.placeholder && !displayOf(cell, column)) {\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__placeholder\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t{{ column.placeholder }}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<!-- Says the cell opens onto a list or a calendar rather than a field, and\n\t\t\t\t\t\t\t\t\topens it on one click \u2014 as a spreadsheet drops the list of a cell that\n\t\t\t\t\t\t\t\t\thas one. Shown on the cell the reader is on and on the one under the\n\t\t\t\t\t\t\t\t\tpointer. Hidden from assistive technology: the keyboard opens the same\n\t\t\t\t\t\t\t\t\tcell with Alt and the down arrow, and reads the editor itself. -->\n\t\t\t\t\t\t\t\t\t\t@if (editable && affordanceOf(column, colIndex); as affordance) {\n\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__affordance\"\n\t\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__affordance--date]=\"affordance === 'date'\"\n\t\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onAffordanceDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (cell.secondary) {\n\t\t\t\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__secondary\">{{ cell.secondary }}</small>\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (hasFillHandle(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t\t<!-- Drawn inside the corner cell so it travels with the selection. Hidden from\n\t\t\t\t\t\t\t\t\tassistive technology: filling is available from the keyboard through the\n\t\t\t\t\t\t\t\t\tclipboard, and a screen reader has no use for a drag target. -->\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__fill-handle\"\n\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onFillHandleDown($event)\"\n\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\t\t\t\t</tr>\n\t\t\t} @empty {\n\t\t\t\t<tr class=\"hub-spreadsheet__row hub-spreadsheet__row--empty\" role=\"row\">\n\t\t\t\t\t<td class=\"hub-spreadsheet__empty\" role=\"gridcell\" [attr.colspan]=\"bounds().cols || 1\">\n\t\t\t\t\t\t{{ emptyText() }}\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t\t@if (rowWindow().after > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().after\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t</tbody>\n\t</table>\n</div>\n\n@if (menuAt(); as at) {\n\t<!-- Positioned against the host, which is the sheet's own corner, so the menu travels with a\n\tscrolled page and never has to be repositioned on scroll. -->\n\t<div\n\t\tclass=\"hub-spreadsheet__menu\"\n\t\trole=\"menu\"\n\t\t[attr.aria-label]=\"'ROW_ACTIONS' | translate\"\n\t\t[style.inset-inline-start.px]=\"at.x\"\n\t\t[style.inset-block-start.px]=\"at.y\"\n\t\t(mousedown)=\"$event.stopPropagation()\"\n\t>\n\t\t@for (entry of menuEntries(); track entry.id) {\n\t\t\t<button class=\"hub-spreadsheet__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"runMenuEntry(entry.id)\">\n\t\t\t\t{{ entry.label | translate: { count: entry.count } }}\n\t\t\t</button>\n\t\t}\n\t</div>\n}\n", styles: ["@charset \"UTF-8\";:host{position:relative;--hub-spreadsheet-bg: var(--hub-table-bg, var(--hub-sys-color-surface, #fff));--hub-spreadsheet-color: var(--hub-table-color, var(--hub-sys-color-text, #212529));--hub-spreadsheet-border-color: var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6));--hub-spreadsheet-border-width: var(--hub-table-border-width, 1px);--hub-spreadsheet-border-radius: var(--hub-table-border-radius, var(--hub-sys-radius-lg, .5rem));--hub-spreadsheet-cell-padding-x: var(--hub-table-cell-padding-x, .6rem);--hub-spreadsheet-cell-padding-y: var(--hub-table-cell-padding-y, .4rem);--hub-spreadsheet-cell-line-height: 1.5em;--hub-spreadsheet-header-bg: var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa));--hub-spreadsheet-header-color: var(--hub-sys-text-muted, #6a737b);--hub-spreadsheet-header-font-size: .8125rem;--hub-spreadsheet-header-font-weight: 600;--hub-spreadsheet-cursor-color: var(--hub-sys-color-primary, #0d6efd);--hub-spreadsheet-cursor-width: 2px;--hub-spreadsheet-selection-bg: color-mix(in srgb, var(--hub-spreadsheet-cursor-color) 12%, transparent);--hub-spreadsheet-readonly-bg: color-mix(in srgb, var(--hub-spreadsheet-header-bg) 55%, var(--hub-spreadsheet-bg));--hub-spreadsheet-affordance-color: var(--hub-select-arrow-color, var(--hub-sys-text-muted, #6a737b));--hub-spreadsheet-affordance-size: var(--hub-select-arrow-size, 5px);--hub-spreadsheet-affordance-gap: var(--hub-select-arrow-gap, .5rem);--hub-spreadsheet-affordance-field-border: var(--hub-input-border-width, 1px);--hub-spreadsheet-placeholder-color: var(--hub-sys-text-muted, #6a737b);--hub-spreadsheet-placeholder-opacity: .65;--hub-spreadsheet-placeholder-transition: var(--hub-sys-transition-fast, all .15s ease-in-out);--hub-spreadsheet-readonly-color: var(--hub-sys-text-muted, #6a737b);--hub-spreadsheet-editor-bg: var(--hub-spreadsheet-bg);--hub-spreadsheet-editor-color: var(--hub-spreadsheet-color);--hub-spreadsheet-editor-field-bg: transparent;--hub-spreadsheet-editor-field-border-color: transparent;--hub-spreadsheet-editor-field-border-radius: 0;--hub-spreadsheet-editor-field-focus-shadow: none;--hub-spreadsheet-state-pending-color: var(--hub-sys-color-warning, #cc8a00);--hub-spreadsheet-state-saving-color: var(--hub-sys-color-info, #0a7ea4);--hub-spreadsheet-state-saved-color: var(--hub-sys-color-success, #1a7f37);--hub-spreadsheet-state-error-color: var(--hub-sys-color-danger, #b3261e);--hub-spreadsheet-state-conflict-color: var(--hub-sys-color-danger, #b3261e);--hub-spreadsheet-state-bar-width: 3px;--hub-spreadsheet-suggestions-bg: var(--hub-spreadsheet-menu-bg);--hub-spreadsheet-suggestions-border-color: var(--hub-spreadsheet-menu-border-color);--hub-spreadsheet-suggestion-on-bg: var(--hub-spreadsheet-selection-bg);--hub-spreadsheet-alias-bg: color-mix(in srgb, var(--hub-spreadsheet-cursor-color) 12%, transparent);--hub-spreadsheet-alias-color: var(--hub-spreadsheet-cursor-color);--hub-spreadsheet-menu-bg: var(--hub-sys-color-surface, #fff);--hub-spreadsheet-menu-color: var(--hub-spreadsheet-color);--hub-spreadsheet-menu-border-color: var(--hub-spreadsheet-border-color);--hub-spreadsheet-menu-radius: var(--hub-sys-radius-sm, .25rem);--hub-spreadsheet-menu-shadow: 0 6px 20px rgb(0 0 0 / 18%);--hub-spreadsheet-menu-item-padding-x: .85rem;--hub-spreadsheet-menu-item-padding-y: .4rem;--hub-spreadsheet-menu-item-bg-hover: var(--hub-spreadsheet-selection-bg);--hub-spreadsheet-resize-grip-width: 5px;--hub-spreadsheet-resize-grip-color: var(--hub-spreadsheet-cursor-color);--hub-spreadsheet-moving-opacity: .5;--hub-spreadsheet-fill-handle-size: 7px;--hub-spreadsheet-fill-handle-color: var(--hub-spreadsheet-cursor-color);--hub-spreadsheet-fill-preview-bg: color-mix(in srgb, var(--hub-spreadsheet-cursor-color) 6%, transparent);--hub-spreadsheet-invalid-color: var(--hub-sys-color-danger, #b3261e);--hub-spreadsheet-invalid-border-width: 1px;--hub-spreadsheet-invalid-bg: color-mix(in srgb, var(--hub-spreadsheet-invalid-color) 10%, transparent);--hub-spreadsheet-max-block-size: var(--hub-table-container-max-block-size, none);--hub-spreadsheet-frozen-divider-color: var(--hub-spreadsheet-border-color, var(--hub-sys-border-color-default, #dee2e6));--hub-spreadsheet-frozen-divider-width: 2px;display:block;color:var(--hub-spreadsheet-color)}.hub-spreadsheet__sink{position:absolute;inline-size:1px;block-size:1px;padding:0;border:0;opacity:0;pointer-events:none;inset-block-start:0;inset-inline-start:0}:host(.hub-spreadsheet--virtual){--hub-spreadsheet-max-block-size: var(--hub-table-container-max-block-size, 24rem)}.hub-spreadsheet__viewport{position:relative;overflow:auto;max-block-size:var(--hub-spreadsheet-max-block-size);border:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-border-color);border-radius:var(--hub-spreadsheet-border-radius);background:var(--hub-spreadsheet-bg)}.hub-spreadsheet__table{inline-size:100%;border-collapse:separate;border-spacing:0;font-variant-numeric:tabular-nums}.hub-spreadsheet__header,.hub-spreadsheet__cell{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-border-width);--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-border-width);padding:var(--hub-spreadsheet-cell-padding-y) var(--hub-spreadsheet-cell-padding-x);border-inline-end:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-border-color);border-block-end:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-border-color);background:var(--hub-spreadsheet-bg);white-space:nowrap;text-align:start}.hub-spreadsheet__header--end-of-sheet,.hub-spreadsheet__cell--end-of-sheet{--hub-spreadsheet-cell-edge-inline: 0px;border-inline-end:0}.hub-spreadsheet__header{position:sticky;isolation:isolate;inset-block-start:0;z-index:2;background:var(--hub-spreadsheet-header-bg);color:var(--hub-spreadsheet-header-color);font-size:var(--hub-spreadsheet-header-font-size);font-weight:var(--hub-spreadsheet-header-font-weight)}.hub-spreadsheet__header--moving{opacity:var(--hub-spreadsheet-moving-opacity)}.hub-spreadsheet__header--move-target{outline:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);outline-offset:calc(var(--hub-spreadsheet-cursor-width) * -1)}.hub-spreadsheet__header--frozen{inset-inline-start:0;z-index:4}.hub-spreadsheet__header--frozen-edge{border-inline-end:var(--hub-spreadsheet-frozen-divider-width) solid var(--hub-spreadsheet-frozen-divider-color)}.hub-spreadsheet__header--end{text-align:end}.hub-spreadsheet__header--center{text-align:center}.hub-spreadsheet__header-text--hidden{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.hub-spreadsheet__subheader{display:block;font-weight:400;opacity:.8}.hub-spreadsheet__cell{position:relative;cursor:cell;-webkit-user-select:none;user-select:none}.hub-spreadsheet__cell--end{text-align:end}.hub-spreadsheet__cell--center{text-align:center}.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--frozen-row{position:sticky;z-index:1}.hub-spreadsheet__cell--frozen-col{inset-inline-start:0}.hub-spreadsheet__cell--frozen-col.hub-spreadsheet__cell--frozen-row{z-index:2}.hub-spreadsheet__cell--frozen-col-edge{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-frozen-divider-width);border-inline-end:var(--hub-spreadsheet-frozen-divider-width) solid var(--hub-spreadsheet-frozen-divider-color)}.hub-spreadsheet__cell--frozen-row-edge{--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-frozen-divider-width);border-block-end:var(--hub-spreadsheet-frozen-divider-width) solid var(--hub-spreadsheet-frozen-divider-color)}.hub-spreadsheet__cell--filling{background-image:linear-gradient(var(--hub-spreadsheet-fill-preview-bg),var(--hub-spreadsheet-fill-preview-bg))}.hub-spreadsheet__cell--selected{background-image:linear-gradient(var(--hub-spreadsheet-selection-bg),var(--hub-spreadsheet-selection-bg))}.hub-spreadsheet__cell--readonly{background-color:var(--hub-spreadsheet-readonly-bg);color:var(--hub-spreadsheet-readonly-color);cursor:default}.hub-spreadsheet__cell--muted{color:var(--hub-spreadsheet-readonly-color)}.hub-spreadsheet__cell--invalid{background-image:linear-gradient(var(--hub-spreadsheet-invalid-bg),var(--hub-spreadsheet-invalid-bg));color:var(--hub-spreadsheet-invalid-color);box-shadow:inset 0 0 0 var(--hub-spreadsheet-invalid-border-width) var(--hub-spreadsheet-invalid-color)}.hub-spreadsheet__cell--active{outline:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);outline-offset:calc(var(--hub-spreadsheet-cursor-width) * -1);z-index:2}.hub-spreadsheet__cell--editing{block-size:calc(var(--hub-spreadsheet-cell-line-height) + var(--hub-spreadsheet-cell-padding-y) * 2 + var(--hub-spreadsheet-cell-edge-block))}.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-row{z-index:3}.hub-spreadsheet__cell[data-state]:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;inline-size:var(--hub-spreadsheet-state-bar-width)}.hub-spreadsheet__cell[data-state=pending]:before{background:var(--hub-spreadsheet-state-pending-color)}.hub-spreadsheet__cell[data-state=saving]:before{background:var(--hub-spreadsheet-state-saving-color)}.hub-spreadsheet__cell[data-state=saved]:before{background:var(--hub-spreadsheet-state-saved-color)}.hub-spreadsheet__cell[data-state=error]:before{background:var(--hub-spreadsheet-state-error-color)}.hub-spreadsheet__cell[data-state=conflict]:before{background:repeating-linear-gradient(45deg,var(--hub-spreadsheet-state-conflict-color) 0 3px,transparent 3px 6px)}.hub-spreadsheet__editor--select{padding-inline:calc(var(--hub-spreadsheet-cell-padding-x) - 2px);appearance:auto}.hub-spreadsheet__custom-editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);display:grid;background:var(--hub-spreadsheet-editor-bg);outline:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);outline-offset:calc(var(--hub-spreadsheet-cursor-width) * -1);z-index:3;--hub-input-bg: var(--hub-spreadsheet-editor-field-bg);--hub-input-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-input-border-radius: var(--hub-spreadsheet-editor-field-border-radius);--hub-input-focus-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-input-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow);--hub-input-padding-x: var(--hub-spreadsheet-cell-padding-x);--hub-select-bg: var(--hub-spreadsheet-editor-field-bg);--hub-select-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-select-border-radius: var(--hub-spreadsheet-editor-field-border-radius);--hub-select-focus-border-color: var(--hub-spreadsheet-editor-field-border-color);--hub-select-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow);--hub-select-padding-x: var(--hub-spreadsheet-cell-padding-x)}.hub-spreadsheet__spacer>td{padding:0;border:0;background:var(--hub-spreadsheet-bg)}.hub-spreadsheet__affordance{position:absolute;inset-block:0;inset-inline-end:calc(var(--hub-spreadsheet-affordance-field-border) - var(--hub-spreadsheet-cell-edge-inline));display:flex;align-items:center;padding-inline:var(--hub-spreadsheet-affordance-gap);cursor:pointer;opacity:0}.hub-spreadsheet__cell:hover>.hub-spreadsheet__affordance,.hub-spreadsheet__cell--active>.hub-spreadsheet__affordance{opacity:1}.hub-spreadsheet__affordance:after{content:\"\";inline-size:0;block-size:0;border-style:solid;border-width:var(--hub-spreadsheet-affordance-size) var(--hub-spreadsheet-affordance-size) calc(var(--hub-spreadsheet-affordance-size) / 2);border-color:var(--hub-spreadsheet-affordance-color) transparent transparent}.hub-spreadsheet__affordance--date:after{inline-size:calc(var(--hub-spreadsheet-affordance-size) * 2);block-size:calc(var(--hub-spreadsheet-affordance-size) * 1.8);border:1px solid var(--hub-spreadsheet-affordance-color);border-block-start-width:3px;border-radius:2px}.hub-spreadsheet__placeholder{position:absolute;inset-block:0;inset-inline:var(--hub-spreadsheet-cell-padding-x);display:flex;align-items:center;color:var(--hub-spreadsheet-placeholder-color);opacity:0;pointer-events:none;transition:var(--hub-spreadsheet-placeholder-transition)}.hub-spreadsheet__cell:hover>.hub-spreadsheet__placeholder{opacity:var(--hub-spreadsheet-placeholder-opacity)}.hub-spreadsheet__check{inline-size:1em;block-size:1em;accent-color:var(--hub-spreadsheet-cursor-color);cursor:pointer}.hub-spreadsheet__check:disabled{cursor:default}.hub-spreadsheet__resize-grip{position:absolute;inset-block:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-resize-grip-width);cursor:col-resize;background:transparent;z-index:5}.hub-spreadsheet__resize-grip:hover,.hub-spreadsheet__resize-grip:active{background:var(--hub-spreadsheet-resize-grip-color)}.hub-spreadsheet__fill-handle{position:absolute;inset-block-end:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-fill-handle-size);block-size:var(--hub-spreadsheet-fill-handle-size);background:var(--hub-spreadsheet-fill-handle-color);cursor:crosshair;z-index:4}.hub-spreadsheet__secondary{display:block;opacity:.75}.hub-spreadsheet__editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);padding:var(--hub-spreadsheet-cell-padding-y) var(--hub-spreadsheet-cell-padding-x);border:var(--hub-spreadsheet-cursor-width) solid var(--hub-spreadsheet-cursor-color);border-radius:0;background:var(--hub-spreadsheet-editor-bg);color:var(--hub-spreadsheet-editor-color);font:inherit;font-variant-numeric:tabular-nums;text-align:inherit;outline:none;z-index:2}.hub-spreadsheet__editor[aria-invalid=true]{border-color:var(--hub-spreadsheet-invalid-color)}.hub-spreadsheet__empty{padding-block:calc(var(--hub-spreadsheet-cell-padding-y) * 4);color:var(--hub-spreadsheet-readonly-color);text-align:center;cursor:default}.hub-spreadsheet__menu{position:absolute;z-index:10;min-inline-size:12rem;padding-block:.25rem;border:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-menu-border-color);border-radius:var(--hub-spreadsheet-menu-radius);background:var(--hub-spreadsheet-menu-bg);color:var(--hub-spreadsheet-menu-color);box-shadow:var(--hub-spreadsheet-menu-shadow)}.hub-spreadsheet__menu-item{display:block;inline-size:100%;padding:var(--hub-spreadsheet-menu-item-padding-y) var(--hub-spreadsheet-menu-item-padding-x);border:0;background:transparent;color:inherit;font:inherit;text-align:start;white-space:nowrap;cursor:pointer}.hub-spreadsheet__menu-item:hover,.hub-spreadsheet__menu-item:focus-visible{background:var(--hub-spreadsheet-menu-item-bg-hover)}.hub-spreadsheet__suggestions{position:absolute;inset-block-start:100%;inset-inline-start:0;z-index:12;display:flex;flex-direction:column;min-inline-size:14rem;max-block-size:14rem;overflow-y:auto;border:var(--hub-spreadsheet-border-width) solid var(--hub-spreadsheet-suggestions-border-color);border-radius:var(--hub-spreadsheet-border-radius);background:var(--hub-spreadsheet-suggestions-bg);box-shadow:var(--hub-spreadsheet-menu-shadow)}.hub-spreadsheet__suggestion{display:flex;gap:.5rem;align-items:baseline;justify-content:space-between;padding:var(--hub-spreadsheet-cell-padding-y) var(--hub-spreadsheet-cell-padding-x);border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.hub-spreadsheet__suggestion:hover,.hub-spreadsheet__suggestion--on{background:var(--hub-spreadsheet-suggestion-on-bg)}.hub-spreadsheet__suggestion-name{font-variant-numeric:tabular-nums}.hub-spreadsheet__suggestion-hint{color:var(--hub-spreadsheet-readonly-color);font-size:var(--hub-spreadsheet-header-font-size)}.hub-spreadsheet__alias{margin-inline-start:.4rem;padding:0 .3rem;border:0;border-radius:.25rem;background:var(--hub-spreadsheet-alias-bg);color:var(--hub-spreadsheet-alias-color);font:inherit;font-size:var(--hub-spreadsheet-header-font-size);font-weight:400;cursor:pointer}\n"] }]
3742
- }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: true }] }], states: [{ type: i0.Input, args: [{ isSignal: true, alias: "states", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], decimalMark: [{ type: i0.Input, args: [{ isSignal: true, alias: "decimalMark", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], frozenColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenColumns", required: false }] }], frozenRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenRows", required: false }] }], spans: [{ type: i0.Input, args: [{ isSignal: true, alias: "spans", required: false }] }], structure: [{ type: i0.Input, args: [{ isSignal: true, alias: "structure", required: false }] }], customEditors: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HubSpreadsheetEditorDirective), { isSignal: true }] }], customCells: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HubSpreadsheetCellDirective), { isSignal: true }] }], retiredColumnKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "retiredColumnKeys", required: false }] }], fillHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillHandle", required: false }] }], canUndo: [{ type: i0.Input, args: [{ isSignal: true, alias: "canUndo", required: false }] }], canRedo: [{ type: i0.Input, args: [{ isSignal: true, alias: "canRedo", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], reorderableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderableColumns", required: false }] }], columnWidths: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidths", required: false }] }, { type: i0.Output, args: ["columnWidthsChange"] }], minColumnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minColumnWidth", required: false }] }], contextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenu", required: false }] }], mergeable: [{ type: i0.Input, args: [{ isSignal: true, alias: "mergeable", required: false }] }], formulas: [{ type: i0.Input, args: [{ isSignal: true, alias: "formulas", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], editOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "editOn", required: false }] }], commit: [{ type: i0.Output, args: ["commit"] }], pasted: [{ type: i0.Output, args: ["pasted"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], insertRequested: [{ type: i0.Output, args: ["insertRequested"] }], deleteRequested: [{ type: i0.Output, args: ["deleteRequested"] }], filled: [{ type: i0.Output, args: ["filled"] }], undoRequested: [{ type: i0.Output, args: ["undoRequested"] }], redoRequested: [{ type: i0.Output, args: ["redoRequested"] }], columnMoved: [{ type: i0.Output, args: ["columnMoved"] }], mergeRequested: [{ type: i0.Output, args: ["mergeRequested"] }], unmergeRequested: [{ type: i0.Output, args: ["unmergeRequested"] }], viewport: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }] } });
4732
+ }, template: "<!--\n\tThe off-screen field exists for one reason: a browser only dispatches `paste` to something\n\teditable. The paste shortcut parks the focus here for the length of the event and the cell\n\ttakes it straight back, so the reader never notices and the grid keeps its own focus model.\n-->\n<textarea class=\"hub-spreadsheet__sink\" tabindex=\"-1\" aria-hidden=\"true\" readonly (paste)=\"onPaste($event)\"></textarea>\n\n<div class=\"hub-spreadsheet__viewport\" #viewport (scroll)=\"onViewportScroll()\">\n\t<table\n\t\tclass=\"hub-spreadsheet__table\"\n\t\trole=\"grid\"\n\t\t[attr.aria-rowcount]=\"bounds().rows\"\n\t\t[attr.aria-colcount]=\"bounds().cols\"\n\t\t[attr.aria-readonly]=\"readonly() || null\"\n\t\t(keydown)=\"onKeydown($event)\"\n\t\t(copy)=\"onCopy($event, false)\"\n\t\t(cut)=\"onCopy($event, true)\"\n\t>\n\t\t<thead class=\"hub-spreadsheet__head\">\n\t\t\t<tr role=\"row\">\n\t\t\t\t<!-- Holds the width of the columns that are not drawn, so the sheet is as wide as it\n\t\t\t\tsays it is and the ones that are drawn land where they belong. -->\n\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t<!-- A minimum, not a width: in a table that shares its space out, a width is a\n\t\t\t\t\tsuggestion the algorithm is free to ignore, and an empty cell asked for three\n\t\t\t\t\tthousand pixels came out two wide. -->\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().before\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\n\t\t\t\t@for (columnIndex of drawnColumns(); track trackColumn(columnIndex, columns()[columnIndex])) {\n\t\t\t\t\t@let column = columns()[columnIndex];\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__header\"\n\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen]=\"isFrozenColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--frozen-edge]=\"isColumnEdge(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t[class.hub-spreadsheet__grouped]=\"groupedColumns().has(columnIndex)\"\n\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(columnIndex)\"\n\t\t\t\t\t\t[style.min-width]=\"column.minWidth\"\n\t\t\t\t\t\t[style.width.px]=\"widthOf(columnIndex)\"\n\t\t\t\t\t\t[attr.data-header]=\"columnIndex\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--end-of-sheet]=\"columnIndex === bounds().cols - 1\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--moving]=\"isMovingColumn(columnIndex)\"\n\t\t\t\t\t\t[class.hub-spreadsheet__header--move-target]=\"isMoveTarget(columnIndex)\"\n\t\t\t\t\t\t(mousedown)=\"onHeaderMouseDown($event, columnIndex)\"\n\t\t\t\t\t\t(mouseenter)=\"onHeaderMouseEnter(columnIndex)\"\n\t\t\t\t\t\t[attr.aria-colindex]=\"columnIndex + 1\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<span [class.hub-spreadsheet__header-text--hidden]=\"column.headerHidden\">\n\t\t\t\t\t\t\t{{ column.header }}\n\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t@if (column.subheader) {\n\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__subheader\">{{ column.subheader }}</small>\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@if (resizableColumns()) {\n\t\t\t\t\t\t\t<!-- Sits inside the header's trailing edge. Hidden from assistive technology:\n\t\t\t\t\t\t\ta width is a matter of taste, and a screen reader has no use for a drag\n\t\t\t\t\t\t\ttarget it cannot aim at. -->\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__resize-grip\"\n\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onResizeStart($event, columnIndex)\"\n\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t}\n\t\t\t\t\t</th>\n\t\t\t\t}\n\n\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t<th\n\t\t\t\t\t\tclass=\"hub-spreadsheet__spacer\"\n\t\t\t\t\t\trole=\"presentation\"\n\t\t\t\t\t\t[style.min-inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t\t[style.inline-size.px]=\"columnWindow().after\"\n\t\t\t\t\t></th>\n\t\t\t\t}\n\t\t\t</tr>\n\t\t</thead>\n\n\t\t<tbody class=\"hub-spreadsheet__body\">\n\t\t\t<!-- The rows above the window are not in the document; this holds their height, so the\n\t\t\tscrollbar still says how long the sheet is and the rows that are drawn land where they\n\t\t\tbelong. Hidden from assistive technology, which reads `aria-rowindex` instead. -->\n\t\t\t@if (rowWindow().before > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().before\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\n\t\t\t@for (rowIndex of drawnRows(); track trackRow(rowIndex)) {\n\t\t\t\t<tr\n\t\t\t\t\tclass=\"hub-spreadsheet__row\"\n\t\t\t\t\trole=\"row\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t[class.hub-spreadsheet__row--frozen-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t[attr.aria-rowindex]=\"rowIndex + 1\"\n\t\t\t\t>\n\t\t\t\t\t@if (columnWindow().before > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\n\t\t\t\t\t@for (colIndex of drawnColumns(); track trackColumn(colIndex, columns()[colIndex])) {\n\t\t\t\t\t\t@let column = columns()[colIndex];\n\t\t\t\t\t\t<!-- A cell swallowed by a merge is not rendered at all: the anchor's rowspan and\n\t\t\t\t\t\tcolspan already occupy its place, and drawing it would push the row out of shape. -->\n\t\t\t\t\t\t@if (!isHidden(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t@let cell = grid()[rowIndex][colIndex];\n\t\t\t\t\t\t\t@let state = stateOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let error = errorOf(rowIndex, colIndex);\n\t\t\t\t\t\t\t@let editable = canEdit(rowIndex, colIndex);\n\t\t\t\t\t\t\t<!-- Worked out once per cell, and only for the one that is open: the config a\n\t\t\t\t\t\tregistered control adapter is asked for, or null when there is nothing to host. -->\n\t\t\t\t\t\t\t@let hosted = editing() && isActive(rowIndex, colIndex) ? hostedControl(rowIndex, colIndex) : null;\n\n\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__cell\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__grouped]=\"groupedColumns().has(colIndex) || groupedRows().has(rowIndex)\"\n\t\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\t\t[attr.data-cell]=\"rowIndex + '-' + colIndex\"\n\t\t\t\t\t\t\t\t[attr.tabindex]=\"tabIndexOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colindex]=\"colIndex + 1\"\n\t\t\t\t\t\t\t\t[attr.rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-rowspan]=\"rowSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-colspan]=\"colSpanOf(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[attr.aria-readonly]=\"!editable || null\"\n\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"(isActive(rowIndex, colIndex) && invalid()) || error ? true : null\"\n\t\t\t\t\t\t\t\t[attr.aria-busy]=\"state === 'saving' || null\"\n\t\t\t\t\t\t\t\t[attr.title]=\"error ?? cell.title\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--active]=\"isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--editing]=\"editing() && isActive(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--selected]=\"isSelected(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--pointed]=\"isPointed(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end-of-sheet]=\"reachesEnd(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--filling]=\"isFillPreview(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--readonly]=\"!editable\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--muted]=\"cell.muted\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--end]=\"column.align === 'end'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--center]=\"column.align === 'center'\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col]=\"isFrozenColumn(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row]=\"isFrozenRow(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-col-edge]=\"isColumnEdge(colIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--frozen-row-edge]=\"isRowEdge(rowIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-inline-start.px]=\"columnOffsetOf(colIndex)\"\n\t\t\t\t\t\t\t\t[style.inset-block-start.px]=\"rowOffsetOf(rowIndex)\"\n\t\t\t\t\t\t\t\t[class.hub-spreadsheet__cell--invalid]=\"\n\t\t\t\t\t\t\t\t\t(isActive(rowIndex, colIndex) && invalid()) ||\n\t\t\t\t\t\t\t\t\t!!error ||\n\t\t\t\t\t\t\t\t\thasFormulaError(rowIndex, colIndex)\n\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t\t[attr.data-state]=\"state\"\n\t\t\t\t\t\t\t\t(mousedown)=\"onMouseDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseenter)=\"onMouseEnter(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(mouseup)=\"onMouseUp($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t(dblclick)=\"onDoubleClick()\"\n\t\t\t\t\t\t\t\t(contextmenu)=\"onContextMenu($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t@if (editing() && isActive(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t@if (editorFor(colIndex); as custom) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host supplied an editor for this column. It reports a value through\n\t\t\t\t\t\t\t\t\t`commit`, or walks away through `cancel`; until it does one of the two the\n\t\t\t\t\t\t\t\t\teditor stays open, so a picker may take as many clicks as it needs. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"custom.template\"\n\t\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"editorContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (hosted) {\n\t\t\t\t\t\t\t\t\t\t<!-- A control adapter is registered and this column is a list, so the cell\n\t\t\t\t\t\t\t\t\topens with whatever the project wired instead of the native list. The\n\t\t\t\t\t\t\t\t\twrapper is the supplied editor's, because from the sheet's side this is\n\t\t\t\t\t\t\t\t\tone: something it did not build, filling the cell. -->\n\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__custom-editor\">\n\t\t\t\t\t\t\t\t\t\t\t<ng-container [hubSpreadsheetControl]=\"hosted\" />\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t} @else if (column.kind === 'select') {\n\t\t\t\t\t\t\t\t\t\t<select\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor hub-spreadsheet__editor--select\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"commitFromEditor($any($event.target).value || null)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<option value=\"\" [selected]=\"cell.value === null\"></option>\n\t\t\t\t\t\t\t\t\t\t\t@for (option of column.options ?? []; track option.value) {\n\t\t\t\t\t\t\t\t\t\t\t\t<option\n\t\t\t\t\t\t\t\t\t\t\t\t\t[value]=\"option.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[selected]=\"isChosen(option, column)\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"option.disabled && option.value !== cell.value\"\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{{ option.label }}\n\t\t\t\t\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t@if (writingFormula()) {\n\t\t\t\t\t\t\t\t\t\t\t<!-- The draft again, in colour, drawn behind the field \u2014 which keeps the\n\t\t\t\t\t\t\t\t\t\ttext transparent so this is what the reader sees. Named by kind so a\n\t\t\t\t\t\t\t\t\t\tfunction, a column alias and a place are told apart at a glance. Hidden\n\t\t\t\t\t\t\t\t\t\tfrom assistive technology: it is a second copy of the field's own text. -->\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"hub-spreadsheet__tokens\" aria-hidden=\"true\" #tokens>\n\t\t\t\t\t\t\t\t\t\t\t\t@for (token of draftTokens(); track $index) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t[class]=\"'hub-spreadsheet__token hub-spreadsheet__token--' + token.kind\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>{{ token.text }}</span\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__editor\"\n\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__editor--coloured]=\"writingFormula()\"\n\t\t\t\t\t\t\t\t\t\t\t[type]=\"column.kind === 'date' ? 'date' : 'text'\"\n\t\t\t\t\t\t\t\t\t\t\t[value]=\"draft()\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-invalid]=\"invalid() || null\"\n\t\t\t\t\t\t\t\t\t\t\t(input)=\"onDraftInput($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(scroll)=\"syncTokens($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t\t\t\t\t\t\t\t\t\t(blur)=\"onEditorBlur()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t@if (column.kind === 'boolean') {\n\t\t\t\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__check\"\n\t\t\t\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\t\t\t\t[checked]=\"isOn(cell)\"\n\t\t\t\t\t\t\t\t\t\t\t[disabled]=\"!editable\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"column.header\"\n\t\t\t\t\t\t\t\t\t\t\t(change)=\"toggleBoolean(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else if (cellTemplateFor(colIndex); as own) {\n\t\t\t\t\t\t\t\t\t\t<!-- The host draws this column's cells. The value still travels to the\n\t\t\t\t\t\t\t\t\tclipboard rather than the drawing, and the cell is still what the\n\t\t\t\t\t\t\t\t\tkeyboard is on: the template is inside it, not instead of it. -->\n\t\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"own.template\"\n\t\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"cellContext(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__value\">{{ displayOf(cell, column) }}</span>\n\n\t\t\t\t\t\t\t\t\t\t@if (column.placeholder && !displayOf(cell, column)) {\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"hub-spreadsheet__placeholder\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t{{ column.placeholder }}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t<!-- Says the cell opens onto a list or a calendar rather than a field, and\n\t\t\t\t\t\t\t\t\topens it on one click \u2014 as a spreadsheet drops the list of a cell that\n\t\t\t\t\t\t\t\t\thas one. Shown on the cell the reader is on and on the one under the\n\t\t\t\t\t\t\t\t\tpointer. Hidden from assistive technology: the keyboard opens the same\n\t\t\t\t\t\t\t\t\tcell with Alt and the down arrow, and reads the editor itself. -->\n\t\t\t\t\t\t\t\t\t\t@if (editable && affordanceOf(column, colIndex); as affordance) {\n\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__affordance\"\n\t\t\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__affordance--date]=\"affordance === 'date'\"\n\t\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onAffordanceDown($event, rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (cell.secondary) {\n\t\t\t\t\t\t\t\t\t\t<small class=\"hub-spreadsheet__secondary\">{{ cell.secondary }}</small>\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@if (hasFillHandle(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t\t<!-- Drawn inside the corner cell so it travels with the selection. Hidden from\n\t\t\t\t\t\t\t\t\tassistive technology: filling is available from the keyboard through the\n\t\t\t\t\t\t\t\t\tclipboard, and a screen reader has no use for a drag target. -->\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__fill-handle\"\n\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t(mousedown)=\"onFillHandleDown($event)\"\n\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t<!-- A cell worked out from a formula rather than typed, marked with a small `fx`\n\t\t\t\t\t\t\t\tin its corner. Shown on the cell under the pointer only: a mark on every formula\n\t\t\t\t\t\t\t\tcell at once would read as content. Pressing it opens the cell for writing, if the\n\t\t\t\t\t\t\t\tformula can be edited at all. Hidden from assistive technology \u2014 the value is what\n\t\t\t\t\t\t\t\tmatters, and the marker says nothing a reader needs. -->\n\t\t\t\t\t\t\t\t@if (isFormulaCell(rowIndex, colIndex)) {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-spreadsheet__fx\"\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t[class.hub-spreadsheet__fx--editable]=\"canEdit(rowIndex, colIndex)\"\n\t\t\t\t\t\t\t\t\t\t(mousedown)=\"editFormula(rowIndex, colIndex, $event)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t@if (columnWindow().after > 0) {\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\"></td>\n\t\t\t\t\t}\n\t\t\t\t</tr>\n\n\t\t\t\t<!-- What the row holds behind its figures, opened in place. A dialog used to be the\n\t\t\t\tonly shape for this and it is the wrong one: it hides the row it is about and cannot\n\t\t\t\tbe read against the one above. -->\n\t\t\t\t@if (expansion() && isExpanded(rowIndex)) {\n\t\t\t\t\t<tr class=\"hub-spreadsheet__expansion\" role=\"row\">\n\t\t\t\t\t\t<td class=\"hub-spreadsheet__expansion-cell\" role=\"gridcell\" [attr.colspan]=\"drawnCellCount()\">\n\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"expansion()!\"\n\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"expansionContext(rowIndex)\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</td>\n\t\t\t\t\t</tr>\n\t\t\t\t}\n\t\t\t} @empty {\n\t\t\t\t<tr class=\"hub-spreadsheet__row hub-spreadsheet__row--empty\" role=\"row\">\n\t\t\t\t\t<td class=\"hub-spreadsheet__empty\" role=\"gridcell\" [attr.colspan]=\"bounds().cols || 1\">\n\t\t\t\t\t\t{{ emptyText() }}\n\t\t\t\t\t</td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t\t@if (rowWindow().after > 0) {\n\t\t\t\t<tr class=\"hub-spreadsheet__spacer\" role=\"presentation\" aria-hidden=\"true\">\n\t\t\t\t\t<td [attr.colspan]=\"drawnCellCount()\" [style.block-size.px]=\"rowWindow().after\"></td>\n\t\t\t\t</tr>\n\t\t\t}\n\t\t</tbody>\n\t</table>\n</div>\n\n<!-- The coordinates, outside the sheet's frame while a formula is written: a column's letter and the\nalias a formula calls it by over the table, the row's number down its leading edge. The viewport\nshifts to make room, so the frame stays between these and the table. Measured off the drawn table\n(the grid already names every cell by its indices, so these are decoration), and the column badge is\nclickable to write its alias into the formula. -->\n@if (writingFormula()) {\n\t<div class=\"hub-spreadsheet__coords\" aria-hidden=\"true\">\n\t\t@for (column of coordColumns(); track $index) {\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__coord hub-spreadsheet__coord--column\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[style.--coord-start]=\"column.start + 'px'\"\n\t\t\t\t[style.--coord-width]=\"column.width + 'px'\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); insertAlias(column.alias)\"\n\t\t\t>\n\t\t\t\t<span class=\"hub-spreadsheet__coord-letter\">{{ column.letter }}</span>\n\t\t\t\t<span class=\"hub-spreadsheet__coord-alias\">{{ column.alias }}</span>\n\t\t\t</button>\n\t\t}\n\n\t\t@for (row of coordRows(); track $index) {\n\t\t\t<span class=\"hub-spreadsheet__coord-row\" [style.inset-block-start.px]=\"row.top\" [style.block-size.px]=\"row.height\">\n\t\t\t\t<span class=\"hub-spreadsheet__coord hub-spreadsheet__coord--row\">{{ row.number }}</span>\n\t\t\t</span>\n\t\t}\n\t</div>\n}\n\n@if (expandToggle() && expansion()) {\n\t<!-- PROTOTYPE: a floating toggle per row, over the frame's leading edge, that opens and closes\n\tthe row's expansion. Its place is measured against the frame and put back on every scroll, so it\n\tstays where its row is rather than travelling with the cells. -->\n\t<div class=\"hub-spreadsheet__toggles\">\n\t\t@for (toggle of expansionToggles(); track toggle.key) {\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__toggle hub-spreadsheet__toggle--start\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-block-start.px]=\"toggle.top\"\n\t\t\t\t[style.block-size.px]=\"toggle.height\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"expandToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleByKey(toggle.key)\"\n\t\t\t></button>\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__toggle hub-spreadsheet__toggle--end\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-block-start.px]=\"toggle.top\"\n\t\t\t\t[style.block-size.px]=\"toggle.height\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"expandToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleByKey(toggle.key)\"\n\t\t\t></button>\n\t\t}\n\t</div>\n}\n\n@if (rowGroupToggles().length) {\n\t<!-- The row groups: a line across the sheet where a run is folded away, and a button at the\n\tmiddle of the row whose fold it is. -->\n\t<div class=\"hub-spreadsheet__row-toggles\">\n\t\t@for (toggle of rowGroupToggles(); track toggle.id) {\n\t\t\t@if (!toggle.open) {\n\t\t\t\t<span\n\t\t\t\t\tclass=\"hub-spreadsheet__fold hub-spreadsheet__fold--row\"\n\t\t\t\t\t[style.inset-block-start.px]=\"toggle.toggle\"\n\t\t\t\t></span>\n\t\t\t}\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__row-toggle\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__row-toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-block-start.px]=\"toggle.toggle\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"rowToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleRowGroup(toggle.id)\"\n\t\t\t></button>\n\t\t}\n\t</div>\n}\n\n@if (columnToggles().length) {\n\t<!-- The column groups: a bracket over the headers across the run it folds \u2014 a coloured mark\n\twider than zero once the run is folded away \u2014 and a button at the bracket's end. -->\n\t<div class=\"hub-spreadsheet__column-toggles\">\n\t\t@for (toggle of columnToggles(); track toggle.id) {\n\t\t\t@if (!toggle.open) {\n\t\t\t\t<span class=\"hub-spreadsheet__fold\" [style.inset-inline-start.px]=\"toggle.toggle\"></span>\n\t\t\t}\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__column-toggle\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__column-toggle--open]=\"toggle.open\"\n\t\t\t\t[style.inset-inline-start.px]=\"toggle.toggle\"\n\t\t\t\t[attr.aria-expanded]=\"toggle.open\"\n\t\t\t\t[attr.aria-label]=\"columnToggleLabel() || null\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); $event.stopPropagation()\"\n\t\t\t\t(click)=\"toggleColumnGroup(toggle.id)\"\n\t\t\t></button>\n\t\t}\n\t</div>\n}\n\n@if (suggesting()) {\n\t<!-- What can go into the formula being written. Anchored to the host, never to the cell: drawn\n\tinside the cell the scroll area cut it, and on the last row \u2014 the row a total is written in \u2014\n\tit was never seen at all. The position is the component's; these are the looks. Hidden from\n\tassistive technology: the field owns the conversation. -->\n\t<div\n\t\tclass=\"hub-spreadsheet__suggestions\"\n\t\trole=\"presentation\"\n\t\taria-hidden=\"true\"\n\t\t[style.inset-inline-start.px]=\"suggestionsAt()?.x\"\n\t\t[style.inset-block-start.px]=\"suggestionsAt()?.top\"\n\t\t[style.inset-block-end.px]=\"suggestionsAt()?.bottom\"\n\t\t[style.max-inline-size.px]=\"suggestionsAt()?.maxWidth\"\n\t>\n\t\t@for (suggestion of suggestions(); track suggestion.label; let index = $index) {\n\t\t\t<button\n\t\t\t\tclass=\"hub-spreadsheet__suggestion\"\n\t\t\t\ttype=\"button\"\n\t\t\t\ttabindex=\"-1\"\n\t\t\t\t[class.hub-spreadsheet__suggestion--on]=\"index === suggestionIndex()\"\n\t\t\t\t(mousedown)=\"$event.preventDefault(); useSuggestion(suggestion)\"\n\t\t\t>\n\t\t\t\t<span class=\"hub-spreadsheet__suggestion-name\">{{ suggestion.label }}</span>\n\t\t\t\t@if (suggestion.hint) {\n\t\t\t\t\t<span class=\"hub-spreadsheet__suggestion-hint\">{{ suggestion.hint }}</span>\n\t\t\t\t}\n\t\t\t</button>\n\t\t}\n\t</div>\n}\n\n@if (menuAt(); as at) {\n\t<!-- Positioned against the host, which is the sheet's own corner, so the menu travels with a\n\tscrolled page and never has to be repositioned on scroll. -->\n\t<div\n\t\tclass=\"hub-spreadsheet__menu\"\n\t\trole=\"menu\"\n\t\t[attr.aria-label]=\"'ROW_ACTIONS' | translate\"\n\t\t[style.inset-inline-start.px]=\"at.x\"\n\t\t[style.inset-block-start.px]=\"at.y\"\n\t\t(mousedown)=\"$event.stopPropagation()\"\n\t>\n\t\t@for (entry of menuEntries(); track entry.id) {\n\t\t\t<button class=\"hub-spreadsheet__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"runMenuEntry(entry.id)\">\n\t\t\t\t{{ entry.label | translate: { count: entry.count } }}\n\t\t\t</button>\n\t\t}\n\t</div>\n}\n", styles: ["@charset \"UTF-8\";:host{position:relative;display:block;color:var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529)))}.hub-spreadsheet__sink{position:absolute;inline-size:1px;block-size:1px;padding:0;border:0;opacity:0;pointer-events:none;inset-block-start:0;inset-inline-start:0}:host(.hub-spreadsheet--virtual) .hub-spreadsheet__viewport{max-block-size:var(--hub-spreadsheet-max-block-size, var(--hub-table-container-max-block-size, 24rem))}.hub-spreadsheet__viewport{position:relative;overflow:auto;max-block-size:var(--hub-spreadsheet-max-block-size, var(--hub-table-container-max-block-size, none));border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));border-radius:var(--hub-spreadsheet-border-radius, var(--hub-table-border-radius, var(--hub-sys-radius-lg, .5rem)));background:var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))}.hub-spreadsheet__table{inline-size:100%;border-collapse:separate;border-spacing:0;font-variant-numeric:tabular-nums}.hub-spreadsheet__header,.hub-spreadsheet__cell{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px));--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px));padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border-inline-end:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));border-block-end:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));background:var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)));white-space:nowrap;text-align:start}.hub-spreadsheet__header--end-of-sheet,.hub-spreadsheet__cell--end-of-sheet{--hub-spreadsheet-cell-edge-inline: 0px;border-inline-end:0}.hub-spreadsheet__header{position:sticky;isolation:isolate;inset-block-start:0;z-index:2;background:var(--hub-spreadsheet-header-bg, var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa)));color:var(--hub-spreadsheet-header-color, var(--hub-sys-text-muted, #6a737b));font-size:var(--hub-spreadsheet-header-font-size, .8125rem);font-weight:var(--hub-spreadsheet-header-font-weight, 600)}.hub-spreadsheet__header--moving{opacity:var(--hub-spreadsheet-moving-opacity, .5)}.hub-spreadsheet__header--move-target{outline:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));outline-offset:calc(var(--hub-spreadsheet-cursor-width, 2px) * -1)}.hub-spreadsheet__header--frozen{inset-inline-start:0;z-index:4}.hub-spreadsheet__header--frozen-edge{border-inline-end:var(--hub-spreadsheet-frozen-divider-width, 2px) solid var(--hub-spreadsheet-frozen-divider-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))))}.hub-spreadsheet__header--end{text-align:end}.hub-spreadsheet__header--center{text-align:center}:host(.hub-spreadsheet--formula){padding-block-start:2.8rem;padding-inline-start:2.6rem}.hub-spreadsheet__coords{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__coord-row{position:absolute;inset-inline-start:.45rem;display:flex;align-items:center;inline-size:calc(2.6rem - .45rem * 2)}.hub-spreadsheet__coord{position:absolute;display:inline-flex;gap:.3rem;align-items:center;justify-content:center;block-size:1.9rem;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:.4rem;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529)));box-shadow:0 1px 2px #0000001f;font:inherit;font-size:var(--hub-spreadsheet-header-font-size, .8125rem);font-weight:400;font-variant-numeric:tabular-nums;line-height:1;white-space:nowrap;pointer-events:auto;animation:hub-spreadsheet-coord-in .18s ease both}.hub-spreadsheet__coord--column{inset-block-start:.45rem;inset-inline-start:calc(var(--coord-start, 0px) + .45rem);inline-size:calc(var(--coord-width, 0px) - .45rem * 2);cursor:default}.hub-spreadsheet__coord--row{position:relative;inline-size:100%}.hub-spreadsheet__toggles{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__toggle{position:absolute;display:flex;align-items:center;justify-content:center;inline-size:1rem;padding:0;border:0;background:none;color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));cursor:pointer;opacity:.5;transition:opacity .15s ease;pointer-events:auto}.hub-spreadsheet__toggle:hover,.hub-spreadsheet__toggle--open{opacity:1}.hub-spreadsheet__toggle:before{content:\"\";inline-size:.85rem;block-size:.85rem;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:50%;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));box-shadow:0 1px 2px #0000001f}.hub-spreadsheet__toggle:after{content:\"\";position:absolute;inset-block-start:50%;inset-inline-start:50%;inline-size:.26rem;block-size:.26rem;border-inline-end:1.5px solid currentColor;border-block-end:1.5px solid currentColor;transform:translate(-58%,-42%) rotate(45deg);transition:transform .15s ease}.hub-spreadsheet__toggle--open:after{transform:translate(-58%,-42%) rotate(-135deg)}.hub-spreadsheet__toggle--start{inset-inline-start:0;margin-inline-start:-.55rem}.hub-spreadsheet__toggle--end{inset-inline-end:0;margin-inline-end:-.55rem}.hub-spreadsheet__column-toggles{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__column-toggle{position:absolute;inset-block-start:-.55rem;display:flex;align-items:center;justify-content:center;inline-size:1rem;block-size:1rem;margin-inline-start:-.5rem;padding:0;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:50%;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));box-shadow:0 1px 2px #0000001f;cursor:pointer;transition:background .15s ease;pointer-events:auto}.hub-spreadsheet__column-toggle:hover{background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 18%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))))}.hub-spreadsheet__column-toggle:before,.hub-spreadsheet__column-toggle:after{content:\"\";position:absolute;background:currentColor}.hub-spreadsheet__column-toggle:before{inline-size:.5rem;block-size:1.5px}.hub-spreadsheet__column-toggle:after{inline-size:1.5px;block-size:.5rem}.hub-spreadsheet__column-toggle--open:after{display:none}.hub-spreadsheet__fold{position:absolute;inset-block:0;inline-size:2px;background:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));opacity:.6;pointer-events:none}.hub-spreadsheet__fold--row{inset-block:auto;inset-inline:0;block-size:2px;inline-size:auto}.hub-spreadsheet__cell.hub-spreadsheet__grouped,.hub-spreadsheet__header.hub-spreadsheet__grouped{background-color:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 14%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))))}.hub-spreadsheet__row-toggles{position:absolute;inset:0;z-index:3;pointer-events:none}.hub-spreadsheet__row-toggle{position:absolute;inset-inline-start:0;display:flex;align-items:center;justify-content:center;inline-size:1rem;block-size:1rem;margin-inline-start:-.5rem;transform:translateY(-50%);padding:0;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 22%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));border-radius:50%;background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 9%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));box-shadow:0 1px 2px #0000001f;cursor:pointer;transition:background .15s ease;pointer-events:auto}.hub-spreadsheet__row-toggle:hover{background:color-mix(in srgb,var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 18%,var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))))}.hub-spreadsheet__row-toggle:before,.hub-spreadsheet__row-toggle:after{content:\"\";position:absolute;background:currentColor}.hub-spreadsheet__row-toggle:before{inline-size:.5rem;block-size:1.5px}.hub-spreadsheet__row-toggle:after{inline-size:1.5px;block-size:.5rem}.hub-spreadsheet__row-toggle--open:after{display:none}.hub-spreadsheet__coord-letter{font-weight:600}.hub-spreadsheet__coord-alias{padding:0 .25rem;border-radius:.25rem;background:var(--hub-spreadsheet-alias-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent));overflow:hidden;text-overflow:ellipsis}@keyframes hub-spreadsheet-coord-in{0%{opacity:0;transform:translateY(.25rem) scale(.85)}to{opacity:1;transform:none}}.hub-spreadsheet__header-text--hidden{position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.hub-spreadsheet__subheader{display:block;font-weight:400;opacity:.8}.hub-spreadsheet__cell{position:relative;cursor:cell;-webkit-user-select:none;user-select:none}.hub-spreadsheet__cell--end{text-align:end}.hub-spreadsheet__cell--center{text-align:center}.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--frozen-row{position:sticky;z-index:1}.hub-spreadsheet__cell--frozen-col{inset-inline-start:0}.hub-spreadsheet__cell--frozen-col.hub-spreadsheet__cell--frozen-row{z-index:2}.hub-spreadsheet__cell--frozen-col-edge{--hub-spreadsheet-cell-edge-inline: var(--hub-spreadsheet-frozen-divider-width, 2px);border-inline-end:var(--hub-spreadsheet-frozen-divider-width, 2px) solid var(--hub-spreadsheet-frozen-divider-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))))}.hub-spreadsheet__cell--frozen-row-edge{--hub-spreadsheet-cell-edge-block: var(--hub-spreadsheet-frozen-divider-width, 2px);border-block-end:var(--hub-spreadsheet-frozen-divider-width, 2px) solid var(--hub-spreadsheet-frozen-divider-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))))}.hub-spreadsheet__cell--filling{background-image:linear-gradient(var(--hub-spreadsheet-fill-preview-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 6%, transparent)),var(--hub-spreadsheet-fill-preview-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 6%, transparent)))}.hub-spreadsheet__cell--selected{background-image:linear-gradient(var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)),var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)))}.hub-spreadsheet__cell--pointed{background-image:linear-gradient(var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)),var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)));box-shadow:inset 0 0 0 var(--hub-spreadsheet-cursor-width, 2px) var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd))}.hub-spreadsheet__cell--readonly{background-color:var(--hub-spreadsheet-readonly-bg, color-mix(in srgb, var(--hub-spreadsheet-header-bg, var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa))) 55%, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))));color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b));cursor:default}.hub-spreadsheet__cell--muted{color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b))}.hub-spreadsheet__cell--invalid{background-image:linear-gradient(var(--hub-spreadsheet-invalid-bg, color-mix(in srgb, var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e)) 10%, transparent)),var(--hub-spreadsheet-invalid-bg, color-mix(in srgb, var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e)) 10%, transparent)));color:var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e));box-shadow:inset 0 0 0 var(--hub-spreadsheet-invalid-border-width, 1px) var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e))}.hub-spreadsheet__cell--active{outline:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));outline-offset:calc(var(--hub-spreadsheet-cursor-width, 2px) * -1);z-index:2}.hub-spreadsheet__cell--editing{block-size:calc(var(--hub-spreadsheet-cell-line-height, 1.5em) + var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) * 2 + var(--hub-spreadsheet-cell-edge-block))}.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-col,.hub-spreadsheet__cell--active.hub-spreadsheet__cell--frozen-row{z-index:3}.hub-spreadsheet__cell[data-state]:before{content:\"\";position:absolute;inset-block:0;inset-inline-start:0;inline-size:var(--hub-spreadsheet-state-bar-width, 3px)}.hub-spreadsheet__cell[data-state=pending]:before{background:var(--hub-spreadsheet-state-pending-color, var(--hub-sys-color-warning, #cc8a00))}.hub-spreadsheet__cell[data-state=saving]:before{background:var(--hub-spreadsheet-state-saving-color, var(--hub-sys-color-info, #0a7ea4))}.hub-spreadsheet__cell[data-state=saved]:before{background:var(--hub-spreadsheet-state-saved-color, var(--hub-sys-color-success, #1a7f37))}.hub-spreadsheet__cell[data-state=error]:before{background:var(--hub-spreadsheet-state-error-color, var(--hub-sys-color-danger, #b3261e))}.hub-spreadsheet__cell[data-state=conflict]:before{background:repeating-linear-gradient(45deg,var(--hub-spreadsheet-state-conflict-color, var(--hub-sys-color-danger, #b3261e)) 0 3px,transparent 3px 6px)}.hub-spreadsheet__editor--select{padding-inline:calc(var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem)) - 2px);appearance:auto}.hub-spreadsheet__custom-editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);display:grid;background:var(--hub-spreadsheet-editor-bg, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));outline:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));outline-offset:calc(var(--hub-spreadsheet-cursor-width, 2px) * -1);z-index:3;--hub-input-bg: var(--hub-spreadsheet-editor-field-bg, transparent);--hub-input-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-input-border-radius: var(--hub-spreadsheet-editor-field-border-radius, 0);--hub-input-focus-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-input-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow, none);--hub-input-padding-x: var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));--hub-select-bg: var(--hub-spreadsheet-editor-field-bg, transparent);--hub-select-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-select-border-radius: var(--hub-spreadsheet-editor-field-border-radius, 0);--hub-select-focus-border-color: var(--hub-spreadsheet-editor-field-border-color, transparent);--hub-select-focus-box-shadow: var(--hub-spreadsheet-editor-field-focus-shadow, none);--hub-select-padding-x: var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem))}.hub-spreadsheet__spacer>td{padding:0;border:0;background:var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))}.hub-spreadsheet__affordance{position:absolute;inset-block:0;inset-inline-end:calc(var(--hub-spreadsheet-affordance-field-border, var(--hub-input-border-width, 1px)) - var(--hub-spreadsheet-cell-edge-inline));display:flex;align-items:center;padding-inline:var(--hub-spreadsheet-affordance-gap, var(--hub-select-arrow-gap, .5rem));cursor:pointer;opacity:0}.hub-spreadsheet__cell:hover>.hub-spreadsheet__affordance,.hub-spreadsheet__cell--active>.hub-spreadsheet__affordance{opacity:1}.hub-spreadsheet__affordance:after{content:\"\";inline-size:0;block-size:0;border-style:solid;border-width:var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) calc(var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) / 2);border-color:var(--hub-spreadsheet-affordance-color, var(--hub-select-arrow-color, var(--hub-sys-text-muted, #6a737b))) transparent transparent}.hub-spreadsheet__affordance--date:after{inline-size:calc(var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) * 2);block-size:calc(var(--hub-spreadsheet-affordance-size, var(--hub-select-arrow-size, 5px)) * 1.8);border:1px solid var(--hub-spreadsheet-affordance-color, var(--hub-select-arrow-color, var(--hub-sys-text-muted, #6a737b)));border-block-start-width:3px;border-radius:2px}.hub-spreadsheet__placeholder{position:absolute;inset-block:0;inset-inline:var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));display:flex;align-items:center;color:var(--hub-spreadsheet-placeholder-color, var(--hub-sys-text-muted, #6a737b));opacity:0;pointer-events:none;transition:var(--hub-spreadsheet-placeholder-transition, var(--hub-sys-transition-fast, all .15s ease-in-out))}.hub-spreadsheet__cell:hover>.hub-spreadsheet__placeholder{opacity:var(--hub-spreadsheet-placeholder-opacity, .65)}.hub-spreadsheet__check{inline-size:1em;block-size:1em;accent-color:var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));cursor:pointer}.hub-spreadsheet__check:disabled{cursor:default}.hub-spreadsheet__resize-grip{position:absolute;inset-block:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-resize-grip-width, 5px);cursor:col-resize;background:transparent;z-index:5}.hub-spreadsheet__resize-grip:hover,.hub-spreadsheet__resize-grip:active{background:var(--hub-spreadsheet-resize-grip-color, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)))}.hub-spreadsheet__fill-handle{position:absolute;inset-block-end:0;inset-inline-end:0;inline-size:var(--hub-spreadsheet-fill-handle-size, 7px);block-size:var(--hub-spreadsheet-fill-handle-size, 7px);background:var(--hub-spreadsheet-fill-handle-color, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)));cursor:crosshair;z-index:4}.hub-spreadsheet__secondary{display:block;opacity:.75}.hub-spreadsheet__expansion-cell{padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border-block-end:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)));background:var(--hub-spreadsheet-header-bg, var(--hub-table-container-bg, var(--hub-sys-color-surface-subtle, #f8f9fa)));white-space:normal;vertical-align:top}.hub-spreadsheet__fx{position:absolute;inset-block-start:2px;inset-inline-end:2px;z-index:1;padding:0 .3rem;border:0;border-radius:.3rem;background:var(--hub-spreadsheet-formula-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 14%, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff)))));color:var(--hub-spreadsheet-formula-color, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)));font-size:.5625rem;font-style:italic;font-weight:600;line-height:1.4;opacity:0;pointer-events:none;transition:opacity .12s ease}.hub-spreadsheet__fx:before{content:\"fx\"}.hub-spreadsheet__cell:hover>.hub-spreadsheet__fx{opacity:1}.hub-spreadsheet__fx--editable{cursor:pointer;pointer-events:auto}.hub-spreadsheet__editor{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border:var(--hub-spreadsheet-cursor-width, 2px) solid var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd));border-radius:0;background:var(--hub-spreadsheet-editor-bg, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-editor-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))));font:inherit;font-variant-numeric:tabular-nums;text-align:inherit;outline:none;z-index:2}.hub-spreadsheet__editor[aria-invalid=true]{border-color:var(--hub-spreadsheet-invalid-color, var(--hub-sys-color-danger, #b3261e))}.hub-spreadsheet__tokens{position:absolute;inset-block-start:0;inset-inline-start:0;inset-block-end:calc(var(--hub-spreadsheet-cell-edge-block) * -1);inset-inline-end:calc(var(--hub-spreadsheet-cell-edge-inline) * -1);padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border:var(--hub-spreadsheet-cursor-width, 2px) solid transparent;border-radius:0;background:var(--hub-spreadsheet-editor-bg, var(--hub-spreadsheet-bg, var(--hub-table-bg, var(--hub-sys-color-surface, #fff))));color:var(--hub-spreadsheet-editor-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))));font:inherit;font-variant-numeric:tabular-nums;text-align:inherit;white-space:pre;overflow:hidden;pointer-events:none;z-index:1}.hub-spreadsheet__editor--coloured{background:transparent;color:transparent;caret-color:var(--hub-spreadsheet-editor-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))))}.hub-spreadsheet__token{border-radius:3px}.hub-spreadsheet__token--function{color:var(--hub-spreadsheet-token-function-color, var(--hub-sys-color-primary, #0d6efd));background:color-mix(in srgb,var(--hub-spreadsheet-token-function-color, var(--hub-sys-color-primary, #0d6efd)) 14%,transparent)}.hub-spreadsheet__token--column{color:var(--hub-spreadsheet-token-column-color, var(--hub-sys-color-info, #0a7ea4));background:color-mix(in srgb,var(--hub-spreadsheet-token-column-color, var(--hub-sys-color-info, #0a7ea4)) 14%,transparent)}.hub-spreadsheet__token--coordinate{color:var(--hub-spreadsheet-token-coordinate-color, var(--hub-sys-color-success, #1a7f37));background:color-mix(in srgb,var(--hub-spreadsheet-token-coordinate-color, var(--hub-sys-color-success, #1a7f37)) 14%,transparent)}.hub-spreadsheet__token--text{color:var(--hub-spreadsheet-token-text-color, var(--hub-sys-color-danger, #b3261e));background:color-mix(in srgb,var(--hub-spreadsheet-token-text-color, var(--hub-sys-color-danger, #b3261e)) 12%,transparent)}.hub-spreadsheet__token--operator,.hub-spreadsheet__token--equals,.hub-spreadsheet__token--paren,.hub-spreadsheet__token--separator{color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b))}.hub-spreadsheet__empty{padding-block:calc(var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) * 4);color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b));text-align:center;cursor:default}.hub-spreadsheet__menu{position:absolute;z-index:10;min-inline-size:12rem;padding-block:.25rem;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-menu-border-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6))));border-radius:var(--hub-spreadsheet-menu-radius, var(--hub-sys-radius-sm, .25rem));background:var(--hub-spreadsheet-menu-bg, var(--hub-sys-color-surface, #fff));color:var(--hub-spreadsheet-menu-color, var(--hub-spreadsheet-color, var(--hub-table-color, var(--hub-sys-color-text, #212529))));box-shadow:var(--hub-spreadsheet-menu-shadow, 0 6px 20px rgba(0, 0, 0, .18))}.hub-spreadsheet__menu-item{display:block;inline-size:100%;padding:var(--hub-spreadsheet-menu-item-padding-y, .4rem) var(--hub-spreadsheet-menu-item-padding-x, .85rem);border:0;background:transparent;color:inherit;font:inherit;text-align:start;white-space:nowrap;cursor:pointer}.hub-spreadsheet__menu-item:hover,.hub-spreadsheet__menu-item:focus-visible{background:var(--hub-spreadsheet-menu-item-bg-hover, var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)))}.hub-spreadsheet__suggestions{position:fixed;z-index:1000;display:flex;flex-direction:column;min-inline-size:14rem;max-block-size:14rem;overflow-y:auto;border:var(--hub-spreadsheet-border-width, var(--hub-table-border-width, 1px)) solid var(--hub-spreadsheet-suggestions-border-color, var(--hub-spreadsheet-menu-border-color, var(--hub-spreadsheet-border-color, var(--hub-table-border-color, var(--hub-sys-border-color-default, #dee2e6)))));border-radius:var(--hub-spreadsheet-border-radius, var(--hub-table-border-radius, var(--hub-sys-radius-lg, .5rem)));background:var(--hub-spreadsheet-suggestions-bg, var(--hub-spreadsheet-menu-bg, var(--hub-sys-color-surface, #fff)));box-shadow:var(--hub-spreadsheet-menu-shadow, 0 6px 20px rgba(0, 0, 0, .18))}.hub-spreadsheet__suggestion{display:flex;gap:.5rem;align-items:baseline;justify-content:space-between;padding:var(--hub-spreadsheet-cell-padding-y, var(--hub-table-cell-padding-y, .4rem)) var(--hub-spreadsheet-cell-padding-x, var(--hub-table-cell-padding-x, .6rem));border:0;background:transparent;color:inherit;font:inherit;text-align:start;cursor:pointer}.hub-spreadsheet__suggestion:hover,.hub-spreadsheet__suggestion--on{background:var(--hub-spreadsheet-suggestion-on-bg, var(--hub-spreadsheet-selection-bg, color-mix(in srgb, var(--hub-spreadsheet-cursor-color, var(--hub-sys-color-primary, #0d6efd)) 12%, transparent)))}.hub-spreadsheet__suggestion-name{font-variant-numeric:tabular-nums}.hub-spreadsheet__suggestion-hint{color:var(--hub-spreadsheet-readonly-color, var(--hub-sys-text-muted, #6a737b));font-size:var(--hub-spreadsheet-header-font-size, .8125rem)}\n"] }]
4733
+ }], ctorParameters: () => [], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: true }] }], states: [{ type: i0.Input, args: [{ isSignal: true, alias: "states", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], decimalMark: [{ type: i0.Input, args: [{ isSignal: true, alias: "decimalMark", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], frozenColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenColumns", required: false }] }], frozenRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozenRows", required: false }] }], spans: [{ type: i0.Input, args: [{ isSignal: true, alias: "spans", required: false }] }], structure: [{ type: i0.Input, args: [{ isSignal: true, alias: "structure", required: false }] }], customEditors: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HubSpreadsheetEditorDirective), { isSignal: true }] }], customCells: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HubSpreadsheetCellDirective), { isSignal: true }] }], retiredColumnKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "retiredColumnKeys", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], disjointSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "disjointSelection", required: false }] }], fillHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillHandle", required: false }] }], canUndo: [{ type: i0.Input, args: [{ isSignal: true, alias: "canUndo", required: false }] }], canRedo: [{ type: i0.Input, args: [{ isSignal: true, alias: "canRedo", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], reorderableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderableColumns", required: false }] }], columnWidths: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidths", required: false }] }, { type: i0.Output, args: ["columnWidthsChange"] }], minColumnWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minColumnWidth", required: false }] }], contextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenu", required: false }] }], mergeable: [{ type: i0.Input, args: [{ isSignal: true, alias: "mergeable", required: false }] }], formulas: [{ type: i0.Input, args: [{ isSignal: true, alias: "formulas", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], editOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "editOn", required: false }] }], expansion: [{ type: i0.Input, args: [{ isSignal: true, alias: "expansion", required: false }] }], expandedRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedRow", required: false }] }, { type: i0.Output, args: ["expandedRowChange"] }], expandToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandToggle", required: false }] }], expandToggleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandToggleLabel", required: false }] }], collapsedColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedColumns", required: false }] }, { type: i0.Output, args: ["collapsedColumnsChange"] }], columnToggleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnToggleLabel", required: false }] }], rowLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowLevel", required: false }] }], collapsedRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedRows", required: false }] }, { type: i0.Output, args: ["collapsedRowsChange"] }], rowToggleLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowToggleLabel", required: false }] }], commit: [{ type: i0.Output, args: ["commit"] }], pasted: [{ type: i0.Output, args: ["pasted"] }], opened: [{ type: i0.Output, args: ["opened"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], insertRequested: [{ type: i0.Output, args: ["insertRequested"] }], deleteRequested: [{ type: i0.Output, args: ["deleteRequested"] }], filled: [{ type: i0.Output, args: ["filled"] }], undoRequested: [{ type: i0.Output, args: ["undoRequested"] }], redoRequested: [{ type: i0.Output, args: ["redoRequested"] }], columnMoved: [{ type: i0.Output, args: ["columnMoved"] }], mergeRequested: [{ type: i0.Output, args: ["mergeRequested"] }], selectionRangesChange: [{ type: i0.Output, args: ["selectionRangesChange"] }], unmergeRequested: [{ type: i0.Output, args: ["unmergeRequested"] }], viewport: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], tokens: [{ type: i0.ViewChild, args: ['tokens', { isSignal: true }] }] } });
3743
4734
 
3744
4735
  /**
3745
4736
  * Handing the reader a file to save.