@rowsncolumns/utils 9.2.35
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/LICENSE.md +100 -0
- package/dist/cjs/array.d.ts +3 -0
- package/dist/cjs/array.d.ts.map +1 -0
- package/dist/cjs/array.js +19 -0
- package/dist/cjs/cell-data-helpers.d.ts +19 -0
- package/dist/cjs/cell-data-helpers.d.ts.map +1 -0
- package/dist/cjs/cell-data-helpers.js +64 -0
- package/dist/cjs/color.d.ts +20 -0
- package/dist/cjs/color.d.ts.map +1 -0
- package/dist/cjs/color.js +168 -0
- package/dist/cjs/colors.d.ts +2 -0
- package/dist/cjs/colors.d.ts.map +1 -0
- package/dist/cjs/colors.js +78 -0
- package/dist/cjs/conditional-format.d.ts +49 -0
- package/dist/cjs/conditional-format.d.ts.map +1 -0
- package/dist/cjs/conditional-format.js +227 -0
- package/dist/cjs/content-hash.d.ts +16 -0
- package/dist/cjs/content-hash.d.ts.map +1 -0
- package/dist/cjs/content-hash.js +72 -0
- package/dist/cjs/data-validation.d.ts +54 -0
- package/dist/cjs/data-validation.d.ts.map +1 -0
- package/dist/cjs/data-validation.js +249 -0
- package/dist/cjs/date.d.ts +15 -0
- package/dist/cjs/date.d.ts.map +1 -0
- package/dist/cjs/date.js +56 -0
- package/dist/cjs/dom.d.ts +10 -0
- package/dist/cjs/dom.d.ts.map +1 -0
- package/dist/cjs/dom.js +97 -0
- package/dist/cjs/formatter.d.ts +22 -0
- package/dist/cjs/formatter.d.ts.map +1 -0
- package/dist/cjs/formatter.js +264 -0
- package/dist/cjs/formula-refs.d.ts +7 -0
- package/dist/cjs/formula-refs.d.ts.map +1 -0
- package/dist/cjs/formula-refs.js +54 -0
- package/dist/cjs/formula.d.ts +80 -0
- package/dist/cjs/formula.d.ts.map +1 -0
- package/dist/cjs/formula.js +401 -0
- package/dist/cjs/index.d.ts +24 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +26 -0
- package/dist/cjs/locale.d.ts +34 -0
- package/dist/cjs/locale.d.ts.map +1 -0
- package/dist/cjs/locale.js +260 -0
- package/dist/cjs/log.d.ts +4 -0
- package/dist/cjs/log.d.ts.map +1 -0
- package/dist/cjs/log.js +28 -0
- package/dist/cjs/memoize.d.ts +18 -0
- package/dist/cjs/memoize.d.ts.map +1 -0
- package/dist/cjs/memoize.js +128 -0
- package/dist/cjs/numbers.d.ts +19 -0
- package/dist/cjs/numbers.d.ts.map +1 -0
- package/dist/cjs/numbers.js +47 -0
- package/dist/cjs/selection.d.ts +88 -0
- package/dist/cjs/selection.d.ts.map +1 -0
- package/dist/cjs/selection.js +368 -0
- package/dist/cjs/shared-string-canonical.d.ts +18 -0
- package/dist/cjs/shared-string-canonical.d.ts.map +1 -0
- package/dist/cjs/shared-string-canonical.js +74 -0
- package/dist/cjs/sort.d.ts +15 -0
- package/dist/cjs/sort.d.ts.map +1 -0
- package/dist/cjs/sort.js +44 -0
- package/dist/cjs/string.d.ts +74 -0
- package/dist/cjs/string.d.ts.map +1 -0
- package/dist/cjs/string.js +237 -0
- package/dist/cjs/table-styles.d.ts +56 -0
- package/dist/cjs/table-styles.d.ts.map +1 -0
- package/dist/cjs/table-styles.js +362 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/cjs/typeguards.d.ts +4 -0
- package/dist/cjs/typeguards.d.ts.map +1 -0
- package/dist/cjs/typeguards.js +15 -0
- package/dist/cjs/uuid.d.ts +8 -0
- package/dist/cjs/uuid.d.ts.map +1 -0
- package/dist/cjs/uuid.js +30 -0
- package/dist/cjs/vitest.config.d.ts +3 -0
- package/dist/cjs/vitest.config.d.ts.map +1 -0
- package/dist/cjs/vitest.config.js +9 -0
- package/dist/esm/array.d.ts +3 -0
- package/dist/esm/array.d.ts.map +1 -0
- package/dist/esm/array.js +14 -0
- package/dist/esm/cell-data-helpers.d.ts +19 -0
- package/dist/esm/cell-data-helpers.d.ts.map +1 -0
- package/dist/esm/cell-data-helpers.js +39 -0
- package/dist/esm/color.d.ts +20 -0
- package/dist/esm/color.d.ts.map +1 -0
- package/dist/esm/color.js +152 -0
- package/dist/esm/colors.d.ts +2 -0
- package/dist/esm/colors.d.ts.map +1 -0
- package/dist/esm/colors.js +75 -0
- package/dist/esm/conditional-format.d.ts +49 -0
- package/dist/esm/conditional-format.d.ts.map +1 -0
- package/dist/esm/conditional-format.js +219 -0
- package/dist/esm/content-hash.d.ts +16 -0
- package/dist/esm/content-hash.d.ts.map +1 -0
- package/dist/esm/content-hash.js +68 -0
- package/dist/esm/data-validation.d.ts +54 -0
- package/dist/esm/data-validation.d.ts.map +1 -0
- package/dist/esm/data-validation.js +237 -0
- package/dist/esm/date.d.ts +15 -0
- package/dist/esm/date.d.ts.map +1 -0
- package/dist/esm/date.js +46 -0
- package/dist/esm/dom.d.ts +10 -0
- package/dist/esm/dom.d.ts.map +1 -0
- package/dist/esm/dom.js +90 -0
- package/dist/esm/formatter.d.ts +22 -0
- package/dist/esm/formatter.d.ts.map +1 -0
- package/dist/esm/formatter.js +253 -0
- package/dist/esm/formula-refs.d.ts +7 -0
- package/dist/esm/formula-refs.d.ts.map +1 -0
- package/dist/esm/formula-refs.js +49 -0
- package/dist/esm/formula.d.ts +80 -0
- package/dist/esm/formula.d.ts.map +1 -0
- package/dist/esm/formula.js +298 -0
- package/dist/esm/index.d.ts +24 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/locale.d.ts +34 -0
- package/dist/esm/locale.d.ts.map +1 -0
- package/dist/esm/locale.js +223 -0
- package/dist/esm/log.d.ts +4 -0
- package/dist/esm/log.d.ts.map +1 -0
- package/dist/esm/log.js +3 -0
- package/dist/esm/memoize.d.ts +18 -0
- package/dist/esm/memoize.d.ts.map +1 -0
- package/dist/esm/memoize.js +95 -0
- package/dist/esm/numbers.d.ts +19 -0
- package/dist/esm/numbers.d.ts.map +1 -0
- package/dist/esm/numbers.js +40 -0
- package/dist/esm/selection.d.ts +88 -0
- package/dist/esm/selection.d.ts.map +1 -0
- package/dist/esm/selection.js +311 -0
- package/dist/esm/shared-string-canonical.d.ts +18 -0
- package/dist/esm/shared-string-canonical.d.ts.map +1 -0
- package/dist/esm/shared-string-canonical.js +68 -0
- package/dist/esm/sort.d.ts +15 -0
- package/dist/esm/sort.d.ts.map +1 -0
- package/dist/esm/sort.js +39 -0
- package/dist/esm/string.d.ts +74 -0
- package/dist/esm/string.d.ts.map +1 -0
- package/dist/esm/string.js +213 -0
- package/dist/esm/table-styles.d.ts +56 -0
- package/dist/esm/table-styles.d.ts.map +1 -0
- package/dist/esm/table-styles.js +367 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/esm/typeguards.d.ts +4 -0
- package/dist/esm/typeguards.d.ts.map +1 -0
- package/dist/esm/typeguards.js +7 -0
- package/dist/esm/uuid.d.ts +8 -0
- package/dist/esm/uuid.d.ts.map +1 -0
- package/dist/esm/uuid.js +25 -0
- package/dist/esm/vitest.config.d.ts +3 -0
- package/dist/esm/vitest.config.d.ts.map +1 -0
- package/dist/esm/vitest.config.js +7 -0
- package/package.json +49 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildConditionalFormatExpression = exports.isConditionalFormatFormulaRule = void 0;
|
|
4
|
+
var data_validation_1 = require("./data-validation");
|
|
5
|
+
var selection_1 = require("./selection");
|
|
6
|
+
var FORMULA_PREDICATE_BASE_CELL_REF = "A1";
|
|
7
|
+
var NUMBER_LITERAL_PATTERN = /^-?\d+(?:\.\d+)?$/;
|
|
8
|
+
var BOOLEAN_LITERAL_PATTERN = /^(TRUE|FALSE)$/i;
|
|
9
|
+
var escapeFormulaText = function (value) { return value.replace(/"/g, '""'); };
|
|
10
|
+
/**
|
|
11
|
+
* Parse a locale-formatted numeric string (e.g., "6,5" in fr-FR) into a JS
|
|
12
|
+
* number. Uses `Intl.NumberFormat` to detect decimal/group separators.
|
|
13
|
+
* Returns NaN on failure.
|
|
14
|
+
*/
|
|
15
|
+
var parseLocaleNumberLoose = function (value, locale) {
|
|
16
|
+
var _a, _b, _c, _d;
|
|
17
|
+
try {
|
|
18
|
+
var parts = new Intl.NumberFormat(locale).formatToParts(1234.5);
|
|
19
|
+
var decimal = (_b = (_a = parts.find(function (part) { return part.type === "decimal"; })) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ".";
|
|
20
|
+
var group = (_d = (_c = parts.find(function (part) { return part.type === "group"; })) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : ",";
|
|
21
|
+
var cleaned = value.split(group).join("").replace(decimal, ".");
|
|
22
|
+
return Number(cleaned);
|
|
23
|
+
}
|
|
24
|
+
catch (_e) {
|
|
25
|
+
return NaN;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a condition value for use in a formula expression.
|
|
30
|
+
* Handles locale-specific number formats (e.g., "6,5" -> "6.5").
|
|
31
|
+
*/
|
|
32
|
+
var normalizeConditionValue = function (raw, allowStringLiteral, locale) {
|
|
33
|
+
if (allowStringLiteral === void 0) { allowStringLiteral = false; }
|
|
34
|
+
if (!raw)
|
|
35
|
+
return "";
|
|
36
|
+
var trimmed = raw.trim();
|
|
37
|
+
if (!trimmed)
|
|
38
|
+
return "";
|
|
39
|
+
if (trimmed.startsWith("=")) {
|
|
40
|
+
return trimmed.slice(1);
|
|
41
|
+
}
|
|
42
|
+
if ((0, selection_1.checkIfStringIsReference)(trimmed)) {
|
|
43
|
+
return trimmed;
|
|
44
|
+
}
|
|
45
|
+
if (NUMBER_LITERAL_PATTERN.test(trimmed) ||
|
|
46
|
+
BOOLEAN_LITERAL_PATTERN.test(trimmed)) {
|
|
47
|
+
return trimmed;
|
|
48
|
+
}
|
|
49
|
+
// Try parsing as locale-aware number
|
|
50
|
+
if (locale) {
|
|
51
|
+
var parsed = parseLocaleNumberLoose(trimmed, locale);
|
|
52
|
+
if (!isNaN(parsed)) {
|
|
53
|
+
return String(parsed);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (allowStringLiteral) {
|
|
57
|
+
return "\"".concat(escapeFormulaText(trimmed), "\"");
|
|
58
|
+
}
|
|
59
|
+
return trimmed;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Whether a CF rule is evaluated via the calculator (formula path) instead of
|
|
63
|
+
* the synchronous `checkBooleanRule` path used in
|
|
64
|
+
* `apps/spreadsheet/hooks/use-conditional-formatting.ts`.
|
|
65
|
+
*
|
|
66
|
+
* Returns `true` for every condition type that `buildConditionalFormatExpression`
|
|
67
|
+
* can express as a single-cell formula: CUSTOM_FORMULA, all NUMBER_/DATE_/TEXT_
|
|
68
|
+
* comparisons, BETWEEN/NOT_BETWEEN, CONTAINS/NOT_CONTAINS, STARTS_WITH/ENDS_WITH,
|
|
69
|
+
* ONE_OF_LIST/ONE_OF_RANGE, BLANK/NOT_BLANK, BOOLEAN, TEXT_IS_EMAIL/TEXT_IS_URL.
|
|
70
|
+
* The calculator stores the per-cell boolean on
|
|
71
|
+
* `cellData.conditionalFormattingResultById[ruleId]`.
|
|
72
|
+
*
|
|
73
|
+
* Returns `false` — leaving the rule on the sync path — for rules that aren't
|
|
74
|
+
* expressible as a single-cell formula:
|
|
75
|
+
* - Aggregate-over-range: gradient/heatmap, TOP/BOTTOM N, DUPLICATE/UNIQUE,
|
|
76
|
+
* NUMBER_ABOVE_AVERAGE / NUMBER_BELOW_AVERAGE — the result depends on
|
|
77
|
+
* other cells in the range, not just the current cell.
|
|
78
|
+
* - Loop-state: NUMBER_GREATER_THAN_PREVIOUS and the other `*_PREVIOUS`
|
|
79
|
+
* variants — compare against the prior row's value held in iteration state.
|
|
80
|
+
* - Calendar-relative dates: DATE_TODAY / DATE_THIS_WEEK / DATE_THIS_MONTH
|
|
81
|
+
* and the surrounding family, plus DATE_IS_VALID. Could be expressed via
|
|
82
|
+
* TODAY()-based formulas if we ever want to migrate them.
|
|
83
|
+
* - Cell error state: ERROR / NOT_ERROR — read directly from the extended
|
|
84
|
+
* value in `getValidationValue`.
|
|
85
|
+
*
|
|
86
|
+
* The consumers (rule create/update/delete enqueueing in spreadsheet-state,
|
|
87
|
+
* `getConditionalFormatFormula` building the expression, `getValidationValue`
|
|
88
|
+
* choosing where to read the per-cell value, and the `isFormulaRule` branch
|
|
89
|
+
* in `use-conditional-formatting.ts`) MUST stay in lockstep with this
|
|
90
|
+
* predicate — if it returns `true`, the calculator owns the result and the
|
|
91
|
+
* sync `checkBooleanRule` path must not run for that rule. Delegating to
|
|
92
|
+
* `buildConditionalFormatExpression` keeps the predicate and the builder from
|
|
93
|
+
* drifting.
|
|
94
|
+
*/
|
|
95
|
+
var isConditionalFormatFormulaRule = function (rule) {
|
|
96
|
+
return Boolean((0, exports.buildConditionalFormatExpression)(rule, FORMULA_PREDICATE_BASE_CELL_REF));
|
|
97
|
+
};
|
|
98
|
+
exports.isConditionalFormatFormulaRule = isConditionalFormatFormulaRule;
|
|
99
|
+
var buildConditionalFormatExpression = function (rule, baseCellRef, locale) {
|
|
100
|
+
var _a, _b, _c, _d;
|
|
101
|
+
var condition = (_a = rule === null || rule === void 0 ? void 0 : rule.booleanRule) === null || _a === void 0 ? void 0 : _a.condition;
|
|
102
|
+
if (!(condition === null || condition === void 0 ? void 0 : condition.type))
|
|
103
|
+
return undefined;
|
|
104
|
+
var values = (_b = condition.values) !== null && _b !== void 0 ? _b : [];
|
|
105
|
+
var fromRaw = (_c = values[0]) === null || _c === void 0 ? void 0 : _c.userEnteredValue;
|
|
106
|
+
var toRaw = (_d = values[1]) === null || _d === void 0 ? void 0 : _d.userEnteredValue;
|
|
107
|
+
if (condition.type === "CUSTOM_FORMULA") {
|
|
108
|
+
var raw = fromRaw !== null && fromRaw !== void 0 ? fromRaw : "";
|
|
109
|
+
var trimmed = raw.trim();
|
|
110
|
+
if (!trimmed)
|
|
111
|
+
return undefined;
|
|
112
|
+
return trimmed.startsWith("=") ? trimmed : "=".concat(trimmed);
|
|
113
|
+
}
|
|
114
|
+
var fromExpr = normalizeConditionValue(fromRaw, true, locale);
|
|
115
|
+
var toExpr = normalizeConditionValue(toRaw, true, locale);
|
|
116
|
+
if (!fromExpr && !toExpr)
|
|
117
|
+
return undefined;
|
|
118
|
+
var comparisonOperatorMap = {
|
|
119
|
+
NUMBER_EQ: "=",
|
|
120
|
+
NUMBER_NOT_EQ: "<>",
|
|
121
|
+
NUMBER_GREATER: ">",
|
|
122
|
+
NUMBER_GREATER_THAN_EQ: ">=",
|
|
123
|
+
NUMBER_LESS: "<",
|
|
124
|
+
NUMBER_LESS_THAN_EQ: "<=",
|
|
125
|
+
DATE_EQ: "=",
|
|
126
|
+
DATE_NOT_EQ: "<>",
|
|
127
|
+
DATE_BEFORE: "<",
|
|
128
|
+
DATE_AFTER: ">",
|
|
129
|
+
DATE_ON_OR_BEFORE: "<=",
|
|
130
|
+
DATE_ON_OR_AFTER: ">=",
|
|
131
|
+
TEXT_EQ: "=",
|
|
132
|
+
TEXT_NOT_EQ: "<>",
|
|
133
|
+
};
|
|
134
|
+
if (condition.type === "NUMBER_BETWEEN" ||
|
|
135
|
+
condition.type === "DATE_BETWEEN") {
|
|
136
|
+
if (!fromExpr || !toExpr)
|
|
137
|
+
return undefined;
|
|
138
|
+
return "=AND(".concat(baseCellRef, ">=").concat(fromExpr, ",").concat(baseCellRef, "<=").concat(toExpr, ")");
|
|
139
|
+
}
|
|
140
|
+
if (condition.type === "NUMBER_NOT_BETWEEN" ||
|
|
141
|
+
condition.type === "DATE_NOT_BETWEEN") {
|
|
142
|
+
if (!fromExpr || !toExpr)
|
|
143
|
+
return undefined;
|
|
144
|
+
return "=OR(".concat(baseCellRef, "<").concat(fromExpr, ",").concat(baseCellRef, ">").concat(toExpr, ")");
|
|
145
|
+
}
|
|
146
|
+
var comparisonOperator = comparisonOperatorMap[condition.type];
|
|
147
|
+
if (comparisonOperator) {
|
|
148
|
+
if (!fromExpr)
|
|
149
|
+
return undefined;
|
|
150
|
+
return "=".concat(baseCellRef).concat(comparisonOperator).concat(fromExpr);
|
|
151
|
+
}
|
|
152
|
+
if (condition.type === "TEXT_CONTAINS") {
|
|
153
|
+
if (!fromExpr)
|
|
154
|
+
return undefined;
|
|
155
|
+
return "=ISNUMBER(SEARCH(".concat(fromExpr, ",").concat(baseCellRef, "))");
|
|
156
|
+
}
|
|
157
|
+
if (condition.type === "TEXT_NOT_CONTAINS") {
|
|
158
|
+
if (!fromExpr)
|
|
159
|
+
return undefined;
|
|
160
|
+
return "=NOT(ISNUMBER(SEARCH(".concat(fromExpr, ",").concat(baseCellRef, ")))");
|
|
161
|
+
}
|
|
162
|
+
if (condition.type === "TEXT_STARTS_WITH") {
|
|
163
|
+
if (!fromExpr)
|
|
164
|
+
return undefined;
|
|
165
|
+
return "=LEFT(".concat(baseCellRef, ",LEN(").concat(fromExpr, "))=").concat(fromExpr);
|
|
166
|
+
}
|
|
167
|
+
if (condition.type === "TEXT_ENDS_WITH") {
|
|
168
|
+
if (!fromExpr)
|
|
169
|
+
return undefined;
|
|
170
|
+
return "=RIGHT(".concat(baseCellRef, ",LEN(").concat(fromExpr, "))=").concat(fromExpr);
|
|
171
|
+
}
|
|
172
|
+
if (condition.type === "TEXT_IS_EMAIL") {
|
|
173
|
+
return "=ISNUMBER(SEARCH(\"@\",".concat(baseCellRef, "))");
|
|
174
|
+
}
|
|
175
|
+
if (condition.type === "TEXT_IS_URL") {
|
|
176
|
+
return "=OR(LEFT(".concat(baseCellRef, ",7)=\"http://\",LEFT(").concat(baseCellRef, ",8)=\"https://\")");
|
|
177
|
+
}
|
|
178
|
+
if (condition.type === "ONE_OF_RANGE") {
|
|
179
|
+
if (!fromExpr)
|
|
180
|
+
return undefined;
|
|
181
|
+
return "=COUNTIF(".concat(fromExpr, ",").concat(baseCellRef, ")>0");
|
|
182
|
+
}
|
|
183
|
+
if (condition.type === "ONE_OF_LIST") {
|
|
184
|
+
var entries = fromRaw
|
|
185
|
+
? fromRaw.split(",").map(function (entry) { return entry.trim(); })
|
|
186
|
+
: [];
|
|
187
|
+
// A leading apostrophe is Excel's text prefix — `'000s` is the text
|
|
188
|
+
// "000s" and a bare `'` is empty text — never part of the value. A cell
|
|
189
|
+
// storing the lone text-prefix apostrophe is equivalent to a blank cell,
|
|
190
|
+
// so the bare `'` entry accepts it too.
|
|
191
|
+
var conditions = entries
|
|
192
|
+
.flatMap(function (entry) {
|
|
193
|
+
if (entry.startsWith("'")) {
|
|
194
|
+
var stripped = (0, data_validation_1.stripDataValidationTextPrefix)(entry);
|
|
195
|
+
var textCondition = "".concat(baseCellRef, "=\"").concat(escapeFormulaText(stripped), "\"");
|
|
196
|
+
return stripped === ""
|
|
197
|
+
? [textCondition, "".concat(baseCellRef, "=\"'\"")]
|
|
198
|
+
: [textCondition];
|
|
199
|
+
}
|
|
200
|
+
return entry
|
|
201
|
+
? ["".concat(baseCellRef, "=").concat(normalizeConditionValue(entry, true, locale))]
|
|
202
|
+
: [];
|
|
203
|
+
})
|
|
204
|
+
.filter(function (entry) { return Boolean(entry); });
|
|
205
|
+
if (!conditions.length)
|
|
206
|
+
return undefined;
|
|
207
|
+
return conditions.length === 1
|
|
208
|
+
? "=".concat(conditions[0])
|
|
209
|
+
: "=OR(".concat(conditions.join(","), ")");
|
|
210
|
+
}
|
|
211
|
+
if (condition.type === "BLANK") {
|
|
212
|
+
return "=".concat(baseCellRef, "=\"\"");
|
|
213
|
+
}
|
|
214
|
+
if (condition.type === "NOT_BLANK") {
|
|
215
|
+
return "=".concat(baseCellRef, "<>\"\"");
|
|
216
|
+
}
|
|
217
|
+
if (condition.type === "BOOLEAN") {
|
|
218
|
+
return "=OR(".concat(baseCellRef, "=TRUE,").concat(baseCellRef, "=FALSE)");
|
|
219
|
+
}
|
|
220
|
+
if (condition.type === "TEXT_EQ" || condition.type === "TEXT_NOT_EQ") {
|
|
221
|
+
if (!fromExpr)
|
|
222
|
+
return undefined;
|
|
223
|
+
return "=".concat(baseCellRef).concat(comparisonOperatorMap[condition.type]).concat(fromExpr);
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
};
|
|
227
|
+
exports.buildConditionalFormatExpression = buildConditionalFormatExpression;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const CONTENT_HASH_LENGTH = 8;
|
|
2
|
+
/**
|
|
3
|
+
* Stable, deterministic 8-character base62 ID derived from the input string.
|
|
4
|
+
* The same value always produces the same id across sessions, clients, and
|
|
5
|
+
* languages (FNV-1a 64-bit over UTF-8 bytes → low-48 → base62).
|
|
6
|
+
*
|
|
7
|
+
* 62^8 ≈ 2.18e14 ids; birthday-collision risk is negligible for spreadsheet
|
|
8
|
+
* shared-string tables. Callers that need defense against the astronomically
|
|
9
|
+
* rare same-id-different-value case should keep a random-id fallback.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: This algorithm is mirrored in
|
|
12
|
+
* `python/rowsncolumns_spreadsheet/utils.py::content_hash`. Any change to the
|
|
13
|
+
* constants, byte encoding, or base62 alphabet must be applied to both ports.
|
|
14
|
+
*/
|
|
15
|
+
export declare const contentHash: (value: string) => string;
|
|
16
|
+
//# sourceMappingURL=content-hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-hash.d.ts","sourceRoot":"","sources":["../../content-hash.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAsCrC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAa3C,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.contentHash = exports.CONTENT_HASH_LENGTH = void 0;
|
|
4
|
+
// BigInt literals (e.g. `0x…n`) need ES2020+; the utils package targets older
|
|
5
|
+
// runtimes, so build them via the BigInt() constructor instead.
|
|
6
|
+
var FNV_OFFSET_BASIS_64 = BigInt("0xcbf29ce484222325");
|
|
7
|
+
var FNV_PRIME_64 = BigInt("0x100000001b3");
|
|
8
|
+
var ONE = BigInt(1);
|
|
9
|
+
var SIXTY_FOUR = BigInt(64);
|
|
10
|
+
var MASK_64 = (ONE << SIXTY_FOUR) - ONE;
|
|
11
|
+
var BASE62_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
12
|
+
var BASE62 = BigInt(BASE62_ALPHABET.length);
|
|
13
|
+
var TEXT_ENCODER = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
14
|
+
exports.CONTENT_HASH_LENGTH = 8;
|
|
15
|
+
var toUtf8Bytes = function (value) {
|
|
16
|
+
if (TEXT_ENCODER)
|
|
17
|
+
return TEXT_ENCODER.encode(value);
|
|
18
|
+
// Minimal UTF-8 fallback for environments without TextEncoder. Mirrors the
|
|
19
|
+
// standard encoding so the hash stays identical across runtimes.
|
|
20
|
+
var bytes = [];
|
|
21
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
22
|
+
var codePoint = value.charCodeAt(i);
|
|
23
|
+
if (codePoint >= 0xd800 && codePoint <= 0xdbff && i + 1 < value.length) {
|
|
24
|
+
var next = value.charCodeAt(i + 1);
|
|
25
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
26
|
+
codePoint = 0x10000 + ((codePoint - 0xd800) << 10) + (next - 0xdc00);
|
|
27
|
+
i += 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (codePoint < 0x80) {
|
|
31
|
+
bytes.push(codePoint);
|
|
32
|
+
}
|
|
33
|
+
else if (codePoint < 0x800) {
|
|
34
|
+
bytes.push(0xc0 | (codePoint >> 6), 0x80 | (codePoint & 0x3f));
|
|
35
|
+
}
|
|
36
|
+
else if (codePoint < 0x10000) {
|
|
37
|
+
bytes.push(0xe0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
bytes.push(0xf0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 0x3f), 0x80 | ((codePoint >> 6) & 0x3f), 0x80 | (codePoint & 0x3f));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return bytes;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Stable, deterministic 8-character base62 ID derived from the input string.
|
|
47
|
+
* The same value always produces the same id across sessions, clients, and
|
|
48
|
+
* languages (FNV-1a 64-bit over UTF-8 bytes → low-48 → base62).
|
|
49
|
+
*
|
|
50
|
+
* 62^8 ≈ 2.18e14 ids; birthday-collision risk is negligible for spreadsheet
|
|
51
|
+
* shared-string tables. Callers that need defense against the astronomically
|
|
52
|
+
* rare same-id-different-value case should keep a random-id fallback.
|
|
53
|
+
*
|
|
54
|
+
* IMPORTANT: This algorithm is mirrored in
|
|
55
|
+
* `python/rowsncolumns_spreadsheet/utils.py::content_hash`. Any change to the
|
|
56
|
+
* constants, byte encoding, or base62 alphabet must be applied to both ports.
|
|
57
|
+
*/
|
|
58
|
+
var contentHash = function (value) {
|
|
59
|
+
var hash = FNV_OFFSET_BASIS_64;
|
|
60
|
+
var bytes = toUtf8Bytes(value);
|
|
61
|
+
for (var i = 0; i < bytes.length; i += 1) {
|
|
62
|
+
hash ^= BigInt(bytes[i]);
|
|
63
|
+
hash = (hash * FNV_PRIME_64) & MASK_64;
|
|
64
|
+
}
|
|
65
|
+
var result = "";
|
|
66
|
+
for (var i = 0; i < exports.CONTENT_HASH_LENGTH; i += 1) {
|
|
67
|
+
result = BASE62_ALPHABET[Number(hash % BASE62)] + result;
|
|
68
|
+
hash = hash / BASE62;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
exports.contentHash = contentHash;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { DataValidationRule } from "@rowsncolumns/common-types";
|
|
2
|
+
/**
|
|
3
|
+
* A leading apostrophe in a data-validation / conditional-format list entry is
|
|
4
|
+
* Excel's text prefix — `'000s` is the text "000s" and a bare `'` is empty
|
|
5
|
+
* text — never part of the value itself.
|
|
6
|
+
*/
|
|
7
|
+
export declare const stripDataValidationTextPrefix: (entry: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Whether a cell's current text matches a ONE_OF_LIST option, with Excel's
|
|
10
|
+
* text-prefix semantics on BOTH sides: the option strips its leading `'`
|
|
11
|
+
* (`'000s` ≡ "000s", a bare `'` ≡ empty text) and a cell storing the lone
|
|
12
|
+
* text-prefix apostrophe is equivalent to a blank cell. Case-insensitive,
|
|
13
|
+
* mirroring `checkBooleanRule`'s ONE_OF_LIST matching.
|
|
14
|
+
*/
|
|
15
|
+
export declare const dataValidationOptionMatchesCellValue: (option: string | undefined | null, cellValue: string | undefined | null) => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether a data validation rule is evaluated via the calculator (formula path)
|
|
18
|
+
* instead of the synchronous `checkBooleanRule` path used in
|
|
19
|
+
* `SheetCell.validate()` (`libs/spreadsheet-state/sheet-cell.ts`).
|
|
20
|
+
*
|
|
21
|
+
* Returns `true` for every condition type that `buildDataValidationExpression`
|
|
22
|
+
* can express as a single-cell formula: CUSTOM_FORMULA, all NUMBER_/DATE_/TEXT_
|
|
23
|
+
* comparisons, BETWEEN/NOT_BETWEEN, CONTAINS/NOT_CONTAINS, STARTS_WITH/ENDS_WITH,
|
|
24
|
+
* ONE_OF_LIST/ONE_OF_RANGE, BLANK/NOT_BLANK, BOOLEAN, TEXT_IS_EMAIL/TEXT_IS_URL.
|
|
25
|
+
* The calculator stores the per-cell boolean on
|
|
26
|
+
* `cellData.dataValidationResultById` and `SheetCell.validate()` reads
|
|
27
|
+
* `this.dataValidationResult` to decide validity.
|
|
28
|
+
*
|
|
29
|
+
* Returns `false` — leaving the rule on the sync path — for rules that aren't
|
|
30
|
+
* expressible as a single-cell formula:
|
|
31
|
+
* - Loop-state: NUMBER_*_PREVIOUS comparisons (compare to prior row).
|
|
32
|
+
* - Calendar-relative dates: DATE_TODAY / DATE_THIS_WEEK / DATE_THIS_MONTH
|
|
33
|
+
* and the surrounding family, plus DATE_IS_VALID.
|
|
34
|
+
* - Cell error state: ERROR / NOT_ERROR.
|
|
35
|
+
*
|
|
36
|
+
* Note (input-gating tradeoff): unlike conditional formatting, data validation
|
|
37
|
+
* decides whether a cell is flagged invalid. The formula path runs through the
|
|
38
|
+
* calculator, so a freshly-typed value spends one calc tick at
|
|
39
|
+
* `dataValidationResult === undefined`, during which `SheetCell.validate()`
|
|
40
|
+
* treats the cell as valid. Anything reading validity synchronously right
|
|
41
|
+
* after a write may see stale-valid for one tick.
|
|
42
|
+
*
|
|
43
|
+
* Consumers that must stay in lockstep with this predicate:
|
|
44
|
+
* - `createDataValidationRule` / update / delete (enqueueing into the calc).
|
|
45
|
+
* - `getDataValidationFormula` (builds the expression for the calculator).
|
|
46
|
+
* - `SheetCell.validate()` (chooses formula-result vs sync `checkBooleanRule`).
|
|
47
|
+
* - `use-on-filter-table` (uses the same predicate to decide its formula path).
|
|
48
|
+
*
|
|
49
|
+
* Delegating to `buildDataValidationExpression` keeps the predicate and the
|
|
50
|
+
* builder from drifting.
|
|
51
|
+
*/
|
|
52
|
+
export declare const isDataValidationFormulaRule: (rule: DataValidationRule | undefined) => boolean;
|
|
53
|
+
export declare const buildDataValidationExpression: (rule: DataValidationRule | undefined, baseCellRef: string, locale?: string) => string | undefined;
|
|
54
|
+
//# sourceMappingURL=data-validation.d.ts.map
|
|
@@ -0,0 +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;AAWpC;;;;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;AAwDF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,2BAA2B,GACtC,MAAM,kBAAkB,GAAG,SAAS,YAKrC,CAAC;AAEF,eAAO,MAAM,6BAA6B,GACxC,MAAM,kBAAkB,GAAG,SAAS,EACpC,aAAa,MAAM,EACnB,SAAS,MAAM,uBA4HhB,CAAC"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDataValidationExpression = exports.isDataValidationFormulaRule = exports.dataValidationOptionMatchesCellValue = exports.stripDataValidationTextPrefix = void 0;
|
|
4
|
+
var selection_1 = require("./selection");
|
|
5
|
+
var FORMULA_PREDICATE_BASE_CELL_REF = "A1";
|
|
6
|
+
var NUMBER_LITERAL_PATTERN = /^-?\d+(?:\.\d+)?$/;
|
|
7
|
+
var BOOLEAN_LITERAL_PATTERN = /^(TRUE|FALSE)$/i;
|
|
8
|
+
var escapeFormulaText = function (value) { return value.replace(/"/g, '""'); };
|
|
9
|
+
/**
|
|
10
|
+
* A leading apostrophe in a data-validation / conditional-format list entry is
|
|
11
|
+
* Excel's text prefix — `'000s` is the text "000s" and a bare `'` is empty
|
|
12
|
+
* text — never part of the value itself.
|
|
13
|
+
*/
|
|
14
|
+
var stripDataValidationTextPrefix = function (entry) {
|
|
15
|
+
return entry.startsWith("'") ? entry.slice(1) : entry;
|
|
16
|
+
};
|
|
17
|
+
exports.stripDataValidationTextPrefix = stripDataValidationTextPrefix;
|
|
18
|
+
/**
|
|
19
|
+
* Whether a cell's current text matches a ONE_OF_LIST option, with Excel's
|
|
20
|
+
* text-prefix semantics on BOTH sides: the option strips its leading `'`
|
|
21
|
+
* (`'000s` ≡ "000s", a bare `'` ≡ empty text) and a cell storing the lone
|
|
22
|
+
* text-prefix apostrophe is equivalent to a blank cell. Case-insensitive,
|
|
23
|
+
* mirroring `checkBooleanRule`'s ONE_OF_LIST matching.
|
|
24
|
+
*/
|
|
25
|
+
var dataValidationOptionMatchesCellValue = function (option, cellValue) {
|
|
26
|
+
var normalizedOption = (0, exports.stripDataValidationTextPrefix)(option !== null && option !== void 0 ? option : "");
|
|
27
|
+
var rawValue = cellValue !== null && cellValue !== void 0 ? cellValue : "";
|
|
28
|
+
var normalizedValue = rawValue === "'" ? "" : rawValue;
|
|
29
|
+
return normalizedOption.toLowerCase() === normalizedValue.toLowerCase();
|
|
30
|
+
};
|
|
31
|
+
exports.dataValidationOptionMatchesCellValue = dataValidationOptionMatchesCellValue;
|
|
32
|
+
/**
|
|
33
|
+
* Parse a locale-formatted numeric string (e.g., "6,5" in fr-FR) into a JS
|
|
34
|
+
* number. Uses `Intl.NumberFormat` to detect decimal/group separators.
|
|
35
|
+
* Returns NaN on failure.
|
|
36
|
+
*/
|
|
37
|
+
var parseLocaleNumberLoose = function (value, locale) {
|
|
38
|
+
var _a, _b, _c, _d;
|
|
39
|
+
try {
|
|
40
|
+
var parts = new Intl.NumberFormat(locale).formatToParts(1234.5);
|
|
41
|
+
var decimal = (_b = (_a = parts.find(function (part) { return part.type === "decimal"; })) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ".";
|
|
42
|
+
var group = (_d = (_c = parts.find(function (part) { return part.type === "group"; })) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : ",";
|
|
43
|
+
var cleaned = value.split(group).join("").replace(decimal, ".");
|
|
44
|
+
return Number(cleaned);
|
|
45
|
+
}
|
|
46
|
+
catch (_e) {
|
|
47
|
+
return NaN;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a condition value for use in a formula expression.
|
|
52
|
+
* Handles locale-specific number formats (e.g., "6,5" -> "6.5").
|
|
53
|
+
*/
|
|
54
|
+
var normalizeConditionValue = function (raw, allowStringLiteral, locale) {
|
|
55
|
+
if (allowStringLiteral === void 0) { allowStringLiteral = false; }
|
|
56
|
+
if (!raw)
|
|
57
|
+
return "";
|
|
58
|
+
var trimmed = raw.trim();
|
|
59
|
+
if (!trimmed)
|
|
60
|
+
return "";
|
|
61
|
+
if (trimmed.startsWith("=")) {
|
|
62
|
+
return trimmed.slice(1);
|
|
63
|
+
}
|
|
64
|
+
if ((0, selection_1.checkIfStringIsReference)(trimmed)) {
|
|
65
|
+
return trimmed;
|
|
66
|
+
}
|
|
67
|
+
if (NUMBER_LITERAL_PATTERN.test(trimmed) ||
|
|
68
|
+
BOOLEAN_LITERAL_PATTERN.test(trimmed)) {
|
|
69
|
+
return trimmed;
|
|
70
|
+
}
|
|
71
|
+
// Try parsing as locale-aware number
|
|
72
|
+
if (locale) {
|
|
73
|
+
var parsed = parseLocaleNumberLoose(trimmed, locale);
|
|
74
|
+
if (!isNaN(parsed)) {
|
|
75
|
+
return String(parsed);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (allowStringLiteral) {
|
|
79
|
+
return "\"".concat(escapeFormulaText(trimmed), "\"");
|
|
80
|
+
}
|
|
81
|
+
return trimmed;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Whether a data validation rule is evaluated via the calculator (formula path)
|
|
85
|
+
* instead of the synchronous `checkBooleanRule` path used in
|
|
86
|
+
* `SheetCell.validate()` (`libs/spreadsheet-state/sheet-cell.ts`).
|
|
87
|
+
*
|
|
88
|
+
* Returns `true` for every condition type that `buildDataValidationExpression`
|
|
89
|
+
* can express as a single-cell formula: CUSTOM_FORMULA, all NUMBER_/DATE_/TEXT_
|
|
90
|
+
* comparisons, BETWEEN/NOT_BETWEEN, CONTAINS/NOT_CONTAINS, STARTS_WITH/ENDS_WITH,
|
|
91
|
+
* ONE_OF_LIST/ONE_OF_RANGE, BLANK/NOT_BLANK, BOOLEAN, TEXT_IS_EMAIL/TEXT_IS_URL.
|
|
92
|
+
* The calculator stores the per-cell boolean on
|
|
93
|
+
* `cellData.dataValidationResultById` and `SheetCell.validate()` reads
|
|
94
|
+
* `this.dataValidationResult` to decide validity.
|
|
95
|
+
*
|
|
96
|
+
* Returns `false` — leaving the rule on the sync path — for rules that aren't
|
|
97
|
+
* expressible as a single-cell formula:
|
|
98
|
+
* - Loop-state: NUMBER_*_PREVIOUS comparisons (compare to prior row).
|
|
99
|
+
* - Calendar-relative dates: DATE_TODAY / DATE_THIS_WEEK / DATE_THIS_MONTH
|
|
100
|
+
* and the surrounding family, plus DATE_IS_VALID.
|
|
101
|
+
* - Cell error state: ERROR / NOT_ERROR.
|
|
102
|
+
*
|
|
103
|
+
* Note (input-gating tradeoff): unlike conditional formatting, data validation
|
|
104
|
+
* decides whether a cell is flagged invalid. The formula path runs through the
|
|
105
|
+
* calculator, so a freshly-typed value spends one calc tick at
|
|
106
|
+
* `dataValidationResult === undefined`, during which `SheetCell.validate()`
|
|
107
|
+
* treats the cell as valid. Anything reading validity synchronously right
|
|
108
|
+
* after a write may see stale-valid for one tick.
|
|
109
|
+
*
|
|
110
|
+
* Consumers that must stay in lockstep with this predicate:
|
|
111
|
+
* - `createDataValidationRule` / update / delete (enqueueing into the calc).
|
|
112
|
+
* - `getDataValidationFormula` (builds the expression for the calculator).
|
|
113
|
+
* - `SheetCell.validate()` (chooses formula-result vs sync `checkBooleanRule`).
|
|
114
|
+
* - `use-on-filter-table` (uses the same predicate to decide its formula path).
|
|
115
|
+
*
|
|
116
|
+
* Delegating to `buildDataValidationExpression` keeps the predicate and the
|
|
117
|
+
* builder from drifting.
|
|
118
|
+
*/
|
|
119
|
+
var isDataValidationFormulaRule = function (rule) {
|
|
120
|
+
return Boolean((0, exports.buildDataValidationExpression)(rule, FORMULA_PREDICATE_BASE_CELL_REF));
|
|
121
|
+
};
|
|
122
|
+
exports.isDataValidationFormulaRule = isDataValidationFormulaRule;
|
|
123
|
+
var buildDataValidationExpression = function (rule, baseCellRef, locale) {
|
|
124
|
+
var _a, _b, _c;
|
|
125
|
+
var condition = rule === null || rule === void 0 ? void 0 : rule.condition;
|
|
126
|
+
if (!(condition === null || condition === void 0 ? void 0 : condition.type))
|
|
127
|
+
return undefined;
|
|
128
|
+
var values = (_a = condition.values) !== null && _a !== void 0 ? _a : [];
|
|
129
|
+
var fromRaw = (_b = values[0]) === null || _b === void 0 ? void 0 : _b.userEnteredValue;
|
|
130
|
+
var toRaw = (_c = values[1]) === null || _c === void 0 ? void 0 : _c.userEnteredValue;
|
|
131
|
+
if (condition.type === "CUSTOM_FORMULA") {
|
|
132
|
+
var raw = fromRaw !== null && fromRaw !== void 0 ? fromRaw : "";
|
|
133
|
+
var trimmed = raw.trim();
|
|
134
|
+
if (!trimmed)
|
|
135
|
+
return undefined;
|
|
136
|
+
return trimmed.startsWith("=") ? trimmed : "=".concat(trimmed);
|
|
137
|
+
}
|
|
138
|
+
var fromExpr = normalizeConditionValue(fromRaw, true, locale);
|
|
139
|
+
var toExpr = normalizeConditionValue(toRaw, true, locale);
|
|
140
|
+
if (!fromExpr && !toExpr)
|
|
141
|
+
return undefined;
|
|
142
|
+
var comparisonOperatorMap = {
|
|
143
|
+
NUMBER_EQ: "=",
|
|
144
|
+
NUMBER_NOT_EQ: "<>",
|
|
145
|
+
NUMBER_GREATER: ">",
|
|
146
|
+
NUMBER_GREATER_THAN_EQ: ">=",
|
|
147
|
+
NUMBER_LESS: "<",
|
|
148
|
+
NUMBER_LESS_THAN_EQ: "<=",
|
|
149
|
+
DATE_EQ: "=",
|
|
150
|
+
DATE_NOT_EQ: "<>",
|
|
151
|
+
DATE_BEFORE: "<",
|
|
152
|
+
DATE_AFTER: ">",
|
|
153
|
+
DATE_ON_OR_BEFORE: "<=",
|
|
154
|
+
DATE_ON_OR_AFTER: ">=",
|
|
155
|
+
TEXT_EQ: "=",
|
|
156
|
+
TEXT_NOT_EQ: "<>",
|
|
157
|
+
};
|
|
158
|
+
if (condition.type === "NUMBER_BETWEEN" ||
|
|
159
|
+
condition.type === "DATE_BETWEEN") {
|
|
160
|
+
if (!fromExpr || !toExpr)
|
|
161
|
+
return undefined;
|
|
162
|
+
return "=AND(".concat(baseCellRef, ">=").concat(fromExpr, ",").concat(baseCellRef, "<=").concat(toExpr, ")");
|
|
163
|
+
}
|
|
164
|
+
if (condition.type === "NUMBER_NOT_BETWEEN" ||
|
|
165
|
+
condition.type === "DATE_NOT_BETWEEN") {
|
|
166
|
+
if (!fromExpr || !toExpr)
|
|
167
|
+
return undefined;
|
|
168
|
+
return "=OR(".concat(baseCellRef, "<").concat(fromExpr, ",").concat(baseCellRef, ">").concat(toExpr, ")");
|
|
169
|
+
}
|
|
170
|
+
var comparisonOperator = comparisonOperatorMap[condition.type];
|
|
171
|
+
if (comparisonOperator) {
|
|
172
|
+
if (!fromExpr)
|
|
173
|
+
return undefined;
|
|
174
|
+
return "=".concat(baseCellRef).concat(comparisonOperator).concat(fromExpr);
|
|
175
|
+
}
|
|
176
|
+
if (condition.type === "TEXT_CONTAINS") {
|
|
177
|
+
if (!fromExpr)
|
|
178
|
+
return undefined;
|
|
179
|
+
return "=ISNUMBER(SEARCH(".concat(fromExpr, ",").concat(baseCellRef, "))");
|
|
180
|
+
}
|
|
181
|
+
if (condition.type === "TEXT_NOT_CONTAINS") {
|
|
182
|
+
if (!fromExpr)
|
|
183
|
+
return undefined;
|
|
184
|
+
return "=NOT(ISNUMBER(SEARCH(".concat(fromExpr, ",").concat(baseCellRef, ")))");
|
|
185
|
+
}
|
|
186
|
+
if (condition.type === "TEXT_STARTS_WITH") {
|
|
187
|
+
if (!fromExpr)
|
|
188
|
+
return undefined;
|
|
189
|
+
return "=LEFT(".concat(baseCellRef, ",LEN(").concat(fromExpr, "))=").concat(fromExpr);
|
|
190
|
+
}
|
|
191
|
+
if (condition.type === "TEXT_ENDS_WITH") {
|
|
192
|
+
if (!fromExpr)
|
|
193
|
+
return undefined;
|
|
194
|
+
return "=RIGHT(".concat(baseCellRef, ",LEN(").concat(fromExpr, "))=").concat(fromExpr);
|
|
195
|
+
}
|
|
196
|
+
if (condition.type === "TEXT_IS_EMAIL") {
|
|
197
|
+
return "=ISNUMBER(SEARCH(\"@\",".concat(baseCellRef, "))");
|
|
198
|
+
}
|
|
199
|
+
if (condition.type === "TEXT_IS_URL") {
|
|
200
|
+
return "=OR(LEFT(".concat(baseCellRef, ",7)=\"http://\",LEFT(").concat(baseCellRef, ",8)=\"https://\")");
|
|
201
|
+
}
|
|
202
|
+
if (condition.type === "ONE_OF_RANGE") {
|
|
203
|
+
if (!fromExpr)
|
|
204
|
+
return undefined;
|
|
205
|
+
if (rule === null || rule === void 0 ? void 0 : rule.allowBlank) {
|
|
206
|
+
return "=IF(".concat(baseCellRef, "=\"\",TRUE,COUNTIF(").concat(fromExpr, ",").concat(baseCellRef, ")>0)");
|
|
207
|
+
}
|
|
208
|
+
return "=COUNTIF(".concat(fromExpr, ",").concat(baseCellRef, ")>0");
|
|
209
|
+
}
|
|
210
|
+
if (condition.type === "ONE_OF_LIST") {
|
|
211
|
+
var entries = fromRaw
|
|
212
|
+
? fromRaw.split(",").map(function (entry) { return entry.trim(); })
|
|
213
|
+
: [];
|
|
214
|
+
// A leading apostrophe is Excel's text prefix — `'000s` is the text
|
|
215
|
+
// "000s" and a bare `'` is empty text — never part of the value.
|
|
216
|
+
var conditions = entries
|
|
217
|
+
.flatMap(function (entry) {
|
|
218
|
+
if (entry.startsWith("'")) {
|
|
219
|
+
var stripped = (0, exports.stripDataValidationTextPrefix)(entry);
|
|
220
|
+
var textCondition = "".concat(baseCellRef, "=\"").concat(escapeFormulaText(stripped), "\"");
|
|
221
|
+
// The bare `'` entry means empty text, and a cell storing the lone
|
|
222
|
+
// text-prefix apostrophe is equivalent to a blank cell — accept it too.
|
|
223
|
+
return stripped === ""
|
|
224
|
+
? [textCondition, "".concat(baseCellRef, "=\"'\"")]
|
|
225
|
+
: [textCondition];
|
|
226
|
+
}
|
|
227
|
+
return entry
|
|
228
|
+
? ["".concat(baseCellRef, "=").concat(normalizeConditionValue(entry, true, locale))]
|
|
229
|
+
: [];
|
|
230
|
+
})
|
|
231
|
+
.filter(function (entry) { return Boolean(entry); });
|
|
232
|
+
if (!conditions.length)
|
|
233
|
+
return undefined;
|
|
234
|
+
return conditions.length === 1
|
|
235
|
+
? "=".concat(conditions[0])
|
|
236
|
+
: "=OR(".concat(conditions.join(","), ")");
|
|
237
|
+
}
|
|
238
|
+
if (condition.type === "BLANK") {
|
|
239
|
+
return "=".concat(baseCellRef, "=\"\"");
|
|
240
|
+
}
|
|
241
|
+
if (condition.type === "NOT_BLANK") {
|
|
242
|
+
return "=".concat(baseCellRef, "<>\"\"");
|
|
243
|
+
}
|
|
244
|
+
if (condition.type === "BOOLEAN") {
|
|
245
|
+
return "=OR(".concat(baseCellRef, "=TRUE,").concat(baseCellRef, "=FALSE)");
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
};
|
|
249
|
+
exports.buildDataValidationExpression = buildDataValidationExpression;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type DateSerialMode = "local" | "excel-utc";
|
|
2
|
+
/**
|
|
3
|
+
* Convert date to serial with explicit time zone behavior.
|
|
4
|
+
* - local: interpret date components in local time (UI entry behavior).
|
|
5
|
+
* - excel-utc: treat Date as UTC (formula engine behavior).
|
|
6
|
+
*/
|
|
7
|
+
export declare const toSerialWithMode: (date: Date, use1904?: boolean, mode?: DateSerialMode) => number;
|
|
8
|
+
export declare const toSerial: (date: Date, use1904?: boolean) => number;
|
|
9
|
+
/**
|
|
10
|
+
* Convert serial to date
|
|
11
|
+
* @param serial
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
14
|
+
export declare const toDate: (serial: number, use1904?: boolean) => Date;
|
|
15
|
+
//# sourceMappingURL=date.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../date.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,WAAW,CAAC;AAEnD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAM,IAAI,EACV,iBAAe,EACf,OAAM,cAAwB,WAwB/B,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,IAAI,EAAE,iBAAe,WAEnD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,QAAQ,MAAM,EAAE,iBAAe,SA+BrD,CAAC"}
|