@rowsncolumns/utils 9.5.1 → 9.5.4

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 (61) 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/date.d.ts +16 -0
  13. package/dist/cjs/date.d.ts.map +1 -1
  14. package/dist/cjs/date.js +33 -4
  15. package/dist/cjs/index.d.ts +4 -0
  16. package/dist/cjs/index.d.ts.map +1 -1
  17. package/dist/cjs/index.js +4 -0
  18. package/dist/cjs/rich-text-html.d.ts +9 -0
  19. package/dist/cjs/rich-text-html.d.ts.map +1 -1
  20. package/dist/cjs/rich-text-html.js +52 -23
  21. package/dist/cjs/sheet-names.d.ts +60 -0
  22. package/dist/cjs/sheet-names.d.ts.map +1 -0
  23. package/dist/cjs/sheet-names.js +157 -0
  24. package/dist/cjs/sheet-protection.d.ts +9 -0
  25. package/dist/cjs/sheet-protection.d.ts.map +1 -0
  26. package/dist/cjs/sheet-protection.js +11 -0
  27. package/dist/cjs/string.d.ts +31 -0
  28. package/dist/cjs/string.d.ts.map +1 -1
  29. package/dist/cjs/string.js +73 -3
  30. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  31. package/dist/esm/clipboard-html-sanitize.d.ts +15 -0
  32. package/dist/esm/clipboard-html-sanitize.d.ts.map +1 -0
  33. package/dist/esm/clipboard-html-sanitize.js +192 -0
  34. package/dist/esm/condition-operand.d.ts +54 -0
  35. package/dist/esm/condition-operand.d.ts.map +1 -0
  36. package/dist/esm/condition-operand.js +203 -0
  37. package/dist/esm/conditional-format.d.ts.map +1 -1
  38. package/dist/esm/conditional-format.js +5 -57
  39. package/dist/esm/data-validation.d.ts +9 -0
  40. package/dist/esm/data-validation.d.ts.map +1 -1
  41. package/dist/esm/data-validation.js +27 -57
  42. package/dist/esm/date.d.ts +16 -0
  43. package/dist/esm/date.d.ts.map +1 -1
  44. package/dist/esm/date.js +22 -1
  45. package/dist/esm/index.d.ts +4 -0
  46. package/dist/esm/index.d.ts.map +1 -1
  47. package/dist/esm/index.js +4 -0
  48. package/dist/esm/rich-text-html.d.ts +9 -0
  49. package/dist/esm/rich-text-html.d.ts.map +1 -1
  50. package/dist/esm/rich-text-html.js +20 -4
  51. package/dist/esm/sheet-names.d.ts +60 -0
  52. package/dist/esm/sheet-names.d.ts.map +1 -0
  53. package/dist/esm/sheet-names.js +109 -0
  54. package/dist/esm/sheet-protection.d.ts +9 -0
  55. package/dist/esm/sheet-protection.d.ts.map +1 -0
  56. package/dist/esm/sheet-protection.js +8 -0
  57. package/dist/esm/string.d.ts +31 -0
  58. package/dist/esm/string.d.ts.map +1 -1
  59. package/dist/esm/string.js +62 -2
  60. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  61. package/package.json +2 -2
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Excel compares worksheet names case-insensitively: `SHEET1` beside `Sheet1` is refused with
3
+ * "That name is already taken. Try a different one." The calc engine's sheet registry folds names
4
+ * the same way, so two sheets differing only by case would alias one engine sheet — every write to
5
+ * the second lands in the first and `=SHEET1!A1` resolves to whichever was registered first
6
+ * (Athena-Intel/demo-app-monorepo#37167 UI2-1). Both engines' rename / create / duplicate paths and
7
+ * the sheet tabs compare titles through these helpers (the rnc store mirrors them in Rust and
8
+ * surfaces the same message through its `rejections` channel).
9
+ */
10
+ export declare const SHEET_NAME_TAKEN_MESSAGE = "That name is already taken. Try a different one.";
11
+ /** The identity two worksheet titles are compared under: trimmed, Unicode-lowercased. */
12
+ export declare const sheetTitleKey: (title: string) => string;
13
+ /** Whether `a` and `b` name the same worksheet under Excel's comparison. */
14
+ export declare const isSameSheetTitle: (a: string, b: string) => boolean;
15
+ /**
16
+ * Whether another sheet (any but `exceptSheetId`) already holds `title`, case-insensitively. A
17
+ * sheet may always re-case its own title (`Sheet1` → `SHEET1` in place is not a collision).
18
+ */
19
+ export declare const isSheetTitleTaken: (title: string, sheets: Iterable<{
20
+ sheetId: number;
21
+ title: string;
22
+ }>, exceptSheetId?: number) => boolean;
23
+ /**
24
+ * `base` if no title in `existing` holds it (case-insensitively), else the first free
25
+ * `base 1`, `base 2`, … — the suffix scheme the JS engine uses for a new / duplicated sheet.
26
+ * Every candidate stays within `maxLength` (the suffix wins over the tail of a long base).
27
+ */
28
+ export declare const uniqueSheetTitle: (base: string, existing: Iterable<string>, maxLength?: number) => string;
29
+ /** Excel's ceiling for a worksheet name, in UTF-16 code units (what `String#length` counts). */
30
+ export declare const SHEET_NAME_MAX_LENGTH = 31;
31
+ /** The characters Excel never allows anywhere in a worksheet name: `\ / ? * [ ] :`. */
32
+ export declare const SHEET_NAME_ILLEGAL_CHARACTERS: readonly ["\\", "/", "?", "*", "[", "]", ":"];
33
+ /** Excel reserves this title for its change-history sheet (compared case-insensitively). */
34
+ export declare const SHEET_NAME_RESERVED_WORD = "History";
35
+ /**
36
+ * Excel 365's rename-refusal dialog, verbatim — one message for every rule except the duplicate
37
+ * title, which has its own ({@link SHEET_NAME_TAKEN_MESSAGE}).
38
+ */
39
+ export declare const SHEET_NAME_INVALID_MESSAGE: string;
40
+ /** Why a worksheet name was refused — a stable key hosts may branch on; `message` is the dialog text. */
41
+ export type SheetNameRejectionReason = "blank" | "too-long" | "illegal-character" | "apostrophe" | "reserved" | "taken";
42
+ export type SheetNameValidation = {
43
+ ok: true;
44
+ } | {
45
+ ok: false;
46
+ reason: SheetNameRejectionReason;
47
+ message: string;
48
+ };
49
+ /**
50
+ * Excel's worksheet-name rules, in the order its dialog lists them: not blank, at most 31 characters,
51
+ * none of `\ / ? * [ ] :`, no leading or trailing apostrophe, not `History`, and unique among
52
+ * `existing` compared case-insensitively (a sheet may always re-case its own title — pass its id as
53
+ * `exceptSheetId`). Both engines' rename / create paths and the sheet tabs validate through this one
54
+ * function (the rnc store mirrors it in Rust and surfaces the same messages via its `rejections`).
55
+ */
56
+ export declare const validateSheetName: (name: string, existing?: Iterable<{
57
+ sheetId: number;
58
+ title: string;
59
+ } | string>, exceptSheetId?: number) => SheetNameValidation;
60
+ //# sourceMappingURL=sheet-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheet-names.d.ts","sourceRoot":"","sources":["../../sheet-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,wBAAwB,qDACe,CAAC;AAErD,yFAAyF;AACzF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MAClB,CAAC;AAE7B,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,OACjB,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAC5B,OAAO,MAAM,EACb,QAAQ,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EACpD,gBAAgB,MAAM,KACrB,OAQF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAM,MAAM,EACZ,UAAU,QAAQ,CAAC,MAAM,CAAC,EAC1B,kBAAoC,KACnC,MAgBF,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,uFAAuF;AACvF,eAAO,MAAM,6BAA6B,+CAAgD,CAAC;AAG3F,4FAA4F;AAC5F,eAAO,MAAM,wBAAwB,YAAY,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,QAO3B,CAAC;AAEb,yGAAyG;AACzG,MAAM,MAAM,wBAAwB,GAChC,OAAO,GACP,UAAU,GACV,mBAAmB,GACnB,YAAY,GACZ,UAAU,GACV,OAAO,CAAC;AAEZ,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,wBAAwB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC5B,MAAM,MAAM,EACZ,WAAU,QAAQ,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAM,EACpE,gBAAgB,MAAM,KACrB,mBAgCF,CAAC"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Excel compares worksheet names case-insensitively: `SHEET1` beside `Sheet1` is refused with
3
+ * "That name is already taken. Try a different one." The calc engine's sheet registry folds names
4
+ * the same way, so two sheets differing only by case would alias one engine sheet — every write to
5
+ * the second lands in the first and `=SHEET1!A1` resolves to whichever was registered first
6
+ * (Athena-Intel/demo-app-monorepo#37167 UI2-1). Both engines' rename / create / duplicate paths and
7
+ * the sheet tabs compare titles through these helpers (the rnc store mirrors them in Rust and
8
+ * surfaces the same message through its `rejections` channel).
9
+ */
10
+ export const SHEET_NAME_TAKEN_MESSAGE = "That name is already taken. Try a different one.";
11
+ /** The identity two worksheet titles are compared under: trimmed, Unicode-lowercased. */
12
+ export const sheetTitleKey = (title) => title.trim().toLowerCase();
13
+ /** Whether `a` and `b` name the same worksheet under Excel's comparison. */
14
+ export const isSameSheetTitle = (a, b) => sheetTitleKey(a) === sheetTitleKey(b);
15
+ /**
16
+ * Whether another sheet (any but `exceptSheetId`) already holds `title`, case-insensitively. A
17
+ * sheet may always re-case its own title (`Sheet1` → `SHEET1` in place is not a collision).
18
+ */
19
+ export const isSheetTitleTaken = (title, sheets, exceptSheetId) => {
20
+ const key = sheetTitleKey(title);
21
+ for (const sheet of sheets) {
22
+ if (sheet.sheetId !== exceptSheetId && sheetTitleKey(sheet.title) === key) {
23
+ return true;
24
+ }
25
+ }
26
+ return false;
27
+ };
28
+ /**
29
+ * `base` if no title in `existing` holds it (case-insensitively), else the first free
30
+ * `base 1`, `base 2`, … — the suffix scheme the JS engine uses for a new / duplicated sheet.
31
+ * Every candidate stays within `maxLength` (the suffix wins over the tail of a long base).
32
+ */
33
+ export const uniqueSheetTitle = (base, existing, maxLength = Number.POSITIVE_INFINITY) => {
34
+ const taken = new Set();
35
+ for (const title of existing) {
36
+ taken.add(sheetTitleKey(title));
37
+ }
38
+ const root = base.slice(0, maxLength);
39
+ if (!taken.has(sheetTitleKey(root))) {
40
+ return root;
41
+ }
42
+ for (let counter = 1;; counter++) {
43
+ const suffix = ` ${counter}`;
44
+ const candidate = `${root.slice(0, Math.max(0, maxLength - suffix.length))}${suffix}`;
45
+ if (!taken.has(sheetTitleKey(candidate))) {
46
+ return candidate;
47
+ }
48
+ }
49
+ };
50
+ /** Excel's ceiling for a worksheet name, in UTF-16 code units (what `String#length` counts). */
51
+ export const SHEET_NAME_MAX_LENGTH = 31;
52
+ /** The characters Excel never allows anywhere in a worksheet name: `\ / ? * [ ] :`. */
53
+ export const SHEET_NAME_ILLEGAL_CHARACTERS = ["\\", "/", "?", "*", "[", "]", ":"];
54
+ const SHEET_NAME_ILLEGAL_CHARACTER = /[\\/?*[\]:]/;
55
+ /** Excel reserves this title for its change-history sheet (compared case-insensitively). */
56
+ export const SHEET_NAME_RESERVED_WORD = "History";
57
+ /**
58
+ * Excel 365's rename-refusal dialog, verbatim — one message for every rule except the duplicate
59
+ * title, which has its own ({@link SHEET_NAME_TAKEN_MESSAGE}).
60
+ */
61
+ export const SHEET_NAME_INVALID_MESSAGE = [
62
+ "Sheet names cannot:",
63
+ "• Be blank.",
64
+ `• Contain more than ${SHEET_NAME_MAX_LENGTH} characters.`,
65
+ "• Contain any of the following characters: / \\ ? * : [ ]",
66
+ "• Begin or end with an apostrophe ('), but they can be used in between text or numbers in a name.",
67
+ `• Be named "${SHEET_NAME_RESERVED_WORD}". This is a reserved word Excel uses internally.`,
68
+ ].join("\n");
69
+ /**
70
+ * Excel's worksheet-name rules, in the order its dialog lists them: not blank, at most 31 characters,
71
+ * none of `\ / ? * [ ] :`, no leading or trailing apostrophe, not `History`, and unique among
72
+ * `existing` compared case-insensitively (a sheet may always re-case its own title — pass its id as
73
+ * `exceptSheetId`). Both engines' rename / create paths and the sheet tabs validate through this one
74
+ * function (the rnc store mirrors it in Rust and surfaces the same messages via its `rejections`).
75
+ */
76
+ export const validateSheetName = (name, existing = [], exceptSheetId) => {
77
+ const invalid = (reason) => ({
78
+ ok: false,
79
+ reason,
80
+ message: SHEET_NAME_INVALID_MESSAGE,
81
+ });
82
+ if (name.trim().length === 0) {
83
+ return invalid("blank");
84
+ }
85
+ if (name.length > SHEET_NAME_MAX_LENGTH) {
86
+ return invalid("too-long");
87
+ }
88
+ if (SHEET_NAME_ILLEGAL_CHARACTER.test(name)) {
89
+ return invalid("illegal-character");
90
+ }
91
+ if (name.startsWith("'") || name.endsWith("'")) {
92
+ return invalid("apostrophe");
93
+ }
94
+ if (isSameSheetTitle(name, SHEET_NAME_RESERVED_WORD)) {
95
+ return invalid("reserved");
96
+ }
97
+ const key = sheetTitleKey(name);
98
+ for (const sheet of existing) {
99
+ if (typeof sheet === "string") {
100
+ if (sheetTitleKey(sheet) === key) {
101
+ return { ok: false, reason: "taken", message: SHEET_NAME_TAKEN_MESSAGE };
102
+ }
103
+ }
104
+ else if (sheet.sheetId !== exceptSheetId && sheetTitleKey(sheet.title) === key) {
105
+ return { ok: false, reason: "taken", message: SHEET_NAME_TAKEN_MESSAGE };
106
+ }
107
+ }
108
+ return { ok: true };
109
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Excel's refusal when an edit touches a locked cell of a protected sheet — the dialog every
3
+ * mutation path shows (typing, Delete / Backspace, fill, paste) on both engines
4
+ * (rowsncolumns/spreadsheet#939 U-35). The rnc store surfaces the same text through its
5
+ * `rejections` channel under the `protected-sheet` code.
6
+ */
7
+ export declare const SHEET_PROTECTION_ERROR_TITLE = "This sheet is protected";
8
+ export declare const 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.";
9
+ //# sourceMappingURL=sheet-protection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheet-protection.d.ts","sourceRoot":"","sources":["../../sheet-protection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,4BAA4B,CAAC;AAEtE,eAAO,MAAM,4BAA4B,0JACgH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Excel's refusal when an edit touches a locked cell of a protected sheet — the dialog every
3
+ * mutation path shows (typing, Delete / Backspace, fill, paste) on both engines
4
+ * (rowsncolumns/spreadsheet#939 U-35). The rnc store surfaces the same text through its
5
+ * `rejections` channel under the `protected-sheet` code.
6
+ */
7
+ export const SHEET_PROTECTION_ERROR_TITLE = "This sheet is protected";
8
+ export const 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"}
@@ -118,13 +118,35 @@ export const cellRangeToAddress = (sheetName, range) => {
118
118
  columnIndex: range.endColumnIndex,
119
119
  })}`;
120
120
  };
121
- /* Sanitize sheet names */
121
+ /** `A1`-style lookalike: 1–3 ASCII letters followed only by digits (`AB12`, `XFD1`). */
122
+ const SHEET_NAME_A1_LOOKALIKE = /^[A-Za-z]{1,3}\d+$/;
123
+ /** `R1C1`-style lookalike: an optional-digit `R` part and/or `C` part, nothing else (`R`, `C22`, `R1C1`). */
124
+ const SHEET_NAME_R1C1_LOOKALIKE = /^(?:[Rr]\d*)?(?:[Cc]\d*)?$/;
125
+ /**
126
+ * Must `name` be single-quoted as a sheet qualifier (`'My Data'!A1`)? Excel writes it bare only when
127
+ * it is a plain identifier — ASCII letters and digits, starting with a letter — and cannot be read
128
+ * as something else: a cell reference (`A1`, `XFD1`, `R1C1`), or a boolean (`TRUE`). A leading digit
129
+ * (`2024`, `1Sheet`) always quotes. Deliberately a superset of Excel's minimum (a `_`, `.` or
130
+ * non-ASCII letter also quotes): over-quoting is always valid, under-quoting makes `=2024!A1` a
131
+ * formula Excel rejects (rowsncolumns/spreadsheet#939 K-10).
132
+ */
133
+ export const sheetNameNeedsQuoting = (name) => name.length === 0 ||
134
+ /[^A-Za-z0-9]/.test(name) ||
135
+ /^\d/.test(name) ||
136
+ SHEET_NAME_A1_LOOKALIKE.test(name) ||
137
+ SHEET_NAME_R1C1_LOOKALIKE.test(name) ||
138
+ /^(?:true|false)$/i.test(name);
139
+ /**
140
+ * Quote a sheet name for use as a formula qualifier when Excel requires it (see
141
+ * {@link sheetNameNeedsQuoting}); apostrophes inside are doubled (`'O''Neil'`). Rename, cross-sheet
142
+ * reference generation and formula-bar insertion all quote through here so they agree.
143
+ */
122
144
  export const sanitizeSheetName = (name) => {
123
145
  if (name === void 0)
124
146
  return void 0;
125
147
  if (typeof name === "number")
126
148
  return name;
127
- if (/\s+|[^a-z|A-Z|0-9]/.test(name)) {
149
+ if (sheetNameNeedsQuoting(name)) {
128
150
  // Escape single quotes by doubling them for valid sheet references.
129
151
  const escaped = name.replace(/'/g, "''");
130
152
  return `'${escaped}'`;
@@ -238,6 +260,44 @@ const URL_SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;
238
260
  // explicit scheme (javascript:, data:, vbscript:, …) is a script/content
239
261
  // injection vector and is rejected.
240
262
  const SAFE_URL_SCHEMES = new Set(["http", "https", "mailto", "tel", "ftp"]);
263
+ /**
264
+ * Whether a hyperlink target read from untrusted markup (clipboard HTML) may
265
+ * be kept. A URL with an explicit scheme must use one of the safe schemes;
266
+ * scheme-less, protocol-relative, path and fragment references are allowed
267
+ * (they are normalized by {@link normalizeExternalUrl} before being opened).
268
+ * Applies the same tab/newline and C0-control stripping as the browser's URL
269
+ * canonicalization so `java\tscript:` cannot slip through.
270
+ */
271
+ export const isSafeHyperlinkUri = (uri) => {
272
+ if (uri == null) {
273
+ return false;
274
+ }
275
+ const trimmed = uri
276
+ .replace(/[\t\n\r]/g, "")
277
+ .replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "")
278
+ .trim();
279
+ if (!trimmed) {
280
+ return false;
281
+ }
282
+ const schemeMatch = URL_SCHEME.exec(trimmed);
283
+ if (!schemeMatch) {
284
+ return true;
285
+ }
286
+ return SAFE_URL_SCHEMES.has(schemeMatch[1].toLowerCase());
287
+ };
288
+ /**
289
+ * Escape text for interpolation into an HTML text node (`text/html`
290
+ * clipboard fragments): a cell whose value is literally `<b>x</b>` must paste
291
+ * back as that text, not as bold "x".
292
+ */
293
+ export const escapeHtmlText = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
294
+ /** Escape a value for interpolation into a quoted HTML attribute. */
295
+ export const escapeHtmlAttribute = (value) => value
296
+ .replace(/&/g, "&amp;")
297
+ .replace(/"/g, "&quot;")
298
+ .replace(/'/g, "&#39;")
299
+ .replace(/</g, "&lt;")
300
+ .replace(/>/g, "&gt;");
241
301
  /**
242
302
  * Normalize an external hyperlink target for the browser. A stored URL
243
303
  * 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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowsncolumns/utils",
3
- "version": "9.5.1",
3
+ "version": "9.5.4",
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.1"
47
+ "@rowsncolumns/common-types": ">=5.3.2"
48
48
  },
49
49
  "devDependencies": {},
50
50
  "peerDependencies": {}