@rowsncolumns/spreadsheet 10.2.73 → 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 (270) hide show
  1. package/dist/cjs/api/evaluate-formula.d.ts +17 -0
  2. package/dist/cjs/api/evaluate-formula.d.ts.map +1 -0
  3. package/dist/cjs/api/evaluate-formula.js +141 -0
  4. package/dist/cjs/api/excel-validation-kinds.d.ts +71 -0
  5. package/dist/cjs/api/excel-validation-kinds.d.ts.map +1 -0
  6. package/dist/cjs/api/excel-validation-kinds.js +304 -0
  7. package/dist/cjs/api/go-to-special.d.ts +32 -0
  8. package/dist/cjs/api/go-to-special.d.ts.map +1 -0
  9. package/dist/cjs/api/go-to-special.js +111 -0
  10. package/dist/cjs/api/go-to.d.ts +19 -0
  11. package/dist/cjs/api/go-to.d.ts.map +1 -0
  12. package/dist/cjs/api/go-to.js +49 -0
  13. package/dist/cjs/api/validation-tooltip.d.ts +17 -0
  14. package/dist/cjs/api/validation-tooltip.d.ts.map +1 -0
  15. package/dist/cjs/api/validation-tooltip.js +42 -0
  16. package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts +28 -0
  17. package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
  18. package/dist/cjs/atoms/advanced-filter-dialog.atom.js +32 -0
  19. package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
  20. package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
  21. package/dist/cjs/atoms/evaluate-formula-dialog.atom.js +36 -0
  22. package/dist/cjs/atoms/go-to-dialog.atom.d.ts +46 -0
  23. package/dist/cjs/atoms/go-to-dialog.atom.d.ts.map +1 -0
  24. package/dist/cjs/atoms/go-to-dialog.atom.js +34 -0
  25. package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts +29 -0
  26. package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
  27. package/dist/cjs/atoms/goal-seek-dialog.atom.js +30 -0
  28. package/dist/cjs/atoms/index.d.ts +5 -0
  29. package/dist/cjs/atoms/index.d.ts.map +1 -1
  30. package/dist/cjs/atoms/index.js +5 -0
  31. package/dist/cjs/atoms/paste-special-dialog.atom.d.ts +29 -0
  32. package/dist/cjs/atoms/paste-special-dialog.atom.d.ts.map +1 -0
  33. package/dist/cjs/atoms/paste-special-dialog.atom.js +34 -0
  34. package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
  35. package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
  36. package/dist/cjs/atoms/remove-duplicates-dialog.atom.js +37 -0
  37. package/dist/cjs/atoms/subtotal-dialog.atom.d.ts +39 -0
  38. package/dist/cjs/atoms/subtotal-dialog.atom.d.ts.map +1 -0
  39. package/dist/cjs/atoms/subtotal-dialog.atom.js +30 -0
  40. package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts +34 -0
  41. package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
  42. package/dist/cjs/atoms/text-to-columns-dialog.atom.js +36 -0
  43. package/dist/cjs/atoms/watch-window.atom.d.ts +37 -0
  44. package/dist/cjs/atoms/watch-window.atom.d.ts.map +1 -0
  45. package/dist/cjs/atoms/watch-window.atom.js +61 -0
  46. package/dist/cjs/components/advanced-filter-dialog/index.d.ts +28 -0
  47. package/dist/cjs/components/advanced-filter-dialog/index.d.ts.map +1 -0
  48. package/dist/cjs/components/advanced-filter-dialog/index.js +134 -0
  49. package/dist/cjs/components/calculation-options-dialog/index.d.ts +33 -0
  50. package/dist/cjs/components/calculation-options-dialog/index.d.ts.map +1 -0
  51. package/dist/cjs/components/calculation-options-dialog/index.js +69 -0
  52. package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
  53. package/dist/cjs/components/canvas-grid/index.js +208 -19
  54. package/dist/cjs/components/canvas-grid/typings.d.ts +72 -9
  55. package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
  56. package/dist/cjs/components/cell/alignment.d.ts +30 -0
  57. package/dist/cjs/components/cell/alignment.d.ts.map +1 -1
  58. package/dist/cjs/components/cell/alignment.js +56 -1
  59. package/dist/cjs/components/cell/index.d.ts.map +1 -1
  60. package/dist/cjs/components/cell/index.js +15 -7
  61. package/dist/cjs/components/cell-diff/index.d.ts +3 -2
  62. package/dist/cjs/components/cell-diff/index.d.ts.map +1 -1
  63. package/dist/cjs/components/cell-editor/index.d.ts.map +1 -1
  64. package/dist/cjs/components/cell-editor/index.js +2 -1
  65. package/dist/cjs/components/context-menu/index.d.ts +21 -3
  66. package/dist/cjs/components/context-menu/index.d.ts.map +1 -1
  67. package/dist/cjs/components/context-menu/index.js +256 -6
  68. package/dist/cjs/components/context-menu/total-row-functions.d.ts +32 -0
  69. package/dist/cjs/components/context-menu/total-row-functions.d.ts.map +1 -0
  70. package/dist/cjs/components/context-menu/total-row-functions.js +62 -0
  71. package/dist/cjs/components/embeds/embed-component.d.ts.map +1 -1
  72. package/dist/cjs/components/embeds/embed-component.js +16 -1
  73. package/dist/cjs/components/evaluate-formula-dialog/index.d.ts +34 -0
  74. package/dist/cjs/components/evaluate-formula-dialog/index.d.ts.map +1 -0
  75. package/dist/cjs/components/evaluate-formula-dialog/index.js +106 -0
  76. package/dist/cjs/components/go-to-dialog/index.d.ts +45 -0
  77. package/dist/cjs/components/go-to-dialog/index.d.ts.map +1 -0
  78. package/dist/cjs/components/go-to-dialog/index.js +136 -0
  79. package/dist/cjs/components/goal-seek-dialog/index.d.ts +32 -0
  80. package/dist/cjs/components/goal-seek-dialog/index.d.ts.map +1 -0
  81. package/dist/cjs/components/goal-seek-dialog/index.js +149 -0
  82. package/dist/cjs/components/paste-special-dialog/index.d.ts +29 -0
  83. package/dist/cjs/components/paste-special-dialog/index.d.ts.map +1 -0
  84. package/dist/cjs/components/paste-special-dialog/index.js +99 -0
  85. package/dist/cjs/components/remove-duplicates-dialog/index.d.ts +37 -0
  86. package/dist/cjs/components/remove-duplicates-dialog/index.d.ts.map +1 -0
  87. package/dist/cjs/components/remove-duplicates-dialog/index.js +91 -0
  88. package/dist/cjs/components/subtotal-dialog/index.d.ts +30 -0
  89. package/dist/cjs/components/subtotal-dialog/index.d.ts.map +1 -0
  90. package/dist/cjs/components/subtotal-dialog/index.js +126 -0
  91. package/dist/cjs/components/text-to-columns-dialog/index.d.ts +37 -0
  92. package/dist/cjs/components/text-to-columns-dialog/index.d.ts.map +1 -0
  93. package/dist/cjs/components/text-to-columns-dialog/index.js +166 -0
  94. package/dist/cjs/components/toolbar/background-color-selector.d.ts +13 -1
  95. package/dist/cjs/components/toolbar/background-color-selector.d.ts.map +1 -1
  96. package/dist/cjs/components/toolbar/background-color-selector.js +57 -11
  97. package/dist/cjs/components/toolbar/cell-style-selector.d.ts +15 -2
  98. package/dist/cjs/components/toolbar/cell-style-selector.d.ts.map +1 -1
  99. package/dist/cjs/components/toolbar/cell-style-selector.js +56 -3
  100. package/dist/cjs/components/toolbar/fill-selectors.d.ts +49 -0
  101. package/dist/cjs/components/toolbar/fill-selectors.d.ts.map +1 -0
  102. package/dist/cjs/components/toolbar/fill-selectors.js +196 -0
  103. package/dist/cjs/components/toolbar/index.d.ts +2 -0
  104. package/dist/cjs/components/toolbar/index.d.ts.map +1 -1
  105. package/dist/cjs/components/toolbar/index.js +2 -0
  106. package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
  107. package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
  108. package/dist/cjs/components/toolbar/text-horizontal-align-selector.js +23 -4
  109. package/dist/cjs/components/toolbar/text-rotation-selector.d.ts +29 -0
  110. package/dist/cjs/components/toolbar/text-rotation-selector.d.ts.map +1 -0
  111. package/dist/cjs/components/toolbar/text-rotation-selector.js +78 -0
  112. package/dist/cjs/components/toolbar/text-wrap-selector.d.ts +7 -1
  113. package/dist/cjs/components/toolbar/text-wrap-selector.d.ts.map +1 -1
  114. package/dist/cjs/components/toolbar/text-wrap-selector.js +10 -3
  115. package/dist/cjs/components/watch-window/index.d.ts +25 -0
  116. package/dist/cjs/components/watch-window/index.d.ts.map +1 -0
  117. package/dist/cjs/components/watch-window/index.js +63 -0
  118. package/dist/cjs/hooks/use-cell-renderer.d.ts.map +1 -1
  119. package/dist/cjs/hooks/use-cell-renderer.js +17 -6
  120. package/dist/cjs/hooks/use-copy-paste-cut.d.ts +20 -1
  121. package/dist/cjs/hooks/use-copy-paste-cut.d.ts.map +1 -1
  122. package/dist/cjs/hooks/use-copy-paste-cut.js +25 -20
  123. package/dist/cjs/hooks/use-get-tooltip.d.ts.map +1 -1
  124. package/dist/cjs/hooks/use-get-tooltip.js +7 -2
  125. package/dist/cjs/hooks/use-keyboard-handler.d.ts +17 -2
  126. package/dist/cjs/hooks/use-keyboard-handler.d.ts.map +1 -1
  127. package/dist/cjs/hooks/use-keyboard-handler.js +206 -31
  128. package/dist/cjs/index.d.ts +14 -1
  129. package/dist/cjs/index.d.ts.map +1 -1
  130. package/dist/cjs/index.js +15 -1
  131. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  132. package/dist/cjs/types.d.ts +289 -4
  133. package/dist/cjs/types.d.ts.map +1 -1
  134. package/dist/cjs/types.js +19 -1
  135. package/dist/esm/api/evaluate-formula.d.ts +17 -0
  136. package/dist/esm/api/evaluate-formula.d.ts.map +1 -0
  137. package/dist/esm/api/evaluate-formula.js +109 -0
  138. package/dist/esm/api/excel-validation-kinds.d.ts +71 -0
  139. package/dist/esm/api/excel-validation-kinds.d.ts.map +1 -0
  140. package/dist/esm/api/excel-validation-kinds.js +287 -0
  141. package/dist/esm/api/go-to-special.d.ts +32 -0
  142. package/dist/esm/api/go-to-special.d.ts.map +1 -0
  143. package/dist/esm/api/go-to-special.js +96 -0
  144. package/dist/esm/api/go-to.d.ts +19 -0
  145. package/dist/esm/api/go-to.d.ts.map +1 -0
  146. package/dist/esm/api/go-to.js +41 -0
  147. package/dist/esm/api/validation-tooltip.d.ts +17 -0
  148. package/dist/esm/api/validation-tooltip.d.ts.map +1 -0
  149. package/dist/esm/api/validation-tooltip.js +34 -0
  150. package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts +28 -0
  151. package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
  152. package/dist/esm/atoms/advanced-filter-dialog.atom.js +21 -0
  153. package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
  154. package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
  155. package/dist/esm/atoms/evaluate-formula-dialog.atom.js +25 -0
  156. package/dist/esm/atoms/go-to-dialog.atom.d.ts +46 -0
  157. package/dist/esm/atoms/go-to-dialog.atom.d.ts.map +1 -0
  158. package/dist/esm/atoms/go-to-dialog.atom.js +25 -0
  159. package/dist/esm/atoms/goal-seek-dialog.atom.d.ts +29 -0
  160. package/dist/esm/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
  161. package/dist/esm/atoms/goal-seek-dialog.atom.js +21 -0
  162. package/dist/esm/atoms/index.d.ts +5 -0
  163. package/dist/esm/atoms/index.d.ts.map +1 -1
  164. package/dist/esm/atoms/index.js +5 -0
  165. package/dist/esm/atoms/paste-special-dialog.atom.d.ts +29 -0
  166. package/dist/esm/atoms/paste-special-dialog.atom.d.ts.map +1 -0
  167. package/dist/esm/atoms/paste-special-dialog.atom.js +25 -0
  168. package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
  169. package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
  170. package/dist/esm/atoms/remove-duplicates-dialog.atom.js +26 -0
  171. package/dist/esm/atoms/subtotal-dialog.atom.d.ts +39 -0
  172. package/dist/esm/atoms/subtotal-dialog.atom.d.ts.map +1 -0
  173. package/dist/esm/atoms/subtotal-dialog.atom.js +21 -0
  174. package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts +34 -0
  175. package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
  176. package/dist/esm/atoms/text-to-columns-dialog.atom.js +25 -0
  177. package/dist/esm/atoms/watch-window.atom.d.ts +37 -0
  178. package/dist/esm/atoms/watch-window.atom.d.ts.map +1 -0
  179. package/dist/esm/atoms/watch-window.atom.js +37 -0
  180. package/dist/esm/components/advanced-filter-dialog/index.d.ts +28 -0
  181. package/dist/esm/components/advanced-filter-dialog/index.d.ts.map +1 -0
  182. package/dist/esm/components/advanced-filter-dialog/index.js +125 -0
  183. package/dist/esm/components/calculation-options-dialog/index.d.ts +33 -0
  184. package/dist/esm/components/calculation-options-dialog/index.d.ts.map +1 -0
  185. package/dist/esm/components/calculation-options-dialog/index.js +61 -0
  186. package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
  187. package/dist/esm/components/canvas-grid/index.js +202 -19
  188. package/dist/esm/components/canvas-grid/typings.d.ts +72 -9
  189. package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
  190. package/dist/esm/components/cell/alignment.d.ts +30 -0
  191. package/dist/esm/components/cell/alignment.d.ts.map +1 -1
  192. package/dist/esm/components/cell/alignment.js +51 -0
  193. package/dist/esm/components/cell/index.d.ts.map +1 -1
  194. package/dist/esm/components/cell/index.js +16 -8
  195. package/dist/esm/components/cell-diff/index.d.ts +3 -2
  196. package/dist/esm/components/cell-diff/index.d.ts.map +1 -1
  197. package/dist/esm/components/cell-editor/index.d.ts.map +1 -1
  198. package/dist/esm/components/cell-editor/index.js +2 -1
  199. package/dist/esm/components/context-menu/index.d.ts +21 -3
  200. package/dist/esm/components/context-menu/index.d.ts.map +1 -1
  201. package/dist/esm/components/context-menu/index.js +251 -9
  202. package/dist/esm/components/context-menu/total-row-functions.d.ts +32 -0
  203. package/dist/esm/components/context-menu/total-row-functions.d.ts.map +1 -0
  204. package/dist/esm/components/context-menu/total-row-functions.js +50 -0
  205. package/dist/esm/components/embeds/embed-component.d.ts.map +1 -1
  206. package/dist/esm/components/embeds/embed-component.js +17 -1
  207. package/dist/esm/components/evaluate-formula-dialog/index.d.ts +34 -0
  208. package/dist/esm/components/evaluate-formula-dialog/index.d.ts.map +1 -0
  209. package/dist/esm/components/evaluate-formula-dialog/index.js +86 -0
  210. package/dist/esm/components/go-to-dialog/index.d.ts +45 -0
  211. package/dist/esm/components/go-to-dialog/index.d.ts.map +1 -0
  212. package/dist/esm/components/go-to-dialog/index.js +124 -0
  213. package/dist/esm/components/goal-seek-dialog/index.d.ts +32 -0
  214. package/dist/esm/components/goal-seek-dialog/index.d.ts.map +1 -0
  215. package/dist/esm/components/goal-seek-dialog/index.js +125 -0
  216. package/dist/esm/components/paste-special-dialog/index.d.ts +29 -0
  217. package/dist/esm/components/paste-special-dialog/index.d.ts.map +1 -0
  218. package/dist/esm/components/paste-special-dialog/index.js +90 -0
  219. package/dist/esm/components/remove-duplicates-dialog/index.d.ts +37 -0
  220. package/dist/esm/components/remove-duplicates-dialog/index.d.ts.map +1 -0
  221. package/dist/esm/components/remove-duplicates-dialog/index.js +82 -0
  222. package/dist/esm/components/subtotal-dialog/index.d.ts +30 -0
  223. package/dist/esm/components/subtotal-dialog/index.d.ts.map +1 -0
  224. package/dist/esm/components/subtotal-dialog/index.js +115 -0
  225. package/dist/esm/components/text-to-columns-dialog/index.d.ts +37 -0
  226. package/dist/esm/components/text-to-columns-dialog/index.d.ts.map +1 -0
  227. package/dist/esm/components/text-to-columns-dialog/index.js +147 -0
  228. package/dist/esm/components/toolbar/background-color-selector.d.ts +13 -1
  229. package/dist/esm/components/toolbar/background-color-selector.d.ts.map +1 -1
  230. package/dist/esm/components/toolbar/background-color-selector.js +61 -11
  231. package/dist/esm/components/toolbar/cell-style-selector.d.ts +15 -2
  232. package/dist/esm/components/toolbar/cell-style-selector.d.ts.map +1 -1
  233. package/dist/esm/components/toolbar/cell-style-selector.js +46 -2
  234. package/dist/esm/components/toolbar/fill-selectors.d.ts +49 -0
  235. package/dist/esm/components/toolbar/fill-selectors.d.ts.map +1 -0
  236. package/dist/esm/components/toolbar/fill-selectors.js +162 -0
  237. package/dist/esm/components/toolbar/index.d.ts +2 -0
  238. package/dist/esm/components/toolbar/index.d.ts.map +1 -1
  239. package/dist/esm/components/toolbar/index.js +2 -0
  240. package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
  241. package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
  242. package/dist/esm/components/toolbar/text-horizontal-align-selector.js +21 -5
  243. package/dist/esm/components/toolbar/text-rotation-selector.d.ts +29 -0
  244. package/dist/esm/components/toolbar/text-rotation-selector.d.ts.map +1 -0
  245. package/dist/esm/components/toolbar/text-rotation-selector.js +71 -0
  246. package/dist/esm/components/toolbar/text-wrap-selector.d.ts +7 -1
  247. package/dist/esm/components/toolbar/text-wrap-selector.d.ts.map +1 -1
  248. package/dist/esm/components/toolbar/text-wrap-selector.js +10 -3
  249. package/dist/esm/components/watch-window/index.d.ts +25 -0
  250. package/dist/esm/components/watch-window/index.d.ts.map +1 -0
  251. package/dist/esm/components/watch-window/index.js +54 -0
  252. package/dist/esm/hooks/use-cell-renderer.d.ts.map +1 -1
  253. package/dist/esm/hooks/use-cell-renderer.js +17 -6
  254. package/dist/esm/hooks/use-copy-paste-cut.d.ts +20 -1
  255. package/dist/esm/hooks/use-copy-paste-cut.d.ts.map +1 -1
  256. package/dist/esm/hooks/use-copy-paste-cut.js +8 -3
  257. package/dist/esm/hooks/use-get-tooltip.d.ts.map +1 -1
  258. package/dist/esm/hooks/use-get-tooltip.js +7 -2
  259. package/dist/esm/hooks/use-keyboard-handler.d.ts +17 -2
  260. package/dist/esm/hooks/use-keyboard-handler.d.ts.map +1 -1
  261. package/dist/esm/hooks/use-keyboard-handler.js +193 -31
  262. package/dist/esm/index.d.ts +14 -1
  263. package/dist/esm/index.d.ts.map +1 -1
  264. package/dist/esm/index.js +13 -0
  265. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  266. package/dist/esm/types.d.ts +289 -4
  267. package/dist/esm/types.d.ts.map +1 -1
  268. package/dist/esm/types.js +18 -0
  269. package/dist/spreadsheet.min.css +1 -1
  270. package/package.json +6 -6
