@rowsncolumns/spreadsheet 10.2.74 → 10.2.75

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 (194) hide show
  1. package/dist/cjs/api/excel-validation-kinds.d.ts +71 -0
  2. package/dist/cjs/api/excel-validation-kinds.d.ts.map +1 -0
  3. package/dist/cjs/api/excel-validation-kinds.js +304 -0
  4. package/dist/cjs/api/go-to-special.d.ts +32 -0
  5. package/dist/cjs/api/go-to-special.d.ts.map +1 -0
  6. package/dist/cjs/api/go-to-special.js +111 -0
  7. package/dist/cjs/api/validation-tooltip.d.ts +17 -0
  8. package/dist/cjs/api/validation-tooltip.d.ts.map +1 -0
  9. package/dist/cjs/api/validation-tooltip.js +42 -0
  10. package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts +28 -0
  11. package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
  12. package/dist/cjs/atoms/advanced-filter-dialog.atom.js +32 -0
  13. package/dist/cjs/atoms/go-to-dialog.atom.d.ts +11 -1
  14. package/dist/cjs/atoms/go-to-dialog.atom.d.ts.map +1 -1
  15. package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts +29 -0
  16. package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
  17. package/dist/cjs/atoms/goal-seek-dialog.atom.js +30 -0
  18. package/dist/cjs/atoms/index.d.ts +1 -0
  19. package/dist/cjs/atoms/index.d.ts.map +1 -1
  20. package/dist/cjs/atoms/index.js +1 -0
  21. package/dist/cjs/atoms/subtotal-dialog.atom.d.ts +39 -0
  22. package/dist/cjs/atoms/subtotal-dialog.atom.d.ts.map +1 -0
  23. package/dist/cjs/atoms/subtotal-dialog.atom.js +30 -0
  24. package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts +34 -0
  25. package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
  26. package/dist/cjs/atoms/text-to-columns-dialog.atom.js +36 -0
  27. package/dist/cjs/atoms/watch-window.atom.d.ts +37 -0
  28. package/dist/cjs/atoms/watch-window.atom.d.ts.map +1 -0
  29. package/dist/cjs/atoms/watch-window.atom.js +61 -0
  30. package/dist/cjs/components/advanced-filter-dialog/index.d.ts +28 -0
  31. package/dist/cjs/components/advanced-filter-dialog/index.d.ts.map +1 -0
  32. package/dist/cjs/components/advanced-filter-dialog/index.js +134 -0
  33. package/dist/cjs/components/calculation-options-dialog/index.d.ts +33 -0
  34. package/dist/cjs/components/calculation-options-dialog/index.d.ts.map +1 -0
  35. package/dist/cjs/components/calculation-options-dialog/index.js +69 -0
  36. package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
  37. package/dist/cjs/components/canvas-grid/index.js +104 -21
  38. package/dist/cjs/components/canvas-grid/typings.d.ts +19 -2
  39. package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
  40. package/dist/cjs/components/cell/alignment.d.ts +30 -0
  41. package/dist/cjs/components/cell/alignment.d.ts.map +1 -1
  42. package/dist/cjs/components/cell/alignment.js +56 -1
  43. package/dist/cjs/components/cell/index.d.ts.map +1 -1
  44. package/dist/cjs/components/cell/index.js +15 -7
  45. package/dist/cjs/components/cell-diff/index.d.ts +3 -2
  46. package/dist/cjs/components/cell-diff/index.d.ts.map +1 -1
  47. package/dist/cjs/components/cell-editor/index.d.ts.map +1 -1
  48. package/dist/cjs/components/cell-editor/index.js +2 -1
  49. package/dist/cjs/components/context-menu/index.d.ts +6 -1
  50. package/dist/cjs/components/context-menu/index.d.ts.map +1 -1
  51. package/dist/cjs/components/context-menu/index.js +146 -3
  52. package/dist/cjs/components/embeds/embed-component.d.ts.map +1 -1
  53. package/dist/cjs/components/embeds/embed-component.js +16 -1
  54. package/dist/cjs/components/go-to-dialog/index.d.ts +11 -1
  55. package/dist/cjs/components/go-to-dialog/index.d.ts.map +1 -1
  56. package/dist/cjs/components/go-to-dialog/index.js +67 -24
  57. package/dist/cjs/components/goal-seek-dialog/index.d.ts +32 -0
  58. package/dist/cjs/components/goal-seek-dialog/index.d.ts.map +1 -0
  59. package/dist/cjs/components/goal-seek-dialog/index.js +149 -0
  60. package/dist/cjs/components/subtotal-dialog/index.d.ts +30 -0
  61. package/dist/cjs/components/subtotal-dialog/index.d.ts.map +1 -0
  62. package/dist/cjs/components/subtotal-dialog/index.js +126 -0
  63. package/dist/cjs/components/text-to-columns-dialog/index.d.ts +37 -0
  64. package/dist/cjs/components/text-to-columns-dialog/index.d.ts.map +1 -0
  65. package/dist/cjs/components/text-to-columns-dialog/index.js +166 -0
  66. package/dist/cjs/components/toolbar/background-color-selector.d.ts +13 -1
  67. package/dist/cjs/components/toolbar/background-color-selector.d.ts.map +1 -1
  68. package/dist/cjs/components/toolbar/background-color-selector.js +57 -11
  69. package/dist/cjs/components/toolbar/cell-style-selector.d.ts +15 -2
  70. package/dist/cjs/components/toolbar/cell-style-selector.d.ts.map +1 -1
  71. package/dist/cjs/components/toolbar/cell-style-selector.js +56 -3
  72. package/dist/cjs/components/toolbar/fill-selectors.d.ts +49 -0
  73. package/dist/cjs/components/toolbar/fill-selectors.d.ts.map +1 -0
  74. package/dist/cjs/components/toolbar/fill-selectors.js +196 -0
  75. package/dist/cjs/components/toolbar/index.d.ts +1 -0
  76. package/dist/cjs/components/toolbar/index.d.ts.map +1 -1
  77. package/dist/cjs/components/toolbar/index.js +1 -0
  78. package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
  79. package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
  80. package/dist/cjs/components/toolbar/text-horizontal-align-selector.js +23 -4
  81. package/dist/cjs/components/watch-window/index.d.ts +25 -0
  82. package/dist/cjs/components/watch-window/index.d.ts.map +1 -0
  83. package/dist/cjs/components/watch-window/index.js +63 -0
  84. package/dist/cjs/hooks/use-cell-renderer.d.ts.map +1 -1
  85. package/dist/cjs/hooks/use-cell-renderer.js +17 -6
  86. package/dist/cjs/hooks/use-get-tooltip.d.ts.map +1 -1
  87. package/dist/cjs/hooks/use-get-tooltip.js +7 -2
  88. package/dist/cjs/hooks/use-keyboard-handler.d.ts.map +1 -1
  89. package/dist/cjs/hooks/use-keyboard-handler.js +24 -12
  90. package/dist/cjs/index.d.ts +8 -0
  91. package/dist/cjs/index.d.ts.map +1 -1
  92. package/dist/cjs/index.js +8 -0
  93. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  94. package/dist/cjs/types.d.ts +117 -4
  95. package/dist/cjs/types.d.ts.map +1 -1
  96. package/dist/cjs/types.js +19 -1
  97. package/dist/esm/api/excel-validation-kinds.d.ts +71 -0
  98. package/dist/esm/api/excel-validation-kinds.d.ts.map +1 -0
  99. package/dist/esm/api/excel-validation-kinds.js +287 -0
  100. package/dist/esm/api/go-to-special.d.ts +32 -0
  101. package/dist/esm/api/go-to-special.d.ts.map +1 -0
  102. package/dist/esm/api/go-to-special.js +96 -0
  103. package/dist/esm/api/validation-tooltip.d.ts +17 -0
  104. package/dist/esm/api/validation-tooltip.d.ts.map +1 -0
  105. package/dist/esm/api/validation-tooltip.js +34 -0
  106. package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts +28 -0
  107. package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
  108. package/dist/esm/atoms/advanced-filter-dialog.atom.js +21 -0
  109. package/dist/esm/atoms/go-to-dialog.atom.d.ts +11 -1
  110. package/dist/esm/atoms/go-to-dialog.atom.d.ts.map +1 -1
  111. package/dist/esm/atoms/goal-seek-dialog.atom.d.ts +29 -0
  112. package/dist/esm/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
  113. package/dist/esm/atoms/goal-seek-dialog.atom.js +21 -0
  114. package/dist/esm/atoms/index.d.ts +1 -0
  115. package/dist/esm/atoms/index.d.ts.map +1 -1
  116. package/dist/esm/atoms/index.js +1 -0
  117. package/dist/esm/atoms/subtotal-dialog.atom.d.ts +39 -0
  118. package/dist/esm/atoms/subtotal-dialog.atom.d.ts.map +1 -0
  119. package/dist/esm/atoms/subtotal-dialog.atom.js +21 -0
  120. package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts +34 -0
  121. package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
  122. package/dist/esm/atoms/text-to-columns-dialog.atom.js +25 -0
  123. package/dist/esm/atoms/watch-window.atom.d.ts +37 -0
  124. package/dist/esm/atoms/watch-window.atom.d.ts.map +1 -0
  125. package/dist/esm/atoms/watch-window.atom.js +37 -0
  126. package/dist/esm/components/advanced-filter-dialog/index.d.ts +28 -0
  127. package/dist/esm/components/advanced-filter-dialog/index.d.ts.map +1 -0
  128. package/dist/esm/components/advanced-filter-dialog/index.js +125 -0
  129. package/dist/esm/components/calculation-options-dialog/index.d.ts +33 -0
  130. package/dist/esm/components/calculation-options-dialog/index.d.ts.map +1 -0
  131. package/dist/esm/components/calculation-options-dialog/index.js +61 -0
  132. package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
  133. package/dist/esm/components/canvas-grid/index.js +99 -20
  134. package/dist/esm/components/canvas-grid/typings.d.ts +19 -2
  135. package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
  136. package/dist/esm/components/cell/alignment.d.ts +30 -0
  137. package/dist/esm/components/cell/alignment.d.ts.map +1 -1
  138. package/dist/esm/components/cell/alignment.js +51 -0
  139. package/dist/esm/components/cell/index.d.ts.map +1 -1
  140. package/dist/esm/components/cell/index.js +16 -8
  141. package/dist/esm/components/cell-diff/index.d.ts +3 -2
  142. package/dist/esm/components/cell-diff/index.d.ts.map +1 -1
  143. package/dist/esm/components/cell-editor/index.d.ts.map +1 -1
  144. package/dist/esm/components/cell-editor/index.js +2 -1
  145. package/dist/esm/components/context-menu/index.d.ts +6 -1
  146. package/dist/esm/components/context-menu/index.d.ts.map +1 -1
  147. package/dist/esm/components/context-menu/index.js +139 -4
  148. package/dist/esm/components/embeds/embed-component.d.ts.map +1 -1
  149. package/dist/esm/components/embeds/embed-component.js +17 -1
  150. package/dist/esm/components/go-to-dialog/index.d.ts +11 -1
  151. package/dist/esm/components/go-to-dialog/index.d.ts.map +1 -1
  152. package/dist/esm/components/go-to-dialog/index.js +63 -23
  153. package/dist/esm/components/goal-seek-dialog/index.d.ts +32 -0
  154. package/dist/esm/components/goal-seek-dialog/index.d.ts.map +1 -0
  155. package/dist/esm/components/goal-seek-dialog/index.js +125 -0
  156. package/dist/esm/components/subtotal-dialog/index.d.ts +30 -0
  157. package/dist/esm/components/subtotal-dialog/index.d.ts.map +1 -0
  158. package/dist/esm/components/subtotal-dialog/index.js +115 -0
  159. package/dist/esm/components/text-to-columns-dialog/index.d.ts +37 -0
  160. package/dist/esm/components/text-to-columns-dialog/index.d.ts.map +1 -0
  161. package/dist/esm/components/text-to-columns-dialog/index.js +147 -0
  162. package/dist/esm/components/toolbar/background-color-selector.d.ts +13 -1
  163. package/dist/esm/components/toolbar/background-color-selector.d.ts.map +1 -1
  164. package/dist/esm/components/toolbar/background-color-selector.js +61 -11
  165. package/dist/esm/components/toolbar/cell-style-selector.d.ts +15 -2
  166. package/dist/esm/components/toolbar/cell-style-selector.d.ts.map +1 -1
  167. package/dist/esm/components/toolbar/cell-style-selector.js +46 -2
  168. package/dist/esm/components/toolbar/fill-selectors.d.ts +49 -0
  169. package/dist/esm/components/toolbar/fill-selectors.d.ts.map +1 -0
  170. package/dist/esm/components/toolbar/fill-selectors.js +162 -0
  171. package/dist/esm/components/toolbar/index.d.ts +1 -0
  172. package/dist/esm/components/toolbar/index.d.ts.map +1 -1
  173. package/dist/esm/components/toolbar/index.js +1 -0
  174. package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
  175. package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
  176. package/dist/esm/components/toolbar/text-horizontal-align-selector.js +21 -5
  177. package/dist/esm/components/watch-window/index.d.ts +25 -0
  178. package/dist/esm/components/watch-window/index.d.ts.map +1 -0
  179. package/dist/esm/components/watch-window/index.js +54 -0
  180. package/dist/esm/hooks/use-cell-renderer.d.ts.map +1 -1
  181. package/dist/esm/hooks/use-cell-renderer.js +17 -6
  182. package/dist/esm/hooks/use-get-tooltip.d.ts.map +1 -1
  183. package/dist/esm/hooks/use-get-tooltip.js +7 -2
  184. package/dist/esm/hooks/use-keyboard-handler.d.ts.map +1 -1
  185. package/dist/esm/hooks/use-keyboard-handler.js +24 -12
  186. package/dist/esm/index.d.ts +8 -0
  187. package/dist/esm/index.d.ts.map +1 -1
  188. package/dist/esm/index.js +8 -0
  189. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  190. package/dist/esm/types.d.ts +117 -4
  191. package/dist/esm/types.d.ts.map +1 -1
  192. package/dist/esm/types.js +18 -0
  193. package/dist/spreadsheet.min.css +1 -1
  194. package/package.json +6 -6
