@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.
- package/dist/cjs/clipboard-html-sanitize.d.ts +15 -0
- package/dist/cjs/clipboard-html-sanitize.d.ts.map +1 -0
- package/dist/cjs/clipboard-html-sanitize.js +237 -0
- package/dist/cjs/condition-operand.d.ts +54 -0
- package/dist/cjs/condition-operand.d.ts.map +1 -0
- package/dist/cjs/condition-operand.js +223 -0
- package/dist/cjs/conditional-format.d.ts.map +1 -1
- package/dist/cjs/conditional-format.js +5 -59
- package/dist/cjs/data-validation.d.ts +9 -0
- package/dist/cjs/data-validation.d.ts.map +1 -1
- package/dist/cjs/data-validation.js +29 -60
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/rich-text-html.d.ts +9 -0
- package/dist/cjs/rich-text-html.d.ts.map +1 -1
- package/dist/cjs/rich-text-html.js +52 -23
- package/dist/cjs/sheet-names.d.ts +60 -0
- package/dist/cjs/sheet-names.d.ts.map +1 -0
- package/dist/cjs/sheet-names.js +157 -0
- package/dist/cjs/sheet-protection.d.ts +9 -0
- package/dist/cjs/sheet-protection.d.ts.map +1 -0
- package/dist/cjs/sheet-protection.js +11 -0
- package/dist/cjs/string.d.ts +31 -0
- package/dist/cjs/string.d.ts.map +1 -1
- package/dist/cjs/string.js +73 -3
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/clipboard-html-sanitize.d.ts +15 -0
- package/dist/esm/clipboard-html-sanitize.d.ts.map +1 -0
- package/dist/esm/clipboard-html-sanitize.js +192 -0
- package/dist/esm/condition-operand.d.ts +54 -0
- package/dist/esm/condition-operand.d.ts.map +1 -0
- package/dist/esm/condition-operand.js +203 -0
- package/dist/esm/conditional-format.d.ts.map +1 -1
- package/dist/esm/conditional-format.js +5 -57
- package/dist/esm/data-validation.d.ts +9 -0
- package/dist/esm/data-validation.d.ts.map +1 -1
- package/dist/esm/data-validation.js +27 -57
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/rich-text-html.d.ts +9 -0
- package/dist/esm/rich-text-html.d.ts.map +1 -1
- package/dist/esm/rich-text-html.js +20 -4
- package/dist/esm/sheet-names.d.ts +60 -0
- package/dist/esm/sheet-names.d.ts.map +1 -0
- package/dist/esm/sheet-names.js +109 -0
- package/dist/esm/sheet-protection.d.ts +9 -0
- package/dist/esm/sheet-protection.d.ts.map +1 -0
- package/dist/esm/sheet-protection.js +8 -0
- package/dist/esm/string.d.ts +31 -0
- package/dist/esm/string.d.ts.map +1 -1
- package/dist/esm/string.js +62 -2
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,58 +1,6 @@
|
|
|
1
|
+
import { escapeConditionFormulaText, normalizeConditionOperand, } from "./condition-operand";
|
|
1
2
|
import { stripDataValidationTextPrefix } from "./data-validation";
|
|
2
|
-
import { checkIfStringIsReference } from "./selection";
|
|
3
3
|
const FORMULA_PREDICATE_BASE_CELL_REF = "A1";
|
|
4
|
-
const NUMBER_LITERAL_PATTERN = /^-?\d+(?:\.\d+)?$/;
|
|
5
|
-
const BOOLEAN_LITERAL_PATTERN = /^(TRUE|FALSE)$/i;
|
|
6
|
-
const escapeFormulaText = (value) => value.replace(/"/g, '""');
|
|
7
|
-
/**
|
|
8
|
-
* Parse a locale-formatted numeric string (e.g., "6,5" in fr-FR) into a JS
|
|
9
|
-
* number. Uses `Intl.NumberFormat` to detect decimal/group separators.
|
|
10
|
-
* Returns NaN on failure.
|
|
11
|
-
*/
|
|
12
|
-
const parseLocaleNumberLoose = (value, locale) => {
|
|
13
|
-
try {
|
|
14
|
-
const parts = new Intl.NumberFormat(locale).formatToParts(1234.5);
|
|
15
|
-
const decimal = parts.find((part) => part.type === "decimal")?.value ?? ".";
|
|
16
|
-
const group = parts.find((part) => part.type === "group")?.value ?? ",";
|
|
17
|
-
const cleaned = value.split(group).join("").replace(decimal, ".");
|
|
18
|
-
return Number(cleaned);
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return NaN;
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Normalize a condition value for use in a formula expression.
|
|
26
|
-
* Handles locale-specific number formats (e.g., "6,5" -> "6.5").
|
|
27
|
-
*/
|
|
28
|
-
const normalizeConditionValue = (raw, allowStringLiteral = false, locale) => {
|
|
29
|
-
if (!raw)
|
|
30
|
-
return "";
|
|
31
|
-
const trimmed = raw.trim();
|
|
32
|
-
if (!trimmed)
|
|
33
|
-
return "";
|
|
34
|
-
if (trimmed.startsWith("=")) {
|
|
35
|
-
return trimmed.slice(1);
|
|
36
|
-
}
|
|
37
|
-
if (checkIfStringIsReference(trimmed)) {
|
|
38
|
-
return trimmed;
|
|
39
|
-
}
|
|
40
|
-
if (NUMBER_LITERAL_PATTERN.test(trimmed) ||
|
|
41
|
-
BOOLEAN_LITERAL_PATTERN.test(trimmed)) {
|
|
42
|
-
return trimmed;
|
|
43
|
-
}
|
|
44
|
-
// Try parsing as locale-aware number
|
|
45
|
-
if (locale) {
|
|
46
|
-
const parsed = parseLocaleNumberLoose(trimmed, locale);
|
|
47
|
-
if (!isNaN(parsed)) {
|
|
48
|
-
return String(parsed);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (allowStringLiteral) {
|
|
52
|
-
return `"${escapeFormulaText(trimmed)}"`;
|
|
53
|
-
}
|
|
54
|
-
return trimmed;
|
|
55
|
-
};
|
|
56
4
|
/**
|
|
57
5
|
* Whether a CF rule is evaluated via the calculator (formula path) instead of
|
|
58
6
|
* the synchronous `checkBooleanRule` path used in
|
|
@@ -104,8 +52,8 @@ export const buildConditionalFormatExpression = (rule, baseCellRef, locale) => {
|
|
|
104
52
|
return undefined;
|
|
105
53
|
return trimmed.startsWith("=") ? trimmed : `=${trimmed}`;
|
|
106
54
|
}
|
|
107
|
-
const fromExpr =
|
|
108
|
-
const toExpr =
|
|
55
|
+
const fromExpr = normalizeConditionOperand(fromRaw, condition.type, locale);
|
|
56
|
+
const toExpr = normalizeConditionOperand(toRaw, condition.type, locale);
|
|
109
57
|
if (!fromExpr && !toExpr)
|
|
110
58
|
return undefined;
|
|
111
59
|
const comparisonOperatorMap = {
|
|
@@ -185,13 +133,13 @@ export const buildConditionalFormatExpression = (rule, baseCellRef, locale) => {
|
|
|
185
133
|
.flatMap((entry) => {
|
|
186
134
|
if (entry.startsWith("'")) {
|
|
187
135
|
const stripped = stripDataValidationTextPrefix(entry);
|
|
188
|
-
const textCondition = `${baseCellRef}="${
|
|
136
|
+
const textCondition = `${baseCellRef}="${escapeConditionFormulaText(stripped)}"`;
|
|
189
137
|
return stripped === ""
|
|
190
138
|
? [textCondition, `${baseCellRef}="'"`]
|
|
191
139
|
: [textCondition];
|
|
192
140
|
}
|
|
193
141
|
return entry
|
|
194
|
-
? [`${baseCellRef}=${
|
|
142
|
+
? [`${baseCellRef}=${normalizeConditionOperand(entry, condition.type, locale)}`]
|
|
195
143
|
: [];
|
|
196
144
|
})
|
|
197
145
|
.filter((entry) => Boolean(entry));
|
|
@@ -50,5 +50,14 @@ export declare const dataValidationOptionMatchesCellValue: (option: string | und
|
|
|
50
50
|
* builder from drifting.
|
|
51
51
|
*/
|
|
52
52
|
export declare const isDataValidationFormulaRule: (rule: DataValidationRule | undefined) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Excel's *Whole number* validation kind rejects any non-integer (`5.5` fails
|
|
55
|
+
* "whole between 1 and 10"). An imported `type="whole"` rule carries a plain
|
|
56
|
+
* `NUMBER_*` comparison as its `condition` (the bounds) — which alone accepts
|
|
57
|
+
* `5.5` — so when the rule's `xlsxSource.type` is `whole` the comparison is
|
|
58
|
+
* wrapped in the same integer guard `buildExcelValidationRule` composes for
|
|
59
|
+
* an authored Whole-number rule: `AND(ISNUMBER(x), x=INT(x), <comparison>)`.
|
|
60
|
+
* Rules of other kinds, and non-`NUMBER_*` conditions, are returned as built.
|
|
61
|
+
*/
|
|
53
62
|
export declare const buildDataValidationExpression: (rule: DataValidationRule | undefined, baseCellRef: string, locale?: string) => string | undefined;
|
|
54
63
|
//# sourceMappingURL=data-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-validation.d.ts","sourceRoot":"","sources":["../../data-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"data-validation.d.ts","sourceRoot":"","sources":["../../data-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AASpC;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAO,MAAM,KAAG,MACd,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAC/C,QAAQ,MAAM,GAAG,SAAS,GAAG,IAAI,EACjC,WAAW,MAAM,GAAG,SAAS,GAAG,IAAI,KACnC,OAKF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,2BAA2B,GACtC,MAAM,kBAAkB,GAAG,SAAS,YAKrC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GACxC,MAAM,kBAAkB,GAAG,SAAS,EACpC,aAAa,MAAM,EACnB,SAAS,MAAM,uBAgBhB,CAAC"}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { escapeConditionFormulaText, normalizeConditionOperand, } from "./condition-operand";
|
|
2
2
|
const FORMULA_PREDICATE_BASE_CELL_REF = "A1";
|
|
3
|
-
const NUMBER_LITERAL_PATTERN = /^-?\d+(?:\.\d+)?$/;
|
|
4
|
-
const BOOLEAN_LITERAL_PATTERN = /^(TRUE|FALSE)$/i;
|
|
5
|
-
const escapeFormulaText = (value) => value.replace(/"/g, '""');
|
|
6
3
|
/**
|
|
7
4
|
* A leading apostrophe in a data-validation / conditional-format list entry is
|
|
8
5
|
* Excel's text prefix — `'000s` is the text "000s" and a bare `'` is empty
|
|
@@ -22,55 +19,6 @@ export const dataValidationOptionMatchesCellValue = (option, cellValue) => {
|
|
|
22
19
|
const normalizedValue = rawValue === "'" ? "" : rawValue;
|
|
23
20
|
return normalizedOption.toLowerCase() === normalizedValue.toLowerCase();
|
|
24
21
|
};
|
|
25
|
-
/**
|
|
26
|
-
* Parse a locale-formatted numeric string (e.g., "6,5" in fr-FR) into a JS
|
|
27
|
-
* number. Uses `Intl.NumberFormat` to detect decimal/group separators.
|
|
28
|
-
* Returns NaN on failure.
|
|
29
|
-
*/
|
|
30
|
-
const parseLocaleNumberLoose = (value, locale) => {
|
|
31
|
-
try {
|
|
32
|
-
const parts = new Intl.NumberFormat(locale).formatToParts(1234.5);
|
|
33
|
-
const decimal = parts.find((part) => part.type === "decimal")?.value ?? ".";
|
|
34
|
-
const group = parts.find((part) => part.type === "group")?.value ?? ",";
|
|
35
|
-
const cleaned = value.split(group).join("").replace(decimal, ".");
|
|
36
|
-
return Number(cleaned);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return NaN;
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Normalize a condition value for use in a formula expression.
|
|
44
|
-
* Handles locale-specific number formats (e.g., "6,5" -> "6.5").
|
|
45
|
-
*/
|
|
46
|
-
const normalizeConditionValue = (raw, allowStringLiteral = false, locale) => {
|
|
47
|
-
if (!raw)
|
|
48
|
-
return "";
|
|
49
|
-
const trimmed = raw.trim();
|
|
50
|
-
if (!trimmed)
|
|
51
|
-
return "";
|
|
52
|
-
if (trimmed.startsWith("=")) {
|
|
53
|
-
return trimmed.slice(1);
|
|
54
|
-
}
|
|
55
|
-
if (checkIfStringIsReference(trimmed)) {
|
|
56
|
-
return trimmed;
|
|
57
|
-
}
|
|
58
|
-
if (NUMBER_LITERAL_PATTERN.test(trimmed) ||
|
|
59
|
-
BOOLEAN_LITERAL_PATTERN.test(trimmed)) {
|
|
60
|
-
return trimmed;
|
|
61
|
-
}
|
|
62
|
-
// Try parsing as locale-aware number
|
|
63
|
-
if (locale) {
|
|
64
|
-
const parsed = parseLocaleNumberLoose(trimmed, locale);
|
|
65
|
-
if (!isNaN(parsed)) {
|
|
66
|
-
return String(parsed);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (allowStringLiteral) {
|
|
70
|
-
return `"${escapeFormulaText(trimmed)}"`;
|
|
71
|
-
}
|
|
72
|
-
return trimmed;
|
|
73
|
-
};
|
|
74
22
|
/**
|
|
75
23
|
* Whether a data validation rule is evaluated via the calculator (formula path)
|
|
76
24
|
* instead of the synchronous `checkBooleanRule` path used in
|
|
@@ -110,7 +58,27 @@ const normalizeConditionValue = (raw, allowStringLiteral = false, locale) => {
|
|
|
110
58
|
export const isDataValidationFormulaRule = (rule) => {
|
|
111
59
|
return Boolean(buildDataValidationExpression(rule, FORMULA_PREDICATE_BASE_CELL_REF));
|
|
112
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Excel's *Whole number* validation kind rejects any non-integer (`5.5` fails
|
|
63
|
+
* "whole between 1 and 10"). An imported `type="whole"` rule carries a plain
|
|
64
|
+
* `NUMBER_*` comparison as its `condition` (the bounds) — which alone accepts
|
|
65
|
+
* `5.5` — so when the rule's `xlsxSource.type` is `whole` the comparison is
|
|
66
|
+
* wrapped in the same integer guard `buildExcelValidationRule` composes for
|
|
67
|
+
* an authored Whole-number rule: `AND(ISNUMBER(x), x=INT(x), <comparison>)`.
|
|
68
|
+
* Rules of other kinds, and non-`NUMBER_*` conditions, are returned as built.
|
|
69
|
+
*/
|
|
113
70
|
export const buildDataValidationExpression = (rule, baseCellRef, locale) => {
|
|
71
|
+
const expression = buildDataValidationConditionExpression(rule, baseCellRef, locale);
|
|
72
|
+
if (!expression)
|
|
73
|
+
return undefined;
|
|
74
|
+
const isWholeSource = rule?.xlsxSource?.type?.trim().toLowerCase() === "whole";
|
|
75
|
+
const isNumberCondition = Boolean(rule?.condition?.type?.startsWith("NUMBER_"));
|
|
76
|
+
if (!isWholeSource || !isNumberCondition)
|
|
77
|
+
return expression;
|
|
78
|
+
const body = expression.startsWith("=") ? expression.slice(1) : expression;
|
|
79
|
+
return `=AND(ISNUMBER(${baseCellRef}),${baseCellRef}=INT(${baseCellRef}),${body})`;
|
|
80
|
+
};
|
|
81
|
+
const buildDataValidationConditionExpression = (rule, baseCellRef, locale) => {
|
|
114
82
|
const condition = rule?.condition;
|
|
115
83
|
if (!condition?.type)
|
|
116
84
|
return undefined;
|
|
@@ -124,8 +92,8 @@ export const buildDataValidationExpression = (rule, baseCellRef, locale) => {
|
|
|
124
92
|
return undefined;
|
|
125
93
|
return trimmed.startsWith("=") ? trimmed : `=${trimmed}`;
|
|
126
94
|
}
|
|
127
|
-
const fromExpr =
|
|
128
|
-
const toExpr =
|
|
95
|
+
const fromExpr = normalizeConditionOperand(fromRaw, condition.type, locale);
|
|
96
|
+
const toExpr = normalizeConditionOperand(toRaw, condition.type, locale);
|
|
129
97
|
if (!fromExpr && !toExpr)
|
|
130
98
|
return undefined;
|
|
131
99
|
const comparisonOperatorMap = {
|
|
@@ -206,7 +174,7 @@ export const buildDataValidationExpression = (rule, baseCellRef, locale) => {
|
|
|
206
174
|
.flatMap((entry) => {
|
|
207
175
|
if (entry.startsWith("'")) {
|
|
208
176
|
const stripped = stripDataValidationTextPrefix(entry);
|
|
209
|
-
const textCondition = `${baseCellRef}="${
|
|
177
|
+
const textCondition = `${baseCellRef}="${escapeConditionFormulaText(stripped)}"`;
|
|
210
178
|
// The bare `'` entry means empty text, and a cell storing the lone
|
|
211
179
|
// text-prefix apostrophe is equivalent to a blank cell — accept it too.
|
|
212
180
|
return stripped === ""
|
|
@@ -214,7 +182,9 @@ export const buildDataValidationExpression = (rule, baseCellRef, locale) => {
|
|
|
214
182
|
: [textCondition];
|
|
215
183
|
}
|
|
216
184
|
return entry
|
|
217
|
-
? [
|
|
185
|
+
? [
|
|
186
|
+
`${baseCellRef}=${normalizeConditionOperand(entry, condition.type, locale)}`,
|
|
187
|
+
]
|
|
218
188
|
: [];
|
|
219
189
|
})
|
|
220
190
|
.filter((entry) => Boolean(entry));
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./colors";
|
|
|
18
18
|
export * from "./formula-refs";
|
|
19
19
|
export * from "./sort";
|
|
20
20
|
export * from "./table-styles";
|
|
21
|
+
export * from "./condition-operand";
|
|
21
22
|
export * from "./conditional-format";
|
|
22
23
|
export * from "./data-validation";
|
|
23
24
|
export * from "./content-hash";
|
|
@@ -26,8 +27,11 @@ export * from "./keyboard";
|
|
|
26
27
|
export * from "./sheet-span";
|
|
27
28
|
export * from "./formula-locale";
|
|
28
29
|
export * from "./table-names";
|
|
30
|
+
export * from "./sheet-names";
|
|
31
|
+
export * from "./sheet-protection";
|
|
29
32
|
export * from "./table-totals";
|
|
30
33
|
export * from "./sparkline-groups";
|
|
31
34
|
export * from "./rich-text-html";
|
|
35
|
+
export * from "./clipboard-html-sanitize";
|
|
32
36
|
export * from "./rich-text-links";
|
|
33
37
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./colors";
|
|
|
18
18
|
export * from "./formula-refs";
|
|
19
19
|
export * from "./sort";
|
|
20
20
|
export * from "./table-styles";
|
|
21
|
+
export * from "./condition-operand";
|
|
21
22
|
export * from "./conditional-format";
|
|
22
23
|
export * from "./data-validation";
|
|
23
24
|
export * from "./content-hash";
|
|
@@ -26,7 +27,10 @@ export * from "./keyboard";
|
|
|
26
27
|
export * from "./sheet-span";
|
|
27
28
|
export * from "./formula-locale";
|
|
28
29
|
export * from "./table-names";
|
|
30
|
+
export * from "./sheet-names";
|
|
31
|
+
export * from "./sheet-protection";
|
|
29
32
|
export * from "./table-totals";
|
|
30
33
|
export * from "./sparkline-groups";
|
|
31
34
|
export * from "./rich-text-html";
|
|
35
|
+
export * from "./clipboard-html-sanitize";
|
|
32
36
|
export * from "./rich-text-links";
|
|
@@ -52,6 +52,15 @@ export type ParsedRichText = {
|
|
|
52
52
|
* Parse an HTML clipboard payload into a hidden, mounted sandbox so
|
|
53
53
|
* `getComputedStyle` resolves the payload's own `<style>` rules (Excel's
|
|
54
54
|
* `.fontN` / `.xlNN` classes). Callers must `cleanup()` when done.
|
|
55
|
+
*
|
|
56
|
+
* The payload is untrusted. It is parsed with `DOMParser` (an inert document:
|
|
57
|
+
* no scripts run, no handlers fire, nothing loads), stripped by
|
|
58
|
+
* {@link sanitizeClipboardDocument} — scripts, frames, embeds, SVG/MathML,
|
|
59
|
+
* every `on*` handler, `javascript:`/`data:` links and network-reaching CSS —
|
|
60
|
+
* and only then are the surviving nodes imported into the live sandbox. The
|
|
61
|
+
* import is node-by-node (`importNode`), never a serialize + `innerHTML`
|
|
62
|
+
* round trip, so the parser cannot re-interpret the sanitized tree (mXSS).
|
|
63
|
+
* This sanitization is not optional and runs for every clipboard consumer.
|
|
55
64
|
*/
|
|
56
65
|
export declare const mountClipboardHtml: (clipboardValue: string) => {
|
|
57
66
|
root: HTMLDivElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-html.d.ts","sourceRoot":"","sources":["../../rich-text-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"rich-text-html.d.ts","sourceRoot":"","sources":["../../rich-text-html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAO5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,EAAE,GAAG,SAAS,CAAC;IAClC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,UAAU,CAAC;IACvB,yFAAyF;IACzF,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAgGF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,GAAI,gBAAgB,MAAM;;;CA0BxD,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAC/B,MAAM,GAAG,SAkBX,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAC/B,MAAM,GAAG,SAoBX,CAAC;AAiTF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAAI,MAAM,WAAW,KAAG,cA+DzD,CAAC;AAgBF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,MAAM,WAAW,KAChB,WAAW,GAAG,SAShB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,MAAM,MAAM,KACX,cAAc,GAAG,SAkBnB,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { sanitizeClipboardDocument } from "./clipboard-html-sanitize";
|
|
1
2
|
import { colorStringToHex } from "./color";
|
|
2
3
|
import { canUseDOM } from "./dom";
|
|
4
|
+
import { isSafeHyperlinkUri } from "./string";
|
|
3
5
|
const BLOCK_TAGS = new Set([
|
|
4
6
|
"p",
|
|
5
7
|
"div",
|
|
@@ -86,16 +88,28 @@ const NON_COLORS = new Set([
|
|
|
86
88
|
* Parse an HTML clipboard payload into a hidden, mounted sandbox so
|
|
87
89
|
* `getComputedStyle` resolves the payload's own `<style>` rules (Excel's
|
|
88
90
|
* `.fontN` / `.xlNN` classes). Callers must `cleanup()` when done.
|
|
91
|
+
*
|
|
92
|
+
* The payload is untrusted. It is parsed with `DOMParser` (an inert document:
|
|
93
|
+
* no scripts run, no handlers fire, nothing loads), stripped by
|
|
94
|
+
* {@link sanitizeClipboardDocument} — scripts, frames, embeds, SVG/MathML,
|
|
95
|
+
* every `on*` handler, `javascript:`/`data:` links and network-reaching CSS —
|
|
96
|
+
* and only then are the surviving nodes imported into the live sandbox. The
|
|
97
|
+
* import is node-by-node (`importNode`), never a serialize + `innerHTML`
|
|
98
|
+
* round trip, so the parser cannot re-interpret the sanitized tree (mXSS).
|
|
99
|
+
* This sanitization is not optional and runs for every clipboard consumer.
|
|
89
100
|
*/
|
|
90
101
|
export const mountClipboardHtml = (clipboardValue) => {
|
|
91
102
|
const parser = new DOMParser();
|
|
92
|
-
const doc = parser.parseFromString(clipboardValue, "text/html");
|
|
103
|
+
const doc = sanitizeClipboardDocument(parser.parseFromString(clipboardValue, "text/html"));
|
|
93
104
|
const sandbox = document.createElement("div");
|
|
94
105
|
sandbox.style.cssText =
|
|
95
106
|
"position:fixed;left:-10000px;top:-10000px;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;font-size:10pt;";
|
|
96
|
-
|
|
107
|
+
const bodyChildren = doc.body ? Array.from(doc.body.childNodes) : [];
|
|
108
|
+
for (const child of bodyChildren) {
|
|
109
|
+
sandbox.appendChild(document.importNode(child, true));
|
|
110
|
+
}
|
|
97
111
|
const styles = doc.head?.querySelectorAll("style") ?? [];
|
|
98
|
-
styles.forEach((style) => sandbox.appendChild(
|
|
112
|
+
styles.forEach((style) => sandbox.appendChild(document.importNode(style, true)));
|
|
99
113
|
document.body.appendChild(sandbox);
|
|
100
114
|
return {
|
|
101
115
|
root: sandbox,
|
|
@@ -232,8 +246,10 @@ const readElementFormat = (element, inherited) => {
|
|
|
232
246
|
break;
|
|
233
247
|
}
|
|
234
248
|
case "a": {
|
|
249
|
+
// The sanitizer already drops unsafe hrefs from clipboard payloads;
|
|
250
|
+
// this converter also serves non-clipboard callers, so re-check here.
|
|
235
251
|
const href = element.getAttribute("href");
|
|
236
|
-
if (href && href.trim()) {
|
|
252
|
+
if (href && href.trim() && isSafeHyperlinkUri(href)) {
|
|
237
253
|
format.link = { uri: href.trim() };
|
|
238
254
|
}
|
|
239
255
|
break;
|
|
@@ -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.";
|
package/dist/esm/string.d.ts
CHANGED
|
@@ -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
|
package/dist/esm/string.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|