@rowsncolumns/utils 9.2.39 → 9.2.41

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 (39) hide show
  1. package/dist/cjs/cell-data-helpers.d.ts +13 -0
  2. package/dist/cjs/cell-data-helpers.d.ts.map +1 -1
  3. package/dist/cjs/cell-data-helpers.js +19 -1
  4. package/dist/cjs/excel-number-format.d.ts +128 -0
  5. package/dist/cjs/excel-number-format.d.ts.map +1 -0
  6. package/dist/cjs/excel-number-format.js +659 -0
  7. package/dist/cjs/formatter.d.ts +20 -0
  8. package/dist/cjs/formatter.d.ts.map +1 -1
  9. package/dist/cjs/formatter.js +300 -2
  10. package/dist/cjs/index.d.ts +2 -0
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +2 -0
  13. package/dist/cjs/locale.d.ts +15 -0
  14. package/dist/cjs/locale.d.ts.map +1 -1
  15. package/dist/cjs/locale.js +58 -6
  16. package/dist/cjs/sheet-span.d.ts +13 -0
  17. package/dist/cjs/sheet-span.d.ts.map +1 -0
  18. package/dist/cjs/sheet-span.js +24 -0
  19. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  20. package/dist/esm/cell-data-helpers.d.ts +13 -0
  21. package/dist/esm/cell-data-helpers.d.ts.map +1 -1
  22. package/dist/esm/cell-data-helpers.js +14 -0
  23. package/dist/esm/excel-number-format.d.ts +128 -0
  24. package/dist/esm/excel-number-format.d.ts.map +1 -0
  25. package/dist/esm/excel-number-format.js +640 -0
  26. package/dist/esm/formatter.d.ts +20 -0
  27. package/dist/esm/formatter.d.ts.map +1 -1
  28. package/dist/esm/formatter.js +277 -1
  29. package/dist/esm/index.d.ts +2 -0
  30. package/dist/esm/index.d.ts.map +1 -1
  31. package/dist/esm/index.js +2 -0
  32. package/dist/esm/locale.d.ts +15 -0
  33. package/dist/esm/locale.d.ts.map +1 -1
  34. package/dist/esm/locale.js +42 -5
  35. package/dist/esm/sheet-span.d.ts +13 -0
  36. package/dist/esm/sheet-span.d.ts.map +1 -0
  37. package/dist/esm/sheet-span.js +20 -0
  38. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  39. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { addLocale, format, formatColor, getFormatDateInfo, getLocale, isDateFormat, isTextFormat, } from "numfmt";
2
2
  import { IndexedColors } from "./colors";