@@ -0,0 +1,287 @@
1
+ import { cellToAddress } from "@rowsncolumns/utils";
2
+ export const EXCEL_VALIDATION_KIND_LABELS = [
3
+ { kind: "whole", label: "Whole number" },
4
+ { kind: "time", label: "Time" },
5
+ { kind: "textLength", label: "Text length" },
6
+ ];
7
+ export const EXCEL_VALIDATION_OPERATOR_LABELS = [
8
+ { operator: "between", label: "between" },
9
+ { operator: "notBetween", label: "not between" },
10
+ { operator: "equal", label: "equal to" },
11
+ { operator: "notEqual", label: "not equal to" },
12
+ { operator: "greaterThan", label: "greater than" },
13
+ { operator: "lessThan", label: "less than" },
14
+ { operator: "greaterThanOrEqual", label: "greater than or equal to" },
15
+ { operator: "lessThanOrEqual", label: "less than or equal to" },
16
+ ];
17
+ const EXCEL_KINDS = new Set(["whole", "time", "textLength"]);
18
+ const EXCEL_OPERATORS = new Set(EXCEL_VALIDATION_OPERATOR_LABELS.map((entry) => entry.operator));
19
+ /** Two-sided operators need both bounds. */
20
+ export const isTwoSidedOperator = (operator) => operator === "between" || operator === "notBetween";
21
+ const NUMBER_CONDITION_BY_OPERATOR = {
22
+ between: "NUMBER_BETWEEN",
23
+ notBetween: "NUMBER_NOT_BETWEEN",
24
+ equal: "NUMBER_EQ",
25
+ notEqual: "NUMBER_NOT_EQ",
26
+ greaterThan: "NUMBER_GREATER",
27
+ lessThan: "NUMBER_LESS",
28
+ greaterThanOrEqual: "NUMBER_GREATER_THAN_EQ",
29
+ lessThanOrEqual: "NUMBER_LESS_THAN_EQ",
30
+ };
31
+ const stripEquals = (text) => {
32
+ const trimmed = text?.trim() ?? "";
33
+ return trimmed.startsWith("=") ? trimmed.slice(1).trim() : trimmed;
34
+ };
35
+ const TIME_PATTERN = /^(\d{1,2})(?::(\d{1,2}))?(?::(\d{1,2}))?\s*([AaPp][Mm]?)?$/;
36
+ /**
37
+ * Parse a typed time (`9:30`, `9:30 AM`, `21:05:10`, `24:00`, `0.5`) to
38
+ * Excel's serial. A bare non-negative number is taken as a serial already and
39
+ * kept whole (Excel's Time validation compares the full serial, so a
40
+ * date-time is out of a same-day range). `undefined` when unparsable.
41
+ */
42
+ export const parseTimeToSerial = (text) => {
43
+ const trimmed = text?.trim() ?? "";
44
+ if (!trimmed)
45
+ return undefined;
46
+ const asNumber = Number(trimmed);
47
+ if (Number.isFinite(asNumber)) {
48
+ return asNumber >= 0 ? asNumber : undefined;
49
+ }
50
+ const match = TIME_PATTERN.exec(trimmed);
51
+ if (!match)
52
+ return undefined;
53
+ let hours = Number(match[1]);
54
+ const minutes = Number(match[2] ?? 0);
55
+ const seconds = Number(match[3] ?? 0);
56
+ const meridiem = match[4]?.toLowerCase();
57
+ if (minutes > 59 || seconds > 59)
58
+ return undefined;
59
+ if (meridiem) {
60
+ if (hours < 1 || hours > 12)
61
+ return undefined;
62
+ if (meridiem.startsWith("p") && hours !== 12)
63
+ hours += 12;
64
+ if (meridiem.startsWith("a") && hours === 12)
65
+ hours = 0;
66
+ }
67
+ else if (hours > 24 || (hours === 24 && (minutes > 0 || seconds > 0))) {
68
+ // Excel accepts `24:00` as exactly 1.0 (midnight at the end of the day).
69
+ return undefined;
70
+ }
71
+ return (hours * 3600 + minutes * 60 + seconds) / 86400;
72
+ };
73
+ /** Render a day-fraction serial as `h:mm AM/PM` (seconds when non-zero). */
74
+ export const formatTimeSerial = (serial) => {
75
+ const total = Math.round((serial - Math.floor(serial)) * 86400) % 86400;
76
+ const hours24 = Math.floor(total / 3600);
77
+ const minutes = Math.floor((total % 3600) / 60);
78
+ const seconds = total % 60;
79
+ const meridiem = hours24 >= 12 ? "PM" : "AM";
80
+ const hours12 = hours24 % 12 === 0 ? 12 : hours24 % 12;
81
+ const mm = String(minutes).padStart(2, "0");
82
+ const ss = seconds ? `:${String(seconds).padStart(2, "0")}` : "";
83
+ return `${hours12}:${mm}${ss} ${meridiem}`;
84
+ };
85
+ /**
86
+ * A bound as it goes into a formula / `xlsxSource`: references and formulas
87
+ * verbatim (no `=`), times as serials, numbers as typed.
88
+ */
89
+ const toBoundFormula = (kind, text) => {
90
+ const raw = text?.trim() ?? "";
91
+ if (!raw)
92
+ return undefined;
93
+ if (raw.startsWith("="))
94
+ return stripEquals(raw);
95
+ if (kind === "time") {
96
+ const serial = parseTimeToSerial(raw);
97
+ return serial === undefined ? raw : String(serial);
98
+ }
99
+ return raw;
100
+ };
101
+ /** The A1 anchor (range top-left) a relative custom formula is written against. */
102
+ export const getValidationAnchor = (range) => cellToAddress(range
103
+ ? { rowIndex: range.startRowIndex, columnIndex: range.startColumnIndex }
104
+ : { rowIndex: 1, columnIndex: 1 }) ?? "A1";
105
+ const comparison = (subject, operator, a, b) => {
106
+ switch (operator) {
107
+ case "between":
108
+ return b === undefined
109
+ ? undefined
110
+ : `AND(${subject}>=${a},${subject}<=${b})`;
111
+ case "notBetween":
112
+ return b === undefined
113
+ ? undefined
114
+ : `OR(${subject}<${a},${subject}>${b})`;
115
+ case "equal":
116
+ return `${subject}=${a}`;
117
+ case "notEqual":
118
+ return `${subject}<>${a}`;
119
+ case "greaterThan":
120
+ return `${subject}>${a}`;
121
+ case "lessThan":
122
+ return `${subject}<${a}`;
123
+ case "greaterThanOrEqual":
124
+ return `${subject}>=${a}`;
125
+ case "lessThanOrEqual":
126
+ return `${subject}<=${a}`;
127
+ }
128
+ };
129
+ /**
130
+ * Build the `condition` + `xlsxSource` pair for an Excel kind. Returns
131
+ * `undefined` when a required bound is missing (the editor keeps the form
132
+ * open in that case).
133
+ */
134
+ export const buildExcelValidationRule = (spec, anchor) => {
135
+ const formula1 = toBoundFormula(spec.kind, spec.formula1);
136
+ const formula2 = toBoundFormula(spec.kind, spec.formula2);
137
+ if (!formula1)
138
+ return undefined;
139
+ if (isTwoSidedOperator(spec.operator) && !formula2)
140
+ return undefined;
141
+ const xlsxSource = {
142
+ type: spec.kind,
143
+ operator: spec.operator,
144
+ formula1,
145
+ ...(isTwoSidedOperator(spec.operator) ? { formula2 } : {}),
146
+ };
147
+ if (spec.kind === "time") {
148
+ const condition = {
149
+ type: NUMBER_CONDITION_BY_OPERATOR[spec.operator],
150
+ values: [
151
+ {
152
+ userEnteredValue: /^[A-Za-z$'!]/.test(formula1)
153
+ ? `=${formula1}`
154
+ : formula1,
155
+ },
156
+ ...(isTwoSidedOperator(spec.operator) && formula2
157
+ ? [
158
+ {
159
+ userEnteredValue: /^[A-Za-z$'!]/.test(formula2)
160
+ ? `=${formula2}`
161
+ : formula2,
162
+ },
163
+ ]
164
+ : []),
165
+ ],
166
+ };
167
+ return { condition, xlsxSource };
168
+ }
169
+ const subject = spec.kind === "textLength" ? `LEN(${anchor})` : anchor;
170
+ const body = comparison(subject, spec.operator, formula1, formula2);
171
+ if (!body)
172
+ return undefined;
173
+ const formula = spec.kind === "whole"
174
+ ? `=AND(ISNUMBER(${anchor}),${anchor}=INT(${anchor}),${body})`
175
+ : `=${body}`;
176
+ return {
177
+ condition: {
178
+ type: "CUSTOM_FORMULA",
179
+ values: [{ userEnteredValue: formula }],
180
+ },
181
+ xlsxSource,
182
+ };
183
+ };
184
+ /**
185
+ * Read an Excel kind back from a rule's `xlsxSource` (imported or authored
186
+ * here); `undefined` for every other rule. Time bounds come back as
187
+ * `h:mm AM/PM` text so the editor shows what the user would type.
188
+ */
189
+ export const readExcelValidationSpec = (rule) => {
190
+ const source = rule?.xlsxSource;
191
+ if (!source || !EXCEL_KINDS.has(source.type))
192
+ return undefined;
193
+ const operator = (source.operator && EXCEL_OPERATORS.has(source.operator)
194
+ ? source.operator
195
+ : "between");
196
+ const kind = source.type;
197
+ const display = (formula) => {
198
+ if (formula === undefined || formula === "")
199
+ return undefined;
200
+ const asNumber = Number(formula);
201
+ if (Number.isFinite(asNumber)) {
202
+ return kind === "time" ? formatTimeSerial(asNumber) : formula;
203
+ }
204
+ return `=${formula}`;
205
+ };
206
+ return {
207
+ kind,
208
+ operator,
209
+ formula1: display(source.formula1),
210
+ formula2: display(source.formula2),
211
+ };
212
+ };
213
+ const compare = (subject, operator, a, b) => {
214
+ switch (operator) {
215
+ case "between":
216
+ return (b !== undefined &&
217
+ subject >= Math.min(a, b) &&
218
+ subject <= Math.max(a, b));
219
+ case "notBetween":
220
+ return (b !== undefined &&
221
+ (subject < Math.min(a, b) || subject > Math.max(a, b)));
222
+ case "equal":
223
+ return subject === a;
224
+ case "notEqual":
225
+ return subject !== a;
226
+ case "greaterThan":
227
+ return subject > a;
228
+ case "lessThan":
229
+ return subject < a;
230
+ case "greaterThanOrEqual":
231
+ return subject >= a;
232
+ case "lessThanOrEqual":
233
+ return subject <= a;
234
+ }
235
+ };
236
+ /** A typed value that is a formula (`=…`) — validated on its result, not its text. */
237
+ export const isFormulaEntry = (value) => typeof value === "string" && value.trim().startsWith("=");
238
+ /**
239
+ * Decide an Excel-kind rule for a freshly typed value without the
240
+ * calculator — the commit-time enforcement Excel applies (`stop` blocks a
241
+ * decimal in a whole-number cell). `undefined` when the bounds are
242
+ * formulas / references, or when the typed value is itself a formula
243
+ * (Excel validates the formula's RESULT after the recalc, so `=5` is a
244
+ * valid whole number), so the caller falls back to the flag-only marker.
245
+ */
246
+ export const checkExcelValidationValue = (rule, value) => {
247
+ const source = rule?.xlsxSource;
248
+ if (!source || !EXCEL_KINDS.has(source.type))
249
+ return undefined;
250
+ if (isFormulaEntry(value))
251
+ return undefined;
252
+ const operator = (source.operator ?? "between");
253
+ if (!EXCEL_OPERATORS.has(operator))
254
+ return undefined;
255
+ const a = Number(source.formula1);
256
+ const b = source.formula2 === undefined ? undefined : Number(source.formula2);
257
+ if (!Number.isFinite(a))
258
+ return undefined;
259
+ if (isTwoSidedOperator(operator) &&
260
+ !(b !== undefined && Number.isFinite(b))) {
261
+ return undefined;
262
+ }
263
+ const text = typeof value === "boolean" ? (value ? "TRUE" : "FALSE") : value;
264
+ switch (source.type) {
265
+ case "textLength":
266
+ return compare(Array.from(text).length, operator, a, b);
267
+ case "whole": {
268
+ if (typeof value === "boolean")
269
+ return false;
270
+ const num = Number(text.trim());
271
+ if (!Number.isFinite(num) || !Number.isInteger(num))
272
+ return false;
273
+ return compare(num, operator, a, b);
274
+ }
275
+ case "time": {
276
+ if (typeof value === "boolean")
277
+ return false;
278
+ // Excel compares the cell's full serial against the bounds (a
279
+ // date-time such as 45292.5 is NOT between 9:00 and 12:00) — the same
280
+ // comparison the composed `NUMBER_*` condition makes for the marker.
281
+ const serial = parseTimeToSerial(text);
282
+ if (serial === undefined)
283
+ return false;
284
+ return compare(serial, operator, a, b);
285
+ }
286
+ }
287
+ };
@@ -0,0 +1,32 @@
1
+ import type { GridRange } from "@rowsncolumns/grid";
2
+ import type { CellData } from "../types";
3
+ /**
4
+ * Excel's Go To Special categories that can be decided from cell data alone.
5
+ * (Row / column differences, precedents / dependents and objects are served
6
+ * by the trace arrows and the drawing layer, not by this dialog.)
7
+ */
8
+ export type GoToSpecialKind = "blanks" | "constants" | "formulas" | "errors" | "notes" | "visible";
9
+ export declare const GO_TO_SPECIAL_OPTIONS: ReadonlyArray<{
10
+ kind: GoToSpecialKind;
11
+ label: string;
12
+ }>;
13
+ export type GoToSpecialContext<T extends CellData = CellData> = {
14
+ sheetId: number;
15
+ /** The area to scan — the current selection, or the used range for a single cell. */
16
+ range: GridRange;
17
+ getCellData(sheetId: number, rowIndex: number, columnIndex: number): T | null | undefined;
18
+ isRowHidden?(rowIndex: number): boolean;
19
+ isColumnHidden?(columnIndex: number): boolean;
20
+ /** Scan guard — Excel refuses huge Go To Special selections as well. */
21
+ maxCells?: number;
22
+ };
23
+ /** Cells scanned before the search gives up (returns what it found so far). */
24
+ export declare const GO_TO_SPECIAL_MAX_CELLS = 250000;
25
+ /**
26
+ * Resolve a Go To Special category to the matching cells of `range`, packed
27
+ * into per-row runs of adjacent columns (what the selection layer paints).
28
+ * Hidden rows / columns are skipped for every kind — Excel never selects
29
+ * into a hidden cell from Go To Special. Returns `[]` when nothing matches.
30
+ */
31
+ export declare const resolveGoToSpecial: <T extends CellData>(kind: GoToSpecialKind, context: GoToSpecialContext<T>) => GridRange[];
32
+ //# sourceMappingURL=go-to-special.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"go-to-special.d.ts","sourceRoot":"","sources":["../../../api/go-to-special.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,WAAW,GACX,UAAU,GACV,QAAQ,GACR,OAAO,GACP,SAAS,CAAC;AAEd,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC;IAChD,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAOA,CAAC;AAEF,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,KAAK,EAAE,SAAS,CAAC;IACjB,WAAW,CACT,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IACxB,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,cAAc,CAAC,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAmD/C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,QAAQ,EACnD,MAAM,eAAe,EACrB,SAAS,kBAAkB,CAAC,CAAC,CAAC,KAC7B,SAAS,EAoCX,CAAC"}
@@ -0,0 +1,96 @@
1
+ export const GO_TO_SPECIAL_OPTIONS = [
2
+ { kind: "notes", label: "Notes" },
3
+ { kind: "constants", label: "Constants" },
4
+ { kind: "formulas", label: "Formulas" },
5
+ { kind: "errors", label: "Errors" },
6
+ { kind: "blanks", label: "Blanks" },
7
+ { kind: "visible", label: "Visible cells only" },
8
+ ];
9
+ /** Cells scanned before the search gives up (returns what it found so far). */
10
+ export const GO_TO_SPECIAL_MAX_CELLS = 250_000;
11
+ const hasContent = (cell) => {
12
+ const ue = cell?.ue ?? cell?.userEnteredValue;
13
+ if (!ue)
14
+ return false;
15
+ return (ue.fv != null ||
16
+ ue.formulaValue != null ||
17
+ ue.sv != null ||
18
+ ue.stringValue != null ||
19
+ ue.nv != null ||
20
+ ue.numberValue != null ||
21
+ ue.bv != null ||
22
+ ue.boolValue != null);
23
+ };
24
+ const isFormula = (cell) => {
25
+ const ue = cell?.ue ?? cell?.userEnteredValue;
26
+ return typeof (ue?.fv ?? ue?.formulaValue) === "string";
27
+ };
28
+ const isError = (cell) => {
29
+ const ev = cell?.ev ?? cell?.effectiveValue;
30
+ const error = ev?.ev ?? ev?.errorValue;
31
+ return !!error && error.type === "Error";
32
+ };
33
+ const hasNote = (cell) => typeof cell?.note === "string" && cell.note.trim().length > 0;
34
+ const matches = (kind, cell) => {
35
+ switch (kind) {
36
+ case "blanks":
37
+ return !hasContent(cell);
38
+ case "constants":
39
+ return hasContent(cell) && !isFormula(cell);
40
+ case "formulas":
41
+ return isFormula(cell);
42
+ case "errors":
43
+ return isError(cell);
44
+ case "notes":
45
+ return hasNote(cell);
46
+ case "visible":
47
+ return true;
48
+ }
49
+ };
50
+ /**
51
+ * Resolve a Go To Special category to the matching cells of `range`, packed
52
+ * into per-row runs of adjacent columns (what the selection layer paints).
53
+ * Hidden rows / columns are skipped for every kind — Excel never selects
54
+ * into a hidden cell from Go To Special. Returns `[]` when nothing matches.
55
+ */
56
+ export const resolveGoToSpecial = (kind, context) => {
57
+ const { sheetId, range, getCellData, isRowHidden, isColumnHidden } = context;
58
+ const maxCells = context.maxCells ?? GO_TO_SPECIAL_MAX_CELLS;
59
+ const runs = [];
60
+ let scanned = 0;
61
+ for (let row = range.startRowIndex; row <= range.endRowIndex; row++) {
62
+ if (isRowHidden?.(row))
63
+ continue;
64
+ let runStart;
65
+ const flush = (endColumn) => {
66
+ if (runStart === undefined)
67
+ return;
68
+ runs.push({
69
+ startRowIndex: row,
70
+ endRowIndex: row,
71
+ startColumnIndex: runStart,
72
+ endColumnIndex: endColumn,
73
+ });
74
+ runStart = undefined;
75
+ };
76
+ for (let col = range.startColumnIndex; col <= range.endColumnIndex; col++) {
77
+ if (++scanned > maxCells) {
78
+ flush(col - 1);
79
+ return runs;
80
+ }
81
+ if (isColumnHidden?.(col)) {
82
+ flush(col - 1);
83
+ continue;
84
+ }
85
+ if (matches(kind, getCellData(sheetId, row, col))) {
86
+ if (runStart === undefined)
87
+ runStart = col;
88
+ }
89
+ else {
90
+ flush(col - 1);
91
+ }
92
+ }
93
+ flush(range.endColumnIndex);
94
+ }
95
+ return runs;
96
+ };
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from "react";
2
+ import type { DataValidationRule, ErrorValue } from "../types";
3
+ /**
4
+ * The data-validation part of a cell tooltip (both engines share it):
5
+ *
6
+ * - a cell flagged **Invalid** shows the rule's own error alert title/body
7
+ * (Excel's custom error message, #131) — the generic "violates its
8
+ * validation rule" text is only the fallback when the rule has none;
9
+ * - a formula **Error** keeps its own tooltip (nothing here);
10
+ * - otherwise the rule's input message (Excel's "show input message when
11
+ * cell is selected", surfaced on hover here — #130).
12
+ *
13
+ * Returns `undefined` when the rule contributes nothing, so the host's own
14
+ * tooltip content / the generic markers apply.
15
+ */
16
+ export declare const getDataValidationTooltipContent: (rule: DataValidationRule | null | undefined, error: ErrorValue | null | undefined) => ReactNode | undefined;
17
+ //# sourceMappingURL=validation-tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-tooltip.d.ts","sourceRoot":"","sources":["../../../api/validation-tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAsB/D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,+BAA+B,GAC1C,MAAM,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAC3C,OAAO,UAAU,GAAG,IAAI,GAAG,SAAS,KACnC,SAAS,GAAG,SAad,CAAC"}
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ const hasText = (message) => Boolean(message && (message.title || message.message));
3
+ const renderMessage = (message, accentClass) => (React.createElement("div", { className: `bg-rnc-background text-rnc-foreground p-2 shadow-md rounded-md border-l-4 border-solid ${accentClass} max-w-[300px] flex flex-col gap-0.5` },
4
+ message.title ? (React.createElement("div", { className: "font-bold font-system text-sm" }, message.title)) : null,
5
+ message.message ? (React.createElement("div", { className: "text-xs font-system whitespace-pre-wrap" }, message.message)) : null));
6
+ /**
7
+ * The data-validation part of a cell tooltip (both engines share it):
8
+ *
9
+ * - a cell flagged **Invalid** shows the rule's own error alert title/body
10
+ * (Excel's custom error message, #131) — the generic "violates its
11
+ * validation rule" text is only the fallback when the rule has none;
12
+ * - a formula **Error** keeps its own tooltip (nothing here);
13
+ * - otherwise the rule's input message (Excel's "show input message when
14
+ * cell is selected", surfaced on hover here — #130).
15
+ *
16
+ * Returns `undefined` when the rule contributes nothing, so the host's own
17
+ * tooltip content / the generic markers apply.
18
+ */
19
+ export const getDataValidationTooltipContent = (rule, error) => {
20
+ if (!rule)
21
+ return undefined;
22
+ if (error?.type === "Error")
23
+ return undefined;
24
+ if (error?.type === "Invalid") {
25
+ const alertMessage = rule.alert?.message;
26
+ return hasText(alertMessage)
27
+ ? renderMessage(alertMessage, "border-l-rnc-destructive")
28
+ : undefined;
29
+ }
30
+ const inputMessage = rule.inputMessage;
31
+ return hasText(inputMessage)
32
+ ? renderMessage(inputMessage, "border-l-blue-700")
33
+ : undefined;
34
+ };
@@ -0,0 +1,28 @@
1
+ import type { AdvancedFilterOptions } from "../types";
2
+ /**
3
+ * Open-state + payload for the Advanced Filter dialog (Excel's Data → Sort &
4
+ * Filter → Advanced). The context menu opens it with the list range around
5
+ * the active cell (A1 text); the host-mounted dialog resolves the typed
6
+ * references and routes the options back through `onApply`.
7
+ */
8
+ export type AdvancedFilterDialogRequest = {
9
+ /** A1 reference of the default "List range". */
10
+ listRange: string;
11
+ /** Routed to `onAdvancedFilter` by the opener. */
12
+ onApply: (options: AdvancedFilterOptions) => void;
13
+ };
14
+ export declare const advancedFilterDialogAtom: import("jotai").PrimitiveAtom<AdvancedFilterDialogRequest | null> & {
15
+ init: AdvancedFilterDialogRequest | null;
16
+ };
17
+ export declare const useAdvancedFilterDialogState: () => [AdvancedFilterDialogRequest | null, (args_0: AdvancedFilterDialogRequest | ((prev: AdvancedFilterDialogRequest | null) => AdvancedFilterDialogRequest | null) | null) => void];
18
+ /** Number of mounted `AdvancedFilterDialogHost`s — the menu item is offered only while > 0. */
19
+ export declare const advancedFilterDialogHostCountAtom: import("jotai").PrimitiveAtom<number> & {
20
+ init: number;
21
+ };
22
+ /** True when at least one `AdvancedFilterDialogHost` is mounted. */
23
+ export declare const useIsAdvancedFilterDialogMounted: () => boolean;
24
+ /** Registers the calling component as a dialog host for its lifetime. */
25
+ export declare const useRegisterAdvancedFilterDialogHost: () => void;
26
+ /** Returns a function that opens the host-mounted Advanced Filter dialog. */
27
+ export declare const useOpenAdvancedFilterDialog: () => (request: AdvancedFilterDialogRequest) => void;
28
+ //# sourceMappingURL=advanced-filter-dialog.atom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advanced-filter-dialog.atom.d.ts","sourceRoot":"","sources":["../../../atoms/advanced-filter-dialog.atom.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,OAAO,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,eAAO,MAAM,wBAAwB;;CAEpC,CAAC;AAEF,eAAO,MAAM,4BAA4B,uLACN,CAAC;AAEpC,+FAA+F;AAC/F,eAAO,MAAM,iCAAiC;;CAAU,CAAC;AAEzD,oEAAoE;AACpE,eAAO,MAAM,gCAAgC,eACQ,CAAC;AAEtD,yEAAyE;AACzE,eAAO,MAAM,mCAAmC,YAM/C,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,2BAA2B,kBAG1B,2BAA2B,SAGxC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
2
+ import { useCallback, useEffect } from "react";
3
+ export const advancedFilterDialogAtom = atom(null);
4
+ export const useAdvancedFilterDialogState = () => useAtom(advancedFilterDialogAtom);
5
+ /** Number of mounted `AdvancedFilterDialogHost`s — the menu item is offered only while > 0. */
6
+ export const advancedFilterDialogHostCountAtom = atom(0);
7
+ /** True when at least one `AdvancedFilterDialogHost` is mounted. */
8
+ export const useIsAdvancedFilterDialogMounted = () => useAtomValue(advancedFilterDialogHostCountAtom) > 0;
9
+ /** Registers the calling component as a dialog host for its lifetime. */
10
+ export const useRegisterAdvancedFilterDialogHost = () => {
11
+ const setCount = useSetAtom(advancedFilterDialogHostCountAtom);
12
+ useEffect(() => {
13
+ setCount((count) => count + 1);
14
+ return () => setCount((count) => count - 1);
15
+ }, [setCount]);
16
+ };
17
+ /** Returns a function that opens the host-mounted Advanced Filter dialog. */
18
+ export const useOpenAdvancedFilterDialog = () => {
19
+ const setRequest = useSetAtom(advancedFilterDialogAtom);
20
+ return useCallback((request) => setRequest(request), [setRequest]);
21
+ };
@@ -1,4 +1,5 @@
1
- import type { CellInterface } from "@rowsncolumns/grid";
1
+ import type { CellInterface, GridRange } from "@rowsncolumns/grid";
2
+ import type { GoToSpecialKind } from "../api/go-to-special";
2
3
  import type { NamedRange, SheetRange } from "../types";
3
4
  /**
4
5
  * Open-state + payload for the Go To dialog (Excel `Ctrl+G` / `F5`). The grid
@@ -14,6 +15,15 @@ export type GoToDialogRequest = {
14
15
  rowCount?: number;
15
16
  columnCount?: number;
16
17
  onNavigate: (range: SheetRange) => void;
18
+ /**
19
+ * Go To Special: resolves a category against the current selection (or
20
+ * the sheet) and selects the resulting runs. Absent when the grid cannot
21
+ * read cell data, in which case the dialog hides the "Special…" panel.
22
+ */
23
+ special?: {
24
+ resolve: (kind: GoToSpecialKind) => GridRange[];
25
+ onSelectRanges: (ranges: GridRange[]) => void;
26
+ };
17
27
  };
18
28
  export declare const goToDialogAtom: import("jotai").PrimitiveAtom<GoToDialogRequest | null> & {
19
29
  init: GoToDialogRequest | null;
@@ -1 +1 @@
1
- {"version":3,"file":"go-to-dialog.atom.d.ts","sourceRoot":"","sources":["../../../atoms/go-to-dialog.atom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,aAAa,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAClC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACzC,CAAC;AAEF,eAAO,MAAM,cAAc;;CAAuC,CAAC;AAEnE,eAAO,MAAM,kBAAkB,+IAAgC,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;CAAU,CAAC;AAE/C,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,eACQ,CAAC;AAE5C,yEAAyE;AACzE,eAAO,MAAM,yBAAyB,YAMrC,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,kBAGhB,iBAAiB,SAG9B,CAAC"}
1
+ {"version":3,"file":"go-to-dialog.atom.d.ts","sourceRoot":"","sources":["../../../atoms/go-to-dialog.atom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAInE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,aAAa,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAClC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,OAAO,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,SAAS,EAAE,CAAC;QAChD,cAAc,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;KAC/C,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,cAAc;;CAAuC,CAAC;AAEnE,eAAO,MAAM,kBAAkB,+IAAgC,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;CAAU,CAAC;AAE/C,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,eACQ,CAAC;AAE5C,yEAAyE;AACzE,eAAO,MAAM,yBAAyB,YAMrC,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,kBAGhB,iBAAiB,SAG9B,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { GoalSeekResult } from "../types";
2
+ /**
3
+ * Open-state + payload for the Goal Seek dialog (Excel's Data → What-If
4
+ * Analysis → Goal Seek). The context menu opens it with the active cell as
5
+ * the "Set cell"; the host-mounted dialog routes the three fields (as A1
6
+ * references) back through `onApply` and shows the search status it
7
+ * resolves to.
8
+ */
9
+ export type GoalSeekDialogRequest = {
10
+ /** A1 reference of the active cell — the default "Set cell". */
11
+ setCell: string;
12
+ /** Runs the search; resolves to the result, or `undefined` when the engine reports none. */
13
+ onApply: (setCell: string, targetValue: number, changingCell: string) => Promise<GoalSeekResult | undefined>;
14
+ };
15
+ export declare const goalSeekDialogAtom: import("jotai").PrimitiveAtom<GoalSeekDialogRequest | null> & {
16
+ init: GoalSeekDialogRequest | null;
17
+ };
18
+ export declare const useGoalSeekDialogState: () => [GoalSeekDialogRequest | null, (args_0: GoalSeekDialogRequest | ((prev: GoalSeekDialogRequest | null) => GoalSeekDialogRequest | null) | null) => void];
19
+ /** Number of mounted `GoalSeekDialogHost`s — the menu item is offered only while > 0. */
20
+ export declare const goalSeekDialogHostCountAtom: import("jotai").PrimitiveAtom<number> & {
21
+ init: number;
22
+ };
23
+ /** True when at least one `GoalSeekDialogHost` is mounted. */
24
+ export declare const useIsGoalSeekDialogMounted: () => boolean;
25
+ /** Registers the calling component as a dialog host for its lifetime. */
26
+ export declare const useRegisterGoalSeekDialogHost: () => void;
27
+ /** Returns a function that opens the host-mounted Goal Seek dialog. */
28
+ export declare const useOpenGoalSeekDialog: () => (request: GoalSeekDialogRequest) => void;
29
+ //# sourceMappingURL=goal-seek-dialog.atom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goal-seek-dialog.atom.d.ts","sourceRoot":"","sources":["../../../atoms/goal-seek-dialog.atom.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,OAAO,EAAE,CACP,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,KACjB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;CAC1C,CAAC;AAEF,eAAO,MAAM,kBAAkB;;CAA2C,CAAC;AAE3E,eAAO,MAAM,sBAAsB,+JAAoC,CAAC;AAExE,yFAAyF;AACzF,eAAO,MAAM,2BAA2B;;CAAU,CAAC;AAEnD,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B,eACQ,CAAC;AAEhD,yEAAyE;AACzE,eAAO,MAAM,6BAA6B,YAMzC,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,kBAGpB,qBAAqB,SAGlC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
2
+ import { useCallback, useEffect } from "react";
3
+ export const goalSeekDialogAtom = atom(null);
4
+ export const useGoalSeekDialogState = () => useAtom(goalSeekDialogAtom);
5
+ /** Number of mounted `GoalSeekDialogHost`s — the menu item is offered only while > 0. */
6
+ export const goalSeekDialogHostCountAtom = atom(0);
7
+ /** True when at least one `GoalSeekDialogHost` is mounted. */
8
+ export const useIsGoalSeekDialogMounted = () => useAtomValue(goalSeekDialogHostCountAtom) > 0;
9
+ /** Registers the calling component as a dialog host for its lifetime. */
10
+ export const useRegisterGoalSeekDialogHost = () => {
11
+ const setCount = useSetAtom(goalSeekDialogHostCountAtom);
12
+ useEffect(() => {
13
+ setCount((count) => count + 1);
14
+ return () => setCount((count) => count - 1);
15
+ }, [setCount]);
16
+ };
17
+ /** Returns a function that opens the host-mounted Goal Seek dialog. */
18
+ export const useOpenGoalSeekDialog = () => {
19
+ const setRequest = useSetAtom(goalSeekDialogAtom);
20
+ return useCallback((request) => setRequest(request), [setRequest]);
21
+ };
@@ -19,4 +19,5 @@ export * from "./paste-special-dialog.atom";
19
19
  export * from "./go-to-dialog.atom";
20
20
  export * from "./evaluate-formula-dialog.atom";
21
21
  export * from "./remove-duplicates-dialog.atom";
22
+ export * from "./watch-window.atom";
22
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../atoms/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iCAAiC,CAAC;AAChD,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../atoms/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iCAAiC,CAAC;AAChD,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,qBAAqB,CAAC"}
@@ -19,3 +19,4 @@ export * from "./paste-special-dialog.atom";
19
19
  export * from "./go-to-dialog.atom";
20
20
  export * from "./evaluate-formula-dialog.atom";
21
21
  export * from "./remove-duplicates-dialog.atom";
22
+ export * from "./watch-window.atom";