@rowsncolumns/utils 9.2.44 → 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.
Files changed (51) hide show
  1. package/dist/cjs/formula-locale/formula-locale-data.generated.d.ts +44 -0
  2. package/dist/cjs/formula-locale/formula-locale-data.generated.d.ts.map +1 -0
  3. package/dist/cjs/formula-locale/formula-locale-data.generated.js +5567 -0
  4. package/dist/cjs/formula-locale.d.ts +159 -0
  5. package/dist/cjs/formula-locale.d.ts.map +1 -0
  6. package/dist/cjs/formula-locale.js +804 -0
  7. package/dist/cjs/index.d.ts +4 -0
  8. package/dist/cjs/index.d.ts.map +1 -1
  9. package/dist/cjs/index.js +4 -0
  10. package/dist/cjs/sparkline-groups.d.ts +102 -0
  11. package/dist/cjs/sparkline-groups.d.ts.map +1 -0
  12. package/dist/cjs/sparkline-groups.js +452 -0
  13. package/dist/cjs/string.d.ts +15 -0
  14. package/dist/cjs/string.d.ts.map +1 -1
  15. package/dist/cjs/string.js +46 -1
  16. package/dist/cjs/table-names.d.ts +55 -0
  17. package/dist/cjs/table-names.d.ts.map +1 -0
  18. package/dist/cjs/table-names.js +189 -0
  19. package/dist/cjs/table-styles.d.ts +9 -0
  20. package/dist/cjs/table-styles.d.ts.map +1 -1
  21. package/dist/cjs/table-styles.js +53 -1
  22. package/dist/cjs/table-totals.d.ts +31 -0
  23. package/dist/cjs/table-totals.d.ts.map +1 -0
  24. package/dist/cjs/table-totals.js +84 -0
  25. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  26. package/dist/esm/formula-locale/formula-locale-data.generated.d.ts +44 -0
  27. package/dist/esm/formula-locale/formula-locale-data.generated.d.ts.map +1 -0
  28. package/dist/esm/formula-locale/formula-locale-data.generated.js +5564 -0
  29. package/dist/esm/formula-locale.d.ts +159 -0
  30. package/dist/esm/formula-locale.d.ts.map +1 -0
  31. package/dist/esm/formula-locale.js +0 -0
  32. package/dist/esm/index.d.ts +4 -0
  33. package/dist/esm/index.d.ts.map +1 -1
  34. package/dist/esm/index.js +4 -0
  35. package/dist/esm/sparkline-groups.d.ts +102 -0
  36. package/dist/esm/sparkline-groups.d.ts.map +1 -0
  37. package/dist/esm/sparkline-groups.js +387 -0
  38. package/dist/esm/string.d.ts +15 -0
  39. package/dist/esm/string.d.ts.map +1 -1
  40. package/dist/esm/string.js +43 -0
  41. package/dist/esm/table-names.d.ts +55 -0
  42. package/dist/esm/table-names.d.ts.map +1 -0
  43. package/dist/esm/table-names.js +129 -0
  44. package/dist/esm/table-styles.d.ts +9 -0
  45. package/dist/esm/table-styles.d.ts.map +1 -1
  46. package/dist/esm/table-styles.js +34 -1
  47. package/dist/esm/table-totals.d.ts +31 -0
  48. package/dist/esm/table-totals.d.ts.map +1 -0
  49. package/dist/esm/table-totals.js +68 -0
  50. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  51. package/package.json +5 -3