@@ -1,13 +1,19 @@
1
1
  import { Direction, isCellWithinBounds, selectionFromActiveCell, } from "@rowsncolumns/grid";
2
- import { ArrowLeftIcon, ArrowRightIcon, ChevronRightIcon, ClipboardIcon, CopyIcon, Cross1Icon, DotsVerticalIcon, EyeNoneIcon, EyeOpenIcon, FunnelIcon, MdBrush, MdOutlineFactCheck, PaintBrush, PlusIcon, ReaderIcon, RemoveDuplicatesIcon, ScissorsIcon, SizeIcon, SortDownIcon, SortUpIcon, SpaceBetweenHorizontallyIcon, TableIcon, TrashIcon, ViewGridIcon, WandIcon, } from "@rowsncolumns/icons";
3
- import { DropdownLeftSlot, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownRightSlot, } from "@rowsncolumns/ui";
4
- import { number2Alpha } from "@rowsncolumns/utils";
2
+ import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronRightIcon, ClipboardIcon, CodeIcon, CopyIcon, Cross1Icon, DotsVerticalIcon, EyeNoneIcon, EyeOpenIcon, FunnelIcon, MdBrush, MdOutlineFactCheck, PaintBrush, PlusIcon, ReaderIcon, RemoveDuplicatesIcon, ScissorsIcon, SizeIcon, SortDownIcon, SortUpIcon, SpaceBetweenHorizontallyIcon, TableIcon, TrashIcon, ViewGridIcon, WandIcon, } from "@rowsncolumns/icons";
3
+ import { DropdownLeftSlot, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownRightSlot, } from "@rowsncolumns/ui";
4
+ import { addressToCell, cellToAddress, number2Alpha } from "@rowsncolumns/utils";
5
5
  import React, { useCallback, useMemo, useRef } from "react";
