@gui-chat-plugin/spreadsheet 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/calculator.d.ts +15 -3
- package/dist/engine/cellBuilder.d.ts +44 -0
- package/dist/engine/cellEmpty.d.ts +13 -0
- package/dist/engine/cellFormatting.d.ts +25 -0
- package/dist/engine/coerce-boolean.d.ts +7 -0
- package/dist/engine/condition.d.ts +49 -0
- package/dist/engine/date-locale.d.ts +12 -0
- package/dist/engine/date-parser.d.ts +7 -1
- package/dist/engine/date-utils.d.ts +7 -4
- package/dist/engine/datedif.d.ts +13 -0
- package/dist/engine/evaluator.d.ts +44 -1
- package/dist/engine/financial-math.d.ts +28 -0
- package/dist/engine/formatter.d.ts +9 -0
- package/dist/engine/formulaError.d.ts +37 -0
- package/dist/engine/formulaRefs.d.ts +37 -0
- package/dist/engine/functions/lookup-math.d.ts +19 -0
- package/dist/engine/functions/statistical-math.d.ts +41 -0
- package/dist/engine/functions/text.d.ts +17 -1
- package/dist/engine/guards.d.ts +10 -0
- package/dist/engine/index.d.ts +11 -0
- package/dist/engine/jsonCellLocator.d.ts +27 -0
- package/dist/engine/math-ops.d.ts +41 -0
- package/dist/engine/numericCoercion.d.ts +38 -0
- package/dist/engine/parser.d.ts +0 -24
- package/dist/engine/registry.d.ts +30 -2
- package/dist/engine/responseDecoder.d.ts +40 -0
- package/dist/engine/spreadsheet-errors.d.ts +42 -0
- package/dist/engine/textFormat.d.ts +32 -0
- package/dist/engine/translateFormula.d.ts +37 -0
- package/dist/engine/types.d.ts +17 -3
- package/dist/style.css +1 -1
- package/dist/vue/cellHighlights.d.ts +42 -0
- package/dist/vue/keyboardNav.d.ts +9 -0
- package/dist/vue.cjs +71 -71
- package/dist/vue.js +6634 -6498
- package/package.json +14 -13
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Numeric coercion helpers for the spreadsheet engine.
|
|
3
|
+
*
|
|
4
|
+
* Two intentionally different reads share one string parser (`parseNumericString`):
|
|
5
|
+
* - `registry.toNumber` — lenient, for range aggregation (SUM / AVERAGE / …):
|
|
6
|
+
* anything unreadable becomes 0. PINNED (booleans are 0, not Excel's 1/0).
|
|
7
|
+
* - `toScalarNumber` — strict, for single-value math functions (ABS / SIGN):
|
|
8
|
+
* booleans are 1/0 and non-numeric text is `#VALUE!`, matching Excel.
|
|
9
|
+
*
|
|
10
|
+
* `holdsNumber` asks the question neither read can answer once it has coerced:
|
|
11
|
+
* was there a number here at all, or is this 0 the reading of something else.
|
|
12
|
+
*/
|
|
13
|
+
import type { CellValue } from "./types";
|
|
14
|
+
import { type SpreadsheetError } from "./spreadsheet-errors";
|
|
15
|
+
/**
|
|
16
|
+
* Parse a spreadsheet string as a number, or `null` when it holds no number.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors the engine's long-standing lenient read: a percentage, a currency
|
|
19
|
+
* amount, or a thousands-separated value, else a bare `parseFloat` (which reads a
|
|
20
|
+
* leading number out of "12abc"). The branch order is load-bearing — each strips
|
|
21
|
+
* only its own characters, so a string mixing "%" and "$" fails in the first.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseNumericString(value: string): number | null;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a value holds a number at all, under the same reading `toNumber` uses.
|
|
26
|
+
*
|
|
27
|
+
* `toNumber` answers 0 for text, for a boolean and for a genuine 0 alike, so a
|
|
28
|
+
* caller that must tell "no number here" from "the number zero" — COUNT — cannot
|
|
29
|
+
* ask it. Booleans are not numbers here, matching `toNumber`'s PINNED behaviour.
|
|
30
|
+
*/
|
|
31
|
+
export declare function holdsNumber(value: CellValue): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Strict scalar coercion for single-value math functions (ABS, SIGN). Follows
|
|
34
|
+
* Excel's scalar rules: a boolean is 1 / 0 and genuinely non-numeric text is
|
|
35
|
+
* `#VALUE!` rather than a silent 0. A partly-numeric string still yields its
|
|
36
|
+
* leading number, matching the engine's other numeric reads.
|
|
37
|
+
*/
|
|
38
|
+
export declare function toScalarNumber(value: CellValue): number | SpreadsheetError;
|
package/dist/engine/parser.d.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles Excel A1 notation parsing and conversion
|
|
5
5
|
*/
|
|
6
|
-
import type { CellRef, RangeRef } from "./types";
|
|
7
6
|
/**
|
|
8
7
|
* Convert Excel column letters to 0-based index
|
|
9
8
|
* A=0, B=1, ..., Z=25, AA=26, etc.
|
|
@@ -20,26 +19,3 @@ export declare function columnToIndex(col: string): number;
|
|
|
20
19
|
* @returns Column letters (e.g., "A", "Z", "AA")
|
|
21
20
|
*/
|
|
22
21
|
export declare function indexToColumn(index: number): string;
|
|
23
|
-
/**
|
|
24
|
-
* Parse a cell reference to its components
|
|
25
|
-
* Supports: A1, $A$1, $A1, A$1, Sheet1!A1, 'My Sheet'!A1
|
|
26
|
-
*
|
|
27
|
-
* @param ref - Cell reference string
|
|
28
|
-
* @returns Parsed cell reference object
|
|
29
|
-
*/
|
|
30
|
-
export declare function parseCellRef(ref: string): CellRef;
|
|
31
|
-
/**
|
|
32
|
-
* Parse a range reference to its components
|
|
33
|
-
* Supports: A1:B10, $A$1:$B$10, Sheet1!A1:B10
|
|
34
|
-
*
|
|
35
|
-
* @param range - Range reference string
|
|
36
|
-
* @returns Parsed range reference object
|
|
37
|
-
*/
|
|
38
|
-
export declare function parseRangeRef(range: string): RangeRef;
|
|
39
|
-
/**
|
|
40
|
-
* Convert a cell reference object back to A1 notation
|
|
41
|
-
*
|
|
42
|
-
* @param ref - Cell reference object
|
|
43
|
-
* @returns A1 notation string
|
|
44
|
-
*/
|
|
45
|
-
export declare function cellRefToA1(ref: CellRef): string;
|
|
@@ -10,11 +10,37 @@ export type CellGetter = (ref: string) => CellValue;
|
|
|
10
10
|
export type RangeGetter = (range: string) => CellValue[];
|
|
11
11
|
export type RawRangeGetter = (range: string) => CellValue[];
|
|
12
12
|
export interface FunctionContext {
|
|
13
|
+
/** The registry name the formula invoked, so a handler can report a failure
|
|
14
|
+
* the way the evaluator names it. */
|
|
15
|
+
functionName: string;
|
|
13
16
|
getCellValue: CellGetter;
|
|
14
17
|
getRangeValues: RangeGetter;
|
|
15
|
-
getRangeValuesRaw?: RawRangeGetter;
|
|
18
|
+
getRangeValuesRaw?: RawRangeGetter | undefined;
|
|
16
19
|
evaluateFormula: (formula: string) => CellValue;
|
|
17
20
|
}
|
|
21
|
+
/** The too-few-arguments error, raised by the evaluator from the registry's
|
|
22
|
+
* `minArgs` before any handler runs — and by `requiredArg` for the argument a
|
|
23
|
+
* handler actually reads, so both report one wording. */
|
|
24
|
+
export declare const tooFewArgumentsError: (funcName: string, minArgs: number) => Error;
|
|
25
|
+
/** The argument at `index`, which the caller has already established is there —
|
|
26
|
+
* the registry's `minArgs` for a mandatory argument, an `args.length` branch for
|
|
27
|
+
* an optional one. An absent one means that guarantee is wrong, so it raises the
|
|
28
|
+
* arity error the guarantee should have raised. Never a default value: a
|
|
29
|
+
* substituted 0 or "" computes a plausible wrong answer, which is worse than
|
|
30
|
+
* the error the formula deserves. */
|
|
31
|
+
export declare const requiredArg: (context: FunctionContext, args: string[], index: number) => string;
|
|
32
|
+
/** The range reader that keeps every cell, for functions whose answer depends on
|
|
33
|
+
* a range's POSITIONS rather than its numbers.
|
|
34
|
+
*
|
|
35
|
+
* `getRangeValues` drops non-numeric cells, which silently renumbers the rows
|
|
36
|
+
* underneath: a criteria/value pair read that way falls out of alignment and
|
|
37
|
+
* aggregates a different row, and an index returned from it points at the wrong
|
|
38
|
+
* one. SUMIF/AVERAGEIF were moved here in #2358; MATCH/XLOOKUP in #2765.
|
|
39
|
+
*
|
|
40
|
+
* Falls back to the numeric reader because `getRangeValuesRaw` is optional on
|
|
41
|
+
* `FunctionContext` — a host that predates it keeps its old behaviour rather
|
|
42
|
+
* than throwing. */
|
|
43
|
+
export declare const rawRangeReader: (context: FunctionContext) => RangeGetter;
|
|
18
44
|
export type FunctionHandler = (args: string[], context: FunctionContext) => CellValue;
|
|
19
45
|
export interface FunctionDefinition {
|
|
20
46
|
name: string;
|
|
@@ -35,7 +61,9 @@ declare class FunctionRegistry {
|
|
|
35
61
|
}
|
|
36
62
|
export declare const functionRegistry: FunctionRegistry;
|
|
37
63
|
/**
|
|
38
|
-
*
|
|
64
|
+
* Lenient numeric coercion for range aggregation: anything unreadable is 0.
|
|
65
|
+
* PINNED behaviour (booleans are 0, not Excel's 1/0) — the string parsing lives
|
|
66
|
+
* in numericCoercion.parseNumericString, shared with the strict scalar read.
|
|
39
67
|
*/
|
|
40
68
|
export declare function toNumber(value: CellValue): number;
|
|
41
69
|
/**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode the `/api/files/content` response for a spreadsheet file
|
|
3
|
+
* into an "ok with sheets" / "error with message" discriminated
|
|
4
|
+
* union. Extracted from `fetchSheets` in
|
|
5
|
+
* `src/plugins/spreadsheet/View.vue` where the decision tree was
|
|
6
|
+
* inlined as several nested try/catch + if branches.
|
|
7
|
+
*
|
|
8
|
+
* Pure — no fetch, no refs. Takes the parsed JSON body and returns
|
|
9
|
+
* a result the caller can pattern-match on. Tested in
|
|
10
|
+
* `test/plugins/spreadsheet/engine/test_responseDecoder.ts`.
|
|
11
|
+
*/
|
|
12
|
+
import type { SheetData } from "./types.js";
|
|
13
|
+
/** Shape of the `/api/files/content` response we care about. The
|
|
14
|
+
* server returns more fields (kind, size, modifiedMs, …) but this
|
|
15
|
+
* decoder only depends on the three that drive branching. */
|
|
16
|
+
export interface FilesContentResponseLike {
|
|
17
|
+
kind?: string;
|
|
18
|
+
content?: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
}
|
|
21
|
+
export type DecodeResult = {
|
|
22
|
+
kind: "ok";
|
|
23
|
+
sheets: SheetData[];
|
|
24
|
+
} | {
|
|
25
|
+
kind: "error";
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Turn a parsed `/files/content` body into an OK/error decision:
|
|
30
|
+
*
|
|
31
|
+
* - `kind` present and not "text" → error with the server's message
|
|
32
|
+
* (e.g. "too-large", "binary"). Spreadsheets only live in text
|
|
33
|
+
* JSON files.
|
|
34
|
+
* - Missing or non-string `content` → error.
|
|
35
|
+
* - `content` is not valid JSON → error.
|
|
36
|
+
* - Parsed `content` is not an array → error (server should never
|
|
37
|
+
* return a non-array but the guard protects downstream render).
|
|
38
|
+
* - Otherwise → ok with the sheets array.
|
|
39
|
+
*/
|
|
40
|
+
export declare function decodeSpreadsheetResponse(body: FilesContentResponseLike): DecodeResult;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formula errors as a distinct VALUE type.
|
|
3
|
+
*
|
|
4
|
+
* A `#NUM!` produced by `SQRT(-1)` and the text `"#NUM!"` produced by
|
|
5
|
+
* `CONCAT("#N","UM!")` used to be the same string, so IFERROR could not tell a
|
|
6
|
+
* real error from text that merely spells one (#2451). An error is now its own
|
|
7
|
+
* value carrying the code, which is what the error-aware functions check; the
|
|
8
|
+
* display pass renders it back to `#NUM!` so cells look unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SPREADSHEET_ERRORS: readonly ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A", "#ERROR!"];
|
|
11
|
+
export type SpreadsheetErrorCode = (typeof SPREADSHEET_ERRORS)[number];
|
|
12
|
+
/** A formula error, distinct from any string. `toString` yields the code so the
|
|
13
|
+
* value renders as `#NUM!` wherever the engine coerces a cell value to text. */
|
|
14
|
+
export declare class SpreadsheetError {
|
|
15
|
+
readonly code: SpreadsheetErrorCode;
|
|
16
|
+
constructor(code: SpreadsheetErrorCode);
|
|
17
|
+
toString(): string;
|
|
18
|
+
toJSON(): string;
|
|
19
|
+
}
|
|
20
|
+
export declare const NULL_ERROR: SpreadsheetError;
|
|
21
|
+
export declare const DIV_ZERO_ERROR: SpreadsheetError;
|
|
22
|
+
export declare const VALUE_ERROR: SpreadsheetError;
|
|
23
|
+
export declare const REF_ERROR: SpreadsheetError;
|
|
24
|
+
export declare const NAME_ERROR: SpreadsheetError;
|
|
25
|
+
export declare const NUM_ERROR: SpreadsheetError;
|
|
26
|
+
export declare const NA_ERROR: SpreadsheetError;
|
|
27
|
+
export declare const UNKNOWN_ERROR: SpreadsheetError;
|
|
28
|
+
/** The error value for a code. */
|
|
29
|
+
export declare const spreadsheetError: (code: SpreadsheetErrorCode) => SpreadsheetError;
|
|
30
|
+
/** Whether a value is one of the error CODES as a plain string. Text that spells
|
|
31
|
+
* an error is not an error value — see `isSpreadsheetErrorValue`. */
|
|
32
|
+
export declare function isSpreadsheetError(value: unknown): value is SpreadsheetErrorCode;
|
|
33
|
+
/** Whether a value IS a formula error, as opposed to text that spells one. */
|
|
34
|
+
export declare function isSpreadsheetErrorValue(value: unknown): value is SpreadsheetError;
|
|
35
|
+
/** The code behind a value: an error value's own code, or the code a plain
|
|
36
|
+
* string spells. Used where a literal `#REF!` written into a cell must still
|
|
37
|
+
* poison the arithmetic that reads it, provenance aside. */
|
|
38
|
+
export declare function errorCodeOf(value: unknown): SpreadsheetErrorCode | null;
|
|
39
|
+
/** Whether an evaluated result should be treated as an error: a formula error
|
|
40
|
+
* VALUE, a NaN / infinite number, or a missing value. This is what IFERROR
|
|
41
|
+
* catches — deliberately NOT a look-alike string, which is ordinary text. */
|
|
42
|
+
export declare function isErrorResult(value: unknown): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel number-format patterns for the TEXT function.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately separate from `formatter.formatNumber`, which renders CELL
|
|
5
|
+
* display values: the two disagree on defaults — Excel's `TEXT(0.5,"0%")` is
|
|
6
|
+
* `"50%"`, while a cell carrying the format `0%` has always displayed
|
|
7
|
+
* `"50.00%"` — so sharing the whole path would move every formatted cell in
|
|
8
|
+
* every stored workbook. Only the digit-grouping primitive is shared.
|
|
9
|
+
*/
|
|
10
|
+
export interface NumberPattern {
|
|
11
|
+
prefix: string;
|
|
12
|
+
suffix: string;
|
|
13
|
+
useGrouping: boolean;
|
|
14
|
+
integerMinDigits: number;
|
|
15
|
+
minDecimals: number;
|
|
16
|
+
maxDecimals: number;
|
|
17
|
+
isPercent: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Split a format code into a leading literal, one numeric core and a trailing
|
|
21
|
+
* literal, or `null` when the code uses a feature this module does not render.
|
|
22
|
+
*/
|
|
23
|
+
export declare const parseNumberPattern: (pattern: string) => NumberPattern | null;
|
|
24
|
+
/**
|
|
25
|
+
* Render a number the way Excel's TEXT does for the common numeric format
|
|
26
|
+
* codes: `#,##0` grouping, `0.00` fixed decimals, `0.##` optional decimals, a
|
|
27
|
+
* literal prefix/suffix such as `$`, and a `%` that scales by 100.
|
|
28
|
+
*
|
|
29
|
+
* Returns `null` for a format code it does not render, so the caller can keep
|
|
30
|
+
* its own fallback rather than inventing a plausible wrong string.
|
|
31
|
+
*/
|
|
32
|
+
export declare const formatWithPattern: (value: number, pattern: string) => string | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel-formula → JS-expression translation
|
|
3
|
+
*
|
|
4
|
+
* Pure string transforms that turn the Excel-operator form of an
|
|
5
|
+
* already-substituted expression (cell references resolved, functions replaced)
|
|
6
|
+
* into the JavaScript form handed to `new Function`, plus the character
|
|
7
|
+
* allowlists that gate that evaluation. No engine or evaluator state is
|
|
8
|
+
* captured — every function here is input → output only.
|
|
9
|
+
*/
|
|
10
|
+
/** Excel `^` exponentiation → JS `**`.
|
|
11
|
+
*
|
|
12
|
+
* Excel's `^` is left-associative (`2^3^2` = `(2^3)^2` = 64); JS `**` is
|
|
13
|
+
* right-associative (`2**3**2` = `2**(3**2)` = 512). This transform does not
|
|
14
|
+
* bridge that difference, so a chained `^` still evaluates the JS way — a known
|
|
15
|
+
* limitation tracked separately (#2359), pinned by the tests. */
|
|
16
|
+
export declare function caretToPow(expr: string): string;
|
|
17
|
+
/** Excel `&` string concatenation → JS `+`, leaving any `&` inside a quoted
|
|
18
|
+
* string literal untouched. A quote toggles the "inside a literal" state only
|
|
19
|
+
* when it is not backslash-escaped. */
|
|
20
|
+
export declare function replaceConcatOperator(expr: string): string;
|
|
21
|
+
/** Excel `=` equality → JS `==`, without disturbing `<=`, `>=` or `!=`. A single
|
|
22
|
+
* `=` is rewritten only when flanked by a non-`<>!` char on the left and a
|
|
23
|
+
* non-`=` on the right.
|
|
24
|
+
*
|
|
25
|
+
* Known limitations, pinned by the tests and tracked separately (#2359): the
|
|
26
|
+
* match consumes both flanking characters so replacements do not overlap
|
|
27
|
+
* (`5=6=7` → `5==6=7`, only the first rewritten); a `=` at either end of the
|
|
28
|
+
* expression has no left/right neighbour and is left alone (`5=` → `5=`); and a
|
|
29
|
+
* hand-typed `==` becomes `===` (its second `=` matches). Excel never emits the
|
|
30
|
+
* last two, so they only bite malformed input. */
|
|
31
|
+
export declare function rewriteComparisonEq(expr: string): string;
|
|
32
|
+
/** Whether an expression contains only the characters an arithmetic evaluation
|
|
33
|
+
* may see: digits, ` + - * / ( ) . ` and spaces. Gates `new Function`. */
|
|
34
|
+
export declare function isSafeArithmetic(expr: string): boolean;
|
|
35
|
+
/** Whether an expression contains only the characters a comparison evaluation
|
|
36
|
+
* may see: the arithmetic set plus ` < > ! = `. Gates `new Function`. */
|
|
37
|
+
export declare function isSafeComparison(expr: string): boolean;
|
package/dist/engine/types.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spreadsheet Engine Type Definitions
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
import type { SpreadsheetError } from "./spreadsheet-errors";
|
|
5
|
+
/** A value as it is STORED in a cell and serialized to the workbook JSON. A
|
|
6
|
+
* formula error is never stored — it only exists as a computed result. */
|
|
7
|
+
export type StoredCellValue = number | string | boolean;
|
|
8
|
+
/** A value as the engine COMPUTES it: a stored value, or a formula error. */
|
|
9
|
+
export type CellValue = StoredCellValue | SpreadsheetError;
|
|
5
10
|
export interface SpreadsheetCell {
|
|
6
|
-
v:
|
|
11
|
+
v: StoredCellValue;
|
|
7
12
|
f?: string;
|
|
8
13
|
}
|
|
9
14
|
export interface SheetData {
|
|
@@ -46,7 +51,16 @@ export interface CalculationError {
|
|
|
46
51
|
error: string;
|
|
47
52
|
type: "circular" | "invalid_ref" | "div_zero" | "syntax" | "unknown";
|
|
48
53
|
}
|
|
49
|
-
|
|
54
|
+
/** Per-calculation settings that change how cell CONTENT is read, as opposed
|
|
55
|
+
* to how the engine runs. Passed down explicitly so the engine stays pure —
|
|
56
|
+
* no ambient locale, no import-time state. */
|
|
57
|
+
export interface CalculateOptions {
|
|
58
|
+
/** Read an ambiguous `A/B/YYYY` date as day-first. Only affects dates whose
|
|
59
|
+
* two leading numbers are both 12 or under; anything else decides itself.
|
|
60
|
+
* See engine/date-locale.ts for how a locale maps onto this. */
|
|
61
|
+
preferDDMMYYYY?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface EngineOptions extends CalculateOptions {
|
|
50
64
|
maxIterations?: number;
|
|
51
65
|
enableCrossSheetRefs?: boolean;
|
|
52
66
|
strictMode?: boolean;
|
package/dist/style.css
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-1{margin-top:var(--spacing)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.contents{display:contents}.flex{display:flex}.table{display:table}.h-\[600px\]{height:600px}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-full{min-height:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[200px\]{max-width:200px}.flex-shrink{flex-shrink:1}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.border-green-200{border-color:var(--color-green-200)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-white{background-color:var(--color-white)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-800{color:var(--color-gray-800)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:border-green-300:hover{border-color:var(--color-green-300)}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}}@media (prefers-color-scheme:dark){.dark\:bg-green-900{background-color:var(--color-green-900)}.dark\:text-gray-200{color:var(--color-gray-200)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-green-300{color:var(--color-green-300)}}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}.spreadsheet-container[data-v-46c4c1c7]{background:#fff;flex-direction:column;width:100%;height:100%;display:flex}.spreadsheet-content-wrapper[data-v-46c4c1c7]{flex:1;min-height:0;overflow-y:auto}.header[data-v-46c4c1c7]{justify-content:space-between;align-items:center;margin-bottom:1em;display:flex}.title[data-v-46c4c1c7]{margin:0;font-size:2em;font-weight:700}.button-group[data-v-46c4c1c7]{gap:.5em;display:flex}.download-btn[data-v-46c4c1c7]{color:#fff;cursor:pointer;border:none;border-radius:4px;align-items:center;gap:.5em;padding:.5em 1em;font-size:.9em;transition:background-color .2s;display:flex}.excel-btn[data-v-46c4c1c7]{background-color:#217346}.excel-btn[data-v-46c4c1c7]:hover{background-color:#1e6a3f}.excel-btn[data-v-46c4c1c7]:active{background-color:#1a5c36}.download-btn .material-icons[data-v-46c4c1c7]{font-size:1.2em}.sheet-tabs[data-v-46c4c1c7]{border-bottom:2px solid #e0e0e0;gap:.25em;margin-bottom:1em;display:flex}.sheet-tab[data-v-46c4c1c7]{cursor:pointer;color:#666;background:#f5f5f5;border:none;border-top-left-radius:4px;border-top-right-radius:4px;padding:.5em 1em;font-size:.9em;transition:background-color .2s}.sheet-tab[data-v-46c4c1c7]:hover{background:#e8e8e8}.sheet-tab.active[data-v-46c4c1c7]{color:#333;background:#fff;border-bottom:2px solid #fff;margin-bottom:-2px;font-weight:500}.table-container[data-v-46c4c1c7]{background:#fff;overflow-x:auto}.table-container[data-v-46c4c1c7] table{border-collapse:collapse;width:100%;font-family:Segoe UI,Arial,sans-serif;font-size:.9em}.table-container[data-v-46c4c1c7] td,.table-container[data-v-46c4c1c7] th{text-align:left;border:1px solid #d0d0d0;padding:.5em .75em}.table-container[data-v-46c4c1c7] th{color:#333;background-color:#f5f5f5;font-weight:600}.table-container[data-v-46c4c1c7] tr:nth-child(2n){background-color:#fafafa}.table-container[data-v-46c4c1c7] tr:hover{background-color:#f0f0f0}.table-container[data-v-46c4c1c7] .cell-editing{outline-offset:-2px;background-color:#e8f5e9!important;outline:2px solid #217346!important}.table-container[data-v-46c4c1c7] .cell-referenced{outline-offset:-2px;background-color:#fff3e0!important;outline:2px solid #ff9800!important}.error[data-v-46c4c1c7]{color:#c62828;background:#ffebee;border-radius:4px;margin:1em 0;padding:1em}.spreadsheet-source[data-v-46c4c1c7]{background:#f5f5f5;border-top:1px solid #e0e0e0;flex-shrink:0;padding:.5rem;font-family:monospace;font-size:.85rem}.spreadsheet-source summary[data-v-46c4c1c7]{cursor:pointer;-webkit-user-select:none;user-select:none;color:#333;background:#e8e8e8;border-radius:4px;padding:.5rem;font-weight:500}.spreadsheet-source[open] summary[data-v-46c4c1c7]{margin-bottom:.5rem}.spreadsheet-source summary[data-v-46c4c1c7]:hover{background:#d8d8d8}.spreadsheet-editor[data-v-46c4c1c7]{color:#333;resize:vertical;background:#fff;border:1px solid #ccc;border-radius:4px;width:100%;height:40vh;margin-bottom:.5rem;padding:1rem;font-family:Courier New,monospace;font-size:.9rem;line-height:1.5}.spreadsheet-editor[data-v-46c4c1c7]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.apply-btn[data-v-46c4c1c7]{color:#fff;cursor:pointer;background:#217346;border:none;border-radius:4px;padding:.5rem 1rem;font-size:.9rem;font-weight:500;transition:background .2s}.apply-btn[data-v-46c4c1c7]:hover{background:#1e6a3f}.apply-btn[data-v-46c4c1c7]:active{background:#1a5c36}.apply-btn[data-v-46c4c1c7]:disabled{color:#666;cursor:not-allowed;opacity:.6;background:#ccc}.apply-btn[data-v-46c4c1c7]:disabled:hover{background:#ccc}.mini-editor-panel[data-v-46c4c1c7]{background:#f8f8f8;border-top:1px solid #d0d0d0;flex-shrink:0}.mini-editor-content[data-v-46c4c1c7]{align-items:center;gap:.5rem;padding:.5rem 1rem;display:flex}.cell-ref[data-v-46c4c1c7]{color:#217346;min-width:2rem;font-family:monospace;font-size:.85rem;font-weight:600}.radio-group[data-v-46c4c1c7]{border-right:1px solid #d0d0d0;gap:.75rem;padding-right:.75rem;display:flex}.radio-option[data-v-46c4c1c7]{cursor:pointer;color:#555;align-items:center;gap:.25rem;font-size:.85rem;display:flex}.radio-option input[type=radio][data-v-46c4c1c7]{cursor:pointer;width:.9rem;height:.9rem}.form-input[data-v-46c4c1c7]{border:1px solid #ccc;border-radius:3px;flex:1;min-width:120px;padding:.4rem .6rem;font-family:inherit;font-size:.85rem;transition:border-color .2s}.form-input[data-v-46c4c1c7]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.form-input[data-v-46c4c1c7]::placeholder{color:#999;font-size:.8rem}.save-btn[data-v-46c4c1c7],.cancel-btn[data-v-46c4c1c7]{cursor:pointer;border:none;border-radius:3px;padding:.4rem .8rem;font-size:.85rem;font-weight:500;transition:background .2s}.save-btn[data-v-46c4c1c7]{color:#fff;background:#217346}.save-btn[data-v-46c4c1c7]:hover{background:#1e6a3f}.cancel-btn[data-v-46c4c1c7]{color:#666;background:0 0;padding:.4rem;font-size:1.2rem;line-height:1}.cancel-btn[data-v-46c4c1c7]:hover{color:#333;background:#e0e0e0}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.row-2{grid-row:2}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-1{margin-top:var(--spacing)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline{display:inline}.table{display:table}.h-\[600px\]{height:600px}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-full{min-height:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[200px\]{max-width:200px}.flex-shrink{flex-shrink:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.border-green-200{border-color:var(--color-green-200)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-white{background-color:var(--color-white)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-800{color:var(--color-gray-800)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:border-green-300:hover{border-color:var(--color-green-300)}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}}@media (prefers-color-scheme:dark){.dark\:bg-green-900{background-color:var(--color-green-900)}.dark\:text-gray-200{color:var(--color-gray-200)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-green-300{color:var(--color-green-300)}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}.spreadsheet-container[data-v-1d9beb6d]{background:#fff;flex-direction:column;width:100%;height:100%;display:flex}.spreadsheet-content-wrapper[data-v-1d9beb6d]{flex:1;min-height:0;overflow-y:auto}.header[data-v-1d9beb6d]{justify-content:space-between;align-items:center;margin-bottom:1em;display:flex}.title[data-v-1d9beb6d]{margin:0;font-size:2em;font-weight:700}.button-group[data-v-1d9beb6d]{gap:.5em;display:flex}.download-btn[data-v-1d9beb6d]{color:#fff;cursor:pointer;border:none;border-radius:4px;align-items:center;gap:.5em;padding:.5em 1em;font-size:.9em;transition:background-color .2s;display:flex}.excel-btn[data-v-1d9beb6d]{background-color:#217346}.excel-btn[data-v-1d9beb6d]:hover{background-color:#1e6a3f}.excel-btn[data-v-1d9beb6d]:active{background-color:#1a5c36}.download-btn .material-icons[data-v-1d9beb6d]{font-size:1.2em}.sheet-tabs[data-v-1d9beb6d]{border-bottom:2px solid #e0e0e0;gap:.25em;margin-bottom:1em;display:flex}.sheet-tab[data-v-1d9beb6d]{cursor:pointer;color:#666;background:#f5f5f5;border:none;border-top-left-radius:4px;border-top-right-radius:4px;padding:.5em 1em;font-size:.9em;transition:background-color .2s}.sheet-tab[data-v-1d9beb6d]:hover{background:#e8e8e8}.sheet-tab.active[data-v-1d9beb6d]{color:#333;background:#fff;border-bottom:2px solid #fff;margin-bottom:-2px;font-weight:500}.table-container[data-v-1d9beb6d]{background:#fff;overflow-x:auto}.table-container[data-v-1d9beb6d] table{border-collapse:collapse;width:100%;font-family:Segoe UI,Arial,sans-serif;font-size:.9em}.table-container[data-v-1d9beb6d] td,.table-container[data-v-1d9beb6d] th{text-align:left;border:1px solid #d0d0d0;padding:.5em .75em}.table-container[data-v-1d9beb6d] th{color:#333;background-color:#f5f5f5;font-weight:600}.table-container[data-v-1d9beb6d] tr:nth-child(2n){background-color:#fafafa}.table-container[data-v-1d9beb6d] tr:hover{background-color:#f0f0f0}.table-container[data-v-1d9beb6d] .cell-editing{outline-offset:-2px;background-color:#e8f5e9!important;outline:2px solid #217346!important}.table-container[data-v-1d9beb6d] .cell-referenced{outline-offset:-2px;background-color:#fff3e0!important;outline:2px solid #ff9800!important}.error[data-v-1d9beb6d]{color:#c62828;background:#ffebee;border-radius:4px;margin:1em 0;padding:1em}.spreadsheet-source[data-v-1d9beb6d]{background:#f5f5f5;border-top:1px solid #e0e0e0;flex-shrink:0;padding:.5rem;font-family:monospace;font-size:.85rem}.spreadsheet-source summary[data-v-1d9beb6d]{cursor:pointer;-webkit-user-select:none;user-select:none;color:#333;background:#e8e8e8;border-radius:4px;padding:.5rem;font-weight:500}.spreadsheet-source[open] summary[data-v-1d9beb6d]{margin-bottom:.5rem}.spreadsheet-source summary[data-v-1d9beb6d]:hover{background:#d8d8d8}.spreadsheet-editor[data-v-1d9beb6d]{color:#333;resize:vertical;background:#fff;border:1px solid #ccc;border-radius:4px;width:100%;height:40vh;margin-bottom:.5rem;padding:1rem;font-family:Courier New,monospace;font-size:.9rem;line-height:1.5}.spreadsheet-editor[data-v-1d9beb6d]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.apply-btn[data-v-1d9beb6d]{color:#fff;cursor:pointer;background:#217346;border:none;border-radius:4px;padding:.5rem 1rem;font-size:.9rem;font-weight:500;transition:background .2s}.apply-btn[data-v-1d9beb6d]:hover{background:#1e6a3f}.apply-btn[data-v-1d9beb6d]:active{background:#1a5c36}.apply-btn[data-v-1d9beb6d]:disabled{color:#666;cursor:not-allowed;opacity:.6;background:#ccc}.apply-btn[data-v-1d9beb6d]:disabled:hover{background:#ccc}.mini-editor-panel[data-v-1d9beb6d]{background:#f8f8f8;border-top:1px solid #d0d0d0;flex-shrink:0}.mini-editor-content[data-v-1d9beb6d]{align-items:center;gap:.5rem;padding:.5rem 1rem;display:flex}.cell-ref[data-v-1d9beb6d]{color:#217346;min-width:2rem;font-family:monospace;font-size:.85rem;font-weight:600}.radio-group[data-v-1d9beb6d]{border-right:1px solid #d0d0d0;gap:.75rem;padding-right:.75rem;display:flex}.radio-option[data-v-1d9beb6d]{cursor:pointer;color:#555;align-items:center;gap:.25rem;font-size:.85rem;display:flex}.radio-option input[type=radio][data-v-1d9beb6d]{cursor:pointer;width:.9rem;height:.9rem}.form-input[data-v-1d9beb6d]{border:1px solid #ccc;border-radius:3px;flex:1;min-width:120px;padding:.4rem .6rem;font-family:inherit;font-size:.85rem;transition:border-color .2s}.form-input[data-v-1d9beb6d]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.form-input[data-v-1d9beb6d]::placeholder{color:#999;font-size:.8rem}.save-btn[data-v-1d9beb6d],.cancel-btn[data-v-1d9beb6d]{cursor:pointer;border:none;border-radius:3px;padding:.4rem .8rem;font-size:.85rem;font-weight:500;transition:background .2s}.save-btn[data-v-1d9beb6d]{color:#fff;background:#217346}.save-btn[data-v-1d9beb6d]:hover{background:#1e6a3f}.cancel-btn[data-v-1d9beb6d]{color:#666;background:0 0;padding:.4rem;font-size:1.2rem;line-height:1}.cancel-btn[data-v-1d9beb6d]:hover{color:#333;background:#e0e0e0}
|
|
3
3
|
/*$vite$:1*/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM helpers for the mini-editor cell-highlight pass. Extracted from
|
|
3
|
+
* the post-flush watch in `src/vue/View.vue`, which
|
|
4
|
+
* had a cognitive complexity of 30 driven by four levels of nested
|
|
5
|
+
* optional chaining + loops.
|
|
6
|
+
*
|
|
7
|
+
* These helpers are side-effectful by nature (they add/remove CSS
|
|
8
|
+
* classes on DOM nodes) but each one is small enough that its
|
|
9
|
+
* behaviour is obvious. Unit-testable with a minimal mock DOM; see
|
|
10
|
+
* `tests/vue/test_cellHighlights.ts`.
|
|
11
|
+
*/
|
|
12
|
+
/** Minimal DOM surface the helpers need. Defined here so tests can
|
|
13
|
+
* pass plain objects without pulling in jsdom. */
|
|
14
|
+
export interface HighlightableElement {
|
|
15
|
+
classList: {
|
|
16
|
+
add: (cls: string) => void;
|
|
17
|
+
remove: (cls: string) => void;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface HighlightableRow {
|
|
21
|
+
querySelectorAll: (selector: string) => ArrayLike<HighlightableElement>;
|
|
22
|
+
}
|
|
23
|
+
export interface HighlightableTable {
|
|
24
|
+
querySelectorAll: (selector: string) => ArrayLike<HighlightableRow>;
|
|
25
|
+
}
|
|
26
|
+
export interface HighlightableContainer {
|
|
27
|
+
querySelector: ((selector: "#spreadsheet-table") => HighlightableTable | null) & ((selector: string) => HighlightableElement | null);
|
|
28
|
+
querySelectorAll: (selector: string) => ArrayLike<HighlightableElement> & Iterable<HighlightableElement>;
|
|
29
|
+
}
|
|
30
|
+
export interface CellCoord {
|
|
31
|
+
row: number;
|
|
32
|
+
col: number;
|
|
33
|
+
}
|
|
34
|
+
/** Remove both kinds of highlight classes from the container. */
|
|
35
|
+
export declare function clearCellHighlights(container: HighlightableContainer | null | undefined): void;
|
|
36
|
+
/** Add `className` to the <td> at (row, col) of the given table.
|
|
37
|
+
* No-op if the row or cell doesn't exist. */
|
|
38
|
+
export declare function highlightCell(table: HighlightableTable | null | undefined, coord: CellCoord, className: string): void;
|
|
39
|
+
/** Apply the editing cell + referenced cells highlights. Looks up
|
|
40
|
+
* the #spreadsheet-table inside the container and no-ops if the
|
|
41
|
+
* table hasn't rendered yet. */
|
|
42
|
+
export declare function applyCellHighlights(container: HighlightableContainer | null | undefined, editingCell: CellCoord | null, references: readonly CellCoord[]): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface CellPosition {
|
|
2
|
+
row: number;
|
|
3
|
+
col: number;
|
|
4
|
+
}
|
|
5
|
+
export interface SheetLike {
|
|
6
|
+
data?: unknown[][];
|
|
7
|
+
}
|
|
8
|
+
export declare function getArrowKeyOffset(key: string, row: number, col: number): CellPosition | null;
|
|
9
|
+
export declare function isWithinSheetBounds(sheet: SheetLike | null | undefined, row: number, col: number): boolean;
|