@svgrid/enterprise 3.0.2 → 3.0.3

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 (364) hide show
  1. package/LICENSE +94 -24
  2. package/README.md +36 -4
  3. package/dist/SvAdvancedFilter.svelte +1 -1
  4. package/dist/SvAlertsManager.svelte +2 -1
  5. package/dist/SvBoard.svelte +2 -1
  6. package/dist/SvExportMenu.svelte +4 -1
  7. package/dist/SvExportMenu.svelte.d.ts +0 -13
  8. package/dist/SvExpressionEditor.svelte +3 -3
  9. package/dist/SvFormulaBar.svelte +94 -20
  10. package/dist/SvFormulaBar.svelte.d.ts +7 -0
  11. package/dist/SvGridAlerts.svelte +49 -9
  12. package/dist/SvGridBoard.svelte +13 -8
  13. package/dist/SvGridBulkEditDrawer.svelte +26 -2
  14. package/dist/SvGridEditPanel.svelte +3 -0
  15. package/dist/SvGridScheduler.svelte +69 -40
  16. package/dist/SvGridSelectionBar.svelte +10 -3
  17. package/dist/SvPivotDesigner.svelte +198 -40
  18. package/dist/SvPivotDesigner.svelte.d.ts +27 -20
  19. package/dist/SvSheet.svelte +4043 -243
  20. package/dist/SvSheet.svelte.d.ts +76 -3
  21. package/dist/SvSheetChartSetup.svelte +151 -0
  22. package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
  23. package/dist/SvSheetComment.svelte +216 -31
  24. package/dist/SvSheetComment.svelte.d.ts +11 -11
  25. package/dist/SvSheetConditionalFormat.svelte +28 -31
  26. package/dist/SvSheetDataValidation.svelte +66 -42
  27. package/dist/SvSheetEditRanges.svelte +217 -0
  28. package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
  29. package/dist/SvSheetErrors.svelte +125 -0
  30. package/dist/SvSheetErrors.svelte.d.ts +20 -0
  31. package/dist/SvSheetEvaluate.svelte +133 -0
  32. package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
  33. package/dist/SvSheetFilterMenu.svelte +137 -34
  34. package/dist/SvSheetFilterMenu.svelte.d.ts +5 -0
  35. package/dist/SvSheetFindReplace.svelte +19 -19
  36. package/dist/SvSheetFormatCells.svelte +125 -81
  37. package/dist/SvSheetGoalSeek.svelte +17 -16
  38. package/dist/SvSheetInsertFunction.svelte +15 -13
  39. package/dist/SvSheetIteration.svelte +81 -0
  40. package/dist/SvSheetIteration.svelte.d.ts +10 -0
  41. package/dist/SvSheetLink.svelte +99 -0
  42. package/dist/SvSheetLink.svelte.d.ts +17 -0
  43. package/dist/SvSheetListPicker.svelte +4 -2
  44. package/dist/SvSheetListPicker.svelte.d.ts +0 -6
  45. package/dist/SvSheetManageRules.svelte +20 -18
  46. package/dist/SvSheetNameManager.svelte +17 -15
  47. package/dist/SvSheetPageSetup.svelte +159 -0
  48. package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
  49. package/dist/SvSheetPasteSpecial.svelte +22 -20
  50. package/dist/SvSheetPivot.svelte +220 -0
  51. package/dist/SvSheetPivot.svelte.d.ts +21 -0
  52. package/dist/SvSheetProtectSheet.svelte +81 -0
  53. package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
  54. package/dist/SvSheetRemoveDuplicates.svelte +13 -10
  55. package/dist/SvSheetRibbon.svelte +104 -51
  56. package/dist/SvSheetSizeDialog.svelte +4 -2
  57. package/dist/SvSheetSort.svelte +218 -0
  58. package/dist/SvSheetSort.svelte.d.ts +28 -0
  59. package/dist/SvSheetSparklines.svelte +135 -0
  60. package/dist/SvSheetSparklines.svelte.d.ts +14 -0
  61. package/dist/SvSheetTable.svelte +160 -0
  62. package/dist/SvSheetTable.svelte.d.ts +24 -0
  63. package/dist/SvSheetTabs.svelte +161 -31
  64. package/dist/SvSheetTabs.svelte.d.ts +22 -8
  65. package/dist/SvSheetTextToColumns.svelte +20 -20
  66. package/dist/SvSheetValidationAlert.svelte +8 -6
  67. package/dist/advanced-filter-enable.js +1 -1
  68. package/dist/alerts/alert-engine.d.ts +6 -0
  69. package/dist/alerts/alert-engine.js +21 -4
  70. package/dist/alerts/alert-formats.js +2 -0
  71. package/dist/alerts/alert-types.d.ts +1 -1
  72. package/dist/alerts.d.ts +1 -1
  73. package/dist/alerts.js +2 -2
  74. package/dist/board.js +2 -2
  75. package/dist/bulk-edit.d.ts +3 -1
  76. package/dist/bulk-edit.js +1 -1
  77. package/dist/cdn/svgrid-enterprise.svelte-external.js +39827 -16429
  78. package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
  79. package/dist/designer/assets/{SvChartMenu-D9hEeHar.js → SvChartMenu-BDoeqGz6.js} +1 -1
  80. package/dist/designer/assets/{SvDateTimePicker-DARvKUL2.js → SvDateTimePicker-DCPg7WGN.js} +1 -1
  81. package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
  82. package/dist/designer/assets/{SvGridChart-B8j1SSy3.js → SvGridChart-Cy1ATrWh.js} +2 -2
  83. package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
  84. package/dist/designer/assets/{SvGridChartPanel-pT6ITc2P.js → SvGridChartPanel-CATdVc9X.js} +3 -3
  85. package/dist/designer/assets/{SvGridChartView-bg281UA1.js → SvGridChartView-CmsZtCED.js} +1 -1
  86. package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
  87. package/dist/designer/assets/{SvTabs-UFCMTHXS.js → SvTabs-2mHC6oaz.js} +1 -1
  88. package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
  89. package/dist/designer/assets/{chart-cIPUsbM5.js → chart-Cn7MnNqA.js} +1 -1
  90. package/dist/designer/assets/{chart-panel-messages-DtTxpBUU.js → chart-panel-messages-Dz7PTIqT.js} +1 -1
  91. package/dist/designer/assets/{chart-summary-Czu_5epb.js → chart-summary-BD1qYB2R.js} +1 -1
  92. package/dist/designer/assets/{chart-trend-D5I5VEe8.js → chart-trend-B8xr5LuD.js} +1 -1
  93. package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
  94. package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
  95. package/dist/designer/assets/{focus-trap-CAreAhlU.js → focus-trap-V2-6Fy9I.js} +1 -1
  96. package/dist/designer/assets/{index-DwU7CAMm.js → index-D5bVbuzI.js} +317 -315
  97. package/dist/designer/assets/index-_JVFxt02.css +1 -0
  98. package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
  99. package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
  100. package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
  101. package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
  102. package/dist/designer/index.html +17 -16
  103. package/dist/export-pdf.d.ts +75 -0
  104. package/dist/export-pdf.js +216 -15
  105. package/dist/export.js +10 -2
  106. package/dist/gantt/SvGridGantt.svelte +2890 -0
  107. package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
  108. package/dist/gantt/gantt-config.d.ts +73 -0
  109. package/dist/gantt/gantt-config.js +1 -0
  110. package/dist/gantt/gantt-critical-path.d.ts +67 -0
  111. package/dist/gantt/gantt-critical-path.js +207 -0
  112. package/dist/gantt/gantt-model.d.ts +257 -0
  113. package/dist/gantt/gantt-model.js +651 -0
  114. package/dist/gantt/gantt-resources.d.ts +59 -0
  115. package/dist/gantt/gantt-resources.js +71 -0
  116. package/dist/gantt/gantt.d.ts +7 -0
  117. package/dist/gantt/gantt.js +36 -0
  118. package/dist/gantt/index.d.ts +15 -0
  119. package/dist/gantt/index.js +22 -0
  120. package/dist/gantt/timeline-arrows.d.ts +102 -0
  121. package/dist/gantt/timeline-arrows.js +181 -0
  122. package/dist/import.d.ts +2 -2
  123. package/dist/import.js +1 -1
  124. package/dist/index.d.ts +34 -7
  125. package/dist/index.js +44 -7
  126. package/dist/install.js +8 -1
  127. package/dist/license-core.d.ts +17 -1
  128. package/dist/license-core.js +16 -5
  129. package/dist/license.d.ts +18 -2
  130. package/dist/license.js +71 -6
  131. package/dist/node/studio.js +140 -8
  132. package/dist/pivot-enable.js +2 -2
  133. package/dist/scheduler-dependencies.d.ts +35 -0
  134. package/dist/scheduler-dependencies.js +36 -1
  135. package/dist/scheduler.js +2 -2
  136. package/dist/selection-bar.js +2 -2
  137. package/dist/server/SvGroupCell.svelte +213 -0
  138. package/dist/server/SvGroupCell.svelte.d.ts +62 -0
  139. package/dist/server/SvRowGroupPanel.svelte +322 -0
  140. package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
  141. package/dist/server/enable.d.ts +17 -0
  142. package/dist/server/enable.js +49 -0
  143. package/dist/server/index.d.ts +22 -0
  144. package/dist/server/index.js +24 -0
  145. package/dist/server/messages.d.ts +45 -0
  146. package/dist/server/messages.js +35 -0
  147. package/dist/server/server-group-model.d.ts +104 -0
  148. package/dist/server/server-group-model.js +272 -0
  149. package/dist/server/server-pivot.d.ts +46 -0
  150. package/dist/server/server-pivot.js +57 -0
  151. package/dist/server/server-row-model.d.ts +547 -0
  152. package/dist/server/server-row-model.js +1551 -0
  153. package/dist/server/server-selection.d.ts +99 -0
  154. package/dist/server/server-selection.js +215 -0
  155. package/dist/server/svgrid-adapter.d.ts +155 -0
  156. package/dist/server/svgrid-adapter.js +202 -0
  157. package/dist/sheet/ast.d.ts +45 -1
  158. package/dist/sheet/ast.js +14 -0
  159. package/dist/sheet/auto-filter.d.ts +58 -4
  160. package/dist/sheet/auto-filter.js +159 -11
  161. package/dist/sheet/autocomplete.d.ts +9 -0
  162. package/dist/sheet/autocomplete.js +90 -4
  163. package/dist/sheet/cell-images.d.ts +39 -0
  164. package/dist/sheet/cell-images.js +90 -0
  165. package/dist/sheet/cell-types.d.ts +58 -0
  166. package/dist/sheet/cell-types.js +96 -0
  167. package/dist/sheet/coerce.d.ts +40 -0
  168. package/dist/sheet/coerce.js +170 -3
  169. package/dist/sheet/comments.d.ts +43 -9
  170. package/dist/sheet/comments.js +85 -27
  171. package/dist/sheet/conditional-formats.d.ts +29 -6
  172. package/dist/sheet/conditional-formats.js +37 -12
  173. package/dist/sheet/csv.d.ts +32 -0
  174. package/dist/sheet/csv.js +158 -0
  175. package/dist/sheet/culture.d.ts +87 -0
  176. package/dist/sheet/culture.js +247 -0
  177. package/dist/sheet/delta.d.ts +102 -0
  178. package/dist/sheet/delta.js +203 -0
  179. package/dist/sheet/deps.d.ts +45 -1
  180. package/dist/sheet/deps.js +77 -2
  181. package/dist/sheet/document.d.ts +106 -4
  182. package/dist/sheet/document.js +204 -3
  183. package/dist/sheet/engine.d.ts +67 -0
  184. package/dist/sheet/engine.js +19 -0
  185. package/dist/sheet/entry.d.ts +8 -4
  186. package/dist/sheet/entry.js +171 -21
  187. package/dist/sheet/error-check.d.ts +49 -0
  188. package/dist/sheet/error-check.js +83 -0
  189. package/dist/sheet/evaluate-steps.d.ts +48 -0
  190. package/dist/sheet/evaluate-steps.js +196 -0
  191. package/dist/sheet/evaluate.d.ts +67 -0
  192. package/dist/sheet/evaluate.js +725 -13
  193. package/dist/sheet/filter-files.d.ts +37 -0
  194. package/dist/sheet/filter-files.js +465 -0
  195. package/dist/sheet/format-store.d.ts +15 -0
  196. package/dist/sheet/format-store.js +11 -1
  197. package/dist/sheet/from-rows.d.ts +14 -0
  198. package/dist/sheet/from-rows.js +50 -0
  199. package/dist/sheet/function-catalog.d.ts +1 -1
  200. package/dist/sheet/function-catalog.js +296 -5
  201. package/dist/sheet/functions.js +363 -91
  202. package/dist/sheet/index.d.ts +35 -8
  203. package/dist/sheet/index.js +35 -8
  204. package/dist/sheet/links.d.ts +57 -0
  205. package/dist/sheet/links.js +174 -0
  206. package/dist/sheet/messages.d.ts +778 -0
  207. package/dist/sheet/messages.js +806 -0
  208. package/dist/sheet/number-format.d.ts +52 -5
  209. package/dist/sheet/number-format.js +465 -68
  210. package/dist/sheet/objects.d.ts +106 -0
  211. package/dist/sheet/objects.js +149 -0
  212. package/dist/sheet/ods-document.d.ts +44 -0
  213. package/dist/sheet/ods-document.js +1275 -0
  214. package/dist/sheet/open-file.d.ts +26 -0
  215. package/dist/sheet/open-file.js +56 -0
  216. package/dist/sheet/outline.d.ts +82 -0
  217. package/dist/sheet/outline.js +228 -0
  218. package/dist/sheet/packs/array.d.ts +16 -0
  219. package/dist/sheet/packs/array.js +172 -0
  220. package/dist/sheet/packs/database.d.ts +42 -0
  221. package/dist/sheet/packs/database.js +142 -0
  222. package/dist/sheet/packs/engineering.d.ts +2 -0
  223. package/dist/sheet/packs/engineering.js +296 -0
  224. package/dist/sheet/packs/financial.d.ts +5 -0
  225. package/dist/sheet/packs/financial.js +220 -0
  226. package/dist/sheet/packs/math-stats.d.ts +2 -0
  227. package/dist/sheet/packs/math-stats.js +372 -0
  228. package/dist/sheet/packs/special.d.ts +44 -0
  229. package/dist/sheet/packs/special.js +187 -0
  230. package/dist/sheet/packs/stats-dist.d.ts +2 -0
  231. package/dist/sheet/packs/stats-dist.js +785 -0
  232. package/dist/sheet/packs/text-date.d.ts +2 -0
  233. package/dist/sheet/packs/text-date.js +332 -0
  234. package/dist/sheet/packs/trig.d.ts +2 -0
  235. package/dist/sheet/packs/trig.js +73 -0
  236. package/dist/sheet/page-setup.d.ts +73 -0
  237. package/dist/sheet/page-setup.js +73 -0
  238. package/dist/sheet/parse.js +42 -10
  239. package/dist/sheet/paste-special.d.ts +22 -0
  240. package/dist/sheet/paste-special.js +14 -5
  241. package/dist/sheet/pivot-range.d.ts +160 -0
  242. package/dist/sheet/pivot-range.js +333 -0
  243. package/dist/sheet/presence.d.ts +73 -0
  244. package/dist/sheet/presence.js +79 -0
  245. package/dist/sheet/print.d.ts +104 -0
  246. package/dist/sheet/print.js +169 -0
  247. package/dist/sheet/protection.d.ts +55 -1
  248. package/dist/sheet/protection.js +55 -2
  249. package/dist/sheet/rects.d.ts +1 -1
  250. package/dist/sheet/refs.d.ts +48 -0
  251. package/dist/sheet/refs.js +155 -8
  252. package/dist/sheet/ribbon-icons.d.ts +1 -1
  253. package/dist/sheet/ribbon-icons.js +186 -0
  254. package/dist/sheet/ribbon.d.ts +1 -1
  255. package/dist/sheet/ribbon.js +324 -23
  256. package/dist/sheet/sheet-kinds.d.ts +74 -0
  257. package/dist/sheet/sheet-kinds.js +91 -0
  258. package/dist/sheet/shortcuts.d.ts +13 -3
  259. package/dist/sheet/shortcuts.js +63 -12
  260. package/dist/sheet/sort.d.ts +51 -0
  261. package/dist/sheet/sort.js +107 -0
  262. package/dist/sheet/sparklines.d.ts +111 -0
  263. package/dist/sheet/sparklines.js +204 -0
  264. package/dist/sheet/table-styles.d.ts +74 -0
  265. package/dist/sheet/table-styles.js +152 -0
  266. package/dist/sheet/tables.d.ts +19 -0
  267. package/dist/sheet/tables.js +31 -0
  268. package/dist/sheet/tokenize.d.ts +18 -1
  269. package/dist/sheet/tokenize.js +60 -9
  270. package/dist/sheet/validation.d.ts +16 -0
  271. package/dist/sheet/validation.js +37 -0
  272. package/dist/sheet/workbook.d.ts +148 -3
  273. package/dist/sheet/workbook.js +742 -31
  274. package/dist/sheet/xls-cfb.d.ts +32 -0
  275. package/dist/sheet/xls-cfb.js +290 -0
  276. package/dist/sheet/xls-document.d.ts +10 -0
  277. package/dist/sheet/xls-document.js +1243 -0
  278. package/dist/sheet/xls-formula.d.ts +40 -0
  279. package/dist/sheet/xls-formula.js +569 -0
  280. package/dist/sheet/xlsx-document.d.ts +108 -0
  281. package/dist/sheet/xlsx-document.js +1849 -0
  282. package/dist/sheet/xlsx-drawing.d.ts +99 -0
  283. package/dist/sheet/xlsx-drawing.js +420 -0
  284. package/dist/sheet-enable.js +6 -7
  285. package/dist/sheet-text.d.ts +7 -0
  286. package/dist/sheet-text.js +29 -0
  287. package/dist/sources/auth-supabase.js +2 -2
  288. package/dist/sources/realtime-supabase.js +2 -2
  289. package/dist/sources/rest.d.ts +6 -2
  290. package/dist/sources/rest.js +42 -7
  291. package/dist/sources/supabase.d.ts +10 -1
  292. package/dist/sources/supabase.js +73 -40
  293. package/dist/studio/emit-project.js +14 -1
  294. package/dist/studio/index.js +1 -1
  295. package/dist/studio/project.d.ts +11 -2
  296. package/dist/studio/project.js +5 -2
  297. package/dist/studio/ui-components.generated.js +107 -2
  298. package/dist/sveltekit/in-memory.d.ts +2 -2
  299. package/dist/sveltekit/in-memory.js +125 -4
  300. package/dist/sveltekit/index.d.ts +1 -1
  301. package/dist/sveltekit/index.js +1 -1
  302. package/dist/sveltekit/query-plan.d.ts +50 -4
  303. package/dist/sveltekit/query-plan.js +25 -3
  304. package/dist/sveltekit/sql-source.d.ts +6 -0
  305. package/dist/sveltekit/sql-source.js +34 -6
  306. package/dist/sveltekit/sql.d.ts +31 -0
  307. package/dist/sveltekit/sql.js +135 -25
  308. package/dist/sveltekit/transport.d.ts +10 -2
  309. package/dist/sveltekit/transport.js +30 -6
  310. package/dist/upgrade-prompt.d.ts +1 -1
  311. package/dist/version.d.ts +1 -1
  312. package/dist/version.js +1 -1
  313. package/dist/watermark.js +35 -2
  314. package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
  315. package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
  316. package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
  317. package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
  318. package/dist/wc/SvField-B_kurx_P.js +149 -0
  319. package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
  320. package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
  321. package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
  322. package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
  323. package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
  324. package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
  325. package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
  326. package/dist/wc/SvModal-DrfOYYyX.js +189 -0
  327. package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
  328. package/dist/wc/chart-6OXlybVT.js +4341 -0
  329. package/dist/wc/chart-export-CeSADGfi.js +135 -0
  330. package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
  331. package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
  332. package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
  333. package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
  334. package/dist/wc/client-DQIzjDBG.js +3064 -0
  335. package/dist/wc/column-resize-DUPgynvc.js +101 -0
  336. package/dist/wc/date-format-DS-z_h9v.js +1130 -0
  337. package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
  338. package/dist/wc/editor-contract-peBDxir_.js +22 -0
  339. package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
  340. package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
  341. package/dist/wc/list-option-DrRyivWp.js +82 -0
  342. package/dist/wc/popover-WsGlvtQv.js +69 -0
  343. package/dist/wc/positioning-BiKLfu1E.js +112 -0
  344. package/dist/wc/react.d.ts +41 -0
  345. package/dist/wc/react.js +90 -0
  346. package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
  347. package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
  348. package/dist/wc/row-resize-limG_N1z.js +111 -0
  349. package/dist/wc/sv-sheet-element.d.ts +97 -0
  350. package/dist/wc/sv-sheet-element.js +44213 -0
  351. package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
  352. package/dist/wc/validate-BqDthkNr.js +88 -0
  353. package/dist/wc/vue.d.ts +31 -0
  354. package/dist/wc/vue.js +75 -0
  355. package/package.json +30 -5
  356. package/dist/designer/assets/GridMenus-BlsvL_aU.js +0 -8
  357. package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +0 -4
  358. package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +0 -1
  359. package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
  360. package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
  361. package/dist/designer/assets/index-CMFgAoVV.css +0 -1
  362. package/dist/designer/assets/row-resize-DLktcqUK.js +0 -1
  363. package/dist/designer/assets/src-EdI3Xqds.js +0 -58
  364. package/dist/designer/assets/validate-h-aslRV9.js +0 -1
