@rowsncolumns/utils 9.5.6 → 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 { escapeConditionFormulaText, normalizeConditionOperand, } from "./condition-operand";
2
+ import { checkIfStringIsReference } from "./selection";
2
3
  const FORMULA_PREDICATE_BASE_CELL_REF = "A1";
3
4
  /**
4
5
  * A leading apostrophe in a data-validation / conditional-format list entry is
@@ -6,6 +7,52 @@ const FORMULA_PREDICATE_BASE_CELL_REF = "A1";
6
7
  * text — never part of the value itself.
7
8
  */
8
9
  export const stripDataValidationTextPrefix = (entry) => entry.startsWith("'") ? entry.slice(1) : entry;
10
+ /**
11
+ * A `ONE_OF_LIST` value that is a SOURCE FORMULA rather than literal items,
12
+ * returned trimmed with its leading `=` (`=RegionList`, `=$A$2:$A$9`,
13
+ * `=Sheet2!A1:A5`, `=INDIRECT("…")`). Excel's List type has one Source box
14
+ * that takes either literal items (`Yes,No`) or a formula starting with `=`;
15
+ * the "Dropdown" (`ONE_OF_LIST`) / "Dropdown (from a range)" (`ONE_OF_RANGE`)
16
+ * split means a `=…` typed into the literal dropdown is read the way Excel
17
+ * reads it — as a `ONE_OF_RANGE` over that source. A quoted literal
18
+ * (`"=a,b"`, Excel's quoted list), a `=` inside a later item (`a,=b`) and a
19
+ * bare `=` stay literal. Rust twin: `rnc_model::validation::one_of_list_formula_source`.
20
+ */
21
+ export const getDataValidationListFormulaSource = (value) => {
22
+ const trimmed = (value ?? "").trim();
23
+ return trimmed.startsWith("=") && trimmed.slice(1).trim()
24
+ ? trimmed
25
+ : undefined;
26
+ };
27
+ /**
28
+ * The condition type a rule behaves as: a `ONE_OF_LIST` whose first value is
29
+ * a source formula (`getDataValidationListFormulaSource`) behaves as
30
+ * `ONE_OF_RANGE`; every other condition is its own type. Rust twin:
31
+ * `rnc_model::validation::effective_condition_kind`.
32
+ */
33
+ export const getEffectiveDataValidationConditionType = (condition) => condition?.type === "ONE_OF_LIST" &&
34
+ getDataValidationListFormulaSource(condition.values?.[0]?.userEnteredValue)
35
+ ? "ONE_OF_RANGE"
36
+ : condition?.type ?? undefined;
37
+ /**
38
+ * The reference a list rule draws its options from, or `undefined` when the
39
+ * rule is a literal item list (or not a list rule at all). `ONE_OF_RANGE` is
40
+ * always a reference (its value, `""` when missing); a `ONE_OF_LIST` is one
41
+ * when its value is a source formula (`=RegionList`) or a bare A1 reference
42
+ * (`$A$1:$A$5`). Shared by the dropdown options, the commit gate and
43
+ * `checkBooleanRule`, so the three always agree.
44
+ */
45
+ export const getDataValidationListReference = (condition) => {
46
+ const firstValue = condition?.values?.[0]?.userEnteredValue;
47
+ if (getEffectiveDataValidationConditionType(condition) === "ONE_OF_RANGE") {
48
+ return getDataValidationListFormulaSource(firstValue) ?? firstValue ?? "";
49
+ }
50
+ if (condition?.type === "ONE_OF_LIST" &&
51
+ checkIfStringIsReference(firstValue)) {
52
+ return firstValue;
53
+ }
54
+ return undefined;
55
+ };
9
56
  /**
10
57
  * Whether a cell's current text matches a ONE_OF_LIST option, with Excel's
11
58
  * text-prefix semantics on BOTH sides: the option strips its leading `'`
@@ -80,20 +127,23 @@ export const buildDataValidationExpression = (rule, baseCellRef, locale) => {
80
127
  };
81
128
  const buildDataValidationConditionExpression = (rule, baseCellRef, locale) => {
82
129
  const condition = rule?.condition;
83
- if (!condition?.type)
130
+ // A ONE_OF_LIST whose value is a source formula (`=RegionList`) compiles as
131
+ // the ONE_OF_RANGE over that source, as Excel's List type reads it.
132
+ const type = getEffectiveDataValidationConditionType(condition);
133
+ if (!condition || !type)
84
134
  return undefined;
85
135
  const values = condition.values ?? [];
86
136
  const fromRaw = values[0]?.userEnteredValue;
87
137
  const toRaw = values[1]?.userEnteredValue;
88
- if (condition.type === "CUSTOM_FORMULA") {
138
+ if (type === "CUSTOM_FORMULA") {
89
139
  const raw = fromRaw ?? "";
90
140
  const trimmed = raw.trim();
91
141
  if (!trimmed)
92
142
  return undefined;
93
143
  return trimmed.startsWith("=") ? trimmed : `=${trimmed}`;
94
144
  }
95
- const fromExpr = normalizeConditionOperand(fromRaw, condition.type, locale);
96
- const toExpr = normalizeConditionOperand(toRaw, condition.type, locale);
145
+ const fromExpr = normalizeConditionOperand(fromRaw, type, locale);
146
+ const toExpr = normalizeConditionOperand(toRaw, type, locale);
97
147
  if (!fromExpr && !toExpr)
98
148
  return undefined;
99
149
  const comparisonOperatorMap = {
@@ -112,51 +162,49 @@ const buildDataValidationConditionExpression = (rule, baseCellRef, locale) => {
112
162
  TEXT_EQ: "=",
113
163
  TEXT_NOT_EQ: "<>",
114
164
  };
115
- if (condition.type === "NUMBER_BETWEEN" ||
116
- condition.type === "DATE_BETWEEN") {
165
+ if (type === "NUMBER_BETWEEN" || type === "DATE_BETWEEN") {
117
166
  if (!fromExpr || !toExpr)
118
167
  return undefined;
119
168
  return `=AND(${baseCellRef}>=${fromExpr},${baseCellRef}<=${toExpr})`;
120
169
  }
121
- if (condition.type === "NUMBER_NOT_BETWEEN" ||
122
- condition.type === "DATE_NOT_BETWEEN") {
170
+ if (type === "NUMBER_NOT_BETWEEN" || type === "DATE_NOT_BETWEEN") {
123
171
  if (!fromExpr || !toExpr)
124
172
  return undefined;
125
173
  return `=OR(${baseCellRef}<${fromExpr},${baseCellRef}>${toExpr})`;
126
174
  }
127
- const comparisonOperator = comparisonOperatorMap[condition.type];
175
+ const comparisonOperator = comparisonOperatorMap[type];
128
176
  if (comparisonOperator) {
129
177
  if (!fromExpr)
130
178
  return undefined;
131
179
  return `=${baseCellRef}${comparisonOperator}${fromExpr}`;
132
180
  }
133
- if (condition.type === "TEXT_CONTAINS") {
181
+ if (type === "TEXT_CONTAINS") {
134
182
  if (!fromExpr)
135
183
  return undefined;
136
184
  return `=ISNUMBER(SEARCH(${fromExpr},${baseCellRef}))`;
137
185
  }
138
- if (condition.type === "TEXT_NOT_CONTAINS") {
186
+ if (type === "TEXT_NOT_CONTAINS") {
139
187
  if (!fromExpr)
140
188
  return undefined;
141
189
  return `=NOT(ISNUMBER(SEARCH(${fromExpr},${baseCellRef})))`;
142
190
  }
143
- if (condition.type === "TEXT_STARTS_WITH") {
191
+ if (type === "TEXT_STARTS_WITH") {
144
192
  if (!fromExpr)
145
193
  return undefined;
146
194
  return `=LEFT(${baseCellRef},LEN(${fromExpr}))=${fromExpr}`;
147
195
  }
148
- if (condition.type === "TEXT_ENDS_WITH") {
196
+ if (type === "TEXT_ENDS_WITH") {
149
197
  if (!fromExpr)
150
198
  return undefined;
151
199
  return `=RIGHT(${baseCellRef},LEN(${fromExpr}))=${fromExpr}`;
152
200
  }
153
- if (condition.type === "TEXT_IS_EMAIL") {
201
+ if (type === "TEXT_IS_EMAIL") {
154
202
  return `=ISNUMBER(SEARCH("@",${baseCellRef}))`;
155
203
  }
156
- if (condition.type === "TEXT_IS_URL") {
204
+ if (type === "TEXT_IS_URL") {
157
205
  return `=OR(LEFT(${baseCellRef},7)="http://",LEFT(${baseCellRef},8)="https://")`;
158
206
  }
159
- if (condition.type === "ONE_OF_RANGE") {
207
+ if (type === "ONE_OF_RANGE") {
160
208
  if (!fromExpr)
161
209
  return undefined;
162
210
  if (rule?.allowBlank) {
@@ -164,7 +212,7 @@ const buildDataValidationConditionExpression = (rule, baseCellRef, locale) => {
164
212
  }
165
213
  return `=COUNTIF(${fromExpr},${baseCellRef})>0`;
166
214
  }
167
- if (condition.type === "ONE_OF_LIST") {
215
+ if (type === "ONE_OF_LIST") {
168
216
  const entries = fromRaw
169
217
  ? fromRaw.split(",").map((entry) => entry.trim())
170
218
  : [];
@@ -181,10 +229,14 @@ const buildDataValidationConditionExpression = (rule, baseCellRef, locale) => {
181
229
  ? [textCondition, `${baseCellRef}="'"`]
182
230
  : [textCondition];
183
231
  }
232
+ // A list ITEM is never a formula (a source formula is the whole value,
233
+ // see `getEffectiveDataValidationConditionType`): `"=a,b"` and `a,=b`
234
+ // list the text `=a` / `=b`.
235
+ if (entry.startsWith("=")) {
236
+ return [`${baseCellRef}="${escapeConditionFormulaText(entry)}"`];
237
+ }
184
238
  return entry
185
- ? [
186
- `${baseCellRef}=${normalizeConditionOperand(entry, condition.type, locale)}`,
187
- ]
239
+ ? [`${baseCellRef}=${normalizeConditionOperand(entry, type, locale)}`]
188
240
  : [];
189
241
  })
190
242
  .filter((entry) => Boolean(entry));
@@ -194,13 +246,13 @@ const buildDataValidationConditionExpression = (rule, baseCellRef, locale) => {
194
246
  ? `=${conditions[0]}`
195
247
  : `=OR(${conditions.join(",")})`;
196
248
  }
197
- if (condition.type === "BLANK") {
249
+ if (type === "BLANK") {
198
250
  return `=${baseCellRef}=""`;
199
251
  }
200
- if (condition.type === "NOT_BLANK") {
252
+ if (type === "NOT_BLANK") {
201
253
  return `=${baseCellRef}<>""`;
202
254
  }
203
- if (condition.type === "BOOLEAN") {
255
+ if (type === "BOOLEAN") {
204
256
  return `=OR(${baseCellRef}=TRUE,${baseCellRef}=FALSE)`;
205
257
  }
206
258
  return undefined;
@@ -44,5 +44,47 @@ export declare const withShortAmPmMarker: (pattern: string) => string | undefine
44
44
  */
