@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,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if its being rendered in Browser or SSR
|
|
3
|
+
*/
|
|
4
|
+
export declare const canUseDOM: boolean;
|
|
5
|
+
export declare const moveSelectionToEndOfActiveInput: () => void;
|
|
6
|
+
export declare const moveSelectionToEndOfTextArea: () => void;
|
|
7
|
+
export declare const scheduleAfterPaint: (callback: () => void) => () => void;
|
|
8
|
+
export declare const focusableNodeNames: Set<string>;
|
|
9
|
+
export declare const isFocusable: (el: Element | null) => boolean;
|
|
10
|
+
//# sourceMappingURL=dom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../dom.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,SAAS,SAGkB,CAAC;AAEzC,eAAO,MAAM,+BAA+B,YAsB3C,CAAC;AAEF,eAAO,MAAM,4BAA4B,YAiBxC,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,IAAI,eAmCtD,CAAC;AAGF,eAAO,MAAM,kBAAkB,aAA2C,CAAC;AAE3E,eAAO,MAAM,WAAW,GAAI,IAAI,OAAO,GAAG,IAAI,YAQ7C,CAAC"}
|
package/dist/esm/dom.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if its being rendered in Browser or SSR
|
|
3
|
+
*/
|
|
4
|
+
export const canUseDOM = typeof window !== "undefined" &&
|
|
5
|
+
typeof document !== "undefined" &&
|
|
6
|
+
typeof window.document !== "undefined";
|
|
7
|
+
export const moveSelectionToEndOfActiveInput = () => {
|
|
8
|
+
try {
|
|
9
|
+
if (!canUseDOM) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
/* Move to end */
|
|
13
|
+
requestAnimationFrame(() => {
|
|
14
|
+
if (document.activeElement) {
|
|
15
|
+
const range = document.createRange();
|
|
16
|
+
range.selectNodeContents(document.activeElement);
|
|
17
|
+
range.collapse(false);
|
|
18
|
+
const selection = typeof window !== "undefined" ? window.getSelection() : undefined;
|
|
19
|
+
if (selection) {
|
|
20
|
+
selection.removeAllRanges();
|
|
21
|
+
selection.addRange(range);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
console.log("Failed to collapse selection", err);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const moveSelectionToEndOfTextArea = () => {
|
|
31
|
+
try {
|
|
32
|
+
if (!canUseDOM) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (document.activeElement &&
|
|
36
|
+
document.activeElement.tagName === "TEXTAREA") {
|
|
37
|
+
const textarea = document.activeElement;
|
|
38
|
+
const end = textarea.value.length;
|
|
39
|
+
// ✅ Move focus to End of textarea
|
|
40
|
+
textarea.setSelectionRange(end, end);
|
|
41
|
+
textarea.focus();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (err) { }
|
|
45
|
+
};
|
|
46
|
+
export const scheduleAfterPaint = (callback) => {
|
|
47
|
+
if (typeof requestAnimationFrame === "function") {
|
|
48
|
+
if (typeof MessageChannel !== "undefined") {
|
|
49
|
+
const channel = new MessageChannel();
|
|
50
|
+
channel.port1.onmessage = () => {
|
|
51
|
+
channel.port1.onmessage = null;
|
|
52
|
+
callback();
|
|
53
|
+
};
|
|
54
|
+
requestAnimationFrame(() => {
|
|
55
|
+
channel.port2.postMessage(null);
|
|
56
|
+
});
|
|
57
|
+
return () => {
|
|
58
|
+
channel.port1.onmessage = null;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const frameId = requestAnimationFrame(() => callback());
|
|
62
|
+
return () => {
|
|
63
|
+
if (typeof cancelAnimationFrame === "function") {
|
|
64
|
+
cancelAnimationFrame(frameId);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (typeof MessageChannel !== "undefined") {
|
|
69
|
+
const channel = new MessageChannel();
|
|
70
|
+
channel.port1.onmessage = () => {
|
|
71
|
+
channel.port1.onmessage = null;
|
|
72
|
+
callback();
|
|
73
|
+
};
|
|
74
|
+
channel.port2.postMessage(null);
|
|
75
|
+
return () => {
|
|
76
|
+
channel.port1.onmessage = null;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
callback();
|
|
80
|
+
return () => { };
|
|
81
|
+
};
|
|
82
|
+
/* Focusable node names */
|
|
83
|
+
export const focusableNodeNames = new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
84
|
+
export const isFocusable = (el) => {
|
|
85
|
+
if (!el) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return (Boolean(el.getAttribute("contenteditable")) ||
|
|
89
|
+
focusableNodeNames.has(el.nodeName));
|
|
90
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern-based type inference (Excel parity). xlsx files routinely carry
|
|
3
|
+
* a `numberFormat.pattern` without an accompanying `type` field — these
|
|
4
|
+
* helpers let callers classify such cells from the pattern alone.
|
|
5
|
+
*
|
|
6
|
+
* Notes:
|
|
7
|
+
* - `isDateFormat` from numfmt returns true for any pattern containing
|
|
8
|
+
* date *or* time tokens. We split that into DATE (date only) vs
|
|
9
|
+
* DATE_TIME (has a time component) using `getFormatDateInfo`, mirroring
|
|
10
|
+
* Excel's `CELL("format")` distinctions.
|
|
11
|
+
* - `isTextFormat` only flags patterns whose entire body is the `@`
|
|
12
|
+
* placeholder. Multi-section formats with an `@` text section
|
|
13
|
+
* (`#,##0;-#,##0;0;[Magenta]@`) are NOT text formats — they're number
|
|
14
|
+
* formats whose text section happens to be reachable for string values.
|
|
15
|
+
*/
|
|
16
|
+
export declare const isDatePattern: (pattern: string | undefined | null) => boolean;
|
|
17
|
+
export declare const isDateTimePattern: (pattern: string | undefined | null) => boolean;
|
|
18
|
+
export declare const isTimePattern: (pattern: string | undefined | null) => boolean;
|
|
19
|
+
export declare const isTextPattern: (pattern: string | undefined | null) => boolean;
|
|
20
|
+
export declare const ssfFormat: (pattern: string, value: any, locale?: string) => string;
|
|
21
|
+
export declare const ssfFormatColor: (pattern: string, value: any) => string | undefined;
|
|
22
|
+
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../formatter.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OASlE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KACjC,OAWF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAWlE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAOlE,CAAC;AAMF,eAAO,MAAM,SAAS,GACpB,SAAS,MAAM,EACf,OAAO,GAAG,EACV,SAAS,MAAM,KACd,MAeF,CAAC;AAmLF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,OAAO,GAAG,KACT,MAAM,GAAG,SAoBX,CAAC"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { format, formatColor, getFormatDateInfo, isDateFormat, isTextFormat, } from "numfmt";
|
|
2
|
+
import { IndexedColors } from "./colors";
|
|
3
|
+
/**
|
|
4
|
+
* Pattern-based type inference (Excel parity). xlsx files routinely carry
|
|
5
|
+
* a `numberFormat.pattern` without an accompanying `type` field — these
|
|
6
|
+
* helpers let callers classify such cells from the pattern alone.
|
|
7
|
+
*
|
|
8
|
+
* Notes:
|
|
9
|
+
* - `isDateFormat` from numfmt returns true for any pattern containing
|
|
10
|
+
* date *or* time tokens. We split that into DATE (date only) vs
|
|
11
|
+
* DATE_TIME (has a time component) using `getFormatDateInfo`, mirroring
|
|
12
|
+
* Excel's `CELL("format")` distinctions.
|
|
13
|
+
* - `isTextFormat` only flags patterns whose entire body is the `@`
|
|
14
|
+
* placeholder. Multi-section formats with an `@` text section
|
|
15
|
+
* (`#,##0;-#,##0;0;[Magenta]@`) are NOT text formats — they're number
|
|
16
|
+
* formats whose text section happens to be reachable for string values.
|
|
17
|
+
*/
|
|
18
|
+
export const isDatePattern = (pattern) => {
|
|
19
|
+
if (!pattern)
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
if (!isDateFormat(pattern))
|
|
23
|
+
return false;
|
|
24
|
+
const info = getFormatDateInfo(pattern);
|
|
25
|
+
return info.day || info.month || info.year;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export const isDateTimePattern = (pattern) => {
|
|
32
|
+
if (!pattern)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
if (!isDateFormat(pattern))
|
|
36
|
+
return false;
|
|
37
|
+
const info = getFormatDateInfo(pattern);
|
|
38
|
+
const hasDate = info.day || info.month || info.year;
|
|
39
|
+
const hasTime = info.hours || info.minutes || info.seconds;
|
|
40
|
+
return hasDate && hasTime;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
export const isTimePattern = (pattern) => {
|
|
47
|
+
if (!pattern)
|
|
48
|
+
return false;
|
|
49
|
+
try {
|
|
50
|
+
if (!isDateFormat(pattern))
|
|
51
|
+
return false;
|
|
52
|
+
const info = getFormatDateInfo(pattern);
|
|
53
|
+
const hasDate = info.day || info.month || info.year;
|
|
54
|
+
const hasTime = info.hours || info.minutes || info.seconds;
|
|
55
|
+
return hasTime && !hasDate;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
export const isTextPattern = (pattern) => {
|
|
62
|
+
if (!pattern)
|
|
63
|
+
return false;
|
|
64
|
+
try {
|
|
65
|
+
return isTextFormat(pattern);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const isSingleSectionElapsedTimePattern = (pattern) => !pattern.includes(";") &&
|
|
72
|
+
/\[(?:h+|m+|s+)\]/i.test(pattern.replace(/\[\$[^\]]+\]/g, ""));
|
|
73
|
+
export const ssfFormat = (pattern, value, locale) => {
|
|
74
|
+
try {
|
|
75
|
+
const isNegativeElapsedTime = typeof value === "number" &&
|
|
76
|
+
value < 0 &&
|
|
77
|
+
isSingleSectionElapsedTimePattern(pattern);
|
|
78
|
+
const formatted = format(pattern, isNegativeElapsedTime ? Math.abs(value) : value, { locale });
|
|
79
|
+
return isNegativeElapsedTime ? `-${formatted}` : formatted;
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
return String(value);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Excel's legacy fixed 56-color palette used by `[ColorN]` directives
|
|
87
|
+
* inside number format patterns (1-indexed). This palette is INDEPENDENT
|
|
88
|
+
* of the workbook's `<indexedColors>` block — `[ColorN]` always refers
|
|
89
|
+
* to Excel's hard-coded legacy palette, even in files that override the
|
|
90
|
+
* indexed colors.
|
|
91
|
+
*
|
|
92
|
+
* Excel's `[ColorN]` (1-56) resolves to indexed-palette entry `N + 7`
|
|
93
|
+
* (indices 8–63 of the 64-entry legacy palette; entries 0–7 are the
|
|
94
|
+
* "EGA duplicate" block that `[ColorN]` never addresses).
|
|
95
|
+
*
|
|
96
|
+
* `numfmt` instead resolves `[ColorN]` as its `indexColors[N - 1]` — a
|
|
97
|
+
* straight off-by-8 against Excel. That happens to agree for N=1..8
|
|
98
|
+
* (the palette's first 8 entries repeat at 8..15) but is wrong for 28
|
|
99
|
+
* of the remaining 48 indices: `[Color11]` (Excel navy `#000080`)
|
|
100
|
+
* renders pure red, as do `[Color17]` and `[Color38]`. We therefore
|
|
101
|
+
* resolve `[ColorN]` ourselves whenever the matched format segment
|
|
102
|
+
* carries one (see `ssfFormatColor`).
|
|
103
|
+
*
|
|
104
|
+
* Source: ECMA-376 §18.8.27 (indexedColors) and Excel's documented
|
|
105
|
+
* legacy palette. Note `[ColorN]` always refers to Excel's hard-coded
|
|
106
|
+
* legacy palette, never a workbook's custom `<indexedColors>` block.
|
|
107
|
+
*/
|
|
108
|
+
const LEGACY_COLOR_PALETTE = {};
|
|
109
|
+
for (let n = 1; n <= 56; n++) {
|
|
110
|
+
LEGACY_COLOR_PALETTE[n] = IndexedColors[n + 7];
|
|
111
|
+
}
|
|
112
|
+
const COLOR_DIRECTIVE_REGEX = /\[Color(\d+)\]/i;
|
|
113
|
+
const COLOR_OVERRIDE_CACHE = new Map();
|
|
114
|
+
const COLOR_OVERRIDE_CACHE_LIMIT = 256;
|
|
115
|
+
/**
|
|
116
|
+
* Extract a `[ColorN]` directive resolution for each segment of a
|
|
117
|
+
* SpreadsheetML pattern. Returns `null` (cached) if the pattern
|
|
118
|
+
* contains no `[ColorN]` at all — short-circuits any further work
|
|
119
|
+
* for the vast majority of patterns. Walks the pattern once,
|
|
120
|
+
* scanning for `;` separators outside string literals and `[ColorN]`
|
|
121
|
+
* tokens; no `String#split` allocation.
|
|
122
|
+
*/
|
|
123
|
+
const PATTERNS_WITHOUT_COLOR = new Set();
|
|
124
|
+
const getPerSegmentColorOverrides = (pattern) => {
|
|
125
|
+
if (PATTERNS_WITHOUT_COLOR.has(pattern))
|
|
126
|
+
return null;
|
|
127
|
+
const cached = COLOR_OVERRIDE_CACHE.get(pattern);
|
|
128
|
+
if (cached)
|
|
129
|
+
return cached;
|
|
130
|
+
// Fast pre-check: no `[Color` substring → no override possible.
|
|
131
|
+
// `indexOf` is far cheaper than a regex when the pattern is long
|
|
132
|
+
// and there's no match. (`[ColorN]` is case-sensitive — Excel
|
|
133
|
+
// accepts `[color10]` too but the standard form is title case.)
|
|
134
|
+
if (pattern.indexOf("[Color") === -1 && pattern.indexOf("[color") === -1) {
|
|
135
|
+
if (PATTERNS_WITHOUT_COLOR.size > COLOR_OVERRIDE_CACHE_LIMIT) {
|
|
136
|
+
PATTERNS_WITHOUT_COLOR.clear();
|
|
137
|
+
}
|
|
138
|
+
PATTERNS_WITHOUT_COLOR.add(pattern);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
// Single pass: track current segment, current `[ColorN]` index, and
|
|
142
|
+
// whether we're inside a string literal. When we hit `;` outside a
|
|
143
|
+
// string, advance the segment. When we see `[Color\d+]`, record it
|
|
144
|
+
// for the current segment (only the first occurrence per segment —
|
|
145
|
+
// matches Excel's behaviour).
|
|
146
|
+
const segmentColors = [
|
|
147
|
+
undefined,
|
|
148
|
+
undefined,
|
|
149
|
+
undefined,
|
|
150
|
+
undefined,
|
|
151
|
+
];
|
|
152
|
+
let seg = 0;
|
|
153
|
+
let inString = false;
|
|
154
|
+
let escape = false;
|
|
155
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
156
|
+
if (seg > 3)
|
|
157
|
+
break;
|
|
158
|
+
const ch = pattern[i];
|
|
159
|
+
if (escape) {
|
|
160
|
+
escape = false;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (ch === "\\") {
|
|
164
|
+
escape = true;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (ch === '"') {
|
|
168
|
+
inString = !inString;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (ch === ";" && !inString) {
|
|
172
|
+
seg++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (ch === "[" && !inString && segmentColors[seg] === undefined) {
|
|
176
|
+
// Look ahead for `Color\d+]` — case-insensitive on the prefix.
|
|
177
|
+
const end = pattern.indexOf("]", i + 1);
|
|
178
|
+
if (end === -1)
|
|
179
|
+
continue;
|
|
180
|
+
const inside = pattern.slice(i + 1, end);
|
|
181
|
+
if (/^Color\d+$/i.test(inside)) {
|
|
182
|
+
const idx = Number(inside.slice(5));
|
|
183
|
+
const resolved = LEGACY_COLOR_PALETTE[idx];
|
|
184
|
+
if (resolved)
|
|
185
|
+
segmentColors[seg] = resolved;
|
|
186
|
+
}
|
|
187
|
+
i = end;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const result = [
|
|
192
|
+
segmentColors[0],
|
|
193
|
+
segmentColors[1],
|
|
194
|
+
segmentColors[2],
|
|
195
|
+
segmentColors[3],
|
|
196
|
+
Math.min(seg, 3) + 1,
|
|
197
|
+
];
|
|
198
|
+
// No `[ColorN]` in any segment after all — treat as "no override".
|
|
199
|
+
if (!result.slice(0, 4).some((x) => x !== undefined)) {
|
|
200
|
+
if (PATTERNS_WITHOUT_COLOR.size > COLOR_OVERRIDE_CACHE_LIMIT) {
|
|
201
|
+
PATTERNS_WITHOUT_COLOR.clear();
|
|
202
|
+
}
|
|
203
|
+
PATTERNS_WITHOUT_COLOR.add(pattern);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
if (COLOR_OVERRIDE_CACHE.size > COLOR_OVERRIDE_CACHE_LIMIT) {
|
|
207
|
+
COLOR_OVERRIDE_CACHE.clear();
|
|
208
|
+
}
|
|
209
|
+
COLOR_OVERRIDE_CACHE.set(pattern, result);
|
|
210
|
+
return result;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Pick the `[ColorN]` override the value's matched segment carries, honoring
|
|
214
|
+
* Excel's section-selection rules for patterns with fewer than 4 sections:
|
|
215
|
+
* one section applies to every numeric value; with two, negatives take the
|
|
216
|
+
* second; zero takes the third only when present; text takes the fourth only
|
|
217
|
+
* when present (a number-only pattern leaves text unformatted — no color).
|
|
218
|
+
*/
|
|
219
|
+
const pickSegmentOverride = (overrides, value) => {
|
|
220
|
+
const sections = overrides[4];
|
|
221
|
+
if (typeof value === "string") {
|
|
222
|
+
return sections === 4 ? overrides[3] : undefined;
|
|
223
|
+
}
|
|
224
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
225
|
+
if (!Number.isFinite(n) || n > 0)
|
|
226
|
+
return overrides[0];
|
|
227
|
+
if (n < 0)
|
|
228
|
+
return sections >= 2 ? overrides[1] : overrides[0];
|
|
229
|
+
return sections >= 3 ? overrides[2] : overrides[0];
|
|
230
|
+
};
|
|
231
|
+
export const ssfFormatColor = (pattern, value) => {
|
|
232
|
+
// numfmt resolves `[ColorN]` as its `indexColors[N - 1]`, but Excel's
|
|
233
|
+
// `[ColorN]` is legacy-indexed-palette entry `N + 7` — numfmt's answer
|
|
234
|
+
// is wrong for most N ≥ 9 (`[Color11]` navy renders pure red) and
|
|
235
|
+
// `#FFF` for some others. Whenever the matched segment carries a
|
|
236
|
+
// `[ColorN]` directive, prefer our palette resolution over numfmt's
|
|
237
|
+
// raw color. The `PATTERNS_WITHOUT_COLOR` set makes this check free
|
|
238
|
+
// for the common case of patterns with no `[ColorN]`.
|
|
239
|
+
const overrides = getPerSegmentColorOverrides(pattern);
|
|
240
|
+
if (overrides) {
|
|
241
|
+
const resolved = pickSegmentOverride(overrides, value);
|
|
242
|
+
if (resolved)
|
|
243
|
+
return resolved;
|
|
244
|
+
}
|
|
245
|
+
let raw;
|
|
246
|
+
try {
|
|
247
|
+
raw = formatColor(pattern, value);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
raw = undefined;
|
|
251
|
+
}
|
|
252
|
+
return raw && raw.length > 0 ? raw : undefined;
|
|
253
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CellInterface, GridRange } from "@rowsncolumns/common-types";
|
|
2
|
+
export interface FormulaToRelativeReferenceOptions {
|
|
3
|
+
exclusionRange?: GridRange;
|
|
4
|
+
ignoreCircular?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare const formulaToRelativeReference: (formula: string | undefined, sourceCell: CellInterface, destinationCell: CellInterface, options?: FormulaToRelativeReferenceOptions) => string | undefined;
|
|
7
|
+
//# sourceMappingURL=formula-refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formula-refs.d.ts","sourceRoot":"","sources":["../../formula-refs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAsB3E,MAAM,WAAW,iCAAiC;IAChD,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM,GAAG,SAAS,EAC3B,YAAY,aAAa,EACzB,iBAAiB,aAAa,EAC9B,UAAU,iCAAiC,KAC1C,MAAM,GAAG,SAgCX,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { alpha2number, number2Alpha } from "./numbers";
|
|
2
|
+
const CELL_ADDR_RE = /((?:'[^']+'|[^'!,]+)!)?(\$?[A-Z]+\$?\d+)(?::(\$?[A-Z]+\$?\d+))?/g;
|
|
3
|
+
const parseAddr = (addr) => {
|
|
4
|
+
const m = /^(\$?)([A-Z]+)(\$?)(\d+)$/.exec(addr);
|
|
5
|
+
if (!m)
|
|
6
|
+
return null;
|
|
7
|
+
const [, absCol, colAlpha, absRow, rowStr] = m;
|
|
8
|
+
return {
|
|
9
|
+
col: alpha2number(colAlpha),
|
|
10
|
+
row: parseInt(rowStr, 10),
|
|
11
|
+
absCol: absCol === "$",
|
|
12
|
+
absRow: absRow === "$",
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const formatAddr = (row, col, absCol, absRow) => {
|
|
16
|
+
const colAlpha = number2Alpha(Math.max(0, col - 1));
|
|
17
|
+
const rowNum = Math.max(1, row);
|
|
18
|
+
return `${absCol ? "$" : ""}${colAlpha}${absRow ? "$" : ""}${rowNum}`;
|
|
19
|
+
};
|
|
20
|
+
export const formulaToRelativeReference = (formula, sourceCell, destinationCell, options) => {
|
|
21
|
+
// Arguments exclusionRange and ignoreCircular were previously unused in this implementation
|
|
22
|
+
// We keep the options signature for compatibility but they remain unused in this regex-based impl?
|
|
23
|
+
// Actually, checking the previous code, they were underscored (_exclusionRange) so likely unused.
|
|
24
|
+
if (!formula)
|
|
25
|
+
return formula;
|
|
26
|
+
if (sourceCell.rowIndex === destinationCell.rowIndex &&
|
|
27
|
+
sourceCell.columnIndex === destinationCell.columnIndex) {
|
|
28
|
+
return formula;
|
|
29
|
+
}
|
|
30
|
+
const dRow = destinationCell.rowIndex - sourceCell.rowIndex;
|
|
31
|
+
const dCol = destinationCell.columnIndex - sourceCell.columnIndex;
|
|
32
|
+
return formula.replace(CELL_ADDR_RE, (match, sheetPrefix, left, right) => {
|
|
33
|
+
const adjustOne = (addr) => {
|
|
34
|
+
const p = parseAddr(addr);
|
|
35
|
+
if (!p)
|
|
36
|
+
return addr;
|
|
37
|
+
const newRow = p.absRow ? p.row : p.row + dRow;
|
|
38
|
+
const newCol = p.absCol ? p.col : p.col + dCol;
|
|
39
|
+
return formatAddr(newRow, newCol, p.absCol, p.absRow);
|
|
40
|
+
};
|
|
41
|
+
if (right) {
|
|
42
|
+
const leftAdjusted = adjustOne(left);
|
|
43
|
+
const rightAdjusted = adjustOne(right);
|
|
44
|
+
return `${sheetPrefix ?? ""}${leftAdjusted}:${rightAdjusted}`;
|
|
45
|
+
}
|
|
46
|
+
const singleAdjusted = adjustOne(left);
|
|
47
|
+
return `${sheetPrefix ?? ""}${singleAdjusted}`;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { AreaStyle, GridRange, SelectionArea } from "@rowsncolumns/common-types";
|
|
2
|
+
/**
|
|
3
|
+
* Add/remove parenthesis in formulas
|
|
4
|
+
* @param str
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export declare const balanceParentheses: (str: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Add closing quotes in formulas when a string is unterminated.
|
|
10
|
+
* Handles escaped quotes ("") inside strings.
|
|
11
|
+
*/
|
|
12
|
+
export declare const balanceQuotes: (str: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Add/remove parenthesis and add closing quotes in formulas.
|
|
15
|
+
*/
|
|
16
|
+
export declare const balanceFormula: (str: string) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Check if parenthesis is balanced
|
|
19
|
+
* @param str
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
export declare const isBalancedParenthesis: (str: string) => boolean;
|
|
23
|
+
export declare const looksLikeMarkdown: (text: string) => boolean;
|
|
24
|
+
export declare const setDefaultEnableAtFormula: (enable?: boolean) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Check if text is a formula
|
|
27
|
+
* @param text
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
export declare const isAFormula: (text: any) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Check if text is a formula-shaped expression stored WITHOUT the
|
|
33
|
+
* leading "=". OOXML defined names (`<definedName>` in workbook.xml)
|
|
34
|
+
* store their expressions bare — `OFFSET(Sheet1!$A$1,0,0,10)` rather
|
|
35
|
+
* than `=OFFSET(...)` — so `isAFormula` can't classify them.
|
|
36
|
+
*
|
|
37
|
+
* Returns false for anything that already resolves as a static value
|
|
38
|
+
* or plain reference today: quoted string / numeric / boolean
|
|
39
|
+
* literals, single-area range references (including external-workbook
|
|
40
|
+
* references like `'[1]Revenue'!$A$17`), and broken `#REF!`
|
|
41
|
+
* expressions. Returns true only for expressions that need the
|
|
42
|
+
* calculator: function calls and operator expressions.
|
|
43
|
+
* @param text
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
export declare const isBareFormulaExpression: (text: unknown) => boolean;
|
|
47
|
+
export declare const isAlternateFormula: (text: string) => boolean;
|
|
48
|
+
export declare const isValidFormula: (text: any) => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a key is an Arrow key
|
|
51
|
+
* @param code
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
54
|
+
export declare const isAnArrowKey: (code: string) => boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Iterate over all cells in a bound
|
|
57
|
+
* @param bounds
|
|
58
|
+
* @param callback
|
|
59
|
+
*/
|
|
60
|
+
export declare const iterateCells: (top: number, right: number | ((rowIndex: number) => number), bottom: number, left: number) => Generator<[number, number]>;
|
|
61
|
+
export declare function combineIterators<T>(...generators: (Generator<T> | undefined)[]): Generator<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Iterate cells in range
|
|
64
|
+
* @param range
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
export type GridRangeIterator = GridRange | {
|
|
68
|
+
startRowIndex: number;
|
|
69
|
+
endColumnIndex: number | ((rowIndex: number) => number);
|
|
70
|
+
endRowIndex: number;
|
|
71
|
+
startColumnIndex: number;
|
|
72
|
+
};
|
|
73
|
+
export declare const iterateCellsInRange: (range: GridRangeIterator) => Generator<[number, number], any, any>;
|
|
74
|
+
/**
|
|
75
|
+
* Iterate all cells in multiple selections
|
|
76
|
+
* @param selections
|
|
77
|
+
* @returns
|
|
78
|
+
*/
|
|
79
|
+
export declare const iterateSelections: <T extends AreaStyle>(selections: SelectionArea<T>[]) => Generator<Generator<[number, number], any, any>, void, unknown>;
|
|
80
|
+
//# sourceMappingURL=formula.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formula.d.ts","sourceRoot":"","sources":["../../formula.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACd,MAAM,4BAA4B,CAAC;AAgBpC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,WAc7C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,WAkDxC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,WAEzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,YAEhD,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,OAsChD,CAAC;AAIF,eAAO,MAAM,yBAAyB,GAAI,SAAS,OAAO,SAEzD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,GAAG,YAcnC,CAAC;AAYF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,OAAO,KAAG,OAgCvD,CAAC;AAgBF,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,YAwC9C,CAAC;AAGF,eAAO,MAAM,cAAc,GAAI,MAAM,GAAG,YAGvC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,YAA6B,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,MAAM,EACX,OAAO,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,EAC9C,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAO5B,CAAC;AAEF,wBAAiB,gBAAgB,CAAC,CAAC,EACjC,GAAG,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,GAC1C,SAAS,CAAC,CAAC,CAAC,CAId;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT;IACE,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AACN,eAAO,MAAM,mBAAmB,GAAI,OAAO,iBAAiB,0CAO3D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAc,CAAC,SAAS,SAAS,EAC7D,YAAY,aAAa,CAAC,CAAC,CAAC,EAAE,oEAK/B,CAAC"}
|