@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
@@ -69,8 +69,20 @@ function splitSections(pattern) {
69
69
  out.push(current);
70
70
  return out;
71
71
  }
72
+ const CONDITION = /^(<=|>=|<>|<|>|=)\s*(-?\d+(?:\.\d+)?)$/;
73
+ function passes(condition, n) {
74
+ switch (condition.op) {
75
+ case '<': return n < condition.value;
76
+ case '<=': return n <= condition.value;
77
+ case '>': return n > condition.value;
78
+ case '>=': return n >= condition.value;
79
+ case '=': return n === condition.value;
80
+ case '<>': return n !== condition.value;
81
+ }
82
+ }
72
83
  function parseSection(raw) {
73
84
  let color;
85
+ let condition;
74
86
  let body = '';
75
87
  // Walk rather than regex-replace: a pattern like `"[" @ "]"` has brackets
76
88
  // INSIDE quoted literals, and a global /\[[^\]]*\]/ happily eats the text
@@ -96,19 +108,37 @@ function parseSection(raw) {
96
108
  continue;
97
109
  }
98
110
  const inner = raw.slice(i + 1, end).trim().toUpperCase();
111
+ // An elapsed-time token is part of the pattern rather than a
112
+ // directive about it, so it stays in the body for the renderer.
113
+ if (ELAPSED_TOKEN.test(raw.slice(i))) {
114
+ body += raw.slice(i, end + 1);
115
+ i = end;
116
+ continue;
117
+ }
99
118
  const named = COLORS[inner];
100
119
  if (named)
101
120
  color = named;
102
- // Anything else (a condition like [<100]) is unsupported: drop it rather
103
- // than print it.
121
+ const cond = CONDITION.exec(inner);
122
+ if (cond)
123
+ condition = { op: cond[1], value: Number(cond[2]) };
124
+ // Anything else ([$-409] locale tags and the like) is dropped rather
125
+ // than printed.
104
126
  i = end;
105
127
  continue;
106
128
  }
107
129
  body += ch;
108
130
  }
109
- return { raw, color, body };
131
+ return condition ? { raw, color, condition, body } : { raw, color, body };
110
132
  }
111
- const DATE_TOKEN = /^(yyyy|yy|mmmm|mmm|mm|m|dddd|ddd|dd|d|hh|h|ss|s|AM\/PM|am\/pm|A\/P)/;
133
+ const DATE_TOKEN = /^(yyyy|yy|mmmmm|mmmm|mmm|mm|m|dddd|ddd|dd|d|hh|h|ss|s|AM\/PM|am\/pm|A\/P|a\/p)/;
134
+ /**
135
+ * Excel's elapsed-time tokens: the hours, minutes or seconds a duration
136
+ * holds in TOTAL rather than the clock's reading of it, which is what a
137
+ * timesheet needs. `[h]:mm` over 1.5 days is 36:00, not 12:00. Only h, m
138
+ * and s go in these brackets, so a colour (`[Red]`) or a condition
139
+ * (`[>100]`) never matches.
140
+ */
141
+ const ELAPSED_TOKEN = /^\[(hh?|mm?|ss?)\]/;
112
142
  /** Does this section describe a date? Scans outside quotes and escapes. */
