@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,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"}
@@ -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"}