6
6
  import { getProtectedRange, isProtectedRange } from "../../api/validation";
7
7
  import { useGetContentfulRangeAroundCell } from "../../atoms";
8
+ import { useIsAdvancedFilterDialogMounted, useOpenAdvancedFilterDialog, } from "../../atoms/advanced-filter-dialog.atom";
9
+ import { useIsGoalSeekDialogMounted, useOpenGoalSeekDialog, } from "../../atoms/goal-seek-dialog.atom";
10
+ import { useIsRemoveDuplicatesDialogMounted, useOpenRemoveDuplicatesDialog, } from "../../atoms/remove-duplicates-dialog.atom";
11
+ import { useIsSubtotalDialogMounted, useOpenSubtotalDialog, } from "../../atoms/subtotal-dialog.atom";
12
+ import { useIsTextToColumnsDialogMounted, useOpenTextToColumnsDialog, } from "../../atoms/text-to-columns-dialog.atom";
8
13
  import { BLANK_LABEL } from "../../config";
9
14
  import { createArrayRange, generateMultiDimTitle } from "../../helpers";
10
- export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selections, basicFilter, isTableHeader, onClearContents, onHideColumn, onHideRow, onGroupRows, onUngroupRows, onGroupColumns, onUngroupColumns, onDeleteRow, onDeleteColumn, onInsertColumn, onInsertRow, onInsertCellsShiftDown, onInsertCellsShiftRight, onDeleteCellsShiftLeft, onDeleteCellsShiftUp, onFreezeRow, onFreezeColumn, onCopy, onCut, onPaste, onInsertNote, onProtectRange, onUnProtectRange, onSortRange, onSortColumn, onRequestDefineNamedRange, onRequestResize, onRequestEditTable, onRequestEditPivotTable, onRequestDeletePivotTable, onPivotDrillDown, onRequestFormatCells, onRequestConditionalFormat, onRequestDataValidation, onRemoveTable, onClearFormatting, onCreateTable, onCreateBasicFilter, onFilterTable, onSortTable, getFormattedValuesFromRange, focusSheet, isSheetFocused, getFormattedValue, onInsertTableColumn, onDeleteTableColumn, onInsertTableRow, onDeleteTableRow, onSplitTextToColumns, onRemoveDuplicates, isHiddenColumn, isHiddenRow, onShowColumn, onShowRow, tables, pivotTables, protectedRanges, sheetId, frozenRows, frozenColumns, selectedRowHeadersIds, selectedColumnHeadersIds, readonly = false, onAddQuickEdit, enableMagicFill, }) => {
15
+ import { getTotalRowFunctionFormula, getTotalRowFunctionFromFormula, TOTAL_ROW_FUNCTIONS, } from "./total-row-functions";
16
+ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selections, basicFilter, isTableHeader, onClearContents, onHideColumn, onHideRow, onGroupRows, onUngroupRows, onGroupColumns, onUngroupColumns, onDeleteRow, onDeleteColumn, onInsertColumn, onInsertRow, onInsertCellsShiftDown, onInsertCellsShiftRight, onDeleteCellsShiftLeft, onDeleteCellsShiftUp, onFreezeRow, onFreezeColumn, onCopy, onCut, onPaste, onRequestPasteSpecial, onRequestEvaluateFormula, onRequestAddWatch, isActiveCellFormula, onInsertNote, onProtectRange, onUnProtectRange, onSortRange, onSortColumn, onRequestDefineNamedRange, onRequestResize, onRequestEditTable, onRequestEditPivotTable, onRequestDeletePivotTable, onPivotDrillDown, onRequestFormatCells, onRequestConditionalFormat, onRequestDataValidation, onRemoveTable, onClearFormatting, onCreateTable, onCreateBasicFilter, onFilterTable, onSortTable, getFormattedValuesFromRange, focusSheet, isSheetFocused, getFormattedValue, onInsertTableColumn, onDeleteTableColumn, onInsertTableRow, onDeleteTableRow, onSplitTextToColumns, onRemoveDuplicates, onGoalSeek, onSubtotal, onAdvancedFilter, onChange, getCellData, isHiddenColumn, isHiddenRow, onShowColumn, onShowRow, tables, pivotTables, protectedRanges, sheetId, frozenRows, frozenColumns, selectedRowHeadersIds, selectedColumnHeadersIds, readonly = false, onAddQuickEdit, enableMagicFill, }) => {
11
17
  const isHeader = isColumnHeader || isRowHeader;
12
18
  const shouldFocusSheetRef = useRef(true);
13
19
  const multiColumnTitle = generateMultiDimTitle(selectedColumnHeadersIds, "y");
@@ -37,6 +43,184 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
37
43
  const lastSelectedRowIndex = selectedRowHeadersIds.reduce((m, v) => (v > m ? v : m), -Infinity);
38
44
  // Get contentful range
39
45
  const getContentfulRangeAroundCell = useGetContentfulRangeAroundCell();
46
+ // The data block the data tools act on: a table's own rows, the selection,
47
+ // or (from a lone cell) the contentful region around it — like Excel.
48
+ const dataRangeAroundSelection = useCallback(() => {
49
+ if (activeTable) {
50
+ return {
51
+ ...activeTable.range,
52
+ endRowIndex: activeTable.range.endRowIndex - (activeTable.totalRow ? 1 : 0),
53
+ };
54
+ }
55
+ if (selections.length) {
56
+ return selection.range;
57
+ }
58
+ return getContentfulRangeAroundCell?.(sheetId, activeCell) ?? selection.range;
59
+ }, [activeTable, selections.length, selection.range, getContentfulRangeAroundCell, sheetId, activeCell]);
60
+ // Excel pre-ticks "my data has headers" when the first row reads as labels:
61
+ // every cell filled and none of them a number.
62
+ const headerColumnsOf = useCallback((range) => {
63
+ const columns = [];
64
+ for (let columnIndex = range.startColumnIndex; columnIndex <= range.endColumnIndex; columnIndex++) {
65
+ const header = activeTable?.columns?.[columnIndex - activeTable.range.startColumnIndex]
66
+ ?.name ?? getFormattedValue(sheetId, range.startRowIndex, columnIndex);
67
+ columns.push({
68
+ columnIndex,
69
+ label: `Column ${number2Alpha(columnIndex - 1)}`,
70
+ header: header || undefined,
71
+ });
72
+ }
73
+ const hasHeader = activeTable
74
+ ? (activeTable.headerRow ?? true)
75
+ : range.endRowIndex > range.startRowIndex &&
76
+ columns.every((column) => !!column.header && Number.isNaN(Number(column.header)));
77
+ return { columns, hasHeader };
78
+ }, [activeTable, getFormattedValue, sheetId]);
79
+ const rangeAddress = (range) => `${cellToAddress({ rowIndex: range.startRowIndex, columnIndex: range.startColumnIndex })}:${cellToAddress({ rowIndex: range.endRowIndex, columnIndex: range.endColumnIndex })}`;
80
+ // Text to Columns → Fixed width (dialog with a ruler over the selected column).
81
+ const isTextToColumnsDialogMounted = useIsTextToColumnsDialogMounted();
82
+ const openTextToColumnsDialog = useOpenTextToColumnsDialog();
83
+ const handleTextToColumnsDialog = useCallback(() => {
84
+ const range = selections.length ? selection.range : dataRangeAroundSelection();
85
+ const columnIndex = selections.length ? range.startColumnIndex : activeCell.columnIndex;
86
+ const preview = [];
87
+ const firstRow = selections.length ? range.startRowIndex : activeCell.rowIndex;
88
+ const lastRow = selections.length ? range.endRowIndex : range.endRowIndex;
89
+ for (let rowIndex = firstRow; rowIndex <= lastRow && preview.length < 8; rowIndex++) {
90
+ preview.push(getFormattedValue(sheetId, rowIndex, columnIndex) ?? "");
91
+ }
92
+ const target = selections.length
93
+ ? selections
94
+ : [
95
+ {
96
+ range: {
97
+ startRowIndex: activeCell.rowIndex,
98
+ endRowIndex: range.endRowIndex,
99
+ startColumnIndex: columnIndex,
100
+ endColumnIndex: columnIndex,
101
+ },
102
+ },
103
+ ];
104
+ openTextToColumnsDialog({
105
+ preview,
106
+ onApply: ({ delimiter, columnBreaks }) => onSplitTextToColumns?.(sheetId, activeCell, target, delimiter, { columnBreaks }),
107
+ });
108
+ }, [
109
+ selections,
110
+ selection.range,
111
+ dataRangeAroundSelection,
112
+ activeCell,
113
+ getFormattedValue,
114
+ sheetId,
115
+ openTextToColumnsDialog,
116
+ onSplitTextToColumns,
117
+ ]);
118
+ // Data → Subtotal.
119
+ const isSubtotalDialogMounted = useIsSubtotalDialogMounted();
120
+ const openSubtotalDialog = useOpenSubtotalDialog();
121
+ const handleSubtotal = useCallback(() => {
122
+ const range = dataRangeAroundSelection();
123
+ const { columns, hasHeader } = headerColumnsOf(range);
124
+ openSubtotalDialog({
125
+ columns,
126
+ hasHeader,
127
+ onApply: (choices) => onSubtotal?.(sheetId, { range, ...choices }),
128
+ });
129
+ }, [dataRangeAroundSelection, headerColumnsOf, openSubtotalDialog, onSubtotal, sheetId]);
130
+ // Data → Advanced filter.
131
+ const isAdvancedFilterDialogMounted = useIsAdvancedFilterDialogMounted();
132
+ const openAdvancedFilterDialog = useOpenAdvancedFilterDialog();
133
+ const handleAdvancedFilter = useCallback(() => {
134
+ openAdvancedFilterDialog({
135
+ listRange: rangeAddress(dataRangeAroundSelection()),
136
+ onApply: (options) => onAdvancedFilter?.(sheetId, options),
137
+ });
138
+ }, [openAdvancedFilterDialog, dataRangeAroundSelection, onAdvancedFilter, sheetId]);
139
+ // Data → What-If Analysis → Goal Seek.
140
+ const isGoalSeekDialogMounted = useIsGoalSeekDialogMounted();
141
+ const openGoalSeekDialog = useOpenGoalSeekDialog();
142
+ const handleGoalSeek = useCallback(() => {
143
+ openGoalSeekDialog({
144
+ setCell: cellToAddress(activeCell) ?? "",
145
+ onApply: async (setCellAddress, targetValue, changingCellAddress) => {
146
+ const setCell = addressToCell(setCellAddress);
147
+ const changingCell = addressToCell(changingCellAddress);
148
+ if (!setCell || !changingCell || !onGoalSeek)
149
+ return undefined;
150
+ return onGoalSeek(sheetId, setCell, targetValue, changingCell);
151
+ },
152
+ });
153
+ }, [openGoalSeekDialog, activeCell, onGoalSeek, sheetId]);
154
+ // Remove duplicates — Excel's dialog (column checklist + "my data has
155
+ // headers") when a host mounts `RemoveDuplicatesDialogHost`, otherwise the
156
+ // one-click de-dupe over the selection.
157
+ const isRemoveDuplicatesDialogMounted = useIsRemoveDuplicatesDialogMounted();
158
+ const openRemoveDuplicatesDialog = useOpenRemoveDuplicatesDialog();
159
+ const handleRemoveDuplicates = useCallback(() => {
160
+ if (!isRemoveDuplicatesDialogMounted) {
161
+ onRemoveDuplicates?.(sheetId, activeCell, selections);
162
+ return;
163
+ }
164
+ // A table de-dupes its own rows; a lone cell expands to the data region
165
+ // around it, like Excel.
166
+ let range;
167
+ if (activeTable) {
168
+ range = {
169
+ ...activeTable.range,
170
+ endRowIndex: activeTable.range.endRowIndex - (activeTable.totalRow ? 1 : 0),
171
+ };
172
+ }
173
+ else if (selections.length) {
174
+ range = selection.range;
175
+ }
176
+ else {
177
+ range = getContentfulRangeAroundCell?.(sheetId, activeCell) ?? selection.range;
178
+ }
179
+ const columns = [];
180
+ for (let columnIndex = range.startColumnIndex; columnIndex <= range.endColumnIndex; columnIndex++) {
181
+ const header = activeTable?.columns?.[columnIndex - activeTable.range.startColumnIndex]
182
+ ?.name ?? getFormattedValue(sheetId, range.startRowIndex, columnIndex);
183
+ columns.push({
184
+ columnIndex,
185
+ label: `Column ${number2Alpha(columnIndex - 1)}`,
186
+ header: header || undefined,
187
+ });
188
+ }
189
+ // Excel pre-ticks "my data has headers" when the first row reads as
190
+ // labels: every cell filled and none of them a number.
191
+ const hasHeader = activeTable
192
+ ? (activeTable.headerRow ?? true)
193
+ : range.endRowIndex > range.startRowIndex &&
194
+ columns.every((column) => !!column.header && Number.isNaN(Number(column.header)));
195
+ const target = range;
196
+ openRemoveDuplicatesDialog({
197
+ columns,
198
+ hasHeader,
199
+ onApply: (options) => onRemoveDuplicates?.(sheetId, activeCell, [{ range: target }], options),
200
+ });
201
+ }, [
202
+ isRemoveDuplicatesDialogMounted,
203
+ openRemoveDuplicatesDialog,
204
+ onRemoveDuplicates,
205
+ sheetId,
206
+ activeCell,
207
+ selections,
208
+ selection,
209
+ activeTable,
210
+ getContentfulRangeAroundCell,
211
+ getFormattedValue,
212
+ ]);
213
+ // Totals-row function picker: the active cell sits on a table's totals row.
214
+ const isActiveCellTableTotal = !!activeTable?.totalRow &&
215
+ activeCell.rowIndex === activeTable.range.endRowIndex;
216
+ const activeTotalRowFunction = useMemo(() => {
217
+ if (!isActiveCellTableTotal) {
218
+ return "none";
219
+ }
220
+ const cellData = getCellData?.(sheetId, activeCell.rowIndex, activeCell.columnIndex);
221
+ const ue = cellData?.ue ?? cellData?.userEnteredValue;
222
+ return getTotalRowFunctionFromFormula(ue?.fv ?? ue?.formulaValue);
223
+ }, [isActiveCellTableTotal, getCellData, sheetId, activeCell]);
40
224
  // Sort table or filter
41
225
  const onSortTableOrFilterView = useCallback((direction) => {
42
226
  // Found a table
@@ -157,7 +341,16 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
157
341
  React.createElement(DropdownMenuItem, { onClick: () => onPaste?.("Formatting") }, "Formatting only"),
158
342
  React.createElement(DropdownMenuItem, { onClick: () => onPaste?.("Formula") }, "Formula"),
159
343
  React.createElement(DropdownMenuSeparator, null),
160
- React.createElement(DropdownMenuItem, { onClick: () => onPaste?.("Transposed") }, "Transposed"))),
344
+ React.createElement(DropdownMenuItem, { onClick: () => onPaste?.("Transposed") }, "Transposed"),
345
+ onRequestPasteSpecial ? (React.createElement(React.Fragment, null,
346
+ React.createElement(DropdownMenuSeparator, null),
347
+ React.createElement(DropdownMenuItem, { onClick: () => {
348
+ // Focus moves to the dialog, not back to the sheet
349
+ shouldFocusSheetRef.current = false;
350
+ onRequestPasteSpecial();
351
+ } },
352
+ "Paste special\u2026",
353
+ React.createElement(DropdownRightSlot, null, "\u2318\u2325V")))) : null)),
161
354
  enableMagicFill ? (React.createElement(React.Fragment, null,
162
355
  React.createElement(DropdownMenuSeparator, null),
163
356
  React.createElement(DropdownMenuItem, { onClick: () => onAddQuickEdit({
@@ -443,7 +636,26 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
443
636
  React.createElement(ChevronRightIcon, null))),
444
637
  React.createElement(DropdownMenuSubContent, { sideOffset: 2, alignOffset: -5 },
445
638
  React.createElement(DropdownMenuItem, { onClick: () => onRemoveTable?.(activeTable), disabled: readonly }, "Convert to range"),
446
- React.createElement(DropdownMenuItem, { onClick: () => onRequestEditTable?.(activeTable), disabled: readonly }, "Edit table")))) : null,
639
+ React.createElement(DropdownMenuItem, { onClick: () => onRequestEditTable?.(activeTable), disabled: readonly }, "Edit table"),
640
+ isActiveCellTableTotal ? (React.createElement(DropdownMenuSub, null,
641
+ React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
642
+ "Total row function",
643
+ React.createElement(DropdownRightSlot, null,
644
+ React.createElement(ChevronRightIcon, null))),
645
+ React.createElement(DropdownMenuSubContent, { sideOffset: 2, alignOffset: -5 }, TOTAL_ROW_FUNCTIONS.map((option) => (React.createElement(DropdownMenuCheckboxItem, { key: option.id, checked: activeTotalRowFunction === option.id, disabled: readonly, onSelect: () => {
646
+ const formula = getTotalRowFunctionFormula(option.id, activeTable, activeCell.columnIndex);
647
+ if (formula === undefined) {
648
+ return;
649
+ }
650
+ if (formula === "") {
651
+ onClearContents?.(sheetId, activeCell, selectionFromActiveCell(activeCell));
652
+ return;
653
+ }
654
+ onChange?.(sheetId, activeCell, formula, null);
655
+ } },
656
+ React.createElement(DropdownMenuItemIndicator, null,
657
+ React.createElement(CheckIcon, null)),
658
+ option.label)))))) : null))) : null,
447
659
  activePivotTable ? (React.createElement(DropdownMenuSub, null,
448
660
  React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
449
661
  React.createElement(DropdownLeftSlot, null,
@@ -547,9 +759,23 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
547
759
  }, disabled: readonly },
548
760
  React.createElement(DropdownLeftSlot, null,
549
761
  React.createElement(PlusIcon, null)),
550
- "Define named range"))),
762
+ "Define named range"),
763
+ onRequestEvaluateFormula && isActiveCellFormula ? (React.createElement(DropdownMenuItem, { onClick: () => {
764
+ shouldFocusSheetRef.current = false;
765
+ onRequestEvaluateFormula(sheetId, activeCell);
766
+ } },
767
+ React.createElement(DropdownLeftSlot, null,
768
+ React.createElement(CodeIcon, null)),
769
+ "Evaluate formula")) : null,
770
+ onRequestAddWatch ? (React.createElement(DropdownMenuItem, { onClick: () => {
771
+ shouldFocusSheetRef.current = false;
772
+ onRequestAddWatch(sheetId, activeCell);
773
+ } },
774
+ React.createElement(DropdownLeftSlot, null,
775
+ React.createElement(EyeOpenIcon, null)),
776
+ "Add to Watch Window")) : null)),
551
777
  React.createElement(DropdownMenuSeparator, null),
