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.
- package/README.md +79 -16
- package/fesm2022/ng-hub-ui-spreadsheet-signals.mjs +1 -1
- package/fesm2022/ng-hub-ui-spreadsheet-signals.mjs.map +1 -1
- package/fesm2022/ng-hub-ui-spreadsheet.mjs +1026 -35
- package/fesm2022/ng-hub-ui-spreadsheet.mjs.map +1 -1
- package/package.json +11 -8
- package/styles/index.scss +11 -0
- package/styles/mixins/_spreadsheet-theme.scss +341 -0
- package/types/ng-hub-ui-spreadsheet.d.ts +455 -7
|
@@ -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,
|
|
4
|
-
import { buildSpanMap, gridWindow, growWindowToSpans,
|
|
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
|
|
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
|
-
|
|
2663
|
+
if (!hidden.has(index)) {
|
|
2664
|
+
drawn.push(index);
|
|
2665
|
+
}
|
|
2264
2666
|
}
|
|
2265
2667
|
for (let index = window.start; index <= window.end; index++) {
|
|
2266
|
-
|
|
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
|
|
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
|
-
|
|
2695
|
+
if (!hidden.has(index)) {
|
|
2696
|
+
drawn.push(index);
|
|
2697
|
+
}
|
|
2291
2698
|
}
|
|
2292
2699
|
for (let index = window.start; index <= window.end; index++) {
|
|
2293
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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 =
|
|
3109
|
+
editor.value = text;
|
|
2383
3110
|
editor.focus();
|
|
2384
|
-
editor.setSelectionRange(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3169
|
-
|
|
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 (
|
|
4136
|
+
for (const row of table.rows) {
|
|
3175
4137
|
const line = [];
|
|
3176
|
-
for (
|
|
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
|
-
|
|
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
|
|
3704
|
-
if (!
|
|
4691
|
+
const ranges = this.selection();
|
|
4692
|
+
if (!ranges.length || this.readonly()) {
|
|
3705
4693
|
return;
|
|
3706
4694
|
}
|
|
3707
|
-
const cells =
|
|
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.
|