@rowsncolumns/utils 9.3.0 → 9.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/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/sparkline-groups.d.ts +102 -0
- package/dist/cjs/sparkline-groups.d.ts.map +1 -0
- package/dist/cjs/sparkline-groups.js +452 -0
- package/dist/cjs/string.d.ts +15 -0
- package/dist/cjs/string.d.ts.map +1 -1
- package/dist/cjs/string.js +46 -1
- package/dist/cjs/table-names.d.ts +55 -0
- package/dist/cjs/table-names.d.ts.map +1 -0
- package/dist/cjs/table-names.js +189 -0
- package/dist/cjs/table-styles.d.ts +9 -0
- package/dist/cjs/table-styles.d.ts.map +1 -1
- package/dist/cjs/table-styles.js +53 -1
- package/dist/cjs/table-totals.d.ts +31 -0
- package/dist/cjs/table-totals.d.ts.map +1 -0
- package/dist/cjs/table-totals.js +84 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/sparkline-groups.d.ts +102 -0
- package/dist/esm/sparkline-groups.d.ts.map +1 -0
- package/dist/esm/sparkline-groups.js +387 -0
- package/dist/esm/string.d.ts +15 -0
- package/dist/esm/string.d.ts.map +1 -1
- package/dist/esm/string.js +43 -0
- package/dist/esm/table-names.d.ts +55 -0
- package/dist/esm/table-names.d.ts.map +1 -0
- package/dist/esm/table-names.js +129 -0
- package/dist/esm/table-styles.d.ts +9 -0
- package/dist/esm/table-styles.d.ts.map +1 -1
- package/dist/esm/table-styles.js +34 -1
- package/dist/esm/table-totals.d.ts +31 -0
- package/dist/esm/table-totals.d.ts.map +1 -0
- package/dist/esm/table-totals.js +68 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { CellInterface, GridRange, SparkLineResultOptions } from "@rowsncolumns/common-types";
|
|
2
|
+
/**
|
|
3
|
+
* Excel sparkline groups (Insert → Sparklines → Line / Column / Win-Loss) on top of the
|
|
4
|
+
* `=SPARKLINE(dataRange, {options})` cell formula — the shape the XLSX importers of BOTH engines
|
|
5
|
+
* produce for an `<x14:sparklineGroup>` (one formula per `<x14:sparkline>`, the group's attributes
|
|
6
|
+
* as the option literal) and the shape the exporters fold back into a group (cells sharing one option
|
|
7
|
+
* fingerprint share one `<x14:sparklineGroup>`). A group therefore has no separate document object:
|
|
8
|
+
* it is the connected run of location cells whose SPARKLINE formulas carry identical options. That
|
|
9
|
+
* keeps live recalc, reference shifting on insert/delete and the round-trip as ordinary formula
|
|
10
|
+
* behavior on both engines.
|
|
11
|
+
*/
|
|
12
|
+
export type SparklineGroupType = "line" | "column" | "winloss";
|
|
13
|
+
/** One sparkline of a group: the cell it paints in and the data range it charts. */
|
|
14
|
+
export type SparklineGroupMember = {
|
|
15
|
+
cell: CellInterface;
|
|
16
|
+
/** As written in the formula (optionally sheet-qualified, e.g. `Data!A2:E2`). */
|
|
17
|
+
dataRange: string;
|
|
18
|
+
};
|
|
19
|
+
/** The group options (everything but `charttype`, which is the group's `type`). */
|
|
20
|
+
export type SparklineGroupOptions = Omit<SparkLineResultOptions, "charttype">;
|
|
21
|
+
export type SparklineGroup = {
|
|
22
|
+
sheetId: number;
|
|
23
|
+
type: SparklineGroupType;
|
|
24
|
+
options: SparklineGroupOptions;
|
|
25
|
+
/** In location order (top → bottom, then left → right). */
|
|
26
|
+
members: SparklineGroupMember[];
|
|
27
|
+
};
|
|
28
|
+
export type ParsedSparklineFormula = {
|
|
29
|
+
dataRange: string;
|
|
30
|
+
options: SparkLineResultOptions;
|
|
31
|
+
};
|
|
32
|
+
export declare const isSparklineFormula: (formula: unknown) => formula is string;
|
|
33
|
+
/**
|
|
34
|
+
* `=SPARKLINE(A1:E1,{"charttype","column","color","#FF0000"})` → its data range and options with the
|
|
35
|
+
* keys canonicalized to `SparkLineResultOptions` (both option-literal shapes and the legacy
|
|
36
|
+
* `highpoint`/… spellings are accepted). `null` when the formula is not a SPARKLINE call.
|
|
37
|
+
*/
|
|
38
|
+
export declare const parseSparklineFormula: (formula: string) => ParsedSparklineFormula | null;
|
|
39
|
+
/**
|
|
40
|
+
* The inverse of {@link parseSparklineFormula}: `=SPARKLINE(range)` when no option is set, else
|
|
41
|
+
* `=SPARKLINE(range,{"k",v,…})` in the importers' emission order. Unset, `false` and `line`
|
|
42
|
+
* (the default type) options are omitted.
|
|
43
|
+
*/
|
|
44
|
+
export declare const buildSparklineFormula: (dataRange: string, options?: SparkLineResultOptions) => string;
|
|
45
|
+
export declare const sparklineTypeFromOptions: (options: SparkLineResultOptions | undefined) => SparklineGroupType;
|
|
46
|
+
/** Group identity: two SPARKLINE cells belong to one group when this string matches. */
|
|
47
|
+
export declare const sparklineOptionsFingerprint: (options: SparkLineResultOptions) => string;
|
|
48
|
+
export type ResolveSparklineGroupArgs = {
|
|
49
|
+
sheetId: number;
|
|
50
|
+
cell: CellInterface;
|
|
51
|
+
/** The user-entered formula of a cell (`undefined` for a non-formula cell). */
|
|
52
|
+
getFormula: (sheetId: number, rowIndex: number, columnIndex: number) => string | undefined;
|
|
53
|
+
/** Upper bound on the cells walked (a runaway sheet-wide fill would freeze the UI). */
|
|
54
|
+
maxCells?: number;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The sparkline group containing `cell`: the 4-connected run of SPARKLINE cells around it whose
|
|
58
|
+
* options match the cell's. `undefined` when the cell holds no SPARKLINE formula.
|
|
59
|
+
*/
|
|
60
|
+
export declare const resolveSparklineGroup: ({ sheetId, cell, getFormula, maxCells, }: ResolveSparklineGroupArgs) => SparklineGroup | undefined;
|
|
61
|
+
export type ParsedRangeAddress = {
|
|
62
|
+
/** The sheet name as typed (quotes removed), `undefined` for a local reference. */
|
|
63
|
+
sheetName?: string;
|
|
64
|
+
range: GridRange;
|
|
65
|
+
};
|
|
66
|
+
/** `'Q1 Data'!A1:E5` / `Sheet1!A1` / `$A$1:$E$5` → its sheet name and grid range. */
|
|
67
|
+
export declare const parseRangeAddress: (address: string) => ParsedRangeAddress | null;
|
|
68
|
+
/** A grid range → `A1:E5` (a single cell → `A1`), optionally sheet-qualified. */
|
|
69
|
+
export declare const rangeToA1: (range: GridRange, sheetName?: string) => string;
|
|
70
|
+
export type PlanSparklineGroupArgs = {
|
|
71
|
+
/** Excel's "Data Range" (may be sheet-qualified). */
|
|
72
|
+
dataRange: string;
|
|
73
|
+
/** Excel's "Location Range" — a single row or column of cells on the group's sheet. */
|
|
74
|
+
locationRange: string;
|
|
75
|
+
};
|
|
76
|
+
export type SparklineGroupPlan = {
|
|
77
|
+
members: SparklineGroupMember[];
|
|
78
|
+
error?: undefined;
|
|
79
|
+
} | {
|
|
80
|
+
members?: undefined;
|
|
81
|
+
error: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Excel's Create Sparklines rule: one sparkline per location cell, each charting one row (or one
|
|
85
|
+
* column) of the data range. A single location cell charts the whole data range; otherwise the data
|
|
86
|
+
* range must span exactly as many rows, or exactly as many columns, as the location has cells.
|
|
87
|
+
*/
|
|
88
|
+
export declare const planSparklineGroup: ({ dataRange, locationRange, }: PlanSparklineGroupArgs) => SparklineGroupPlan;
|
|
89
|
+
/**
|
|
90
|
+
* The location Excel's dialog is seeded with for a data selection: the column just right of the
|
|
91
|
+
* data (one sparkline per data row), or the cell right of a single-row selection.
|
|
92
|
+
*/
|
|
93
|
+
export declare const defaultSparklineLocation: (data: GridRange) => GridRange;
|
|
94
|
+
/** The bounding range of a group's location cells (its members are a single row or column run). */
|
|
95
|
+
export declare const sparklineGroupLocation: (group: SparklineGroup) => GridRange;
|
|
96
|
+
/**
|
|
97
|
+
* The group's "Data Range" as Excel's Edit Group dialog shows it: the union of the members' ranges
|
|
98
|
+
* when they tile one rectangle by rows or columns; otherwise `undefined` (members chart unrelated
|
|
99
|
+
* ranges — Excel then shows the individual ranges only).
|
|
100
|
+
*/
|
|
101
|
+
export declare const sparklineGroupDataRange: (group: SparklineGroup) => string | undefined;
|
|
102
|
+
//# sourceMappingURL=sparkline-groups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sparkline-groups.d.ts","sourceRoot":"","sources":["../../sparkline-groups.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { addressToSelection } from "./selection";
|
|
2
|
+
import { cellToAddress, desanitizeSheetName, sanitizeSheetName } from "./string";
|
|
3
|
+
/** Lower-cased option key → the `SparkLineResultOptions` key the renderer reads. */
|
|
4
|
+
const OPTION_KEYS = {
|
|
5
|
+
charttype: "charttype",
|
|
6
|
+
color: "color",
|
|
7
|
+
colorhigh: "colorHigh",
|
|
8
|
+
colorlow: "colorLow",
|
|
9
|
+
colorfirst: "colorFirst",
|
|
10
|
+
colorlast: "colorLast",
|
|
11
|
+
colormarkers: "colorMarkers",
|
|
12
|
+
colornegative: "colorNegative",
|
|
13
|
+
emptycells: "emptycells",
|
|
14
|
+
dateaxis: "dateaxis",
|
|
15
|
+
markers: "markers",
|
|
16
|
+
high: "high",
|
|
17
|
+
highpoint: "high",
|
|
18
|
+
low: "low",
|
|
19
|
+
lowpoint: "low",
|
|
20
|
+
first: "first",
|
|
21
|
+
firstpoint: "first",
|
|
22
|
+
last: "last",
|
|
23
|
+
lastpoint: "last",
|
|
24
|
+
negative: "negative",
|
|
25
|
+
negativepoints: "negative",
|
|
26
|
+
axis: "axis",
|
|
27
|
+
rtl: "rtl",
|
|
28
|
+
displayhidden: "displayhidden",
|
|
29
|
+
lineweight: "lineweight",
|
|
30
|
+
max: "max",
|
|
31
|
+
min: "min",
|
|
32
|
+
minaxistype: "minaxistype",
|
|
33
|
+
maxaxistype: "maxaxistype",
|
|
34
|
+
};
|
|
35
|
+
/** Emission order of `buildSparklineFormula` — the XLSX importers' order, so a group written here
|
|
36
|
+
* and a group read from a workbook produce byte-identical formulas. */
|
|
37
|
+
const OPTION_ORDER = [
|
|
38
|
+
"charttype",
|
|
39
|
+
"color",
|
|
40
|
+
"colorHigh",
|
|
41
|
+
"colorLow",
|
|
42
|
+
"colorFirst",
|
|
43
|
+
"colorLast",
|
|
44
|
+
"colorMarkers",
|
|
45
|
+
"colorNegative",
|
|
46
|
+
"emptycells",
|
|
47
|
+
"dateaxis",
|
|
48
|
+
"markers",
|
|
49
|
+
"high",
|
|
50
|
+
"low",
|
|
51
|
+
"first",
|
|
52
|
+
"last",
|
|
53
|
+
"negative",
|
|
54
|
+
"axis",
|
|
55
|
+
"rtl",
|
|
56
|
+
"displayhidden",
|
|
57
|
+
"lineweight",
|
|
58
|
+
"max",
|
|
59
|
+
"min",
|
|
60
|
+
"minaxistype",
|
|
61
|
+
"maxaxistype",
|
|
62
|
+
];
|
|
63
|
+
const SPARKLINE_HEAD = /^\s*=\s*SPARKLINE\s*\(/i;
|
|
64
|
+
export const isSparklineFormula = (formula) => typeof formula === "string" && SPARKLINE_HEAD.test(formula);
|
|
65
|
+
/**
|
|
66
|
+
* Split the argument list of a SPARKLINE call at its top-level commas, honoring quotes, braces and
|
|
67
|
+
* nested parentheses (so `'Q1, Q2'!A1:E1` and `{...}` literals stay whole).
|
|
68
|
+
*/
|
|
69
|
+
const splitTopLevel = (args) => {
|
|
70
|
+
const parts = [];
|
|
71
|
+
let depth = 0;
|
|
72
|
+
let quote = null;
|
|
73
|
+
let current = "";
|
|
74
|
+
for (const ch of args) {
|
|
75
|
+
if (quote) {
|
|
76
|
+
current += ch;
|
|
77
|
+
if (ch === quote)
|
|
78
|
+
quote = null;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (ch === "'" || ch === '"') {
|
|
82
|
+
quote = ch;
|
|
83
|
+
current += ch;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (ch === "(" || ch === "{")
|
|
87
|
+
depth++;
|
|
88
|
+
if (ch === ")" || ch === "}")
|
|
89
|
+
depth--;
|
|
90
|
+
if (ch === "," && depth === 0) {
|
|
91
|
+
parts.push(current);
|
|
92
|
+
current = "";
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
current += ch;
|
|
96
|
+
}
|
|
97
|
+
parts.push(current);
|
|
98
|
+
return parts;
|
|
99
|
+
};
|
|
100
|
+
/** The tokens of an option literal body (`"k",v,"k2",v2` or `"k",v;"k2",v2`), quotes intact. */
|
|
101
|
+
const tokenizeLiteral = (body) => splitTopLevel(body.replace(/;/g, ","))
|
|
102
|
+
.map((t) => t.trim())
|
|
103
|
+
.filter((t) => t.length > 0);
|
|
104
|
+
const parseOptionValue = (raw) => {
|
|
105
|
+
if (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) {
|
|
106
|
+
return raw.slice(1, -1).replace(/""/g, '"');
|
|
107
|
+
}
|
|
108
|
+
const upper = raw.toUpperCase();
|
|
109
|
+
if (upper === "TRUE")
|
|
110
|
+
return true;
|
|
111
|
+
if (upper === "FALSE")
|
|
112
|
+
return false;
|
|
113
|
+
const n = Number(raw);
|
|
114
|
+
return Number.isFinite(n) ? n : raw;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* `=SPARKLINE(A1:E1,{"charttype","column","color","#FF0000"})` → its data range and options with the
|
|
118
|
+
* keys canonicalized to `SparkLineResultOptions` (both option-literal shapes and the legacy
|
|
119
|
+
* `highpoint`/… spellings are accepted). `null` when the formula is not a SPARKLINE call.
|
|
120
|
+
*/
|
|
121
|
+
export const parseSparklineFormula = (formula) => {
|
|
122
|
+
if (!isSparklineFormula(formula))
|
|
123
|
+
return null;
|
|
124
|
+
const open = formula.indexOf("(");
|
|
125
|
+
const close = formula.lastIndexOf(")");
|
|
126
|
+
if (open < 0 || close < open)
|
|
127
|
+
return null;
|
|
128
|
+
const [dataArg, optionsArg] = splitTopLevel(formula.slice(open + 1, close));
|
|
129
|
+
const dataRange = dataArg?.trim() ?? "";
|
|
130
|
+
if (!dataRange)
|
|
131
|
+
return null;
|
|
132
|
+
const options = {};
|
|
133
|
+
const literal = optionsArg?.trim();
|
|
134
|
+
if (literal && literal.startsWith("{") && literal.endsWith("}")) {
|
|
135
|
+
const tokens = tokenizeLiteral(literal.slice(1, -1));
|
|
136
|
+
for (let i = 0; i + 1 < tokens.length; i += 2) {
|
|
137
|
+
const rawKey = parseOptionValue(tokens[i]);
|
|
138
|
+
const key = OPTION_KEYS[String(rawKey).toLowerCase()];
|
|
139
|
+
if (!key)
|
|
140
|
+
continue;
|
|
141
|
+
options[key] = parseOptionValue(tokens[i + 1]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { dataRange, options };
|
|
145
|
+
};
|
|
146
|
+
const quote = (s) => `"${s.replace(/"/g, '""')}"`;
|
|
147
|
+
/**
|
|
148
|
+
* The inverse of {@link parseSparklineFormula}: `=SPARKLINE(range)` when no option is set, else
|
|
149
|
+
* `=SPARKLINE(range,{"k",v,…})` in the importers' emission order. Unset, `false` and `line`
|
|
150
|
+
* (the default type) options are omitted.
|
|
151
|
+
*/
|
|
152
|
+
export const buildSparklineFormula = (dataRange, options = {}) => {
|
|
153
|
+
const pairs = [];
|
|
154
|
+
for (const key of OPTION_ORDER) {
|
|
155
|
+
const value = options[key];
|
|
156
|
+
if (value === undefined || value === null || value === false)
|
|
157
|
+
continue;
|
|
158
|
+
if (key === "charttype" && value === "line")
|
|
159
|
+
continue;
|
|
160
|
+
if (typeof value === "boolean") {
|
|
161
|
+
pairs.push(quote(key), "TRUE");
|
|
162
|
+
}
|
|
163
|
+
else if (typeof value === "number") {
|
|
164
|
+
if (!Number.isFinite(value))
|
|
165
|
+
continue;
|
|
166
|
+
pairs.push(quote(key), String(value));
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
pairs.push(quote(key), quote(String(value)));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return pairs.length
|
|
173
|
+
? `=SPARKLINE(${dataRange},{${pairs.join(",")}})`
|
|
174
|
+
: `=SPARKLINE(${dataRange})`;
|
|
175
|
+
};
|
|
176
|
+
export const sparklineTypeFromOptions = (options) => options?.charttype === "column" || options?.charttype === "winloss"
|
|
177
|
+
? options.charttype
|
|
178
|
+
: "line";
|
|
179
|
+
/** Group identity: two SPARKLINE cells belong to one group when this string matches. */
|
|
180
|
+
export const sparklineOptionsFingerprint = (options) => OPTION_ORDER.map((key) => {
|
|
181
|
+
const value = options[key];
|
|
182
|
+
if (value === undefined || value === null || value === false)
|
|
183
|
+
return "";
|
|
184
|
+
if (key === "charttype" && value === "line")
|
|
185
|
+
return "";
|
|
186
|
+
return String(value);
|
|
187
|
+
}).join("|");
|
|
188
|
+
/**
|
|
189
|
+
* The sparkline group containing `cell`: the 4-connected run of SPARKLINE cells around it whose
|
|
190
|
+
* options match the cell's. `undefined` when the cell holds no SPARKLINE formula.
|
|
191
|
+
*/
|
|
192
|
+
export const resolveSparklineGroup = ({ sheetId, cell, getFormula, maxCells = 4096, }) => {
|
|
193
|
+
const parse = (rowIndex, columnIndex) => {
|
|
194
|
+
const formula = getFormula(sheetId, rowIndex, columnIndex);
|
|
195
|
+
return formula ? parseSparklineFormula(formula) : null;
|
|
196
|
+
};
|
|
197
|
+
const seed = parse(cell.rowIndex, cell.columnIndex);
|
|
198
|
+
if (!seed)
|
|
199
|
+
return undefined;
|
|
200
|
+
const fingerprint = sparklineOptionsFingerprint(seed.options);
|
|
201
|
+
const key = (r, c) => `${r}:${c}`;
|
|
202
|
+
const seen = new Set([key(cell.rowIndex, cell.columnIndex)]);
|
|
203
|
+
const members = [
|
|
204
|
+
{ cell: { rowIndex: cell.rowIndex, columnIndex: cell.columnIndex }, dataRange: seed.dataRange },
|
|
205
|
+
];
|
|
206
|
+
const queue = [members[0].cell];
|
|
207
|
+
while (queue.length && members.length < maxCells) {
|
|
208
|
+
const { rowIndex, columnIndex } = queue.shift();
|
|
209
|
+
for (const [r, c] of [
|
|
210
|
+
[rowIndex - 1, columnIndex],
|
|
211
|
+
[rowIndex + 1, columnIndex],
|
|
212
|
+
[rowIndex, columnIndex - 1],
|
|
213
|
+
[rowIndex, columnIndex + 1],
|
|
214
|
+
]) {
|
|
215
|
+
if (r < 1 || c < 1 || seen.has(key(r, c)))
|
|
216
|
+
continue;
|
|
217
|
+
seen.add(key(r, c));
|
|
218
|
+
const parsed = parse(r, c);
|
|
219
|
+
if (!parsed || sparklineOptionsFingerprint(parsed.options) !== fingerprint) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const member = { cell: { rowIndex: r, columnIndex: c }, dataRange: parsed.dataRange };
|
|
223
|
+
members.push(member);
|
|
224
|
+
queue.push(member.cell);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
members.sort((a, b) => a.cell.rowIndex - b.cell.rowIndex || a.cell.columnIndex - b.cell.columnIndex);
|
|
228
|
+
const { charttype: _charttype, ...options } = seed.options;
|
|
229
|
+
return { sheetId, type: sparklineTypeFromOptions(seed.options), options, members };
|
|
230
|
+
};
|
|
231
|
+
const A1_RANGE = /^\$?[A-Za-z]{1,3}\$?\d+(?::\$?[A-Za-z]{1,3}\$?\d+)?$/;
|
|
232
|
+
/** `'Q1 Data'!A1:E5` / `Sheet1!A1` / `$A$1:$E$5` → its sheet name and grid range. */
|
|
233
|
+
export const parseRangeAddress = (address) => {
|
|
234
|
+
const trimmed = address.trim();
|
|
235
|
+
if (!trimmed)
|
|
236
|
+
return null;
|
|
237
|
+
const bang = trimmed.lastIndexOf("!");
|
|
238
|
+
const sheetPart = bang >= 0 ? trimmed.slice(0, bang) : undefined;
|
|
239
|
+
const cellPart = bang >= 0 ? trimmed.slice(bang + 1) : trimmed;
|
|
240
|
+
if (!A1_RANGE.test(cellPart))
|
|
241
|
+
return null;
|
|
242
|
+
const selection = addressToSelection(cellPart.replace(/\$/g, ""));
|
|
243
|
+
if (!selection)
|
|
244
|
+
return null;
|
|
245
|
+
const sheetName = sheetPart !== undefined ? String(desanitizeSheetName(sheetPart)) : undefined;
|
|
246
|
+
return { sheetName, range: selection.range };
|
|
247
|
+
};
|
|
248
|
+
/** A grid range → `A1:E5` (a single cell → `A1`), optionally sheet-qualified. */
|
|
249
|
+
export const rangeToA1 = (range, sheetName) => {
|
|
250
|
+
const from = cellToAddress({
|
|
251
|
+
rowIndex: range.startRowIndex,
|
|
252
|
+
columnIndex: range.startColumnIndex,
|
|
253
|
+
});
|
|
254
|
+
const to = cellToAddress({
|
|
255
|
+
rowIndex: range.endRowIndex,
|
|
256
|
+
columnIndex: range.endColumnIndex,
|
|
257
|
+
});
|
|
258
|
+
const prefix = sheetName ? `${sanitizeSheetName(sheetName)}!` : "";
|
|
259
|
+
return `${prefix}${from === to ? from : `${from}:${to}`}`;
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Excel's Create Sparklines rule: one sparkline per location cell, each charting one row (or one
|
|
263
|
+
* column) of the data range. A single location cell charts the whole data range; otherwise the data
|
|
264
|
+
* range must span exactly as many rows, or exactly as many columns, as the location has cells.
|
|
265
|
+
*/
|
|
266
|
+
export const planSparklineGroup = ({ dataRange, locationRange, }) => {
|
|
267
|
+
const data = parseRangeAddress(dataRange);
|
|
268
|
+
if (!data)
|
|
269
|
+
return { error: "The data range is not valid." };
|
|
270
|
+
const location = parseRangeAddress(locationRange);
|
|
271
|
+
if (!location)
|
|
272
|
+
return { error: "The location range is not valid." };
|
|
273
|
+
const loc = location.range;
|
|
274
|
+
const locRows = loc.endRowIndex - loc.startRowIndex + 1;
|
|
275
|
+
const locCols = loc.endColumnIndex - loc.startColumnIndex + 1;
|
|
276
|
+
if (locRows > 1 && locCols > 1) {
|
|
277
|
+
return { error: "The location range must be a single row or column." };
|
|
278
|
+
}
|
|
279
|
+
const cells = [];
|
|
280
|
+
for (let r = loc.startRowIndex; r <= loc.endRowIndex; r++) {
|
|
281
|
+
for (let c = loc.startColumnIndex; c <= loc.endColumnIndex; c++) {
|
|
282
|
+
cells.push({ rowIndex: r, columnIndex: c });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const d = data.range;
|
|
286
|
+
const dataRows = d.endRowIndex - d.startRowIndex + 1;
|
|
287
|
+
const dataCols = d.endColumnIndex - d.startColumnIndex + 1;
|
|
288
|
+
const qualify = (range) => rangeToA1(range, data.sheetName);
|
|
289
|
+
if (cells.length === 1) {
|
|
290
|
+
return { members: [{ cell: cells[0], dataRange: qualify(d) }] };
|
|
291
|
+
}
|
|
292
|
+
if (dataRows === cells.length) {
|
|
293
|
+
return {
|
|
294
|
+
members: cells.map((cell, i) => ({
|
|
295
|
+
cell,
|
|
296
|
+
dataRange: qualify({
|
|
297
|
+
startRowIndex: d.startRowIndex + i,
|
|
298
|
+
endRowIndex: d.startRowIndex + i,
|
|
299
|
+
startColumnIndex: d.startColumnIndex,
|
|
300
|
+
endColumnIndex: d.endColumnIndex,
|
|
301
|
+
}),
|
|
302
|
+
})),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (dataCols === cells.length) {
|
|
306
|
+
return {
|
|
307
|
+
members: cells.map((cell, i) => ({
|
|
308
|
+
cell,
|
|
309
|
+
dataRange: qualify({
|
|
310
|
+
startRowIndex: d.startRowIndex,
|
|
311
|
+
endRowIndex: d.endRowIndex,
|
|
312
|
+
startColumnIndex: d.startColumnIndex + i,
|
|
313
|
+
endColumnIndex: d.startColumnIndex + i,
|
|
314
|
+
}),
|
|
315
|
+
})),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
error: "The data range must have the same number of rows or columns as the location range.",
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* The location Excel's dialog is seeded with for a data selection: the column just right of the
|
|
324
|
+
* data (one sparkline per data row), or the cell right of a single-row selection.
|
|
325
|
+
*/
|
|
326
|
+
export const defaultSparklineLocation = (data) => {
|
|
327
|
+
const rows = data.endRowIndex - data.startRowIndex + 1;
|
|
328
|
+
const columnIndex = data.endColumnIndex + 1;
|
|
329
|
+
return rows > 1
|
|
330
|
+
? {
|
|
331
|
+
startRowIndex: data.startRowIndex,
|
|
332
|
+
endRowIndex: data.endRowIndex,
|
|
333
|
+
startColumnIndex: columnIndex,
|
|
334
|
+
endColumnIndex: columnIndex,
|
|
335
|
+
}
|
|
336
|
+
: {
|
|
337
|
+
startRowIndex: data.startRowIndex,
|
|
338
|
+
endRowIndex: data.startRowIndex,
|
|
339
|
+
startColumnIndex: columnIndex,
|
|
340
|
+
endColumnIndex: columnIndex,
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
/** The bounding range of a group's location cells (its members are a single row or column run). */
|
|
344
|
+
export const sparklineGroupLocation = (group) => {
|
|
345
|
+
const rows = group.members.map((m) => m.cell.rowIndex);
|
|
346
|
+
const cols = group.members.map((m) => m.cell.columnIndex);
|
|
347
|
+
return {
|
|
348
|
+
startRowIndex: Math.min(...rows),
|
|
349
|
+
endRowIndex: Math.max(...rows),
|
|
350
|
+
startColumnIndex: Math.min(...cols),
|
|
351
|
+
endColumnIndex: Math.max(...cols),
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* The group's "Data Range" as Excel's Edit Group dialog shows it: the union of the members' ranges
|
|
356
|
+
* when they tile one rectangle by rows or columns; otherwise `undefined` (members chart unrelated
|
|
357
|
+
* ranges — Excel then shows the individual ranges only).
|
|
358
|
+
*/
|
|
359
|
+
export const sparklineGroupDataRange = (group) => {
|
|
360
|
+
const parsed = group.members.map((m) => parseRangeAddress(m.dataRange));
|
|
361
|
+
if (parsed.some((p) => !p))
|
|
362
|
+
return undefined;
|
|
363
|
+
const ranges = parsed;
|
|
364
|
+
const sheetName = ranges[0].sheetName;
|
|
365
|
+
if (ranges.some((r) => r.sheetName !== sheetName))
|
|
366
|
+
return undefined;
|
|
367
|
+
if (ranges.length === 1)
|
|
368
|
+
return rangeToA1(ranges[0].range, sheetName);
|
|
369
|
+
const first = ranges[0].range;
|
|
370
|
+
const byRows = ranges.every((r, i) => r.range.startColumnIndex === first.startColumnIndex &&
|
|
371
|
+
r.range.endColumnIndex === first.endColumnIndex &&
|
|
372
|
+
r.range.startRowIndex === first.startRowIndex + i &&
|
|
373
|
+
r.range.endRowIndex === r.range.startRowIndex);
|
|
374
|
+
const byCols = ranges.every((r, i) => r.range.startRowIndex === first.startRowIndex &&
|
|
375
|
+
r.range.endRowIndex === first.endRowIndex &&
|
|
376
|
+
r.range.startColumnIndex === first.startColumnIndex + i &&
|
|
377
|
+
r.range.endColumnIndex === r.range.startColumnIndex);
|
|
378
|
+
if (!byRows && !byCols)
|
|
379
|
+
return undefined;
|
|
380
|
+
const last = ranges[ranges.length - 1].range;
|
|
381
|
+
return rangeToA1({
|
|
382
|
+
startRowIndex: first.startRowIndex,
|
|
383
|
+
startColumnIndex: first.startColumnIndex,
|
|
384
|
+
endRowIndex: byRows ? last.endRowIndex : first.endRowIndex,
|
|
385
|
+
endColumnIndex: byCols ? last.endColumnIndex : first.endColumnIndex,
|
|
386
|
+
}, sheetName);
|
|
387
|
+
};
|
package/dist/esm/string.d.ts
CHANGED
|
@@ -52,6 +52,21 @@ export declare const desanitizeSheetName: (name: string | undefined) => string |
|
|
|
52
52
|
* @returns
|
|
53
53
|
*/
|
|
54
54
|
export declare const escapeCharacters: (str: string) => string;
|
|
55
|
+
/**
|
|
56
|
+
* Regular-expression source for an Excel wildcard pattern: `*` matches any run
|
|
57
|
+
* of characters, `?` exactly one, and `~` escapes the next character (`~*` is a
|
|
58
|
+
* literal asterisk). Every other character is matched literally. Callers anchor
|
|
59
|
+
* the source (`^…$` for "equals", `^…` for "begins with", …) and compile it
|
|
60
|
+
* case-insensitively — Excel's text criteria never distinguish case.
|
|
61
|
+
*/
|
|
62
|
+
export declare const wildcardPatternToRegExpSource: (pattern: string) => string;
|
|
63
|
+
/**
|
|
64
|
+
* Does `text` satisfy an Excel wildcard `pattern` (see
|
|
65
|
+
* `wildcardPatternToRegExpSource`) under the given anchoring? `equals` is a
|
|
66
|
+
* whole-string match, `startsWith` / `endsWith` anchor one side, `contains`
|
|
67
|
+
* neither. Case-insensitive like Excel's AutoFilter and COUNTIF.
|
|
68
|
+
*/
|
|
69
|
+
export declare const matchesWildcardPattern: (pattern: string, text: string, anchor: "equals" | "startsWith" | "endsWith" | "contains") => boolean;
|
|
55
70
|
/**
|
|
56
71
|
* Get selection from start and end cell
|
|
57
72
|
* @param start
|
package/dist/esm/string.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../../string.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAK3E;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,aAAa,KAAG,SAKvD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,SAAS,MAAM,EACf,UAAS;IACP,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,KACL,aAAa,GAAG,IAuDlB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,uBAEvD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,uBAErD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,aAAa,GAAG,IAAI,EAC1B,0BAAwB,EACxB,uBAAqB,EACrB,sBAAoB,EACpB,mBAAiB,KAChB,MAAM,GAAG,IAiBX,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,WAAW,MAAM,GAAG,SAAS,EAC7B,OAAO,SAAS,WAUjB,CAAC;AAGF,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,GAAG,SAAS,uBASzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,GAAG,SAAS,uBAgB3D,CAAC;AAKF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,WAI3C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,aAAa,EACpB,MAAK,aAAa,GAAG,IAAY,KAChC,SAQF,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WAKzD,CAAC;AAUF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,WAoB/C,CAAC"}
|
|
1
|
+
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../../string.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAK3E;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,aAAa,KAAG,SAKvD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,SAAS,MAAM,EACf,UAAS;IACP,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,KACL,aAAa,GAAG,IAuDlB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,uBAEvD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,uBAErD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,aAAa,GAAG,IAAI,EAC1B,0BAAwB,EACxB,uBAAqB,EACrB,sBAAoB,EACpB,mBAAiB,KAChB,MAAM,GAAG,IAiBX,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,WAAW,MAAM,GAAG,SAAS,EAC7B,OAAO,SAAS,WAUjB,CAAC;AAGF,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,GAAG,SAAS,uBASzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,GAAG,SAAS,uBAgB3D,CAAC;AAKF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,WAI3C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,SAAS,MAAM,KAAG,MAe/D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,EACf,MAAM,MAAM,EACZ,QAAQ,QAAQ,GAAG,YAAY,GAAG,UAAU,GAAG,UAAU,KACxD,OAWF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,aAAa,EACpB,MAAK,aAAa,GAAG,IAAY,KAChC,SAQF,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WAKzD,CAAC;AAUF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,WAoB/C,CAAC"}
|
package/dist/esm/string.js
CHANGED
|
@@ -166,6 +166,49 @@ export const escapeCharacters = (str) => {
|
|
|
166
166
|
? str.replace(SPECIAL_CHARACTERS, "\\$1")
|
|
167
167
|
: str || "";
|
|
168
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* Regular-expression source for an Excel wildcard pattern: `*` matches any run
|
|
171
|
+
* of characters, `?` exactly one, and `~` escapes the next character (`~*` is a
|
|
172
|
+
* literal asterisk). Every other character is matched literally. Callers anchor
|
|
173
|
+
* the source (`^…$` for "equals", `^…` for "begins with", …) and compile it
|
|
174
|
+
* case-insensitively — Excel's text criteria never distinguish case.
|
|
175
|
+
*/
|
|
176
|
+
export const wildcardPatternToRegExpSource = (pattern) => {
|
|
177
|
+
let source = "";
|
|
178
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
179
|
+
const char = pattern[i];
|
|
180
|
+
if (char === "~" && i + 1 < pattern.length) {
|
|
181
|
+
source += escapeCharacters(pattern[++i]);
|
|
182
|
+
}
|
|
183
|
+
else if (char === "*") {
|
|
184
|
+
source += ".*";
|
|
185
|
+
}
|
|
186
|
+
else if (char === "?") {
|
|
187
|
+
source += ".";
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
source += escapeCharacters(char);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return source;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Does `text` satisfy an Excel wildcard `pattern` (see
|
|
197
|
+
* `wildcardPatternToRegExpSource`) under the given anchoring? `equals` is a
|
|
198
|
+
* whole-string match, `startsWith` / `endsWith` anchor one side, `contains`
|
|
199
|
+
* neither. Case-insensitive like Excel's AutoFilter and COUNTIF.
|
|
200
|
+
*/
|
|
201
|
+
export const matchesWildcardPattern = (pattern, text, anchor) => {
|
|
202
|
+
const source = wildcardPatternToRegExpSource(pattern);
|
|
203
|
+
const anchored = anchor === "equals"
|
|
204
|
+
? `^${source}$`
|
|
205
|
+
: anchor === "startsWith"
|
|
206
|
+
? `^${source}`
|
|
207
|
+
: anchor === "endsWith"
|
|
208
|
+
? `${source}$`
|
|
209
|
+
: source;
|
|
210
|
+
return new RegExp(anchored, "is").test(text);
|
|
211
|
+
};
|
|
169
212
|
/**
|
|
170
213
|
* Get selection from start and end cell
|
|
171
214
|
* @param start
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel's rules for a table name (Table Design → Table Name / "Rename an Excel table"):
|
|
3
|
+
*
|
|
4
|
+
* - starts with a letter, an underscore (`_`) or a backslash (`\`);
|
|
5
|
+
* - the rest is letters, digits, periods (`.`) and underscores — no spaces;
|
|
6
|
+
* - is not a cell reference (`A1`, `R1C1`, `XFD1048576`) and not the lone `C` / `R` shortcuts;
|
|
7
|
+
* - is at most 255 characters;
|
|
8
|
+
* - is unique across the workbook, compared case-insensitively — against the other tables AND the
|
|
9
|
+
* defined names (Excel refuses a table named like an existing defined name, and a defined name
|
|
10
|
+
* spelled like an existing table).
|
|
11
|
+
*
|
|
12
|
+
* Both engines create and rename tables through these checks (the rnc store mirrors them in Rust and
|
|
13
|
+
* surfaces the same messages through its `rejections` channel), and the table editor's validation
|
|
14
|
+
* schema shows the same text under the name field.
|
|
15
|
+
*/
|
|
16
|
+
export declare const TABLE_NAME_MAX_LENGTH = 255;
|
|
17
|
+
export declare const TABLE_NAME_REQUIRED_MESSAGE = "Enter a table name.";
|
|
18
|
+
export declare const TABLE_NAME_TOO_LONG_MESSAGE = "Table names can't be longer than 255 characters.";
|
|
19
|
+
export declare const TABLE_NAME_SPACES_MESSAGE = "Table names can't contain spaces.";
|
|
20
|
+
export declare const TABLE_NAME_LEADING_CHAR_MESSAGE = "Table names must start with a letter, an underscore (_) or a backslash (\\).";
|
|
21
|
+
export declare const TABLE_NAME_INVALID_CHAR_MESSAGE = "Table names can only contain letters, numbers, periods (.) and underscores (_).";
|
|
22
|
+
export declare const TABLE_NAME_CELL_REFERENCE_MESSAGE = "Table names can't be a cell reference such as A1 or R1C1.";
|
|
23
|
+
export declare const tableNameDuplicateMessage: (existing: string) => string;
|
|
24
|
+
export declare const tableNameDefinedNameMessage: (existing: string) => string;
|
|
25
|
+
/** `create-table` with an `id` an existing table already holds is refused on both engines (#668 T-56). */
|
|
26
|
+
export declare const tableIdDuplicateMessage: (id: string | number) => string;
|
|
27
|
+
export declare const definedNameTableNameMessage: (existing: string) => string;
|
|
28
|
+
/** True when `name` reads as a cell reference in either A1 or R1C1 notation. */
|
|
29
|
+
export declare const isCellReferenceLikeName: (name: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The reason `name` is not an acceptable table name, or `undefined` when it is. `existingNames` are
|
|
32
|
+
* the OTHER tables' names in the workbook (leave the table being renamed out of it) and
|
|
33
|
+
* `existingDefinedNames` the workbook's defined names; duplicates against either are detected
|
|
34
|
+
* case-insensitively, the way Excel compares names.
|
|
35
|
+
*/
|
|
36
|
+
export declare const validateTableName: (name: string, existingNames?: Iterable<string> | null, existingDefinedNames?: Iterable<string> | null) => string | undefined;
|
|
37
|
+
export declare const isValidTableName: (name: string, existingNames?: Iterable<string> | null, existingDefinedNames?: Iterable<string> | null) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The reason a defined name `name` cannot be created or renamed to — it is spelled like one of
|
|
40
|
+
* `tableNames` (case-insensitive) — or `undefined` when no table holds it. Excel keeps tables and
|
|
41
|
+
* defined names in one namespace.
|
|
42
|
+
*/
|
|
43
|
+
export declare const validateDefinedNameAgainstTables: (name: string, tableNames?: Iterable<string> | null) => string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The default name for a new table: `Table1`, `Table2`, … — the LOWEST number no existing table
|
|
46
|
+
* uses (case-insensitive), so a deleted table's number is reissued and the result never collides.
|
|
47
|
+
* `format` localises the pattern (`(n) => \`Tabelle${n}\``).
|
|
48
|
+
*/
|
|
49
|
+
export declare const getNextTableName: (existingNames: Iterable<string>, format?: (index: number) => string) => string;
|
|
50
|
+
/**
|
|
51
|
+
* The name for a copy of table `base`: `Sales2`, `Sales3`, … — the LOWEST number no existing table
|
|
52
|
+
* uses (case-insensitive), the way Excel names the table on a duplicated sheet.
|
|
53
|
+
*/
|
|
54
|
+
export declare const getNextTableCopyName: (base: string, existingNames: Iterable<string>) => string;
|
|
55
|
+
//# sourceMappingURL=table-names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-names.d.ts","sourceRoot":"","sources":["../../table-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAYzC,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;AACjE,eAAO,MAAM,2BAA2B,qDAA0E,CAAC;AACnH,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAC7E,eAAO,MAAM,+BAA+B,iFACoC,CAAC;AACjF,eAAO,MAAM,+BAA+B,oFACuC,CAAC;AACpF,eAAO,MAAM,iCAAiC,8DACe,CAAC;AAC9D,eAAO,MAAM,yBAAyB,GAAI,UAAU,MAAM,WACkC,CAAC;AAC7F,eAAO,MAAM,2BAA2B,GAAI,UAAU,MAAM,WACmD,CAAC;AAChH,0GAA0G;AAC1G,eAAO,MAAM,uBAAuB,GAAI,IAAI,MAAM,GAAG,MAAM,WACK,CAAC;AACjE,eAAO,MAAM,2BAA2B,GAAI,UAAU,MAAM,WACkD,CAAC;AAmB/G,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,OAGzB,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,gBAAgB,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,EACvC,uBAAuB,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,KAC7C,MAAM,GAAG,SA4BX,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,MAAM,MAAM,EACZ,gBAAgB,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,EACvC,uBAAuB,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,KAC7C,OACyE,CAAC;AAE7E;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,GAC3C,MAAM,MAAM,EACZ,aAAa,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,KACnC,MAAM,GAAG,SAGX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,eAAe,QAAQ,CAAC,MAAM,CAAC,EAC/B,SAAQ,CAAC,KAAK,EAAE,MAAM,KAAK,MAAmC,KAC7D,MAUF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,MAAM,MAAM,EACZ,eAAe,QAAQ,CAAC,MAAM,CAAC,KAC9B,MAUF,CAAC"}
|