ooxml-ui 0.31.0 → 1.0.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.
Files changed (96) hide show
  1. package/README.md +14 -0
  2. package/custom-elements.json +10 -0
  3. package/dist/{chunk-RAZAJMHK.js → chunk-4QQASR5A.js} +2 -2
  4. package/dist/{chunk-HZ422LTR.js → chunk-BOX43CZD.js} +1 -0
  5. package/dist/{chunk-3GE2NHNG.js → chunk-FLHM5Q4O.js} +119 -81
  6. package/dist/{chunk-2QW4QE56.js → chunk-T3HB5LMH.js} +103 -68
  7. package/dist/controls.js +2 -2
  8. package/dist/docx/hard-break-command.d.ts +1 -3
  9. package/dist/docx/link-commands.d.ts +4 -15
  10. package/dist/docx/note-editor.d.ts +3 -2
  11. package/dist/docx/run-styles.d.ts +1 -1
  12. package/dist/docx/schema.d.ts +1 -1
  13. package/dist/docx/toggle-commands.d.ts +2 -8
  14. package/dist/docx.js +109 -302
  15. package/dist/form/gradient-direction-gallery.d.ts +19 -0
  16. package/dist/form/gradient-gallery-preview.d.ts +3 -0
  17. package/dist/form/gradient-preset-gallery.d.ts +16 -0
  18. package/dist/form/gradient-stop-track.d.ts +20 -0
  19. package/dist/form/number-range.d.ts +12 -0
  20. package/dist/icons.d.ts.map +1 -1
  21. package/dist/icons.js +1 -1
  22. package/dist/index.js +4 -4
  23. package/dist/menu/menu-button.d.ts +10 -0
  24. package/dist/menu/menu-button.d.ts.map +1 -1
  25. package/dist/pptx/{chart-3d-view-scene-IANLDFIE.mjs → chart-3d-view-scene-2KRQ5667.mjs} +2 -2
  26. package/dist/pptx/{chart-3d-view-scene-3DK5TUBZ.cjs → chart-3d-view-scene-36DMZWZ3.cjs} +37 -37
  27. package/dist/pptx/{chunk-C42ZL35K.mjs → chunk-ADOC5JLI.mjs} +1 -1
  28. package/dist/pptx/{chunk-QM4FGZG4.cjs → chunk-AIL3BNZS.cjs} +31 -131
  29. package/dist/pptx/{chunk-WLETAW3T.cjs → chunk-CJU4P5FT.cjs} +33 -13
  30. package/dist/pptx/{chunk-6SCK57RJ.cjs → chunk-FEFQTZDM.cjs} +158 -245
  31. package/dist/pptx/{chunk-EVGSFPUV.mjs → chunk-XWE5GSE7.mjs} +16 -109
  32. package/dist/pptx/{chunk-7JEMQW6R.mjs → chunk-YARCML7O.mjs} +11 -122
  33. package/dist/pptx/{chunk-OHE5UXST.cjs → chunk-YHA6XAB3.cjs} +1 -1
  34. package/dist/pptx/{chunk-X2F6PA2D.mjs → chunk-YOGDIDPB.mjs} +22 -3
  35. package/dist/pptx/dom/index.cjs +235 -235
  36. package/dist/pptx/dom/index.mjs +6 -6
  37. package/dist/pptx/export/html2canvas-clone.cjs +2 -2
  38. package/dist/pptx/export/html2canvas-clone.mjs +1 -1
  39. package/dist/pptx/index.cjs +2993 -2989
  40. package/dist/pptx/index.mjs +4 -4
  41. package/dist/pptx/render/chart-combo-stock.cjs +3 -3
  42. package/dist/pptx/render/chart-combo-stock.mjs +1 -1
  43. package/dist/pptx/render/chart-manual-layout.d.ts +2 -20
  44. package/dist/pptx/render/chart-manual-layout.d.ts.map +1 -1
  45. package/dist/pptx/render/chart-overlays.cjs +7 -7
  46. package/dist/pptx/render/chart-overlays.mjs +1 -1
  47. package/dist/pptx/render/chart-surface-treemap.cjs +4 -4
  48. package/dist/pptx/render/chart-surface-treemap.mjs +1 -1
  49. package/dist/pptx/render/chart-svg-def-types.d.ts +6 -1
  50. package/dist/pptx/render/chart-svg-def-types.d.ts.map +1 -1
  51. package/dist/pptx/render/chart-view-model-dom-helpers.d.ts +1 -1
  52. package/dist/pptx/render/chart-view-model-dom-helpers.d.ts.map +1 -1
  53. package/dist/pptx/render/chart-view-model.cjs +41 -41
  54. package/dist/pptx/render/chart-view-model.mjs +1 -1
  55. package/dist/pptx/render/chart-waterfall-map.cjs +6 -6
  56. package/dist/pptx/render/chart-waterfall-map.mjs +1 -1
  57. package/dist/pptx/render/element-interaction.d.ts +1 -1
  58. package/dist/pptx/render/element-interaction.d.ts.map +1 -1
  59. package/dist/pptx/render/gradient-picker.d.ts.map +1 -1
  60. package/dist/pptx/render/index.d.ts +1 -0
  61. package/dist/pptx/render/index.d.ts.map +1 -1
  62. package/dist/pptx/render/path-gradient-rect.d.ts +1 -75
  63. package/dist/pptx/render/path-gradient-rect.d.ts.map +1 -1
  64. package/dist/pptx/render/rotate-handle-placement.d.ts +1 -27
  65. package/dist/pptx/render/rotate-handle-placement.d.ts.map +1 -1
  66. package/dist/pptx/render/rotation-drag.d.ts +1 -7
  67. package/dist/pptx/render/rotation-drag.d.ts.map +1 -1
  68. package/dist/teams/app/files-panel.d.ts +8 -3
  69. package/dist/teams/app/teams-app.d.ts +1 -0
  70. package/dist/teams/app/teams-settings.d.ts +6 -0
  71. package/dist/teams.js +3 -3
  72. package/dist/visio/render-fill.d.ts +2 -1
  73. package/dist/visio/render-svg.d.ts +2 -0
  74. package/dist/visio/ribbon-action.d.ts +8 -1
  75. package/dist/visio/ribbon-parts.d.ts +2 -1
  76. package/dist/visio/ribbon-router.d.ts +4 -2
  77. package/dist/visio/viewer-commands.d.ts +3 -3
  78. package/dist/visio/viewer-draw-tool.d.ts +16 -6
  79. package/dist/visio/viewer-handle-events.d.ts +19 -0
  80. package/dist/visio/viewer-line-endpoints.d.ts +14 -0
  81. package/dist/visio/viewer-rotation-handle.d.ts +14 -0
  82. package/dist/visio/viewer-rotation-preview.d.ts +7 -0
  83. package/dist/visio.js +734 -88
  84. package/dist/xlsx/chart-area-pane.d.ts +9 -0
  85. package/dist/xlsx/chart-fill-binding.d.ts +23 -0
  86. package/dist/xlsx/chart-gradient-preview.d.ts +8 -0
  87. package/dist/xlsx/chart-series-fill.d.ts +2 -1
  88. package/dist/xlsx/chart-series-gradient-type.d.ts +9 -0
  89. package/dist/xlsx/chart-series-gradient.d.ts +8 -0
  90. package/dist/xlsx/chart-series-pane.d.ts +2 -1
  91. package/dist/xlsx/chart-series-range-preview.d.ts +7 -0
  92. package/dist/xlsx/chart-series-transparency.d.ts +2 -1
  93. package/dist/xlsx/grid/measure.d.ts +5 -0
  94. package/dist/xlsx/grid/styles.d.ts +1 -1
  95. package/dist/xlsx.js +962 -102
  96. package/package.json +2 -2
package/dist/xlsx.js CHANGED
@@ -1,20 +1,21 @@
1
- import './chunk-RAZAJMHK.js';
1
+ import './chunk-4QQASR5A.js';
2
2
  import { defineSmartArt } from './chunk-XDUYS4KK.js';
3
- import { withExtension, saveExtension, downloadBytes, emit, emitEvent, blobFor, announceFileCommand, pickFile } from './chunk-3GE2NHNG.js';
4
- export { XLSX_EDITOR_EVENTS } from './chunk-3GE2NHNG.js';
3
+ import { withExtension, saveExtension, downloadBytes, emit, emitEvent, blobFor, announceFileCommand, pickFile } from './chunk-FLHM5Q4O.js';
4
+ export { XLSX_EDITOR_EVENTS } from './chunk-FLHM5Q4O.js';
5
5
  import './chunk-SNV6YKPC.js';
6
6
  import './chunk-X7RINDJQ.js';
7
- import { range_control_default, createRibbonOverflow, defineBackstage, defineRibbon, defineTitleBar, defineStatusBar, defineZoomSlider, rangeControl, defineDialog, runKeyTips, assignKeyTips, defineCheckbox, defineGallery, defineContextMenu, parseSvgPreview } from './chunk-2QW4QE56.js';
8
- import { getIcon, registerIcon } from './chunk-HZ422LTR.js';
7
+ import { range_control_default, createRibbonOverflow, defineBackstage, defineRibbon, defineTitleBar, defineStatusBar, defineZoomSlider, rangeControl, defineDialog, runKeyTips, assignKeyTips, defineCheckbox, defineGallery, defineContextMenu, parseSvgPreview } from './chunk-T3HB5LMH.js';
8
+ import { getIcon, registerIcon } from './chunk-BOX43CZD.js';
9
9
  import './chunk-OXKWNNNJ.js';
10
10
  import { withTokens } from './chunk-ZV2Z4IWH.js';
11
11
  import './chunk-JFJXBYRD.js';
12
12
  import { SPECIAL_CODES, FRACTION_CODES, AUTOSUM_FUNCTIONS, themeToCssVars, lightTheme, darkTheme, ICON_ACCENTS, ICON_PATHS, normalizeThemeMode, applyThemeColors, parseSelectionRef, selectionRef, normalizeEditorLocale, DirtyState, createCommandRegistry, createDialogRegistry, activeRef, createSelectionModel, SheetSelections, translator, initialSelection, sheetBaseName, onRibbonTabsChange, activeChart, editChart, ribbonTabs, isTabHidden, selectTab, setTabHidden, tabButton, searchCommands, formatNumber, TEMPLATES, createTemplateWorkbook, isMenuSeparator, registerRibbonTabs, installFormatPainter, SHEET_ICONS, clipboardCommands, fontCommands, alignmentCommands, numberCommands, cellCommands, editingCommands, pageLayoutCommands, formulaCommands, dataCommands, contextualCommands, reviewTab, viewTab, helpTab, editBridge, CellViewCache, QUADRANTS, columnHeaderHeight, rowHeaderWidth, GridGeometry, cellRange, scrollToReveal, buildItems, fitNumber, cssFont, clampAddress, selectCell, movingEnd, extendSelection, moveWithinSelection, rowsRange, colsRange, ALL_RANGE, tabMenu, editing, icon, DATA_BAR_COLORS, dataBarRule, COLOR_SCALES, colorScaleRule, iconSetRule, target, tableAt, viewing, activeTable, numberFormatOptions, FONT_NAMES, FONT_SIZES, BORDER_PRESETS, ORIENTATIONS, ACCOUNTING_FORMATS, themePaletteItems, MARGIN_PRESETS, PAPER_SIZES, FUNCTION_MENUS, MORE_FUNCTION_CATEGORIES, functionsIn, isSingleCell, spanRange, selectionCellCount, addRange, activeStyle, selectSpecial, insertFunctionCall, guessHeader, regionOf, HIGHLIGHT_STYLES, ICON_SETS, TABLE_STYLES, tableStylePreview, resolveNameBox, absoluteReference, nameForRange, fillPaint, iconSvg, colSelected, rowSelected, rowHeaderMenu, columnHeaderMenu, cellMenu, recentFunctions, evaluateRef, buildChart, detectCategory, CATEGORIES, TIME_CODES, DATE_CODES, DEFAULT_OPTIONS, cellStyleItems, textDecoration, justify, alignItems, textAlign, hasBorders, edgeCss, diagonalSvg, SYMBOLS, buildCode, UNSET, dxfCss, negativeCodes, iconCount, iconThresholds, rowsOf, colsOf } from 'ooxml-core/xlsx/ui';
