@rowsncolumns/utils 9.5.1 → 9.5.3

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 (55) hide show
  1. package/dist/cjs/clipboard-html-sanitize.d.ts +15 -0
  2. package/dist/cjs/clipboard-html-sanitize.d.ts.map +1 -0
  3. package/dist/cjs/clipboard-html-sanitize.js +237 -0
  4. package/dist/cjs/condition-operand.d.ts +54 -0
  5. package/dist/cjs/condition-operand.d.ts.map +1 -0
  6. package/dist/cjs/condition-operand.js +223 -0
  7. package/dist/cjs/conditional-format.d.ts.map +1 -1
  8. package/dist/cjs/conditional-format.js +5 -59
  9. package/dist/cjs/data-validation.d.ts +9 -0
  10. package/dist/cjs/data-validation.d.ts.map +1 -1
  11. package/dist/cjs/data-validation.js +29 -60
  12. package/dist/cjs/index.d.ts +4 -0
  13. package/dist/cjs/index.d.ts.map +1 -1
  14. package/dist/cjs/index.js +4 -0
  15. package/dist/cjs/rich-text-html.d.ts +9 -0
  16. package/dist/cjs/rich-text-html.d.ts.map +1 -1
  17. package/dist/cjs/rich-text-html.js +52 -23
  18. package/dist/cjs/sheet-names.d.ts +60 -0
  19. package/dist/cjs/sheet-names.d.ts.map +1 -0
  20. package/dist/cjs/sheet-names.js +157 -0
  21. package/dist/cjs/sheet-protection.d.ts +9 -0
  22. package/dist/cjs/sheet-protection.d.ts.map +1 -0
  23. package/dist/cjs/sheet-protection.js +11 -0
  24. package/dist/cjs/string.d.ts +31 -0
  25. package/dist/cjs/string.d.ts.map +1 -1
  26. package/dist/cjs/string.js +73 -3
  27. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  28. package/dist/esm/clipboard-html-sanitize.d.ts +15 -0
  29. package/dist/esm/clipboard-html-sanitize.d.ts.map +1 -0
  30. package/dist/esm/clipboard-html-sanitize.js +192 -0
  31. package/dist/esm/condition-operand.d.ts +54 -0
  32. package/dist/esm/condition-operand.d.ts.map +1 -0
  33. package/dist/esm/condition-operand.js +203 -0
  34. package/dist/esm/conditional-format.d.ts.map +1 -1
  35. package/dist/esm/conditional-format.js +5 -57
  36. package/dist/esm/data-validation.d.ts +9 -0
  37. package/dist/esm/data-validation.d.ts.map +1 -1
  38. package/dist/esm/data-validation.js +27 -57
  39. package/dist/esm/index.d.ts +4 -0
  40. package/dist/esm/index.d.ts.map +1 -1
  41. package/dist/esm/index.js +4 -0
  42. package/dist/esm/rich-text-html.d.ts +9 -0
  43. package/dist/esm/rich-text-html.d.ts.map +1 -1
  44. package/dist/esm/rich-text-html.js +20 -4
  45. package/dist/esm/sheet-names.d.ts +60 -0
  46. package/dist/esm/sheet-names.d.ts.map +1 -0
  47. package/dist/esm/sheet-names.js +109 -0
  48. package/dist/esm/sheet-protection.d.ts +9 -0
  49. package/dist/esm/sheet-protection.d.ts.map +1 -0
  50. package/dist/esm/sheet-protection.js +8 -0
  51. package/dist/esm/string.d.ts +31 -0
  52. package/dist/esm/string.d.ts.map +1 -1
  53. package/dist/esm/string.js +62 -2
  54. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  55. package/package.json +2 -2
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROTECTED_SHEET_EDIT_MESSAGE = exports.SHEET_PROTECTION_ERROR_TITLE = void 0;
4
+ /**
5
+ * Excel's refusal when an edit touches a locked cell of a protected sheet — the dialog every
6
+ * mutation path shows (typing, Delete / Backspace, fill, paste) on both engines
7
+ * (rowsncolumns/spreadsheet#939 U-35). The rnc store surfaces the same text through its
8
+ * `rejections` channel under the `protected-sheet` code.
9
+ */
10
+ exports.SHEET_PROTECTION_ERROR_TITLE = "This sheet is protected";
11
+ exports.PROTECTED_SHEET_EDIT_MESSAGE = "The cell or chart you're trying to change is on a protected sheet. To make a change, unprotect the sheet. You might be requested to enter a password.";
@@ -38,6 +38,20 @@ export declare const cellToAddress: (cell: CellInterface | null, isAbsoluteColum
38
38
  * @returns
39
39
  */
40
40
  export declare const cellRangeToAddress: (sheetName: string | undefined, range: GridRange) => string;
41
+ /**
42
+ * Must `name` be single-quoted as a sheet qualifier (`'My Data'!A1`)? Excel writes it bare only when
43
+ * it is a plain identifier — ASCII letters and digits, starting with a letter — and cannot be read
44
+ * as something else: a cell reference (`A1`, `XFD1`, `R1C1`), or a boolean (`TRUE`). A leading digit
45
+ * (`2024`, `1Sheet`) always quotes. Deliberately a superset of Excel's minimum (a `_`, `.` or
46
+ * non-ASCII letter also quotes): over-quoting is always valid, under-quoting makes `=2024!A1` a
47
+ * formula Excel rejects (rowsncolumns/spreadsheet#939 K-10).
48
+ */
49
+ export declare const sheetNameNeedsQuoting: (name: string) => boolean;
50
+ /**
51
+ * Quote a sheet name for use as a formula qualifier when Excel requires it (see
52
+ * {@link sheetNameNeedsQuoting}); apostrophes inside are doubled (`'O''Neil'`). Rename, cross-sheet
53
+ * reference generation and formula-bar insertion all quote through here so they agree.
54
+ */
41
55
  export declare const sanitizeSheetName: (name: string | undefined) => string | undefined;
42
56
  /**
43
57
  * Remove single quotes from sheetname
@@ -76,6 +90,23 @@ export declare const matchesWildcardPattern: (pattern: string, text: string, anc
76
90
  */
77
91
  export declare const selectionBoundsFromCells: (start: CellInterface, end?: CellInterface | null) => GridRange;
78
92
  export declare const supplant: (s: string, o: Record<string, any>) => string;
93
+ /**
94
+ * Whether a hyperlink target read from untrusted markup (clipboard HTML) may
95
+ * be kept. A URL with an explicit scheme must use one of the safe schemes;
96
+ * scheme-less, protocol-relative, path and fragment references are allowed
97
+ * (they are normalized by {@link normalizeExternalUrl} before being opened).
98
+ * Applies the same tab/newline and C0-control stripping as the browser's URL
99
+ * canonicalization so `java\tscript:` cannot slip through.
100
+ */
101
+ export declare const isSafeHyperlinkUri: (uri: string | null | undefined) => boolean;
102
+ /**
103
+ * Escape text for interpolation into an HTML text node (`text/html`
104
+ * clipboard fragments): a cell whose value is literally `<b>x</b>` must paste
105
+ * back as that text, not as bold "x".
106
+ */
107
+ export declare const escapeHtmlText: (value: string) => string;
108
+ /** Escape a value for interpolation into a quoted HTML attribute. */
109
+ export declare const escapeHtmlAttribute: (value: string) => string;
79
110
  /**
80
111
  * Normalize an external hyperlink target for the browser. A stored URL
81
112
  * without a scheme ("paradigm.xyz") would otherwise be resolved relative to
@@ -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,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"}
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;AAOF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAI,MAAM,MAAM,KAAG,OAMrB,CAAC;AAEjC;;;;GAIG;AACH,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;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,OAgBnE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,MAC2B,CAAC;AAE3E,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,GAAI,OAAO,MAAM,KAAG,MAM1B,CAAC;AAE3B;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,WAoB/C,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeExternalUrl = exports.supplant = exports.selectionBoundsFromCells = exports.matchesWildcardPattern = exports.wildcardPatternToRegExpSource = exports.escapeCharacters = exports.desanitizeSheetName = exports.sanitizeSheetName = exports.cellRangeToAddress = exports.cellToAddress = exports.getRowIndexFromAddress = exports.getColumnNameFromAddress = exports.addressToCell = exports.convertCellToRange = void 0;
3
+ exports.normalizeExternalUrl = exports.escapeHtmlAttribute = exports.escapeHtmlText = exports.isSafeHyperlinkUri = exports.supplant = exports.selectionBoundsFromCells = exports.matchesWildcardPattern = exports.wildcardPatternToRegExpSource = exports.escapeCharacters = exports.desanitizeSheetName = exports.sanitizeSheetName = exports.sheetNameNeedsQuoting = exports.cellRangeToAddress = exports.cellToAddress = exports.getRowIndexFromAddress = exports.getColumnNameFromAddress = exports.addressToCell = exports.convertCellToRange = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var typeguards_1 = require("./typeguards");
6
6
  var numbers_1 = require("./numbers");
@@ -135,13 +135,38 @@ var cellRangeToAddress = function (sheetName, range) {
135
135
  }));
136
136
  };
137
137
  exports.cellRangeToAddress = cellRangeToAddress;
138
- /* Sanitize sheet names */
138
+ /** `A1`-style lookalike: 1–3 ASCII letters followed only by digits (`AB12`, `XFD1`). */
139
+ var SHEET_NAME_A1_LOOKALIKE = /^[A-Za-z]{1,3}\d+$/;
140
+ /** `R1C1`-style lookalike: an optional-digit `R` part and/or `C` part, nothing else (`R`, `C22`, `R1C1`). */
141
+ var SHEET_NAME_R1C1_LOOKALIKE = /^(?:[Rr]\d*)?(?:[Cc]\d*)?$/;
142
+ /**
143
+ * Must `name` be single-quoted as a sheet qualifier (`'My Data'!A1`)? Excel writes it bare only when
144
+ * it is a plain identifier — ASCII letters and digits, starting with a letter — and cannot be read
145
+ * as something else: a cell reference (`A1`, `XFD1`, `R1C1`), or a boolean (`TRUE`). A leading digit
146
+ * (`2024`, `1Sheet`) always quotes. Deliberately a superset of Excel's minimum (a `_`, `.` or
147
+ * non-ASCII letter also quotes): over-quoting is always valid, under-quoting makes `=2024!A1` a
148
+ * formula Excel rejects (rowsncolumns/spreadsheet#939 K-10).
149
+ */
150
+ var sheetNameNeedsQuoting = function (name) {
151
+ return name.length === 0 ||
152
+ /[^A-Za-z0-9]/.test(name) ||
153
+ /^\d/.test(name) ||
154
+ SHEET_NAME_A1_LOOKALIKE.test(name) ||
155
+ SHEET_NAME_R1C1_LOOKALIKE.test(name) ||
156
+ /^(?:true|false)$/i.test(name);
157
+ };
158
+ exports.sheetNameNeedsQuoting = sheetNameNeedsQuoting;
159
+ /**
160
+ * Quote a sheet name for use as a formula qualifier when Excel requires it (see
161
+ * {@link sheetNameNeedsQuoting}); apostrophes inside are doubled (`'O''Neil'`). Rename, cross-sheet
162
+ * reference generation and formula-bar insertion all quote through here so they agree.
163
+ */
139
164
  var sanitizeSheetName = function (name) {
140
165
  if (name === void 0)
141
166
  return void 0;
142
167
  if (typeof name === "number")
143
168
  return name;
144
- if (/\s+|[^a-z|A-Z|0-9]/.test(name)) {
169
+ if ((0, exports.sheetNameNeedsQuoting)(name)) {
145
170
  // Escape single quotes by doubling them for valid sheet references.
146
171
  var escaped = name.replace(/'/g, "''");
147
172
  return "'".concat(escaped, "'");
@@ -263,6 +288,51 @@ var URL_SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;
263
288
  // explicit scheme (javascript:, data:, vbscript:, …) is a script/content
264
289
  // injection vector and is rejected.
265
290
  var SAFE_URL_SCHEMES = new Set(["http", "https", "mailto", "tel", "ftp"]);
291
+ /**
292
+ * Whether a hyperlink target read from untrusted markup (clipboard HTML) may
293
+ * be kept. A URL with an explicit scheme must use one of the safe schemes;
294
+ * scheme-less, protocol-relative, path and fragment references are allowed
295
+ * (they are normalized by {@link normalizeExternalUrl} before being opened).
296
+ * Applies the same tab/newline and C0-control stripping as the browser's URL
297
+ * canonicalization so `java\tscript:` cannot slip through.
298
+ */
299
+ var isSafeHyperlinkUri = function (uri) {
300
+ if (uri == null) {
301
+ return false;
302
+ }
303
+ var trimmed = uri
304
+ .replace(/[\t\n\r]/g, "")
305
+ .replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "")
306
+ .trim();
307
+ if (!trimmed) {
308
+ return false;
309
+ }
310
+ var schemeMatch = URL_SCHEME.exec(trimmed);
311
+ if (!schemeMatch) {
312
+ return true;
313
+ }
314
+ return SAFE_URL_SCHEMES.has(schemeMatch[1].toLowerCase());
315
+ };
316
+ exports.isSafeHyperlinkUri = isSafeHyperlinkUri;
317
+ /**
318
+ * Escape text for interpolation into an HTML text node (`text/html`
319
+ * clipboard fragments): a cell whose value is literally `<b>x</b>` must paste
320
+ * back as that text, not as bold "x".
321
+ */
322
+ var escapeHtmlText = function (value) {
323
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
324
+ };
325
+ exports.escapeHtmlText = escapeHtmlText;
326
+ /** Escape a value for interpolation into a quoted HTML attribute. */
327
+ var escapeHtmlAttribute = function (value) {
328
+ return value
329
+ .replace(/&/g, "&amp;")
330
+ .replace(/"/g, "&quot;")
331
+ .replace(/'/g, "&#39;")
332
+ .replace(/</g, "&lt;")
333
+ .replace(/>/g, "&gt;");
334
+ };
335
+ exports.escapeHtmlAttribute = escapeHtmlAttribute;
266
336
  /**
267
337
  * Normalize an external hyperlink target for the browser. A stored URL
268
338
  * without a scheme ("paradigm.xyz") would otherwise be resolved relative to
@@ -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","../../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-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","../../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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `<img src>` may point at http(s) or an inline `data:image/*` (browsers copy
3
+ * inline images that way). Anything else — `javascript:`, `data:text/html`,
4
+ * `blob:` from another origin — is dropped.
5
+ */
6
+ export declare const isSafeImageSrc: (value: string | null | undefined) => boolean;
7
+ export declare const sanitizeInlineStyle: (value: string) => string;
8
+ export declare const sanitizeStylesheetText: (value: string) => string;
9
+ /**
10
+ * Strip executable and network-reaching content from an inert (DOMParser)
11
+ * document in place. Safe to call on a document that was never attached; the
12
+ * caller imports the result into a live sandbox afterwards.
13
+ */
14
+ export declare const sanitizeClipboardDocument: (doc: Document) => Document;
15
+ //# sourceMappingURL=clipboard-html-sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard-html-sanitize.d.ts","sourceRoot":"","sources":["../../clipboard-html-sanitize.ts"],"names":[],"mappings":"AA6FA;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,OAWjE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,MAAM,KAAG,MAanD,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,OAAO,MAAM,KAAG,MACT,CAAC;AAmC/C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,KAAK,QAAQ,KAAG,QAyCzD,CAAC"}
@@ -0,0 +1,192 @@
1
+ import { isSafeHyperlinkUri } from "./string";
2
+ /**
3
+ * Sanitizer for clipboard `text/html` before any of it touches the live DOM.
4
+ *
5
+ * Clipboard HTML is attacker-controlled: a page the user copied from (or a
6
+ * synthetic `paste` event) can carry `<img onerror>`, `<svg onload>`,
7
+ * `<script>`, `<iframe srcdoc>` or `javascript:` links. The paste converters
8
+ * only ever need the document's structure (tables, rows, cells, inline
9
+ * formatting tags, `data-*` value hints), its inline `style` attributes and
10
+ * the payload's own `<style>` rules (Excel's `.fontN` / `.xlNN` classes), so
11
+ * everything else is dropped from the INERT `DOMParser` document before the
12
+ * sanitized nodes are imported into the mounted sandbox.
13
+ *
14
+ * This runs unconditionally; a consumer's `sanitizeHtml` prop runs on the raw
15
+ * string before this and can only tighten the result.
16
+ */
17
+ /** Elements removed with their subtree. */
18
+ const FORBIDDEN_ELEMENTS = new Set([
19
+ "script",
20
+ "noscript",
21
+ "template",
22
+ "iframe",
23
+ "frame",
24
+ "frameset",
25
+ "object",
26
+ "embed",
27
+ "applet",
28
+ "link",
29
+ "base",
30
+ "svg",
31
+ "math",
32
+ "input",
33
+ "button",
34
+ "select",
35
+ "textarea",
36
+ "audio",
37
+ "video",
38
+ "source",
39
+ "track",
40
+ "canvas",
41
+ "portal",
42
+ "dialog",
43
+ ]);
44
+ /** Elements removed but whose children are kept in place. */
45
+ const UNWRAPPED_ELEMENTS = new Set(["form", "fieldset", "label", "output"]);
46
+ /** URL-bearing attributes that never carry spreadsheet meaning. */
47
+ const FORBIDDEN_ATTRIBUTES = new Set([
48
+ "srcdoc",
49
+ "srcset",
50
+ "formaction",
51
+ "action",
52
+ "background",
53
+ "poster",
54
+ "ping",
55
+ "dynsrc",
56
+ "lowsrc",
57
+ "cite",
58
+ "longdesc",
59
+ "manifest",
60
+ "codebase",
61
+ "classid",
62
+ "data",
63
+ "archive",
64
+ "profile",
65
+ "usemap",
66
+ "xlink:href",
67
+ "xml:base",
68
+ ]);
69
+ /** Attributes whose value must be a safe URL; otherwise the attribute is dropped. */
70
+ const URL_ATTRIBUTES = new Set(["href", "src"]);
71
+ /**
72
+ * CSS constructs that can reach the network or (in legacy engines) run code.
73
+ * Any declaration containing one of them is dropped; the rest of the `style`
74
+ * survives so legitimate `font-weight` / `color` / `mso-number-format` hints
75
+ * are kept.
76
+ */
77
+ const DANGEROUS_CSS = /(expression\s*\(|url\s*\(|@import|behavior\s*:|-moz-binding|javascript:|vbscript:|data:)/i;
78
+ /**
79
+ * Stylesheet-level constructs that can reach the network. `<style>` text is
80
+ * needed for Excel's class rules but never for anything that fetches.
81
+ */
82
+ const DANGEROUS_STYLESHEET_RULE = /@import[^;]*;?|url\s*\([^)]*\)|expression\s*\([^)]*\)|behavior\s*:[^;}]*|-moz-binding\s*:[^;}]*/gi;
83
+ const isEventHandlerAttribute = (name) => name.length > 2 && name.charCodeAt(0) === 111 /* o */ && name.charCodeAt(1) === 110 /* n */;
84
+ /**
85
+ * `<img src>` may point at http(s) or an inline `data:image/*` (browsers copy
86
+ * inline images that way). Anything else — `javascript:`, `data:text/html`,
87
+ * `blob:` from another origin — is dropped.
88
+ */
89
+ export const isSafeImageSrc = (value) => {
90
+ if (!value) {
91
+ return false;
92
+ }
93
+ const trimmed = value.replace(/[\t\n\r]/g, "").trim();
94
+ if (/^data:image\/(png|gif|jpe?g|webp|bmp|svg\+xml|x-icon|tiff);/i.test(trimmed)) {
95
+ // Inline SVG can carry scripts when navigated to, but as an `<img>` source
96
+ // it is rendered in an isolated, script-less document; allow it.
97
+ return true;
98
+ }
99
+ return /^https?:\/\//i.test(trimmed) || /^\/\//.test(trimmed);
100
+ };
101
+ export const sanitizeInlineStyle = (value) => {
102
+ // Untouched styles are kept byte-for-byte: Excel's `mso-number-format`
103
+ // patterns carry escaped `\;` and significant spaces that a split/rejoin
104
+ // would mangle.
105
+ if (!DANGEROUS_CSS.test(value)) {
106
+ return value;
107
+ }
108
+ return value
109
+ .split(";")
110
+ .filter((declaration) => declaration.trim() && !DANGEROUS_CSS.test(declaration))
111
+ .join(";");
112
+ };
113
+ export const sanitizeStylesheetText = (value) => value.replace(DANGEROUS_STYLESHEET_RULE, "");
114
+ const sanitizeAttributes = (element) => {
115
+ const tag = element.tagName.toLowerCase();
116
+ // Copy first: removing while iterating a live NamedNodeMap skips entries.
117
+ const attributes = Array.from(element.attributes);
118
+ for (const attribute of attributes) {
119
+ const name = attribute.name.toLowerCase();
120
+ if (isEventHandlerAttribute(name) || FORBIDDEN_ATTRIBUTES.has(name)) {
121
+ element.removeAttribute(attribute.name);
122
+ continue;
123
+ }
124
+ if (name === "style") {
125
+ const safe = sanitizeInlineStyle(attribute.value);
126
+ if (safe !== attribute.value) {
127
+ if (safe) {
128
+ element.setAttribute(attribute.name, safe);
129
+ }
130
+ else {
131
+ element.removeAttribute(attribute.name);
132
+ }
133
+ }
134
+ continue;
135
+ }
136
+ if (URL_ATTRIBUTES.has(name)) {
137
+ const safe = name === "src"
138
+ ? tag === "img" && isSafeImageSrc(attribute.value)
139
+ : isSafeHyperlinkUri(attribute.value);
140
+ if (!safe) {
141
+ element.removeAttribute(attribute.name);
142
+ }
143
+ }
144
+ }
145
+ };
146
+ /**
147
+ * Strip executable and network-reaching content from an inert (DOMParser)
148
+ * document in place. Safe to call on a document that was never attached; the
149
+ * caller imports the result into a live sandbox afterwards.
150
+ */
151
+ export const sanitizeClipboardDocument = (doc) => {
152
+ // A static snapshot: removals below detach subtrees, and detached
153
+ // descendants are skipped via `doc.contains`.
154
+ const elements = Array.from(doc.querySelectorAll("*"));
155
+ for (const element of elements) {
156
+ if (!doc.contains(element)) {
157
+ continue;
158
+ }
159
+ const tag = element.tagName.toLowerCase();
160
+ if (FORBIDDEN_ELEMENTS.has(tag)) {
161
+ element.remove();
162
+ continue;
163
+ }
164
+ if (UNWRAPPED_ELEMENTS.has(tag)) {
165
+ const parent = element.parentNode;
166
+ if (parent) {
167
+ while (element.firstChild) {
168
+ parent.insertBefore(element.firstChild, element);
169
+ }
170
+ }
171
+ element.remove();
172
+ continue;
173
+ }
174
+ if (tag === "style") {
175
+ const text = element.textContent ?? "";
176
+ const safe = sanitizeStylesheetText(text);
177
+ if (safe !== text) {
178
+ element.textContent = safe;
179
+ }
180
+ // A stylesheet never needs attributes beyond `type`/`media`.
181
+ for (const attribute of Array.from(element.attributes)) {
182
+ const name = attribute.name.toLowerCase();
183
+ if (name !== "type" && name !== "media") {
184
+ element.removeAttribute(attribute.name);
185
+ }
186
+ }
187
+ continue;
188
+ }
189
+ sanitizeAttributes(element);
190
+ }
191
+ return doc;
192
+ };
@@ -0,0 +1,54 @@
1
+ import type { ConditionType } from "@rowsncolumns/common-types";
2
+ /**
3
+ * Whether a condition type compares TEXT — its operand is always a string
4
+ * literal in Excel, even when it looks like a number or a boolean.
5
+ */
6
+ export declare const isTextConditionType: (type: ConditionType | string | undefined | null) => boolean;
7
+ export declare const escapeConditionFormulaText: (value: string) => string;
8
+ /**
9
+ * The formula fragment for an operand that is a typed LITERAL — a number
10
+ * (`5`, `1e3`, locale `6,5`), a percentage (`50%` → `0.5`), a boolean, or a
11
+ * date / time in the document locale (`2024-01-05`, `1/5/2024`, `10:30` → the
12
+ * serial). `undefined` for anything else (text, references, formulas).
13
+ */
14
+ export declare const conditionOperandLiteral: (raw: string | undefined | null, locale?: string) => string | undefined;
15
+ /**
16
+ * Whether an operand (with any leading `=` already removed) is an A1
17
+ * reference — a cell, a range, a whole column/row, with `$` anchors and an
18
+ * optional (quoted) sheet qualifier: `B1`, `$B$1`, `B$1`, `Sheet1!$B$1`,
19
+ * `'My Sheet'!$B$1:$C$9`, `$A:$A`.
20
+ */
21
+ export declare const isConditionOperandReference: (text: string) => boolean;
22
+ /**
23
+ * Whether an operand (with any leading `=` already removed) is a function
24
+ * call — the form OOXML stores a formula bound in (`TODAY()`,
25
+ * `DATE(2024,1,1)`, `LEN($B$1)`): evaluated as a formula, never quoted.
26
+ */
27
+ export declare const isConditionOperandFormulaCall: (text: string) => boolean;
28
+ /**
29
+ * Normalize one rule operand into the formula fragment the rule builders
30
+ * splice into `=<cell><op><fragment>` (and the xlsx exporters write inside
31
+ * `<formula>` / `<formula1>` — never with a leading `=`).
32
+ *
33
+ * - `=…` → the formula text (leading `=` stripped).
34
+ * - `TEXT_*` types → ALWAYS a quoted string literal (`"1"`, `"TRUE"`).
35
+ * - A complete `"…"` string literal → as is.
36
+ * - An A1 reference (`$B$1`, `B$1`, `Sheet1!$B$1`, `'My Sheet'!$B$1`) → as is.
37
+ * - A function call (`TODAY()`, `DATE(2024,1,1)`, `LEN(A1)`) → as is.
38
+ * - A typed literal (number, `50%`, boolean, date/time in `locale`) → its
39
+ * numeric form (`0.5`, `45296`, `TRUE`).
40
+ * - Anything else → a quoted string literal (`"abc"`).
41
+ *
42
+ * `""` for an empty operand.
43
+ */
44
+ export declare const normalizeConditionOperand: (raw: string | undefined | null, type: ConditionType | string | undefined | null, locale?: string) => string;
45
+ /**
46
+ * The OOXML `<formula>` / `<formula1>` text for a number/date rule operand:
47
+ * the leading `=` is stripped (Excel never writes one), typed literals are
48
+ * normalized to what Excel itself stores (`50%` → `0.5`, `2024-01-05` → its
49
+ * serial `45296` rather than the arithmetic `2024-01-05` = 2022), and every
50
+ * other operand — references, defined names, function calls — is written
51
+ * verbatim.
52
+ */
53
+ export declare const conditionOperandToFormulaText: (raw: string | undefined | null, locale?: string) => string;
54
+ //# sourceMappingURL=condition-operand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"condition-operand.d.ts","sourceRoot":"","sources":["../../condition-operand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAyChE;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,KAC9C,OAA0D,CAAC;AAE9D,eAAO,MAAM,0BAA0B,GAAI,OAAO,MAAM,WAC7B,CAAC;AAsC5B;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,EAC9B,SAAS,MAAM,KACd,MAAM,GAAG,SA2CX,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,MAAM,KAAG,OACU,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,OAC5B,CAAC;AAElC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,yBAAyB,GACpC,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,EAC9B,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,EAC/C,SAAS,MAAM,KACd,MA0BF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACxC,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,EAC9B,SAAS,MAAM,KACd,MAQF,CAAC"}
@@ -0,0 +1,203 @@
1
+ import { parseDate, parseTime } from "numfmt";
2
+ import { checkIfStringIsReference } from "./selection";
3
+ /**
4
+ * How a conditional-format / data-validation rule operand (`userEnteredValue`)
5
+ * becomes a formula fragment — the ONE place both rule builders and both xlsx
6
+ * exporters classify an operand, so the runtime and the exported file agree.
7
+ *
8
+ * Excel's rule dialog stores every operand as a formula. Typed text becomes a
9
+ * string literal, a typed number a number, a typed date its serial, `50%` the
10
+ * fraction `0.5`, and `=…` stays a formula. The `TEXT_*` rules ("Text that
11
+ * contains…", "begins with", cellIs equal `"5"`) take a string literal no
12
+ * matter what was typed: *begins with "1"* highlights `1abc` AND `123`, and
13
+ * `cellIs equal "5"` matches the text `5` but not the number 5.
14
+ */
15
+ const NUMBER_LITERAL_PATTERN = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
16
+ const BOOLEAN_LITERAL_PATTERN = /^(TRUE|FALSE)$/i;
17
+ const PERCENT_LITERAL_PATTERN = /^([+-]?[\d.,\s ]+)%$/;
18
+ /** A complete formula string literal — `"abc"`, `"say ""hi"""`. */
19
+ const QUOTED_STRING_LITERAL_PATTERN = /^"(?:[^"]|"")*"$/;
20
+ /**
21
+ * A function call — `TODAY()`, `DATE(2024,1,1)`, `LEN($B$1)`, `_xlfn.XLOOKUP(…)` — which is how
22
+ * OOXML stores a formula bound (`<formula1>TODAY()</formula1>`, never with a leading `=`).
23
+ */
24
+ const FORMULA_CALL_PATTERN = /^[A-Z_\\][\w.]*\([\s\S]*\)$/i;
25
+ /** Whole-column / whole-row spans (`A:A`, `$A:$C`, `1:1`), optionally sheet-qualified. */
26
+ const AXIS_REFERENCE_PATTERN = /^(?:[^!]+!)?(?:\$?[A-Z]{1,3}:\$?[A-Z]{1,3}|\$?\d+:\$?\d+)$/i;
27
+ const TEXT_CONDITION_TYPES = new Set([
28
+ "TEXT_EQ",
29
+ "TEXT_NOT_EQ",
30
+ "TEXT_CONTAINS",
31
+ "TEXT_NOT_CONTAINS",
32
+ "TEXT_STARTS_WITH",
33
+ "TEXT_ENDS_WITH",
34
+ ]);
35
+ /**
36
+ * Whether a condition type compares TEXT — its operand is always a string
37
+ * literal in Excel, even when it looks like a number or a boolean.
38
+ */
39
+ export const isTextConditionType = (type) => Boolean(type && TEXT_CONDITION_TYPES.has(type));
40
+ export const escapeConditionFormulaText = (value) => value.replace(/"/g, '""');
41
+ /**
42
+ * Parse a locale-formatted numeric string (e.g., "6,5" in fr-FR) into a JS
43
+ * number. Uses `Intl.NumberFormat` to detect decimal/group separators.
44
+ * Returns NaN on failure.
45
+ */
46
+ const parseLocaleNumberLoose = (value, locale) => {
47
+ try {
48
+ const parts = new Intl.NumberFormat(locale).formatToParts(1234.5);
49
+ const decimal = parts.find((part) => part.type === "decimal")?.value ?? ".";
50
+ const group = parts.find((part) => part.type === "group")?.value ?? ",";
51
+ const cleaned = value.split(group).join("").replace(decimal, ".");
52
+ if (!cleaned.trim() || !NUMBER_LITERAL_PATTERN.test(cleaned.trim())) {
53
+ return NaN;
54
+ }
55
+ return Number(cleaned);
56
+ }
57
+ catch {
58
+ return NaN;
59
+ }
60
+ };
61
+ /** Shortest decimal text of a number rounded to Excel's 15 significant digits. */
62
+ const formatNumberOperand = (value) => String(Number(value.toPrecision(15)));
63
+ const parseNumberOperand = (text, locale) => {
64
+ if (NUMBER_LITERAL_PATTERN.test(text)) {
65
+ const value = Number(text);
66
+ return Number.isFinite(value) ? value : undefined;
67
+ }
68
+ if (locale) {
69
+ const value = parseLocaleNumberLoose(text, locale);
70
+ return Number.isFinite(value) ? value : undefined;
71
+ }
72
+ return undefined;
73
+ };
74
+ /**
75
+ * The formula fragment for an operand that is a typed LITERAL — a number
76
+ * (`5`, `1e3`, locale `6,5`), a percentage (`50%` → `0.5`), a boolean, or a
77
+ * date / time in the document locale (`2024-01-05`, `1/5/2024`, `10:30` → the
78
+ * serial). `undefined` for anything else (text, references, formulas).
79
+ */
80
+ export const conditionOperandLiteral = (raw, locale) => {
81
+ if (!raw)
82
+ return undefined;
83
+ const trimmed = raw.trim();
84
+ if (!trimmed)
85
+ return undefined;
86
+ if (BOOLEAN_LITERAL_PATTERN.test(trimmed)) {
87
+ return trimmed.toUpperCase();
88
+ }
89
+ if (NUMBER_LITERAL_PATTERN.test(trimmed)) {
90
+ // Keep the typed digits — `6.50` and `6.5` are the same number and the
91
+ // formula parser reads either.
92
+ return Number.isFinite(Number(trimmed)) ? trimmed : undefined;
93
+ }
94
+ const percent = PERCENT_LITERAL_PATTERN.exec(trimmed);
95
+ if (percent) {
96
+ const value = parseNumberOperand(percent[1].trim(), locale ?? "en-US");
97
+ if (value !== undefined) {
98
+ return formatNumberOperand(value / 100);
99
+ }
100
+ }
101
+ // Dates (incl. date+time) and times, in the document locale's field order
102
+ // (`1/5/2024` is Jan 5 in en-US, May 1 in en-GB; `1.5.2024` is a de-DE
103
+ // date, not the grouped number 152024). numfmt's default locale is
104
+ // month-first, matching the app default.
105
+ try {
106
+ const dateOptions = locale ? { locale } : undefined;
107
+ const date = parseDate(trimmed, dateOptions);
108
+ if (date && typeof date.v === "number" && Number.isFinite(date.v)) {
109
+ return formatNumberOperand(date.v);
110
+ }
111
+ const time = parseTime(trimmed);
112
+ if (time && typeof time.v === "number" && Number.isFinite(time.v)) {
113
+ return formatNumberOperand(time.v);
114
+ }
115
+ }
116
+ catch {
117
+ // numfmt throws on malformed locale tags — the operand is then plain text.
118
+ }
119
+ if (locale) {
120
+ const value = parseLocaleNumberLoose(trimmed, locale);
121
+ if (Number.isFinite(value)) {
122
+ return formatNumberOperand(value);
123
+ }
124
+ }
125
+ return undefined;
126
+ };
127
+ /**
128
+ * Whether an operand (with any leading `=` already removed) is an A1
129
+ * reference — a cell, a range, a whole column/row, with `$` anchors and an
130
+ * optional (quoted) sheet qualifier: `B1`, `$B$1`, `B$1`, `Sheet1!$B$1`,
131
+ * `'My Sheet'!$B$1:$C$9`, `$A:$A`.
132
+ */
133
+ export const isConditionOperandReference = (text) => checkIfStringIsReference(text) || AXIS_REFERENCE_PATTERN.test(text);
134
+ /**
135
+ * Whether an operand (with any leading `=` already removed) is a function
136
+ * call — the form OOXML stores a formula bound in (`TODAY()`,
137
+ * `DATE(2024,1,1)`, `LEN($B$1)`): evaluated as a formula, never quoted.
138
+ */
139
+ export const isConditionOperandFormulaCall = (text) => FORMULA_CALL_PATTERN.test(text);
140
+ /**
141
+ * Normalize one rule operand into the formula fragment the rule builders
142
+ * splice into `=<cell><op><fragment>` (and the xlsx exporters write inside
143
+ * `<formula>` / `<formula1>` — never with a leading `=`).
144
+ *
145
+ * - `=…` → the formula text (leading `=` stripped).
146
+ * - `TEXT_*` types → ALWAYS a quoted string literal (`"1"`, `"TRUE"`).
147
+ * - A complete `"…"` string literal → as is.
148
+ * - An A1 reference (`$B$1`, `B$1`, `Sheet1!$B$1`, `'My Sheet'!$B$1`) → as is.
149
+ * - A function call (`TODAY()`, `DATE(2024,1,1)`, `LEN(A1)`) → as is.
150
+ * - A typed literal (number, `50%`, boolean, date/time in `locale`) → its
151
+ * numeric form (`0.5`, `45296`, `TRUE`).
152
+ * - Anything else → a quoted string literal (`"abc"`).
153
+ *
154
+ * `""` for an empty operand.
155
+ */
156
+ export const normalizeConditionOperand = (raw, type, locale) => {
157
+ if (!raw)
158
+ return "";
159
+ const trimmed = raw.trim();
160
+ if (!trimmed)
161
+ return "";
162
+ if (trimmed.startsWith("=")) {
163
+ return trimmed.slice(1);
164
+ }
165
+ if (isTextConditionType(type)) {
166
+ return `"${escapeConditionFormulaText(trimmed)}"`;
167
+ }
168
+ if (QUOTED_STRING_LITERAL_PATTERN.test(trimmed)) {
169
+ return trimmed;
170
+ }
171
+ if (checkIfStringIsReference(trimmed) || isConditionOperandFormulaCall(trimmed)) {
172
+ return trimmed;
173
+ }
174
+ // Typed literals before whole-row spans: `10:30` is the time 10:30, not
175
+ // rows 10–30 (Excel's dialog reads it the way a cell entry would).
176
+ const literal = conditionOperandLiteral(trimmed, locale);
177
+ if (literal !== undefined) {
178
+ return literal;
179
+ }
180
+ if (isConditionOperandReference(trimmed)) {
181
+ return trimmed;
182
+ }
183
+ return `"${escapeConditionFormulaText(trimmed)}"`;
184
+ };
185
+ /**
186
+ * The OOXML `<formula>` / `<formula1>` text for a number/date rule operand:
187
+ * the leading `=` is stripped (Excel never writes one), typed literals are
188
+ * normalized to what Excel itself stores (`50%` → `0.5`, `2024-01-05` → its
189
+ * serial `45296` rather than the arithmetic `2024-01-05` = 2022), and every
190
+ * other operand — references, defined names, function calls — is written
191
+ * verbatim.
192
+ */
193
+ export const conditionOperandToFormulaText = (raw, locale) => {
194
+ if (!raw)
195
+ return "";
196
+ const trimmed = raw.trim();
197
+ if (!trimmed)
198
+ return "";
199
+ if (trimmed.startsWith("=")) {
200
+ return trimmed.slice(1);
201
+ }
202
+ return conditionOperandLiteral(trimmed, locale) ?? trimmed;
203
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"conditional-format.d.ts","sourceRoot":"","sources":["../../conditional-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,4BAA4B,CAAC;AAKpC;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,WAAW,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAC9D,CAAC;AA+DF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,8BAA8B,GACzC,MAAM,yBAAyB,GAAG,SAAS,YAK5C,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAC3C,MAAM,yBAAyB,GAAG,SAAS,EAC3C,aAAa,MAAM,EACnB,SAAS,MAAM,uBA8HhB,CAAC"}
1
+ {"version":3,"file":"conditional-format.d.ts","sourceRoot":"","sources":["../../conditional-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,4BAA4B,CAAC;AAQpC;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,WAAW,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAC9D,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,8BAA8B,GACzC,MAAM,yBAAyB,GAAG,SAAS,YAK5C,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAC3C,MAAM,yBAAyB,GAAG,SAAS,EAC3C,aAAa,MAAM,EACnB,SAAS,MAAM,uBA8HhB,CAAC"}