45
45
  export declare const withWorkbookSeparators: (pattern: string, locale?: string) => string | undefined;
46
46
  export declare const ssfFormat: (pattern: string, value: any, locale?: string) => string;
47
+ /**
48
+ * One piece of a number format's width-aware output (see
49
+ * {@link ssfFormatLayout}):
50
+ * - `text` — literal characters, painted as they are;
51
+ * - `fill` — Excel's `*x`: `x` repeated to fill the cell's free width;
52
+ * - `skip` — Excel's `_x`: blank space as wide as the glyph `x`;
53
+ * - `digit` — a `?` placeholder with no digit: blank space as wide as a
54
+ * digit.
55
+ */
56
+ export type NumberFormatLayoutPart = {
57
+ type: "text";
58
+ text: string;
59
+ } | {
60
+ type: "fill";
61
+ char: string;
62
+ } | {
63
+ type: "skip";
64
+ char: string;
65
+ } | {
66
+ type: "digit";
67
+ };
68
+ /**
69
+ * Excel's width-aware number-format output: `*x` repeats `x` to the cell's
70
+ * width, `_x` reserves the width of `x` and `?` reserves a digit's width
71
+ * ("Number format codes", support.microsoft.com). A plain formatted string
72
+ * cannot carry that — `ssfFormat` drops the fill and renders the reserves as
73
+ * single spaces — so the painter asks for the parts and expands them with
74
+ * measured glyph widths.
75
+ *
76
+ * Returns `undefined` when the pattern has none of those tokens (the plain
77
+ * string is already exact), and for East Asian numeral codes. The parts'
78
+ * plain text ({@link numberFormatLayoutText}) equals `ssfFormat`'s output;
79
+ * callers compare it against the string they paint and ignore the layout
80
+ * when the two disagree.
81
+ */
82
+ export declare const ssfFormatLayout: (pattern: string, value: any, locale?: string) => NumberFormatLayoutPart[] | undefined;
83
+ /**
84
+ * The plain string a layout stands for — what `ssfFormat` returns for the
85
+ * same pattern and value: fills drop out, skips and digit pads are one
86
+ * space each.
87
+ */
88
+ export declare const numberFormatLayoutText: (parts: readonly NumberFormatLayoutPart[]) => string;
47
89
  export declare const ssfFormatColor: (pattern: string, value: any) => string | undefined;
