@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.
- package/LICENSE +94 -24
- package/README.md +36 -4
- package/dist/SvAdvancedFilter.svelte +1 -1
- package/dist/SvAlertsManager.svelte +2 -1
- package/dist/SvBoard.svelte +2 -1
- package/dist/SvExportMenu.svelte +4 -1
- package/dist/SvExportMenu.svelte.d.ts +0 -13
- package/dist/SvExpressionEditor.svelte +3 -3
- package/dist/SvFormulaBar.svelte +94 -20
- package/dist/SvFormulaBar.svelte.d.ts +7 -0
- package/dist/SvGridAlerts.svelte +49 -9
- package/dist/SvGridBoard.svelte +13 -8
- package/dist/SvGridBulkEditDrawer.svelte +26 -2
- package/dist/SvGridEditPanel.svelte +3 -0
- package/dist/SvGridScheduler.svelte +69 -40
- package/dist/SvGridSelectionBar.svelte +10 -3
- package/dist/SvPivotDesigner.svelte +198 -40
- package/dist/SvPivotDesigner.svelte.d.ts +27 -20
- package/dist/SvSheet.svelte +4043 -243
- package/dist/SvSheet.svelte.d.ts +76 -3
- package/dist/SvSheetChartSetup.svelte +151 -0
- package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
- package/dist/SvSheetComment.svelte +216 -31
- package/dist/SvSheetComment.svelte.d.ts +11 -11
- package/dist/SvSheetConditionalFormat.svelte +28 -31
- package/dist/SvSheetDataValidation.svelte +66 -42
- package/dist/SvSheetEditRanges.svelte +217 -0
- package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
- package/dist/SvSheetErrors.svelte +125 -0
- package/dist/SvSheetErrors.svelte.d.ts +20 -0
- package/dist/SvSheetEvaluate.svelte +133 -0
- package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
- package/dist/SvSheetFilterMenu.svelte +137 -34
- package/dist/SvSheetFilterMenu.svelte.d.ts +5 -0
- package/dist/SvSheetFindReplace.svelte +19 -19
- package/dist/SvSheetFormatCells.svelte +125 -81
- package/dist/SvSheetGoalSeek.svelte +17 -16
- package/dist/SvSheetInsertFunction.svelte +15 -13
- package/dist/SvSheetIteration.svelte +81 -0
- package/dist/SvSheetIteration.svelte.d.ts +10 -0
- package/dist/SvSheetLink.svelte +99 -0
- package/dist/SvSheetLink.svelte.d.ts +17 -0
- package/dist/SvSheetListPicker.svelte +4 -2
- package/dist/SvSheetListPicker.svelte.d.ts +0 -6
- package/dist/SvSheetManageRules.svelte +20 -18
- package/dist/SvSheetNameManager.svelte +17 -15
- package/dist/SvSheetPageSetup.svelte +159 -0
- package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
- package/dist/SvSheetPasteSpecial.svelte +22 -20
- package/dist/SvSheetPivot.svelte +220 -0
- package/dist/SvSheetPivot.svelte.d.ts +21 -0
- package/dist/SvSheetProtectSheet.svelte +81 -0
- package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
- package/dist/SvSheetRemoveDuplicates.svelte +13 -10
- package/dist/SvSheetRibbon.svelte +104 -51
- package/dist/SvSheetSizeDialog.svelte +4 -2
- package/dist/SvSheetSort.svelte +218 -0
- package/dist/SvSheetSort.svelte.d.ts +28 -0
- package/dist/SvSheetSparklines.svelte +135 -0
- package/dist/SvSheetSparklines.svelte.d.ts +14 -0
- package/dist/SvSheetTable.svelte +160 -0
- package/dist/SvSheetTable.svelte.d.ts +24 -0
- package/dist/SvSheetTabs.svelte +161 -31
- package/dist/SvSheetTabs.svelte.d.ts +22 -8
- package/dist/SvSheetTextToColumns.svelte +20 -20
- package/dist/SvSheetValidationAlert.svelte +8 -6
- package/dist/advanced-filter-enable.js +1 -1
- package/dist/alerts/alert-engine.d.ts +6 -0
- package/dist/alerts/alert-engine.js +21 -4
- package/dist/alerts/alert-formats.js +2 -0
- package/dist/alerts/alert-types.d.ts +1 -1
- package/dist/alerts.d.ts +1 -1
- package/dist/alerts.js +2 -2
- package/dist/board.js +2 -2
- package/dist/bulk-edit.d.ts +3 -1
- package/dist/bulk-edit.js +1 -1
- package/dist/cdn/svgrid-enterprise.svelte-external.js +39827 -16429
- package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
- package/dist/designer/assets/{SvChartMenu-D9hEeHar.js → SvChartMenu-BDoeqGz6.js} +1 -1
- package/dist/designer/assets/{SvDateTimePicker-DARvKUL2.js → SvDateTimePicker-DCPg7WGN.js} +1 -1
- package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
- package/dist/designer/assets/{SvGridChart-B8j1SSy3.js → SvGridChart-Cy1ATrWh.js} +2 -2
- package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
- package/dist/designer/assets/{SvGridChartPanel-pT6ITc2P.js → SvGridChartPanel-CATdVc9X.js} +3 -3
- package/dist/designer/assets/{SvGridChartView-bg281UA1.js → SvGridChartView-CmsZtCED.js} +1 -1
- package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
- package/dist/designer/assets/{SvTabs-UFCMTHXS.js → SvTabs-2mHC6oaz.js} +1 -1
- package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
- package/dist/designer/assets/{chart-cIPUsbM5.js → chart-Cn7MnNqA.js} +1 -1
- package/dist/designer/assets/{chart-panel-messages-DtTxpBUU.js → chart-panel-messages-Dz7PTIqT.js} +1 -1
- package/dist/designer/assets/{chart-summary-Czu_5epb.js → chart-summary-BD1qYB2R.js} +1 -1
- package/dist/designer/assets/{chart-trend-D5I5VEe8.js → chart-trend-B8xr5LuD.js} +1 -1
- package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
- package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
- package/dist/designer/assets/{focus-trap-CAreAhlU.js → focus-trap-V2-6Fy9I.js} +1 -1
- package/dist/designer/assets/{index-DwU7CAMm.js → index-D5bVbuzI.js} +317 -315
- package/dist/designer/assets/index-_JVFxt02.css +1 -0
- package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
- package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
- package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
- package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
- package/dist/designer/index.html +17 -16
- package/dist/export-pdf.d.ts +75 -0
- package/dist/export-pdf.js +216 -15
- package/dist/export.js +10 -2
- package/dist/gantt/SvGridGantt.svelte +2890 -0
- package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
- package/dist/gantt/gantt-config.d.ts +73 -0
- package/dist/gantt/gantt-config.js +1 -0
- package/dist/gantt/gantt-critical-path.d.ts +67 -0
- package/dist/gantt/gantt-critical-path.js +207 -0
- package/dist/gantt/gantt-model.d.ts +257 -0
- package/dist/gantt/gantt-model.js +651 -0
- package/dist/gantt/gantt-resources.d.ts +59 -0
- package/dist/gantt/gantt-resources.js +71 -0
- package/dist/gantt/gantt.d.ts +7 -0
- package/dist/gantt/gantt.js +36 -0
- package/dist/gantt/index.d.ts +15 -0
- package/dist/gantt/index.js +22 -0
- package/dist/gantt/timeline-arrows.d.ts +102 -0
- package/dist/gantt/timeline-arrows.js +181 -0
- package/dist/import.d.ts +2 -2
- package/dist/import.js +1 -1
- package/dist/index.d.ts +34 -7
- package/dist/index.js +44 -7
- package/dist/install.js +8 -1
- package/dist/license-core.d.ts +17 -1
- package/dist/license-core.js +16 -5
- package/dist/license.d.ts +18 -2
- package/dist/license.js +71 -6
- package/dist/node/studio.js +140 -8
- package/dist/pivot-enable.js +2 -2
- package/dist/scheduler-dependencies.d.ts +35 -0
- package/dist/scheduler-dependencies.js +36 -1
- package/dist/scheduler.js +2 -2
- package/dist/selection-bar.js +2 -2
- package/dist/server/SvGroupCell.svelte +213 -0
- package/dist/server/SvGroupCell.svelte.d.ts +62 -0
- package/dist/server/SvRowGroupPanel.svelte +322 -0
- package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
- package/dist/server/enable.d.ts +17 -0
- package/dist/server/enable.js +49 -0
- package/dist/server/index.d.ts +22 -0
- package/dist/server/index.js +24 -0
- package/dist/server/messages.d.ts +45 -0
- package/dist/server/messages.js +35 -0
- package/dist/server/server-group-model.d.ts +104 -0
- package/dist/server/server-group-model.js +272 -0
- package/dist/server/server-pivot.d.ts +46 -0
- package/dist/server/server-pivot.js +57 -0
- package/dist/server/server-row-model.d.ts +547 -0
- package/dist/server/server-row-model.js +1551 -0
- package/dist/server/server-selection.d.ts +99 -0
- package/dist/server/server-selection.js +215 -0
- package/dist/server/svgrid-adapter.d.ts +155 -0
- package/dist/server/svgrid-adapter.js +202 -0
- package/dist/sheet/ast.d.ts +45 -1
- package/dist/sheet/ast.js +14 -0
- package/dist/sheet/auto-filter.d.ts +58 -4
- package/dist/sheet/auto-filter.js +159 -11
- package/dist/sheet/autocomplete.d.ts +9 -0
- package/dist/sheet/autocomplete.js +90 -4
- package/dist/sheet/cell-images.d.ts +39 -0
- package/dist/sheet/cell-images.js +90 -0
- package/dist/sheet/cell-types.d.ts +58 -0
- package/dist/sheet/cell-types.js +96 -0
- package/dist/sheet/coerce.d.ts +40 -0
- package/dist/sheet/coerce.js +170 -3
- package/dist/sheet/comments.d.ts +43 -9
- package/dist/sheet/comments.js +85 -27
- package/dist/sheet/conditional-formats.d.ts +29 -6
- package/dist/sheet/conditional-formats.js +37 -12
- package/dist/sheet/csv.d.ts +32 -0
- package/dist/sheet/csv.js +158 -0
- package/dist/sheet/culture.d.ts +87 -0
- package/dist/sheet/culture.js +247 -0
- package/dist/sheet/delta.d.ts +102 -0
- package/dist/sheet/delta.js +203 -0
- package/dist/sheet/deps.d.ts +45 -1
- package/dist/sheet/deps.js +77 -2
- package/dist/sheet/document.d.ts +106 -4
- package/dist/sheet/document.js +204 -3
- package/dist/sheet/engine.d.ts +67 -0
- package/dist/sheet/engine.js +19 -0
- package/dist/sheet/entry.d.ts +8 -4
- package/dist/sheet/entry.js +171 -21
- package/dist/sheet/error-check.d.ts +49 -0
- package/dist/sheet/error-check.js +83 -0
- package/dist/sheet/evaluate-steps.d.ts +48 -0
- package/dist/sheet/evaluate-steps.js +196 -0
- package/dist/sheet/evaluate.d.ts +67 -0
- package/dist/sheet/evaluate.js +725 -13
- package/dist/sheet/filter-files.d.ts +37 -0
- package/dist/sheet/filter-files.js +465 -0
- package/dist/sheet/format-store.d.ts +15 -0
- package/dist/sheet/format-store.js +11 -1
- package/dist/sheet/from-rows.d.ts +14 -0
- package/dist/sheet/from-rows.js +50 -0
- package/dist/sheet/function-catalog.d.ts +1 -1
- package/dist/sheet/function-catalog.js +296 -5
- package/dist/sheet/functions.js +363 -91
- package/dist/sheet/index.d.ts +35 -8
- package/dist/sheet/index.js +35 -8
- package/dist/sheet/links.d.ts +57 -0
- package/dist/sheet/links.js +174 -0
- package/dist/sheet/messages.d.ts +778 -0
- package/dist/sheet/messages.js +806 -0
- package/dist/sheet/number-format.d.ts +52 -5
- package/dist/sheet/number-format.js +465 -68
- package/dist/sheet/objects.d.ts +106 -0
- package/dist/sheet/objects.js +149 -0
- package/dist/sheet/ods-document.d.ts +44 -0
- package/dist/sheet/ods-document.js +1275 -0
- package/dist/sheet/open-file.d.ts +26 -0
- package/dist/sheet/open-file.js +56 -0
- package/dist/sheet/outline.d.ts +82 -0
- package/dist/sheet/outline.js +228 -0
- package/dist/sheet/packs/array.d.ts +16 -0
- package/dist/sheet/packs/array.js +172 -0
- package/dist/sheet/packs/database.d.ts +42 -0
- package/dist/sheet/packs/database.js +142 -0
- package/dist/sheet/packs/engineering.d.ts +2 -0
- package/dist/sheet/packs/engineering.js +296 -0
- package/dist/sheet/packs/financial.d.ts +5 -0
- package/dist/sheet/packs/financial.js +220 -0
- package/dist/sheet/packs/math-stats.d.ts +2 -0
- package/dist/sheet/packs/math-stats.js +372 -0
- package/dist/sheet/packs/special.d.ts +44 -0
- package/dist/sheet/packs/special.js +187 -0
- package/dist/sheet/packs/stats-dist.d.ts +2 -0
- package/dist/sheet/packs/stats-dist.js +785 -0
- package/dist/sheet/packs/text-date.d.ts +2 -0
- package/dist/sheet/packs/text-date.js +332 -0
- package/dist/sheet/packs/trig.d.ts +2 -0
- package/dist/sheet/packs/trig.js +73 -0
- package/dist/sheet/page-setup.d.ts +73 -0
- package/dist/sheet/page-setup.js +73 -0
- package/dist/sheet/parse.js +42 -10
- package/dist/sheet/paste-special.d.ts +22 -0
- package/dist/sheet/paste-special.js +14 -5
- package/dist/sheet/pivot-range.d.ts +160 -0
- package/dist/sheet/pivot-range.js +333 -0
- package/dist/sheet/presence.d.ts +73 -0
- package/dist/sheet/presence.js +79 -0
- package/dist/sheet/print.d.ts +104 -0
- package/dist/sheet/print.js +169 -0
- package/dist/sheet/protection.d.ts +55 -1
- package/dist/sheet/protection.js +55 -2
- package/dist/sheet/rects.d.ts +1 -1
- package/dist/sheet/refs.d.ts +48 -0
- package/dist/sheet/refs.js +155 -8
- package/dist/sheet/ribbon-icons.d.ts +1 -1
- package/dist/sheet/ribbon-icons.js +186 -0
- package/dist/sheet/ribbon.d.ts +1 -1
- package/dist/sheet/ribbon.js +324 -23
- package/dist/sheet/sheet-kinds.d.ts +74 -0
- package/dist/sheet/sheet-kinds.js +91 -0
- package/dist/sheet/shortcuts.d.ts +13 -3
- package/dist/sheet/shortcuts.js +63 -12
- package/dist/sheet/sort.d.ts +51 -0
- package/dist/sheet/sort.js +107 -0
- package/dist/sheet/sparklines.d.ts +111 -0
- package/dist/sheet/sparklines.js +204 -0
- package/dist/sheet/table-styles.d.ts +74 -0
- package/dist/sheet/table-styles.js +152 -0
- package/dist/sheet/tables.d.ts +19 -0
- package/dist/sheet/tables.js +31 -0
- package/dist/sheet/tokenize.d.ts +18 -1
- package/dist/sheet/tokenize.js +60 -9
- package/dist/sheet/validation.d.ts +16 -0
- package/dist/sheet/validation.js +37 -0
- package/dist/sheet/workbook.d.ts +148 -3
- package/dist/sheet/workbook.js +742 -31
- package/dist/sheet/xls-cfb.d.ts +32 -0
- package/dist/sheet/xls-cfb.js +290 -0
- package/dist/sheet/xls-document.d.ts +10 -0
- package/dist/sheet/xls-document.js +1243 -0
- package/dist/sheet/xls-formula.d.ts +40 -0
- package/dist/sheet/xls-formula.js +569 -0
- package/dist/sheet/xlsx-document.d.ts +108 -0
- package/dist/sheet/xlsx-document.js +1849 -0
- package/dist/sheet/xlsx-drawing.d.ts +99 -0
- package/dist/sheet/xlsx-drawing.js +420 -0
- package/dist/sheet-enable.js +6 -7
- package/dist/sheet-text.d.ts +7 -0
- package/dist/sheet-text.js +29 -0
- package/dist/sources/auth-supabase.js +2 -2
- package/dist/sources/realtime-supabase.js +2 -2
- package/dist/sources/rest.d.ts +6 -2
- package/dist/sources/rest.js +42 -7
- package/dist/sources/supabase.d.ts +10 -1
- package/dist/sources/supabase.js +73 -40
- package/dist/studio/emit-project.js +14 -1
- package/dist/studio/index.js +1 -1
- package/dist/studio/project.d.ts +11 -2
- package/dist/studio/project.js +5 -2
- package/dist/studio/ui-components.generated.js +107 -2
- package/dist/sveltekit/in-memory.d.ts +2 -2
- package/dist/sveltekit/in-memory.js +125 -4
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/sveltekit/query-plan.d.ts +50 -4
- package/dist/sveltekit/query-plan.js +25 -3
- package/dist/sveltekit/sql-source.d.ts +6 -0
- package/dist/sveltekit/sql-source.js +34 -6
- package/dist/sveltekit/sql.d.ts +31 -0
- package/dist/sveltekit/sql.js +135 -25
- package/dist/sveltekit/transport.d.ts +10 -2
- package/dist/sveltekit/transport.js +30 -6
- package/dist/upgrade-prompt.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/watermark.js +35 -2
- package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
- package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
- package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
- package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
- package/dist/wc/SvField-B_kurx_P.js +149 -0
- package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
- package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
- package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
- package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
- package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
- package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
- package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
- package/dist/wc/SvModal-DrfOYYyX.js +189 -0
- package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
- package/dist/wc/chart-6OXlybVT.js +4341 -0
- package/dist/wc/chart-export-CeSADGfi.js +135 -0
- package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
- package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
- package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
- package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
- package/dist/wc/client-DQIzjDBG.js +3064 -0
- package/dist/wc/column-resize-DUPgynvc.js +101 -0
- package/dist/wc/date-format-DS-z_h9v.js +1130 -0
- package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
- package/dist/wc/editor-contract-peBDxir_.js +22 -0
- package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
- package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
- package/dist/wc/list-option-DrRyivWp.js +82 -0
- package/dist/wc/popover-WsGlvtQv.js +69 -0
- package/dist/wc/positioning-BiKLfu1E.js +112 -0
- package/dist/wc/react.d.ts +41 -0
- package/dist/wc/react.js +90 -0
- package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
- package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
- package/dist/wc/row-resize-limG_N1z.js +111 -0
- package/dist/wc/sv-sheet-element.d.ts +97 -0
- package/dist/wc/sv-sheet-element.js +44213 -0
- package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
- package/dist/wc/validate-BqDthkNr.js +88 -0
- package/dist/wc/vue.d.ts +31 -0
- package/dist/wc/vue.js +75 -0
- package/package.json +30 -5
- package/dist/designer/assets/GridMenus-BlsvL_aU.js +0 -8
- package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +0 -4
- package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +0 -1
- package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
- package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
- package/dist/designer/assets/index-CMFgAoVV.css +0 -1
- package/dist/designer/assets/row-resize-DLktcqUK.js +0 -1
- package/dist/designer/assets/src-EdI3Xqds.js +0 -58
- package/dist/designer/assets/validate-h-aslRV9.js +0 -1
package/dist/sheet/functions.js
CHANGED
|
@@ -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 {
|
|
18
|
-
import { toNumber, toBool, toText, looseEquals, compare, numericOnly, isBlank,
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
for (let
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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"
|
|
252
|
-
//
|
|
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()) /
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
|
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()`. */
|