113
143
  function looksLikeDate(body) {
114
144
  for (let i = 0; i < body.length; i += 1) {
@@ -122,6 +152,15 @@ function looksLikeDate(body) {
122
152
  i = end < 0 ? body.length : end;
123
153
  continue;
124
154
  }
155
+ if (ELAPSED_TOKEN.test(body.slice(i)))
156
+ return true;
157
+ if (ch === '[') {
158
+ // Any other bracket is a colour or a condition, and says nothing
159
+ // about whether this is a date.
160
+ const end = body.indexOf(']', i + 1);
161
+ i = end < 0 ? body.length : end;
162
+ continue;
163
+ }
125
164
  if (DATE_TOKEN.test(body.slice(i))) {
126
165
  // `m` alone is ambiguous with the thousands/number grammar only in
127
166
  // theory: a numeric pattern never contains a bare letter outside quotes.
@@ -150,9 +189,74 @@ function toDate(value) {
150
189
  }
151
190
  return null;
152
191
  }
153
- function renderDate(body, date) {
192
+ /** The meridiem token a pattern carries, which puts `h` on a 12-hour clock. */
193
+ function meridiemOf(body) {
194
+ for (let i = 0; i < body.length; i += 1) {
195
+ if (body[i] === '\\') {
196
+ i += 1;
197
+ continue;
198
+ }
199
+ if (body[i] === '"') {
200
+ const end = body.indexOf('"', i + 1);
201
+ i = end < 0 ? body.length : end;
202
+ continue;
203
+ }
204
+ const m = /^(AM\/PM|am\/pm|A\/P|a\/p)/.exec(body.slice(i));
205
+ if (m)
206
+ return m[1];
207
+ }
208
+ return null;
209
+ }
210
+ /**
211
+ * The finest unit a time pattern shows, in milliseconds, or null for a
212
+ * pattern that shows no time. Excel ROUNDS to it: 23:59:40 under `hh:mm`
213
+ * reads 00:00, and the date rolls with it where the pattern shows one.
214
+ */
215
+ function finestUnit(body) {
216
+ let unit = null;
217
+ let sawHour = false;
218
+ const finer = (ms) => { unit = unit === null ? ms : Math.min(unit, ms); };
219
+ for (let i = 0; i < body.length; i += 1) {
220
+ if (body[i] === '\\') {
221
+ i += 1;
222
+ continue;
223
+ }
224
+ if (body[i] === '"') {
225
+ const end = body.indexOf('"', i + 1);
226
+ i = end < 0 ? body.length : end;
227
+ continue;
228
+ }
229
+ const fraction = /^\.(0+)/.exec(body.slice(i));
230
+ if (fraction && unit !== null) {
231
+ finer(Math.pow(10, 3 - fraction[1].length));
232
+ i += fraction[0].length - 1;
233
+ continue;
234
+ }
235
+ const m = DATE_TOKEN.exec(body.slice(i));
236
+ if (!m)
237
+ continue;
238
+ const token = m[1];
239
+ i += token.length - 1;
240
+ if (token === 'h' || token === 'hh') {
241
+ sawHour = true;
242
+ finer(3600000);
243
+ }
244
+ else if (token === 's' || token === 'ss')
245
+ finer(1000);
246
+ else if ((token === 'm' || token === 'mm') && sawHour)
247
+ finer(60000);
248
+ }
249
+ return unit;
250
+ }
251
+ function renderDate(body, date, serial) {
252
+ const unit = finestUnit(body);
253
+ if (unit !== null && unit > 0)
254
+ date = new Date(Math.round(date.getTime() / unit) * unit);
255
+ const meridiem = meridiemOf(body);
256
+ const hourOf = (d) => (meridiem ? ((d.getUTCHours() + 11) % 12) + 1 : d.getUTCHours());
154
257
  let out = '';
155
258
  let sawHour = false;
259
+ let sawSecond = false;
156
260
  for (let i = 0; i < body.length; i += 1) {
157
261
  const ch = body[i];
158
262
  if (ch === '\\') {
@@ -160,12 +264,33 @@ function renderDate(body, date) {
160
264
  i += 1;
161
265
  continue;
162
266
  }
267
+ const elapsed = ELAPSED_TOKEN.exec(body.slice(i));
268
+ if (elapsed) {
269
+ const token = elapsed[1];
270
+ i += elapsed[0].length - 1;
271
+ // Without a serial there is no duration to total, so the clock's own
272
+ // reading is the best answer; with one, the whole of it counts.
273
+ const days = serial ?? 0;
274
+ const total = token.startsWith('h') ? days * 24 : token.startsWith('m') ? days * 1440 : days * 86400;
275
+ const whole = Math.floor(Math.abs(total) + 1e-9) * (total < 0 ? -1 : 1);
276
+ out += token.length === 2 ? pad2(whole) : String(whole);
277
+ if (token.startsWith('h'))
278
+ sawHour = true;
279
+ continue;
280
+ }
163
281
  if (ch === '"') {
164
282
  const end = body.indexOf('"', i + 1);
165
283
  out += body.slice(i + 1, end < 0 ? body.length : end);
166
284
  i = end < 0 ? body.length : end;
167
285
  continue;
168
286
  }
287
+ const fraction = sawSecond ? /^\.(0+)/.exec(body.slice(i)) : null;
288
+ if (fraction) {
289
+ const digits = fraction[1].length;
290
+ out += `.${String(date.getUTCMilliseconds()).padStart(3, '0').slice(0, digits).padEnd(digits, '0')}`;
291
+ i += fraction[0].length - 1;
292
+ continue;
293
+ }
169
294
  const m = DATE_TOKEN.exec(body.slice(i));
170
295
  if (!m) {
171
296
  out += ch;
@@ -173,6 +298,8 @@ function renderDate(body, date) {
173
298
  }
174
299
  const token = m[1];
175
300
  i += token.length - 1;
301
+ if (token === 's' || token === 'ss')
302
+ sawSecond = true;
176
303
  switch (token) {
177
304
  case 'yyyy':
178
305
  out += date.getUTCFullYear();
@@ -199,11 +326,11 @@ function renderDate(body, date) {
199
326
  out += date.getUTCDate();
200
327
  break;
201
328
  case 'hh':
202
- out += pad2(date.getUTCHours());
329
+ out += pad2(hourOf(date));
203
330
  sawHour = true;
204
331
  break;
205
332
  case 'h':
206
- out += date.getUTCHours();
333
+ out += hourOf(date);
207
334
  sawHour = true;
208
335
  break;
209
336
  case 'ss':
@@ -212,10 +339,22 @@ function renderDate(body, date) {
212
339
  case 's':
213
340
  out += date.getUTCSeconds();
214
341
  break;
342
+ // Excel prints what the token spells: AM/PM in full, A/P as the one
343
+ // letter, and each in the case it was written in.
215
344
  case 'AM/PM':
345
+ out += date.getUTCHours() < 12 ? 'AM' : 'PM';
346
+ break;
216
347
  case 'am/pm':
348
+ out += date.getUTCHours() < 12 ? 'am' : 'pm';
349
+ break;
217
350
  case 'A/P':
218
- out += date.getUTCHours() < 12 ? 'AM' : 'PM';
351
+ out += date.getUTCHours() < 12 ? 'A' : 'P';
352
+ break;
353
+ case 'a/p':
354
+ out += date.getUTCHours() < 12 ? 'a' : 'p';
355
+ break;
356
+ case 'mmmmm':
357
+ out += MONTHS[date.getUTCMonth()].slice(0, 1);
219
358
  break;
220
359
  // `m` and `mm` are MINUTES after an hour token, MONTHS otherwise. This
221
360
  // is the one piece of context the scanner carries, and getting it wrong
@@ -231,7 +370,105 @@ function renderDate(body, date) {
231
370
  }
232
371
  return out;
233
372
  }
234
- /** Pull a numeric section apart into literal text and digit placeholders. */
373
+ const FRACTION = /^(?:([#0?]+)\s+)?([#0?]+)\/([#0?]+|\d+)$/;
374
+ /** The plan for a fraction section, or null when it is not one. */
375
+ function planFraction(body) {
376
+ // Only the bare shape reads as a fraction; anything with quoted text or an
377
+ // escape around it stays on the ordinary numeric path, where it always was.
378
+ const m = FRACTION.exec(body.trim());
379
+ if (!m)
380
+ return null;
381
+ const den = m[3];
382
+ return { int: m[1] ?? null, num: m[2], den, denLiteral: /^\d+$/.test(den) ? Number(den) : null };
383
+ }
384
+ /** The closest numerator and denominator, with the denominator capped. */
385
+ function bestFraction(value, maxDen) {
386
+ // Stern-Brocot: take the mediant of the two bounding fractions until the
387
+ // denominator would pass the cap. Exact for anything that fits, and it
388
+ // cannot run longer than the cap.
389
+ let loNum = 0;
390
+ let loDen = 1;
391
+ let hiNum = 1;
392
+ let hiDen = 0;
393
+ let bestNum = Math.round(value);
394
+ let bestDen = 1;
395
+ let bestErr = Math.abs(value - bestNum);
396
+ for (let i = 0; i < 64; i += 1) {
397
+ const num = loNum + hiNum;
398
+ const den = loDen + hiDen;
399
+ if (den > maxDen)
400
+ break;
401
+ const mediant = num / den;
402
+ const err = Math.abs(value - mediant);
403
+ if (err < bestErr - 1e-12) {
404
+ bestErr = err;
405
+ bestNum = num;
406
+ bestDen = den;
407
+ }
408
+ if (err < 1e-12)
409
+ break;
410
+ if (mediant < value) {
411
+ loNum = num;
412
+ loDen = den;
413
+ }
414
+ else {
415
+ hiNum = num;
416
+ hiDen = den;
417
+ }
418
+ }
419
+ return { num: bestNum, den: bestDen };
420
+ }
421
+ /** A placeholder group as Excel pads it: `0` a zero, `?` a space, `#` nothing. */
422
+ function padHolders(text, holders) {
423
+ if (text.length >= holders.length)
424
+ return text;
425
+ const filler = holders.includes('0') ? '0' : holders.includes('?') ? ' ' : '';
426
+ return filler ? filler.repeat(holders.length - text.length) + text : text;
427
+ }
428
+ /** One number through a fraction pattern. */
429
+ function renderFraction(plan, value) {
430
+ const sign = value < 0 ? '-' : '';
431
+ const magnitude = Math.abs(value);
432
+ const whole = plan.int ? Math.floor(magnitude + 1e-12) : 0;
433
+ const rest = magnitude - whole;
434
+ const maxDen = plan.denLiteral ?? Math.pow(10, plan.den.length) - 1;
435
+ const found = plan.denLiteral
436
+ ? { num: Math.round(rest * plan.denLiteral), den: plan.denLiteral }
437
+ : bestFraction(rest, maxDen);
438
+ let num = found.num;
439
+ const den = found.den;
440
+ let carried = whole;
441
+ // Rounding up to a whole: 0.99 over `# ?/?` is 1, not 1/1.
442
+ if (den > 0 && num >= den) {
443
+ carried += Math.floor(num / den);
444
+ num %= den;
445
+ }
446
+ if (!plan.int) {
447
+ return `${sign}${padHolders(String(num + carried * den), plan.num)}/${padHolders(String(den), plan.den)}`;
448
+ }
449
+ const blanks = (holders) => (holders.includes('?') ? ' '.repeat(holders.length) : '');
450
+ const wholeText = carried === 0 && !plan.int.includes('0')
451
+ ? ' '.repeat(plan.int.includes('?') ? plan.int.length : 0)
452
+ : padHolders(String(carried), plan.int);
453
+ // No remainder: Excel leaves the fraction's width blank rather than
454
+ // printing 0/1, so a column of them still lines up.
455
+ const fraction = num === 0
456
+ ? `${blanks(plan.num)} ${blanks(plan.den)}`
457
+ : `${padHolders(String(num), plan.num)}/${padHolders(String(den), plan.den)}`;
458
+ return `${sign}${wholeText} ${fraction}`.replace(/\s+$/, '');
459
+ }
460
+ /**
461
+ * Pull a numeric section apart into literal text and digit placeholders.
462
+ *
463
+ * Literal text before the first digit is the prefix and after the last is
464
+ * the suffix. Literal text BETWEEN integer digits, as in `000-00-0000` or
465
+ * `(###) ###-####`, turns the integer side into a mask that the digits are
466
+ * laid into from the right, which is how Excel's Special formats work.
467
+ *
468
+ * `_x` (Excel: leave the width of x) and `*x` (fill the cell with x) both
469
+ * become one space here: text has no cell width to leave or fill, and one
470
+ * space is what keeps `_($* #,##0.00_)` reading as `$ 1,234.00`.
471
+ */
235
472
  function planNumeric(body) {
236
473
  let prefix = '';
237
474
  let suffix = '';
@@ -244,24 +481,35 @@ function planNumeric(body) {
244
481
  let scale = 1;
245
482
  let seenPlaceholder = false;
246
483
  let afterDecimal = false;
484
+ // The integer side as tokens, literal text included, until the decimal
485
+ // point. `pending` holds literal text after the last integer digit, which
486
+ // is part of the mask only if another digit follows it.
487
+ const mask = [];
488
+ let pending = '';
489
+ let maskHasLiteral = false;
490
+ const literal = (text) => {
491
+ if (!seenPlaceholder)
492
+ prefix += text;
493
+ else if (afterDecimal || scientific)
494
+ suffix += text;
495
+ else
496
+ pending += text;
497
+ };
247
498
  for (let i = 0; i < body.length; i += 1) {
248
499
  const ch = body[i];
249
500
  if (ch === '\\') {
250
- const next = body[i + 1] ?? '';
251
- if (seenPlaceholder)
252
- suffix += next;
253
- else
254
- prefix += next;
501
+ literal(body[i + 1] ?? '');
502
+ i += 1;
503
+ continue;
504
+ }
505
+ if (ch === '_' || ch === '*') {
506
+ literal(' ');
255
507
  i += 1;
256
508
  continue;
257
509
  }
258
510
  if (ch === '"') {
259
511
  const end = body.indexOf('"', i + 1);
260
- const text = body.slice(i + 1, end < 0 ? body.length : end);
261
- if (seenPlaceholder)
262
- suffix += text;
263
- else
264
- prefix += text;
512
+ literal(body.slice(i + 1, end < 0 ? body.length : end));
265
513
  i = end < 0 ? body.length : end;
266
514
  continue;
267
515
  }
@@ -273,8 +521,15 @@ function planNumeric(body) {
273
521
  expPlaceholders += ch;
274
522
  else if (afterDecimal)
275
523
  fracPlaceholders += ch;
276
- else
524
+ else {
277
525
  intPlaceholders += ch;
526
+ if (pending) {
527
+ mask.push({ kind: 'lit', text: pending });
528
+ maskHasLiteral = true;
529
+ pending = '';
530
+ }
531
+ mask.push({ kind: 'digit', ch });
532
+ }
278
533
  continue;
279
534
  }
280
535
  if (ch === '.') {
@@ -294,10 +549,7 @@ function planNumeric(body) {
294
549
  }
295
550
  if (ch === '%') {
296
551
  percent = true;
297
- if (seenPlaceholder)
298
- suffix += '%';
299
- else
300
- prefix += '%';
552
+ literal('%');
301
553
  continue;
302
554
  }
303
555
  if ((ch === 'E' || ch === 'e') && /^[+-]/.test(body.slice(i + 1))) {
@@ -305,33 +557,74 @@ function planNumeric(body) {
305
557
  i += 1;
306
558
  continue;
307
559
  }
308
- if (seenPlaceholder)
309
- suffix += ch;
310
- else
311
- prefix += ch;
560
+ literal(ch);
312
561
  }
562
+ // Literal text after the last integer digit belongs to the suffix, before
563
+ // whatever the fraction and exponent added.
564
+ if (pending)
565
+ suffix = pending + suffix;
566
+ // A `?` on the integer side pads with a space, which the mask renderer
567
+ // does and the plain path (digits, grouping) does not.
568
+ const needsMask = maskHasLiteral || intPlaceholders.includes('?');
313
569
  return {
314
- prefix, suffix, intPlaceholders, fracPlaceholders, expPlaceholders,
570
+ prefix, suffix, intPlaceholders, intMask: needsMask ? mask : null,
571
+ fracPlaceholders, expPlaceholders,
315
572
  useGrouping, scale, percent, scientific,
316
573
  };
317
574
  }
318
- function group(intText) {
319
- return intText.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
575
+ /** Lay digits into a mask from the right: `000-00-0000` on 123456789 is
576
+ * `123-45-6789`; digits left over go in front, missing ones follow the
577
+ * placeholder's rule, so `###-####` on 1234 is `-1234` as Excel gives. */
578
+ function renderMask(mask, digits) {
579
+ let out = '';
580
+ let at = digits.length;
581
+ for (let i = mask.length - 1; i >= 0; i -= 1) {
582
+ const token = mask[i];
583
+ if (token.kind === 'lit') {
584
+ out = token.text + out;
585
+ continue;
586
+ }
587
+ if (at > 0) {
588
+ at -= 1;
589
+ out = digits[at] + out;
590
+ continue;
591
+ }
592
+ if (token.ch === '0')
593
+ out = `0${out}`;
594
+ else if (token.ch === '?')
595
+ out = ` ${out}`;
596
+ }
597
+ return digits.slice(0, at) + out;
598
+ }
599
+ function group(intText, mark) {
600
+ return intText.replace(/\B(?=(\d{3})+(?!\d))/g, mark);
320
601
  }
321
- function renderNumeric(plan, value) {
602
+ function renderNumeric(plan, value, marks) {
322
603
  let n = value;
323
604
  if (plan.percent)
324
605
  n *= 100;
325
606
  if (plan.scale !== 1)
326
607
  n /= plan.scale;
327
608
  if (plan.scientific) {
328
- const text = n.toExponential(plan.fracPlaceholders.length);
329
- // toExponential gives "1.23e+4"; Excel writes the exponent with at least
330
- // as many digits as the pattern asked for.
331
- const [mantissa = '', exponent = ''] = text.split('e');
332
- const sign = exponent.startsWith('-') ? '-' : '+';
333
- const digits = exponent.replace(/^[+-]/, '').padStart(plan.expPlaceholders.length || 1, '0');
334
- return `${plan.prefix}${mantissa}E${sign}${digits}${plan.suffix}`;
609
+ // The integer placeholders set the STEP of the exponent, which is what
610
+ // makes `##0.0E+0` engineering notation: three of them means the
611
+ // exponent moves in threes and the mantissa carries up to three integer
612
+ // digits, so 12345 reads 12.3E+3 rather than 1.2E+4. The everyday
613
+ // `0.00E+00` has one, and is the ordinary scientific form.
614
+ const step = Math.max(1, plan.intPlaceholders.length);
615
+ const decimals = plan.fracPlaceholders.length;
616
+ let exponent = n === 0 ? 0 : Math.floor(Math.log10(Math.abs(n)));
617
+ exponent = Math.floor(exponent / step) * step;
618
+ let mantissa = n / Math.pow(10, exponent);
619
+ // Rounding can carry the mantissa over its own width: 999.95 with one
620
+ // decimal is 1000.0, which is 1.0 a step further out.
621
+ if (Math.abs(Number(mantissa.toFixed(decimals))) >= Math.pow(10, step)) {
622
+ mantissa /= Math.pow(10, step);
623
+ exponent += step;
624
+ }
625
+ const sign = exponent < 0 ? '-' : '+';
626
+ const digits = String(Math.abs(exponent)).padStart(plan.expPlaceholders.length || 1, '0');
627
+ return `${plan.prefix}${mantissa.toFixed(decimals)}E${sign}${digits}${plan.suffix}`;
335
628
  }
336
629
  const decimals = plan.fracPlaceholders.length;
337
630
  const fixed = Math.abs(n).toFixed(decimals);
@@ -340,12 +633,17 @@ function renderNumeric(plan, value) {
340
633
  // leading zero entirely when the pattern is all `#`.
341
634
  const minInt = plan.intPlaceholders.split('').filter((c) => c === '0').length;
342
635
  let intText = rawInt;
343
- if (intText.length < minInt)
344
- intText = intText.padStart(minInt, '0');
345
- if (minInt === 0 && intText === '0' && plan.intPlaceholders.length > 0)
346
- intText = '';
347
- if (plan.useGrouping && intText !== '')
348
- intText = group(intText);
636
+ if (plan.intMask) {
637
+ intText = renderMask(plan.intMask, rawInt === '0' && minInt === 0 ? '' : rawInt);
638
+ }
639
+ else {
640
+ if (intText.length < minInt)
641
+ intText = intText.padStart(minInt, '0');
642
+ if (minInt === 0 && intText === '0' && plan.intPlaceholders.length > 0)
643
+ intText = '';
644
+ if (plan.useGrouping && intText !== '')
645
+ intText = group(intText, marks.group);
646
+ }
349
647
  // Trim optional decimals from the right: `0.0#` on 1.5 gives "1.5", not
350
648
  // "1.50". A `0` placeholder is never trimmed, a `#` always may be, and a
351
649
  // `?` becomes a space so columns line up.
@@ -358,7 +656,7 @@ function renderNumeric(plan, value) {
358
656
  break;
359
657
  fracText = kind === '?' ? `${fracText.slice(0, i)} ` : fracText.slice(0, i);
360
658
  }
361
- const body = fracText === '' ? intText : `${intText}.${fracText}`;
659
+ const body = fracText === '' ? intText : `${intText}${marks.decimal}${fracText}`;
362
660
  // `#` on zero prints nothing, which is how "hide zeros" patterns work. The
363
661
  // fallback below is only for a pattern with no digit placeholders at all.
364
662
  return plan.prefix + body + plan.suffix;
@@ -368,6 +666,32 @@ function renderNumeric(plan, value) {
368
666
  function hasPlaceholders(plan) {
369
667
  return plan.intPlaceholders !== '' || plan.fracPlaceholders !== '';
370
668
  }
669
+ /** The text section: `@` is the value, quotes and backslashes wrap
670
+ * literals, `_x` and `*x` are one space, as on the numeric side. */
671
+ function renderText(body, value) {
672
+ let out = '';
673
+ for (let i = 0; i < body.length; i += 1) {
674
+ const ch = body[i];
675
+ if (ch === '\\') {
676
+ out += body[i + 1] ?? '';
677
+ i += 1;
678
+ continue;
679
+ }
680
+ if (ch === '_' || ch === '*') {
681
+ out += ' ';
682
+ i += 1;
683
+ continue;
684
+ }
685
+ if (ch === '"') {
686
+ const end = body.indexOf('"', i + 1);
687
+ out += body.slice(i + 1, end < 0 ? body.length : end);
688
+ i = end < 0 ? body.length : end;
689
+ continue;
690
+ }
691
+ out += ch === '@' ? value : ch;
692
+ }
693
+ return out;
694
+ }
371
695
  /** Excel's General: up to 11 significant digits, no trailing zeros. */
372
696
  function general(value) {
373
697
  if (typeof value === 'number') {
@@ -386,8 +710,12 @@ function general(value) {
386
710
  * ten thousand cells sharing one format parses it once.
387
711
  */
388
712
  const cache = new Map();
389
- export function compileNumberFormat(pattern) {
390
- const hit = cache.get(pattern);
713
+ export const INVARIANT_MARKS = { decimal: '.', group: ',' };
714
+ export function compileNumberFormat(pattern, marks = INVARIANT_MARKS) {
715
+ // The marks join the cache key, so a sheet showing one locale does not
716
+ // serve its rendered numbers to a sheet showing another.
717
+ const key = marks === INVARIANT_MARKS ? pattern : `${pattern}\u0000${marks.decimal}${marks.group}`;
718
+ const hit = cache.get(key);
391
719
  if (hit)
392
720
  return hit;
393
721
  const sections = splitSections(pattern).map(parseSection);
@@ -395,15 +723,22 @@ export function compileNumberFormat(pattern) {
395
723
  const negative = sections[1];
396
724
  const zero = sections[2];
397
725
  const text = sections[3];
726
+ const conditional = sections.some((sec) => sec.condition);
398
727
  // Plans are built once here, not per value.
399
728
  const plans = new Map();
729
+ const fractions = new Map();
400
730
  const dateFlags = new Map();
401
731
  for (const s of sections) {
402
732
  if (!s)
403
733
  continue;
404
734
  const isDate = looksLikeDate(s.body);
405
735
  dateFlags.set(s, isDate);
406
- if (!isDate)
736
+ if (isDate)
737
+ continue;
738
+ const fraction = planFraction(s.body);
739
+ if (fraction)
740
+ fractions.set(s, fraction);
741
+ else
407
742
  plans.set(s, planNumeric(s.body));
408
743
  }
409
744
  const compiled = {
@@ -413,15 +748,15 @@ export function compileNumberFormat(pattern) {
413
748
  return { text: general(value) };
414
749
  }
415
750
  if (typeof value === 'string' && value !== '') {
416
- const section = text ?? positive;
751
+ // Excel's FOURTH section is the one that governs text. With one, `@`
752
+ // puts the text where it stands and a section without an `@` shows
753
+ // only its own literals, which is what makes `;;;` hide a cell and
754
+ // `;;;"n/a"` show n/a whatever was typed. With no fourth section the
755
+ // text is shown as it is, even under a numeric pattern like `0.00`.
756
+ const section = text ?? (positive?.body.includes('@') ? positive : undefined);
417
757
  if (!section)
418
758
  return { text: value };
419
- // The text section substitutes @ for the value; without an @ the
420
- // section's literal text is all you get.
421
- const body = section.body.includes('@')
422
- ? section.body.replace(/@/g, value).replace(/"/g, '')
423
- : value;
424
- return { text: body, color: section.color };
759
+ return { text: renderText(section.body, section.body.includes('@') ? value : ''), color: section.color };
425
760
  }
426
761
  if (value === null || value === undefined || value === '')
427
762
  return { text: '' };
@@ -436,55 +771,113 @@ export function compileNumberFormat(pattern) {
436
771
  return { text: String(value) };
437
772
  }
438
773
  // Pick the section. Two sections means the second covers zero too.
439
- let section = positive;
440
- if (n < 0 && negative)
441
- section = negative;
442
- else if (n === 0 && zero)
443
- section = zero;
774
+ // With conditions, the first section whose condition the value passes
775
+ // wins and an unconditioned one is the fallback, as in Excel's
776
+ // `[<=9999999]###-####;(###) ###-####`.
777
+ let section;
778
+ if (conditional) {
779
+ section = sections.find((sec) => sec && (sec.condition ? passes(sec.condition, n) : true));
780
+ }
781
+ else {
782
+ section = positive;
783
+ if (n < 0 && negative)
784
+ section = negative;
785
+ else if (n === 0 && zero)
786
+ section = zero;
787
+ }
444
788
  if (!section)
445
789
  return { text: general(value) };
446
790
  if (dateFlags.get(section)) {
447
791
  const d = toDate(value);
792
+ // `n` is the serial the value stands for, which is what an elapsed
793
+ // token totals: 1.5 is a day and a half, [h]:mm reads 36:00.
448
794
  return d
449
- ? { text: renderDate(section.body, d), color: section.color }
795
+ ? { text: renderDate(section.body, d, n), color: section.color }
450
796
  : { text: general(value) };
451
797
  }
798
+ const asFraction = fractions.get(section);
799
+ if (asFraction) {
800
+ // A negative with its own section is rendered from its magnitude,
801
+ // as everywhere else: the section supplies the sign.
802
+ const usingNegative = n < 0 && section === negative && !conditional;
803
+ return { text: renderFraction(asFraction, usingNegative ? Math.abs(n) : n), color: section.color };
804
+ }
452
805
  const plan = plans.get(section);
453
806
  if (!hasPlaceholders(plan)) {
807
+ // `@` is the text placeholder, and a NUMBER in a cell formatted as
808
+ // Text is still shown by Excel as the number rather than as the
809
+ // placeholder: `@` over 5 reads 5, not @.
810
+ if (section.body.includes('@')) {
811
+ return { text: renderText(section.body, general(value)), color: section.color };
812
+ }
454
813
  return { text: plan.prefix + plan.suffix, color: section.color };
455
814
  }
456
815
  // A negative shown by its own section renders its ABSOLUTE value: the
457
816
  // section supplies the sign, usually as parentheses or a literal minus.
458
- const usingNegativeSection = n < 0 && section === negative;
817
+ const usingNegativeSection = n < 0 && section === negative && !conditional;
459
818
  const magnitude = usingNegativeSection ? Math.abs(n) : n;
460
- let out = renderNumeric(plan, magnitude);
819
+ let out = renderNumeric(plan, magnitude, marks);
461
820
  // With no negative section, put the sign back on.
462
821
  if (n < 0 && !usingNegativeSection && !out.startsWith('-'))
463
822
  out = `-${out}`;
464
823
  return { text: out, color: section.color };
465
824
  },
466
825
  };
467
- cache.set(pattern, compiled);
826
+ cache.set(key, compiled);
468
827
  return compiled;
469
828
  }
470
829
  /** Format one value against a pattern. Convenience over compile-then-call. */
471
830
  export function formatWithPattern(value, pattern) {
472
831
  return compileNumberFormat(pattern).format(value).text;
473
832
  }
474
- /** The presets behind Ctrl+Shift+1 through 6. */
833
+ /** The presets behind Ctrl+Shift+1 through 6 and the ribbon's $ % , buttons.
834
+ * Percent is Excel's Percent Style, no decimals: 0.425 reads 43%. */
475
835
  export const FORMAT_PRESETS = {
476
836
  general: 'General',
477
837
  number: '#,##0.00',
478
838
  time: 'h:mm AM/PM',
479
839
  date: 'yyyy-mm-dd',
480
840
  currency: '$#,##0.00;($#,##0.00)',
481
- percent: '0.00%',
841
+ accounting: '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)',
842
+ percent: '0%',
482
843
  scientific: '0.00E+00',
483
844
  };
845
+ /** What the ribbon's Number Format combo applies for a category. Excel's
846
+ * "Percentage" there carries two decimals where its % button carries none;
847
+ * the other entries are the presets. */
848
+ export const FORMAT_CATEGORY_PATTERNS = {
849
+ ...FORMAT_PRESETS,
850
+ percent: '0.00%',
851
+ };
852
+ /** Excel's Special category: fixed-shape numbers laid into a mask. */
853
+ export const SPECIAL_FORMATS = {
854
+ zip: { label: 'Zip Code', pattern: '00000' },
855
+ zip4: { label: 'Zip Code + 4', pattern: '00000-0000' },
856
+ phone: { label: 'Phone Number', pattern: '[<=9999999]###-####;(###) ###-####' },
857
+ ssn: { label: 'Social Security Number', pattern: '000-00-0000' },
858
+ };
859
+ /** The pattern Excel's Accounting category spells for a symbol and a
860
+ * number of decimals; an empty symbol is its "None". */
861
+ export function accountingPattern(symbol, decimals) {
862
+ const digits = `#,##0${decimals > 0 ? '.' + '0'.repeat(decimals) : ''}`;
863
+ const sym = symbol === '$' ? '$* ' : symbol ? `${JSON.stringify(symbol)}* ` : '* ';
864
+ return `_(${sym}${digits}_);_(${sym}(${digits});_(${sym}"-"${'?'.repeat(decimals)}_);_(@_)`;
865
+ }
866
+ /** The symbol and decimals an accounting pattern was built from, or null
867
+ * when the pattern is not one `accountingPattern` would spell. */
868
+ export function accountingParts(fmt) {
869
+ if (!fmt)
870
+ return null;
871
+ const m = /^_\((?:"([^"]*)"|\$)?\* #,##0(\.0+)?_\);/.exec(fmt);
872
+ if (!m)
873
+ return null;
874
+ const symbol = m[1] !== undefined ? m[1] : fmt.startsWith('_($') ? '$' : '';
875
+ return { symbol, decimals: m[2] ? m[2].length - 1 : 0 };
876
+ }
484
877
  /**
485
878
  * Which of Excel's categories a pattern belongs to, with the decimals and
486
879
  * separator it carries, so the ribbon's combo can say "Percentage" for a
487
- * cell holding `0%` (a typed 12%) as well as for the preset's `0.00%`, and
880
+ * cell holding `0%` (a typed 12%) as well as for the combo's `0.00%`, and
488
881
  * Format Cells can open on the right category. A pattern that is none of
489
882
  * them is 'custom'.
490
883
  */
@@ -492,6 +885,10 @@ export function formatCategory(fmt) {
492
885
  if (!fmt || fmt === 'General')
493
886
  return { category: 'general', decimals: 2, thousands: true };
494
887
  const decimals = fmt.match(/\.(0+)/)?.[1]?.length ?? 0;
888
+ if (accountingParts(fmt))
889
+ return { category: 'accounting', decimals, thousands: true };
890
+ if (Object.values(SPECIAL_FORMATS).some((sp) => sp.pattern === fmt))
891
+ return { category: 'special', decimals: 0, thousands: false };
495
892
  if (/E\+/i.test(fmt))
496
893
  return { category: 'scientific', decimals, thousands: false };
497
894
  if (/%$/.test(fmt))