48
90
  //# sourceMappingURL=formatter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../formatter.ts"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OASlE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KACjC,OAWF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAWlE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAOlE,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,KAAG,OA4B1D,CAAC;AA+LF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,SA4C9D,CAAC;AAiIF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,EACf,SAAS,MAAM,KACd,MAAM,GAAG,SAiBX,CAAC;AAiBF,eAAO,MAAM,SAAS,GACpB,SAAS,MAAM,EACf,OAAO,GAAG,EACV,SAAS,MAAM,KACd,MA0CF,CAAC;AAmLF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,OAAO,GAAG,KACT,MAAM,GAAG,SAoBX,CAAC"}
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../formatter.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OASlE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KACjC,OAWF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAWlE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAOlE,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,KAAG,OA4B1D,CAAC;AA+LF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,SA4C9D,CAAC;AAiIF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,EACf,SAAS,MAAM,KACd,MAAM,GAAG,SAiBX,CAAC;AAiBF,eAAO,MAAM,SAAS,GACpB,SAAS,MAAM,EACf,OAAO,GAAG,EACV,SAAS,MAAM,KACd,MASF,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAoDtB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,EACf,OAAO,GAAG,EACV,SAAS,MAAM,KACd,sBAAsB,EAAE,GAAG,SA8C7B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO,SAAS,sBAAsB,EAAE,KACvC,MAOF,CAAC;AAkQF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,OAAO,GAAG,KACT,MAAM,GAAG,SAoBX,CAAC"}
@@ -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.6",
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": {}