13
13
  export { EDITOR_LOCALES, THEME_KEYS, normalizeEditorLocale, registerRibbonTabs, themeToCssVars, darkTheme as xlsxDarkTheme, lightTheme as xlsxLightTheme } from 'ooxml-core/xlsx/ui';
14
- import { createWorksheet, createEditSession, approximateMeasure, createWorkbook, MAX_ROW, MAX_COL, chartView, createRefEvaluator, chartBarSpacing, selectionStats, formatRange, usedRange, chartSeriesTransparency, createGridMetrics, mergeView, cellView, DEFAULT_THEME, resolveColor, chartSeriesTransparencyPatch, chartSeriesSolidFillPatch, chartSeriesFillPatch, nextSheetName, FUNCTION_CATALOG, mergeAt, visibleCells, getCell, cellInputText, closeParens, isFormulaText, parseCellInput, referenceTarget, formatAddress, navigate, anchorToPixelBox, renderChartSvg, pixelBoxToAnchor, listValidationOptions, validationAt, getValue, resolvePasteOptions, DEFAULT_COL_WIDTH, nameAtCaret, functionMatches, callAtCaret, splice, acceptsReference, translateFormula, isCellError, parseHtmlTable, toggleAbsolute, rangeContains, formatValue, isDateFormat, pictureAnchorAt, validateDefinedName, parseAddress, styleAt, parseRange, rangesIntersect, chartColorScheme, coloredReferences, syntaxParams, activeParam, columnLabel, THEME_SLOTS, normalizeRange, currentRegion, autoFitColumnWidth, validateSheetName, dateToSerial, IMAGE_EXTENSIONS, verifySheetPassword, verifyWorkbookPassword, sheetByName, quoteSheetName, serialToDate, BUILTIN_NUMBER_FORMATS, pixelsToColumnWidth, validateTableName } from 'ooxml-core/xlsx';
14
+ import { createWorksheet, createEditSession, approximateMeasure, createWorkbook, MAX_ROW, MAX_COL, chartView, createRefEvaluator, chartBarSpacing, CHART_FILL_PARTS, selectionStats, formatRange, chartSeriesTransparencyPatch, chartSeriesTransparency, chartSeriesGradientPatch, chartSeriesSolidFillPatch, chartSeriesFillPatch, chartWithElementFills, chartElementTransparencyPatch, chartElementTransparency, chartElementGradientPatch, chartElementSolidFillPatch, chartElementFillPatch, chartElementFill, usedRange, chartGradientStopTransparency, createGridMetrics, mergeView, cellView, DEFAULT_THEME, resolveColor, nextSheetName, FUNCTION_CATALOG, mergeAt, visibleCells, getCell, cellInputText, closeParens, isFormulaText, parseCellInput, referenceTarget, formatAddress, navigate, anchorToPixelBox, renderChartSvg, pixelBoxToAnchor, listValidationOptions, validationAt, getValue, resolvePasteOptions, DEFAULT_COL_WIDTH, nameAtCaret, functionMatches, callAtCaret, splice, acceptsReference, translateFormula, isCellError, parseHtmlTable, toggleAbsolute, rangeContains, formatValue, isDateFormat, pictureAnchorAt, validateDefinedName, parseAddress, styleAt, parseRange, rangesIntersect, chartColorScheme, coloredReferences, syntaxParams, activeParam, columnLabel, THEME_SLOTS, normalizeRange, currentRegion, autoFitColumnWidth, validateSheetName, dateToSerial, IMAGE_EXTENSIONS, verifySheetPassword, verifyWorkbookPassword, sheetByName, quoteSheetName, serialToDate, BUILTIN_NUMBER_FORMATS, pixelsToColumnWidth, validateTableName } from 'ooxml-core/xlsx';
15
15
  import { hyperlinkPolicy } from 'ooxml-core/opc';
16
- import { CHART_COLOR_PALETTES, chartPaletteColors } from 'ooxml-core/chart';
16
+ import { resolveChartGradient, sortGradientStops, withChartGradientDirection, buildChartGradientDef, chartGradientMarkup, CHART_COLOR_PALETTES, chartPaletteColors } from 'ooxml-core/chart';
17
17
  import { render } from 'lit';
18
+ import { RECT_GRADIENT_DIRECTIONS, OFFICE_GRADIENT_PRESETS, officeGradientPresetId, officeGradientPresetFill, resolveDrawingColor, drawingColorBrightness, drawingColorCss, rectGradientDirection } from 'ooxml-core/diagram';
18
19
 
19
20
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/xlsx/theme/office-bridge.css?raw
20
21
  var office_bridge_default = "/*\n * The shared ooxml-ui elements read only --office-* tokens. This feeds them from the editor's\n * --xve-* theme, declared on the host so a theme, a dark scheme or a host override of --xve-*\n * keeps working (the same approach as pptx-viewer's office-token-bridge and docx-viewer's).\n */\n:host {\n --office-font: 'Segoe UI', system-ui, -apple-system, sans-serif;\n --office-foreground: var(--xve-foreground);\n --office-muted-foreground: var(--xve-muted-foreground);\n --office-background: var(--xve-background);\n --office-surface: var(--xve-card);\n --office-selected: var(--xve-accent);\n --office-border: var(--xve-border);\n --office-accent: var(--xve-primary);\n --office-accent-foreground: var(--xve-primary-foreground);\n --office-ring: var(--xve-ring);\n --office-popover: var(--xve-popover);\n --office-popover-foreground: var(--xve-popover-foreground, var(--xve-foreground));\n --office-danger: var(--xve-destructive);\n --office-danger-foreground: var(--xve-destructive-foreground);\n}\n";
@@ -32,7 +33,7 @@ var popups_default = "/* Floating surfaces: ribbon popovers, menus, the colour g
32
33
  var backstage_default = "/* File backstage: the shared office-ui-backstage draws the navigation; these style the page content.\n The nav colours come from its tokens. */\n.xve-backstage {\n z-index: 40;\n --office-background: var(--surface);\n --office-backstage-nav-background: var(--xl);\n --office-backstage-nav-foreground: var(--xve-primary-foreground);\n}\n.xve-backstage-content {\n overflow: auto;\n padding: 28px 40px;\n color: var(--ink);\n}\n.xve-backstage-content h2 {\n margin: 0 0 20px;\n font-size: 30px;\n font-weight: 300;\n}\n.xve-backstage-content h3 {\n margin: 24px 0 10px;\n font-size: 15px;\n font-weight: 600;\n}\n.xve-backstage-title {\n margin: 0 0 4px;\n font-size: 26px;\n font-weight: 400;\n overflow-wrap: anywhere;\n}\n.xve-backstage-muted {\n color: var(--muted);\n}\n.xve-backstage-facts {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 6px 24px;\n margin: 0;\n}\n.xve-backstage-facts dt {\n color: var(--muted);\n}\n.xve-backstage-facts dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n.xve-compatibility-notes {\n display: grid;\n gap: 8px;\n max-width: 760px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.xve-compatibility-notes li {\n display: flex;\n gap: 8px;\n padding: 10px 12px;\n border: 1px solid var(--line);\n border-radius: 6px;\n line-height: 1.45;\n}\n.xve-compatibility-notes .xve-icon {\n margin-top: 2px;\n color: #b7791f;\n}\n.xve-backstage-columns {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 300px;\n gap: 40px;\n align-items: start;\n}\n.xve-backstage-properties {\n padding: 4px 0 0 24px;\n border-left: 1px solid var(--line);\n}\n.xve-backstage-properties h3 {\n margin-top: 0;\n}\n.xve-backstage-tiles {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n}\n.xve-backstage-tile {\n display: inline-flex;\n flex-direction: column;\n gap: 8px;\n padding: 8px;\n border: 1px solid transparent;\n border-radius: 6px;\n background: none;\n color: var(--ink);\n font: inherit;\n cursor: pointer;\n}\n.xve-backstage-tile:hover {\n border-color: var(--line);\n background: var(--xl-soft);\n}\n.xve-backstage-tile-preview {\n display: block;\n width: 150px;\n height: 112px;\n border: 1px solid var(--line);\n background-color: #fff;\n background-image:\n linear-gradient(#e1e1e1 1px, transparent 1px),\n linear-gradient(90deg, #e1e1e1 1px, transparent 1px);\n background-size: 30px 14px;\n box-shadow: 0 1px 3px #0002;\n}\n.xve-backstage-tile-preview[data-template]::before {\n display: block;\n height: 14px;\n background: var(--xl);\n content: '';\n opacity: 0.85;\n}\n.xve-backstage-card {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n max-width: 760px;\n margin-bottom: 12px;\n padding: 14px 18px;\n border: 1px solid var(--line);\n border-radius: 6px;\n}\n.xve-backstage-card h3 {\n margin: 0 0 4px;\n}\n.xve-backstage-card p {\n margin: 0;\n}\n.xve-backstage-primary {\n padding: 8px 18px;\n border: 0;\n border-radius: 4px;\n background: var(--xl);\n color: var(--xve-primary-foreground);\n font: inherit;\n cursor: pointer;\n white-space: nowrap;\n}\n.xve-backstage-field {\n display: grid;\n gap: 4px;\n max-width: 420px;\n margin-bottom: 14px;\n color: var(--muted);\n font-size: 12px;\n}\n.xve-backstage-field input,\n.xve-backstage-field select {\n height: 32px;\n padding: 0 8px;\n border: 1px solid var(--line);\n border-radius: 4px;\n background: var(--surface);\n color: var(--ink);\n font:\n 14px 'Segoe UI',\n Arial,\n sans-serif;\n}\n.xve-backstage-check {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 10px;\n}\n.xve-customize-group {\n margin: 0 0 8px;\n padding: 6px 10px 8px;\n border: 1px solid var(--line);\n border-radius: 4px;\n}\n.xve-customize-group legend {\n padding: 0 4px;\n color: var(--muted);\n font-size: 12px;\n}\n.xve-customize-row {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 180px;\n padding: 2px 8px 2px 0;\n font-size: 13px;\n}\n@media (max-width: 760px) {\n .xve-backstage-content {\n padding: 20px;\n }\n .xve-backstage-columns {\n grid-template-columns: minmax(0, 1fr);\n }\n .xve-backstage-properties {\n padding-left: 0;\n border-left: 0;\n }\n}\n";
33
34
 