@@ -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
+ };
@@ -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
@@ -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"}
@@ -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"}
@@ -0,0 +1,129 @@
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 const TABLE_NAME_MAX_LENGTH = 255;
17
+ // Built with `RegExp` (not literals) so the `u` flag survives the CJS build's ES5 target.
18
+ const LEADING_CHAR = new RegExp("^[\\p{L}_\\\\]", "u");
19
+ const BODY_CHARS = new RegExp("^[\\p{L}\\p{N}._]*$", "u");
20
+ /** `A1`-style — up to three ASCII column letters then a row number. */
21
+ const A1_REFERENCE = /^[A-Za-z]{1,3}\d+$/;
22
+ /** `R1C1`-style — `R`/`C` each followed by an optional index (`RC`, `R1C`, `R1C1`). */
23
+ const R1C1_REFERENCE = /^[Rr]\d*[Cc]\d*$/;
24
+ /** `C` and `R` alone select the active cell's column / row in the Name Box. */
25
+ const RESERVED_SHORTCUT = /^[CcRr]$/;
26
+ export const TABLE_NAME_REQUIRED_MESSAGE = "Enter a table name.";
27
+ export const TABLE_NAME_TOO_LONG_MESSAGE = `Table names can't be longer than ${TABLE_NAME_MAX_LENGTH} characters.`;
28
+ export const TABLE_NAME_SPACES_MESSAGE = "Table names can't contain spaces.";
29
+ export const TABLE_NAME_LEADING_CHAR_MESSAGE = "Table names must start with a letter, an underscore (_) or a backslash (\\).";
30
+ export const TABLE_NAME_INVALID_CHAR_MESSAGE = "Table names can only contain letters, numbers, periods (.) and underscores (_).";
31
+ export const TABLE_NAME_CELL_REFERENCE_MESSAGE = "Table names can't be a cell reference such as A1 or R1C1.";
32
+ export const tableNameDuplicateMessage = (existing) => `A table named "${existing}" already exists in this workbook. Table names must be unique.`;
33
+ export const tableNameDefinedNameMessage = (existing) => `A defined name "${existing}" already exists in this workbook. Table names and defined names must be unique.`;
34
+ /** `create-table` with an `id` an existing table already holds is refused on both engines (#668 T-56). */
35
+ export const tableIdDuplicateMessage = (id) => `A table with the id "${id}" already exists in this workbook.`;
36
+ export const definedNameTableNameMessage = (existing) => `A table named "${existing}" already exists in this workbook. Table names and defined names must be unique.`;
37
+ /** The entry of `names` that equals `name` case-insensitively, the way Excel compares names. */
38
+ const findNameCollision = (name, names) => {
39
+ if (!names) {
40
+ return undefined;
41
+ }
42
+ const lower = name.toLowerCase();
43
+ for (const existing of names) {
44
+ if (existing.toLowerCase() === lower) {
45
+ return existing;
46
+ }
47
+ }
48
+ return undefined;
49
+ };
50
+ /** True when `name` reads as a cell reference in either A1 or R1C1 notation. */
51
+ export const isCellReferenceLikeName = (name) => A1_REFERENCE.test(name) ||
52
+ R1C1_REFERENCE.test(name) ||
53
+ RESERVED_SHORTCUT.test(name);
54
+ /**
55
+ * The reason `name` is not an acceptable table name, or `undefined` when it is. `existingNames` are
56
+ * the OTHER tables' names in the workbook (leave the table being renamed out of it) and
57
+ * `existingDefinedNames` the workbook's defined names; duplicates against either are detected
58
+ * case-insensitively, the way Excel compares names.
59
+ */
60
+ export const validateTableName = (name, existingNames, existingDefinedNames) => {
61
+ if (!name) {
62
+ return TABLE_NAME_REQUIRED_MESSAGE;
63
+ }
64
+ if (name.length > TABLE_NAME_MAX_LENGTH) {
65
+ return TABLE_NAME_TOO_LONG_MESSAGE;
66
+ }
67
+ if (/\s/.test(name)) {
68
+ return TABLE_NAME_SPACES_MESSAGE;
69
+ }
70
+ if (!LEADING_CHAR.test(name)) {
71
+ return TABLE_NAME_LEADING_CHAR_MESSAGE;
72
+ }
73
+ if (!BODY_CHARS.test(name.slice(1))) {
74
+ return TABLE_NAME_INVALID_CHAR_MESSAGE;
75
+ }
76
+ if (isCellReferenceLikeName(name)) {
77
+ return TABLE_NAME_CELL_REFERENCE_MESSAGE;
78
+ }
79
+ const duplicate = findNameCollision(name, existingNames);
80
+ if (duplicate !== undefined) {
81
+ return tableNameDuplicateMessage(duplicate);
82
+ }
83
+ const definedName = findNameCollision(name, existingDefinedNames);
84
+ if (definedName !== undefined) {
85
+ return tableNameDefinedNameMessage(definedName);
86
+ }
87
+ return undefined;
88
+ };
89
+ export const isValidTableName = (name, existingNames, existingDefinedNames) => validateTableName(name, existingNames, existingDefinedNames) === undefined;
90
+ /**
91
+ * The reason a defined name `name` cannot be created or renamed to — it is spelled like one of
92
+ * `tableNames` (case-insensitive) — or `undefined` when no table holds it. Excel keeps tables and
93
+ * defined names in one namespace.
94
+ */
95
+ export const validateDefinedNameAgainstTables = (name, tableNames) => {
96
+ const table = findNameCollision(name, tableNames);
97
+ return table === undefined ? undefined : definedNameTableNameMessage(table);
98
+ };
99
+ /**
100
+ * The default name for a new table: `Table1`, `Table2`, … — the LOWEST number no existing table
101
+ * uses (case-insensitive), so a deleted table's number is reissued and the result never collides.
102
+ * `format` localises the pattern (`(n) => \`Tabelle${n}\``).
103
+ */
104
+ export const getNextTableName = (existingNames, format = (index) => `Table${index}`) => {
105
+ const taken = new Set();
106
+ for (const name of existingNames) {
107
+ taken.add(name.toLowerCase());
108
+ }
109
+ let index = 1;
110
+ while (taken.has(format(index).toLowerCase())) {
111
+ index++;
112
+ }
113
+ return format(index);
114
+ };
115
+ /**
116
+ * The name for a copy of table `base`: `Sales2`, `Sales3`, … — the LOWEST number no existing table
117
+ * uses (case-insensitive), the way Excel names the table on a duplicated sheet.
118
+ */
119
+ export const getNextTableCopyName = (base, existingNames) => {
120
+ const taken = new Set();
121
+ for (const name of existingNames) {
122
+ taken.add(name.toLowerCase());
123
+ }
124
+ let index = 2;
125
+ while (taken.has(`${base}${index}`.toLowerCase())) {
126
+ index++;
127
+ }
128
+ return `${base}${index}`;
129
+ };
@@ -32,6 +32,15 @@ export type BandingProperties = {
32
32
  secondBandBorder?: Maybe<Borders>;
33
33
  totalRowColor?: Maybe<Color>;
34
34
  totalRowBorder?: Maybe<Borders>;
35
+ /**
36
+ * Fill of the table's first / last column when the table shows them (`TableView.showFirstColumn`
37
+ * / `showLastColumn` — Excel's Table Design → First Column / Last Column). Every built-in style
38
+ * emphasises those columns in bold; only the Dark styles add a fill.
39
+ */
40
+ firstColumnColor?: Maybe<Color>;
41
+ lastColumnColor?: Maybe<Color>;
42
+ /** Excel's `totalRow` style element bolds the totals row. */
43
+ totalRowBold?: Maybe<boolean>;
35
44
  };
36
45
  export type BandedDefinition = {
37
46
  rowProperties?: Maybe<BandingProperties>;