@@ -14,8 +14,16 @@
14
14
  * short-circuit semantics) are handled in the evaluator instead, because by the
15
15
  * time a table entry runs, evaluation has already happened.
16
16
  */
17
- import { FormulaError, isError, err } from './ast.js';
18
- import { toNumber, toBool, toText, looseEquals, compare, numericOnly, isBlank, matchesCriterion, } from './coerce.js';
17
+ import { isError, err } from './ast.js';
18
+ import { toNumber, toBool, toText, looseEquals, compare, patternEquals, hasWildcards, wildcardRegExp, unescapeWildcards, numericOnly, isBlank, toDate, isoDate as iso, criteriaHits, multiCriteriaHits, criteriaPairs, } from './coerce.js';
19
+ import { formatWithPattern } from './number-format.js';
20
+ import { FINANCIAL_FUNCTIONS } from './packs/financial.js';
21
+ import { MATH_STATS_FUNCTIONS } from './packs/math-stats.js';
22
+ import { TEXT_DATE_FUNCTIONS } from './packs/text-date.js';
23
+ import { TRIG_FUNCTIONS } from './packs/trig.js';
24
+ import { ENGINEERING_FUNCTIONS } from './packs/engineering.js';
25
+ import { DATABASE_FUNCTIONS } from './packs/database.js';
26
+ import { STATS_DIST_FUNCTIONS } from './packs/stats-dist.js';
19
27
  const first = (a) => a.flat[0] ?? '';