34
35
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/xlsx/theme/chart-series.css?raw
35
- var chart_series_default = ".xve-canvas-row {\n display: flex;\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n.xve-chart-series-pane {\n box-sizing: border-box;\n width: 300px;\n max-width: 45%;\n flex: none;\n overflow: auto;\n border-left: 1px solid var(--line);\n background: var(--surface);\n}\n.xve-chart-series-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 12px 12px 18px;\n border-bottom: 1px solid var(--line);\n}\n.xve-chart-series-header h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 400;\n}\n.xve-chart-series-body,\n.xve-chart-series-pane > .xve-note {\n margin: 0;\n padding: 18px;\n}\n.xve-chart-series-body h3 {\n margin: 0 0 20px;\n font-size: 13px;\n font-weight: 600;\n}\n.xve-chart-series-body .xve-field {\n display: grid;\n grid-template-columns: 1fr 70px 12px;\n align-items: center;\n gap: 6px;\n margin: 14px 0;\n}\n.xve-chart-series-body .xve-input-number {\n box-sizing: border-box;\n width: 70px;\n min-width: 0;\n}\n.xve-chart-series-range .office-range-control {\n box-sizing: border-box;\n width: 100%;\n margin: 0 0 8px;\n}\n.xve-chart-series-fill {\n margin-top: 20px;\n padding-top: 18px;\n border-top: 1px solid var(--line);\n}\n.xve-chart-series-body .xve-field:has(select),\n.xve-chart-series-body .xve-field:has(.xve-chart-series-color) {\n grid-template-columns: 1fr minmax(100px, 1fr);\n}\n.xve-chart-series-color {\n height: 28px;\n background: var(--series-fill);\n border: 1px solid var(--line);\n cursor: pointer;\n}\n.xve-chart-series-color:disabled {\n opacity: 0.5;\n cursor: default;\n}\n";
36
+ var chart_series_default = ".xve-canvas-row {\n display: flex;\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n.xve-chart-series-pane {\n box-sizing: border-box;\n width: 300px;\n max-width: 45%;\n flex: none;\n overflow: auto;\n border-left: 1px solid var(--line);\n background: var(--surface);\n}\n.xve-chart-series-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 12px 12px 18px;\n border-bottom: 1px solid var(--line);\n}\n.xve-chart-series-header h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 400;\n}\n.xve-chart-series-body,\n.xve-chart-series-pane > .xve-note {\n margin: 0;\n padding: 18px;\n}\n.xve-chart-series-body h3 {\n margin: 0 0 20px;\n font-size: 13px;\n font-weight: 600;\n}\n.xve-chart-series-body .xve-field {\n display: grid;\n grid-template-columns: 1fr 70px 12px;\n align-items: center;\n gap: 6px;\n margin: 14px 0;\n}\n.xve-chart-series-body .xve-input-number {\n box-sizing: border-box;\n width: 70px;\n min-width: 0;\n}\n.xve-chart-series-range .office-range-control {\n box-sizing: border-box;\n width: 100%;\n margin: 0 0 8px;\n}\n.xve-chart-series-fill {\n margin-top: 20px;\n padding-top: 18px;\n border-top: 1px solid var(--line);\n}\n.xve-chart-series-body .xve-field:has(select),\n.xve-chart-series-body .xve-field:has(.xve-chart-series-color),\n.xve-chart-series-body .xve-field:has(office-ui-gallery) {\n grid-template-columns: 1fr minmax(100px, 1fr);\n}\n.xve-chart-series-body office-ui-gallery .trigger {\n box-sizing: border-box;\n width: 100%;\n white-space: normal;\n}\n.xve-chart-series-color {\n height: 28px;\n background: var(--series-fill);\n border: 1px solid var(--line);\n cursor: pointer;\n}\n.xve-chart-series-color:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.xve-chart-series-gradient {\n margin-top: 18px;\n}\n.xve-chart-gradient-buttons {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n.xve-chart-gradient-selector {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.xve-chart-gradient-selector .office-gradient-stop-track {\n flex: 1;\n min-width: 0;\n}\n.xve-chart-gradient-buttons .xve-button {\n width: 26px;\n height: 28px;\n padding: 0;\n font-size: 18px;\n}\n";
36
37
 
37
38
  // src/xlsx/theme/styles.ts
38
39
  var declarations = (theme, scheme) => Object.entries(themeToCssVars(theme)).map(([name, value]) => `${name}:${value};`).join("") + `color-scheme:${scheme};`;
