@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.
Files changed (154) hide show
  1. package/LICENSE.md +100 -0
  2. package/dist/cjs/array.d.ts +3 -0
  3. package/dist/cjs/array.d.ts.map +1 -0
  4. package/dist/cjs/array.js +19 -0
  5. package/dist/cjs/cell-data-helpers.d.ts +19 -0
  6. package/dist/cjs/cell-data-helpers.d.ts.map +1 -0
  7. package/dist/cjs/cell-data-helpers.js +64 -0
  8. package/dist/cjs/color.d.ts +20 -0
  9. package/dist/cjs/color.d.ts.map +1 -0
  10. package/dist/cjs/color.js +168 -0
  11. package/dist/cjs/colors.d.ts +2 -0
  12. package/dist/cjs/colors.d.ts.map +1 -0
  13. package/dist/cjs/colors.js +78 -0
  14. package/dist/cjs/conditional-format.d.ts +49 -0
  15. package/dist/cjs/conditional-format.d.ts.map +1 -0
  16. package/dist/cjs/conditional-format.js +227 -0
  17. package/dist/cjs/content-hash.d.ts +16 -0
  18. package/dist/cjs/content-hash.d.ts.map +1 -0
  19. package/dist/cjs/content-hash.js +72 -0
  20. package/dist/cjs/data-validation.d.ts +54 -0
  21. package/dist/cjs/data-validation.d.ts.map +1 -0
  22. package/dist/cjs/data-validation.js +249 -0
  23. package/dist/cjs/date.d.ts +15 -0
  24. package/dist/cjs/date.d.ts.map +1 -0
  25. package/dist/cjs/date.js +56 -0
  26. package/dist/cjs/dom.d.ts +10 -0
  27. package/dist/cjs/dom.d.ts.map +1 -0
  28. package/dist/cjs/dom.js +97 -0
  29. package/dist/cjs/formatter.d.ts +22 -0
  30. package/dist/cjs/formatter.d.ts.map +1 -0
  31. package/dist/cjs/formatter.js +264 -0
  32. package/dist/cjs/formula-refs.d.ts +7 -0
  33. package/dist/cjs/formula-refs.d.ts.map +1 -0
  34. package/dist/cjs/formula-refs.js +54 -0
  35. package/dist/cjs/formula.d.ts +80 -0
  36. package/dist/cjs/formula.d.ts.map +1 -0
  37. package/dist/cjs/formula.js +401 -0
  38. package/dist/cjs/index.d.ts +24 -0
  39. package/dist/cjs/index.d.ts.map +1 -0
  40. package/dist/cjs/index.js +26 -0
  41. package/dist/cjs/locale.d.ts +34 -0
  42. package/dist/cjs/locale.d.ts.map +1 -0
  43. package/dist/cjs/locale.js +260 -0
  44. package/dist/cjs/log.d.ts +4 -0
  45. package/dist/cjs/log.d.ts.map +1 -0
  46. package/dist/cjs/log.js +28 -0
  47. package/dist/cjs/memoize.d.ts +18 -0
  48. package/dist/cjs/memoize.d.ts.map +1 -0
  49. package/dist/cjs/memoize.js +128 -0
  50. package/dist/cjs/numbers.d.ts +19 -0
  51. package/dist/cjs/numbers.d.ts.map +1 -0
  52. package/dist/cjs/numbers.js +47 -0
  53. package/dist/cjs/selection.d.ts +88 -0
  54. package/dist/cjs/selection.d.ts.map +1 -0
  55. package/dist/cjs/selection.js +368 -0
  56. package/dist/cjs/shared-string-canonical.d.ts +18 -0
  57. package/dist/cjs/shared-string-canonical.d.ts.map +1 -0
  58. package/dist/cjs/shared-string-canonical.js +74 -0
  59. package/dist/cjs/sort.d.ts +15 -0
  60. package/dist/cjs/sort.d.ts.map +1 -0
  61. package/dist/cjs/sort.js +44 -0
  62. package/dist/cjs/string.d.ts +74 -0
  63. package/dist/cjs/string.d.ts.map +1 -0
  64. package/dist/cjs/string.js +237 -0
  65. package/dist/cjs/table-styles.d.ts +56 -0
  66. package/dist/cjs/table-styles.d.ts.map +1 -0
  67. package/dist/cjs/table-styles.js +362 -0
  68. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
  69. package/dist/cjs/typeguards.d.ts +4 -0
  70. package/dist/cjs/typeguards.d.ts.map +1 -0
  71. package/dist/cjs/typeguards.js +15 -0
  72. package/dist/cjs/uuid.d.ts +8 -0
  73. package/dist/cjs/uuid.d.ts.map +1 -0
  74. package/dist/cjs/uuid.js +30 -0
  75. package/dist/cjs/vitest.config.d.ts +3 -0
  76. package/dist/cjs/vitest.config.d.ts.map +1 -0
  77. package/dist/cjs/vitest.config.js +9 -0
  78. package/dist/esm/array.d.ts +3 -0
  79. package/dist/esm/array.d.ts.map +1 -0
  80. package/dist/esm/array.js +14 -0
  81. package/dist/esm/cell-data-helpers.d.ts +19 -0
  82. package/dist/esm/cell-data-helpers.d.ts.map +1 -0
  83. package/dist/esm/cell-data-helpers.js +39 -0
  84. package/dist/esm/color.d.ts +20 -0
  85. package/dist/esm/color.d.ts.map +1 -0
  86. package/dist/esm/color.js +152 -0
  87. package/dist/esm/colors.d.ts +2 -0
  88. package/dist/esm/colors.d.ts.map +1 -0
  89. package/dist/esm/colors.js +75 -0
  90. package/dist/esm/conditional-format.d.ts +49 -0
  91. package/dist/esm/conditional-format.d.ts.map +1 -0
  92. package/dist/esm/conditional-format.js +219 -0
  93. package/dist/esm/content-hash.d.ts +16 -0
  94. package/dist/esm/content-hash.d.ts.map +1 -0
  95. package/dist/esm/content-hash.js +68 -0
  96. package/dist/esm/data-validation.d.ts +54 -0
  97. package/dist/esm/data-validation.d.ts.map +1 -0
  98. package/dist/esm/data-validation.js +237 -0
  99. package/dist/esm/date.d.ts +15 -0
  100. package/dist/esm/date.d.ts.map +1 -0
  101. package/dist/esm/date.js +46 -0
  102. package/dist/esm/dom.d.ts +10 -0
  103. package/dist/esm/dom.d.ts.map +1 -0
  104. package/dist/esm/dom.js +90 -0
  105. package/dist/esm/formatter.d.ts +22 -0
  106. package/dist/esm/formatter.d.ts.map +1 -0
  107. package/dist/esm/formatter.js +253 -0
  108. package/dist/esm/formula-refs.d.ts +7 -0
  109. package/dist/esm/formula-refs.d.ts.map +1 -0
  110. package/dist/esm/formula-refs.js +49 -0
  111. package/dist/esm/formula.d.ts +80 -0
  112. package/dist/esm/formula.d.ts.map +1 -0
  113. package/dist/esm/formula.js +298 -0
  114. package/dist/esm/index.d.ts +24 -0
  115. package/dist/esm/index.d.ts.map +1 -0
  116. package/dist/esm/index.js +23 -0
  117. package/dist/esm/locale.d.ts +34 -0
  118. package/dist/esm/locale.d.ts.map +1 -0
  119. package/dist/esm/locale.js +223 -0
  120. package/dist/esm/log.d.ts +4 -0
  121. package/dist/esm/log.d.ts.map +1 -0
  122. package/dist/esm/log.js +3 -0
  123. package/dist/esm/memoize.d.ts +18 -0
  124. package/dist/esm/memoize.d.ts.map +1 -0
  125. package/dist/esm/memoize.js +95 -0
  126. package/dist/esm/numbers.d.ts +19 -0
  127. package/dist/esm/numbers.d.ts.map +1 -0
  128. package/dist/esm/numbers.js +40 -0
  129. package/dist/esm/selection.d.ts +88 -0
  130. package/dist/esm/selection.d.ts.map +1 -0
  131. package/dist/esm/selection.js +311 -0
  132. package/dist/esm/shared-string-canonical.d.ts +18 -0
  133. package/dist/esm/shared-string-canonical.d.ts.map +1 -0
  134. package/dist/esm/shared-string-canonical.js +68 -0
  135. package/dist/esm/sort.d.ts +15 -0
  136. package/dist/esm/sort.d.ts.map +1 -0
  137. package/dist/esm/sort.js +39 -0
  138. package/dist/esm/string.d.ts +74 -0
  139. package/dist/esm/string.d.ts.map +1 -0
  140. package/dist/esm/string.js +213 -0
  141. package/dist/esm/table-styles.d.ts +56 -0
  142. package/dist/esm/table-styles.d.ts.map +1 -0
  143. package/dist/esm/table-styles.js +367 -0
  144. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
  145. package/dist/esm/typeguards.d.ts +4 -0
  146. package/dist/esm/typeguards.d.ts.map +1 -0
  147. package/dist/esm/typeguards.js +7 -0
  148. package/dist/esm/uuid.d.ts +8 -0
  149. package/dist/esm/uuid.d.ts.map +1 -0
  150. package/dist/esm/uuid.js +25 -0
  151. package/dist/esm/vitest.config.d.ts +3 -0
  152. package/dist/esm/vitest.config.d.ts.map +1 -0
  153. package/dist/esm/vitest.config.js +7 -0
  154. package/package.json +49 -0
