@rowsncolumns/utils 9.5.7 → 9.6.1

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.
@@ -1,4 +1,5 @@
1
1
  import { addLocale, format, formatColor, getFormatDateInfo, getLocale, isDateFormat, isTextFormat, } from "numfmt";
2
+ import { prepareCjkNumberFormat } from "./cjk-number-format";
2
3
  import { IndexedColors } from "./colors";
3
4
  import { excelNumberPrePass, splitFormatSections, } from "./excel-number-format";
4
5
  /**
@@ -498,6 +499,153 @@ const withWorkbookSeparatorsCached = (code, locale) => {
498
499
  return rewritten;
499
500
  };
500
501
  export const ssfFormat = (pattern, value, locale) => {
502
+ // East Asian tokens numfmt does not render (`[DBNumN]`, Japanese era `g`/`e`,
503
+ // `aaa` weekdays): rewrite the code to plain literals, then convert the
504
+ // rendered digits to the `[DBNumN]` numeral system (see cjk-number-format).
505
+ if (typeof value === "number") {
506
+ const cjk = prepareCjkNumberFormat(pattern, value, locale);
507
+ if (cjk)
508
+ return cjk.finish(ssfFormatBase(cjk.code, value, locale));
509
+ }
510
+ return ssfFormatBase(pattern, value, locale);
511
+ };
512
+ const LAYOUT_FILL_MARK = "\uE000";
513
+ const LAYOUT_SKIP_MARK = "\uE001";
514
+ const LAYOUT_LITERAL_SPACE = "\uE002";
515
+ const LAYOUT_DIGIT_PAD = "\u00a0";
516
+ /**
517
+ * Quote every literal space of `code` as a private-use sentinel so that,
518
+ * rendered with numfmt's `nbsp` option, the only non-breaking spaces left
519
+ * in the output are the `?` digit pads.
520
+ */
521
+ const markLiteralSpaces = (code) => {
522
+ let out = "";
523
+ let inQuotes = false;
524
+ let inBrackets = false;
525
+ for (let i = 0; i < code.length; i++) {
526
+ const char = code[i];
527
+ if (inQuotes) {
528
+ if (char === '"')
529
+ inQuotes = false;
530
+ out += char === " " ? LAYOUT_LITERAL_SPACE : char;
531
+ continue;
532
+ }
533
+ if (inBrackets) {
534
+ if (char === "]")
535
+ inBrackets = false;
536
+ out += char;
537
+ continue;
538
+ }
539
+ if (char === '"') {
540
+ inQuotes = true;
541
+ }
542
+ else if (char === "[") {
543
+ inBrackets = true;
544
+ }
545
+ else if (char === "\\" && code[i + 1] === " ") {
546
+ out += `"${LAYOUT_LITERAL_SPACE}"`;
547
+ i++;
548
+ continue;
549
+ }
550
+ else if (char === "\\" || char === "_" || char === "*") {
551
+ out += char + (code[i + 1] ?? "");
552
+ i++;
553
+ continue;
554
+ }
555
+ else if (char === " ") {
556
+ out += `"${LAYOUT_LITERAL_SPACE}"`;
557
+ continue;
558
+ }
559
+ out += char;
560
+ }
561
+ return out;
562
+ };
563
+ /** Does the pattern carry a `*` fill, `_` skip or `?` placeholder at all? */
564
+ const LAYOUT_TOKEN_REGEX = /[*_?]/;
565
+ /**
566
+ * Excel's width-aware number-format output: `*x` repeats `x` to the cell's
567
+ * width, `_x` reserves the width of `x` and `?` reserves a digit's width
568
+ * ("Number format codes", support.microsoft.com). A plain formatted string
569
+ * cannot carry that — `ssfFormat` drops the fill and renders the reserves as
570
+ * single spaces — so the painter asks for the parts and expands them with
571
+ * measured glyph widths.
572
+ *
573
+ * Returns `undefined` when the pattern has none of those tokens (the plain
574
+ * string is already exact), and for East Asian numeral codes. The parts'
575
+ * plain text ({@link numberFormatLayoutText}) equals `ssfFormat`'s output;
576
+ * callers compare it against the string they paint and ignore the layout
577
+ * when the two disagree.
578
+ */
579
+ export const ssfFormatLayout = (pattern, value, locale) => {
580
+ if (!pattern || !LAYOUT_TOKEN_REGEX.test(pattern))
581
+ return undefined;
582
+ if (typeof value === "number" && prepareCjkNumberFormat(pattern, value, locale)) {
583
+ return undefined;
584
+ }
585
+ const raw = ssfFormatBase(pattern, value, locale, {
586
+ nbsp: true,
587
+ fillChar: LAYOUT_FILL_MARK,
588
+ skipChar: LAYOUT_SKIP_MARK,
589
+ transform: markLiteralSpaces,
590
+ });
591
+ if (raw === undefined)
592
+ return undefined;
593
+ // A locale whose group separator is itself a non-breaking space makes the
594
+ // digit pads ambiguous: keep those as plain spaces.
595
+ const digitPads = separatorsFor(locale).group !== LAYOUT_DIGIT_PAD;
596
+ const parts = [];
597
+ let text = "";
598
+ const flush = () => {
599
+ if (text)
600
+ parts.push({ type: "text", text });
601
+ text = "";
602
+ };
603
+ for (let i = 0; i < raw.length; i++) {
604
+ const char = raw[i];
605
+ if (char === LAYOUT_FILL_MARK || char === LAYOUT_SKIP_MARK) {
606
+ const next = raw[i + 1] ?? " ";
607
+ flush();
608
+ parts.push({
609
+ type: char === LAYOUT_FILL_MARK ? "fill" : "skip",
610
+ char: next,
611
+ });
612
+ i++;
613
+ }
614
+ else if (char === LAYOUT_DIGIT_PAD) {
615
+ if (digitPads) {
616
+ flush();
617
+ parts.push({ type: "digit" });
618
+ }
619
+ else {
620
+ text += char;
621
+ }
622
+ }
623
+ else if (char === LAYOUT_LITERAL_SPACE) {
624
+ text += " ";
625
+ }
626
+ else {
627
+ text += char;
628
+ }
629
+ }
630
+ flush();
631
+ return parts.some((part) => part.type !== "text") ? parts : undefined;
632
+ };
633
+ /**
634
+ * The plain string a layout stands for — what `ssfFormat` returns for the
635
+ * same pattern and value: fills drop out, skips and digit pads are one
636
+ * space each.
637
+ */
638
+ export const numberFormatLayoutText = (parts) => {
639
+ let out = "";
640
+ for (const part of parts) {
641
+ if (part.type === "text")
642
+ out += part.text;
643
+ else if (part.type !== "fill")
644
+ out += " ";
645
+ }
646
+ return out;
647
+ };
648
+ function ssfFormatBase(pattern, value, locale, layout) {
501
649
  try {
502
650
  if (locale)
503
651
  ensureNumfmtLocale(locale);
@@ -506,12 +654,22 @@ export const ssfFormat = (pattern, value, locale) => {
506
654
  shortAmPm = withShortAmPmMarker(pattern);
507
655
  shortAmPmPatternCache.set(pattern, shortAmPm);
508
656
  }
509
- const code = withWorkbookSeparatorsCached(shortAmPm ?? pattern, locale);
657
+ const baseCode = withWorkbookSeparatorsCached(shortAmPm ?? pattern, locale);
658
+ const code = layout ? layout.transform(baseCode) : baseCode;
510
659
  let patternLocale = localeForPattern(pattern, locale);
511
660
  if (shortAmPm !== undefined && patternLocale) {
512
661
  patternLocale = getShortAmPmLocale(patternLocale);
513
662
  }
514
- const options = { locale: patternLocale };
663
+ // numfmt types `fillChar` / `skipChar` as booleans; at runtime they are
664
+ // the marker strings it emits ahead of the fill / skip character.
665
+ const options = layout
666
+ ? {
667
+ locale: patternLocale,
668
+ nbsp: layout.nbsp,
669
+ fillChar: layout.fillChar,
670
+ skipChar: layout.skipChar,
671
+ }
672
+ : { locale: patternLocale };
515
673
  if (typeof value === "number") {
516
674
  // Excel decides rounding, `General`, huge magnitudes and conditional
517
675
  // sections from the 15-significant-digit decimal, not the binary
@@ -529,9 +687,9 @@ export const ssfFormat = (pattern, value, locale) => {
529
687
  return isNegativeElapsedTime ? `-${formatted}` : formatted;
530
688
  }
531
689
  catch (err) {
532
- return String(value);
690
+ return layout ? undefined : String(value);
533
691
  }
534
- };
692
+ }
535
693
  /**
536
694
  * Excel's legacy fixed 56-color palette used by `[ColorN]` directives
537
695
  * inside number format patterns (1-indexed). This palette is INDEPENDENT
@@ -1 +1 @@
1
- {"root":["../../array.ts","../../cell-data-helpers.ts","../../clipboard-html-sanitize.ts","../../color.ts","../../colors.ts","../../condition-operand.ts","../../conditional-format.ts","../../content-hash.ts","../../data-validation.ts","../../date.ts","../../dom.ts","../../excel-number-format.ts","../../formatter.ts","../../formula-locale.ts","../../formula-refs.ts","../../formula.ts","../../index.ts","../../keyboard.ts","../../locale.ts","../../log.ts","../../memoize.ts","../../numbers.ts","../../rich-text-html.ts","../../rich-text-links.ts","../../selection.ts","../../shared-string-canonical.ts","../../sheet-names.ts","../../sheet-protection.ts","../../sheet-span.ts","../../sort.ts","../../sparkline-groups.ts","../../string.ts","../../table-names.ts","../../table-styles.ts","../../table-totals.ts","../../typeguards.ts","../../uuid.ts","../../vitest.config.ts","../../formula-locale/formula-locale-data.generated.ts"],"version":"5.8.3"}
1
+ {"root":["../../array.ts","../../cell-data-helpers.ts","../../cjk-number-format.ts","../../clipboard-html-sanitize.ts","../../color.ts","../../colors.ts","../../condition-operand.ts","../../conditional-format.ts","../../content-hash.ts","../../data-validation.ts","../../date.ts","../../dom.ts","../../excel-number-format.ts","../../formatter.ts","../../formula-locale.ts","../../formula-refs.ts","../../formula.ts","../../index.ts","../../keyboard.ts","../../locale.ts","../../log.ts","../../memoize.ts","../../numbers.ts","../../rich-text-html.ts","../../rich-text-links.ts","../../selection.ts","../../shared-string-canonical.ts","../../sheet-names.ts","../../sheet-protection.ts","../../sheet-span.ts","../../sort.ts","../../sparkline-groups.ts","../../string.ts","../../table-names.ts","../../table-styles.ts","../../table-totals.ts","../../typeguards.ts","../../uuid.ts","../../vitest.config.ts","../../formula-locale/formula-locale-data.generated.ts"],"version":"5.8.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowsncolumns/utils",
3
- "version": "9.5.7",
3
+ "version": "9.6.1",
4
4
  "author": "Vinay M",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./dist/cjs/index.js",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "numfmt": "^3.2.2",
47
- "@rowsncolumns/common-types": ">=5.3.2"
47
+ "@rowsncolumns/common-types": ">=5.3.5"
48
48
  },
49
49
  "devDependencies": {},
50
50
  "peerDependencies": {}