@@ -1650,13 +1651,13 @@ var DISPLAY = {
1650
1651
  function formatKeys(spec, mac) {
1651
1652
  const parsed = parse(spec);
1652
1653
  const key = parsed.code ?? (spec.endsWith("+") ? "+" : spec.split("+").pop() ?? "");
1653
- const names = [
1654
+ const names2 = [
1654
1655
  parsed.mod ? mac ? "Cmd" : "Ctrl" : "",
1655
1656
  parsed.alt ? mac ? "Option" : "Alt" : "",
1656
1657
  parsed.shift ? "Shift" : "",
1657
1658
  DISPLAY[key.toLowerCase()] ?? (key.length === 1 ? key.toUpperCase() : key)
1658
1659
  ];
1659
- return names.filter(Boolean).join("+");
1660
+ return names2.filter(Boolean).join("+");
1660
1661
  }
1661
1662
  var displayKeys = (shortcut, mac) => shortcut.keys.filter((spec) => !spec.includes("code:")).map((spec) => formatKeys(spec, mac));
1662
1663
  function findShortcut(event, mac) {
@@ -1921,12 +1922,12 @@ function openMenu(anchor, entries, label = "") {
1921
1922
  item.setAttribute("role", entry.checked === void 0 ? "menuitem" : "menuitemcheckbox");
1922
1923
  if (entry.checked !== void 0) item.setAttribute("aria-checked", String(entry.checked));
1923
1924
  item.disabled = entry.disabled ?? false;
1924
- const icon6 = doc.createElement("span");
1925
- icon6.className = "ribbon-menu-icon";
1926
- if (entry.icon) icon6.append(ribbonIcon(doc, entry.icon, 16));
1925
+ const icon7 = doc.createElement("span");
1926
+ icon7.className = "ribbon-menu-icon";
1927
+ if (entry.icon) icon7.append(ribbonIcon(doc, entry.icon, 16));
1927
1928
  const text3 = doc.createElement("span");
1928
1929
  text3.textContent = entry.label;
1929
- item.append(icon6, text3);
1930
+ item.append(icon7, text3);
1930
1931
  if (entry.shortcut) {
1931
1932
  const keys = doc.createElement("span");
1932
1933
  keys.className = "ribbon-menu-shortcut";
@@ -3002,10 +3003,10 @@ function openGoTo(ctx) {
3002
3003
  (name) => reference.value = name,
3003
3004
  () => confirm2?.()
3004
3005
  );
3005
- const names = (workbook?.definedNames ?? []).filter(
3006
+ const names2 = (workbook?.definedNames ?? []).filter(
3006
3007
  (n) => !n.hidden && !n.name.startsWith("_xlnm.")
3007
3008
  );
3008
- list.setItems(names.map((n) => [n.name, n.name]));
3009
+ list.setItems(names2.map((n) => [n.name, n.name]));
3009
3010
  const special = button(ctx, "Special...");
3010
3011
  return showDialog(
3011
3012
  ctx,
@@ -3193,15 +3194,15 @@ function openCreateNames(ctx) {
3193
3194
  opened: () => boxes[0]?.input.focus(),
3194
3195
  submit: () => {
3195
3196
  const chosen = boxes.filter((b) => b.input.checked).map((b) => b.edge);
3196
- const names = namesFromSelection(ctx, chosen);
3197
- if (!names.length) {
3197
+ const names2 = namesFromSelection(ctx, chosen);
3198
+ if (!names2.length) {
3198
3199
  ctx.toast(ctx.t("No names could be created from the selection."), "warning");
3199
3200
  return void 0;
3200
3201
  }
3201
3202
  t.session.batch("Create names", () => {
3202
- for (const name of names) t.session.setDefinedName(name);
3203
+ for (const name of names2) t.session.setDefinedName(name);
3203
3204
  });
3204
- return names.length;
3205
+ return names2.length;
3205
3206
  }
3206
3207
  });
3207
3208
  }
@@ -4225,7 +4226,7 @@ function nameValue(ctx, name) {
4225
4226
  return { text: "#NAME?", error: true };
4226
4227
  }
4227
4228
  }
4228
- var visibleNames = (names) => names.filter((n) => !n.hidden && !n.name.startsWith("_xlnm."));
4229
+ var visibleNames = (names2) => names2.filter((n) => !n.hidden && !n.name.startsWith("_xlnm."));
4229
4230
  function openNameManager(ctx) {
4230
4231
  const doc = ctx.host.ownerDocument;
4231
4232
  const filter = select(ctx, FILTERS, "all");
@@ -4253,9 +4254,9 @@ function openNameManager(ctx) {
4253
4254
  head.append(th);
4254
4255
  }
4255
4256
  const body = table.createTBody();
4256
- const names = visibleNames(workbook?.definedNames ?? []);
4257
- if (selected && !names.includes(selected)) selected = void 0;
4258
- for (const name of names) {
4257
+ const names2 = visibleNames(workbook?.definedNames ?? []);
4258
+ if (selected && !names2.includes(selected)) selected = void 0;
4259
+ for (const name of names2) {
4259
4260
  const value = nameValue(ctx, name);
4260
4261
  const kind = filter.value;
4261
4262
  if (kind === "worksheet" && name.localSheet === void 0) continue;
@@ -5711,12 +5712,12 @@ function hyperlinkDialog(ctx) {
5711
5712
  const tooltip2 = textInput(ctx, existing?.tooltip ?? "");
5712
5713
  const address = textInput(ctx, !mail ? existing?.target ?? "" : "");
5713
5714
  const sheets = t.workbook.sheets.map((s) => [s.name, s.name]);
5714
- const names = t.workbook.definedNames.filter((n) => !n.hidden && !n.name.startsWith("_xlnm.")).map((n) => [`name:${n.name}`, n.name]);
5715
+ const names2 = t.workbook.definedNames.filter((n) => !n.hidden && !n.name.startsWith("_xlnm.")).map((n) => [`name:${n.name}`, n.name]);
5715
5716
  const loc = existing?.location ? splitLocation(existing.location) : { sheet: t.ws.name, ref: "A1" };
5716
- const isName = !loc.sheet && names.some(([, n]) => n === loc.ref);
5717
+ const isName = !loc.sheet && names2.some(([, n]) => n === loc.ref);
5717
5718
  const place2 = select(
5718
5719
  ctx,
5719
- [...sheets, ...names],
5720
+ [...sheets, ...names2],
5720
5721
  isName ? `name:${loc.ref}` : loc.sheet ?? t.ws.name,
5721
5722
  true
5722
5723
  );
@@ -6166,14 +6167,14 @@ function registerSimpleDialogs(ctx) {
6166
6167
  d.register("use-in-formula", (c) => {
6167
6168
  const t = target(c);
6168
6169
  if (!t) return Promise.resolve(void 0);
6169
- const names = t.workbook.definedNames.filter(
6170
+ const names2 = t.workbook.definedNames.filter(
6170
6171
  (n) => !n.hidden && !n.name.startsWith("_xlnm.") && (n.localSheet === void 0 || n.localSheet === t.sheet)
6171
6172
  );
6172
6173
  return pickDialog(c, {
6173
6174
  name: "use-in-formula",
6174
6175
  heading: "Paste Name",
6175
6176
  label: "Paste name",
6176
- items: names.map((n) => [n.name, n.name]),
6177
+ items: names2.map((n) => [n.name, n.name]),
6177
6178
  apply: (name) => {
6178
6179
  const grid = c.grid();
6179
6180
  const cell = getCell(t.ws, t.active.row, t.active.col);
@@ -7988,7 +7989,10 @@ function chartDesignTab() {
7988
7989
  {
7989
7990
  id: "series-options",
7990
7991
  label: "Series Options",
7991
- controls: [{ kind: "button", command: "chart.format-series", size: "large" }]
7992
+ controls: [
7993
+ { kind: "button", command: "chart.format-series", size: "large" },
7994
+ { kind: "button", command: "chart.format-area", size: "large" }
7995
+ ]
7992
7996
  },
7993
7997
  {
7994
7998
  id: "chart-delete",
@@ -8731,44 +8735,122 @@ function reviewCommands() {
8731
8735
  })
8732
8736
  ];
8733
8737
  }
8734
- function createSeriesTransparency(ctx, selected) {
8735
- const element = el(ctx, "div");
8736
- const input = numberInput(ctx, 0, 0, 100);
8737
- const row2 = field(ctx, "Transparency", input);
8738
- const unit = el(ctx, "span");
8739
- unit.textContent = "%";
8740
- row2.append(unit);
8741
- const host = el(ctx, "div", "xve-chart-series-range");
8742
- element.append(row2, host);
8743
- let current;
8738
+ function seriesFillBinding(selected) {
8739
+ return {
8740
+ commandId: "chart.format-series",
8741
+ key: selected,
8742
+ fill: (chart) => chart.series[selected()]?.fill,
8743
+ gradient: (view) => view.series[selected()]?.gradient,
8744
+ paint: (view) => view.series[selected()]?.color,
8745
+ rectangular: (chart) => chart.chartType === "bar" || chart.chartType === "column",
8746
+ color: (chart, color) => chartSeriesFillPatch(chart, selected(), color),
8747
+ solid: (chart) => chartSeriesSolidFillPatch(chart, selected()),
8748
+ edit: (chart, edit) => chartSeriesGradientPatch(chart, selected(), edit),
8749
+ transparency: (chart) => chartSeriesTransparency(chart, selected()),
8750
+ setTransparency: (chart, value) => chartSeriesTransparencyPatch(chart, selected(), value),
8751
+ preview: (chart, patch) => ({ ...chart, ...patch })
8752
+ };
8753
+ }
8754
+ function backgroundFillBinding(selected) {
8755
+ return {
8756
+ commandId: "chart.format-area",
8757
+ key: selected,
8758
+ fill: (chart) => chartElementFill(chart, selected()),
8759
+ gradient: (view) => view.appearance?.[selected()]?.gradient,
8760
+ paint: (view) => view.appearance?.[selected()]?.fillColor ?? (selected() === "chartArea" ? "#FFFFFF" : "none"),
8761
+ rectangular: () => true,
8762
+ color: (chart, color) => chartElementFillPatch(chart, selected(), color),
8763
+ solid: (chart) => chartElementSolidFillPatch(chart, selected()),
8764
+ edit: (chart, edit) => chartElementGradientPatch(chart, selected(), edit),
8765
+ transparency: (chart) => chartElementTransparency(chart, selected()),
8766
+ setTransparency: (chart, value) => chartElementTransparencyPatch(chart, selected(), value),
8767
+ preview: (chart, patch) => chartWithElementFills(chart, patch.elementFills ?? {})
8768
+ };
8769
+ }
8770
+ function createNumberRange(input, options) {
8771
+ const element = input.ownerDocument.createElement("div");
8772
+ let original;
8773
+ const enabled = () => !input.disabled && (options.enabled?.() ?? true);
8774
+ const cancel = () => {
8775
+ if (original === void 0) return;
8776
+ input.value = original;
8777
+ original = void 0;
8778
+ options.onPreview?.(void 0);
8779
+ paint();
8780
+ };
8744
8781
  const paint = () => render(
8745
8782
  rangeControl({
8746
- label: ctx.t("Transparency"),
8783
+ label: options.label(),
8747
8784
  value: input.valueAsNumber,
8748
- valueText: `${input.value}%`,
8749
- min: 0,
8750
- max: 100,
8751
- disabled: input.disabled,
8785
+ valueText: input.value ? `${input.value}%` : "",
8786
+ min: Number(input.min),
8787
+ max: Number(input.max),
8788
+ step: Number(input.step) || 1,
8789
+ disabled: !enabled(),
8752
8790
  onInput: (value) => {
8753
- if (!ctx.commands.isEnabled("chart.format-series")) return;
8791
+ if (!enabled() || !Number.isFinite(value)) return;
8792
+ original ??= input.value;
8754
8793
  input.value = String(value);
8794
+ options.onPreview?.(value);
8755
8795
  paint();
8756
8796
  },
8757
- onChange: () => input.dispatchEvent(new Event("change"))
8797
+ onChange: () => {
8798
+ if (original === void 0) return;
8799
+ if (!enabled()) return cancel();
8800
+ const value = input.value;
8801
+ cancel();
8802
+ input.value = value;
8803
+ input.dispatchEvent(new Event("change"));
8804
+ paint();
8805
+ }
8758
8806
  }),
8759
- host
8807
+ element
8760
8808
  );
8809
+ element.addEventListener("pointercancel", cancel);
8810
+ element.addEventListener("keydown", (event) => {
8811
+ if (event.key === "Escape" && original !== void 0) {
8812
+ event.preventDefault();
8813
+ event.stopPropagation();
8814
+ cancel();
8815
+ }
8816
+ });
8817
+ return {
8818
+ element,
8819
+ cancel,
8820
+ refresh: () => {
8821
+ cancel();
8822
+ paint();
8823
+ }
8824
+ };
8825
+ }
8826
+
8827
+ // src/xlsx/chart-series-transparency.ts
8828
+ function createSeriesTransparency(ctx, selected, binding = seriesFillBinding(selected)) {
8829
+ const element = el(ctx, "div");
8830
+ const input = numberInput(ctx, 0, 0, 100);
8831
+ const row2 = field(ctx, "Transparency", input);
8832
+ const unit = el(ctx, "span");
8833
+ unit.textContent = "%";
8834
+ row2.append(unit);
8835
+ const range2 = createNumberRange(input, {
8836
+ label: () => ctx.t("Transparency"),
8837
+ enabled: () => ctx.commands.isEnabled(binding.commandId)
8838
+ });
8839
+ range2.element.className = "xve-chart-series-range";
8840
+ element.append(row2, range2.element);
8841
+ let current;
8761
8842
  const refresh = (chart) => {
8843
+ range2.cancel();
8762
8844
  current = chart;
8763
- const value = chart ? chartSeriesTransparency(chart, selected()) : void 0;
8845
+ const value = chart ? binding.transparency(chart) : void 0;
8764
8846
  input.value = String(value ?? 0);
8765
- input.disabled = value === void 0 || !ctx.commands.isEnabled("chart.format-series");
8847
+ input.disabled = value === void 0 || !ctx.commands.isEnabled(binding.commandId);
8766
8848
  row2.querySelector("span").textContent = ctx.t("Transparency");
8767
8849
  input.setAttribute("aria-label", ctx.t("Transparency"));
8768
- paint();
8850
+ range2.refresh();
8769
8851
  };
8770
8852
  input.addEventListener("change", () => {
8771
- if (!current || input.disabled || !ctx.commands.isEnabled("chart.format-series"))
8853
+ if (!current || input.disabled || !ctx.commands.isEnabled(binding.commandId))
8772
8854
  return refresh(current);
8773
8855
  if (!input.checkValidity() || !Number.isInteger(input.valueAsNumber)) {
8774
8856
  ctx.toast(
@@ -8779,15 +8861,656 @@ function createSeriesTransparency(ctx, selected) {
8779
8861
  }
8780
8862
  const found = activeChart(ctx);
8781
8863
  if (!found || found.chart !== current) return refresh(current);
8782
- const patch = chartSeriesTransparencyPatch(current, selected(), input.valueAsNumber);
8864
+ const patch = binding.setTransparency(current, input.valueAsNumber);
8783
8865
  if (patch) ctx.session()?.updateChart(ctx.activeSheet(), found.index, patch);
8784
8866
  refresh(found.chart);
8785
8867
  });
8786
8868
  return { element, refresh };
8787
8869
  }
8870
+ function createGradientStopTrack(doc) {
8871
+ const element = doc.createElement("div");
8872
+ element.className = "office-gradient-stop-track";
8873
+ element.setAttribute("role", "group");
8874
+ const style = doc.createElement("style");
8875
+ style.textContent = `.office-gradient-stop-track{position:relative;height:40px;margin:12px 7px}
8876
+ .office-gradient-stop-paint{position:absolute;inset:3px 0 15px;border:1px solid var(--line,#aaa)}
8877
+ .office-gradient-stop{position:absolute;top:0;width:14px;height:30px;padding:0;border:1px solid var(--line,#888);border-radius:2px;cursor:pointer;touch-action:none}
8878
+ .office-gradient-stop[aria-pressed=true]{outline:2px solid var(--accent,#217346);outline-offset:2px}
8879
+ .office-gradient-stop:focus-visible{outline:2px solid var(--accent,#217346);outline-offset:3px}
8880
+ .office-gradient-stop:disabled{opacity:.5;cursor:default}`;
8881
+ const paint = doc.createElement("div");
8882
+ paint.className = "office-gradient-stop-paint";
8883
+ let current;
8884
+ let drag;
8885
+ const paintStops = (stops) => {
8886
+ paint.style.background = `linear-gradient(90deg,${sortGradientStops(stops).map((stop) => `${stop.color} ${stop.position}%`).join(",")})`;
8887
+ };
8888
+ const cancel = () => {
8889
+ if (!drag) return;
8890
+ const previous = drag;
8891
+ drag = void 0;
8892
+ paintStops(current.stops);
8893
+ element.querySelectorAll("button")[previous.index]?.style.setProperty("left", `calc(${previous.origin}% - 7px)`);
8894
+ current.onPreview?.(previous.index, void 0);
8895
+ if (element.hasPointerCapture?.(previous.pointer))
8896
+ element.releasePointerCapture(previous.pointer);
8897
+ };
8898
+ const update = (options) => {
8899
+ cancel();
8900
+ current = options;
8901
+ element.setAttribute("aria-label", options.label);
8902
+ paintStops(options.stops);
8903
+ element.replaceChildren(
8904
+ style,
8905
+ paint,
8906
+ ...options.stops.map((stop, index) => {
8907
+ const button2 = doc.createElement("button");
8908
+ button2.type = "button";
8909
+ button2.className = "office-gradient-stop";
8910
+ button2.disabled = options.disabled;
8911
+ button2.setAttribute("aria-label", options.stopLabel(index));
8912
+ button2.setAttribute("aria-pressed", String(index === options.selected));
8913
+ button2.style.left = `calc(${stop.position}% - 7px)`;
8914
+ button2.style.background = stop.color;
8915
+ button2.addEventListener("click", () => {
8916
+ if (!current.disabled) current.onSelect(index);
8917
+ });
8918
+ button2.addEventListener("keydown", (event) => {
8919
+ const next = event.key === "ArrowRight" ? index + 1 : event.key === "ArrowLeft" ? index - 1 : void 0;
8920
+ if (next === void 0 || current.disabled) return;
8921
+ event.preventDefault();
8922
+ const selected = Math.max(0, Math.min(current.stops.length - 1, next));
8923
+ current.onSelect(selected);
8924
+ element.querySelectorAll("button")[selected]?.focus();
8925
+ });
8926
+ return button2;
8927
+ })
8928
+ );
8929
+ };
8930
+ element.addEventListener("pointerdown", (event) => {
8931
+ if (drag || !current || event.isPrimary === false || event.button !== 0 || current.disabled || !current.onMove)
8932
+ return;
8933
+ const button2 = event.target.closest("button");
8934
+ const index = Array.from(element.querySelectorAll("button")).indexOf(
8935
+ button2
8936
+ );
8937
+ if (index < 0) return;
8938
+ event.preventDefault();
8939
+ current.onSelect(index);
8940
+ const width = paint.getBoundingClientRect().width;
8941
+ const origin = current.stops[index]?.position;
8942
+ if (!width || origin === void 0) return;
8943
+ drag = { pointer: event.pointerId, index, x: event.clientX, width, origin, value: origin };
8944
+ try {
8945
+ element.setPointerCapture?.(event.pointerId);
8946
+ } catch {
8947
+ }
8948
+ element.querySelectorAll("button")[index]?.focus();
8949
+ });
8950
+ element.addEventListener("pointermove", (event) => {
8951
+ if (!drag || event.pointerId !== drag.pointer || current.disabled) return;
8952
+ const value = Math.max(
8953
+ 0,
8954
+ Math.min(100, Math.round(drag.origin + (event.clientX - drag.x) / drag.width * 100))
8955
+ );
8956
+ if (value === drag.value) return;
8957
+ drag.value = value;
8958
+ element.querySelectorAll("button")[drag.index]?.style.setProperty("left", `calc(${value}% - 7px)`);
8959
+ paintStops(
8960
+ current.stops.map(
8961
+ (stop, index) => index === drag.index ? { ...stop, position: value } : stop
8962
+ )
8963
+ );
8964
+ current.onPreview?.(drag.index, value);
8965
+ });
8966
+ element.addEventListener("pointerup", (event) => {
8967
+ if (!drag || event.pointerId !== drag.pointer) return;
8968
+ const previous = drag;
8969
+ cancel();
8970
+ if (!current.disabled && element.isConnected && previous.value !== previous.origin)
8971
+ current.onMove?.(previous.index, previous.value);
8972
+ });
8973
+ for (const name of ["pointercancel", "lostpointercapture"])
8974
+ element.addEventListener(name, (event) => {
8975
+ if (drag?.pointer === event.pointerId) cancel();
8976
+ });
8977
+ element.addEventListener("keydown", (event) => {
8978
+ if (event.key === "Escape" && drag) {
8979
+ event.preventDefault();
8980
+ event.stopPropagation();
8981
+ cancel();
8982
+ }
8983
+ });
8984
+ return {
8985
+ element,
8986
+ update,
8987
+ cancel,
8988
+ preview: (stops) => {
8989
+ paintStops(stops);
8990
+ const buttons = element.querySelectorAll("button");
8991
+ stops.forEach((stop, index) => {
8992
+ const button2 = buttons[index];
8993
+ if (button2) {
8994
+ button2.style.left = `calc(${stop.position}% - 7px)`;
8995
+ button2.style.background = stop.color;
8996
+ }
8997
+ });
8998
+ }
8999
+ };
9000
+ }
9001
+ function gradientGalleryPreview(id, fill) {
9002
+ const def = buildChartGradientDef(id, fill, { width: 38, height: 38, shape: "rect" });
9003
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs>${chartGradientMarkup(def)}</defs><rect x="1" y="1" width="38" height="38" fill="url(#${def.id})" stroke="#999"/></svg>`;
9004
+ }
9005
+
9006
+ // src/form/gradient-direction-gallery.ts
9007
+ var DIRECTIONS2 = [
9008
+ [0, "Linear Right"],
9009
+ [45, "Linear Diagonal - Bottom Right"],
9010
+ [90, "Linear Down"],
9011
+ [135, "Linear Diagonal - Bottom Left"],
9012
+ [180, "Linear Left"],
9013
+ [225, "Linear Diagonal - Top Left"],
9014
+ [270, "Linear Up"],
9015
+ [315, "Linear Diagonal - Top Right"]
9016
+ ];
9017
+ var sequence = 0;
9018
+ function createGradientDirectionGallery(doc) {
9019
+ const prefix = `office-gradient-direction-${++sequence}`;
9020
+ defineGallery(doc.defaultView?.customElements);
9021
+ const element = doc.createElement("office-ui-gallery");
9022
+ let current;
9023
+ element.addEventListener("office-gallery-pick", (event) => {
9024
+ const id = event.detail.itemId;
9025
+ if (["rect", "circle", "shape"].includes(current?.gradient.path ?? "")) {
9026
+ const direction2 = RECT_GRADIENT_DIRECTIONS.find((direction3) => direction3.id === id);
9027
+ if (direction2 && current && !current.disabled) {
9028
+ if (current.onPathPick) current.onPathPick(direction2.id);
9029
+ else if (current.gradient.path === "rect") current.onRectPick?.(direction2.id);
9030
+ }
9031
+ return;
9032
+ }
9033
+ const direction = DIRECTIONS2.find(([angle]) => String(angle) === id);
9034
+ if (direction && current && !current.disabled) current.onPick(direction[0]);
9035
+ });
9036
+ const update = (options) => {
9037
+ current = options;
9038
+ if (options.disabled) element.open = false;
9039
+ const rectangular = ["rect", "circle", "shape"].includes(options.gradient.path ?? "");
9040
+ const items2 = rectangular ? RECT_GRADIENT_DIRECTIONS.map(({ id, label }) => ({
9041
+ id,
9042
+ label: options.translate(label),
9043
+ applied: rectGradientDirection(options.gradient.fillToRect) === id,
9044
+ preview: gradientGalleryPreview(
9045
+ `${prefix}-${id}`,
9046
+ withChartGradientDirection(options.gradient, id)
9047
+ )
9048
+ })) : DIRECTIONS2.map(([angle, label]) => ({
9049
+ id: String(angle),
9050
+ label: options.translate(label),
9051
+ applied: options.gradient.angle === angle,
9052
+ preview: gradientGalleryPreview(`${prefix}-${angle}`, {
9053
+ ...options.gradient,
9054
+ type: "linear",
9055
+ angle
9056
+ })
9057
+ }));
9058
+ element.state = {
9059
+ id: "gradient-direction",
9060
+ label: options.label,
9061
+ moreLabel: options.label,
9062
+ disabled: options.disabled,
9063
+ sections: [
9064
+ {
9065
+ columns: 4,
9066
+ tileWidth: 44,
9067
+ tileHeight: 44,
9068
+ items: items2
9069
+ }
9070
+ ]
9071
+ };
9072
+ };
9073
+ return {
9074
+ element,
9075
+ update,
9076
+ close: () => {
9077
+ element.open = false;
9078
+ }
9079
+ };
9080
+ }
9081
+ var sequence2 = 0;
9082
+ function createGradientPresetGallery(doc) {
9083
+ const prefix = `office-gradient-preset-${++sequence2}`;
9084
+ defineGallery(doc.defaultView?.customElements);
9085
+ const element = doc.createElement("office-ui-gallery");
9086
+ let current;
9087
+ element.addEventListener("office-gallery-pick", (event) => {
9088
+ const id = event.detail.itemId;
9089
+ const preset = OFFICE_GRADIENT_PRESETS.find((preset2) => String(preset2.id) === id);
9090
+ if (preset && current && !current.disabled) current.onPick(preset.id);
9091
+ });
9092
+ return {
9093
+ element,
9094
+ close: () => {
9095
+ element.open = false;
9096
+ },
9097
+ update(options) {
9098
+ current = options;
9099
+ if (options.disabled) element.open = false;
9100
+ const selected = officeGradientPresetId(options.fill);
9101
+ element.state = {
9102
+ id: "gradient-presets",
9103
+ label: options.label,
9104
+ moreLabel: options.label,
9105
+ disabled: options.disabled,
9106
+ sections: [
9107
+ {
9108
+ columns: 6,
9109
+ tileWidth: 40,
9110
+ tileHeight: 40,
9111
+ items: OFFICE_GRADIENT_PRESETS.map((preset) => ({
9112
+ id: String(preset.id),
9113
+ label: options.translate(preset.label),
9114
+ applied: preset.id === selected,
9115
+ preview: gradientGalleryPreview(
9116
+ `${prefix}-${preset.id}`,
9117
+ resolveChartGradient(
9118
+ officeGradientPresetFill(preset.id),
9119
+ (color) => resolveDrawingColor(color)
9120
+ )
9121
+ )
9122
+ }))
9123
+ }
9124
+ ]
9125
+ };
9126
+ }
9127
+ };
9128
+ }
9129
+
9130
+ // src/xlsx/chart-series-gradient-type.ts
9131
+ function createGradientTypeField(ctx, onPick) {
9132
+ const input = select(ctx, [
9133
+ ["linear", "Linear"],
9134
+ ["circle", "Radial"],
9135
+ ["rect", "Rectangular"],
9136
+ ["shape", "Path"]
9137
+ ]);
9138
+ input.options[1].disabled = true;
9139
+ input.options[3].disabled = true;
9140
+ const other = el(ctx, "option");
9141
+ other.value = "unsupported";
9142
+ other.disabled = true;
9143
+ input.append(other);
9144
+ const element = field(ctx, "Type", input);
9145
+ input.addEventListener("change", () => {
9146
+ if (!input.disabled && !input.selectedOptions[0]?.disabled && ["linear", "rect", "circle", "shape"].includes(input.value))
9147
+ onPick(input.value);
9148
+ });
9149
+ return {
9150
+ element,
9151
+ refresh(fill, disabled, rectangularMarks = false) {
9152
+ input.options[1].disabled = input.options[3].disabled = !rectangularMarks;
9153
+ element.querySelector("span").textContent = ctx.t("Type");
9154
+ input.setAttribute("aria-label", ctx.t("Type"));
9155
+ const linearLabel = ctx.t("Linear gradient");
9156
+ input.options[0].textContent = linearLabel === "Linear gradient" ? "Linear" : linearLabel;
9157
+ input.options[1].textContent = ctx.t("Radial");
9158
+ input.options[2].textContent = ctx.t("Rectangular");
9159
+ input.options[3].textContent = ctx.t("Path");
9160
+ other.textContent = ctx.t("Imported gradient type");
9161
+ input.value = !fill.path ? "linear" : ["rect", "circle", "shape"].includes(fill.path) ? fill.path : "unsupported";
9162
+ other.hidden = input.value !== "unsupported";
9163
+ input.disabled = disabled;
9164
+ }
9165
+ };
9166
+ }
9167
+ function createChartGradientPreview(root, drawing, series, gradient) {
9168
+ const target24 = typeof series === "number" ? `s${series}` : series;
9169
+ const nodes = Array.from(
9170
+ root.querySelectorAll(
9171
+ `.xg-obj[data-index="${drawing}"] :is(linearGradient,radialGradient,pattern)[id$="-${target24}"]`
9172
+ )
9173
+ );
9174
+ const originals = nodes.map((node) => ({
9175
+ node,
9176
+ children: Array.from(node.childNodes).map((child) => child.cloneNode(true))
9177
+ }));
9178
+ const paint = (fill) => {
9179
+ const bounds = nodes.some((node) => node.localName === "pattern") ? { width: 1, height: 1, shape: "rect" } : void 0;
9180
+ const def = buildChartGradientDef("preview", fill, bounds);
9181
+ for (const node of nodes) {
9182
+ if (!node.isConnected) continue;
9183
+ if (def.kind === "rectPath") {
9184
+ const image = node.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "image");
9185
+ image.setAttribute("href", def.href);
9186
+ image.setAttribute("width", "1");
9187
+ image.setAttribute("height", "1");
9188
+ image.setAttribute("preserveAspectRatio", "none");
9189
+ node.replaceChildren(image);
9190
+ continue;
9191
+ }
9192
+ node.replaceChildren(
9193
+ ...def.stops.map((stop) => {
9194
+ const element = node.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "stop");
9195
+ element.setAttribute("offset", String(stop.offset));
9196
+ element.setAttribute("stop-color", stop.color);
9197
+ element.setAttribute("stop-opacity", String(stop.opacity ?? 1));
9198
+ return element;
9199
+ })
9200
+ );
9201
+ }
9202
+ };
9203
+ return {
9204
+ paint,
9205
+ position(index, position) {
9206
+ paint({
9207
+ ...gradient,
9208
+ stops: gradient.stops.map((stop, i) => i === index ? { ...stop, position } : stop)
9209
+ });
9210
+ },
9211
+ restore() {
9212
+ for (const { node, children } of originals)
9213
+ if (node.isConnected) node.replaceChildren(...children);
9214
+ }
9215
+ };
9216
+ }
9217
+ function seriesRangePreview(ctx, chart, view, series, stop, drawing, track, color, valid, binding) {
9218
+ const gradient = binding.gradient(view);
9219
+ const book = ctx.workbook();
9220
+ const sheet = ctx.activeSheet();
9221
+ let preview;
9222
+ const strip = (fill) => {
9223
+ track.preview(
9224
+ fill.stops.map((item) => ({
9225
+ position: item.position,
9226
+ color: drawingColorCss({ hex: item.color, alpha: item.opacity ?? 1, unapplied: [] })
9227
+ }))
9228
+ );
9229
+ color.style.setProperty("--series-fill", fill.stops[stop]?.color ?? "transparent");
9230
+ };
9231
+ return (property, value) => {
9232
+ if (value === void 0) {
9233
+ preview?.restore();
9234
+ preview = void 0;
9235
+ if (valid() && gradient) strip(gradient);
9236
+ return;
9237
+ }
9238
+ if (!valid() || !book || !gradient || ctx.workbook() !== book || ctx.activeSheet() !== sheet || !ctx.commands.isEnabled(binding.commandId))
9239
+ return;
9240
+ const result = binding.edit(chart, {
9241
+ kind: "stop",
9242
+ index: stop,
9243
+ [property]: value
9244
+ });
9245
+ const paint = result ? binding.gradient(
9246
+ chartView(
9247
+ book,
9248
+ sheet,
9249
+ binding.preview(chart, result.patch),
9250
+ createRefEvaluator(book, ctx.session()?.calc, { sheet })
9251
+ )
9252
+ ) : gradient;
9253
+ if (!paint) return;
9254
+ preview ??= createChartGradientPreview(ctx.root, drawing, series, gradient);
9255
+ preview.paint(paint);
9256
+ strip(paint);
9257
+ };
9258
+ }
9259
+
9260
+ // src/xlsx/chart-series-gradient.ts
9261
+ function createSeriesGradient(ctx, selected, binding = seriesFillBinding(selected)) {
9262
+ const element = el(ctx, "div", "xve-chart-series-gradient");
9263
+ const angle = numberInput(ctx, 90, 0, 360);
9264
+ const position = numberInput(ctx, 0, 0, 100);
9265
+ const transparency = numberInput(ctx, 0, 0, 100);
9266
+ const brightness = numberInput(ctx, 0, -100, 100);
9267
+ const color = el(ctx, "button", "xve-input xve-chart-series-color");
9268
+ color.type = "button";
9269
+ const track = createGradientStopTrack(element.ownerDocument);
9270
+ const direction = createGradientDirectionGallery(element.ownerDocument);
9271
+ const preset = createGradientPresetGallery(element.ownerDocument);
9272
+ const presetRow = field(ctx, "Preset gradients", preset.element);
9273
+ const type = createGradientTypeField(ctx, (type2) => {
9274
+ direction.close();
9275
+ apply({ kind: "geometry", type: type2 });
9276
+ });
9277
+ const rows = [
9278
+ [field(ctx, "Angle", angle), angle, "Angle"],
9279
+ [field(ctx, "Color", color), color, "Color"],
9280
+ [field(ctx, "Position", position), position, "Position"],
9281
+ [field(ctx, "Brightness", brightness), brightness, "Brightness"],
9282
+ [field(ctx, "Transparency", transparency), transparency, "Transparency"]
9283
+ ];
9284
+ for (const [row2, , label] of rows.filter(([, input]) => input !== color)) {
9285
+ const unit = el(ctx, "span");
9286
+ unit.textContent = label === "Angle" ? "\xB0" : "%";
9287
+ row2.append(unit);
9288
+ }
9289
+ const buttons = el(ctx, "div", "xve-chart-gradient-buttons");
9290
+ const selector = el(ctx, "div", "xve-chart-gradient-selector");
9291
+ const add = el(ctx, "button", "xve-button");
9292
+ add.type = "button";
9293
+ const remove = el(ctx, "button", "xve-button");
9294
+ remove.type = "button";
9295
+ buttons.append(add, remove);
9296
+ selector.append(track.element, buttons);
9297
+ const directionRow = field(ctx, "Direction", direction.element);
9298
+ let previewRange;
9299
+ const ranges = /* @__PURE__ */ new Map();
9300
+ for (const [property, input] of [
9301
+ ["position", position],
9302
+ ["brightness", brightness],
9303
+ ["transparency", transparency]
9304
+ ]) {
9305
+ const range2 = createNumberRange(input, {
9306
+ label: () => input.getAttribute("aria-label") ?? "",
9307
+ enabled: () => {
9308
+ const fill = current && binding.fill(current);
9309
+ return ctx.commands.isEnabled(binding.commandId) && fill?.kind === "gradient" && fill.stops.length === (model && binding.gradient(model)?.stops.length);
9310
+ },
9311
+ onPreview: (value) => {
9312
+ if (value !== void 0) {
9313
+ for (const other of ranges.values()) if (other !== range2) other.cancel();
9314
+ }
9315
+ previewRange?.(property, value);
9316
+ }
9317
+ });
9318
+ range2.element.className = "xve-chart-series-range";
9319
+ ranges.set(input, range2);
9320
+ }
9321
+ element.append(
9322
+ presetRow,
9323
+ type.element,
9324
+ directionRow,
9325
+ rows[0][0],
9326
+ selector,
9327
+ ...rows.slice(1).flatMap(([row2, input]) => {
9328
+ const range2 = ranges.get(input);
9329
+ return range2 ? [row2, range2.element] : [row2];
9330
+ })
9331
+ );
9332
+ let current;
9333
+ let model;
9334
+ let stopIndex = 0;
9335
+ let seriesIndex = -1;
9336
+ let drawingIndex = -1;
9337
+ let shownBook = ctx.workbook();
9338
+ let shownSheet = -1;
9339
+ let shownPath;
9340
+ const apply = (edit) => {
9341
+ if (!current || !ctx.commands.isEnabled(binding.commandId)) return;
9342
+ const found = activeChart(ctx);
9343
+ if (!found || found.chart !== current) return;
9344
+ const result = binding.edit(current, edit);
9345
+ if (!result) return;
9346
+ if (edit.kind !== "angle" && edit.kind !== "geometry") stopIndex = result.stopIndex;
9347
+ ctx.session()?.updateChart(ctx.activeSheet(), found.index, result.patch);
9348
+ };
9349
+ const refresh = (chart, view) => {
9350
+ track.cancel();
9351
+ for (const range2 of ranges.values()) range2.cancel();
9352
+ previewRange = void 0;
9353
+ const nextDrawing = activeChart(ctx)?.index ?? -1;
9354
+ if (seriesIndex !== binding.key() || drawingIndex !== nextDrawing || shownBook !== ctx.workbook() || shownSheet !== ctx.activeSheet()) {
9355
+ stopIndex = 0;
9356
+ direction.close();
9357
+ preset.close();
9358
+ }
9359
+ shownBook = ctx.workbook();
9360
+ shownSheet = ctx.activeSheet();
9361
+ seriesIndex = binding.key();
9362
+ drawingIndex = nextDrawing;
9363
+ current = chart;
9364
+ model = view;
9365
+ const fill = chart && binding.fill(chart);
9366
+ element.hidden = fill?.kind !== "gradient";
9367
+ if (fill?.kind !== "gradient") {
9368
+ direction.close();
9369
+ preset.close();
9370
+ return;
9371
+ }
9372
+ stopIndex = Math.max(0, Math.min(stopIndex, fill.stops.length - 1));
9373
+ const disabled = !ctx.commands.isEnabled(binding.commandId);
9374
+ presetRow.querySelector("span").textContent = ctx.t("Preset gradients");
9375
+ preset.update({
9376
+ fill,
9377
+ disabled,
9378
+ label: ctx.t("Preset gradients"),
9379
+ translate: ctx.t,
9380
+ onPick: (id) => apply({ kind: "preset", id })
9381
+ });
9382
+ const rectangularMarks = !!chart && binding.rectangular(chart);
9383
+ type.refresh(fill, disabled, rectangularMarks);
9384
+ if (shownPath !== fill.path) direction.close();
9385
+ shownPath = fill.path;
9386
+ angle.value = String(fill.angle ?? 90);
9387
+ angle.disabled = disabled || !!fill.path;
9388
+ position.value = String(fill.stops[stopIndex]?.position ?? 0);
9389
+ transparency.value = String(chartGradientStopTransparency(fill, stopIndex));
9390
+ const stopColor = fill.stops[stopIndex]?.color;
9391
+ const stopBrightness = stopColor && drawingColorBrightness(stopColor);
9392
+ brightness.value = stopBrightness === void 0 ? "" : String(stopBrightness);
9393
+ position.disabled = transparency.disabled = color.disabled = brightness.disabled = disabled || !fill.stops.length;
9394
+ const stops = (view && binding.gradient(view)?.stops) ?? [];
9395
+ directionRow.querySelector("span").textContent = ctx.t("Direction");
9396
+ direction.update({
9397
+ gradient: (view && binding.gradient(view)) ?? { type: "linear", stops: [] },
9398
+ disabled: disabled || !!fill.path && fill.path !== "rect" && !(rectangularMarks && ["circle", "shape"].includes(fill.path)),
9399
+ label: ctx.t("Direction"),
9400
+ translate: ctx.t,
9401
+ onPick: (value) => apply({ kind: "angle", value }),
9402
+ onRectPick: (direction2) => apply({ kind: "geometry", type: "rect", direction: direction2 }),
9403
+ onPathPick: (direction2) => {
9404
+ if (fill.path === "rect" || fill.path === "circle" || fill.path === "shape")
9405
+ apply({ kind: "geometry", type: fill.path, direction: direction2 });
9406
+ }
9407
+ });
9408
+ brightness.disabled ||= stopBrightness === void 0;
9409
+ const previewSeries = binding.key();
9410
+ const previewStop = stopIndex;
9411
+ if (chart && view)
9412
+ previewRange = seriesRangePreview(
9413
+ ctx,
9414
+ chart,
9415
+ view,
9416
+ previewSeries,
9417
+ previewStop,
9418
+ drawingIndex,
9419
+ track,
9420
+ color,
9421
+ () => current === chart && binding.key() === previewSeries && stopIndex === previewStop && binding.fill(chart) === fill,
9422
+ binding
9423
+ );
9424
+ color.style.setProperty("--series-fill", stops[stopIndex]?.color ?? "transparent");
9425
+ let preview;
9426
+ const gradient = view && binding.gradient(view);
9427
+ track.update({
9428
+ stops: stops.map((stop) => ({
9429
+ position: stop.position,
9430
+ color: drawingColorCss({ hex: stop.color, alpha: stop.opacity ?? 1, unapplied: [] })
9431
+ })),
9432
+ selected: stopIndex,
9433
+ disabled: disabled || stops.length !== fill.stops.length,
9434
+ label: ctx.t("Gradient stops"),
9435
+ stopLabel: (index) => ctx.t("Gradient stop {index}", { index: index + 1 }),
9436
+ onSelect: (index) => {
9437
+ stopIndex = index;
9438
+ refresh(current, model);
9439
+ },
9440
+ onMove: (index, position2) => apply({ kind: "stop", index, position: position2 }),
9441
+ onPreview: (index, value) => {
9442
+ if (value === void 0) {
9443
+ preview?.restore();
9444
+ preview = void 0;
9445
+ }
9446
+ if (current !== chart || chart && binding.fill(chart) !== fill) return;
9447
+ position.value = String(value ?? fill.stops[index]?.position ?? 0);
9448
+ if (value !== void 0 && gradient) {
9449
+ preview ??= createChartGradientPreview(ctx.root, drawingIndex, binding.key(), gradient);
9450
+ preview.position(index, value);
9451
+ }
9452
+ }
9453
+ });
9454
+ add.disabled = disabled || !fill.stops.length;
9455
+ remove.disabled = disabled || fill.stops.length <= 2;
9456
+ add.textContent = "+";
9457
+ remove.textContent = "\u2212";
9458
+ add.setAttribute("aria-label", ctx.t("Add gradient stop"));
9459
+ remove.setAttribute("aria-label", ctx.t("Remove gradient stop"));
9460
+ for (const [row2, input, label] of rows) {
9461
+ row2.querySelector("span").textContent = ctx.t(label);
9462
+ input.setAttribute("aria-label", ctx.t(label));
9463
+ }
9464
+ for (const range2 of ranges.values()) range2.refresh();
9465
+ };
9466
+ for (const [input, property] of [
9467
+ [angle, "angle"],
9468
+ [position, "position"],
9469
+ [transparency, "transparency"],
9470
+ [brightness, "brightness"]
9471
+ ]) {
9472
+ input.addEventListener("change", () => {
9473
+ if (input.disabled || !ctx.commands.isEnabled(binding.commandId))
9474
+ return refresh(current, model);
9475
+ const value = input.valueAsNumber;
9476
+ if (!Number.isFinite(value) || !input.checkValidity()) {
9477
+ ctx.toast(
9478
+ ctx.t("Enter a whole number from {min} to {max}.", { min: input.min, max: input.max }),
9479
+ "warning"
9480
+ );
9481
+ return refresh(current, model);
9482
+ }
9483
+ apply(
9484
+ property === "angle" ? { kind: "angle", value } : { kind: "stop", index: stopIndex, [property]: value }
9485
+ );
9486
+ });
9487
+ }
9488
+ add.addEventListener("click", () => {
9489
+ if (!add.disabled) apply({ kind: "add", index: stopIndex });
9490
+ });
9491
+ remove.addEventListener("click", () => {
9492
+ if (!remove.disabled) apply({ kind: "remove", index: stopIndex });
9493
+ });
9494
+ color.addEventListener("click", () => {
9495
+ if (color.disabled || !ctx.commands.isEnabled(binding.commandId)) return;
9496
+ const chart = current;
9497
+ const series = binding.key();
9498
+ const index = stopIndex;
9499
+ const book = ctx.workbook();
9500
+ openColorGrid(
9501
+ color,
9502
+ (choice) => {
9503
+ if (choice && current === chart && binding.key() === series && stopIndex === index && ctx.workbook() === book)
9504
+ apply({ kind: "stop", index, color: choice });
9505
+ },
9506
+ { t: ctx.t, ...book ? { theme: book.theme } : {} }
9507
+ );
9508
+ });
9509
+ return { element, refresh, create: () => apply({ kind: "create" }) };
9510
+ }
8788
9511
 
8789
9512
  // src/xlsx/chart-series-fill.ts
8790
- function createSeriesFill(ctx, selected) {
9513
+ function createSeriesFill(ctx, selected, binding = seriesFillBinding(selected)) {
8791
9514
  const element = el(ctx, "div", "xve-chart-series-fill");
8792
9515
  const heading2 = el(ctx, "h3");
8793
9516
  const kind = select(ctx, []);
@@ -8795,35 +9518,37 @@ function createSeriesFill(ctx, selected) {
8795
9518
  const color = el(ctx, "button", "xve-input xve-chart-series-color");
8796
9519
  color.type = "button";
8797
9520
  const colorField = field(ctx, "Color", color);
8798
- const transparency = createSeriesTransparency(ctx, selected);
8799
- element.append(heading2, kindField, colorField, transparency.element);
9521
+ const transparency = createSeriesTransparency(ctx, selected, binding);
9522
+ const gradient = createSeriesGradient(ctx, selected, binding);
9523
+ element.append(heading2, kindField, colorField, transparency.element, gradient.element);
8800
9524
  let current;
8801
9525
  const apply = (value) => {
8802
- if (!current || !ctx.commands.isEnabled("chart.format-series")) return;
9526
+ if (!current || !ctx.commands.isEnabled(binding.commandId)) return;
8803
9527
  const found = activeChart(ctx);
8804
9528
  if (!found || found.chart !== current) return;
8805
- const patch = chartSeriesFillPatch(current, selected(), value);
9529
+ const patch = binding.color(current, value);
8806
9530
  if (patch) ctx.session()?.updateChart(ctx.activeSheet(), found.index, patch);
8807
9531
  };
8808
9532
  kind.addEventListener("change", () => {
8809
9533
  if (kind.value === "none") apply(null);
9534
+ else if (kind.value === "gradient") gradient.create();
8810
9535
  else if (kind.value === "solid") {
8811
- if (!current || !ctx.commands.isEnabled("chart.format-series")) return;
9536
+ if (!current || !ctx.commands.isEnabled(binding.commandId)) return;
8812
9537
  const found = activeChart(ctx);
8813
9538
  if (!found || found.chart !== current) return;
8814
- const patch = chartSeriesSolidFillPatch(current, selected());
9539
+ const patch = binding.solid(current);
8815
9540
  if (patch) ctx.session()?.updateChart(ctx.activeSheet(), found.index, patch);
8816
9541
  }
8817
9542
  });
8818
9543
  color.addEventListener("click", () => {
8819
- if (!current || !ctx.commands.isEnabled("chart.format-series")) return;
9544
+ if (!current || !ctx.commands.isEnabled(binding.commandId)) return;
8820
9545
  const chart = current;
8821
- const index = selected();
9546
+ const index = binding.key();
8822
9547
  const workbook = ctx.workbook();
8823
9548
  openColorGrid(
8824
9549
  color,
8825
9550
  (choice) => {
8826
- if (current === chart && ctx.workbook() === workbook && selected() === index && choice)
9551
+ if (current === chart && ctx.workbook() === workbook && binding.key() === index && choice)
8827
9552
  apply(choice);
8828
9553
  },
8829
9554
  { t: ctx.t, ...workbook ? { theme: workbook.theme } : {} }
@@ -8832,28 +9557,30 @@ function createSeriesFill(ctx, selected) {
8832
9557
  const refresh = (chart, model) => {
8833
9558
  current = chart;
8834
9559
  transparency.refresh(chart);
8835
- const series = chart?.series[selected()];
8836
- const fill = series?.fill;
9560
+ gradient.refresh(chart, model);
9561
+ const fill = chart && binding.fill(chart);
8837
9562
  const value = fill?.kind === "none" ? "none" : fill?.kind === "gradient" ? "gradient" : fill && fill.kind !== "solid" ? "imported" : "solid";
8838
9563
  const entries = [
8839
9564
  ["none", "No fill"],
8840
- ["solid", "Solid fill"]
9565
+ ["solid", "Solid fill"],
9566
+ ["gradient", "Gradient fill"]
8841
9567
  ];
8842
- if (value === "gradient") entries.push(["gradient", "Gradient fill"]);
8843
- else if (value === "imported") entries.push(["imported", "Imported fill"]);
9568
+ if (value === "imported") entries.push(["imported", "Imported fill"]);
8844
9569
  kind.replaceChildren(
8845
9570
  ...entries.map(([key, label]) => {
8846
9571
  const option = el(ctx, "option");
8847
9572
  option.value = key;
8848
9573
  option.textContent = ctx.t(label);
8849
- option.disabled = key === "gradient" || key === "imported";
9574
+ option.disabled = key === "imported";
8850
9575
  return option;
8851
9576
  })
8852
9577
  );
8853
9578
  kind.value = value;
8854
- kind.disabled = !series || !ctx.commands.isEnabled("chart.format-series");
9579
+ colorField.hidden = value !== "solid";
9580
+ transparency.element.hidden = value !== "solid";
9581
+ kind.disabled = !chart || !ctx.commands.isEnabled(binding.commandId);
8855
9582
  color.disabled = kind.disabled || value !== "solid";
8856
- const paint = model?.series[selected()]?.color;
9583
+ const paint = model && binding.paint(model);
8857
9584
  color.style.setProperty("--series-fill", paint && paint !== "none" ? paint : "transparent");
8858
9585
  heading2.textContent = ctx.t("Fill");
8859
9586
  for (const [row2, control, label] of [
@@ -8881,7 +9608,7 @@ function chartSeriesCommand() {
8881
9608
  run: (ctx, arg) => void ctx.dialogs.open("format-chart-series", arg)
8882
9609
  });
8883
9610
  }
8884
- function createChartSeriesPane(ctx) {
9611
+ function createChartSeriesPane(ctx, onOpen) {
8885
9612
  const element = el(ctx, "aside", "xve-chart-series-pane");
8886
9613
  element.hidden = true;
8887
9614
  element.setAttribute("role", "complementary");
@@ -8891,6 +9618,7 @@ function createChartSeriesPane(ctx) {
8891
9618
  closeButton.type = "button";
8892
9619
  closeButton.textContent = "\xD7";
8893
9620
  const close = () => {
9621
+ fills.refresh(void 0, void 0);
8894
9622
  element.hidden = true;
8895
9623
  ctx.grid()?.focus();
8896
9624
  };
@@ -9031,6 +9759,7 @@ function createChartSeriesPane(ctx) {
9031
9759
  relocalize();
9032
9760
  ctx.dialogs.register("format-chart-series", async (_ctx, props2) => {
9033
9761
  if (!ctx.commands.isEnabled("chart.format-series")) return;
9762
+ onOpen?.();
9034
9763
  element.hidden = false;
9035
9764
  refresh();
9036
9765
  if (typeof props2 === "number" && Number.isInteger(props2) && activeChart(ctx)?.chart.series[props2]) {
@@ -9039,7 +9768,112 @@ function createChartSeriesPane(ctx) {
9039
9768
  }
9040
9769
  overlap.focus();
9041
9770
  });
9042
- return { element, refresh, relocalize };
9771
+ return { element, refresh, relocalize, close };
9772
+ }
9773
+ var names = {
9774
+ chartArea: ["Chart Area", "Format Chart Area"],
9775
+ plotArea: ["Plot Area", "Format Plot Area"],
9776
+ title: ["Chart Title", "Format Chart Title"],
9777
+ legend: ["Legend", "Format Legend"]
9778
+ };
9779
+ function chartAreaCommand() {
9780
+ return editing({
9781
+ id: "chart.format-area",
9782
+ label: "Format Chart Area",
9783
+ icon: icon("chartColumn"),
9784
+ lock: "objects",
9785
+ enabled: (ctx) => !!activeChart(ctx),
9786
+ run: (ctx, arg) => void ctx.dialogs.open("format-chart-area", arg)
9787
+ });
9788
+ }
9789
+ function createChartAreaPane(ctx, onOpen) {
9790
+ const element = el(ctx, "aside", "xve-chart-series-pane");
9791
+ element.hidden = true;
9792
+ element.setAttribute("role", "complementary");
9793
+ const header = el(ctx, "div", "xve-chart-series-header");
9794
+ const title = el(ctx, "h2");
9795
+ const closeButton = el(ctx, "button", "xve-icon-button");
9796
+ closeButton.type = "button";
9797
+ closeButton.textContent = "\xD7";
9798
+ const body = el(ctx, "div", "xve-chart-series-body");
9799
+ let selected = "chartArea";
9800
+ const target24 = select(ctx, []);
9801
+ const targetField = field(ctx, "Chart element", target24);
9802
+ const fills = createSeriesFill(
9803
+ ctx,
9804
+ () => 0,
9805
+ backgroundFillBinding(() => selected)
9806
+ );
9807
+ const close = () => {
9808
+ fills.refresh(void 0, void 0);
9809
+ element.hidden = true;
9810
+ ctx.grid()?.focus();
9811
+ };
9812
+ closeButton.addEventListener("click", close);
9813
+ element.addEventListener("keydown", (event) => {
9814
+ if (event.key === "Escape") {
9815
+ event.preventDefault();
9816
+ event.stopPropagation();
9817
+ close();
9818
+ }
9819
+ });
9820
+ header.append(title, closeButton);
9821
+ body.append(targetField, fills.element);
9822
+ element.append(header, body);
9823
+ const refresh = () => {
9824
+ if (element.hidden) return;
9825
+ const found = activeChart(ctx), book = ctx.workbook();
9826
+ if (!found || !book) {
9827
+ close();
9828
+ return;
9829
+ }
9830
+ const available = (part) => part === "title" ? !!found.chart.title : part === "legend" ? found.chart.showLegend : true;
9831
+ for (const option of target24.options)
9832
+ option.disabled = !available(option.value);
9833
+ if (!available(selected)) selected = "chartArea";
9834
+ title.textContent = ctx.t(names[selected][1]);
9835
+ element.setAttribute("aria-label", title.textContent);
9836
+ target24.value = selected;
9837
+ target24.disabled = !ctx.commands.isEnabled("chart.format-area");
9838
+ fills.refresh(
9839
+ found.chart,
9840
+ chartView(
9841
+ book,
9842
+ ctx.activeSheet(),
9843
+ found.chart,
9844
+ createRefEvaluator(book, ctx.session()?.calc, { sheet: ctx.activeSheet() })
9845
+ )
9846
+ );
9847
+ };
9848
+ const relocalize = () => {
9849
+ closeButton.setAttribute("aria-label", ctx.t("Close"));
9850
+ targetField.querySelector("span").textContent = ctx.t("Chart element");
9851
+ target24.setAttribute("aria-label", ctx.t("Chart element"));
9852
+ target24.replaceChildren(
9853
+ ...CHART_FILL_PARTS.map((part) => {
9854
+ const option = el(ctx, "option");
9855
+ option.value = part;
9856
+ option.textContent = ctx.t(names[part][0]);
9857
+ return option;
9858
+ })
9859
+ );
9860
+ refresh();
9861
+ };
9862
+ target24.addEventListener("change", () => {
9863
+ if (!target24.disabled && CHART_FILL_PARTS.includes(target24.value))
9864
+ selected = target24.value;
9865
+ refresh();
9866
+ });
9867
+ ctx.dialogs.register("format-chart-area", async (_ctx, part) => {
9868
+ if (!ctx.commands.isEnabled("chart.format-area")) return;
9869
+ onOpen();
9870
+ selected = CHART_FILL_PARTS.includes(part) ? part : "chartArea";
9871
+ element.hidden = false;
9872
+ refresh();
9873
+ target24.focus();
9874
+ });
9875
+ relocalize();
9876
+ return { element, refresh, relocalize, close };
9043
9877
  }
9044
9878
 
9045
9879
  // src/xlsx/commands/index.ts
@@ -9060,7 +9894,8 @@ function allCommands() {
9060
9894
  ...reviewCommands(),
9061
9895
  ...viewCommands(),
9062
9896
  ...contextualCommands(),
9063
- chartSeriesCommand()
9897
+ chartSeriesCommand(),
9898
+ chartAreaCommand()
9064
9899
  ];
9065
9900
  }
9066
9901
  function installCommands(ctx) {
@@ -9953,10 +10788,12 @@ var DrawingLayer = class {
9953
10788
  node.addEventListener("pointerdown", (event) => this.#press(event, index));
9954
10789
  node.addEventListener("dblclick", (event) => {
9955
10790
  const series = event.currentTarget.xgSeriesHit;
9956
- if (series === void 0) return;
10791
+ const part = event.currentTarget.xgChartPart;
10792
+ if (series === void 0 && part === void 0) return;
9957
10793
  event.stopPropagation();
9958
10794
  view.ctx.selection.set({ drawing: index });
9959
- void view.ctx.commands.run("chart.format-series", series);
10795
+ if (series !== void 0) void view.ctx.commands.run("chart.format-series", series);
10796
+ else void view.ctx.commands.run("chart.format-area", part);
9960
10797
  });
9961
10798
  this.#nodes.set(key, node);
9962
10799
  }
@@ -9965,7 +10802,7 @@ var DrawingLayer = class {
9965
10802
  const sig = `${drawing.kind}:${Math.round(box.w)}x${Math.round(box.h)}:${this.#generation}`;
9966
10803
  if (node.xgSig !== sig) {
9967
10804
  node.xgSig = sig;
9968
- this.#paint(node, drawing, box, index);
10805
+ this.#paint(node, drawing, box);
9969
10806
  }
9970
10807
  this.#selection(node, index);
9971
10808
  }
@@ -9976,7 +10813,7 @@ var DrawingLayer = class {
9976
10813
  this.#nodes.delete(key);
9977
10814
  }
9978
10815
  }
9979
- #paint(node, drawing, box, index) {
10816
+ #paint(node, drawing, box) {
9980
10817
  const view = this.#view;
9981
10818
  const doc = view.doc;
9982
10819
  node.replaceChildren();
@@ -10002,11 +10839,10 @@ var DrawingLayer = class {
10002
10839
  node.append(
10003
10840
  svgNode(
10004
10841
  doc,
10005
- renderChartSvg(
10006
- model,
10007
- Math.max(10, Math.round(box.w)),
10008
- Math.max(10, Math.round(box.h))
10009
- )
10842
+ renderChartSvg(model, Math.max(10, box.w), Math.max(10, box.h), {
10843
+ measureText: (content, font) => view.measurer.measure(content, font),
10844
+ measureFont: (font) => view.measurer.fontMetrics(font)
10845
+ })
10010
10846
  )
10011
10847
  );
10012
10848
  } catch {
@@ -10052,6 +10888,8 @@ var DrawingLayer = class {
10052
10888
  const node = event.currentTarget;
10053
10889
  const series = event.target.closest("[data-chart-series]")?.dataset.chartSeries;
10054
10890
  node.xgSeriesHit = series === void 0 ? void 0 : Number(series);
10891
+ const part = event.target.closest("[data-chart-part]")?.dataset.chartPart;
10892
+ node.xgChartPart = CHART_FILL_PARTS.includes(part) ? part : void 0;
10055
10893
  view.ctx.selection.set({ drawing: index });
10056
10894
  view.ctx.grid()?.focus();
10057
10895
  const session = view.ctx.session();
@@ -10142,9 +10980,9 @@ var GridA11y = class {
10142
10980
  }
10143
10981
  input;
10144
10982
  /** Updates the sink label and announces (debounced) after a selection change. */
10145
- update(selection, editing6) {
10983
+ update(selection, editing7) {
10146
10984
  const text3 = describeSelection(this.#view, selection);
10147
- if (!editing6) this.input.setAttribute("aria-label", text3);
10985
+ if (!editing7) this.input.setAttribute("aria-label", text3);
10148
10986
  if (this.#timer) clearTimeout(this.#timer);
10149
10987
  this.#timer = setTimeout(() => {
10150
10988
  this.live.textContent = text3;
@@ -11190,12 +12028,12 @@ function paintCell(doc, node, item, zoom) {
11190
12028
  }
11191
12029
  }
11192
12030
  if (view.icon) {
11193
- const icon6 = svgNode(
12031
+ const icon7 = svgNode(
11194
12032
  doc,
11195
12033
  iconSvg(view.icon.set, view.icon.index, Math.round(16 * (zoom / 100)))
11196
12034
  );
11197
- icon6.setAttribute("class", "xg-ic");
11198
- node.append(icon6);
12035
+ icon7.setAttribute("class", "xg-ic");
12036
+ node.append(icon7);
11199
12037
  }
11200
12038
  if (item.text) {
11201
12039
  const box = h(doc, "div", "xg-t");
@@ -11356,6 +12194,7 @@ function estimateWidth(text3, font) {
11356
12194
  function createTextMeasurer(doc) {
11357
12195
  let context;
11358
12196
  const cache = /* @__PURE__ */ new Map();
12197
+ const metricsCache = /* @__PURE__ */ new Map();
11359
12198
  const ctx = () => {
11360
12199
  if (context !== void 0) return context;
11361
12200
  try {
@@ -11383,7 +12222,24 @@ function createTextMeasurer(doc) {
11383
12222
  };
11384
12223
  return {
11385
12224
  measure,
11386
- measureFont: (text3, font) => measure(text3, cssFont(font, 100))
12225
+ measureFont: (text3, font) => measure(text3, cssFont(font, 100)),
12226
+ fontMetrics: (font) => {
12227
+ const hit = metricsCache.get(font);
12228
+ if (hit) return hit;
12229
+ const c = ctx();
12230
+ if (!c) return void 0;
12231
+ if (c.font !== font) c.font = font;
12232
+ const result = c.measureText("Mg");
12233
+ const metrics = {
12234
+ ascent: result.fontBoundingBoxAscent,
12235
+ descent: result.fontBoundingBoxDescent
12236
+ };
12237
+ if (!Number.isFinite(metrics.ascent) || !Number.isFinite(metrics.descent) || metrics.ascent + metrics.descent <= 0)
12238
+ return void 0;
12239
+ if (metricsCache.size > 1e4) metricsCache.clear();
12240
+ metricsCache.set(font, metrics);
12241
+ return metrics;
12242
+ }
11387
12243
  };
11388
12244
  }
11389
12245
  function rectOf(metrics, range2, maxRow, maxCol) {
@@ -11976,9 +12832,9 @@ var GRID_CSS = `
11976
12832
  .xg-drawings{z-index:4}
11977
12833
  .xg-obj{position:absolute;box-sizing:border-box;pointer-events:auto;cursor:default}
11978
12834
  .xg-obj img,.xg-obj svg{display:block;width:100%;height:100%;pointer-events:none}
11979
- .xg-chart svg [data-chart-series]{pointer-events:auto}
11980
- .xg-obj.xg-chart{background:#fff;border:1px solid #d9d9d9}
11981
- .xg-obj.xg-obj-sel{outline:1px solid #8a8a8a}
12835
+ .xg-chart svg [data-chart-series],.xg-chart svg [data-chart-part]{pointer-events:auto}
12836
+ .xg-obj.xg-chart{background:#fff;outline:1px solid #d9d9d9;outline-offset:-1px}
12837
+ .xg-obj.xg-obj-sel{outline:1px solid #8a8a8a;outline-offset:0}
11982
12838
  .xg-obj .xg-grip{position:absolute;width:8px;height:8px;background:#fff;border:1px solid #555;box-sizing:border-box;border-radius:50%}
11983
12839
  .xg-obj-unsupported{background:repeating-linear-gradient(45deg,#f6f6f6,#f6f6f6 6px,#ececec 6px,#ececec 12px);border:1px dashed #aaa;
11984
12840
  font-size:11px;color:#555;display:flex;align-items:center;justify-content:center;text-align:center}
@@ -12372,10 +13228,10 @@ function createFormulaInput(ctx, doc, bar) {
12372
13228
  const onKey = (event) => {
12373
13229
  event.stopPropagation();
12374
13230
  if (assist.handleKey(event, field2)) return;
12375
- const editing6 = bridge.state().editing;
13231
+ const editing7 = bridge.state().editing;
12376
13232
  if (event.key === "Escape") {
12377
13233
  event.preventDefault();
12378
- if (editing6) bridge.cancel();
13234
+ if (editing7) bridge.cancel();
12379
13235
  else showCell();
12380
13236
  ctx.grid()?.focus();
12381
13237
  return;
@@ -12388,7 +13244,7 @@ function createFormulaInput(ctx, doc, bar) {
12388
13244
  onInput();
12389
13245
  return;
12390
13246
  }
12391
- if (!editing6) return;
13247
+ if (!editing7) return;
12392
13248
  if (event.key === "Enter") {
12393
13249
  event.preventDefault();
12394
13250
  if (event.ctrlKey || event.metaKey) bridge.commit("none", true);
@@ -12526,10 +13382,10 @@ function createNameBox(ctx, doc) {
12526
13382
  const wb = ctx.workbook();
12527
13383
  list.replaceChildren();
12528
13384
  const sheet = ctx.activeSheet();
12529
- const names = (wb?.definedNames ?? []).filter(
13385
+ const names2 = (wb?.definedNames ?? []).filter(
12530
13386
  (d) => !d.hidden && (d.localSheet === void 0 || d.localSheet === sheet)
12531
13387
  );
12532
- for (const named of names) {
13388
+ for (const named of names2) {
12533
13389
  const item = h(doc, "li", "xfb-name-item", { role: "option" });
12534
13390
  item.textContent = named.name;
12535
13391
  item.addEventListener("mousedown", (event) => {
@@ -12540,7 +13396,7 @@ function createNameBox(ctx, doc) {
12540
13396
  });
12541
13397
  list.append(item);
12542
13398
  }
12543
- if (!names.length) {
13399
+ if (!names2.length) {
12544
13400
  const empty = h(doc, "li", "xfb-name-empty", { "aria-disabled": "true" });
12545
13401
  empty.textContent = t("No defined names");
12546
13402
  list.append(empty);
@@ -13493,10 +14349,10 @@ function adjacentSheet(ctx, step2) {
13493
14349
  return void 0;
13494
14350
  }
13495
14351
  function shellCommands(handlers) {
13496
- const file = (id, label, command, icon6, shortcut) => ({
14352
+ const file = (id, label, command, icon7, shortcut) => ({
13497
14353
  id,
13498
14354
  label,
13499
- icon: icon6,
14355
+ icon: icon7,
13500
14356
  ...shortcut ? { shortcut } : {},
13501
14357
  enabled: (ctx) => command === "new" || command === "open" || hasWorkbook(ctx),
13502
14358
  run: () => handlers.fileCommand(command)
@@ -14010,7 +14866,8 @@ function buildShell(core) {
14010
14866
  });
14011
14867
  const toaster = createToaster(doc, () => ctx.t("Close"));
14012
14868
  const workspace = el2(doc, "div", "xve-workspace");
14013
- const chartSeriesPane = createChartSeriesPane(ctx);
14869
+ const chartSeriesPane = createChartSeriesPane(ctx, () => chartAreaPane.close());
14870
+ const chartAreaPane = createChartAreaPane(ctx, () => chartSeriesPane.close());
14014
14871
  const canvasRow = el2(doc, "div", "xve-canvas-row");
14015
14872
  const formulaRow = el2(doc, "div", "xve-formula-row");
14016
14873
  const gridHost = el2(doc, "div", "xve-grid-host");
@@ -14019,7 +14876,7 @@ function buildShell(core) {
14019
14876
  const emptyText = el2(doc, "p");
14020
14877
  empty.append(emptyText);
14021
14878
  workspace.append(formulaRow, gridHost, tabsRow, empty);
14022
- canvasRow.append(workspace, chartSeriesPane.element);
14879
+ canvasRow.append(workspace, chartSeriesPane.element, chartAreaPane.element);
14023
14880
  frame.append(
14024
14881
  titleBar.element,
14025
14882
  ribbon.element,
@@ -14044,6 +14901,7 @@ function buildShell(core) {
14044
14901
  gridHost,
14045
14902
  tabsRow,
14046
14903
  chartSeriesPane.element,
14904
+ chartAreaPane.element,
14047
14905
  statusBar.element
14048
14906
  ].filter((region) => region.getClientRects().length);
14049
14907
  const focusRegion = (region) => {
@@ -14083,6 +14941,7 @@ function buildShell(core) {
14083
14941
  titleBar.refresh();
14084
14942
  statusBar.refresh();
14085
14943
  chartSeriesPane.refresh();
14944
+ chartAreaPane.refresh();
14086
14945
  titleBar.setFileName(core.fileName);
14087
14946
  if (core.dirty.dirty && saveState !== "dirty" && saveState !== "saving")
14088
14947
  chrome.setSaveState("dirty");
@@ -14098,6 +14957,7 @@ function buildShell(core) {
14098
14957
  statusBar.relocalize();
14099
14958
  backstage.relocalize();
14100
14959
  chartSeriesPane.relocalize();
14960
+ chartAreaPane.relocalize();
14101
14961
  core.requestRender();
14102
14962
  }
14103
14963
  };