3
+ import { excelNumberPrePass, splitFormatSections, } from "./excel-number-format";
3
4
  /**
4
5
  * Pattern-based type inference (Excel parity). xlsx files routinely carry
5
6
  * a `numberFormat.pattern` without an accompanying `type` field — these
@@ -177,12 +178,287 @@ const localeForPattern = (pattern, locale) => {
177
178
  }
178
179
  return standalone ? (getStandaloneMonthLocale(locale) ?? locale) : locale;
179
180
  };
181
+ const shortAmPmLocales = new Map();
182
+ /**
183
+ * Excel's `A/P` renders one letter (`6:00 P`) in every locale, but numfmt
184
+ * honours the short form only when no locale is passed — under a locale it
185
+ * emits that locale's full designator (`6:00 PM`). Patterns with `A/P` render
186
+ * under a companion locale whose AM/PM designators are `A`/`P`. `locale` is a
187
+ * BCP 47 tag or an Excel LCID (`409`), as numfmt accepts both.
188
+ */
189
+ const getShortAmPmLocale = (locale) => {
190
+ const cached = shortAmPmLocales.get(locale);
191
+ if (cached !== undefined)
192
+ return cached;
193
+ let overlay = locale;
194
+ try {
195
+ overlay = `ampm_${locale.replace(/[^a-z\d]/gi, "")}`;
196
+ addLocale({ ...(getLocale(locale) ?? {}), ampm: ["A", "P"] }, overlay);
197
+ }
198
+ catch {
199
+ overlay = locale;
200
+ }
201
+ shortAmPmLocales.set(locale, overlay);
202
+ return overlay;
203
+ };
204
+ /**
205
+ * An Excel `[$-LCID]` / `[$symbol-LCID]` token pins the section's locale, and
206
+ * numfmt resolves it to that LCID's own table — ahead of the companion locale
207
+ * `ssfFormat` passes, so `[$-409]h:mm A/P` would still render `6:00 PM`.
208
+ * Re-point a known LCID or tag at its companion (`$-409` → `$-ampm_409`); an
209
+ * LCID numfmt does not know (`F400`) or one carrying calendar bits stays as
210
+ * written.
211
+ */
212
+ const shortAmPmLocaleToken = (token) => {
213
+ const dash = token.indexOf("-");
214
+ if (dash < 0)
215
+ return token;
216
+ const code = token.slice(dash + 1);
217
+ const wincode = Number.parseInt(code, 16);
218
+ if (Number.isFinite(wincode) && wincode & 0xff0000)
219
+ return token;
220
+ if (!getLocale(code))
221
+ return token;
222
+ return `${token.slice(0, dash + 1)}${getShortAmPmLocale(code)}`;
223
+ };
224
+ /**
225
+ * Does the pattern carry Excel's short `A/P` marker outside quoted literals,
226
+ * bracket directives and escapes? Returns the pattern with the marker
227
+ * upper-cased (Excel accepts `a/p` and renders it `A`/`P`; numfmt only
228
+ * tokenizes `A/P`) and its `[$-LCID]` tokens re-pointed at the companion
229
+ * `A`/`P` locale, or `undefined` when there is none.
230
+ */
231
+ export const withShortAmPmMarker = (pattern) => {
232
+ const chunks = [];
233
+ const localeChunks = [];
234
+ let found = false;
235
+ let inQuotes = false;
236
+ let inBrackets = false;
237
+ for (let i = 0; i < pattern.length; i++) {
238
+ const char = pattern[i];
239
+ if (inQuotes) {
240
+ inQuotes = char !== '"';
241
+ }
242
+ else if (inBrackets) {
243
+ inBrackets = char !== "]";
244
+ }
245
+ else if (char === '"') {
246
+ inQuotes = true;
247
+ }
248
+ else if (char === "[") {
249
+ const end = pattern[i + 1] === "$" ? pattern.indexOf("]", i) : -1;
250
+ if (end > 0) {
251
+ localeChunks.push(chunks.length);
252
+ chunks.push(pattern.slice(i + 1, end));
253
+ i = end;
254
+ continue;
255
+ }
256
+ inBrackets = true;
257
+ }
258
+ else if (char === "\\" || char === "_" || char === "*") {
259
+ chunks.push(char + (pattern[i + 1] ?? ""));
260
+ i++;
261
+ continue;
262
+ }
263
+ else if ((char === "A" || char === "a") &&
264
+ pattern[i + 1] === "/" &&
265
+ (pattern[i + 2] === "P" || pattern[i + 2] === "p")) {
266
+ chunks.push("A/P");
267
+ found = true;
268
+ i += 2;
269
+ continue;
270
+ }
271
+ chunks.push(char);
272
+ }
273
+ if (!found)
274
+ return undefined;
275
+ for (const index of localeChunks) {
276
+ chunks[index] = `[${shortAmPmLocaleToken(chunks[index])}]`;
277
+ }
278
+ return chunks.join("");
279
+ };
280
+ const shortAmPmPatternCache = new Map();
281
+ const DEFAULT_SEPARATORS = { decimal: ".", group: "," };
282
+ const separatorsFor = (locale) => {
283
+ if (!locale)
284
+ return DEFAULT_SEPARATORS;
285
+ const l10n = getLocale(locale);
286
+ if (!l10n)
287
+ return DEFAULT_SEPARATORS;
288
+ return {
289
+ decimal: l10n.decimal ?? ".",
290
+ group: l10n.group ?? ",",
291
+ };
292
+ };
293
+ /**
294
+ * Rewrite the `[$symbol-LCID]` tokens of one section through `rewrite`,
295
+ * which receives the bracket content (`€-407`) and returns the new content,
296
+ * or an empty string to drop the token. Quoted literals, escapes and other
297
+ * bracket directives are left as written.
298
+ */
299
+ const rewriteSectionLocaleTokens = (section, rewrite) => {
300
+ let out = "";
301
+ let inQuotes = false;
302
+ let inBrackets = false;
303
+ for (let i = 0; i < section.length; i++) {
304
+ const char = section[i];
305
+ if (inQuotes) {
306
+ inQuotes = char !== '"';
307
+ }
308
+ else if (inBrackets) {
309
+ inBrackets = char !== "]";
310
+ }
311
+ else if (char === '"') {
312
+ inQuotes = true;
313
+ }
314
+ else if (char === "[") {
315
+ const end = section[i + 1] === "$" ? section.indexOf("]", i) : -1;
316
+ if (end > 0) {
317
+ const token = rewrite(section.slice(i + 1, end));
318
+ if (token)
319
+ out += `[${token}]`;
320
+ i = end;
321
+ continue;
322
+ }
323
+ inBrackets = true;
324
+ }
325
+ else if (char === "\\" || char === "_" || char === "*") {
326
+ out += char + (section[i + 1] ?? "");
327
+ i++;
328
+ continue;
329
+ }
330
+ out += char;
331
+ }
332
+ return out;
333
+ };
334
+ /** `[$€-407]` → `[$€]`; a bare `[$-407]` goes away. */
335
+ const currencySymbolOnly = (token) => {
336
+ const dash = token.indexOf("-");
337
+ const symbol = dash < 0 ? token : token.slice(0, dash);
338
+ return symbol.length > 1 ? symbol : "";
339
+ };
340
+ const separatorLocales = new Map();
341
+ /**
342
+ * Companion numfmt locale carrying `locale`'s calendar names and AM/PM with
343
+ * the workbook's decimal / group separators, so a date section pinned to
344
+ * `[$-407]` renders German names but a workbook decimal mark in `ss.00`.
345
+ * Returns `locale` itself when its separators already match.
346
+ */
347
+ const getWorkbookSeparatorLocale = (locale, separators) => {
348
+ const key = `${locale}\u0000${separators.decimal}\u0000${separators.group}`;
349
+ const cached = separatorLocales.get(key);
350
+ if (cached !== undefined)
351
+ return cached;
352
+ let overlay = locale;
353
+ try {
354
+ const base = getLocale(locale);
355
+ if (base &&
356
+ (base.decimal !== separators.decimal || base.group !== separators.group)) {
357
+ // numfmt locale ids are `[a-z\d]+` with one optional `_` part.
358
+ const hex = (text) => [...text].map((c) => c.codePointAt(0).toString(16)).join("x");
359
+ overlay = `sep${hex(separators.decimal)}x${hex(separators.group)}_${locale.replace(/[^a-z\d]/gi, "")}`;
360
+ addLocale({ ...base, decimal: separators.decimal, group: separators.group }, overlay);
361
+ }
362
+ }
363
+ catch {
364
+ overlay = locale;
365
+ }
366
+ separatorLocales.set(key, overlay);
367
+ return overlay;
368
+ };
369
+ /**
370
+ * Re-point a date section's `[$symbol-LCID]` token at the companion locale
371
+ * with the workbook's separators (`$-407` → `$-sep2ex2c_407`). An LCID numfmt
372
+ * does not know or one carrying calendar bits stays as written.
373
+ */
374
+ const workbookSeparatorLocaleToken = (separators) => (token) => {
375
+ const dash = token.indexOf("-");
376
+ if (dash < 0)
377
+ return token;
378
+ const code = token.slice(dash + 1);
379
+ const wincode = Number.parseInt(code, 16);
380
+ if (Number.isFinite(wincode) && wincode & 0xff0000)
381
+ return token;
382
+ if (!getLocale(code))
383
+ return token;
384
+ return `${token.slice(0, dash + 1)}${getWorkbookSeparatorLocale(code, separators)}`;
385
+ };
386
+ const isDateSection = (section) => {
387
+ try {
388
+ return isDateFormat(section);
389
+ }
390
+ catch {
391
+ return false;
392
+ }
393
+ };
394
+ /**
395
+ * Excel's `[$symbol-LCID]` token selects the currency symbol and the
396
+ * language of day/month names and AM/PM; digit grouping and the decimal
397
+ * separator follow the workbook locale (`[$€-407] #,##0.00` is
398
+ * `€ 1,234.50` on a US host). numfmt applies the token's locale to the whole
399
+ * section, separators included, so the locale part is dropped from every
400
+ * section that is not a date (number, scientific, fraction, text, …); a
401
+ * date section keeps the token for its names, re-pointed at a companion
402
+ * locale that carries the workbook's separators for fractional seconds.
403
+ * Returns `undefined` when nothing had to change.
404
+ */
405
+ export const withWorkbookSeparators = (pattern, locale) => {
406
+ if (!pattern.includes("[$"))
407
+ return undefined;
408
+ const sections = splitFormatSections(pattern);
409
+ let changed = false;
410
+ let dateTokens;
411
+ const rewritten = sections.map((section) => {
412
+ if (!section.includes("[$"))
413
+ return section;
414
+ let rewrite = currencySymbolOnly;
415
+ if (isDateSection(section)) {
416
+ dateTokens ??= workbookSeparatorLocaleToken(separatorsFor(locale));
417
+ rewrite = dateTokens;
418
+ }
419
+ const stripped = rewriteSectionLocaleTokens(section, rewrite);
420
+ changed ||= stripped !== section;
421
+ return stripped;
422
+ });
423
+ return changed ? rewritten.join(";") : undefined;
424
+ };
425
+ const workbookSeparatorPatternCache = new Map();
426
+ const withWorkbookSeparatorsCached = (code, locale) => {
427
+ const key = `${locale ?? ""}\u0000${code}`;
428
+ let rewritten = workbookSeparatorPatternCache.get(key);
429
+ if (rewritten === undefined) {
430
+ rewritten = withWorkbookSeparators(code, locale) ?? code;
431
+ workbookSeparatorPatternCache.set(key, rewritten);
432
+ }
433
+ return rewritten;
434
+ };
180
435
  export const ssfFormat = (pattern, value, locale) => {
181
436
  try {
437
+ let shortAmPm = shortAmPmPatternCache.get(pattern);
438
+ if (shortAmPm === undefined && !shortAmPmPatternCache.has(pattern)) {
439
+ shortAmPm = withShortAmPmMarker(pattern);
440
+ shortAmPmPatternCache.set(pattern, shortAmPm);
441
+ }
442
+ const code = withWorkbookSeparatorsCached(shortAmPm ?? pattern, locale);
443
+ let patternLocale = localeForPattern(pattern, locale);
444
+ if (shortAmPm !== undefined && patternLocale) {
445
+ patternLocale = getShortAmPmLocale(patternLocale);
446
+ }
447
+ const options = { locale: patternLocale };
448
+ if (typeof value === "number") {
449
+ // Excel decides rounding, `General`, huge magnitudes and conditional
450
+ // sections from the 15-significant-digit decimal, not the binary
451
+ // double (#546 U8 / U21).
452
+ const pre = excelNumberPrePass(code, value, separatorsFor(locale), (section, sectionValue) => format(section, sectionValue, options));
453
+ if (pre.text !== undefined) {
454
+ return pre.text;
455
+ }
456
+ value = pre.value;
457
+ }
182
458
  const isNegativeElapsedTime = typeof value === "number" &&
183
459
  value < 0 &&
184
460
  isSingleSectionElapsedTimePattern(pattern);
185
- const formatted = format(pattern, isNegativeElapsedTime ? Math.abs(value) : value, { locale: localeForPattern(pattern, locale) });
461
+ const formatted = format(code, isNegativeElapsedTime ? Math.abs(value) : value, options);
186
462
  return isNegativeElapsedTime ? `-${formatted}` : formatted;
187
463
  }
188
464
  catch (err) {
@@ -9,6 +9,7 @@ export * from "./locale";
9
9
  export * from "./date";
10
10
  export * from "./log";
11
11
  export * from "./array";
12
+ export * from "./excel-number-format";
12
13
  export * from "./formatter";
13
14
  export * from "./memoize";
14
15
  export * from "./cell-data-helpers";
@@ -22,4 +23,5 @@ export * from "./data-validation";
22
23
  export * from "./content-hash";
23
24
  export * from "./shared-string-canonical";
24
25
  export * from "./keyboard";
26
+ export * from "./sheet-span";
25
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
package/dist/esm/index.js CHANGED
@@ -9,6 +9,7 @@ export * from "./locale";
9
9
  export * from "./date";
10
10
  export * from "./log";
11
11
  export * from "./array";
12
+ export * from "./excel-number-format";
12
13
  export * from "./formatter";
13
14
  export * from "./memoize";
14
15
  export * from "./cell-data-helpers";
@@ -22,3 +23,4 @@ export * from "./data-validation";
22
23
  export * from "./content-hash";
23
24
  export * from "./shared-string-canonical";
24
25
  export * from "./keyboard";
26
+ export * from "./sheet-span";
@@ -16,6 +16,21 @@ export declare const getLocaleDateParts: (locale: string) => [DatePartType, stri
16
16
  * de-DE → `d.m.yyyy`, hu-HU → `yyyy. mm. dd.`, en-CA → `yyyy-mm-dd`.
17
17
  */
18
18
  export declare const getDefaultDateFormat: (locale?: string) => string;
19
+ export interface DateFieldTokens {
20
+ day: "d" | "dd";
21
+ month: "m" | "mm";
22
+ year: "yy" | "yyyy";
23
+ }
24
+ /**
25
+ * The day / month / year tokens of {@link getDefaultDateFormat} for a
26
+ * locale, i.e. whether the locale zero-pads each numeric field — ru-RU
27
+ * (`dd.mm.yyyy`) → `{ day: "dd", month: "mm", year: "yyyy" }`, en-US
28
+ * (`m/d/yyyy`) → `{ day: "d", month: "m", year: "yyyy" }`. A date typed
29
+ * as digits is displayed with the locale's own padding (Excel re-renders
30
+ * `30.9.2025` as `30.09.2025` under ru-RU), so the date parser assigns
31
+ * these tokens rather than echoing the padding the user happened to type.
32
+ */
33
+ export declare const getDefaultDateFieldTokens: (locale?: string) => DateFieldTokens;
19
34
  /**
20
35
  * Default time pattern for a locale, honoring its 12/24-hour convention,
21
36
  * separators and hour padding — e.g. en-US → `h:mm:ss AM/PM`,
@@ -1 +1 @@
1
- {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../locale.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,cAU5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,MAAM,qCAkC3D,CAAC;AAEF,eAAO,MAAM,kBAAkB,sDAAyC,CAAC;AAqFzE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAQ,MAA2B,WAClC,CAAC;AAuCvC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAQ,MAA2B,EACnC,YAAW,QAAQ,GAAG,QAAmB,WACS,CAAC;AAErD,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,WAEvD,CAAC;AA2CF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAQ,MAA2B,WAClC,CAAC"}
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../locale.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,cAU5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,MAAM,qCAkC3D,CAAC;AAEF,eAAO,MAAM,kBAAkB,sDAAyC,CAAC;AAiGzE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAQ,MAA2B,WAClC,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IAChB,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,IAAI,GAAG,MAAM,CAAC;CACrB;AAwBD;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAQ,MAA2B,KAClC,eAA4D,CAAC;AAuChE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAQ,MAA2B,EACnC,YAAW,QAAQ,GAAG,QAAmB,WACS,CAAC;AAErD,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,WAEvD,CAAC;AA2CF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAQ,MAA2B,WAClC,CAAC"}
@@ -105,9 +105,15 @@ const buildPatternFromParts = (parts, toToken) => {
105
105
  // trailing fields we skipped (e.g. an era) — trim stray whitespace.
106
106
  return pattern.trim();
107
107
  };
108
- const getDefaultDateFormatMemoized = memoize((locale) => {
108
+ /**
109
+ * `formatToParts` of the locale's numeric short date for the sample
110
+ * instant — the single source both the default pattern and its per-field
111
+ * tokens are read from, so they can never disagree. Empty when Intl has
112
+ * no data for the locale.
113
+ */
114
+ const getDefaultDateParts = memoize((locale) => {
109
115
  try {
110
- const parts = new Intl.DateTimeFormat(locale, {
116
+ return new Intl.DateTimeFormat(locale, {
111
117
  calendar: "gregory",
112
118
  numberingSystem: "latn",
113
119
  year: "numeric",
@@ -115,13 +121,15 @@ const getDefaultDateFormatMemoized = memoize((locale) => {
115
121
  day: "numeric",
116
122
  timeZone: "UTC",
117
123
  }).formatToParts(FORMAT_SAMPLE_DATE);
118
- const pattern = buildPatternFromParts(parts, dateFieldToken);
119
- return pattern || "m/d/yyyy";
120
124
  }
121
125
  catch (err) {
122
- return "m/d/yyyy";
126
+ return [];
123
127
  }
124
128
  });
129
+ const getDefaultDateFormatMemoized = memoize((locale) => {
130
+ const pattern = buildPatternFromParts(getDefaultDateParts(locale), dateFieldToken);
131
+ return pattern || "m/d/yyyy";
132
+ });
125
133
  /**
126
134
  * Default numeric (short) date pattern for a locale, preserving the
127
135
  * locale's field order, separators, literal text and zero-padding as
@@ -129,6 +137,35 @@ const getDefaultDateFormatMemoized = memoize((locale) => {
129
137
  * de-DE → `d.m.yyyy`, hu-HU → `yyyy. mm. dd.`, en-CA → `yyyy-mm-dd`.
130
138
  */
131
139
  export const getDefaultDateFormat = (locale = getCurrentLocale()) => getDefaultDateFormatMemoized(locale);
140
+ const getDefaultDateFieldTokensMemoized = memoize((locale) => {
141
+ const tokens = { day: "d", month: "m", year: "yyyy" };
142
+ for (const part of getDefaultDateParts(locale)) {
143
+ switch (part.type) {
144
+ case "day":
145
+ tokens.day = part.value.length === 2 ? "dd" : "d";
146
+ break;
147
+ case "month":
148
+ tokens.month = part.value.length === 2 ? "mm" : "m";
149
+ break;
150
+ case "year":
151
+ tokens.year = part.value.length === 2 ? "yy" : "yyyy";
152
+ break;
153
+ default:
154
+ break;
155
+ }
156
+ }
157
+ return tokens;
158
+ });
159
+ /**
160
+ * The day / month / year tokens of {@link getDefaultDateFormat} for a
161
+ * locale, i.e. whether the locale zero-pads each numeric field — ru-RU
162
+ * (`dd.mm.yyyy`) → `{ day: "dd", month: "mm", year: "yyyy" }`, en-US
163
+ * (`m/d/yyyy`) → `{ day: "d", month: "m", year: "yyyy" }`. A date typed
164
+ * as digits is displayed with the locale's own padding (Excel re-renders
165
+ * `30.9.2025` as `30.09.2025` under ru-RU), so the date parser assigns
166
+ * these tokens rather than echoing the padding the user happened to type.
167
+ */
168
+ export const getDefaultDateFieldTokens = (locale = getCurrentLocale()) => getDefaultDateFieldTokensMemoized(locale);
132
169
  const getDefaultTimeFormatMemoized = memoize((locale, precision) => {
133
170
  try {
134
171
  const parts = new Intl.DateTimeFormat(locale, {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Sheets covered by a 3-D reference span `first:last` (`Sheet1:Sheet3!A1`):
3
+ * every name between the two endpoints in tab order, inclusive. Excel spans
4
+ * the tabs between the endpoints whichever one is written first and matches
5
+ * sheet names case-insensitively. Returns undefined when an endpoint is not a
6
+ * sheet (the caller reports #REF!).
7
+ * @param sheetNames sheet names in tab order
8
+ * @param first
9
+ * @param last
10
+ * @returns
11
+ */
12
+ export declare const resolveSheetSpan: (sheetNames: readonly string[], first: string, last: string) => string[] | undefined;
13
+ //# sourceMappingURL=sheet-span.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheet-span.d.ts","sourceRoot":"","sources":["../../sheet-span.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAC3B,YAAY,SAAS,MAAM,EAAE,EAC7B,OAAO,MAAM,EACb,MAAM,MAAM,KACX,MAAM,EAAE,GAAG,SAQb,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Sheets covered by a 3-D reference span `first:last` (`Sheet1:Sheet3!A1`):
3
+ * every name between the two endpoints in tab order, inclusive. Excel spans
4
+ * the tabs between the endpoints whichever one is written first and matches
5
+ * sheet names case-insensitively. Returns undefined when an endpoint is not a
6
+ * sheet (the caller reports #REF!).
7
+ * @param sheetNames sheet names in tab order
8
+ * @param first
9
+ * @param last
10
+ * @returns
11
+ */
12
+ export const resolveSheetSpan = (sheetNames, first, last) => {
13
+ const lower = sheetNames.map((name) => name.toLowerCase());
14
+ const start = lower.indexOf(first.toLowerCase());
15
+ const end = lower.indexOf(last.toLowerCase());
16
+ if (start === -1 || end === -1) {
17
+ return undefined;
18
+ }
19
+ return sheetNames.slice(Math.min(start, end), Math.max(start, end) + 1);
20
+ };
@@ -1 +1 @@
1
- {"root":["../../array.ts","../../cell-data-helpers.ts","../../color.ts","../../colors.ts","../../conditional-format.ts","../../content-hash.ts","../../data-validation.ts","../../date.ts","../../dom.ts","../../formatter.ts","../../formula-refs.ts","../../formula.ts","../../index.ts","../../keyboard.ts","../../locale.ts","../../log.ts","../../memoize.ts","../../numbers.ts","../../selection.ts","../../shared-string-canonical.ts","../../sort.ts","../../string.ts","../../table-styles.ts","../../typeguards.ts","../../uuid.ts","../../vitest.config.ts"],"version":"5.8.3"}
1
+ {"root":["../../array.ts","../../cell-data-helpers.ts","../../color.ts","../../colors.ts","../../conditional-format.ts","../../content-hash.ts","../../data-validation.ts","../../date.ts","../../dom.ts","../../excel-number-format.ts","../../formatter.ts","../../formula-refs.ts","../../formula.ts","../../index.ts","../../keyboard.ts","../../locale.ts","../../log.ts","../../memoize.ts","../../numbers.ts","../../selection.ts","../../shared-string-canonical.ts","../../sheet-span.ts","../../sort.ts","../../string.ts","../../table-styles.ts","../../typeguards.ts","../../uuid.ts","../../vitest.config.ts"],"version":"5.8.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowsncolumns/utils",
3
- "version": "9.2.39",
3
+ "version": "9.2.41",
4
4
  "author": "Vinay M",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./dist/cjs/index.js",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "numfmt": "^3.2.2",
45
- "@rowsncolumns/common-types": ">=5.2.30"
45
+ "@rowsncolumns/common-types": ">=5.2.32"
46
46
  },
47
47
  "devDependencies": {},
48
48
  "peerDependencies": {}