@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
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* `sheet/ribbon.ts`, which calls the same function the matching keystroke
|
|
37
37
|
* does, so a button and its shortcut cannot drift apart.
|
|
38
38
|
*/
|
|
39
|
+
import { tick } from 'svelte'
|
|
39
40
|
import { SvPopover } from '@svgrid/grid'
|
|
40
41
|
import type { GridCommandContext } from '@svgrid/grid/shortcuts'
|
|
41
42
|
import SvRibbonIcon from './SvRibbonIcon.svelte'
|
|
@@ -44,6 +45,8 @@
|
|
|
44
45
|
type RibbonTab, type RibbonGroup, type RibbonItem, type RibbonOption, type RibbonActionId,
|
|
45
46
|
} from './sheet/ribbon'
|
|
46
47
|
import { THEME_COLOURS, STANDARD_COLOURS } from './sheet/palette'
|
|
48
|
+
import { useSheetText } from './sheet-text'
|
|
49
|
+
import { defaultSheetMessages } from './sheet/messages'
|
|
47
50
|
|
|
48
51
|
type Props = {
|
|
49
52
|
/**
|
|
@@ -81,7 +84,8 @@
|
|
|
81
84
|
onAction,
|
|
82
85
|
activeActions = [],
|
|
83
86
|
tabs = RIBBON_TABS,
|
|
84
|
-
|
|
87
|
+
// Excel opens on Home, not on File, whatever tab comes first.
|
|
88
|
+
tab = $bindable(tabs.some((t) => t.id === 'home') ? 'home' : tabs[0]?.id ?? 'home'),
|
|
85
89
|
without = [],
|
|
86
90
|
collapsed = $bindable(false),
|
|
87
91
|
}: Props = $props()
|
|
@@ -183,8 +187,20 @@
|
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
/** Excel puts the shortcut after the name in the tooltip. */
|
|
190
|
+
// Every string the ribbon prints goes through the shell's map, keyed
|
|
191
|
+
// off the model, so a translation covers a button the model gains later.
|
|
192
|
+
const t = useSheetText()
|
|
193
|
+
const label = (item: RibbonItem): string => t(`ribbon.${item.id}.label`)
|
|
194
|
+
const title = (item: RibbonItem): string => t(`ribbon.${item.id}.title`)
|
|
195
|
+
/** An option's text, unless its key belongs to an earlier option with the
|
|
196
|
+
* same value (two palette colours on one hex): that one keeps its own label. */
|
|
197
|
+
const optionLabel = (item: RibbonItem, option: RibbonOption): string => {
|
|
198
|
+
const key = `ribbon.${item.id}.option.${option.value}`
|
|
199
|
+
return defaultSheetMessages[key] === option.label ? t(key) : option.label
|
|
200
|
+
}
|
|
201
|
+
const groupLabel = (group: RibbonGroup): string => t(`ribbon.group.${group.id}`)
|
|
186
202
|
function tip(item: RibbonItem): string {
|
|
187
|
-
return item.keys ? `${item
|
|
203
|
+
return item.keys ? `${title(item)} (${item.keys})` : title(item)
|
|
188
204
|
}
|
|
189
205
|
|
|
190
206
|
/**
|
|
@@ -350,11 +366,31 @@
|
|
|
350
366
|
if (first) themeFont = first
|
|
351
367
|
})
|
|
352
368
|
|
|
369
|
+
/**
|
|
370
|
+
* The tab strip's keyboard model, as WAI-ARIA asks for a tablist: the arrows
|
|
371
|
+
* move between tabs, Home and End jump to the ends, and FOCUS FOLLOWS the
|
|
372
|
+
* selection - without that last part a screen reader announces the old tab
|
|
373
|
+
* while the band under it has changed. The arrows are mirrored on a
|
|
374
|
+
* right-to-left ribbon, where the next tab is to the left.
|
|
375
|
+
*/
|
|
353
376
|
function onTabKey(event: KeyboardEvent) {
|
|
354
377
|
const ids = tabs.map((t) => t.id)
|
|
355
378
|
const at = ids.indexOf(tab)
|
|
356
|
-
|
|
357
|
-
|
|
379
|
+
const strip = (event.currentTarget as HTMLElement).parentElement
|
|
380
|
+
const rtl = strip ? getComputedStyle(strip).direction === 'rtl' : false
|
|
381
|
+
const forward = rtl ? 'ArrowLeft' : 'ArrowRight'
|
|
382
|
+
const back = rtl ? 'ArrowRight' : 'ArrowLeft'
|
|
383
|
+
let next = -1
|
|
384
|
+
if (event.key === forward && at < ids.length - 1) next = at + 1
|
|
385
|
+
else if (event.key === back && at > 0) next = at - 1
|
|
386
|
+
else if (event.key === 'Home') next = 0
|
|
387
|
+
else if (event.key === 'End') next = ids.length - 1
|
|
388
|
+
if (next < 0 || next === at) return
|
|
389
|
+
event.preventDefault()
|
|
390
|
+
tab = ids[next]!
|
|
391
|
+
// After the strip re-renders, the selected tab is the one Tab reaches, so
|
|
392
|
+
// it is the one that should hold focus.
|
|
393
|
+
void tick().then(() => strip?.querySelector<HTMLElement>('[role="tab"][aria-selected="true"]')?.focus())
|
|
358
394
|
}
|
|
359
395
|
</script>
|
|
360
396
|
|
|
@@ -374,17 +410,17 @@
|
|
|
374
410
|
class:on={state.on}
|
|
375
411
|
disabled={!state.enabled}
|
|
376
412
|
title={tip(item)}
|
|
377
|
-
aria-label={item
|
|
413
|
+
aria-label={title(item)}
|
|
378
414
|
aria-pressed={item.kind === 'toggle' ? state.on : undefined}
|
|
379
415
|
onclick={() => run(item)}
|
|
380
416
|
>
|
|
381
417
|
{#if item.icon}
|
|
382
418
|
<SvRibbonIcon name={item.icon} size={item.size === 'large' ? 32 : 16} />
|
|
383
419
|
{:else}
|
|
384
|
-
<span class="glyph" data-glyph={item.id}>{item
|
|
420
|
+
<span class="glyph" data-glyph={item.id}>{label(item)}</span>
|
|
385
421
|
{/if}
|
|
386
422
|
{#if item.size === 'large' || item.wide}
|
|
387
|
-
<span class="label">{item
|
|
423
|
+
<span class="label">{label(item)}</span>
|
|
388
424
|
{/if}
|
|
389
425
|
</button>
|
|
390
426
|
{/snippet}
|
|
@@ -400,38 +436,38 @@
|
|
|
400
436
|
class:has-bar={item.palette}
|
|
401
437
|
disabled={!state.enabled}
|
|
402
438
|
title={tip(item)}
|
|
403
|
-
aria-label={item
|
|
439
|
+
aria-label={title(item)}
|
|
404
440
|
onclick={() => run(item, faceValue(item))}
|
|
405
441
|
>
|
|
406
442
|
{#if item.icon && !(item.palette && item.id === 'text-colour')}
|
|
407
443
|
<SvRibbonIcon name={item.icon} />
|
|
408
444
|
{:else}
|
|
409
|
-
<span class="glyph" data-glyph={item.id}>{item
|
|
445
|
+
<span class="glyph" data-glyph={item.id}>{label(item)}</span>
|
|
410
446
|
{/if}
|
|
411
447
|
{#if item.palette}
|
|
412
448
|
<span class="bar" class:none={colour === null} style:background={colour ?? 'transparent'}></span>
|
|
413
449
|
{/if}
|
|
414
450
|
</button>
|
|
415
451
|
{#if live}
|
|
416
|
-
<SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={item
|
|
452
|
+
<SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={title(item)}>
|
|
417
453
|
{#snippet anchor()}
|
|
418
454
|
<button
|
|
419
455
|
type="button"
|
|
420
456
|
class="btn arrow"
|
|
421
457
|
disabled={!state.enabled}
|
|
422
|
-
aria-label={
|
|
458
|
+
aria-label={t('menuOptions', { title: title(item) })}
|
|
423
459
|
aria-haspopup="menu"
|
|
424
460
|
aria-expanded={menuOpen[item.id] ?? false}
|
|
425
461
|
><SvRibbonIcon name="chevron-down" size={10} /></button>
|
|
426
462
|
{/snippet}
|
|
427
463
|
{#if item.palette}
|
|
428
|
-
<div class="palette" role="menu" aria-label={item
|
|
464
|
+
<div class="palette" role="menu" aria-label={title(item)}>
|
|
429
465
|
{#if item.none}
|
|
430
466
|
<button type="button" class="palette-none" role="menuitem" onclick={() => pick(item, item.none!.value)}>
|
|
431
|
-
<span class="swatch no-colour" aria-hidden="true"></span>{item.none
|
|
467
|
+
<span class="swatch no-colour" aria-hidden="true"></span>{t(`ribbon.${item.id}.none`)}
|
|
432
468
|
</button>
|
|
433
469
|
{/if}
|
|
434
|
-
<div class="palette-title">
|
|
470
|
+
<div class="palette-title">{t('paletteThemeColors')}</div>
|
|
435
471
|
<div class="swatches">
|
|
436
472
|
{#each THEME_COLOURS as row, r (r)}
|
|
437
473
|
{#each row as c (c.value + r)}
|
|
@@ -439,7 +475,7 @@
|
|
|
439
475
|
{/each}
|
|
440
476
|
{/each}
|
|
441
477
|
</div>
|
|
442
|
-
<div class="palette-title">
|
|
478
|
+
<div class="palette-title">{t('paletteStandardColors')}</div>
|
|
443
479
|
<div class="swatches">
|
|
444
480
|
{#each STANDARD_COLOURS as c (c.value)}
|
|
445
481
|
<button type="button" class="swatch" role="menuitem" title={c.label} aria-label={c.label} style:background={c.value} onclick={() => pick(item, c.value)}></button>
|
|
@@ -447,11 +483,11 @@
|
|
|
447
483
|
</div>
|
|
448
484
|
</div>
|
|
449
485
|
{:else}
|
|
450
|
-
<div class="menu" role="menu" aria-label={item
|
|
486
|
+
<div class="menu" role="menu" aria-label={title(item)}>
|
|
451
487
|
{#each item.options ?? [] as option (option.value)}
|
|
452
488
|
<button type="button" class="menu-item" role="menuitem" onclick={() => pick(item, option.value)}>
|
|
453
489
|
{#if option.icon}<SvRibbonIcon name={option.icon} />{/if}
|
|
454
|
-
<span>{option
|
|
490
|
+
<span>{optionLabel(item, option)}</span>
|
|
455
491
|
</button>
|
|
456
492
|
{/each}
|
|
457
493
|
</div>
|
|
@@ -474,38 +510,38 @@
|
|
|
474
510
|
class:large={item.size === 'large'}
|
|
475
511
|
disabled={!state.enabled}
|
|
476
512
|
title={tip(item)}
|
|
477
|
-
aria-label={item
|
|
513
|
+
aria-label={title(item)}
|
|
478
514
|
aria-haspopup="menu"
|
|
479
515
|
aria-expanded={live ? (menuOpen[item.id] ?? false) : false}
|
|
480
516
|
>
|
|
481
517
|
{#if item.icon}
|
|
482
518
|
<SvRibbonIcon name={item.icon} size={item.size === 'large' ? 32 : 16} />
|
|
483
519
|
{:else}
|
|
484
|
-
<span class="glyph" data-glyph={item.id}>{item
|
|
520
|
+
<span class="glyph" data-glyph={item.id}>{label(item)}</span>
|
|
485
521
|
{/if}
|
|
486
522
|
{#if item.size === 'large' || item.wide}
|
|
487
|
-
<span class="label">{item
|
|
523
|
+
<span class="label">{label(item)}</span>
|
|
488
524
|
{/if}
|
|
489
525
|
<SvRibbonIcon name="chevron-down" size={8} />
|
|
490
526
|
</button>
|
|
491
527
|
{/snippet}
|
|
492
528
|
{#snippet entries()}
|
|
493
|
-
<div class="menu" role="menu" aria-label={item
|
|
529
|
+
<div class="menu" role="menu" aria-label={title(item)}>
|
|
494
530
|
{#each item.options ?? [] as option (option.value)}
|
|
495
531
|
{#if option.heading}
|
|
496
|
-
<div class="menu-heading" role="presentation">{option
|
|
532
|
+
<div class="menu-heading" role="presentation">{optionLabel(item, option)}</div>
|
|
497
533
|
{:else}
|
|
498
534
|
{#if option.toggle && option.emits}
|
|
499
535
|
{@const on = live && activeActions.includes(option.emits)}
|
|
500
536
|
<button type="button" class="menu-item" class:on role="menuitemcheckbox" aria-checked={on} onclick={() => pickOption(item, option)}>
|
|
501
537
|
{#if option.icon}<SvRibbonIcon name={option.icon} />{/if}
|
|
502
|
-
<span>{option
|
|
538
|
+
<span>{optionLabel(item, option)}</span>
|
|
503
539
|
{#if option.keys}<span class="keys">{option.keys}</span>{/if}
|
|
504
540
|
</button>
|
|
505
541
|
{:else}
|
|
506
542
|
<button type="button" class="menu-item" role="menuitem" onclick={() => pickOption(item, option)}>
|
|
507
543
|
{#if option.icon}<SvRibbonIcon name={option.icon} />{/if}
|
|
508
|
-
<span>{option
|
|
544
|
+
<span>{optionLabel(item, option)}</span>
|
|
509
545
|
{#if option.keys}<span class="keys">{option.keys}</span>{/if}
|
|
510
546
|
</button>
|
|
511
547
|
{/if}
|
|
@@ -524,38 +560,38 @@
|
|
|
524
560
|
class:large={item.size === 'large'}
|
|
525
561
|
disabled={!state.enabled}
|
|
526
562
|
title={tip(item)}
|
|
527
|
-
aria-label={item
|
|
563
|
+
aria-label={title(item)}
|
|
528
564
|
onclick={() => run(item)}
|
|
529
565
|
>
|
|
530
566
|
{#if item.icon}
|
|
531
567
|
<SvRibbonIcon name={item.icon} size={item.size === 'large' ? 32 : 16} />
|
|
532
568
|
{:else}
|
|
533
|
-
<span class="glyph" data-glyph={item.id}>{item
|
|
569
|
+
<span class="glyph" data-glyph={item.id}>{label(item)}</span>
|
|
534
570
|
{/if}
|
|
535
571
|
<!-- A small wide split (Merge & Center) prints its label on the face, as
|
|
536
572
|
Excel does; the colour splits stay icon-only. -->
|
|
537
|
-
{#if item.wide && item.size !== 'large'}<span class="label">{item
|
|
573
|
+
{#if item.wide && item.size !== 'large'}<span class="label">{label(item)}</span>{/if}
|
|
538
574
|
</button>
|
|
539
575
|
{#if live}
|
|
540
|
-
<SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={item
|
|
576
|
+
<SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={title(item)}>
|
|
541
577
|
{#snippet anchor()}
|
|
542
578
|
<button
|
|
543
579
|
type="button"
|
|
544
580
|
class="btn arrow"
|
|
545
581
|
disabled={!state.enabled}
|
|
546
|
-
aria-label={
|
|
582
|
+
aria-label={t('menuOptions', { title: title(item) })}
|
|
547
583
|
aria-haspopup="menu"
|
|
548
584
|
aria-expanded={menuOpen[item.id] ?? false}
|
|
549
|
-
>{#if item.size === 'large'}<span class="label">{item
|
|
585
|
+
>{#if item.size === 'large'}<span class="label">{label(item)}</span>{/if}<SvRibbonIcon name="chevron-down" size={10} /></button>
|
|
550
586
|
{/snippet}
|
|
551
587
|
{@render entries()}
|
|
552
588
|
</SvPopover>
|
|
553
589
|
{:else}
|
|
554
|
-
<span class="btn arrow" aria-hidden="true">{#if item.size === 'large'}<span class="label">{item
|
|
590
|
+
<span class="btn arrow" aria-hidden="true">{#if item.size === 'large'}<span class="label">{label(item)}</span>{/if}<SvRibbonIcon name="chevron-down" size={10} /></span>
|
|
555
591
|
{/if}
|
|
556
592
|
</div>
|
|
557
593
|
{:else if live}
|
|
558
|
-
<SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={item
|
|
594
|
+
<SvPopover bind:open={menuOpen[item.id]} placement="bottom-start" arrow={false} offset={2} ariaLabel={title(item)}>
|
|
559
595
|
{#snippet anchor()}
|
|
560
596
|
{@render face()}
|
|
561
597
|
{/snippet}
|
|
@@ -573,7 +609,7 @@
|
|
|
573
609
|
class="select"
|
|
574
610
|
data-select={item.id}
|
|
575
611
|
title={tip(item)}
|
|
576
|
-
aria-label={item
|
|
612
|
+
aria-label={title(item)}
|
|
577
613
|
disabled={!state.enabled}
|
|
578
614
|
value={live ? currentOf(item) : ''}
|
|
579
615
|
onchange={(e) => run(item, e.currentTarget.value)}
|
|
@@ -584,7 +620,7 @@
|
|
|
584
620
|
<option value={currentOf(item)}>{currentOf(item)}</option>
|
|
585
621
|
{/if}
|
|
586
622
|
{#each item.options ?? [] as option (option.value)}
|
|
587
|
-
<option value={option.value}>{option.value === '' && item.id === 'font-family' ? themeFont : option
|
|
623
|
+
<option value={option.value}>{option.value === '' && item.id === 'font-family' ? themeFont : optionLabel(item, option)}</option>
|
|
588
624
|
{/each}
|
|
589
625
|
</select>
|
|
590
626
|
{:else if item.kind === 'menu'}
|
|
@@ -620,8 +656,8 @@
|
|
|
620
656
|
{/if}
|
|
621
657
|
{/snippet}
|
|
622
658
|
|
|
623
|
-
<div class="sv-ribbon" class:collapsed role="toolbar" aria-label=
|
|
624
|
-
<div class="tabs" role="tablist" aria-label=
|
|
659
|
+
<div class="sv-ribbon" class:collapsed role="toolbar" aria-label={t('ribbonLabel')} aria-orientation="horizontal" bind:this={rootEl}>
|
|
660
|
+
<div class="tabs" role="tablist" aria-label={t('ribbonTabs')}>
|
|
625
661
|
{#each tabs as entry (entry.id)}
|
|
626
662
|
<button
|
|
627
663
|
type="button"
|
|
@@ -633,11 +669,11 @@
|
|
|
633
669
|
onclick={() => onTabClick(entry.id)}
|
|
634
670
|
ondblclick={toggleCollapsed}
|
|
635
671
|
onkeydown={onTabKey}
|
|
636
|
-
>{entry.
|
|
672
|
+
>{t(`ribbon.tab.${entry.id}`)}</button>
|
|
637
673
|
{/each}
|
|
638
674
|
<span class="tabs-grow"></span>
|
|
639
675
|
{#if collapsed && !peek}
|
|
640
|
-
<button type="button" class="ribbon-toggle" title=
|
|
676
|
+
<button type="button" class="ribbon-toggle" title={`${t('ribbonExpand')} (Ctrl+F1)`} aria-label={t('ribbonExpand')} aria-expanded="false" onclick={toggleCollapsed}>
|
|
641
677
|
<svg viewBox="0 0 10 10" width="10" height="10" aria-hidden="true"><path d="M2 3.5l3 3 3-3" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
642
678
|
</button>
|
|
643
679
|
{/if}
|
|
@@ -646,27 +682,30 @@
|
|
|
646
682
|
<div class="band" class:away={collapsed && !peek} class:peek={collapsed && peek} bind:clientWidth={bandWidth}>
|
|
647
683
|
{#each current?.groups ?? [] as group (group.id)}
|
|
648
684
|
{#if collapsedGroups.has(group.id)}
|
|
649
|
-
<section class="group folded" aria-label={group
|
|
650
|
-
<SvPopover bind:open={groupOpen[group.id]} placement="bottom-start" arrow={false} offset={4} ariaLabel={group
|
|
685
|
+
<section class="group folded" role="group" aria-label={groupLabel(group)}>
|
|
686
|
+
<SvPopover bind:open={groupOpen[group.id]} placement="bottom-start" arrow={false} offset={4} ariaLabel={groupLabel(group)}>
|
|
651
687
|
{#snippet anchor()}
|
|
652
|
-
<button type="button" class="btn large" aria-haspopup="true" aria-expanded={groupOpen[group.id] ?? false} title={group
|
|
688
|
+
<button type="button" class="btn large" aria-haspopup="true" aria-expanded={groupOpen[group.id] ?? false} title={groupLabel(group)}>
|
|
653
689
|
<span class="fold-icon"><SvRibbonIcon name={groupIcon(group)} size={32} /></span>
|
|
654
|
-
<span class="label">{group
|
|
690
|
+
<span class="label">{groupLabel(group)} <SvRibbonIcon name="chevron-down" size={10} /></span>
|
|
655
691
|
</button>
|
|
656
692
|
{/snippet}
|
|
657
693
|
<div class="popup-group">
|
|
658
694
|
{@render body(group, true)}
|
|
659
|
-
<div class="label-row"><span class="group-label">{group
|
|
695
|
+
<div class="label-row"><span class="group-label">{groupLabel(group)}</span></div>
|
|
660
696
|
</div>
|
|
661
697
|
</SvPopover>
|
|
662
698
|
</section>
|
|
663
699
|
{:else}
|
|
664
|
-
|
|
700
|
+
<!-- A group of the toolbar, not a landmark: a named <section> is a
|
|
701
|
+
region, and a ribbon of twelve regions (or two ribbons on one
|
|
702
|
+
page) buries whatever landmarks the page really has. -->
|
|
703
|
+
<section class="group" class:compact={fit.compact.has(group.id)} role="group" aria-label={groupLabel(group)}>
|
|
665
704
|
{@render body(group, true)}
|
|
666
705
|
<div class="label-row">
|
|
667
|
-
<span class="group-label">{group
|
|
706
|
+
<span class="group-label">{groupLabel(group)}</span>
|
|
668
707
|
{#if group.launcher}
|
|
669
|
-
<button type="button" class="launcher" title={
|
|
708
|
+
<button type="button" class="launcher" title={t('groupSettings', { group: groupLabel(group) })} aria-label={t('groupSettings', { group: groupLabel(group) })} onclick={() => launch(group.launcher!)}>
|
|
670
709
|
<svg viewBox="0 0 8 8" width="8" height="8" aria-hidden="true"><path d="M1 1h3.5M1 1v3.5M1.5 1.5L6.5 6.5M6.5 3.5V6.5H3.5" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
671
710
|
</button>
|
|
672
711
|
{/if}
|
|
@@ -676,7 +715,7 @@
|
|
|
676
715
|
{/each}
|
|
677
716
|
<!-- Excel's chevron at the band's end: collapse, or pin the peeked band. -->
|
|
678
717
|
<span class="band-grow"></span>
|
|
679
|
-
<button type="button" class="ribbon-toggle in-band" title={collapsed ? '
|
|
718
|
+
<button type="button" class="ribbon-toggle in-band" title={`${collapsed ? t('ribbonPin') : t('ribbonCollapse')} (Ctrl+F1)`} aria-label={collapsed ? t('ribbonPin') : t('ribbonCollapse')} onclick={toggleCollapsed}>
|
|
680
719
|
{#if collapsed}
|
|
681
720
|
<svg viewBox="0 0 12 12" width="11" height="11" aria-hidden="true"><path d="M4 1.5h4M6 1.5v3.5M3.5 5l2.5 2.5L8.5 5M6 7.5V11" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
|
682
721
|
{:else}
|
|
@@ -696,7 +735,7 @@
|
|
|
696
735
|
{#each current?.groups ?? [] as group (group.id)}
|
|
697
736
|
<section class="group" use:measure={group.id}>
|
|
698
737
|
{@render body(group, false)}
|
|
699
|
-
<div class="label-row"><span class="group-label">{group
|
|
738
|
+
<div class="label-row"><span class="group-label">{groupLabel(group)}</span></div>
|
|
700
739
|
</section>
|
|
701
740
|
{/each}
|
|
702
741
|
</div>
|
|
@@ -704,7 +743,7 @@
|
|
|
704
743
|
{#each current?.groups ?? [] as group (group.id)}
|
|
705
744
|
<section class="group compact" use:measure={`${group.id}:compact`}>
|
|
706
745
|
{@render body(group, false)}
|
|
707
|
-
<div class="label-row"><span class="group-label">{group
|
|
746
|
+
<div class="label-row"><span class="group-label">{groupLabel(group)}</span></div>
|
|
708
747
|
</section>
|
|
709
748
|
{/each}
|
|
710
749
|
</div>
|
|
@@ -713,7 +752,7 @@
|
|
|
713
752
|
<section class="group folded" use:measure={`${group.id}:folded`}>
|
|
714
753
|
<button type="button" class="btn large" tabindex="-1">
|
|
715
754
|
<span class="fold-icon"><SvRibbonIcon name={groupIcon(group)} size={32} /></span>
|
|
716
|
-
<span class="label">{group
|
|
755
|
+
<span class="label">{groupLabel(group)} <SvRibbonIcon name="chevron-down" size={10} /></span>
|
|
717
756
|
</button>
|
|
718
757
|
</section>
|
|
719
758
|
{/each}
|
|
@@ -746,8 +785,14 @@
|
|
|
746
785
|
gap: 0;
|
|
747
786
|
height: 30px;
|
|
748
787
|
padding: 0 8px;
|
|
788
|
+
/* On a phone the eight labels are wider than the screen. They scroll
|
|
789
|
+
sideways rather than shrink into each other, with no bar showing. */
|
|
790
|
+
overflow-x: auto;
|
|
791
|
+
scrollbar-width: none;
|
|
749
792
|
}
|
|
793
|
+
.tabs::-webkit-scrollbar { display: none; }
|
|
750
794
|
.tab {
|
|
795
|
+
flex: 0 0 auto;
|
|
751
796
|
position: relative;
|
|
752
797
|
font: inherit;
|
|
753
798
|
border: 0;
|
|
@@ -759,7 +804,15 @@
|
|
|
759
804
|
border-radius: 4px 4px 0 0;
|
|
760
805
|
}
|
|
761
806
|
.tab:hover { background: var(--sg-row-hover-bg, rgba(0, 0, 0, 0.04)); }
|
|
762
|
-
|
|
807
|
+
/* The label is the accent mixed toward the text colour, not the raw
|
|
808
|
+
accent: a 12px label in a host theme's accent (an orange, a cyan) lands
|
|
809
|
+
under 4.5:1 on the ribbon's near-white band, which axe fails and a
|
|
810
|
+
low-vision reader feels. The underline below keeps the accent pure, so
|
|
811
|
+
the tab still reads as Excel's. */
|
|
812
|
+
.tab.active {
|
|
813
|
+
color: color-mix(in srgb, var(--sg-accent, #107c41) 72%, var(--sg-fg, #242424));
|
|
814
|
+
font-weight: 600;
|
|
815
|
+
}
|
|
763
816
|
/* Excel's selected-tab mark: a short rounded underline, not a full-width
|
|
764
817
|
border, inset from the label's edges. */
|
|
765
818
|
.tab.active::after {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* shell opens it from the column letter's and the row number's menus and
|
|
5
5
|
* applies the size to every column or row the selection spans.
|
|
6
6
|
*/
|
|
7
|
+
import { useSheetText } from './sheet-text'
|
|
7
8
|
import { SvModal } from '@svgrid/grid'
|
|
8
9
|
|
|
9
10
|
type Props = {
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
let { open = $bindable(false), title, label, value, onApply, onClose }: Props = $props()
|
|
20
|
+
const t = useSheetText()
|
|
19
21
|
|
|
20
22
|
let draft = $state('')
|
|
21
23
|
let input = $state<HTMLInputElement | null>(null)
|
|
@@ -51,8 +53,8 @@
|
|
|
51
53
|
</form>
|
|
52
54
|
{#snippet footer()}
|
|
53
55
|
<div class="sv-sheet-dialog-buttons">
|
|
54
|
-
<button type="button" class="btn primary" onclick={ok} disabled={!valid}>
|
|
55
|
-
<button type="button" class="btn" onclick={close}>
|
|
56
|
+
<button type="button" class="btn primary" onclick={ok} disabled={!valid}>{t('ok')}</button>
|
|
57
|
+
<button type="button" class="btn" onclick={close}>{t('cancel')}</button>
|
|
56
58
|
</div>
|
|
57
59
|
{/snippet}
|
|
58
60
|
</SvModal>
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Excel's Sort dialog: a level per key, each a column and an order, over
|
|
4
|
+
* the block the shell worked out, with "My data has headers" deciding
|
|
5
|
+
* whether the first row is named in the column lists or sorted with the
|
|
6
|
+
* rest. Add Level, Delete Level, OK; the shell does the sorting, in one
|
|
7
|
+
* undo, from the keys handed back.
|
|
8
|
+
*/
|
|
9
|
+
import { useSheetText } from './sheet-text'
|
|
10
|
+
import { untrack } from 'svelte'
|
|
11
|
+
import { SvModal } from '@svgrid/grid'
|
|
12
|
+
import type { Workbook } from './sheet/workbook'
|
|
13
|
+
import type { SortKey } from './sheet/sort'
|
|
14
|
+
import { colToLetters } from './sheet/address'
|
|
15
|
+
|
|
16
|
+
type Block = { top: number; left: number; bottom: number; right: number }
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
open?: boolean
|
|
20
|
+
workbook: Workbook
|
|
21
|
+
/** The block to sort, and whether its first row looks like headers. */
|
|
22
|
+
block: Block
|
|
23
|
+
headerGuess: boolean
|
|
24
|
+
/** The column the dialog opens on: the active cell's. */
|
|
25
|
+
activeCol: number
|
|
26
|
+
/**
|
|
27
|
+
* A cell's fill or font colour, for a level that sorts on one. Without
|
|
28
|
+
* it the Sort On list offers values only, since there would be no
|
|
29
|
+
* colours to offer.
|
|
30
|
+
*/
|
|
31
|
+
colourAt?: (row: number, col: number, on: 'fill' | 'color') => string | null
|
|
32
|
+
onApply: (keys: SortKey[], hasHeaders: boolean) => void
|
|
33
|
+
onClose?: () => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let { open = $bindable(false), workbook, block, headerGuess, activeCol, colourAt, onApply, onClose }: Props = $props()
|
|
37
|
+
const t = useSheetText()
|
|
38
|
+
|
|
39
|
+
let levels = $state<SortKey[]>([])
|
|
40
|
+
let hasHeaders = $state(true)
|
|
41
|
+
|
|
42
|
+
const raw = (r: number, c: number) => workbook.getRaw(workbook.active, r, c)
|
|
43
|
+
|
|
44
|
+
/** The column list: the header text when the block has headers, else the letter. */
|
|
45
|
+
const columns = $derived.by(() =>
|
|
46
|
+
Array.from({ length: block.right - block.left + 1 }, (_, i) => {
|
|
47
|
+
const col = block.left + i
|
|
48
|
+
const letter = t('sort.columnLetter', { letter: colToLetters(col) })
|
|
49
|
+
return { col, label: hasHeaders ? raw(block.top, col) || letter : letter }
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
$effect(() => {
|
|
54
|
+
if (!open) return
|
|
55
|
+
untrack(() => {
|
|
56
|
+
hasHeaders = headerGuess
|
|
57
|
+
levels = [{ col: Math.min(Math.max(activeCol, block.left), block.right), direction: 'asc', on: 'value' }]
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The colours a level's column carries, each once. Excel offers exactly
|
|
63
|
+
* these, because a colour that is nowhere in the column would sort
|
|
64
|
+
* nothing.
|
|
65
|
+
*/
|
|
66
|
+
function coloursIn(col: number, on: 'fill' | 'color'): string[] {
|
|
67
|
+
if (!colourAt) return []
|
|
68
|
+
const seen: string[] = []
|
|
69
|
+
for (let r = block.top + (hasHeaders ? 1 : 0); r <= block.bottom; r += 1) {
|
|
70
|
+
const colour = colourAt(r, col, on)?.trim().toLowerCase()
|
|
71
|
+
if (colour && !seen.includes(colour)) seen.push(colour)
|
|
72
|
+
}
|
|
73
|
+
return seen
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Keep a level's colour one the column actually carries. */
|
|
77
|
+
function onSortOnChange(level: SortKey) {
|
|
78
|
+
if (level.on === 'value') { level.colour = undefined; return }
|
|
79
|
+
const colours = coloursIn(level.col, level.on === 'color' ? 'color' : 'fill')
|
|
80
|
+
if (!level.colour || !colours.includes(level.colour)) level.colour = colours[0]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function addLevel() {
|
|
84
|
+
// The next column not yet used, or the first one when all are.
|
|
85
|
+
const used = new Set(levels.map((l) => l.col))
|
|
86
|
+
const free = columns.find((c) => !used.has(c.col)) ?? columns[0]
|
|
87
|
+
if (!free) return
|
|
88
|
+
levels = [...levels, { col: free.col, direction: 'asc', on: 'value' }]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function deleteLevel(index: number) {
|
|
92
|
+
if (levels.length <= 1) return
|
|
93
|
+
levels = levels.filter((_, i) => i !== index)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function ok() {
|
|
97
|
+
if (!levels.length) return
|
|
98
|
+
onApply(levels.map((l) => ({ ...l })), hasHeaders)
|
|
99
|
+
open = false
|
|
100
|
+
onClose?.()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function close() {
|
|
104
|
+
open = false
|
|
105
|
+
onClose?.()
|
|
106
|
+
}
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<SvModal bind:open onClose={onClose} title={t('sort.title')} size="sm" width={460}>
|
|
110
|
+
<form class="sv-sheet-dialog sort" onsubmit={(e) => { e.preventDefault(); ok() }}>
|
|
111
|
+
<div class="toolbar">
|
|
112
|
+
<button type="button" class="btn" onclick={addLevel} disabled={levels.length >= columns.length}>{t('sort.addLevel')}</button>
|
|
113
|
+
<button type="button" class="btn" onclick={() => deleteLevel(levels.length - 1)} disabled={levels.length <= 1}>{t('sort.deleteLevel')}</button>
|
|
114
|
+
<span class="spacer"></span>
|
|
115
|
+
<label class="check"><input type="checkbox" bind:checked={hasHeaders} /> {t('sort.hasHeaders')}</label>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="levels" role="list" aria-label={t('sort.levels')}>
|
|
118
|
+
{#each levels as level, index (index)}
|
|
119
|
+
{@const by = t(index === 0 ? 'sort.sortBy' : 'sort.thenBy')}
|
|
120
|
+
<div class="level" role="listitem">
|
|
121
|
+
<span class="by">{by}</span>
|
|
122
|
+
<select aria-label={t('sort.column', { by })} bind:value={level.col}>
|
|
123
|
+
{#each columns as c (c.col)}<option value={c.col}>{c.label}</option>{/each}
|
|
124
|
+
</select>
|
|
125
|
+
<select
|
|
126
|
+
aria-label={t('sort.on', { by })}
|
|
127
|
+
bind:value={level.on}
|
|
128
|
+
onchange={() => onSortOnChange(level)}
|
|
129
|
+
disabled={!colourAt}
|
|
130
|
+
>
|
|
131
|
+
<option value="value">{t('sort.onValue')}</option>
|
|
132
|
+
<option value="fill">{t('sort.onFill')}</option>
|
|
133
|
+
<option value="color">{t('sort.onFont')}</option>
|
|
134
|
+
</select>
|
|
135
|
+
<select aria-label={t('sort.order', { by })} bind:value={level.direction}>
|
|
136
|
+
<option value="asc">{level.on === 'value' ? t('sort.asc') : t('sort.onTop')}</option>
|
|
137
|
+
<option value="desc">{level.on === 'value' ? t('sort.desc') : t('sort.onBottom')}</option>
|
|
138
|
+
</select>
|
|
139
|
+
{#if level.on !== 'value'}
|
|
140
|
+
{@const colours = coloursIn(level.col, level.on === 'color' ? 'color' : 'fill')}
|
|
141
|
+
<span class="colour-row">
|
|
142
|
+
<span class="by">{t('sort.colour')}</span>
|
|
143
|
+
{#if colours.length}
|
|
144
|
+
<select aria-label={t('sort.colour')} bind:value={level.colour}>
|
|
145
|
+
{#each colours as colour (colour)}<option value={colour}>{colour}</option>{/each}
|
|
146
|
+
</select>
|
|
147
|
+
<span class="swatch" style:background={level.colour} aria-hidden="true"></span>
|
|
148
|
+
{:else}
|
|
149
|
+
<span class="none">{t('sort.noColours')}</span>
|
|
150
|
+
{/if}
|
|
151
|
+
</span>
|
|
152
|
+
{/if}
|
|
153
|
+
</div>
|
|
154
|
+
{/each}
|
|
155
|
+
</div>
|
|
156
|
+
<p class="hint">
|
|
157
|
+
{t('sort.hint', { range: `${colToLetters(block.left)}${block.top + 1}:${colToLetters(block.right)}${block.bottom + 1}` })}
|
|
158
|
+
</p>
|
|
159
|
+
</form>
|
|
160
|
+
{#snippet footer()}
|
|
161
|
+
<div class="sv-sheet-dialog-buttons">
|
|
162
|
+
<button type="button" class="btn primary" onclick={ok} disabled={!levels.length}>{t('ok')}</button>
|
|
163
|
+
<button type="button" class="btn" onclick={close}>{t('cancel')}</button>
|
|
164
|
+
</div>
|
|
165
|
+
{/snippet}
|
|
166
|
+
</SvModal>
|
|
167
|
+
|
|
168
|
+
<style>
|
|
169
|
+
.toolbar {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
gap: 6px;
|
|
173
|
+
margin-bottom: 8px;
|
|
174
|
+
}
|
|
175
|
+
.toolbar .spacer { flex: 1; }
|
|
176
|
+
.levels {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: 6px;
|
|
180
|
+
padding: 6px;
|
|
181
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
182
|
+
border-radius: 3px;
|
|
183
|
+
background: var(--sg-bg, #fff);
|
|
184
|
+
}
|
|
185
|
+
.level {
|
|
186
|
+
display: grid;
|
|
187
|
+
grid-template-columns: 64px 1fr 104px 112px;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 8px;
|
|
190
|
+
}
|
|
191
|
+
/* The colour a colour level sorts on, under the level it belongs to. */
|
|
192
|
+
.colour-row {
|
|
193
|
+
grid-column: 1 / -1;
|
|
194
|
+
display: grid;
|
|
195
|
+
grid-template-columns: 64px 1fr auto;
|
|
196
|
+
align-items: center;
|
|
197
|
+
gap: 8px;
|
|
198
|
+
}
|
|
199
|
+
.colour-row .swatch {
|
|
200
|
+
width: 18px;
|
|
201
|
+
height: 18px;
|
|
202
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
203
|
+
border-radius: 3px;
|
|
204
|
+
}
|
|
205
|
+
.colour-row .none { color: var(--sg-muted, #616161); font-size: 12px; }
|
|
206
|
+
.level .by { color: var(--sg-muted, #616161); }
|
|
207
|
+
.level select {
|
|
208
|
+
min-width: 0;
|
|
209
|
+
font: inherit;
|
|
210
|
+
color: inherit;
|
|
211
|
+
background: var(--sg-bg, #fff);
|
|
212
|
+
border: 1px solid var(--sg-border, #d1d1d1);
|
|
213
|
+
border-radius: 2px;
|
|
214
|
+
padding: 2px 4px;
|
|
215
|
+
}
|
|
216
|
+
.check { display: inline-flex; align-items: center; gap: 6px; }
|
|
217
|
+
.hint { margin: 8px 0 0; color: var(--sg-muted, #616161); font-size: 12px; }
|
|
218
|
+
</style>
|