@@ -0,0 +1,298 @@
1
+ import { checkIfStringIsReference } from "./selection";
2
+ /* Close parenthesis mapping */
3
+ const inverseParenMap = {
4
+ "(": ")",
5
+ "[": "]",
6
+ };
7
+ /* Check if a str is function parenthesis */
8
+ const isParen = (char) => "()[]".indexOf(char) !== -1;
9
+ /* Check if a str is open parenthesis */
10
+ const isOpenParen = (char) => "(" === char || "[" === char;
11
+ /**
12
+ * Add/remove parenthesis in formulas
13
+ * @param str
14
+ * @returns
15
+ */
16
+ export const balanceParentheses = (str) => {
17
+ const chars = str.split("");
18
+ const stack = [];
19
+ for (let i = 0; i < chars.length; i++) {
20
+ const char = chars[i];
21
+ if (isParen(char)) {
22
+ if (isOpenParen(char)) {
23
+ stack.push(char);
24
+ }
25
+ else {
26
+ if (stack.length !== 0)
27
+ stack.pop();
28
+ }
29
+ }
30
+ }
31
+ return str + stack.map((item) => inverseParenMap[item]).join("");
32
+ };
33
+ /**
34
+ * Add closing quotes in formulas when a string is unterminated.
35
+ * Handles escaped quotes ("") inside strings.
36
+ */
37
+ export const balanceQuotes = (str) => {
38
+ let inString = false;
39
+ let openScopeCount = 0;
40
+ let stringStartScopeCount = 0;
41
+ for (let i = 0; i < str.length; i++) {
42
+ const char = str[i];
43
+ if (char === '"') {
44
+ if (inString && str[i + 1] === '"') {
45
+ i += 1;
46
+ continue;
47
+ }
48
+ inString = !inString;
49
+ if (inString) {
50
+ stringStartScopeCount = openScopeCount;
51
+ }
52
+ continue;
53
+ }
54
+ if (!inString) {
55
+ if (isOpenParen(char)) {
56
+ openScopeCount++;
57
+ }
58
+ else if (char === ")" || char === "]") {
59
+ if (openScopeCount > 0)
60
+ openScopeCount--;
61
+ }
62
+ }
63
+ }
64
+ if (!inString) {
65
+ return str;
66
+ }
67
+ let insertIndex = str.length;
68
+ while (insertIndex > 0 && /\s/.test(str[insertIndex - 1])) {
69
+ insertIndex -= 1;
70
+ }
71
+ let closeStart = insertIndex;
72
+ while (closeStart > 0) {
73
+ const char = str[closeStart - 1];
74
+ if (char !== ")" && char !== "]")
75
+ break;
76
+ closeStart -= 1;
77
+ }
78
+ const trailingParensCount = insertIndex - closeStart;
79
+ const parensToKeepOutside = Math.min(stringStartScopeCount, trailingParensCount);
80
+ const insertionPos = insertIndex - parensToKeepOutside;
81
+ return `${str.slice(0, insertionPos)}"${str.slice(insertionPos)}`;
82
+ };
83
+ /**
84
+ * Add/remove parenthesis and add closing quotes in formulas.
85
+ */
86
+ export const balanceFormula = (str) => {
87
+ return balanceParentheses(str);
88
+ };
89
+ /**
90
+ * Check if parenthesis is balanced
91
+ * @param str
92
+ * @returns
93
+ */
94
+ export const isBalancedParenthesis = (str) => {
95
+ return balanceParentheses(str).length === str.length;
96
+ };
97
+ export const looksLikeMarkdown = (text) => {
98
+ const normalizedText = text.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
99
+ const trimmedStart = normalizedText.trimStart();
100
+ if (!trimmedStart) {
101
+ return false;
102
+ }
103
+ const firstLine = trimmedStart.split("\n", 1)[0];
104
+ // Text starting with `---` is considered markdown (horizontal rule or front matter)
105
+ if (firstLine.startsWith("--")) {
106
+ return true;
107
+ }
108
+ // Front matter: starts with --- and another --- appears within a few lines.
109
+ if (/^---\s*$/.test(firstLine)) {
110
+ const lines = trimmedStart.split("\n").slice(1, 20);
111
+ if (lines.some((line) => /^---\s*$/.test(line))) {
112
+ return true;
113
+ }
114
+ // Heuristic: if a front-matter-like start is followed by markdown markers,
115
+ // treat it as markdown even without a closing ---.
116
+ if (lines.some((line) => /^(#{1,6}\s|>\s|[-*]\s|\d+\.\s|\|)/.test(line.trimStart()))) {
117
+ return true;
118
+ }
119
+ }
120
+ // Common markdown markers.
121
+ if (/^(#{1,6}\s|>\s|[-*]\s|\d+\.\s|\|)/.test(trimmedStart)) {
122
+ return true;
123
+ }
124
+ // Horizontal rule only (line with ---/***/___).
125
+ return /^(?:---|\*\*\*|___)\s*$/.test(firstLine);
126
+ };
127
+ let defaultEnableAtFormula = false;
128
+ export const setDefaultEnableAtFormula = (enable) => {
129
+ defaultEnableAtFormula = enable ?? false;
130
+ };
131
+ /**
132
+ * Check if text is a formula
133
+ * @param text
134
+ * @returns
135
+ */
136
+ export const isAFormula = (text) => {
137
+ const textStr = String(text);
138
+ if (looksLikeMarkdown(textStr)) {
139
+ return false;
140
+ }
141
+ // A formula's expression can never legitimately begin with a second "="
142
+ // right after the leading one — neither Excel nor Google Sheets has a token
143
+ // that starts an expression with "=". So "==", "===", "=== heading ===" and
144
+ // similar are decorative/label text, not a (broken) formula. Treat them as
145
+ // plain text instead of routing them through the calculator.
146
+ if (textStr.startsWith("==")) {
147
+ return false;
148
+ }
149
+ return !!textStr.startsWith("=") || isAlternateFormula(textStr);
150
+ };
151
+ /* Quoted string literal, e.g. `"North Region"` (with `""` escapes). */
152
+ const STRING_LITERAL_ONLY = /^"(?:[^"]|"")*"$/;
153
+ /* Numeric literal, e.g. `42`, `-1.5`, `.5`, `1e3`, `12%`. */
154
+ const NUMERIC_LITERAL_ONLY = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?%?$/;
155
+ /* Boolean literal. */
156
+ const BOOLEAN_LITERAL_ONLY = /^(?:TRUE|FALSE)$/i;
157
+ /* Formula operators that can only appear in an expression that needs
158
+ * the calculator (scanned outside quoted segments). */
159
+ const FORMULA_OPERATOR = /[+\-*/^&<>=]/;
160
+ /**
161
+ * Check if text is a formula-shaped expression stored WITHOUT the
162
+ * leading "=". OOXML defined names (`<definedName>` in workbook.xml)
163
+ * store their expressions bare — `OFFSET(Sheet1!$A$1,0,0,10)` rather
164
+ * than `=OFFSET(...)` — so `isAFormula` can't classify them.
165
+ *
166
+ * Returns false for anything that already resolves as a static value
167
+ * or plain reference today: quoted string / numeric / boolean
168
+ * literals, single-area range references (including external-workbook
169
+ * references like `'[1]Revenue'!$A$17`), and broken `#REF!`
170
+ * expressions. Returns true only for expressions that need the
171
+ * calculator: function calls and operator expressions.
172
+ * @param text
173
+ * @returns
174
+ */
175
+ export const isBareFormulaExpression = (text) => {
176
+ if (typeof text !== "string") {
177
+ return false;
178
+ }
179
+ const trimmed = text.trim();
180
+ if (!trimmed || trimmed.startsWith("=")) {
181
+ return false;
182
+ }
183
+ if (trimmed.includes("#REF!")) {
184
+ return false;
185
+ }
186
+ if (STRING_LITERAL_ONLY.test(trimmed) ||
187
+ NUMERIC_LITERAL_ONLY.test(trimmed) ||
188
+ BOOLEAN_LITERAL_ONLY.test(trimmed)) {
189
+ return false;
190
+ }
191
+ if (checkIfStringIsReference(trimmed)) {
192
+ return false;
193
+ }
194
+ // Function call or parenthesized sub-expression.
195
+ if (trimmed.includes("(")) {
196
+ return true;
197
+ }
198
+ // Operator expression like `Sheet1!$A$1*2`. Strip string literals
199
+ // and quoted sheet names first so `'My-Sheet'!$A$1` isn't misread
200
+ // as subtraction.
201
+ const scannable = trimmed
202
+ .replace(/"(?:[^"]|"")*"/g, "")
203
+ .replace(/'[^']*'/g, "");
204
+ return FORMULA_OPERATOR.test(scannable);
205
+ };
206
+ // Pre-compiled `\p{Sc}` (Unicode currency-symbol category) check. Used to
207
+ // distinguish currency input like `-$100` / `+€50` from formula-prefixed
208
+ // input like `-A1` / `+SUM(A1:B2)`. Wrapped in try/catch because some
209
+ // older JS engines don't support Unicode property escapes.
210
+ const CURRENCY_SYMBOL_START = (() => {
211
+ try {
212
+ return new RegExp("^\\p{Sc}", "u");
213
+ }
214
+ catch {
215
+ return /^[£$€¥₹₽₩₪₫₴₸₺₼₾₿]/;
216
+ }
217
+ })();
218
+ // Check if its alternate formula
219
+ // ie the text starts with +
220
+ export const isAlternateFormula = (text) => {
221
+ // @ can be treated as formula in Excel (even if invalid), but is disabled by default here
222
+ if (text.startsWith("@"))
223
+ return defaultEnableAtFormula;
224
+ if (!text.startsWith("+") && !text.startsWith("-"))
225
+ return false;
226
+ // Remove the leading + and check if what remains is just a number
227
+ const afterSign = text.slice(1).trim();
228
+ // If it's empty or just a number literal, it's not a formula
229
+ // Also check with common thousands separators removed (comma and period)
230
+ // to handle locale-formatted numbers like "1,234" or "1.234"
231
+ if (!afterSign || !isNaN(Number(afterSign))) {
232
+ return false;
233
+ }
234
+ // Excel parity: a leading sign followed by a currency symbol is the start
235
+ // of a (possibly in-progress) currency negative / positive — `-$100`,
236
+ // `+€50`, `-£1,234.56`. Excel treats these as currency, not formulas,
237
+ // even mid-type (the bare `-$` shows as text until digits are added).
238
+ if (CURRENCY_SYMBOL_START.test(afterSign)) {
239
+ return false;
240
+ }
241
+ // Check for numbers with thousands separators (e.g., "1,234" or "1.234,56")
242
+ // Strip commas and periods that look like thousands separators
243
+ const withoutThousandsSeparators = afterSign.replace(/[,.]/g, "");
244
+ if (/^\d+$/.test(withoutThousandsSeparators)) {
245
+ return false;
246
+ }
247
+ // If there is whitespace with no letters, treat it as text unless it uses
248
+ // non-hyphen operators (phone numbers like "+1 224-535-1764").
249
+ const hasLetters = /[A-Za-z]/.test(afterSign);
250
+ if (/\s/.test(afterSign) && !hasLetters) {
251
+ return /[+*/^=<>]/.test(afterSign);
252
+ }
253
+ // Otherwise, it's a formula like +A1, +SUM(A1:B2), +1-2, etc.
254
+ return true;
255
+ };
256
+ // Check if its a valid formula
257
+ export const isValidFormula = (text) => {
258
+ const str = String(text);
259
+ return str[0] === "=" && str.length > 1;
260
+ };
261
+ /**
262
+ * Check if a key is an Arrow key
263
+ * @param code
264
+ * @returns
265
+ */
266
+ export const isAnArrowKey = (code) => code.startsWith("Arrow");
267
+ /**
268
+ * Iterate over all cells in a bound
269
+ * @param bounds
270
+ * @param callback
271
+ */
272
+ export const iterateCells = function* (top, right, bottom, left) {
273
+ for (let rowIndex = top; rowIndex <= bottom; rowIndex++) {
274
+ const rightBound = typeof right === "function" ? right(rowIndex) : right;
275
+ for (let columnIndex = left; columnIndex <= rightBound; columnIndex++) {
276
+ yield [rowIndex, columnIndex];
277
+ }
278
+ }
279
+ };
280
+ export function* combineIterators(...generators) {
281
+ for (const generator of generators) {
282
+ if (generator)
283
+ yield* generator;
284
+ }
285
+ }
286
+ export const iterateCellsInRange = (range) => {
287
+ return iterateCells(range.startRowIndex, range.endColumnIndex, range.endRowIndex, range.startColumnIndex);
288
+ };
289
+ /**
290
+ * Iterate all cells in multiple selections
291
+ * @param selections
292
+ * @returns
293
+ */
294
+ export const iterateSelections = function* (selections) {
295
+ for (const selection of selections) {
296
+ yield iterateCellsInRange(selection.range);
297
+ }
298
+ };
@@ -0,0 +1,24 @@
1
+ export * from "./numbers";
2
+ export * from "./uuid";
3
+ export * from "./typeguards";
4
+ export * from "./string";
5
+ export * from "./formula";
6
+ export * from "./color";
7
+ export * from "./dom";
8
+ export * from "./locale";
9
+ export * from "./date";
10
+ export * from "./log";
11
+ export * from "./array";
12
+ export * from "./formatter";
13
+ export * from "./memoize";
14
+ export * from "./cell-data-helpers";
15
+ export * from "./selection";
16
+ export * from "./colors";
17
+ export * from "./formula-refs";
18
+ export * from "./sort";
19
+ export * from "./table-styles";
20
+ export * from "./conditional-format";
21
+ export * from "./data-validation";
22
+ export * from "./content-hash";
23
+ export * from "./shared-string-canonical";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,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"}
@@ -0,0 +1,23 @@
1
+ export * from "./numbers";
2
+ export * from "./uuid";
3
+ export * from "./typeguards";
4
+ export * from "./string";
5
+ export * from "./formula";
6
+ export * from "./color";
7
+ export * from "./dom";
8
+ export * from "./locale";
9
+ export * from "./date";
10
+ export * from "./log";
11
+ export * from "./array";
12
+ export * from "./formatter";
13
+ export * from "./memoize";
14
+ export * from "./cell-data-helpers";
15
+ export * from "./selection";
16
+ export * from "./colors";
17
+ export * from "./formula-refs";
18
+ export * from "./sort";
19
+ export * from "./table-styles";
20
+ export * from "./conditional-format";
21
+ export * from "./data-validation";
22
+ export * from "./content-hash";
23
+ export * from "./shared-string-canonical";
@@ -0,0 +1,34 @@
1
+ export declare const getCurrentLocale: () => string;
2
+ export type DatePartType = "month" | "day" | "year";
3
+ /**
4
+ * PARSER metadata only: relative day/month order for a locale, with the
5
+ * year deliberately moved last so the date-parser's regex can use fixed
6
+ * capture-group positions. This ordering is WRONG for display in
7
+ * year-first locales (ja-JP, hu-HU, sv-SE, …) — formatting helpers must
8
+ * use the `formatToParts`-driven builders below instead.
9
+ */
10
+ export declare const getLocaleDatePartsNotMemoized: (locale: string) => [DatePartType, string, string][];
11
+ export declare const getLocaleDateParts: (locale: string) => [DatePartType, string, string][];
12
+ /**
13
+ * Default numeric (short) date pattern for a locale, preserving the
14
+ * locale's field order, separators, literal text and zero-padding as
15
+ * reported by `Intl.DateTimeFormat(...).formatToParts()` — e.g.
16
+ * de-DE → `d.m.yyyy`, hu-HU → `yyyy. mm. dd.`, en-CA → `yyyy-mm-dd`.
17
+ */
18
+ export declare const getDefaultDateFormat: (locale?: string) => string;
19
+ /**
20
+ * Default time pattern for a locale, honoring its 12/24-hour convention,
21
+ * separators and hour padding — e.g. en-US → `h:mm:ss AM/PM`,
22
+ * de-DE → `hh:mm:ss`, fi-FI → `h.mm.ss`.
23
+ */
24
+ export declare const getDefaultTimeFormat: (locale?: string, precision?: "minute" | "second") => string;
25
+ export declare const getDefaultDateTimeFormat: (locale?: string) => string;
26
+ /**
27
+ * Long (full) date pattern for a locale, following the locale's own
28
+ * field placement and literal text — e.g. de-DE →
29
+ * `[$-de-DE]dddd, d. mmmm yyyy`, ja-JP → `[$-ja-JP]yyyy"年"m"月"d"日"dddd`.
30
+ * Localized weekday/month names are best-effort: they render through the
31
+ * spreadsheet formatter's own locale data via the `[$-locale]` tag.
32
+ */
33
+ export declare const getLongDateFormat: (locale?: string) => string;
34
+ //# sourceMappingURL=locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../locale.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,cAU5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,MAAM,qCAkC3D,CAAC;AAEF,eAAO,MAAM,kBAAkB,sDAAyC,CAAC;AAqFzE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAQ,MAA2B,WAClC,CAAC;AAuCvC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAQ,MAA2B,EACnC,YAAW,QAAQ,GAAG,QAAmB,WACS,CAAC;AAErD,eAAO,MAAM,wBAAwB,GAAI,SAAS,MAAM,WAEvD,CAAC;AA2CF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAQ,MAA2B,WAClC,CAAC"}
@@ -0,0 +1,223 @@
1
+ import { memoize } from "./memoize";
2
+ let defaultLocale;
3
+ export const getCurrentLocale = () => {
4
+ if (defaultLocale) {
5
+ return defaultLocale;
6
+ }
7
+ try {
8
+ defaultLocale = Intl.DateTimeFormat().resolvedOptions().locale;
9
+ }
10
+ catch (err) {
11
+ defaultLocale = "en-US";
12
+ }
13
+ return defaultLocale;
14
+ };
15
+ /**
16
+ * PARSER metadata only: relative day/month order for a locale, with the
17
+ * year deliberately moved last so the date-parser's regex can use fixed
18
+ * capture-group positions. This ordering is WRONG for display in
19
+ * year-first locales (ja-JP, hu-HU, sv-SE, …) — formatting helpers must
20
+ * use the `formatToParts`-driven builders below instead.
21
+ */
22
+ export const getLocaleDatePartsNotMemoized = (locale) => {
23
+ let parts = [];
24
+ try {
25
+ const formatObj = new Intl.DateTimeFormat(locale).formatToParts(new Date());
26
+ for (const part of formatObj) {
27
+ if (part.type === "day") {
28
+ parts.push(["day", "d", "dd"]);
29
+ }
30
+ if (part.type === "month") {
31
+ parts.push(["month", "m", "mm"]);
32
+ }
33
+ if (part.type === "year") {
34
+ parts.push(["year", "yy", "yyyy"]);
35
+ }
36
+ }
37
+ // Always move year to last to prevent
38
+ // errors in Regex (duplicate capture group)
39
+ parts.sort((_, [typeB]) => {
40
+ if (typeB === "year") {
41
+ return -1;
42
+ }
43
+ return 1;
44
+ });
45
+ }
46
+ catch (err) {
47
+ // en-US
48
+ parts = [
49
+ ["month", "m", "mm"],
50
+ ["day", "d", "dd"],
51
+ ["year", "yy", "yyyy"],
52
+ ];
53
+ }
54
+ return parts;
55
+ };
56
+ export const getLocaleDateParts = memoize(getLocaleDatePartsNotMemoized);
57
+ /**
58
+ * Fixed sample instant used to discover a locale's date/time pattern via
59
+ * `formatToParts`. Every numeric field is single-digit (5 April 2008,
60
+ * 05:09:07 UTC) so locale padding is observable: a locale that renders
61
+ * "05" for the day pads days, one that renders "5" does not.
62
+ */
63
+ const FORMAT_SAMPLE_DATE = new Date(Date.UTC(2008, 3, 5, 5, 9, 7));
64
+ /**
65
+ * Literal characters that spreadsheet number-format syntax accepts
66
+ * unquoted inside a date/time section. Anything else (words such as
67
+ * "г.", CJK markers such as 年/月/日, bidi controls, commas — which
68
+ * numfmt/Excel read as the thousands/scaling operator, e.g. hu-HU's
69
+ * `.,` literal — …) must be wrapped in double quotes.
70
+ */
71
+ const SAFE_PATTERN_LITERAL = /^[\s./:-]*$/;
72
+ const escapePatternLiteral = (value) => {
73
+ if (SAFE_PATTERN_LITERAL.test(value)) {
74
+ return value;
75
+ }
76
+ // Number-format strings cannot represent an embedded double quote —
77
+ // no locale literal contains one, so dropping it is safe.
78
+ return `"${value.replace(/"/g, "")}"`;
79
+ };
80
+ const numericFieldToken = (short, long, partValue) => (partValue.length === 2 ? long : short);
81
+ const dateFieldToken = (part) => {
82
+ switch (part.type) {
83
+ case "day":
84
+ return numericFieldToken("d", "dd", part.value);
85
+ case "month":
86
+ return numericFieldToken("m", "mm", part.value);
87
+ case "year":
88
+ return part.value.length === 2 ? "yy" : "yyyy";
89
+ case "literal":
90
+ return escapePatternLiteral(part.value);
91
+ default:
92
+ // era and other exotic fields have no spreadsheet token — skip.
93
+ return undefined;
94
+ }
95
+ };
96
+ const buildPatternFromParts = (parts, toToken) => {
97
+ let pattern = "";
98
+ for (const part of parts) {
99
+ const token = toToken(part);
100
+ if (token !== undefined) {
101
+ pattern += token;
102
+ }
103
+ }
104
+ // A locale's pattern may end (or begin) with a literal that belongs to
105
+ // trailing fields we skipped (e.g. an era) — trim stray whitespace.
106
+ return pattern.trim();
107
+ };
108
+ const getDefaultDateFormatMemoized = memoize((locale) => {
109
+ try {
110
+ const parts = new Intl.DateTimeFormat(locale, {
111
+ calendar: "gregory",
112
+ numberingSystem: "latn",
113
+ year: "numeric",
114
+ month: "numeric",
115
+ day: "numeric",
116
+ timeZone: "UTC",
117
+ }).formatToParts(FORMAT_SAMPLE_DATE);
118
+ const pattern = buildPatternFromParts(parts, dateFieldToken);
119
+ return pattern || "m/d/yyyy";
120
+ }
121
+ catch (err) {
122
+ return "m/d/yyyy";
123
+ }
124
+ });
125
+ /**
126
+ * Default numeric (short) date pattern for a locale, preserving the
127
+ * locale's field order, separators, literal text and zero-padding as
128
+ * reported by `Intl.DateTimeFormat(...).formatToParts()` — e.g.
129
+ * de-DE → `d.m.yyyy`, hu-HU → `yyyy. mm. dd.`, en-CA → `yyyy-mm-dd`.
130
+ */
131
+ export const getDefaultDateFormat = (locale = getCurrentLocale()) => getDefaultDateFormatMemoized(locale);
132
+ const getDefaultTimeFormatMemoized = memoize((locale, precision) => {
133
+ try {
134
+ const parts = new Intl.DateTimeFormat(locale, {
135
+ numberingSystem: "latn",
136
+ hour: "numeric",
137
+ minute: "numeric",
138
+ ...(precision === "second" ? { second: "numeric" } : {}),
139
+ timeZone: "UTC",
140
+ }).formatToParts(FORMAT_SAMPLE_DATE);
141
+ const pattern = buildPatternFromParts(parts, (part) => {
142
+ switch (part.type) {
143
+ case "hour":
144
+ return numericFieldToken("h", "hh", part.value);
145
+ case "minute":
146
+ return "mm";
147
+ case "second":
148
+ return "ss";
149
+ case "dayPeriod":
150
+ // Spreadsheet syntax only has the English AM/PM token; its
151
+ // presence is what switches the hour field to 12-hour mode.
152
+ return "AM/PM";
153
+ case "literal":
154
+ return escapePatternLiteral(part.value);
155
+ default:
156
+ return undefined;
157
+ }
158
+ });
159
+ const fallback = precision === "second" ? "h:mm:ss AM/PM" : "h:mm AM/PM";
160
+ return pattern || fallback;
161
+ }
162
+ catch (err) {
163
+ return precision === "second" ? "h:mm:ss AM/PM" : "h:mm AM/PM";
164
+ }
165
+ });
166
+ /**
167
+ * Default time pattern for a locale, honoring its 12/24-hour convention,
168
+ * separators and hour padding — e.g. en-US → `h:mm:ss AM/PM`,
169
+ * de-DE → `hh:mm:ss`, fi-FI → `h.mm.ss`.
170
+ */
171
+ export const getDefaultTimeFormat = (locale = getCurrentLocale(), precision = "second") => getDefaultTimeFormatMemoized(locale, precision);
172
+ export const getDefaultDateTimeFormat = (locale) => {
173
+ return getDefaultDateFormat(locale) + " " + getDefaultTimeFormat(locale);
174
+ };
175
+ const getLongDateFormatMemoized = memoize((locale) => {
176
+ try {
177
+ const parts = new Intl.DateTimeFormat(locale, {
178
+ calendar: "gregory",
179
+ numberingSystem: "latn",
180
+ weekday: "long",
181
+ year: "numeric",
182
+ month: "long",
183
+ day: "numeric",
184
+ timeZone: "UTC",
185
+ }).formatToParts(FORMAT_SAMPLE_DATE);
186
+ const pattern = buildPatternFromParts(parts, (part) => {
187
+ switch (part.type) {
188
+ case "weekday":
189
+ return "dddd";
190
+ case "month":
191
+ // CJK locales keep the month numeric even in long dates
192
+ // ("9月"): Intl reports the digit with 月/월 as a literal, and
193
+ // the formatter's own `mmmm` would re-append the marker.
194
+ return /^\d+$/.test(part.value)
195
+ ? numericFieldToken("m", "mm", part.value)
196
+ : "mmmm";
197
+ case "day":
198
+ return numericFieldToken("d", "dd", part.value);
199
+ case "year":
200
+ return part.value.length === 2 ? "yy" : "yyyy";
201
+ case "literal":
202
+ return escapePatternLiteral(part.value);
203
+ default:
204
+ return undefined;
205
+ }
206
+ });
207
+ if (!pattern) {
208
+ return `[$-${locale}]dddd, mmmm d, yyyy`;
209
+ }
210
+ return `[$-${locale}]${pattern}`;
211
+ }
212
+ catch (err) {
213
+ return `[$-${locale}]dddd, mmmm d, yyyy`;
214
+ }
215
+ });
216
+ /**
217
+ * Long (full) date pattern for a locale, following the locale's own
218
+ * field placement and literal text — e.g. de-DE →
219
+ * `[$-de-DE]dddd, d. mmmm yyyy`, ja-JP → `[$-ja-JP]yyyy"年"m"月"d"日"dddd`.
220
+ * Localized weekday/month names are best-effort: they render through the
221
+ * spreadsheet formatter's own locale data via the `[$-locale]` tag.
222
+ */
223
+ export const getLongDateFormat = (locale = getCurrentLocale()) => getLongDateFormatMemoized(locale);
@@ -0,0 +1,4 @@
1
+ export declare const log: (...args: any[]) => false | void;
2
+ export declare const debug: (...args: any[]) => false | void;
3
+ export declare const error: (...args: any[]) => false | void;
4
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../log.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,GAAG,EAAE,iBAC6B,CAAC;AAChE,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,GAAG,EAAE,iBAC6B,CAAC;AAClE,eAAO,MAAM,KAAK,GAAI,GAAG,MAAM,GAAG,EAAE,iBAC6B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export const log = (...args) => process.env.NODE_ENV !== "production" && console.log(...args);
2
+ export const debug = (...args) => process.env.NODE_ENV !== "production" && console.debug(...args);
3
+ export const error = (...args) => process.env.NODE_ENV !== "production" && console.error(...args);
@@ -0,0 +1,18 @@
1
+ export declare const memoize: <T extends unknown[], K>(fn: (...args: T) => K) => (...args: T) => K;
2
+ /**
3
+ * Mint a stable cellXfs key ("sid") for a CellFormat. The key is
4
+ * `contentHash` (FNV-1a-64 → 8-char base62) over a delimiter-separated
5
+ * canonical serialization of the format's defined properties — the
6
+ * previous 31-bit rolling int hash silently collided two different
7
+ * formats onto one registry key (first-writer wins, so e.g. a red
8
+ * format could hijack a blue one on import).
9
+ *
10
+ * Sids are minted at import/edit time and persisted in the doc's
11
+ * cellXfs map together with the cell references that carry them —
12
+ * readback resolves a stored sid against the stored map, never by
13
+ * re-hashing. Changing this algorithm therefore only affects newly
14
+ * minted keys; existing docs keep resolving their old sids.
15
+ */
16
+ export declare const getCellXfsKey: (userObj: any | null | undefined) => string;
17
+ export declare const definedProps: <T extends Record<string, any> | undefined | null>(obj: T) => Partial<NonNullable<T>> | T;
18
+ //# sourceMappingURL=memoize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoize.d.ts","sourceRoot":"","sources":["../../memoize.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,CAAC,MAE3D,GAAG,MAAM,CAAC,MASnB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,GAAG,GAAG,IAAI,GAAG,SAAS,KAAG,MA4D/D,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,IAAI,EAC3E,KAAK,CAAC,KACL,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAU5B,CAAC"}