20
28
  const nth = (a, i) => a.args[i]?.[0] ?? '';
21
29
  function sum(values) {
@@ -33,38 +41,73 @@ function roundTo(value, digits) {
33
41
  // 1.00, which is the binary-float surprise users report as a bug.
34
42
  return Math.round((value * f) * (1 + Number.EPSILON)) / f;
35
43
  }
36
- function toDate(v) {
37
- if (typeof v === 'number') {
38
- // Excel serial: day 1 is 1900-01-01, with the famous phantom leap day.
39
- return new Date(Date.UTC(1899, 11, 30) + v * 86400000);
44
+ /**
45
+ * Excel's approximate match, which is what the lookup family does when it
46
+ * cannot find the value itself. The column is taken as sorted, and the answer
47
+ * is the entry NEAREST the needle on one side: `dir` 1 for the largest entry
48
+ * not past it (VLOOKUP / HLOOKUP with range_lookup TRUE, MATCH 1, XLOOKUP -1),
49
+ * -1 for the smallest entry not before it (MATCH -1, XLOOKUP 1).
50
+ *
51
+ * Only cells of the needle's own type take part, so the text header sitting
52
+ * above a numeric tier table cannot become the answer. Among equal entries the
53
+ * later one wins, as Excel's own search lands on the last of a run.
54
+ */
55
+ function nearestIndex(pool, needle, dir) {
56
+ let best = -1;
57
+ for (let i = 0; i < pool.length; i += 1) {
58
+ const v = pool[i];
59
+ if (v === undefined || v === '' || isError(v))
60
+ continue;
61
+ if (typeof v !== typeof needle)
62
+ continue;
63
+ const side = compare(v, needle);
64
+ if (dir === 1 ? side > 0 : side < 0)
65
+ continue;
66
+ if (best < 0 || compare(v, pool[best]) * dir >= 0)
67
+ best = i;
40
68
  }
41
- const d = new Date(toText(v));
42
- if (Number.isNaN(d.getTime()))
43
- throw new FormulaError('#VALUE!');
44
- return d;
69
+ return best;
70
+ }
71
+ /** FIND and SEARCH count their optional start from 1; -1 says the argument
72
+ * is out of range, which Excel reports as #VALUE!. */
73
+ function startPosition(a) {
74
+ if (a.args[2] === undefined)
75
+ return 0;
76
+ const given = Math.trunc(toNumber(nth(a, 2)));
77
+ return given < 1 ? -1 : given - 1;
45
78
  }
46
- const iso = (d) => d.toISOString().slice(0, 10);
47
- /** Sum / count / average over a criterion range, optionally summing a parallel
48
- * range. The three IF functions differ only in what they do with the hits. */
49
- function criteriaHits(range, criterion) {
50
- const out = [];
51
- for (let i = 0; i < range.length; i += 1) {
52
- if (matchesCriterion(range[i], criterion))
53
- out.push(i);
79
+ /** The index of the `instance`-th `delim` in `s`, counted from the start for
80
+ * a positive instance and from the end for a negative one, or null when
81
+ * there are fewer than that many. Backs TEXTBEFORE and TEXTAFTER. */
82
+ function nthDelimiter(s, delim, instance) {
83
+ const positions = [];
84
+ for (let from = 0;;) {
85
+ const at = s.indexOf(delim, from);
86
+ if (at < 0)
87
+ break;
88
+ positions.push(at);
89
+ from = at + delim.length;
54
90
  }
55
- return out;
91
+ if (instance > 0)
92
+ return instance <= positions.length ? positions[instance - 1] : null;
93
+ if (instance < 0) {
94
+ const k = positions.length + instance;
95
+ return k >= 0 ? positions[k] : null;
96
+ }
97
+ return null;
56
98
  }
57
- /** SUMIFS / COUNTIFS: every (range, criterion) pair must match at an index. */
58
- function multiCriteriaHits(pairs) {
59
- const firstPair = pairs[0];
60
- if (!firstPair)
61
- return [];
62
- const out = [];
63
- for (let i = 0; i < firstPair[0].length; i += 1) {
64
- if (pairs.every(([range, crit]) => matchesCriterion(range[i] ?? '', crit)))
65
- out.push(i);
99
+ /** Where a wildcard pattern starts inside a text, or -1. Excel reports the
100
+ * earliest position a match can begin at, so the search walks forward and
101
+ * tries every ending from the shortest up. */
102
+ function wildcardSearch(text, pattern, from = 0) {
103
+ const re = wildcardRegExp(pattern);
104
+ for (let start = from; start <= text.length; start += 1) {
105
+ for (let end = start; end <= text.length; end += 1) {
106
+ if (re.test(text.slice(start, end)))
107
+ return start;
108
+ }
66
109
  }
67
- return out;
110
+ return -1;
68
111
  }
69
112
  export const FUNCTIONS = {
70
113
  // ---- Math and aggregation -------------------------------------------
@@ -86,7 +129,18 @@ export const FUNCTIONS = {
86
129
  const n = toNumber(nth(a, 0));
87
130
  return n - d * Math.floor(n / d);
88
131
  },
89
- POWER: (a) => Math.pow(toNumber(nth(a, 0)), toNumber(nth(a, 1))),
132
+ POWER: (a) => {
133
+ // The cube root of a negative is NaN in JavaScript and #NUM! in Excel;
134
+ // letting NaN out reaches the sheet as a blank, which says nothing.
135
+ const out = Math.pow(toNumber(nth(a, 0)), toNumber(nth(a, 1)));
136
+ return Number.isFinite(out) ? out : err('#NUM!');
137
+ },
138
+ /** Excel's NA(): the #N/A a lookup writes when it finds nothing. */
139
+ NA: () => err('#N/A'),
140
+ // Excel's TRUE() and FALSE(). Worth having for their own sake, and a file
141
+ // from LibreOffice writes every boolean cell as one of them.
142
+ TRUE: () => true,
143
+ FALSE: () => false,
90
144
  SQRT: (a) => {
91
145
  const n = toNumber(first(a));
92
146
  return n < 0 ? err('#NUM!') : Math.sqrt(n);
@@ -144,25 +198,9 @@ export const FUNCTIONS = {
144
198
  },
145
199
  SUMIFS: (a) => {
146
200
  const target = a.args[0] ?? [];
147
- const pairs = [];
148
- for (let i = 1; i + 1 < a.args.length + 1; i += 2) {
149
- const range = a.args[i];
150
- if (!range)
151
- break;
152
- pairs.push([range, a.args[i + 1]?.[0] ?? '']);
153
- }
154
- return sum(multiCriteriaHits(pairs).map((i) => target[i] ?? ''));
155
- },
156
- COUNTIFS: (a) => {
157
- const pairs = [];
158
- for (let i = 0; i < a.args.length; i += 2) {
159
- const range = a.args[i];
160
- if (!range)
161
- break;
162
- pairs.push([range, a.args[i + 1]?.[0] ?? '']);
163
- }
164
- return multiCriteriaHits(pairs).length;
201
+ return sum(multiCriteriaHits(criteriaPairs(a.args, 1)).map((i) => target[i] ?? ''));
165
202
  },
203
+ COUNTIFS: (a) => multiCriteriaHits(criteriaPairs(a.args, 0)).length,
166
204
  // ---- Logical ---------------------------------------------------------
167
205
  NOT: (a) => !toBool(first(a)),
168
206
  AND: (a) => a.flat.every((v) => toBool(v)),
@@ -193,6 +231,22 @@ export const FUNCTIONS = {
193
231
  UPPER: (a) => toText(first(a)).toUpperCase(),
194
232
  LOWER: (a) => toText(first(a)).toLowerCase(),
195
233
  TRIM: (a) => toText(first(a)).trim().replace(/\s+/g, ' '),
234
+ // Excel's HYPERLINK shows the friendly name and follows the link. The
235
+ // shell reads the link off the cell (`sheet/links.ts`), which is where a
236
+ // link put there by Insert > Link lives too, so a cell is clickable
237
+ // whichever way its link arrived; the value here is only what it shows.
238
+ HYPERLINK: (a) => {
239
+ const target = toText(first(a));
240
+ const friendly = a.args.length > 1 ? nth(a, 1) : undefined;
241
+ return friendly === undefined || friendly === '' ? target : friendly;
242
+ },
243
+ /**
244
+ * IMAGE(source, [alt]): a picture IN the cell. The value is the source,
245
+ * so the formula reads like any other one and a cell that reads it gets
246
+ * an address rather than a picture it cannot use; the shell is what
247
+ * draws it, from the call it finds on the cell (`sheet/cell-images.ts`).
248
+ */
249
+ IMAGE: (a) => toText(first(a)),
196
250
  CONCAT: (a) => a.flat.map((v) => toText(v)).join(''),
197
251
  CONCATENATE: (a) => a.flat.map((v) => toText(v)).join(''),
198
252
  TEXTJOIN: (a) => {
@@ -201,37 +255,150 @@ export const FUNCTIONS = {
201
255
  const rest = a.args.slice(2).flat();
202
256
  return rest.filter((v) => !skipEmpty || !isBlank(v)).map((v) => toText(v)).join(sep);
203
257
  },
258
+ // SUBSTITUTE(text, old, new, [instance]): every occurrence by default, or
259
+ // just the instance-th one when the fourth argument names it, the way Excel
260
+ // rewrites the second slash of a date and leaves the first.
204
261
  SUBSTITUTE: (a) => {
205
262
  const s = toText(nth(a, 0));
206
263
  const find = toText(nth(a, 1));
207
264
  const replace = toText(nth(a, 2));
208
265
  if (find === '')
209
266
  return s;
210
- return s.split(find).join(replace);
267
+ if (a.args[3] === undefined)
268
+ return s.split(find).join(replace);
269
+ const instance = Math.trunc(toNumber(nth(a, 3)));
270
+ if (instance < 1)
271
+ return err('#VALUE!');
272
+ let count = 0;
273
+ let from = 0;
274
+ for (;;) {
275
+ const at = s.indexOf(find, from);
276
+ // Fewer than `instance` occurrences: Excel leaves the text as it is.
277
+ if (at < 0)
278
+ return s;
279
+ count += 1;
280
+ if (count === instance)
281
+ return s.slice(0, at) + replace + s.slice(at + find.length);
282
+ from = at + find.length;
283
+ }
284
+ },
285
+ // TEXTBEFORE / TEXTAFTER(text, delimiter, [instance]): the part before or
286
+ // after the instance-th delimiter, counted from the end when instance is
287
+ // negative. The delimiter that is not there is #N/A, unless a sixth
288
+ // `if_not_found` is given.
289
+ TEXTBEFORE: (a) => {
290
+ const s = toText(nth(a, 0));
291
+ const delim = toText(nth(a, 1));
292
+ const instance = a.args[2] !== undefined ? Math.trunc(toNumber(nth(a, 2))) : 1;
293
+ if (delim === '')
294
+ return '';
295
+ const at = nthDelimiter(s, delim, instance);
296
+ return at === null ? (a.args[5] !== undefined ? nth(a, 5) : err('#N/A')) : s.slice(0, at);
297
+ },
298
+ TEXTAFTER: (a) => {
299
+ const s = toText(nth(a, 0));
300
+ const delim = toText(nth(a, 1));
301
+ const instance = a.args[2] !== undefined ? Math.trunc(toNumber(nth(a, 2))) : 1;
302
+ if (delim === '')
303
+ return s;
304
+ const at = nthDelimiter(s, delim, instance);
305
+ return at === null ? (a.args[5] !== undefined ? nth(a, 5) : err('#N/A')) : s.slice(at + delim.length);
306
+ },
307
+ // DOLLAR / FIXED(number, [decimals], [no_commas]): a number as the text of
308
+ // a currency or a fixed-decimal figure, grouped in thousands. DOLLAR wraps
309
+ // a negative in parentheses; a negative decimals rounds to the left of the
310
+ // point, so DOLLAR(1234.5, -2) is "$1,200".
311
+ DOLLAR: (a) => {
312
+ const decimals = a.args[1] !== undefined ? Math.trunc(toNumber(nth(a, 1))) : 2;
313
+ const rounded = roundTo(toNumber(nth(a, 0)), decimals);
314
+ const places = Math.max(decimals, 0);
315
+ const body = `$${Math.abs(rounded).toLocaleString('en-US', { minimumFractionDigits: places, maximumFractionDigits: places })}`;
316
+ return rounded < 0 ? `(${body})` : body;
317
+ },
318
+ FIXED: (a) => {
319
+ const decimals = a.args[1] !== undefined ? Math.trunc(toNumber(nth(a, 1))) : 2;
320
+ const noCommas = a.args[2] !== undefined && toBool(nth(a, 2));
321
+ const rounded = roundTo(toNumber(nth(a, 0)), decimals);
322
+ const places = Math.max(decimals, 0);
323
+ return noCommas
324
+ ? rounded.toFixed(places)
325
+ : rounded.toLocaleString('en-US', { minimumFractionDigits: places, maximumFractionDigits: places });
326
+ },
327
+ // COMBIN / PERMUT(n, k): the ways to choose k of n, unordered and ordered.
328
+ // The product form keeps the intermediate values small, so a big n does not
329
+ // overflow the way n! would.
330
+ COMBIN: (a) => {
331
+ const n = Math.trunc(toNumber(nth(a, 0)));
332
+ const k = Math.trunc(toNumber(nth(a, 1)));
333
+ if (n < 0 || k < 0 || k > n)
334
+ return err('#NUM!');
335
+ let result = 1;
336
+ for (let i = 0; i < k; i += 1)
337
+ result = (result * (n - i)) / (i + 1);
338
+ return Math.round(result);
339
+ },
340
+ PERMUT: (a) => {
341
+ const n = Math.trunc(toNumber(nth(a, 0)));
342
+ const k = Math.trunc(toNumber(nth(a, 1)));
343
+ if (n < 0 || k < 0 || k > n)
344
+ return err('#NUM!');
345
+ let result = 1;
346
+ for (let i = 0; i < k; i += 1)
347
+ result *= n - i;
348
+ return result;
349
+ },
350
+ // TYPE(value): 1 a number, 2 text, 4 a logical, 16 an error, 64 an array.
351
+ TYPE: (a) => {
352
+ const grid = a.grids[0];
353
+ if (grid && (grid.length > 1 || (grid[0]?.length ?? 0) > 1))
354
+ return 64;
355
+ const v = nth(a, 0);
356
+ if (isError(v))
357
+ return 16;
358
+ if (typeof v === 'number')
359
+ return 1;
360
+ if (typeof v === 'boolean')
361
+ return 4;
362
+ return 2;
211
363
  },
364
+ // FIND(find, within, [start]): case sensitive, 1-based, #VALUE! when the
365
+ // text is not there. The third argument is where the search BEGINS, and
366
+ // the answer is still counted from the start of the text, which is what
367
+ // makes the walk over every occurrence work.
212
368
  FIND: (a) => {
213
- // Case sensitive, 1-based, #VALUE! when absent. SEARCH is its
214
- // case-insensitive twin.
215
- const at = toText(nth(a, 1)).indexOf(toText(nth(a, 0)));
369
+ const from = startPosition(a);
370
+ if (from < 0)
371
+ return err('#VALUE!');
372
+ const at = toText(nth(a, 1)).indexOf(toText(nth(a, 0)), from);
216
373
  return at < 0 ? err('#VALUE!') : at + 1;
217
374
  },
375
+ // SEARCH is FIND's case-insensitive twin, and unlike FIND it reads Excel's
376
+ // wildcards: =SEARCH("n?rth", A1) finds either spelling.
218
377
  SEARCH: (a) => {
219
- const at = toText(nth(a, 1)).toLowerCase().indexOf(toText(nth(a, 0)).toLowerCase());
378
+ const needle = toText(nth(a, 0));
379
+ const hay = toText(nth(a, 1));
380
+ const from = startPosition(a);
381
+ if (from < 0)
382
+ return err('#VALUE!');
383
+ if (hasWildcards(needle)) {
384
+ const at = wildcardSearch(hay, needle, from);
385
+ return at < 0 ? err('#VALUE!') : at + 1;
386
+ }
387
+ const at = hay.toLowerCase().indexOf(unescapeWildcards(needle).toLowerCase(), from);
220
388
  return at < 0 ? err('#VALUE!') : at + 1;
221
389
  },
390
+ /**
391
+ * TEXT(value, format): the value through a number-format string, which is
392
+ * the same grammar a cell's own format speaks. It reads dates, fractions,
393
+ * elapsed time and the rest, since it is the same compiler; a colour in
394
+ * the pattern is ignored, as Excel ignores it here, and an empty pattern
395
+ * is an empty string.
396
+ */
222
397
  TEXT: (a) => {
223
- // Minimal: the full Excel format grammar arrives with per-cell number
224
- // formats. Until then this covers the common fixed-decimal case.
225
- const v = toNumber(nth(a, 0));
226
398
  const pattern = toText(nth(a, 1));
227
- const decimals = /\.(0+)/.exec(pattern)?.[1]?.length ?? 0;
228
- const body = v.toFixed(decimals);
229
- if (pattern.includes(',')) {
230
- const [int = '', frac] = body.split('.');
231
- const grouped = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
232
- return frac ? `${grouped}.${frac}` : grouped;
233
- }
234
- return body;
399
+ if (pattern === '')
400
+ return '';
401
+ return formatWithPattern(first(a), pattern);
235
402
  },
236
403
  // ---- Date ------------------------------------------------------------
237
404
  TODAY: () => iso(new Date()),
@@ -248,16 +415,19 @@ export const FUNCTIONS = {
248
415
  },
249
416
  // DAYS(end, start): whole days, negative when the end comes first.
250
417
  DAYS: (a) => Math.round((toDate(nth(a, 0)).getTime() - toDate(nth(a, 1)).getTime()) / 86400000),
251
- // DATEDIF(start, end, unit): Excel's "d", "m" and "y", completed units
252
- // only, and #NUM! when the start comes after the end, as Excel gives.
418
+ // DATEDIF(start, end, unit): Excel's six units - "d", "m", "y" for the
419
+ // whole difference, and "md", "ym", "yd" for the part left when the higher
420
+ // units are set aside, which is how an age reads "y years, m months, d
421
+ // days". #NUM! when the start comes after the end, as Excel gives.
253
422
  DATEDIF: (a) => {
254
423
  const start = toDate(nth(a, 0));
255
424
  const end = toDate(nth(a, 1));
256
425
  if (end.getTime() < start.getTime())
257
426
  return err('#NUM!');
258
427
  const unit = toText(nth(a, 2)).toUpperCase();
428
+ const day = 86400000;
259
429
  if (unit === 'D')
260
- return Math.floor((end.getTime() - start.getTime()) / 86400000);
430
+ return Math.floor((end.getTime() - start.getTime()) / day);
261
431
  let months = (end.getUTCFullYear() - start.getUTCFullYear()) * 12 + (end.getUTCMonth() - start.getUTCMonth());
262
432
  if (end.getUTCDate() < start.getUTCDate())
263
433
  months -= 1;
@@ -265,9 +435,35 @@ export const FUNCTIONS = {
265
435
  return months;
266
436
  if (unit === 'Y')
267
437
  return Math.floor(months / 12);
438
+ // Months ignoring years: the month part of the whole difference.
439
+ if (unit === 'YM')
440
+ return months % 12;
441
+ // Days ignoring months and years: the days past the last whole month.
442
+ // Anchoring `months` months onto the start, its day clamped to that
443
+ // month's length, keeps the count from going negative on the awkward
444
+ // month-end cases Excel itself is inconsistent about (Jan 31 -> Mar 1).
445
+ if (unit === 'MD') {
446
+ const anchorMonth = start.getUTCMonth() + months;
447
+ const lastDay = new Date(Date.UTC(start.getUTCFullYear(), anchorMonth + 1, 0)).getUTCDate();
448
+ const anchorDay = Math.min(start.getUTCDate(), lastDay);
449
+ const anchor = Date.UTC(start.getUTCFullYear(), anchorMonth, anchorDay);
450
+ return Math.floor((end.getTime() - anchor) / day);
451
+ }
452
+ // Days ignoring years: the days between, with the start moved to end's
453
+ // year (or the year before, when that would overshoot the end).
454
+ if (unit === 'YD') {
455
+ let anchor = Date.UTC(end.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate());
456
+ if (anchor > end.getTime())
457
+ anchor = Date.UTC(end.getUTCFullYear() - 1, start.getUTCMonth(), start.getUTCDate());
458
+ return Math.floor((end.getTime() - anchor) / day);
459
+ }
268
460
  return err('#NUM!');
269
461
  },
270
462
  // ---- Lookup ----------------------------------------------------------
463
+ // VLOOKUP(lookup, table, colIndex, [rangeLookup]). The fourth argument is
464
+ // Excel's range_lookup and it defaults to TRUE: the banded lookup - tax
465
+ // brackets, commission tiers, a grade table - is the DEFAULT reading, and
466
+ // FALSE is how you ask for an exact match.
271
467
  VLOOKUP: (a) => {
272
468
  const needle = nth(a, 0);
273
469
  const grid = a.grids[1];
@@ -276,14 +472,18 @@ export const FUNCTIONS = {
276
472
  const col = Math.round(toNumber(nth(a, 2)));
277
473
  if (col < 1)
278
474
  return err('#VALUE!');
279
- for (const row of grid) {
280
- if (row[0] !== undefined && looseEquals(row[0], needle)) {
281
- const cell = row[col - 1];
282
- return cell === undefined ? err('#REF!') : cell;
283
- }
284
- }
285
- return err('#N/A');
475
+ const keys = grid.map((row) => row[0] ?? '');
476
+ const exact = a.args[3] !== undefined && !toBool(nth(a, 3));
477
+ const at = exact
478
+ ? keys.findIndex((v) => patternEquals(v, needle))
479
+ : nearestIndex(keys, needle, 1);
480
+ if (at < 0)
481
+ return err('#N/A');
482
+ const cell = grid[at][col - 1];
483
+ return cell === undefined ? err('#REF!') : cell;
286
484
  },
485
+ // HLOOKUP reads the same way, along the header row instead of down the
486
+ // first column.
287
487
  HLOOKUP: (a) => {
288
488
  const needle = nth(a, 0);
289
489
  const grid = a.grids[1];
@@ -293,29 +493,27 @@ export const FUNCTIONS = {
293
493
  if (rowIndex < 1)
294
494
  return err('#VALUE!');
295
495
  const header = grid[0] ?? [];
296
- for (let c = 0; c < header.length; c += 1) {
297
- if (looseEquals(header[c], needle)) {
298
- const cell = grid[rowIndex - 1]?.[c];
299
- return cell === undefined ? err('#REF!') : cell;
300
- }
301
- }
302
- return err('#N/A');
496
+ const exact = a.args[3] !== undefined && !toBool(nth(a, 3));
497
+ const at = exact
498
+ ? header.findIndex((v) => patternEquals(v, needle))
499
+ : nearestIndex(header, needle, 1);
500
+ if (at < 0)
501
+ return err('#N/A');
502
+ const cell = grid[rowIndex - 1]?.[at];
503
+ return cell === undefined ? err('#REF!') : cell;
303
504
  },
304
505
  MATCH: (a) => {
305
506
  const needle = nth(a, 0);
306
507
  const pool = a.args[1] ?? [];
307
508
  const mode = a.args[2] ? Math.round(toNumber(nth(a, 2))) : 1;
308
509
  if (mode === 0) {
309
- const at = pool.findIndex((v) => looseEquals(v, needle));
510
+ // An exact match takes Excel's wildcards: =MATCH("North*", ...).
511
+ const at = pool.findIndex((v) => patternEquals(v, needle));
310
512
  return at < 0 ? err('#N/A') : at + 1;
311
513
  }
312
- // Ordered search: the last value not past the needle.
313
- let best = -1;
314
- for (let i = 0; i < pool.length; i += 1) {
315
- const cmp = compare(pool[i], needle);
316
- if (mode === 1 ? cmp <= 0 : cmp >= 0)
317
- best = i;
318
- }
514
+ // Ordered search: 1 wants the last value not past the needle in an
515
+ // ascending range, -1 the last value not before it in a descending one.
516
+ const best = nearestIndex(pool, needle, mode === 1 ? 1 : -1);
319
517
  return best < 0 ? err('#N/A') : best + 1;
320
518
  },
321
519
  INDEX: (a) => {
@@ -338,16 +536,90 @@ export const FUNCTIONS = {
338
536
  const cell = row[colIndex - 1];
339
537
  return cell === undefined ? err('#REF!') : cell;
340
538
  },
539
+ // XLOOKUP(lookup, haystack, results, [ifMissing], [matchMode], [searchMode]).
540
+ // matchMode 0 is the exact match it defaults to, -1 falls back to the next
541
+ // smaller item, 1 to the next larger one and 2 reads the value as a wildcard
542
+ // pattern; a negative searchMode reads the range from the end, which is how
543
+ // you pick the LAST of several matches.
341
544
  XLOOKUP: (a) => {
342
545
  const needle = nth(a, 0);
343
546
  const haystack = a.args[1] ?? [];
344
547
  const results = a.args[2] ?? [];
345
- const at = haystack.findIndex((v) => looseEquals(v, needle));
548
+ const mode = a.args[4] !== undefined ? Math.round(toNumber(nth(a, 4))) : 0;
549
+ const back = a.args[5] !== undefined && Math.round(toNumber(nth(a, 5))) < 0;
550
+ // Unlike the older lookups, XLOOKUP reads wildcards only when asked:
551
+ // match mode 2.
552
+ const same = mode === 2
553
+ ? (v) => patternEquals(v, needle)
554
+ : (v) => looseEquals(v, needle);
555
+ let at = -1;
556
+ for (let i = 0; i < haystack.length; i += 1) {
557
+ const j = back ? haystack.length - 1 - i : i;
558
+ if (same(haystack[j])) {
559
+ at = j;
560
+ break;
561
+ }
562
+ }
563
+ if (at < 0 && mode === -1)
564
+ at = nearestIndex(haystack, needle, 1);
565
+ if (at < 0 && mode === 1)
566
+ at = nearestIndex(haystack, needle, -1);
346
567
  if (at < 0)
347
- return a.args[3] ? nth(a, 3) : err('#N/A');
568
+ return a.args[3] !== undefined ? nth(a, 3) : err('#N/A');
348
569
  const cell = results[at];
349
570
  return cell === undefined ? err('#REF!') : cell;
350
571
  },
572
+ // LOOKUP(value, vector, [result]): the old approximate lookup that assumes
573
+ // its vector is sorted ascending and takes the largest item not past the
574
+ // value, returning the matching cell of the result vector - the lookup
575
+ // vector itself when none is given.
576
+ LOOKUP: (a) => {
577
+ const needle = nth(a, 0);
578
+ const vector = a.args[1] ?? [];
579
+ const result = a.args[2] ?? vector;
580
+ const at = nearestIndex(vector, needle, 1);
581
+ if (at < 0)
582
+ return err('#N/A');
583
+ const cell = result[at];
584
+ return cell === undefined ? err('#N/A') : cell;
585
+ },
586
+ // XMATCH(value, array, [matchMode], [searchMode]): MATCH's modern twin.
587
+ // matchMode 0 is the exact default, -1 falls back to the next smaller item,
588
+ // 1 to the next larger, 2 reads a wildcard; a negative searchMode reads from
589
+ // the end, so it finds the LAST of several matches.
590
+ XMATCH: (a) => {
591
+ const needle = nth(a, 0);
592
+ const pool = a.args[1] ?? [];
593
+ const mode = a.args[2] !== undefined ? Math.round(toNumber(nth(a, 2))) : 0;
594
+ const back = a.args[3] !== undefined && Math.round(toNumber(nth(a, 3))) < 0;
595
+ const same = mode === 2
596
+ ? (v) => patternEquals(v, needle)
597
+ : (v) => looseEquals(v, needle);
598
+ let at = -1;
599
+ for (let i = 0; i < pool.length; i += 1) {
600
+ const j = back ? pool.length - 1 - i : i;
601
+ if (same(pool[j])) {
602
+ at = j;
603
+ break;
604
+ }
605
+ }
606
+ if (at < 0 && mode === -1)
607
+ at = nearestIndex(pool, needle, 1);
608
+ if (at < 0 && mode === 1)
609
+ at = nearestIndex(pool, needle, -1);
610
+ return at < 0 ? err('#N/A') : at + 1;
611
+ },
612
+ // ---- The packs: financial, more math and statistics, more text and
613
+ // date. Each lives in its own module under ./packs so this table stays
614
+ // readable; all of them are on by default, as a sheet user expects PMT
615
+ // to work without registering anything.
616
+ ...FINANCIAL_FUNCTIONS,
617
+ ...MATH_STATS_FUNCTIONS,
618
+ ...TEXT_DATE_FUNCTIONS,
619
+ ...TRIG_FUNCTIONS,
620
+ ...ENGINEERING_FUNCTIONS,
621
+ ...DATABASE_FUNCTIONS,
622
+ ...STATS_DIST_FUNCTIONS,
351
623
  };
352
624
  /** Merge caller-supplied functions over the built-ins. Keys are uppercased so
353
625
  * a consumer can register `myFn` and write `=MYFN()`. */