552
- React.createElement(DropdownMenuItem, { onClick: () => onRemoveDuplicates?.(sheetId, activeCell, selections), disabled: readonly },
778
+ React.createElement(DropdownMenuItem, { onClick: handleRemoveDuplicates, disabled: readonly },
553
779
  React.createElement(DropdownLeftSlot, null,
554
780
  React.createElement(RemoveDuplicatesIcon, null)),
555
781
  "Remove duplicates"),
@@ -565,7 +791,23 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
565
791
  React.createElement(DropdownMenuItem, { onClick: () => onSplitTextToColumns?.(sheetId, activeCell, selections, ";") }, "Semicolon"),
566
792
  React.createElement(DropdownMenuItem, { onClick: () => onSplitTextToColumns?.(sheetId, activeCell, selections, ".") }, "Period"),
567
793
  React.createElement(DropdownMenuItem, { onClick: () => onSplitTextToColumns?.(sheetId, activeCell, selections, " ") }, "Space"),
568
- React.createElement(DropdownMenuItem, { onClick: () => onSplitTextToColumns?.(sheetId, activeCell, selections, "|") }, "Pipe"))),
794
+ React.createElement(DropdownMenuItem, { onClick: () => onSplitTextToColumns?.(sheetId, activeCell, selections, "|") }, "Pipe"),
795
+ isTextToColumnsDialogMounted ? (React.createElement(React.Fragment, null,
796
+ React.createElement(DropdownMenuSeparator, null),
797
+ React.createElement(DropdownMenuItem, { onClick: handleTextToColumnsDialog }, "Fixed width\u2026"))) : null)),
798
+ (isSubtotalDialogMounted && onSubtotal) ||
799
+ (isAdvancedFilterDialogMounted && onAdvancedFilter) ||
800
+ (isGoalSeekDialogMounted && onGoalSeek) ? (React.createElement(DropdownMenuSub, null,
801
+ React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
802
+ React.createElement(DropdownLeftSlot, null,
803
+ React.createElement(SpaceBetweenHorizontallyIcon, null)),
804
+ "Data tools",
805
+ React.createElement(DropdownRightSlot, null,
806
+ React.createElement(ChevronRightIcon, null))),
807
+ React.createElement(DropdownMenuSubContent, { sideOffset: 2, alignOffset: -5 },
808
+ isSubtotalDialogMounted && onSubtotal ? (React.createElement(DropdownMenuItem, { onClick: handleSubtotal }, "Subtotal\u2026")) : null,
809
+ isAdvancedFilterDialogMounted && onAdvancedFilter ? (React.createElement(DropdownMenuItem, { onClick: handleAdvancedFilter }, "Advanced filter\u2026")) : null,
810
+ isGoalSeekDialogMounted && onGoalSeek ? (React.createElement(DropdownMenuItem, { onClick: handleGoalSeek }, "Goal seek\u2026")) : null))) : null,
569
811
  React.createElement(DropdownMenuSub, null,
570
812
  React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
571
813
  React.createElement(DropdownLeftSlot, null,
@@ -0,0 +1,32 @@
1
+ import type { TableView } from "../../types";
2
+ /**
3
+ * The aggregations Excel offers in a table's totals-row dropdown, each backed
4
+ * by the `SUBTOTAL` function number that ignores hidden (filtered) rows.
5
+ */
6
+ export type TotalRowFunction = "none" | "average" | "count" | "countNumbers" | "max" | "min" | "sum" | "stdDev" | "var";
7
+ export type TotalRowFunctionOption = {
8
+ id: TotalRowFunction;
9
+ label: string;
10
+ /** `SUBTOTAL` function number (101–111 = ignore hidden rows). */
11
+ subtotal?: number;
12
+ };
13
+ export declare const TOTAL_ROW_FUNCTIONS: TotalRowFunctionOption[];
14
+ /**
15
+ * Escape a column name for use inside a structured reference: `[`, `]`, `#`
16
+ * and `'` are prefixed with `'`, as Excel does.
17
+ */
18
+ export declare const escapeStructuredColumnName: (name: string) => string;
19
+ /**
20
+ * Formula for a totals-row cell, e.g. `=SUBTOTAL(109,Table1[Amount])`.
21
+ * Qualified with the table name so it evaluates from any engine and matches
22
+ * what Excel writes for a totals row. Returns `""` for "none" (clear the
23
+ * cell) and `undefined` when the column has no name to reference.
24
+ */
25
+ export declare const getTotalRowFunctionFormula: (fn: TotalRowFunction, table: Pick<TableView, "title" | "columns" | "range">, columnIndex: number) => string | undefined;
26
+ /**
27
+ * Recognise the aggregation a totals-row formula uses, so the picker can show
28
+ * the active choice. Both the hidden-row-aware (1xx) and plain (1–11)
29
+ * `SUBTOTAL` numbers map to the same option; anything else is "none".
30
+ */
31
+ export declare const getTotalRowFunctionFromFormula: (formula: string | null | undefined) => TotalRowFunction;
32
+ //# sourceMappingURL=total-row-functions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"total-row-functions.d.ts","sourceRoot":"","sources":["../../../../components/context-menu/total-row-functions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,SAAS,GACT,OAAO,GACP,cAAc,GACd,KAAK,GACL,KAAK,GACL,KAAK,GACL,QAAQ,GACR,KAAK,CAAC;AAEV,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,gBAAgB,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,sBAAsB,EAUvD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,GAAI,MAAM,MAAM,WACnB,CAAC;AAErC;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GACrC,IAAI,gBAAgB,EACpB,OAAO,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,EACrD,aAAa,MAAM,KAClB,MAAM,GAAG,SAaX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,GACzC,SAAS,MAAM,GAAG,IAAI,GAAG,SAAS,KACjC,gBAgBF,CAAC"}
@@ -0,0 +1,50 @@
1
+ export const TOTAL_ROW_FUNCTIONS = [
2
+ { id: "none", label: "None" },
3
+ { id: "average", label: "Average", subtotal: 101 },
4
+ { id: "count", label: "Count", subtotal: 103 },
5
+ { id: "countNumbers", label: "Count Numbers", subtotal: 102 },
6
+ { id: "max", label: "Max", subtotal: 104 },
7
+ { id: "min", label: "Min", subtotal: 105 },
8
+ { id: "sum", label: "Sum", subtotal: 109 },
9
+ { id: "stdDev", label: "StdDev", subtotal: 107 },
10
+ { id: "var", label: "Var", subtotal: 110 },
11
+ ];
12
+ /**
13
+ * Escape a column name for use inside a structured reference: `[`, `]`, `#`
14
+ * and `'` are prefixed with `'`, as Excel does.
15
+ */
16
+ export const escapeStructuredColumnName = (name) => name.replace(/(['\[\]#])/g, "'$1");
17
+ /**
18
+ * Formula for a totals-row cell, e.g. `=SUBTOTAL(109,Table1[Amount])`.
19
+ * Qualified with the table name so it evaluates from any engine and matches
20
+ * what Excel writes for a totals row. Returns `""` for "none" (clear the
21
+ * cell) and `undefined` when the column has no name to reference.
22
+ */
23
+ export const getTotalRowFunctionFormula = (fn, table, columnIndex) => {
24
+ if (fn === "none") {
25
+ return "";
26
+ }
27
+ const option = TOTAL_ROW_FUNCTIONS.find((item) => item.id === fn);
28
+ const columnName = table.columns?.[columnIndex - table.range.startColumnIndex]?.name;
29
+ if (!option?.subtotal || !columnName || !table.title) {
30
+ return undefined;
31
+ }
32
+ return `=SUBTOTAL(${option.subtotal},${table.title}[${escapeStructuredColumnName(columnName)}])`;
33
+ };
34
+ /**
35
+ * Recognise the aggregation a totals-row formula uses, so the picker can show
36
+ * the active choice. Both the hidden-row-aware (1xx) and plain (1–11)
37
+ * `SUBTOTAL` numbers map to the same option; anything else is "none".
38
+ */
39
+ export const getTotalRowFunctionFromFormula = (formula) => {
40
+ if (!formula) {
41
+ return "none";
42
+ }
43
+ const match = /^\s*=\s*SUBTOTAL\s*\(\s*(\d{1,3})\s*,/i.exec(formula);
44
+ if (!match) {
45
+ return "none";
46
+ }
47
+ const number = Number(match[1]);
48
+ const normalized = number > 100 ? number - 100 : number;
49
+ return (TOTAL_ROW_FUNCTIONS.find((item) => item.subtotal !== undefined && item.subtotal - 100 === normalized)?.id ?? "none");
50
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"embed-component.d.ts","sourceRoot":"","sources":["../../../../components/embeds/embed-component.tsx"],"names":[],"mappings":"AASA,OAAO,KAAe,MAAM,OAAO,CAAC;AAEpC,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAIhF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,gBAAgB,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC;IAChD,kBAAkB,EAAE,eAAe,CAAC,oBAAoB,CAAC,CAAC;CAC3D,CAAC;AAEF,eAAO,MAAM,cAAc,wHAUtB,mBAAmB,uBA+LvB,CAAC"}
1
+ {"version":3,"file":"embed-component.d.ts","sourceRoot":"","sources":["../../../../components/embeds/embed-component.tsx"],"names":[],"mappings":"AASA,OAAO,KAAe,MAAM,OAAO,CAAC;AAEpC,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAKhF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,gBAAgB,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC;IAChD,kBAAkB,EAAE,eAAe,CAAC,oBAAoB,CAAC,CAAC;CAC3D,CAAC;AAEF,eAAO,MAAM,cAAc,wHAUtB,mBAAmB,uBA4NvB,CAAC"}
@@ -2,6 +2,7 @@ import { DotsVerticalIcon } from "@rowsncolumns/icons";
2
2
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuTrigger, IconButton, } from "@rowsncolumns/ui";
3
3
  import React, { memo } from "react";
4
4
  import { getStringifiedColor } from "../../themes";
5
+ import { resolveCanvasTextAlign } from "../cell/alignment";
5
6
  import { ShapeSvg } from "./shape-svg";
6
7
  export const EmbedComponent = memo(({ embed, width, height, readonly, isSelected, onDeleteEmbed, onRequestEditEmbed, theme, }) => {
7
8
  const renderTextBox = (spec) => {
@@ -39,7 +40,9 @@ export const EmbedComponent = memo(({ embed, width, height, readonly, isSelected
39
40
  height: "100%",
40
41
  backgroundColor,
41
42
  padding: spec.padding ? `${spec.padding}px` : "4px",
42
- textAlign: spec.horizontalAlignment || undefined,
43
+ textAlign: spec.horizontalAlignment
44
+ ? resolveCanvasTextAlign(spec.horizontalAlignment)
45
+ : undefined,
43
46
  display: "flex",
44
47
  flexDirection: "column",
45
48
  justifyContent: spec.verticalAlignment === "top"
@@ -110,6 +113,19 @@ export const EmbedComponent = memo(({ embed, width, height, readonly, isSelected
110
113
  type: "textbox",
111
114
  ...embed.spec.textBody,
112
115
  }))) : null)) : null,
116
+ embed?.spec.type === "formControl" ? (
117
+ // Excel form controls round-trip but are not interactive here: draw
118
+ // the caption in a control-shaped placeholder so the layout reads.
119
+ React.createElement("div", { className: "flex h-full w-full items-center gap-1 overflow-hidden whitespace-nowrap px-1 text-xs", title: embed.spec.name ?? undefined },
120
+ embed.spec.objectType === "CheckBox" ||
121
+ embed.spec.objectType === "Radio" ? (React.createElement("span", { className: "inline-block h-3 w-3 shrink-0 border border-current", style: {
122
+ borderRadius: embed.spec.objectType === "Radio"
123
+ ? "50%"
124
+ : 2,
125
+ } })) : null,
126
+ React.createElement("span", { className: "truncate" }, embed.spec.text ??
127
+ embed.spec.name ??
128
+ embed.spec.objectType))) : null,
113
129
  readonly || !isSelected ? null : (React.createElement(DropdownMenu, null,
114
130
  React.createElement(DropdownMenuTrigger, { asChild: true, className: "print:hidden" },
115
131
  React.createElement(IconButton, { className: "absolute right-1 top-1 z-10" },
@@ -0,0 +1,34 @@
1
+ import { type EvaluationValue } from "@rowsncolumns/calculator";
2
+ import React from "react";
3
+ export type EvaluateFormulaDialogProps = {
4
+ /** Controls visibility. */
5
+ open: boolean;
6
+ onOpenChange(open: boolean): void;
7
+ /** Display label of the evaluated cell, e.g. `Sheet1!$A$1`. */
8
+ reference: string;
9
+ /** The cell's formula, including the leading `=`. */
10
+ formula: string;
11
+ /**
12
+ * Scratch-evaluates one sub-expression (no leading `=`) in the cell's
13
+ * context. Errors come back as `{ error: "#DIV/0!" }`, blanks as `null`.
14
+ */
15
+ evaluate(expression: string): Promise<EvaluationValue>;
16
+ };
17
+ /**
18
+ * Excel's Evaluate Formula dialog: a read-only stepper that underlines the
19
+ * next sub-expression the calculator would resolve and, on Evaluate,
20
+ * replaces it with its value until only the result remains. Nothing is
21
+ * written to the sheet.
22
+ */
23
+ export declare const EvaluateFormulaDialog: React.MemoExoticComponent<({ open, onOpenChange, reference, formula, evaluate, }: EvaluateFormulaDialogProps) => React.JSX.Element>;
24
+ /**
25
+ * Host-mounted, atom-driven wrapper around {@link EvaluateFormulaDialog}.
26
+ * Mount it once in the host alongside the other editor dialogs; the grid
27
+ * opens it from the context menu ("Evaluate formula…") for a formula cell.
28
+ * Keyed per request so each opening restarts from the cell's formula.
29
+ */
30
+ export declare const EvaluateFormulaDialogHost: {
31
+ (): React.JSX.Element | null;
32
+ displayName: string;
33
+ };
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/evaluate-formula-dialog/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAWlC,OAAO,KAAsC,MAAM,OAAO,CAAC;AAQ3D,MAAM,MAAM,0BAA0B,GAAG;IACvC,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACxD,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,oFAO7B,0BAA0B,uBAiG9B,CAAC;AAUF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;CAiBrC,CAAC"}
@@ -0,0 +1,86 @@
1
+ import { advanceFormulaEvaluation, createFormulaEvaluation, } from "@rowsncolumns/calculator";
2
+ import { Button, Dialog, DialogBody, DialogCloseButton, DialogContent, DialogTitle, Separator, } from "@rowsncolumns/ui";
3
+ import { cellToAddress, sanitizeSheetName } from "@rowsncolumns/utils";
4
+ import React, { memo, useCallback, useState } from "react";
5
+ import { useEvaluateFormulaDialogState, useRegisterEvaluateFormulaDialogHost, } from "../../atoms/evaluate-formula-dialog.atom";
6
+ const CANNOT_EVALUATE = "This formula cannot be stepped through. Check it for syntax errors.";
7
+ /**
8
+ * Excel's Evaluate Formula dialog: a read-only stepper that underlines the
9
+ * next sub-expression the calculator would resolve and, on Evaluate,
10
+ * replaces it with its value until only the result remains. Nothing is
11
+ * written to the sheet.
12
+ */
13
+ export const EvaluateFormulaDialog = memo(({ open, onOpenChange, reference, formula, evaluate, }) => {
14
+ const [state, setState] = useState(() => createFormulaEvaluation(formula));
15
+ const [busy, setBusy] = useState(false);
16
+ const [failure, setFailure] = useState(undefined);
17
+ const step = useCallback(async () => {
18
+ if (!state || state.done || busy)
19
+ return;
20
+ setBusy(true);
21
+ try {
22
+ setState(await advanceFormulaEvaluation(state, evaluate));
23
+ setFailure(undefined);
24
+ }
25
+ catch (err) {
26
+ setFailure(err instanceof Error ? err.message : "The expression could not be evaluated.");
27
+ }
28
+ finally {
29
+ setBusy(false);
30
+ }
31
+ }, [state, busy, evaluate]);
32
+ const restart = useCallback(() => {
33
+ setState(createFormulaEvaluation(formula));
34
+ setFailure(undefined);
35
+ }, [formula]);
36
+ const target = state?.target;
37
+ const before = state && target ? state.text.slice(0, target.start) : "";
38
+ const underlined = state && target ? state.text.slice(target.start, target.end + 1) : "";
39
+ const after = state && target ? state.text.slice(target.end + 1) : "";
40
+ return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange, modal: true },
41
+ React.createElement(DialogContent, { className: "w-[520px] max-w-[92vw] pointer-events-auto" },
42
+ React.createElement(DialogTitle, null, "Evaluate Formula"),
43
+ React.createElement(DialogCloseButton, null),
44
+ React.createElement(DialogBody, { className: "flex flex-col gap-3" },
45
+ React.createElement("div", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-xs" },
46
+ React.createElement("span", { className: "font-medium" }, "Reference:"),
47
+ React.createElement("span", { className: "font-mono" }, reference),
48
+ React.createElement("span", { className: "font-medium" }, "Evaluation:"),
49
+ React.createElement("div", { "data-testid": "evaluate-formula-expression", className: "font-mono whitespace-pre-wrap break-all rounded border border-rnc-border px-2 py-1.5 min-h-[64px]" }, state ? (state.done ? (React.createElement("span", { className: "italic" },
50
+ "= ",
51
+ state.text)) : (React.createElement(React.Fragment, null,
52
+ "= ",
53
+ before,
54
+ React.createElement("u", { "data-testid": "evaluate-formula-target" }, underlined),
55
+ after))) : (React.createElement("span", { className: "text-rnc-destructive" }, CANNOT_EVALUATE)))),
56
+ failure ? (React.createElement("div", { role: "alert", className: "text-xs text-rnc-destructive" }, failure)) : null,
57
+ React.createElement("p", { className: "text-xs text-rnc-foreground/70" }, "To show the result of the underlined expression, click Evaluate. The final result appears italicized."),
58
+ React.createElement(Separator, null),
59
+ React.createElement("div", { className: "flex justify-end gap-2" },
60
+ React.createElement(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false) }, "Close"),
61
+ state?.done ? (React.createElement(Button, { size: "sm", onClick: restart }, "Restart")) : (React.createElement(Button, { size: "sm", onClick: step, disabled: !state || busy }, "Evaluate")))))));
62
+ });
63
+ EvaluateFormulaDialog.displayName = "EvaluateFormulaDialog";
64
+ const referenceOf = (request) => {
65
+ const address = cellToAddress(request.cell, true, true) ?? "";
66
+ const sheetName = sanitizeSheetName(request.sheetName);
67
+ return sheetName ? `${sheetName}!${address}` : address;
68
+ };
69
+ /**
70
+ * Host-mounted, atom-driven wrapper around {@link EvaluateFormulaDialog}.
71
+ * Mount it once in the host alongside the other editor dialogs; the grid
72
+ * opens it from the context menu ("Evaluate formula…") for a formula cell.
73
+ * Keyed per request so each opening restarts from the cell's formula.
74
+ */
75
+ export const EvaluateFormulaDialogHost = () => {
76
+ useRegisterEvaluateFormulaDialogHost();
77
+ const [request, setRequest] = useEvaluateFormulaDialogState();
78
+ const close = useCallback(() => setRequest(null), [setRequest]);
79
+ if (!request)
80
+ return null;
81
+ return (React.createElement(EvaluateFormulaDialog, { key: `${request.sheetId}:${request.cell.rowIndex}:${request.cell.columnIndex}:${request.formula}`, open: true, onOpenChange: (open) => {
82
+ if (!open)
83
+ close();
84
+ }, reference: referenceOf(request), formula: request.formula, evaluate: request.evaluate }));
85
+ };
86
+ EvaluateFormulaDialogHost.displayName = "EvaluateFormulaDialogHost";
@@ -0,0 +1,45 @@
1
+ import type { GridRange } from "@rowsncolumns/grid";
2
+ import React from "react";
3
+ import { type GoToSpecialKind } from "../../api/go-to-special";
4
+ import type { NamedRange, SheetRange } from "../../types";
5
+ export type GoToDialogProps = {
6
+ /** Controls visibility. */
7
+ open: boolean;
8
+ onOpenChange(open: boolean): void;
9
+ /** Defined names listed in the "Go to" box; picking one fills the reference. */
10
+ namedRanges?: NamedRange[] | null;
11
+ /** Shown as the placeholder of the reference box (the current location). */
12
+ currentAddress?: string;
13
+ /**
14
+ * Resolves the typed text to a range, or `undefined` when it is not a
15
+ * valid reference / name (the dialog then shows Excel's error).
16
+ */
17
+ resolve(reference: string): SheetRange | undefined;
18
+ /** Receives the resolved range; the host selects and scrolls to it. */
19
+ onNavigate(range: SheetRange): void;
20
+ /**
21
+ * Go To Special. `resolve` returns the matching cell runs for a category;
22
+ * `onSelectRanges` receives them. Omit to hide the "Special…" panel.
23
+ */
24
+ special?: {
25
+ resolve(kind: GoToSpecialKind): GridRange[];
26
+ onSelectRanges(ranges: GridRange[]): void;
27
+ };
28
+ };
29
+ /**
30
+ * Excel's Go To dialog (`Ctrl+G` / `F5`): type a cell or range reference,
31
+ * optionally sheet-qualified, or pick a defined name, and the selection
32
+ * jumps there. Resolution is delegated to `resolve` so the dialog stays
33
+ * UI-only.
34
+ */
35
+ export declare const GoToDialog: React.MemoExoticComponent<({ open, onOpenChange, namedRanges, currentAddress, resolve, onNavigate, special, }: GoToDialogProps) => React.JSX.Element>;
36
+ /**
37
+ * Host-mounted, atom-driven wrapper around {@link GoToDialog}. Mount it once
38
+ * in the host alongside the other editor dialogs; the grid opens it on
39
+ * `Ctrl+G` / `F5` (unless the host handles `onRequestGoTo` itself).
40
+ */
41
+ export declare const GoToDialogHost: {
42
+ (): React.JSX.Element | null;
43
+ displayName: string;
44
+ };
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/go-to-dialog/index.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAsC,MAAM,OAAO,CAAC;AAG3D,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAMjC,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG;IAC5B,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,gFAAgF;IAChF,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAClC,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IACnD,uEAAuE;IACvE,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,EAAE,CAAC;QAC5C,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;KAC3C,CAAC;CACH,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,iHASlB,eAAe,uBA0MnB,CAAC;AAUF;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;CA+B1B,CAAC"}