@svgrid/enterprise 3.0.2 → 3.1.0
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-BSjv6Qec.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
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Excel's Error Checking: the cells on this sheet worth a second look,
|
|
4
|
+
* walked one at a time. Each one says what the error means rather than
|
|
5
|
+
* leaving the code to be looked up, and Show Calculation Steps hands the
|
|
6
|
+
* cell to Evaluate Formula, which is where the answer usually is.
|
|
7
|
+
*
|
|
8
|
+
* The selection follows the walk, so the cell being talked about is the
|
|
9
|
+
* cell on screen.
|
|
10
|
+
*/
|
|
11
|
+
import { untrack } from 'svelte'
|
|
12
|
+
import { SvModal } from '@svgrid/grid'
|
|
13
|
+
import { useSheetText } from './sheet-text'
|
|
14
|
+
import { colToLetters } from './sheet/address'
|
|
15
|
+
import { checkSheet, describeFinding, type ErrorFinding } from './sheet/error-check'
|
|
16
|
+
import type { Workbook } from './sheet/workbook'
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
open?: boolean
|
|
20
|
+
workbook: Workbook
|
|
21
|
+
sheet: string
|
|
22
|
+
/** Put the selection on a cell being looked at. */
|
|
23
|
+
onGoTo: (cell: { row: number; col: number }) => void
|
|
24
|
+
/** Open Evaluate Formula on the cell being looked at. */
|
|
25
|
+
onSteps: (cell: { row: number; col: number }) => void
|
|
26
|
+
onClose?: () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let { open = $bindable(false), workbook, sheet, onGoTo, onSteps, onClose }: Props = $props()
|
|
30
|
+
const t = useSheetText()
|
|
31
|
+
|
|
32
|
+
let findings = $state<ErrorFinding[]>([])
|
|
33
|
+
let at = $state(0)
|
|
34
|
+
let nextButton = $state<HTMLButtonElement | null>(null)
|
|
35
|
+
|
|
36
|
+
const current = $derived(findings[at] ?? null)
|
|
37
|
+
const address = $derived(current ? `${colToLetters(current.col)}${current.row + 1}` : '')
|
|
38
|
+
|
|
39
|
+
// Opening is the only thing this effect follows. Reading the workbook
|
|
40
|
+
// through it would tie it to every repaint, and moving the selection
|
|
41
|
+
// repaints, so the walk would restart itself forever.
|
|
42
|
+
$effect(() => {
|
|
43
|
+
if (!open) return
|
|
44
|
+
untrack(load)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
function load() {
|
|
48
|
+
const found = checkSheet(sheet, {
|
|
49
|
+
rowCount: () => workbook.rowCount(sheet),
|
|
50
|
+
colCount: () => workbook.colCount(sheet),
|
|
51
|
+
getRaw: (row, col) => workbook.getRaw(sheet, row, col),
|
|
52
|
+
getValue: (row, col) => workbook.getValue(sheet, row, col),
|
|
53
|
+
})
|
|
54
|
+
findings = found
|
|
55
|
+
at = 0
|
|
56
|
+
show(found[0])
|
|
57
|
+
queueMicrotask(() => nextButton?.focus())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function show(finding: ErrorFinding | undefined) {
|
|
61
|
+
if (finding) onGoTo({ row: finding.row, col: finding.col })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function move(by: number) {
|
|
65
|
+
const list = findings
|
|
66
|
+
if (list.length === 0) return
|
|
67
|
+
at = (at + by + list.length) % list.length
|
|
68
|
+
show(list[at])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function close() {
|
|
72
|
+
open = false
|
|
73
|
+
onClose?.()
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<SvModal bind:open onClose={onClose} title={t('errors.title')} size="sm">
|
|
78
|
+
<div class="sv-sheet-dialog">
|
|
79
|
+
{#if current}
|
|
80
|
+
<p class="position">{t('errors.position', { index: at + 1, count: findings.length })}</p>
|
|
81
|
+
<p class="headline">
|
|
82
|
+
<code>{address}</code>
|
|
83
|
+
<strong>{current.kind === 'error' ? current.error : t('errors.inconsistent')}</strong>
|
|
84
|
+
</p>
|
|
85
|
+
<pre class="formula">{current.text}</pre>
|
|
86
|
+
<p class="meaning">{describeFinding(current)}</p>
|
|
87
|
+
{:else}
|
|
88
|
+
<p class="none">{t('errors.none')}</p>
|
|
89
|
+
{/if}
|
|
90
|
+
<p class="hint">{t('errors.hint')}</p>
|
|
91
|
+
</div>
|
|
92
|
+
{#snippet footer()}
|
|
93
|
+
<div class="sv-sheet-dialog-buttons">
|
|
94
|
+
<button type="button" class="btn" onclick={() => move(-1)} disabled={findings.length < 2}>{t('errors.previous')}</button>
|
|
95
|
+
<button type="button" class="btn" bind:this={nextButton} onclick={() => move(1)} disabled={findings.length < 2}>{t('errors.next')}</button>
|
|
96
|
+
<span class="spacer"></span>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
class="btn primary"
|
|
100
|
+
disabled={!current}
|
|
101
|
+
onclick={() => { if (current) { open = false; onSteps({ row: current.row, col: current.col }) } }}
|
|
102
|
+
>{t('errors.steps')}</button>
|
|
103
|
+
<button type="button" class="btn" onclick={close}>{t('close')}</button>
|
|
104
|
+
</div>
|
|
105
|
+
{/snippet}
|
|
106
|
+
</SvModal>
|
|
107
|
+
|
|
108
|
+
<style>
|
|
109
|
+
.spacer { flex: 1; }
|
|
110
|
+
.position { margin: 0 0 6px; color: var(--sg-muted, #64748b); font-size: 12px; }
|
|
111
|
+
.headline { margin: 0 0 8px; display: flex; align-items: baseline; gap: 8px; }
|
|
112
|
+
.formula {
|
|
113
|
+
margin: 0 0 8px;
|
|
114
|
+
padding: 8px 10px;
|
|
115
|
+
border: 1px solid var(--sg-border, #d4d4d8);
|
|
116
|
+
border-radius: 4px;
|
|
117
|
+
background: var(--sg-surface-2, #f8fafc);
|
|
118
|
+
font-family: var(--sg-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
119
|
+
font-size: 13px;
|
|
120
|
+
white-space: pre-wrap;
|
|
121
|
+
overflow-wrap: anywhere;
|
|
122
|
+
}
|
|
123
|
+
.meaning { margin: 0; }
|
|
124
|
+
.none { margin: 0 0 8px; }
|
|
125
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Workbook } from './sheet/workbook.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
workbook: Workbook;
|
|
5
|
+
sheet: string;
|
|
6
|
+
/** Put the selection on a cell being looked at. */
|
|
7
|
+
onGoTo: (cell: {
|
|
8
|
+
row: number;
|
|
9
|
+
col: number;
|
|
10
|
+
}) => void;
|
|
11
|
+
/** Open Evaluate Formula on the cell being looked at. */
|
|
12
|
+
onSteps: (cell: {
|
|
13
|
+
row: number;
|
|
14
|
+
col: number;
|
|
15
|
+
}) => void;
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
};
|
|
18
|
+
declare const SvSheetErrors: import("svelte").Component<Props, {}, "open">;
|
|
19
|
+
type SvSheetErrors = ReturnType<typeof SvSheetErrors>;
|
|
20
|
+
export default SvSheetErrors;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Excel's Evaluate Formula: the active cell's formula with one part
|
|
4
|
+
* underlined, and a button that replaces that part with what it is worth.
|
|
5
|
+
* Keep clicking and the formula collapses into the cell's answer, which is
|
|
6
|
+
* the quickest way to see which half of a long formula is the wrong half.
|
|
7
|
+
*
|
|
8
|
+
* The walk itself is `evaluationSteps`; this shows it and lets the reader
|
|
9
|
+
* move back and forth through it.
|
|
10
|
+
*/
|
|
11
|
+
import { untrack } from 'svelte'
|
|
12
|
+
import { SvModal } from '@svgrid/grid'
|
|
13
|
+
import { useSheetText } from './sheet-text'
|
|
14
|
+
import { colToLetters } from './sheet/address'
|
|
15
|
+
import { parseFormula } from './sheet/parse'
|
|
16
|
+
import { evaluationSteps, printValue, type EvaluationStep } from './sheet/evaluate-steps'
|
|
17
|
+
import type { Workbook } from './sheet/workbook'
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
open?: boolean
|
|
21
|
+
workbook: Workbook
|
|
22
|
+
sheet: string
|
|
23
|
+
/** The cell to walk, which is the active one when the dialog opens. */
|
|
24
|
+
cell: { row: number; col: number }
|
|
25
|
+
onClose?: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let { open = $bindable(false), workbook, sheet, cell, onClose }: Props = $props()
|
|
29
|
+
const t = useSheetText()
|
|
30
|
+
|
|
31
|
+
let steps = $state<EvaluationStep[]>([])
|
|
32
|
+
let at = $state(0)
|
|
33
|
+
let problem = $state('')
|
|
34
|
+
let evaluateButton = $state<HTMLButtonElement | null>(null)
|
|
35
|
+
|
|
36
|
+
const address = $derived(`${sheet}!${colToLetters(cell.col)}${cell.row + 1}`)
|
|
37
|
+
/** The line on screen: the formula as it stands before the next step, or
|
|
38
|
+
* the final value once every part has been worked out. */
|
|
39
|
+
const current = $derived(steps[at] ?? null)
|
|
40
|
+
const finished = $derived(steps.length > 0 && at >= steps.length)
|
|
41
|
+
const answer = $derived(steps.length > 0 ? steps[steps.length - 1]!.value : null)
|
|
42
|
+
|
|
43
|
+
// The cell is followed, because Error Checking hands this dialog a new
|
|
44
|
+
// one while it is open; the workbook is not, or every repaint would
|
|
45
|
+
// restart the walk.
|
|
46
|
+
$effect(() => {
|
|
47
|
+
if (!open) return
|
|
48
|
+
const target = cell
|
|
49
|
+
untrack(() => walk(target))
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
function walk(target: { row: number; col: number }) {
|
|
53
|
+
at = 0
|
|
54
|
+
problem = ''
|
|
55
|
+
steps = []
|
|
56
|
+
const text = workbook.getRaw(sheet, target.row, target.col).trim()
|
|
57
|
+
if (!text.startsWith('=')) {
|
|
58
|
+
problem = t('evaluate.noFormula')
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
steps = evaluationSteps(
|
|
63
|
+
parseFormula(text),
|
|
64
|
+
(formula) => workbook.evaluateText(sheet, formula, undefined, { row: target.row, col: target.col }),
|
|
65
|
+
)
|
|
66
|
+
} catch {
|
|
67
|
+
problem = t('evaluate.noFormula')
|
|
68
|
+
}
|
|
69
|
+
queueMicrotask(() => evaluateButton?.focus())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function close() {
|
|
73
|
+
open = false
|
|
74
|
+
onClose?.()
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<SvModal bind:open onClose={onClose} title={t('evaluate.title')} size="md">
|
|
79
|
+
<div class="sv-sheet-dialog">
|
|
80
|
+
<p class="reference"><span class="label">{t('evaluate.reference')}</span> <code>{address}</code></p>
|
|
81
|
+
{#if problem}
|
|
82
|
+
<p class="error">{problem}</p>
|
|
83
|
+
{:else}
|
|
84
|
+
<p class="label">{t('evaluate.evaluation')}</p>
|
|
85
|
+
<pre class="evaluation" data-testid="sv-sheet-evaluation">{#if current}{current.formula.slice(0, current.from)}<mark>{current.formula.slice(current.from, current.to)}</mark>{current.formula.slice(current.to)}{:else}{printValue(answer ?? '')}{/if}</pre>
|
|
86
|
+
{#if finished}
|
|
87
|
+
<p class="done">{t('evaluate.done')} <strong>{printValue(answer ?? '')}</strong></p>
|
|
88
|
+
{/if}
|
|
89
|
+
<p class="hint">{t('evaluate.hint')}</p>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
{#snippet footer()}
|
|
93
|
+
<div class="sv-sheet-dialog-buttons">
|
|
94
|
+
<button type="button" class="btn" onclick={() => { at = Math.max(0, at - 1) }} disabled={at === 0 || !!problem}>{t('evaluate.stepBack')}</button>
|
|
95
|
+
<button type="button" class="btn" onclick={() => { at = 0 }} disabled={at === 0 || !!problem}>{t('evaluate.restart')}</button>
|
|
96
|
+
<span class="spacer"></span>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
class="btn primary"
|
|
100
|
+
bind:this={evaluateButton}
|
|
101
|
+
onclick={() => { at = Math.min(steps.length, at + 1) }}
|
|
102
|
+
disabled={finished || !!problem}
|
|
103
|
+
>{t('evaluate.evaluate')}</button>
|
|
104
|
+
<button type="button" class="btn" onclick={close}>{t('close')}</button>
|
|
105
|
+
</div>
|
|
106
|
+
{/snippet}
|
|
107
|
+
</SvModal>
|
|
108
|
+
|
|
109
|
+
<style>
|
|
110
|
+
.spacer { flex: 1; }
|
|
111
|
+
.reference { margin: 0 0 8px; }
|
|
112
|
+
.label { color: var(--sg-muted, #64748b); font-size: 12px; }
|
|
113
|
+
.evaluation {
|
|
114
|
+
margin: 0;
|
|
115
|
+
padding: 10px 12px;
|
|
116
|
+
border: 1px solid var(--sg-border, #d4d4d8);
|
|
117
|
+
border-radius: 4px;
|
|
118
|
+
background: var(--sg-surface-2, #f8fafc);
|
|
119
|
+
font-family: var(--sg-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
120
|
+
font-size: 13px;
|
|
121
|
+
line-height: 1.6;
|
|
122
|
+
white-space: pre-wrap;
|
|
123
|
+
overflow-wrap: anywhere;
|
|
124
|
+
}
|
|
125
|
+
.evaluation mark {
|
|
126
|
+
background: var(--sg-accent-soft, #dbeafe);
|
|
127
|
+
color: inherit;
|
|
128
|
+
text-decoration: underline;
|
|
129
|
+
text-underline-offset: 3px;
|
|
130
|
+
}
|
|
131
|
+
.done { margin: 8px 0 0; }
|
|
132
|
+
.error { margin: 4px 0 0; color: var(--sg-danger, #c42b1c); font-size: 12px; }
|
|
133
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Workbook } from './sheet/workbook.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
workbook: Workbook;
|
|
5
|
+
sheet: string;
|
|
6
|
+
/** The cell to walk, which is the active one when the dialog opens. */
|
|
7
|
+
cell: {
|
|
8
|
+
row: number;
|
|
9
|
+
col: number;
|
|
10
|
+
};
|
|
11
|
+
onClose?: () => void;
|
|
12
|
+
};
|
|
13
|
+
declare const SvSheetEvaluate: import("svelte").Component<Props, {}, "open">;
|
|
14
|
+
type SvSheetEvaluate = ReturnType<typeof SvSheetEvaluate>;
|
|
15
|
+
export default SvSheetEvaluate;
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* column's values to tick, OK and Cancel. The shell mounts it inside the
|
|
8
8
|
* popover anchored to the header cell.
|
|
9
9
|
*/
|
|
10
|
+
import { useSheetText } from './sheet-text'
|
|
10
11
|
import type { ExcelFilterOperator } from '@svgrid/grid/filtering'
|
|
11
|
-
import { valuesFilter, type ColumnFilter, type FilterValue, type FilterCondition } from './sheet/auto-filter'
|
|
12
|
+
import { valuesFilter, type ColumnFilter, type FilterValue, type FilterCondition, type DatePeriod } from './sheet/auto-filter'
|
|
12
13
|
|
|
13
14
|
type Props = {
|
|
14
15
|
/** The header text, for Clear Filter From "X". */
|
|
@@ -19,26 +20,43 @@
|
|
|
19
20
|
filter: ColumnFilter | null
|
|
20
21
|
/** Whether the column holds numbers, for Number Filters over Text Filters. */
|
|
21
22
|
numeric: boolean
|
|
23
|
+
/** Whether the column holds dates, for Date Filters. */
|
|
24
|
+
dates?: boolean
|
|
25
|
+
/** The fills the column's cells carry (null for none), for Filter by
|
|
26
|
+
* Color; the entry is left off with fewer than two. */
|
|
27
|
+
fills?: ReadonlyArray<string | null>
|
|
22
28
|
onSort: (direction: 'asc' | 'desc') => void
|
|
23
29
|
onApply: (filter: ColumnFilter | null) => void
|
|
24
30
|
onCancel: () => void
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
let { header, values, filter, numeric, onSort, onApply, onCancel }: Props = $props()
|
|
33
|
+
let { header, values, filter, numeric, dates = false, fills = [], onSort, onApply, onCancel }: Props = $props()
|
|
34
|
+
const t = useSheetText()
|
|
28
35
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
const PERIODS: ReadonlyArray<DatePeriod> = [
|
|
37
|
+
'equals', 'before', 'after', 'between', 'tomorrow', 'today', 'yesterday',
|
|
38
|
+
'nextWeek', 'thisWeek', 'lastWeek', 'nextMonth', 'thisMonth', 'lastMonth',
|
|
39
|
+
'nextQuarter', 'thisQuarter', 'lastQuarter', 'nextYear', 'thisYear', 'lastYear', 'yearToDate',
|
|
32
40
|
]
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
const typedPeriod = (p: DatePeriod) => p === 'equals' || p === 'before' || p === 'after' || p === 'between'
|
|
42
|
+
|
|
43
|
+
const TEXT_OPS: ReadonlyArray<ExcelFilterOperator> = [
|
|
44
|
+
'equals', 'notEquals', 'startsWith', 'endsWith', 'contains', 'notContains', 'isBlank', 'isNotBlank',
|
|
45
|
+
]
|
|
46
|
+
const NUMBER_OPS: ReadonlyArray<ExcelFilterOperator> = [
|
|
47
|
+
'equals', 'notEquals', 'greaterThan', 'lessThan', 'between', 'isBlank', 'isNotBlank',
|
|
36
48
|
]
|
|
37
49
|
const ops = $derived(numeric ? NUMBER_OPS : TEXT_OPS)
|
|
38
50
|
|
|
39
51
|
let search = $state('')
|
|
40
52
|
let ticked = $state<Set<string>>(new Set())
|
|
41
|
-
let mode = $state<'values' | 'condition'>('values')
|
|
53
|
+
let mode = $state<'values' | 'condition' | 'date' | 'top'>('values')
|
|
54
|
+
let period = $state<DatePeriod>('today')
|
|
55
|
+
let d1 = $state('')
|
|
56
|
+
let d2 = $state('')
|
|
57
|
+
let topSide = $state<'top' | 'bottom'>('top')
|
|
58
|
+
let topCount = $state(10)
|
|
59
|
+
let topUnit = $state<'items' | 'percent'>('items')
|
|
42
60
|
let op1 = $state<ExcelFilterOperator>('equals')
|
|
43
61
|
let v1 = $state('')
|
|
44
62
|
let v1to = $state('')
|
|
@@ -52,9 +70,15 @@
|
|
|
52
70
|
// Open on the column's state: the ticks it has, or the condition it has.
|
|
53
71
|
const f = filter
|
|
54
72
|
const all = values.map((v) => v.text)
|
|
55
|
-
if (f?.kind === 'values') ticked = new Set(all.filter((t) => !f.excluded.includes(t)))
|
|
73
|
+
if (f?.kind === 'values') ticked = new Set(f.included ? all.filter((t) => f.included!.includes(t)) : all.filter((t) => !f.excluded.includes(t)))
|
|
56
74
|
else ticked = new Set(all)
|
|
57
|
-
mode = f?.kind === 'condition' ? 'condition' : 'values'
|
|
75
|
+
mode = f?.kind === 'condition' ? 'condition' : f?.kind === 'date' ? 'date' : f?.kind === 'top' ? 'top' : 'values'
|
|
76
|
+
period = f?.kind === 'date' ? f.period : 'today'
|
|
77
|
+
d1 = f?.kind === 'date' ? f.value ?? '' : ''
|
|
78
|
+
d2 = f?.kind === 'date' ? f.valueTo ?? '' : ''
|
|
79
|
+
topSide = f?.kind === 'top' && !f.top ? 'bottom' : 'top'
|
|
80
|
+
topCount = f?.kind === 'top' ? f.count : 10
|
|
81
|
+
topUnit = f?.kind === 'top' && f.percent ? 'percent' : 'items'
|
|
58
82
|
if (f?.kind === 'condition') {
|
|
59
83
|
op1 = f.first.op; v1 = f.first.value ?? ''; v1to = f.first.valueTo ?? ''
|
|
60
84
|
join = f.join ?? 'and'
|
|
@@ -93,7 +117,22 @@
|
|
|
93
117
|
&& (op2 === '' || !needsValue(op2) || v2.trim() !== '') && (op2 !== 'between' || v2to.trim() !== ''),
|
|
94
118
|
)
|
|
95
119
|
|
|
120
|
+
const dateValid = $derived(
|
|
121
|
+
!typedPeriod(period) || (d1.trim() !== '' && (period !== 'between' || d2.trim() !== '')),
|
|
122
|
+
)
|
|
123
|
+
const topValid = $derived(Number.isFinite(topCount) && topCount > 0)
|
|
124
|
+
|
|
96
125
|
function ok() {
|
|
126
|
+
if (mode === 'date') {
|
|
127
|
+
if (!dateValid) return
|
|
128
|
+
onApply({ kind: 'date', period, ...(typedPeriod(period) ? { value: d1.trim() } : {}), ...(period === 'between' ? { valueTo: d2.trim() } : {}) })
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
if (mode === 'top') {
|
|
132
|
+
if (!topValid) return
|
|
133
|
+
onApply({ kind: 'top', top: topSide === 'top', count: Math.trunc(topCount), ...(topUnit === 'percent' ? { percent: true } : {}) })
|
|
134
|
+
return
|
|
135
|
+
}
|
|
97
136
|
if (mode === 'condition') {
|
|
98
137
|
if (!conditionValid) return
|
|
99
138
|
const first: FilterCondition = { op: op1, ...(needsValue(op1) ? { value: v1.trim() } : {}), ...(op1 === 'between' ? { valueTo: v1to.trim() } : {}) }
|
|
@@ -115,52 +154,100 @@
|
|
|
115
154
|
</script>
|
|
116
155
|
|
|
117
156
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
118
|
-
<div class="sv-sheet-filter-menu" role="dialog" aria-label={
|
|
119
|
-
<button type="button" class="row" onclick={() => onSort('asc')}>{numeric ? '
|
|
120
|
-
<button type="button" class="row" onclick={() => onSort('desc')}>{numeric ? '
|
|
157
|
+
<div class="sv-sheet-filter-menu" role="dialog" tabindex="-1" aria-label={t('filter.label', { header })} onkeydown={onKeyDown}>
|
|
158
|
+
<button type="button" class="row" onclick={() => onSort('asc')}>{t(numeric ? 'filter.sortSmallest' : 'filter.sortAsc')}</button>
|
|
159
|
+
<button type="button" class="row" onclick={() => onSort('desc')}>{t(numeric ? 'filter.sortLargest' : 'filter.sortDesc')}</button>
|
|
121
160
|
<hr />
|
|
122
|
-
<button type="button" class="row" disabled={!filter} onclick={() => onApply(null)}>
|
|
161
|
+
<button type="button" class="row" disabled={!filter} onclick={() => onApply(null)}>{t('filter.clear', { header })}</button>
|
|
162
|
+
{#if fills.length > 1}
|
|
163
|
+
<!-- Excel's Filter by Color: the fills the column carries, as swatches. -->
|
|
164
|
+
<details class="conditions colours" open={filter?.kind === 'color'}>
|
|
165
|
+
<summary>{t('filter.byColor')}</summary>
|
|
166
|
+
<div class="swatches" role="group" aria-label={t('filter.byColorGroup')}>
|
|
167
|
+
{#each fills as fill (fill ?? '')}
|
|
168
|
+
<button
|
|
169
|
+
type="button"
|
|
170
|
+
class="swatch"
|
|
171
|
+
class:none={fill === null}
|
|
172
|
+
class:on={filter?.kind === 'color' && (filter.fill ?? '') === (fill ?? '')}
|
|
173
|
+
style:background={fill ?? undefined}
|
|
174
|
+
title={fill ?? t('filter.noFill')}
|
|
175
|
+
aria-label={fill ? t('filter.byFill', { fill }) : t('filter.byNoFill')}
|
|
176
|
+
onclick={() => onApply({ kind: 'color', fill })}
|
|
177
|
+
>{#if fill === null}{t('filter.noFill')}{/if}</button>
|
|
178
|
+
{/each}
|
|
179
|
+
</div>
|
|
180
|
+
</details>
|
|
181
|
+
{/if}
|
|
182
|
+
{#if dates}
|
|
183
|
+
<details class="conditions" open={mode === 'date'} ontoggle={(e) => { if ((e.currentTarget as HTMLDetailsElement).open) mode = 'date' }}>
|
|
184
|
+
<summary>{t('filter.dateFilters')}</summary>
|
|
185
|
+
<div class="condition">
|
|
186
|
+
<select bind:value={period} aria-label={t('filter.datePeriod')} onchange={() => (mode = 'date')}>
|
|
187
|
+
{#each PERIODS as id (id)}<option value={id}>{t(`filter.period.${id}`)}</option>{/each}
|
|
188
|
+
</select>
|
|
189
|
+
{#if typedPeriod(period)}<input type="date" bind:value={d1} aria-label={t('filter.date')} oninput={() => (mode = 'date')} />{/if}
|
|
190
|
+
{#if period === 'between'}<span>{t('filter.and')}</span><input type="date" bind:value={d2} aria-label={t('filter.secondDate')} />{/if}
|
|
191
|
+
</div>
|
|
192
|
+
</details>
|
|
193
|
+
{/if}
|
|
123
194
|
<details class="conditions" open={mode === 'condition'} ontoggle={(e) => { if ((e.currentTarget as HTMLDetailsElement).open) mode = 'condition' }}>
|
|
124
|
-
<summary>{numeric ? '
|
|
195
|
+
<summary>{t(numeric ? 'filter.numberFilters' : 'filter.textFilters')}</summary>
|
|
125
196
|
<div class="condition">
|
|
126
|
-
<select bind:value={op1} aria-label=
|
|
127
|
-
{#each ops as
|
|
197
|
+
<select bind:value={op1} aria-label={t('filter.firstCondition')} onchange={() => (mode = 'condition')}>
|
|
198
|
+
{#each ops as op (op)}<option value={op}>{t(`filter.op.${op}`)}</option>{/each}
|
|
128
199
|
</select>
|
|
129
|
-
{#if needsValue(op1)}<input type="text" bind:value={v1} aria-label=
|
|
130
|
-
{#if op1 === 'between'}<span>and</span><input type="text" bind:value={v1to} aria-label=
|
|
200
|
+
{#if needsValue(op1)}<input type="text" bind:value={v1} aria-label={t('filter.firstValue')} oninput={() => (mode = 'condition')} />{/if}
|
|
201
|
+
{#if op1 === 'between'}<span>{t('filter.and')}</span><input type="text" bind:value={v1to} aria-label={t('filter.firstUpperValue')} />{/if}
|
|
131
202
|
</div>
|
|
132
203
|
<div class="join">
|
|
133
|
-
<label class="check"><input type="radio" name="sv-sheet-filter-join" value="and" bind:group={join} />
|
|
134
|
-
<label class="check"><input type="radio" name="sv-sheet-filter-join" value="or" bind:group={join} />
|
|
204
|
+
<label class="check"><input type="radio" name="sv-sheet-filter-join" value="and" bind:group={join} /> {t('filter.joinAnd')}</label>
|
|
205
|
+
<label class="check"><input type="radio" name="sv-sheet-filter-join" value="or" bind:group={join} /> {t('filter.joinOr')}</label>
|
|
135
206
|
</div>
|
|
136
207
|
<div class="condition">
|
|
137
|
-
<select bind:value={op2} aria-label=
|
|
138
|
-
<option value="">(none)</option>
|
|
139
|
-
{#each ops as
|
|
208
|
+
<select bind:value={op2} aria-label={t('filter.secondCondition')}>
|
|
209
|
+
<option value="">{t('filter.none')}</option>
|
|
210
|
+
{#each ops as op (op)}<option value={op}>{t(`filter.op.${op}`)}</option>{/each}
|
|
140
211
|
</select>
|
|
141
|
-
{#if needsValue(op2)}<input type="text" bind:value={v2} aria-label=
|
|
142
|
-
{#if op2 === 'between'}<span>and</span><input type="text" bind:value={v2to} aria-label=
|
|
212
|
+
{#if needsValue(op2)}<input type="text" bind:value={v2} aria-label={t('filter.secondValue')} />{/if}
|
|
213
|
+
{#if op2 === 'between'}<span>{t('filter.and')}</span><input type="text" bind:value={v2to} aria-label={t('filter.secondUpperValue')} />{/if}
|
|
143
214
|
</div>
|
|
144
215
|
</details>
|
|
216
|
+
{#if numeric}
|
|
217
|
+
<details class="conditions" open={mode === 'top'} ontoggle={(e) => { if ((e.currentTarget as HTMLDetailsElement).open) mode = 'top' }}>
|
|
218
|
+
<summary>{t('filter.top10')}</summary>
|
|
219
|
+
<div class="condition top">
|
|
220
|
+
<select bind:value={topSide} aria-label={t('filter.topOrBottom')} onchange={() => (mode = 'top')}>
|
|
221
|
+
<option value="top">{t('filter.top')}</option>
|
|
222
|
+
<option value="bottom">{t('filter.bottom')}</option>
|
|
223
|
+
</select>
|
|
224
|
+
<input type="number" min="1" bind:value={topCount} aria-label={t('filter.howMany')} oninput={() => (mode = 'top')} />
|
|
225
|
+
<select bind:value={topUnit} aria-label={t('filter.itemsOrPercent')}>
|
|
226
|
+
<option value="items">{t('filter.items')}</option>
|
|
227
|
+
<option value="percent">{t('filter.percent')}</option>
|
|
228
|
+
</select>
|
|
229
|
+
</div>
|
|
230
|
+
</details>
|
|
231
|
+
{/if}
|
|
145
232
|
<hr />
|
|
146
|
-
<input bind:this={searchBox} type="search" class="search" placeholder=
|
|
147
|
-
<div class="values" role="group" aria-label=
|
|
233
|
+
<input bind:this={searchBox} type="search" class="search" placeholder={t('filter.search')} aria-label={t('filter.searchValues')} bind:value={search} />
|
|
234
|
+
<div class="values" role="group" aria-label={t('filter.values')}>
|
|
148
235
|
<label class="check all">
|
|
149
236
|
<input type="checkbox" checked={allShownTicked} indeterminate={!allShownTicked && !noneShownTicked} onchange={toggleAll} />
|
|
150
|
-
(
|
|
237
|
+
{t(search.trim() ? 'filter.selectAllResults' : 'filter.selectAll')}
|
|
151
238
|
</label>
|
|
152
239
|
{#each shown as v (v.text)}
|
|
153
240
|
<label class="check">
|
|
154
241
|
<input type="checkbox" checked={ticked.has(v.text)} onchange={() => toggle(v.text)} />
|
|
155
|
-
<span class="text">{v.text === '' ? '
|
|
242
|
+
<span class="text">{v.text === '' ? t('filter.blanks') : v.text}</span>
|
|
156
243
|
<span class="count">{v.count}</span>
|
|
157
244
|
</label>
|
|
158
245
|
{/each}
|
|
159
|
-
{#if shown.length === 0}<div class="none">
|
|
246
|
+
{#if shown.length === 0}<div class="none">{t('filter.noMatches')}</div>{/if}
|
|
160
247
|
</div>
|
|
161
248
|
<div class="sv-sheet-dialog-buttons">
|
|
162
|
-
<button type="button" class="btn primary" onclick={ok} disabled={mode === 'condition' ? !conditionValid : noneShownTicked && !search.trim()}>
|
|
163
|
-
<button type="button" class="btn" onclick={onCancel}>
|
|
249
|
+
<button type="button" class="btn primary" onclick={ok} disabled={mode === 'condition' ? !conditionValid : mode === 'date' ? !dateValid : mode === 'top' ? !topValid : noneShownTicked && !search.trim()}>{t('ok')}</button>
|
|
250
|
+
<button type="button" class="btn" onclick={onCancel}>{t('cancel')}</button>
|
|
164
251
|
</div>
|
|
165
252
|
</div>
|
|
166
253
|
|
|
@@ -192,6 +279,22 @@
|
|
|
192
279
|
.condition, .join { display: flex; align-items: center; gap: 6px; padding: 3px 8px; }
|
|
193
280
|
.condition select { flex: 0 0 120px; }
|
|
194
281
|
.condition input { flex: 1 1 60px; min-width: 40px; }
|
|
282
|
+
.condition.top select { flex: 0 0 80px; }
|
|
283
|
+
.condition.top input { flex: 0 0 56px; }
|
|
284
|
+
.swatches { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 8px; }
|
|
285
|
+
.swatch {
|
|
286
|
+
width: 22px;
|
|
287
|
+
height: 22px;
|
|
288
|
+
padding: 0;
|
|
289
|
+
font: inherit;
|
|
290
|
+
font-size: 10px;
|
|
291
|
+
color: inherit;
|
|
292
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
293
|
+
border-radius: 3px;
|
|
294
|
+
cursor: pointer;
|
|
295
|
+
}
|
|
296
|
+
.swatch.none { width: auto; padding: 0 6px; background: var(--sg-bg, #fff); }
|
|
297
|
+
.swatch.on { outline: 2px solid var(--sg-accent, #217346); outline-offset: 1px; }
|
|
195
298
|
select, input[type="text"], .search {
|
|
196
299
|
height: 24px;
|
|
197
300
|
padding: 0 6px;
|
|
@@ -8,6 +8,11 @@ type Props = {
|
|
|
8
8
|
filter: ColumnFilter | null;
|
|
9
9
|
/** Whether the column holds numbers, for Number Filters over Text Filters. */
|
|
10
10
|
numeric: boolean;
|
|
11
|
+
/** Whether the column holds dates, for Date Filters. */
|
|
12
|
+
dates?: boolean;
|
|
13
|
+
/** The fills the column's cells carry (null for none), for Filter by
|
|
14
|
+
* Color; the entry is left off with fewer than two. */
|
|
15
|
+
fills?: ReadonlyArray<string | null>;
|
|
11
16
|
onSort: (direction: 'asc' | 'desc') => void;
|
|
12
17
|
onApply: (filter: ColumnFilter | null) => void;
|
|
13
18
|
onCancel: () => void;
|