ooxml-ui 1.0.0 → 1.2.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 (169) hide show
  1. package/README.md +40 -36
  2. package/custom-elements.json +211 -4
  3. package/dist/chrome/ruler.d.ts +60 -2
  4. package/dist/chrome/ruler.d.ts.map +1 -1
  5. package/dist/chrome/status-bar.d.ts +3 -1
  6. package/dist/chrome/status-bar.d.ts.map +1 -1
  7. package/dist/{chunk-T3HB5LMH.js → chunk-7FNDOWTX.js} +393 -15
  8. package/dist/{chunk-FLHM5Q4O.js → chunk-AYXFLBSV.js} +92 -13
  9. package/dist/{chunk-X7RINDJQ.js → chunk-JAJFLFAV.js} +1 -1
  10. package/dist/{chunk-OXKWNNNJ.js → chunk-VUB44CAJ.js} +1 -1
  11. package/dist/{chunk-JFJXBYRD.js → chunk-XNCWB2X6.js} +5 -0
  12. package/dist/{chunk-XDUYS4KK.js → chunk-Y2E22JVO.js} +1 -1
  13. package/dist/{chunk-ZV2Z4IWH.js → chunk-YK2DPYMU.js} +5 -2
  14. package/dist/{chunk-4QQASR5A.js → chunk-YP5S7DTT.js} +6 -5
  15. package/dist/comments/comments-pane.d.ts +116 -0
  16. package/dist/comments/comments-pane.d.ts.map +1 -0
  17. package/dist/comments/types.d.ts +80 -0
  18. package/dist/comments/types.d.ts.map +1 -0
  19. package/dist/controls.d.ts +4 -2
  20. package/dist/controls.d.ts.map +1 -1
  21. package/dist/controls.js +4 -4
  22. package/dist/docx/comments-panel.d.ts +8 -1
  23. package/dist/docx/image-media.d.ts +1 -1
  24. package/dist/docx/locales/en-ribbon.d.ts +1 -0
  25. package/dist/docx/locales/en.d.ts +1 -0
  26. package/dist/docx/localization-strings.d.ts +2 -1
  27. package/dist/docx/localization.d.ts +7 -1
  28. package/dist/docx/print-layout.d.ts +2 -2
  29. package/dist/docx/print-picture.d.ts +12 -0
  30. package/dist/docx/review-controller.d.ts +2 -0
  31. package/dist/docx/ruler.d.ts +7 -10
  32. package/dist/docx.js +289 -339
  33. package/dist/form/gradient-preset-gallery.d.ts +2 -2
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +8 -8
  36. package/dist/pptx/{chart-3d-view-scene-2KRQ5667.mjs → chart-3d-view-scene-6B5TOWB6.mjs} +3 -3
  37. package/dist/pptx/{chart-3d-view-scene-36DMZWZ3.cjs → chart-3d-view-scene-MJ6YNT56.cjs} +37 -37
  38. package/dist/pptx/{chunk-YOGDIDPB.mjs → chunk-AWKKC3VW.mjs} +8 -2
  39. package/dist/pptx/{chunk-XQZWZMGZ.cjs → chunk-SM3CWDIQ.cjs} +7 -17
  40. package/dist/pptx/{chunk-CJU4P5FT.cjs → chunk-STKGUT5S.cjs} +18 -12
  41. package/dist/pptx/{chunk-JSBGBVBH.mjs → chunk-VRFNAF3Q.mjs} +7 -17
  42. package/dist/pptx/{chunk-YHA6XAB3.cjs → chunk-XIQMFSNG.cjs} +1 -1
  43. package/dist/pptx/{chunk-YARCML7O.mjs → chunk-XQVKQD5Z.mjs} +294 -265
  44. package/dist/pptx/{chunk-XWE5GSE7.mjs → chunk-YI5LH5NN.mjs} +533 -428
  45. package/dist/pptx/{chunk-ADOC5JLI.mjs → chunk-YMTQLZ2I.mjs} +1 -1
  46. package/dist/pptx/{chunk-AIL3BNZS.cjs → chunk-YRHK45AB.cjs} +301 -265
  47. package/dist/pptx/{chunk-FEFQTZDM.cjs → chunk-ZK344LOB.cjs} +689 -574
  48. package/dist/pptx/dom/elements/chart.d.ts +2 -3
  49. package/dist/pptx/dom/elements/chart.d.ts.map +1 -1
  50. package/dist/pptx/dom/elements/table.d.ts.map +1 -1
  51. package/dist/pptx/dom/index.cjs +257 -246
  52. package/dist/pptx/dom/index.mjs +28 -17
  53. package/dist/pptx/export/html2canvas-clone.cjs +2 -2
  54. package/dist/pptx/export/html2canvas-clone.mjs +1 -1
  55. package/dist/pptx/i18n/index.cjs +6 -6
  56. package/dist/pptx/i18n/index.mjs +1 -1
  57. package/dist/pptx/i18n/translator.d.ts.map +1 -1
  58. package/dist/pptx/index.cjs +3035 -2991
  59. package/dist/pptx/index.mjs +5 -5
  60. package/dist/pptx/render/chart-axis-range.d.ts +7 -0
  61. package/dist/pptx/render/chart-axis-range.d.ts.map +1 -1
  62. package/dist/pptx/render/chart-axis-render.d.ts +3 -1
  63. package/dist/pptx/render/chart-axis-render.d.ts.map +1 -1
  64. package/dist/pptx/render/chart-axis-style.d.ts +5 -0
  65. package/dist/pptx/render/chart-axis-style.d.ts.map +1 -1
  66. package/dist/pptx/render/chart-axis.d.ts +1 -1
  67. package/dist/pptx/render/chart-axis.d.ts.map +1 -1
  68. package/dist/pptx/render/chart-cartesian-axes.d.ts.map +1 -1
  69. package/dist/pptx/render/chart-cartesian-bars.d.ts +3 -3
  70. package/dist/pptx/render/chart-cartesian-bars.d.ts.map +1 -1
  71. package/dist/pptx/render/chart-cartesian-percent-stacked.d.ts.map +1 -1
  72. package/dist/pptx/render/chart-cartesian.d.ts.map +1 -1
  73. package/dist/pptx/render/chart-combo-stock.cjs +3 -3
  74. package/dist/pptx/render/chart-combo-stock.mjs +1 -1
  75. package/dist/pptx/render/chart-combo.d.ts.map +1 -1
  76. package/dist/pptx/render/chart-gridlines-toggle.d.ts +13 -1
  77. package/dist/pptx/render/chart-gridlines-toggle.d.ts.map +1 -1
  78. package/dist/pptx/render/chart-helpers.d.ts +13 -13
  79. package/dist/pptx/render/chart-helpers.d.ts.map +1 -1
  80. package/dist/pptx/render/chart-horizontal-bars-helpers.d.ts +7 -3
  81. package/dist/pptx/render/chart-horizontal-bars-helpers.d.ts.map +1 -1
  82. package/dist/pptx/render/chart-horizontal-bars.d.ts.map +1 -1
  83. package/dist/pptx/render/chart-interaction-stacked.d.ts.map +1 -1
  84. package/dist/pptx/render/chart-overlays.cjs +7 -7
  85. package/dist/pptx/render/chart-overlays.mjs +1 -1
  86. package/dist/pptx/render/chart-palette.d.ts +5 -14
  87. package/dist/pptx/render/chart-palette.d.ts.map +1 -1
  88. package/dist/pptx/render/chart-quick-action-styles.d.ts +5 -3
  89. package/dist/pptx/render/chart-quick-action-styles.d.ts.map +1 -1
  90. package/dist/pptx/render/chart-scatter-x-axis.d.ts.map +1 -1
  91. package/dist/pptx/render/chart-style-palette.d.ts +52 -0
  92. package/dist/pptx/render/chart-style-palette.d.ts.map +1 -0
  93. package/dist/pptx/render/chart-surface-treemap.cjs +4 -4
  94. package/dist/pptx/render/chart-surface-treemap.mjs +1 -1
  95. package/dist/pptx/render/chart-view-model-bars.d.ts +31 -1
  96. package/dist/pptx/render/chart-view-model-bars.d.ts.map +1 -1
  97. package/dist/pptx/render/chart-view-model-build.d.ts.map +1 -1
  98. package/dist/pptx/render/chart-view-model.cjs +53 -41
  99. package/dist/pptx/render/chart-view-model.mjs +1 -1
  100. package/dist/pptx/render/chart-waterfall-map.cjs +6 -6
  101. package/dist/pptx/render/chart-waterfall-map.mjs +1 -1
  102. package/dist/pptx/render/connector-elbow-geometry.d.ts +7 -43
  103. package/dist/pptx/render/connector-elbow-geometry.d.ts.map +1 -1
  104. package/dist/pptx/render/foreign-object-svg-document.d.ts +12 -0
  105. package/dist/pptx/render/foreign-object-svg-document.d.ts.map +1 -1
  106. package/dist/pptx/render/index.d.ts +2 -0
  107. package/dist/pptx/render/index.d.ts.map +1 -1
  108. package/dist/pptx/render/table-cell-css.d.ts +3 -2
  109. package/dist/pptx/render/table-cell-css.d.ts.map +1 -1
  110. package/dist/pptx/render/table-cell-edit.d.ts +1 -1
  111. package/dist/pptx/render/table-cell-edit.d.ts.map +1 -1
  112. package/dist/pptx/render/table-cell-paragraphs.d.ts +29 -0
  113. package/dist/pptx/render/table-cell-paragraphs.d.ts.map +1 -0
  114. package/dist/pptx/render/table-style.d.ts +2 -0
  115. package/dist/pptx/render/table-style.d.ts.map +1 -1
  116. package/dist/pptx/render/title-bar.d.ts +5 -0
  117. package/dist/pptx/render/title-bar.d.ts.map +1 -1
  118. package/dist/pptx/render/viewer-root-options.d.ts +54 -0
  119. package/dist/pptx/render/viewer-root-options.d.ts.map +1 -0
  120. package/dist/pptx/web-components/office-token-bridge.d.ts.map +1 -1
  121. package/dist/pptx/web-components/registration-contract.d.ts.map +1 -1
  122. package/dist/presence.js +3 -3
  123. package/dist/registry.d.ts +2 -0
  124. package/dist/registry.d.ts.map +1 -1
  125. package/dist/ribbon/ribbon-tabs.d.ts +2 -1
  126. package/dist/ribbon/ribbon-tabs.d.ts.map +1 -1
  127. package/dist/smartart/smartart-svg.d.ts +3 -2
  128. package/dist/smartart.js +3 -3
  129. package/dist/suite/attachment-store.d.ts +13 -0
  130. package/dist/suite/document-store.d.ts +31 -0
  131. package/dist/suite/index.d.ts +2 -0
  132. package/dist/suite.js +132 -1
  133. package/dist/teams/app/channel-tab.d.ts +5 -0
  134. package/dist/teams/chat-composer.d.ts +3 -0
  135. package/dist/teams.js +6 -6
  136. package/dist/theme.js +2 -2
  137. package/dist/tokens.d.ts +5 -0
  138. package/dist/tokens.d.ts.map +1 -1
  139. package/dist/visio/styles/index.d.ts +2 -0
  140. package/dist/visio/styles/theme.d.ts +11 -0
  141. package/dist/visio/styles.d.ts +2 -2
  142. package/dist/visio.js +76 -25
  143. package/dist/xlsx/backstage/index.d.ts +4 -1
  144. package/dist/xlsx/backstage/pages-share.d.ts +19 -0
  145. package/dist/xlsx/backstage/parts.d.ts +3 -0
  146. package/dist/xlsx/chart-fill-binding.d.ts +2 -2
  147. package/dist/xlsx/chart-series-gradient-type.d.ts +3 -2
  148. package/dist/xlsx/collaboration-session.d.ts +9 -0
  149. package/dist/xlsx/collaboration-types.d.ts +41 -0
  150. package/dist/xlsx/collaboration.d.ts +56 -0
  151. package/dist/xlsx/component.d.ts +8 -0
  152. package/dist/xlsx/dialogs/comments-list.d.ts +4 -0
  153. package/dist/xlsx/dialogs/help-dialogs.d.ts +1 -1
  154. package/dist/xlsx/editor-core.d.ts +3 -0
  155. package/dist/xlsx/element-api.d.ts +19 -1
  156. package/dist/xlsx/events.d.ts +4 -1
  157. package/dist/xlsx/grid/remote-layer.d.ts +3 -0
  158. package/dist/xlsx/grid/sparkline.d.ts +3 -0
  159. package/dist/xlsx/grid/styles.d.ts +1 -1
  160. package/dist/xlsx/index.d.ts +1 -0
  161. package/dist/xlsx/ribbon-actions.d.ts +21 -0
  162. package/dist/xlsx/shell-commands.d.ts +1 -1
  163. package/dist/xlsx/title-bar.d.ts +4 -2
  164. package/dist/xlsx.js +712 -90
  165. package/package.json +3 -3
  166. package/dist/pptx/web-components/status-bar-styles.d.ts +0 -3
  167. package/dist/pptx/web-components/status-bar-styles.d.ts.map +0 -1
  168. package/dist/pptx/web-components/title-bar-styles.d.ts +0 -7
  169. package/dist/pptx/web-components/title-bar-styles.d.ts.map +0 -1
package/dist/xlsx.js CHANGED
@@ -1,27 +1,24 @@
1
- import './chunk-4QQASR5A.js';
2
- import { defineSmartArt } from './chunk-XDUYS4KK.js';
3
- import { withExtension, saveExtension, downloadBytes, emit, emitEvent, blobFor, announceFileCommand, pickFile } from './chunk-FLHM5Q4O.js';
4
- export { XLSX_EDITOR_EVENTS } from './chunk-FLHM5Q4O.js';
1
+ import './chunk-YP5S7DTT.js';
2
+ import { defineSmartArt } from './chunk-Y2E22JVO.js';
3
+ import { withExtension, saveExtension, downloadBytes, emit, emitEvent, blobFor, announceFileCommand, pickFile } from './chunk-AYXFLBSV.js';
4
+ export { XLSX_EDITOR_EVENTS } from './chunk-AYXFLBSV.js';
5
5
  import './chunk-SNV6YKPC.js';
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-T3HB5LMH.js';
6
+ import { shadcnBridge } from './chunk-JAJFLFAV.js';
7
+ import { range_control_default, createRibbonOverflow, defineBackstage, defineRibbon, defineTitleBar, defineStatusBar, defineZoomSlider, rangeControl, defineDialog, runKeyTips, assignKeyTips, defineCheckbox, defineGallery, defineContextMenu, defineCommentsPane, parseSvgPreview } from './chunk-7FNDOWTX.js';
8
8
  import { getIcon, registerIcon } from './chunk-BOX43CZD.js';
9
- import './chunk-OXKWNNNJ.js';
10
- import { withTokens } from './chunk-ZV2Z4IWH.js';
11
- import './chunk-JFJXBYRD.js';
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';
9
+ import { definePresence } from './chunk-VUB44CAJ.js';
10
+ import { withTokens } from './chunk-YK2DPYMU.js';
11
+ import './chunk-XNCWB2X6.js';
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, historyOf, 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, 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';
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, layoutRemoteSelections, 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
16
  import { resolveChartGradient, sortGradientStops, withChartGradientDirection, buildChartGradientDef, chartGradientMarkup, CHART_COLOR_PALETTES, chartPaletteColors } from 'ooxml-core/chart';
17
17
  import { render } from 'lit';
18
18
  import { RECT_GRADIENT_DIRECTIONS, OFFICE_GRADIENT_PRESETS, officeGradientPresetId, officeGradientPresetFill, resolveDrawingColor, drawingColorBrightness, drawingColorCss, rectGradientDirection } from 'ooxml-core/diagram';
19
19
 
20
- // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/xlsx/theme/office-bridge.css?raw
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";
22
-
23
20
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/xlsx/theme/shell.css?raw
24
- var shell_default = "/* Frame, title bar and status bar. Mirrors docx-viewer's chrome (styles/base.css,\n chrome-titlebar.css, status-dialogs.css) with --xve-* tokens. */\n:host {\n --xl: var(--xve-primary);\n --xl-strong: color-mix(in srgb, var(--xve-primary) 70%, #000);\n --xl-soft: color-mix(in srgb, var(--xve-primary) 12%, transparent);\n --accent-soft: var(--xl-soft);\n --ink: var(--xve-foreground);\n --muted: var(--xve-muted-foreground);\n --line: var(--xve-border);\n --surface: var(--xve-card);\n --chrome: var(--xve-background);\n display: block;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n min-height: 0;\n color: var(--ink);\n font:\n 13px/1.35 'Segoe UI',\n Arial,\n sans-serif;\n}\n.xve-frame {\n position: relative;\n display: flex;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n min-height: 0;\n min-width: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--surface);\n}\n:focus-visible {\n outline: 2px solid var(--xve-ring);\n outline-offset: 1px;\n}\n[hidden],\n[data-xve-hidden] {\n display: none !important;\n}\n.xve-icon {\n flex: none;\n}\n.xve-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 28px;\n padding: 0 6px;\n border: 0;\n border-radius: 4px;\n background: none;\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n.xve-icon-button:hover:not(:disabled),\n.xve-icon-button[aria-pressed='true'] {\n background: var(--xl-soft);\n}\n.xve-icon-button:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* Workspace: formula bar, grid and sheet tabs between the ribbon and the status bar. */\n.xve-workspace {\n position: relative;\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n flex-direction: column;\n background: var(--xve-sheet-bg);\n}\n.xve-formula-row {\n flex: none;\n min-width: 0;\n border-bottom: 1px solid var(--line);\n background: var(--surface);\n}\n.xve-grid-host {\n position: relative;\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n background: var(--xve-sheet-bg);\n color-scheme: light;\n}\n.xve-grid-host > * {\n flex: 1;\n min-width: 0;\n}\n.xve-tabs-row {\n flex: none;\n min-width: 0;\n border-top: 1px solid var(--line);\n background: var(--chrome);\n}\n.xve-empty {\n position: absolute;\n z-index: 5;\n inset: 0;\n display: grid;\n place-items: center;\n color: var(--muted);\n background: var(--chrome);\n}\n:host([show-formula-bar='false']) .xve-formula-row,\n:host([show-toolbar='false']) .xve-ribbon {\n display: none;\n}\n\n/* Title bar: drawn by the shared office-ui-title-bar; the mark, background and borders come from its tokens. */\n.xve-titlebar {\n display: block;\n flex: none;\n --office-title-bar-background: var(--chrome);\n --office-title-bar-foreground: var(--ink);\n --office-title-bar-border: var(--line);\n --office-title-bar-mark-background: var(--xl);\n --office-title-bar-mark-foreground: var(--xve-primary-foreground);\n}\n.xve-title-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n.xve-mode-select {\n height: 28px;\n margin-left: 4px;\n padding: 0 6px;\n border: 1px solid var(--line);\n border-radius: 4px;\n background: var(--surface);\n color: var(--ink);\n font: inherit;\n}\n\n/* Status bar: drawn by the shared office-ui-status-bar; this places it and styles the slotted stats. */\n.xve-status {\n display: block;\n flex: none;\n min-width: 0;\n border-top: 1px solid var(--line);\n}\n.xve-status-stats {\n margin-inline-end: 10px;\n overflow: hidden;\n color: var(--ink);\n font-size: 11px;\n white-space: nowrap;\n}\n\n@media (max-width: 760px) {\n .xve-status-stats {\n display: none;\n }\n}\n";
21
+ var shell_default = "/* Frame, title bar and status bar. Mirrors docx-viewer's chrome (styles/base.css,\n chrome-titlebar.css, status-dialogs.css) with --xve-* tokens. */\n:host {\n --xl: var(--xve-primary);\n --xl-strong: color-mix(in srgb, var(--xve-primary) 70%, #000);\n --xl-soft: color-mix(in srgb, var(--xve-primary) 12%, transparent);\n --accent-soft: var(--xl-soft);\n --ink: var(--xve-foreground);\n --muted: var(--xve-muted-foreground);\n --line: var(--xve-border);\n --surface: var(--xve-card);\n --chrome: var(--xve-background);\n display: block;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n min-height: 0;\n color: var(--ink);\n font:\n 13px/1.35 'Segoe UI',\n Arial,\n sans-serif;\n}\n.xve-frame {\n position: relative;\n display: flex;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n min-height: 0;\n min-width: 0;\n flex-direction: column;\n overflow: hidden;\n background: var(--surface);\n}\n:focus-visible {\n outline: 2px solid var(--xve-ring);\n outline-offset: 1px;\n}\n[hidden],\n[data-xve-hidden] {\n display: none !important;\n}\n.xve-icon {\n flex: none;\n}\n.xve-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n height: 28px;\n padding: 0 6px;\n border: 0;\n border-radius: 4px;\n background: none;\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n.xve-icon-button:hover:not(:disabled),\n.xve-icon-button[aria-pressed='true'] {\n background: var(--xl-soft);\n}\n.xve-icon-button:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* Workspace: formula bar, grid and sheet tabs between the ribbon and the status bar. */\n.xve-workspace {\n position: relative;\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n flex-direction: column;\n background: var(--xve-sheet-bg);\n}\n.xve-formula-row {\n flex: none;\n min-width: 0;\n border-bottom: 1px solid var(--line);\n background: var(--surface);\n}\n.xve-grid-host {\n position: relative;\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n background: var(--xve-sheet-bg);\n color-scheme: light;\n}\n.xve-grid-host > * {\n flex: 1;\n min-width: 0;\n}\n.xve-tabs-row {\n flex: none;\n min-width: 0;\n border-top: 1px solid var(--line);\n background: var(--chrome);\n}\n.xve-empty {\n position: absolute;\n z-index: 5;\n inset: 0;\n display: grid;\n place-items: center;\n color: var(--muted);\n background: var(--chrome);\n}\n:host([show-formula-bar='false']) .xve-formula-row,\n:host([show-toolbar='false']) .xve-ribbon {\n display: none;\n}\n\n/* Title bar: drawn by the shared office-ui-title-bar; the mark, background and borders come from its tokens. */\n.xve-titlebar {\n display: block;\n flex: none;\n --office-title-bar-background: var(--chrome);\n --office-title-bar-foreground: var(--ink);\n --office-title-bar-border: var(--line);\n --office-title-bar-mark-background: var(--xl);\n --office-title-bar-mark-foreground: var(--xve-primary-foreground);\n}\n\n/* Ribbon tab row, right end (the shared ribbon's actions slot): Editing / Viewing, Comments and\n Share, as Excel 365 places them. */\n.xve-ribbon-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n padding-inline-start: 8px;\n}\n.xve-mode {\n position: relative;\n display: inline-flex;\n align-items: center;\n height: 28px;\n border-radius: 4px;\n color: var(--ink);\n}\n.xve-mode > .ribbon-icon {\n position: absolute;\n inset-inline-start: 8px;\n pointer-events: none;\n}\n.xve-mode-select {\n height: 28px;\n padding: 0 6px 0 30px;\n border: 1px solid var(--line);\n border-radius: 4px;\n background: var(--surface);\n color: var(--ink);\n font: inherit;\n cursor: pointer;\n}\n.xve-comments-button {\n gap: 6px;\n border: 1px solid var(--line);\n}\n.xve-share-button {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 28px;\n padding: 0 12px;\n border: 1px solid var(--xl);\n border-radius: 4px;\n background: var(--xl);\n color: var(--xve-primary-foreground);\n font: inherit;\n cursor: pointer;\n}\n.xve-share-button:hover,\n.xve-share-button[aria-pressed='true'] {\n border-color: var(--xl-strong);\n background: var(--xl-strong);\n}\n.xve-mode-select:focus-visible,\n.xve-comments-button:focus-visible,\n.xve-share-button:focus-visible {\n outline: 2px solid var(--xl);\n outline-offset: 1px;\n}\n/* Narrow windows keep the icons and drop the labels, as Office's simplified layout does. */\n@media (max-width: 900px) {\n .xve-action-label {\n display: none;\n }\n}\n\n/* Status bar: drawn by the shared office-ui-status-bar; this places it and styles the slotted stats. */\n.xve-status {\n display: block;\n flex: none;\n min-width: 0;\n border-top: 1px solid var(--line);\n}\n.xve-status-stats {\n margin-inline-end: 10px;\n overflow: hidden;\n color: var(--ink);\n font-size: 11px;\n white-space: nowrap;\n}\n\n@media (max-width: 760px) {\n .xve-status-stats {\n display: none;\n }\n}\n";
25
22
 
26
23
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/xlsx/theme/ribbon.css?raw
27
24
  var ribbon_default = "/* Ribbon: tabs, panels, groups and controls. Mirrors docx-viewer's styles/ribbon*.css. */\n.xve-ribbon-gallery[data-office-gallery] .trigger {\n flex-direction: column;\n justify-content: center;\n min-width: 56px;\n height: 78px;\n white-space: normal;\n text-align: center;\n}\n.xve-ribbon-gallery[data-office-gallery] .trigger > svg {\n width: 28px;\n height: 28px;\n}\n.xve-ribbon-gallery[data-office-gallery] .trigger > span:not([aria-hidden]) {\n max-width: 56px;\n}\n.xve-ribbon {\n position: relative;\n display: flex;\n flex: none;\n min-width: 0;\n flex-direction: column;\n background: var(--surface);\n border-bottom: 1px solid var(--line);\n}\n/* The tab row, File button and collapse button are the shared office-ui-ribbon; these colour it. */\n.xve-ribbon {\n --office-ribbon-contextual: #0f7b6c;\n}\n.xve-ribbon::part(file) {\n font-weight: 600;\n}\n.ribbon-panel {\n display: flex;\n flex: none;\n box-sizing: border-box;\n height: 104px;\n min-width: 0;\n align-items: stretch;\n gap: 4px;\n padding: 6px 10px 4px;\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n}\n.ribbon-group {\n position: relative;\n display: flex;\n flex: 0 0 auto;\n align-items: flex-start;\n min-width: 0;\n gap: 2px;\n padding: 2px 10px 18px 2px;\n border-right: 1px solid var(--line);\n}\n.ribbon-group:last-child {\n border: 0;\n}\n.ribbon-group::after {\n position: absolute;\n right: 0;\n bottom: 2px;\n left: 0;\n color: var(--muted);\n font-size: 10px;\n line-height: 1.1;\n text-align: center;\n white-space: nowrap;\n content: attr(data-caption);\n}\n.ribbon-caption-sizer {\n position: absolute;\n visibility: hidden;\n font-size: 10px;\n white-space: nowrap;\n}\n.ribbon-stack {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n}\n.ribbon-stack > .ribbon-stack {\n flex-direction: row;\n align-items: center;\n}\n.ribbon-separator {\n align-self: stretch;\n width: 1px;\n margin: 2px 3px;\n background: var(--line);\n}\n.ribbon-stack > .ribbon-separator {\n width: auto;\n height: 1px;\n}\n.ribbon-stack > .ribbon-stack > .ribbon-separator {\n width: 1px;\n height: 20px;\n}\n\n/* Buttons */\n.ribbon-group button {\n flex: none;\n min-width: 28px;\n height: 28px;\n max-width: 100%;\n padding: 0 4px;\n overflow: hidden;\n border: 1px solid transparent;\n border-radius: 4px;\n background: transparent;\n color: var(--ink);\n font:\n 400 12px 'Segoe UI',\n Arial,\n sans-serif;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: pointer;\n}\n.ribbon-group button:hover:not(:disabled) {\n border-color: var(--line);\n background: var(--accent-soft);\n}\n.ribbon-group button[aria-pressed='true'],\n.ribbon-group button[aria-expanded='true'] {\n border-color: color-mix(in srgb, var(--xl) 45%, transparent);\n background: color-mix(in srgb, var(--xl) 20%, transparent);\n}\n.ribbon-group button:active:not(:disabled) {\n background: color-mix(in srgb, var(--xl) 28%, transparent);\n}\n.ribbon-group button:focus-visible {\n outline: 2px solid var(--xl);\n outline-offset: -2px;\n}\n.ribbon-group button:disabled,\n.ribbon-group input:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.ribbon-group button.ribbon-small {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 24px;\n padding: 0 3px;\n}\n.ribbon-group button.ribbon-small.ribbon-labelled {\n justify-content: flex-start;\n padding: 0 6px 0 4px;\n}\n.ribbon-group button.ribbon-large {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-start;\n gap: 2px;\n width: auto;\n min-width: 48px;\n height: 76px;\n padding: 3px 3px 2px;\n font-size: 11px;\n line-height: 1.15;\n white-space: normal;\n text-align: center;\n}\n.ribbon-large > .ribbon-icon:last-child:not(:first-child) {\n margin-top: auto;\n}\n.ribbon-large > span,\n.ribbon-caption {\n /* Two balanced lines (see ribbon/caption.ts); the button grows to the widest. */\n white-space: pre;\n}\n.ribbon-icon {\n flex: none;\n}\n.ribbon-icon[data-icon$='-caret'],\n.ribbon-icon[data-icon='caret'] {\n opacity: 0.8;\n}\n\n/* Split buttons: a main half and a caret that opens a menu or the colour grid. */\n.ribbon-split {\n display: inline-flex;\n align-items: stretch;\n}\n.ribbon-split.ribbon-split-large {\n flex-direction: column;\n}\n.ribbon-group .ribbon-split > button:first-child {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.ribbon-group .ribbon-split > .ribbon-caret {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n min-width: 14px;\n padding: 0;\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.ribbon-group .ribbon-split-large > button:first-child {\n height: 46px;\n border-radius: 4px 4px 0 0;\n}\n.ribbon-group .ribbon-split-large > .ribbon-caret {\n width: auto;\n height: 26px;\n border-radius: 0 0 4px 4px;\n font-size: 11px;\n flex-direction: column;\n gap: 0;\n}\n.ribbon-color {\n position: relative;\n}\n.ribbon-group button.ribbon-color::after {\n position: absolute;\n right: 4px;\n bottom: 2px;\n left: 4px;\n height: 3px;\n background: var(--bar, transparent);\n outline: 1px solid var(--line);\n content: '';\n}\n\n/* Editable combo boxes (font name and size) and plain selects. */\n.ribbon-combo {\n display: inline-flex;\n align-items: stretch;\n height: 24px;\n border: 1px solid var(--line);\n border-radius: 3px;\n background: var(--surface);\n}\n.ribbon-combo:focus-within {\n border-color: var(--xl);\n outline: 1px solid var(--xl);\n}\n.ribbon-combo input {\n flex: 1;\n min-width: 0;\n padding: 0 6px;\n border: 0;\n background: transparent;\n color: var(--ink);\n font:\n 12px 'Segoe UI',\n Arial,\n sans-serif;\n outline: none;\n}\n.ribbon-group .ribbon-combo .ribbon-caret {\n width: 18px;\n min-width: 18px;\n height: 100%;\n padding: 0;\n border: 0;\n border-radius: 0;\n}\n\n/* Plain drop-downs (Number Format) and gallery previews. */\n.ribbon-group select.ribbon-select {\n height: 24px;\n padding: 0 6px;\n border: 1px solid var(--line);\n border-radius: 3px;\n background: var(--surface);\n color: var(--ink);\n font:\n 12px 'Segoe UI',\n Arial,\n sans-serif;\n text-overflow: ellipsis;\n}\n.ribbon-group select.ribbon-select:focus-visible {\n outline: 2px solid var(--xl);\n outline-offset: -1px;\n}\n.ribbon-gallery-preview {\n display: block;\n height: 36px;\n margin-bottom: 3px;\n border: 1px solid var(--line);\n border-radius: 2px;\n background: #fff;\n color: #1f1f1f;\n font-size: 11px;\n line-height: 36px;\n text-align: center;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.ribbon-gallery-preview > svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n/* Dialog launcher: the corner arrow of a group. */\n.ribbon-group button.ribbon-launcher {\n position: absolute;\n right: 2px;\n bottom: 0;\n z-index: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n min-width: 14px;\n height: 14px;\n padding: 0;\n color: var(--muted);\n}\n\n/* Collapsed groups: a narrow ribbon folds its rightmost groups into one dropdown each. */\n.ribbon-group > .ribbon-overflow-button {\n display: none;\n}\n.ribbon-group[data-collapsed] {\n padding-right: 4px;\n}\n.ribbon-group[data-collapsed] > :not(.ribbon-overflow-button) {\n display: none !important;\n}\n.ribbon-group[data-collapsed] > button.ribbon-overflow-button {\n display: flex;\n}\n.ribbon-group[data-collapsed]::after {\n display: none;\n}\n.ribbon-overflow-panel .ribbon-group {\n height: 84px;\n padding-right: 2px;\n border: 0;\n}\n\n/* KeyTips */\n.xve-keytip {\n position: absolute;\n bottom: -2px;\n left: 50%;\n z-index: 70;\n min-width: 16px;\n padding: 1px 4px;\n border: 1px solid var(--line);\n border-radius: 3px;\n background: #1f1f1f;\n color: #fff;\n font:\n 600 11px/14px 'Segoe UI',\n Arial,\n sans-serif;\n text-align: center;\n pointer-events: none;\n transform: translateX(-50%);\n}\n.xve-keytip-command {\n position: fixed;\n bottom: auto;\n}\n\n/* Accent hues on command icons, as in Office. */\n:host {\n --ribbon-accent-blue: #2b7cd3;\n --ribbon-accent-green: #2f9e5f;\n --ribbon-accent-red: #d13438;\n --ribbon-accent-orange: #d9822b;\n}\n@media (prefers-color-scheme: dark) {\n :host(:not([theme='light'])) {\n --ribbon-accent-blue: #5ea5f0;\n --ribbon-accent-green: #5cc98a;\n --ribbon-accent-red: #f07a7d;\n --ribbon-accent-orange: #f0a35e;\n }\n}\n:host([theme='dark']) {\n --ribbon-accent-blue: #5ea5f0;\n --ribbon-accent-green: #5cc98a;\n --ribbon-accent-red: #f07a7d;\n --ribbon-accent-orange: #f0a35e;\n}\n.ribbon-icon[data-accent='blue'] {\n color: var(--ribbon-accent-blue);\n}\n.ribbon-icon[data-accent='green'] {\n color: var(--ribbon-accent-green);\n}\n.ribbon-icon[data-accent='red'] {\n color: var(--ribbon-accent-red);\n}\n.ribbon-icon[data-accent='orange'] {\n color: var(--ribbon-accent-orange);\n}\n";
@@ -43,7 +40,10 @@ var themeTokenText = `:host{${declarations(lightTheme, "light")}}
43
40
  :host([theme="light"]){${declarations(lightTheme, "light")}}`;
44
41
  var editorStyleText = [
45
42
  themeTokenText,
46
- office_bridge_default,
43
+ // The shared elements read --office-*; feed them from the editor's --xve-* theme.
44
+ shadcnBridge(":host", "--xve-", {
45
+ extra: { "--office-font": "'Segoe UI', system-ui, -apple-system, sans-serif" }
46
+ }),
47
47
  shell_default,
48
48
  ribbon_default,
49
49
  popups_default,
@@ -363,7 +363,7 @@ function renderInfo(page) {
363
363
  paragraph(
364
364
  page,
365
365
  t(
366
- "Charts are shown but not edited; pivot tables, sparklines, macros and other unsupported parts are kept in the file but not shown."
366
+ "Charts and sparklines are shown but not edited; pivot tables, macros and other unsupported parts are kept in the file but not shown."
367
367
  ),
368
368
  "xve-backstage-muted"
369
369
  )
@@ -856,12 +856,105 @@ function renderCustomize(page) {
856
856
  ...sections
857
857
  );
858
858
  }
859
+
860
+ // src/xlsx/backstage/pages-share.ts
861
+ var STATUS = {
862
+ off: "Not sharing.",
863
+ connecting: "Connecting...",
864
+ connected: "Connected.",
865
+ disconnected: "Disconnected. Changes are kept and sent when the connection returns.",
866
+ error: "The connection failed. Changes are kept and sent when the connection returns."
867
+ };
868
+ var suggestRoom = () => `book-${Math.random().toString(36).slice(2, 8)}`;
869
+ function renderShare(page) {
870
+ const { t, doc } = page;
871
+ const share = page.host.share;
872
+ if (!share) {
873
+ page.content.replaceChildren(heading(page, t("Share")));
874
+ return;
875
+ }
876
+ const state = share.state();
877
+ const status = paragraph(page, t(STATUS[state.status]), "xve-share-status");
878
+ status.setAttribute("role", "status");
879
+ const say = (message) => {
880
+ status.textContent = message;
881
+ };
882
+ const children = [
883
+ heading(page, t("Share")),
884
+ paragraph(
885
+ page,
886
+ t(
887
+ "Edit this workbook together with other people. Everyone who joins the same session sees changes as they are made."
888
+ )
889
+ )
890
+ ];
891
+ if (state.active) {
892
+ children.push(paragraph(page, t("Session: {room}", { room: state.roomId ?? "" })));
893
+ if (state.status === "connected" && !state.synced) say(t("Joining the session..."));
894
+ } else {
895
+ const room = doc.createElement("input");
896
+ room.type = "text";
897
+ room.className = "xve-share-room";
898
+ room.maxLength = 128;
899
+ room.autocomplete = "off";
900
+ room.spellcheck = false;
901
+ room.value = suggestRoom();
902
+ children.push(labelled(page, t("Session name"), room));
903
+ const start = primary(page, t("Start sharing"), () => {
904
+ start.disabled = true;
905
+ say(t("Connecting..."));
906
+ share.start(room.value.trim()).catch((error) => {
907
+ start.disabled = false;
908
+ say(t(error instanceof Error ? error.message : String(error)));
909
+ });
910
+ });
911
+ start.dataset.share = "start";
912
+ children.push(start);
913
+ }
914
+ children.push(status);
915
+ if (state.active) {
916
+ definePresence();
917
+ const people = doc.createElement("office-ui-presence");
918
+ people.className = "xve-share-people";
919
+ people.setAttribute("label", t("People in this session"));
920
+ people.participants = participants(state);
921
+ const stop = primary(page, t("Stop sharing"), () => share.stop());
922
+ stop.dataset.share = "stop";
923
+ children.push(people, stop);
924
+ }
925
+ children.push(
926
+ paragraph(
927
+ page,
928
+ t(
929
+ "Cell values, formulas, formats, sheets, rows, columns, merges and names are shared. Charts, pictures, comments, conditional formats, data validation, tables and page setup stay in this window."
930
+ ),
931
+ "xve-backstage-muted"
932
+ ),
933
+ paragraph(
934
+ page,
935
+ t(
936
+ "Without a server set by the page, a session connects only windows and tabs of this browser. Nothing is uploaded."
937
+ ),
938
+ "xve-backstage-muted"
939
+ )
940
+ );
941
+ page.content.replaceChildren(...children);
942
+ }
943
+ function participants(state) {
944
+ return state.people.map((person) => ({
945
+ id: String(person.clientId),
946
+ name: person.name,
947
+ color: person.color,
948
+ ...person.self ? { self: true } : {}
949
+ }));
950
+ }
859
951
  var PAGES = [
860
952
  ["info", "Info"],
861
953
  ["new", "New"],
862
954
  ["open", "Open"],
863
955
  ["saveAs", "Save As"],
864
956
  ["print", "Print"],
957
+ ["share", "Share"],
865
958
  ["export", "Export"],
866
959
  ["options", "Options"],
867
960
  ["customize", "Customize Ribbon"]
@@ -872,6 +965,7 @@ var RENDERERS = {
872
965
  open: renderOpen,
873
966
  saveAs: renderSaveAs,
874
967
  print: renderPrint,
968
+ share: renderShare,
875
969
  export: renderExport,
876
970
  options: renderOptions,
877
971
  customize: renderCustomize
@@ -934,6 +1028,7 @@ function createBackstage(host) {
934
1028
  item("save", "Save"),
935
1029
  page("saveAs"),
936
1030
  page("print"),
1031
+ page("share"),
937
1032
  page("export"),
938
1033
  page("options", "footer"),
939
1034
  page("customize", "footer")
@@ -953,7 +1048,10 @@ function createBackstage(host) {
953
1048
  get isOpen() {
954
1049
  return element.open;
955
1050
  },
956
- relocalize
1051
+ relocalize,
1052
+ refresh(page) {
1053
+ if (element.open && current === page) show(page);
1054
+ }
957
1055
  };
958
1056
  }
959
1057
 
@@ -3083,46 +3181,61 @@ function openGoToSpecial(ctx) {
3083
3181
  }
3084
3182
 
3085
3183
  // src/xlsx/dialogs/comments-list.ts
3184
+ function excelCommentThreads(comments) {
3185
+ return [...comments].sort((a, b) => a.address.row - b.address.row || a.address.col - b.address.col).map((comment) => {
3186
+ const id = formatAddress(comment.address);
3187
+ return {
3188
+ id,
3189
+ anchorLabel: id,
3190
+ resolved: false,
3191
+ comments: [
3192
+ { id: `${id}:0`, author: comment.author, text: comment.text },
3193
+ ...(comment.replies ?? []).map((reply, index) => ({
3194
+ id: `${id}:${index + 1}`,
3195
+ author: reply.author,
3196
+ text: reply.text,
3197
+ ...reply.date ? { created: reply.date } : {}
3198
+ }))
3199
+ ]
3200
+ };
3201
+ });
3202
+ }
3086
3203
  function openCommentsList(ctx) {
3087
3204
  const ws = ctx.workbook()?.sheets[ctx.activeSheet()];
3088
- const comments = [...ws?.comments ?? []].sort(
3089
- (a, b) => a.address.row - b.address.row || a.address.col - b.address.col
3090
- );
3205
+ const comments = ws?.comments ?? [];
3206
+ const threads = excelCommentThreads(comments);
3091
3207
  let close;
3092
3208
  let chosen;
3093
- const activate = (value) => {
3094
- const comment = comments[Number(value)];
3209
+ defineCommentsPane(ctx.host.ownerDocument.defaultView?.customElements);
3210
+ const pane = ctx.host.ownerDocument.createElement(
3211
+ "office-ui-comments-pane"
3212
+ );
3213
+ pane.className = "xve-comments-list";
3214
+ pane.listOnly = true;
3215
+ pane.labels = {
3216
+ heading: "",
3217
+ list: ctx.t("Comments"),
3218
+ empty: ctx.t("There are no comments on this sheet.")
3219
+ };
3220
+ pane.threads = threads;
3221
+ pane.activeThreadId = threads[0]?.id ?? null;
3222
+ pane.addEventListener("thread-select", (event) => {
3223
+ const id = event.detail.threadId;
3224
+ const comment = comments.find((item) => formatAddress(item.address) === id);
3095
3225
  if (!comment) return;
3096
3226
  const at = { ...comment.address };
3097
- chosen = formatAddress(at);
3227
+ chosen = id;
3098
3228
  selectOn(ctx, ctx.activeSheet(), { start: at, end: at });
3099
3229
  close?.();
3100
- };
3101
- const list = listBox(ctx, "Comments", void 0, activate);
3102
- list.setItems(
3103
- comments.map(
3104
- (c, i) => [
3105
- String(i),
3106
- `${formatAddress(c.address)} ${c.author}: ${c.text.replace(/\s+/g, " ")}`
3107
- ]
3108
- )
3109
- );
3110
- for (const option of list.element.querySelectorAll('[role="option"]'))
3111
- option.addEventListener(
3112
- "click",
3113
- () => option.dataset.value !== void 0 && activate(option.dataset.value)
3114
- );
3115
- const first = comments.length ? "0" : void 0;
3116
- if (first) list.select(first);
3117
- const body = comments.length ? [list.element] : [text(ctx, "There are no comments on this sheet.")];
3230
+ });
3118
3231
  return showDialog(
3119
3232
  ctx,
3120
3233
  {
3121
3234
  name: "comments-list",
3122
3235
  heading: "Comments",
3123
3236
  okLabel: null,
3124
- body,
3125
- opened: () => list.element.focus()
3237
+ body: [pane],
3238
+ opened: () => pane.focus()
3126
3239
  },
3127
3240
  (open) => {
3128
3241
  close = open.close;
@@ -4067,7 +4180,8 @@ var SUPPORTED_FEATURES = [
4067
4180
  "Freeze panes"
4068
4181
  ];
4069
4182
  var UNSUPPORTED_FEATURES = [
4070
- "Pivot tables and sparklines (kept in the file but not shown)",
4183
+ "Pivot tables (kept in the file but not shown)",
4184
+ "Creating or editing sparklines (existing ones are shown)",
4071
4185
  "Show Formulas",
4072
4186
  "Manual calculation",
4073
4187
  "Outline (grouping) for columns",
@@ -11092,10 +11206,10 @@ function gridCommands(host) {
11092
11206
  icon: "undo",
11093
11207
  shortcut: "Ctrl+Z",
11094
11208
  editing: true,
11095
- enabled: (ctx) => Boolean(ctx.session()?.canUndo()) || host.isEditing(),
11209
+ enabled: (ctx) => Boolean(historyOf(ctx)?.canUndo()) || host.isEditing(),
11096
11210
  run: (ctx) => {
11097
11211
  if (host.isEditing()) return host.cancelEdit();
11098
- ctx.session()?.undo();
11212
+ historyOf(ctx)?.undo();
11099
11213
  }
11100
11214
  },
11101
11215
  {
@@ -11104,8 +11218,8 @@ function gridCommands(host) {
11104
11218
  icon: "redo",
11105
11219
  shortcut: "Ctrl+Y",
11106
11220
  editing: true,
11107
- enabled: (ctx) => Boolean(ctx.session()?.canRedo()),
11108
- run: (ctx) => void ctx.session()?.redo()
11221
+ enabled: (ctx) => Boolean(historyOf(ctx)?.canRedo()),
11222
+ run: (ctx) => void historyOf(ctx)?.redo()
11109
11223
  },
11110
11224
  {
11111
11225
  id: "edit.select-all",
@@ -11880,6 +11994,82 @@ var GridSelection = class {
11880
11994
  return g.fCols > 0 ? 0 : this.#view.metrics.colAt(g.scrollLeft);
11881
11995
  }
11882
11996
  };
11997
+
11998
+ // src/xlsx/grid/sparkline.ts
11999
+ var SVG = "http://www.w3.org/2000/svg";
12000
+ var fixed = (n) => String(Math.round(n * 100) / 100);
12001
+ function svgElement(doc, tag, attrs) {
12002
+ const node = doc.createElementNS(SVG, tag);
12003
+ for (const [name, value] of Object.entries(attrs)) node.setAttribute(name, value);
12004
+ return node;
12005
+ }
12006
+ function sparklineSvg(doc, view, w, h2, zoom) {
12007
+ const scale = zoom / 100;
12008
+ const stroke = Math.max(0.5, view.lineWeightPt * (96 / 72) * scale);
12009
+ const radius = view.markers.length ? Math.max(1.5 * scale, stroke * 1.25) : 0;
12010
+ const padX = 2 * scale + (view.type === "line" ? Math.max(radius, stroke / 2) : 0);
12011
+ const padY = 2 * scale + (view.type === "line" ? Math.max(radius, stroke / 2) : 0);
12012
+ const innerW = Math.max(0, w - 2 * padX);
12013
+ const innerH = Math.max(0, h2 - 2 * padY);
12014
+ const px = (x) => padX + x * innerW;
12015
+ const py = (y) => padY + y * innerH;
12016
+ const svg = svgElement(doc, "svg", {
12017
+ class: "xg-spark",
12018
+ width: fixed(w),
12019
+ height: fixed(h2),
12020
+ "aria-hidden": "true"
12021
+ });
12022
+ if (view.axis)
12023
+ svg.append(
12024
+ svgElement(doc, "line", {
12025
+ x1: fixed(padX),
12026
+ x2: fixed(padX + innerW),
12027
+ y1: fixed(py(view.axis.y)),
12028
+ y2: fixed(py(view.axis.y)),
12029
+ stroke: view.axis.color,
12030
+ "stroke-width": fixed(Math.max(1, scale)),
12031
+ "shape-rendering": "crispEdges"
12032
+ })
12033
+ );
12034
+ for (const column of view.columns) {
12035
+ const height = Math.max(1, column.h * innerH);
12036
+ const top = column.h * innerH < 1 && column.y >= 0.5 ? py(column.y) - 1 : py(column.y);
12037
+ svg.append(
12038
+ svgElement(doc, "rect", {
12039
+ x: fixed(px(column.x)),
12040
+ y: fixed(top),
12041
+ width: fixed(Math.max(1, column.w * innerW)),
12042
+ height: fixed(height),
12043
+ fill: column.color
12044
+ })
12045
+ );
12046
+ }
12047
+ for (const line of view.lines) {
12048
+ if (line.length === 1) continue;
12049
+ svg.append(
12050
+ svgElement(doc, "polyline", {
12051
+ points: line.map((p) => `${fixed(px(p.x))},${fixed(py(p.y))}`).join(" "),
12052
+ fill: "none",
12053
+ stroke: view.color,
12054
+ "stroke-width": fixed(stroke),
12055
+ "stroke-linejoin": "round",
12056
+ "stroke-linecap": "round"
12057
+ })
12058
+ );
12059
+ }
12060
+ for (const marker of view.markers)
12061
+ svg.append(
12062
+ svgElement(doc, "circle", {
12063
+ cx: fixed(px(marker.x)),
12064
+ cy: fixed(py(marker.y)),
12065
+ r: fixed(radius),
12066
+ fill: marker.color
12067
+ })
12068
+ );
12069
+ return svg;
12070
+ }
12071
+
12072
+ // src/xlsx/grid/cell-layer.ts
11883
12073
  var viewKeys = /* @__PURE__ */ new WeakMap();
11884
12074
  var viewKey = (view) => {
11885
12075
  let key = viewKeys.get(view);
@@ -12006,6 +12196,7 @@ function paintCell(doc, node, item, zoom) {
12006
12196
  style.backgroundImage = fill?.image ?? "";
12007
12197
  node.className = `xg-c${item.overflowing ? " xg-c-over" : ""}${view.hasHyperlink ? " xg-link" : ""}${item.merged ? " xg-merged" : ""}`;
12008
12198
  node.replaceChildren();
12199
+ if (view.sparkline) node.append(sparklineSvg(doc, view.sparkline, item.w, item.h, zoom));
12009
12200
  if (view.dataBar) {
12010
12201
  const bar = h(doc, "div", "xg-db");
12011
12202
  const width = Math.max(0, Math.min(1, view.dataBar.fraction)) * (item.w - 4);
@@ -12764,6 +12955,7 @@ var GRID_CSS = `
12764
12955
  .xg-link{cursor:pointer}
12765
12956
  .xg-b{position:absolute;left:-1px;top:-1px;box-sizing:border-box;pointer-events:none}
12766
12957
  .xg-diag{position:absolute;left:0;top:0;pointer-events:none}
12958
+ .xg-spark{position:absolute;left:0;top:0;pointer-events:none;overflow:hidden}
12767
12959
  .xg-db{position:absolute;left:2px;top:2px;bottom:2px;border:1px solid;box-sizing:border-box}
12768
12960
  .xg-ic{position:absolute;left:2px;top:50%;transform:translateY(-50%)}
12769
12961
  .xg-cm{position:absolute;right:0;top:0;width:0;height:0;border-left:6px solid transparent;border-top:6px solid #d13438;z-index:2}
@@ -12781,6 +12973,12 @@ var GRID_CSS = `
12781
12973
  @media (prefers-reduced-motion:reduce){.xg-ants{animation:none}}
12782
12974
  .xg-fill-preview{position:absolute;box-sizing:border-box;border:1px dashed #666}
12783
12975
  .xg-refbox{position:absolute;box-sizing:border-box;border:2px solid}
12976
+ .xg-remote-layer{position:absolute;left:0;top:0;pointer-events:none;z-index:3}
12977
+ .xg-remote{position:absolute;box-sizing:border-box;border:2px solid}
12978
+ .xg-remote[hidden]{display:none}
12979
+ .xg-remote-tag{position:absolute;left:-2px;bottom:100%;max-width:160px;padding:0 4px;height:16px;line-height:16px;
12980
+ font:600 11px/16px var(--xve-font,system-ui,sans-serif);color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-radius:2px 2px 2px 0}
12981
+ .xg-remote[data-tag="inside"] .xg-remote-tag{bottom:auto;top:0;left:0;border-radius:0 0 2px 0}
12784
12982
  .xg-hbox{background:var(--xve-header-bg,#f3f3f3);z-index:4}
12785
12983
  .xg-hdr-plane{position:absolute;left:0;top:0;will-change:transform}
12786
12984
  .xg-hd{position:absolute;box-sizing:border-box;display:flex;align-items:center;justify-content:center;
@@ -12986,6 +13184,66 @@ var ValidationList = class {
12986
13184
  if (refocus) this.#onDone();
12987
13185
  }
12988
13186
  };
13187
+ var TAG_HEIGHT = 16;
13188
+ var RemoteLayer = class {
13189
+ constructor(doc) {
13190
+ this.doc = doc;
13191
+ this.element = h(doc, "div", "xg-remote-layer");
13192
+ }
13193
+ doc;
13194
+ element;
13195
+ #nodes = [];
13196
+ render(boxes) {
13197
+ boxes.forEach((box, index) => {
13198
+ let node = this.#nodes[index];
13199
+ if (!node) {
13200
+ node = h(this.doc, "div", "xg-remote");
13201
+ node.append(h(this.doc, "span", "xg-remote-tag"));
13202
+ this.#nodes.push(node);
13203
+ this.element.append(node);
13204
+ }
13205
+ node.hidden = false;
13206
+ place(node, box.x - 1, box.y - 1, box.w + 1, box.h + 1);
13207
+ node.style.borderColor = box.color;
13208
+ node.dataset.clientId = String(box.clientId);
13209
+ node.dataset.tag = box.tag;
13210
+ node.title = box.name;
13211
+ const tag = node.firstElementChild;
13212
+ tag.textContent = box.label;
13213
+ tag.style.backgroundColor = box.color;
13214
+ });
13215
+ for (let i = boxes.length; i < this.#nodes.length; i++) {
13216
+ const node = this.#nodes[i];
13217
+ if (node) node.hidden = true;
13218
+ }
13219
+ }
13220
+ };
13221
+ function installRemoteSelections(view) {
13222
+ const ctx = view.ctx;
13223
+ const layers = QUADRANTS.map((q) => {
13224
+ const layer = new RemoteLayer(view.doc);
13225
+ view.quads[q].plane.append(layer.element);
13226
+ return layer;
13227
+ });
13228
+ const hook = () => {
13229
+ const peers = ctx.remoteSelections?.() ?? [];
13230
+ const boxes = peers.length ? layoutRemoteSelections(view.metrics, peers, {
13231
+ sheet: view.sheetIndex(),
13232
+ maxRow: view.extentRow,
13233
+ maxCol: view.extentCol,
13234
+ tagHeight: TAG_HEIGHT
13235
+ }) : [];
13236
+ for (const layer of layers) layer.render(boxes);
13237
+ };
13238
+ view.hooks.add(hook);
13239
+ const stop = ctx.onRemoteSelectionsChange?.(() => view.schedule());
13240
+ hook();
13241
+ return () => {
13242
+ stop?.();
13243
+ view.hooks.delete(hook);
13244
+ for (const layer of layers) layer.element.remove();
13245
+ };
13246
+ }
12989
13247
  var structuralKinds = /* @__PURE__ */ new Set(["structure", "sheets", "view", "undo", "redo", "batch"]);
12990
13248
  function mountGrid(ctx, container) {
12991
13249
  ensureStyle(ctx.root, "xg", GRID_CSS);
@@ -12998,6 +13256,7 @@ function mountGrid(ctx, container) {
12998
13256
  const drawings = new DrawingLayer(view);
12999
13257
  const list = new ValidationList(view, () => editor.focus());
13000
13258
  const a11y = new GridA11y(view, sink);
13259
+ const removeRemote = installRemoteSelections(view);
13001
13260
  let marqueeSheet = -1;
13002
13261
  const clipboard = createGridClipboard({
13003
13262
  ctx,
@@ -13141,6 +13400,7 @@ function mountGrid(ctx, container) {
13141
13400
  drawings.destroy();
13142
13401
  editor.destroy();
13143
13402
  a11y.destroy();
13403
+ removeRemote();
13144
13404
  view.destroy();
13145
13405
  };
13146
13406
  }
@@ -14341,6 +14601,58 @@ function createRibbon(ctx, handlers) {
14341
14601
  }
14342
14602
  };
14343
14603
  }
14604
+
14605
+ // src/xlsx/ribbon-actions.ts
14606
+ function createRibbonActions(ctx, handlers) {
14607
+ const doc = ctx.host.ownerDocument;
14608
+ const element = el2(doc, "div", "xve-ribbon-actions");
14609
+ element.slot = "actions";
14610
+ const modeBox = el2(doc, "label", "xve-mode");
14611
+ const mode = el2(doc, "select", "xve-mode-select");
14612
+ mode.append(new Option("", "editing"), new Option("", "viewing"));
14613
+ mode.addEventListener("change", () => handlers.setReadOnly(mode.value === "viewing"));
14614
+ modeBox.append(ribbonIcon(doc, "pencil", 16), mode);
14615
+ const labelled2 = (className, icon7) => {
14616
+ const button2 = el2(doc, "button", className);
14617
+ button2.type = "button";
14618
+ const text3 = el2(doc, "span", "xve-action-label");
14619
+ button2.append(ribbonIcon(doc, icon7, 16), text3);
14620
+ return { button: button2, text: text3 };
14621
+ };
14622
+ const comments = labelled2("xve-icon-button xve-comments-button", "comments");
14623
+ comments.button.addEventListener("click", () => void ctx.commands.run("review.show-comments"));
14624
+ const share = labelled2("xve-share-button", "share");
14625
+ share.button.addEventListener("click", () => void ctx.commands.run("file.share"));
14626
+ element.append(modeBox, comments.button, share.button);
14627
+ const relocalize = () => {
14628
+ mode.setAttribute("aria-label", ctx.t("Editing mode"));
14629
+ mode.title = ctx.t("Editing mode");
14630
+ mode.options[0].textContent = ctx.t("Editing");
14631
+ mode.options[1].textContent = ctx.t("Viewing");
14632
+ comments.text.textContent = ctx.t("Comments");
14633
+ comments.button.title = ctx.t("Show comments");
14634
+ share.text.textContent = ctx.t("Share");
14635
+ share.button.title = ctx.t("Share");
14636
+ refresh();
14637
+ };
14638
+ const refresh = () => {
14639
+ mode.value = ctx.readOnly() ? "viewing" : "editing";
14640
+ share.button.hidden = !ctx.commands.get("file.share") || handlers.isHidden("file.share");
14641
+ share.button.setAttribute("aria-pressed", String(Boolean(handlers.collaboration?.().active)));
14642
+ const showComments = ctx.commands.get("review.show-comments");
14643
+ comments.button.hidden = !showComments;
14644
+ if (!showComments) return;
14645
+ let pressed = false;
14646
+ try {
14647
+ pressed = showComments.checked?.(ctx) ?? false;
14648
+ } catch {
14649
+ pressed = false;
14650
+ }
14651
+ comments.button.setAttribute("aria-pressed", String(pressed));
14652
+ };
14653
+ relocalize();
14654
+ return { element, refresh, relocalize };
14655
+ }
14344
14656
  var hasWorkbook = (ctx) => Boolean(ctx.workbook());
14345
14657
  function adjacentSheet(ctx, step2) {
14346
14658
  const sheets = ctx.workbook()?.sheets ?? [];
@@ -14372,6 +14684,12 @@ function shellCommands(handlers) {
14372
14684
  run: () => handlers.openBackstage("saveAs")
14373
14685
  },
14374
14686
  { id: "file.info", label: "Info", icon: "info", run: () => handlers.openBackstage("info") },
14687
+ {
14688
+ id: "file.share",
14689
+ label: "Share",
14690
+ icon: "share",
14691
+ run: () => handlers.openBackstage("share")
14692
+ },
14375
14693
  {
14376
14694
  id: "file.options",
14377
14695
  label: "Options",
@@ -14384,8 +14702,8 @@ function shellCommands(handlers) {
14384
14702
  icon: "undo",
14385
14703
  shortcut: "Ctrl+Z",
14386
14704
  editing: true,
14387
- enabled: (ctx) => ctx.session()?.canUndo() ?? false,
14388
- run: (ctx) => void ctx.session()?.undo()
14705
+ enabled: (ctx) => historyOf(ctx)?.canUndo() ?? false,
14706
+ run: (ctx) => void historyOf(ctx)?.undo()
14389
14707
  },
14390
14708
  {
14391
14709
  id: "edit.redo",
@@ -14393,8 +14711,8 @@ function shellCommands(handlers) {
14393
14711
  icon: "redo",
14394
14712
  shortcut: "Ctrl+Y",
14395
14713
  editing: true,
14396
- enabled: (ctx) => ctx.session()?.canRedo() ?? false,
14397
- run: (ctx) => void ctx.session()?.redo()
14714
+ enabled: (ctx) => historyOf(ctx)?.canRedo() ?? false,
14715
+ run: (ctx) => void historyOf(ctx)?.redo()
14398
14716
  },
14399
14717
  {
14400
14718
  id: "sheet.next",
@@ -14517,7 +14835,7 @@ function createStatusBar(ctx, handlers) {
14517
14835
  element.setAttribute("part", "status-bar");
14518
14836
  element.setAttribute("role", "status");
14519
14837
  const stats = el2(doc, "span", "xve-status-stats");
14520
- stats.slot = "end";
14838
+ stats.slot = "summary";
14521
14839
  const shown2 = /* @__PURE__ */ new Set(["average", "count", "sum"]);
14522
14840
  stats.addEventListener("contextmenu", (event) => {
14523
14841
  event.preventDefault();
@@ -14603,22 +14921,17 @@ function createTitleBar(ctx, handlers) {
14603
14921
  const element = doc.createElement("office-ui-title-bar");
14604
14922
  element.className = "xve-titlebar";
14605
14923
  element.setAttribute("part", "title-bar");
14606
- const actions = el2(doc, "div", "xve-title-actions");
14607
- actions.slot = "actions";
14608
- const comments = el2(doc, "button", "xve-icon-button");
14609
- comments.type = "button";
14610
- comments.append(ribbonIcon(doc, "comments", 16));
14611
- comments.addEventListener("click", () => void ctx.commands.run("review.show-comments"));
14612
- const mode = el2(doc, "select", "xve-mode-select");
14613
- mode.append(new Option("", "editing"), new Option("", "viewing"));
14614
- mode.addEventListener("change", () => handlers.setReadOnly(mode.value === "viewing"));
14615
- actions.append(comments, mode);
14616
- element.append(actions);
14924
+ definePresence();
14925
+ const people = doc.createElement("office-ui-presence");
14926
+ people.className = "xve-title-people";
14927
+ people.slot = "collaboration";
14928
+ people.setAttribute("max", "3");
14929
+ people.hidden = true;
14930
+ element.append(people);
14617
14931
  let fileName = "";
14618
14932
  let saveState = "saved";
14619
14933
  const render3 = () => {
14620
- const session = ctx.session();
14621
- const undoLabel = session?.undoLabel();
14934
+ const undoLabel = historyOf(ctx)?.undoLabel();
14622
14935
  const entries = [
14623
14936
  { id: "save", icon: "save", label: ctx.t("Save"), title: `${ctx.t("Save")} (Ctrl+S)` },
14624
14937
  {
@@ -14669,31 +14982,15 @@ function createTitleBar(ctx, handlers) {
14669
14982
  ctx.grid()?.focus();
14670
14983
  void ctx.commands.run(command.id);
14671
14984
  });
14672
- const label = (control, text3) => {
14673
- const translated = ctx.t(text3);
14674
- control.setAttribute("aria-label", translated);
14675
- control.title = translated;
14676
- };
14677
14985
  const relocalize = () => {
14678
- label(comments, "Show comments");
14679
- mode.setAttribute("aria-label", ctx.t("Editing mode"));
14680
- mode.options[0].textContent = ctx.t("Editing");
14681
- mode.options[1].textContent = ctx.t("Viewing");
14986
+ people.setAttribute("label", ctx.t("People in this session"));
14682
14987
  render3();
14683
14988
  };
14684
14989
  const refresh = () => {
14685
- mode.value = ctx.readOnly() ? "viewing" : "editing";
14686
- const showComments = ctx.commands.get("review.show-comments");
14687
- comments.hidden = !showComments;
14688
- if (showComments) {
14689
- let pressed = false;
14690
- try {
14691
- pressed = showComments.checked?.(ctx) ?? false;
14692
- } catch {
14693
- pressed = false;
14694
- }
14695
- comments.setAttribute("aria-pressed", String(pressed));
14696
- }
14990
+ const sharing = handlers.collaboration?.();
14991
+ people.hidden = !sharing?.active;
14992
+ const next = sharing?.active ? participants(sharing) : [];
14993
+ if (JSON.stringify(next) !== JSON.stringify(people.participants)) people.participants = next;
14697
14994
  render3();
14698
14995
  };
14699
14996
  relocalize();
@@ -14822,6 +15119,11 @@ function buildShell(core) {
14822
15119
  core.dirty.set(true);
14823
15120
  backstage.relocalize();
14824
15121
  },
15122
+ share: {
15123
+ state: () => core.collab.state(),
15124
+ start: (roomId) => core.collab.start({ roomId }),
15125
+ stop: () => core.collab.stop()
15126
+ },
14825
15127
  hiddenActions: () => core.hiddenActions,
14826
15128
  setHiddenActions(ids) {
14827
15129
  element.hiddenActions = ids;
@@ -14841,10 +15143,16 @@ function buildShell(core) {
14841
15143
  );
14842
15144
  safeMount("commands", () => installCommands(ctx));
14843
15145
  const ribbon = createRibbon(ctx, { openBackstage: () => backstage.open("info"), isHidden });
15146
+ const ribbonActions = createRibbonActions(ctx, {
15147
+ isHidden,
15148
+ setReadOnly: (readOnly) => element.readOnly = readOnly,
15149
+ collaboration: () => core.collab.state()
15150
+ });
15151
+ ribbon.element.append(ribbonActions.element);
14844
15152
  const titleBar = createTitleBar(ctx, {
14845
15153
  isHidden,
14846
15154
  save: () => void fileCommand("save"),
14847
- setReadOnly: (readOnly) => element.readOnly = readOnly,
15155
+ collaboration: () => core.collab.state(),
14848
15156
  revealControl(id) {
14849
15157
  const control = ribbon.element.querySelector(
14850
15158
  `.ribbon-panel [data-command="${id.replace(/["\\]/g, "")}"]`
@@ -14935,10 +15243,12 @@ function buildShell(core) {
14935
15243
  focusRegion(next);
14936
15244
  };
14937
15245
  installKeyboard(ctx, actions);
15246
+ element.addEventListener("collaboration-change", () => backstage.refresh("share"));
14938
15247
  core.shell = {
14939
15248
  render() {
14940
15249
  ribbon.refresh();
14941
15250
  titleBar.refresh();
15251
+ ribbonActions.refresh();
14942
15252
  statusBar.refresh();
14943
15253
  chartSeriesPane.refresh();
14944
15254
  chartAreaPane.refresh();
@@ -14954,6 +15264,7 @@ function buildShell(core) {
14954
15264
  emptyText.textContent = ctx.t("Open a workbook or create a new one from the File tab.");
14955
15265
  ribbon.rebuild();
14956
15266
  titleBar.relocalize();
15267
+ ribbonActions.relocalize();
14957
15268
  statusBar.relocalize();
14958
15269
  backstage.relocalize();
14959
15270
  chartSeriesPane.relocalize();
@@ -14972,6 +15283,269 @@ function buildShell(core) {
14972
15283
  fileCommand
14973
15284
  };
14974
15285
  }
15286
+
15287
+ // src/xlsx/collaboration-session.ts
15288
+ function createRoomSession(collab, xlsx, options, defaultName) {
15289
+ const roomId = options.roomId ?? "";
15290
+ if (!collab.isValidRoomId(roomId))
15291
+ throw new Error("Use 1 to 128 letters, digits, hyphens or underscores for the room name.");
15292
+ let provider = options.provider;
15293
+ if (!provider && options.serverUrl)
15294
+ provider = collab.transportProvider({
15295
+ transport: collab.createWebSocketTransport({
15296
+ url: collab.roomUrl(options.serverUrl, roomId)
15297
+ })
15298
+ });
15299
+ if (!provider) {
15300
+ if (!collab.canBroadcast())
15301
+ throw new Error("This browser cannot share between windows; pass a server URL.");
15302
+ provider = collab.transportProvider({
15303
+ transport: collab.createBroadcastTransport({ roomId, prefix: "ooxml-xlsx:" })
15304
+ });
15305
+ }
15306
+ const user = options.user ?? {};
15307
+ return collab.createCollabSession({
15308
+ roomId,
15309
+ provider,
15310
+ user: {
15311
+ name: user.name || defaultName,
15312
+ ...user.color ? { color: user.color } : {},
15313
+ ...user.role ? { role: user.role } : {}
15314
+ },
15315
+ initialPresence: {},
15316
+ sanitizePayload: xlsx.sanitizeXlsxPresence,
15317
+ teardown: true
15318
+ });
15319
+ }
15320
+
15321
+ // src/xlsx/collaboration-types.ts
15322
+ var OFF_STATE = Object.freeze({
15323
+ active: false,
15324
+ roomId: void 0,
15325
+ status: "off",
15326
+ synced: false,
15327
+ people: []
15328
+ });
15329
+
15330
+ // src/xlsx/collaboration.ts
15331
+ var loadRuntime = () => Promise.all([import('ooxml-core/collab'), import('ooxml-core/xlsx/collab')]);
15332
+ function presenceRange(selection) {
15333
+ const { row: row2, col } = selection.active;
15334
+ const holding = selection.ranges.find(
15335
+ (r) => row2 >= r.start.row && row2 <= r.end.row && col >= r.start.col && col <= r.end.col
15336
+ );
15337
+ return holding ?? selection.ranges[selection.ranges.length - 1] ?? {
15338
+ start: selection.active,
15339
+ end: selection.active
15340
+ };
15341
+ }
15342
+ var XlsxCollaboration = class {
15343
+ constructor(host, load = loadRuntime) {
15344
+ this.host = host;
15345
+ this.load = load;
15346
+ }
15347
+ host;
15348
+ load;
15349
+ #active;
15350
+ #starting;
15351
+ /** Bumped by every stop, so a join still loading the runtime is abandoned. */
15352
+ #epoch = 0;
15353
+ #wanted;
15354
+ #lost = false;
15355
+ #connected = false;
15356
+ #listeners = /* @__PURE__ */ new Set();
15357
+ get active() {
15358
+ return this.#active !== void 0;
15359
+ }
15360
+ /** Joined or joining. */
15361
+ get busy() {
15362
+ return this.#active !== void 0 || this.#starting !== void 0;
15363
+ }
15364
+ /** The options of the declarative `collaboration` property, if set. */
15365
+ get wanted() {
15366
+ return this.#wanted;
15367
+ }
15368
+ /**
15369
+ * Declarative sharing: while set, the open workbook is shared in that room, and a workbook
15370
+ * opened later joins it too (the room's content wins when it already has one). Undefined stops.
15371
+ */
15372
+ want(options) {
15373
+ if (options === this.#wanted) return;
15374
+ this.#wanted = options;
15375
+ this.#leave();
15376
+ this.resume();
15377
+ }
15378
+ /** Leaves the room before the workbook is replaced; true when it was shared. */
15379
+ suspend() {
15380
+ const was = this.busy;
15381
+ this.#leave();
15382
+ return was;
15383
+ }
15384
+ /** Joins the wanted room with the workbook now open. */
15385
+ resume() {
15386
+ const wanted = this.#wanted;
15387
+ if (!wanted || this.busy || !this.host.edit()) return;
15388
+ this.start(wanted).catch((error) => {
15389
+ const failure = asError(error);
15390
+ this.host.toast(this.host.t(failure.message), "error");
15391
+ this.host.error(failure);
15392
+ });
15393
+ }
15394
+ /** Joins a room. Rejects when one is active, no workbook is open or the room is invalid. */
15395
+ start(options) {
15396
+ if (this.#active || this.#starting)
15397
+ return Promise.reject(new Error("Stop the current collaboration session first."));
15398
+ const starting = this.#start(options).finally(() => {
15399
+ if (this.#starting === starting) this.#starting = void 0;
15400
+ });
15401
+ this.#starting = starting;
15402
+ return starting;
15403
+ }
15404
+ async #start(options) {
15405
+ if (!this.host.edit()) throw new Error("Open or create a workbook before sharing it.");
15406
+ const epoch = this.#epoch;
15407
+ const [collab, xlsx] = await this.load();
15408
+ if (epoch !== this.#epoch) return;
15409
+ const edit = this.host.edit();
15410
+ if (!edit) throw new Error("Open or create a workbook before sharing it.");
15411
+ const session = options.session ?? createRoomSession(collab, xlsx, options, this.host.userName());
15412
+ const binding = xlsx.bindWorkbookSession(session, edit, {
15413
+ onError: (error) => this.host.error(asError(error))
15414
+ });
15415
+ const active2 = { session, binding, owned: !options.session, stop: [] };
15416
+ this.#active = active2;
15417
+ this.#lost = false;
15418
+ this.#connected = session.status === "connected";
15419
+ const publish = (selection) => binding.setSelection(selection.sheet, presenceRange(selection));
15420
+ active2.stop.push(
15421
+ this.host.selection.onChange(publish),
15422
+ session.on("peers", () => this.#changed()),
15423
+ session.on("synced", () => this.#changed()),
15424
+ session.on("status", (status) => this.#status(status)),
15425
+ session.on("error", (error) => this.host.error(error)),
15426
+ edit.onChange((change) => {
15427
+ if (change.external) this.#notify();
15428
+ })
15429
+ );
15430
+ if (session.role === "viewer") this.host.setReadOnly(true);
15431
+ publish(this.host.selection.get());
15432
+ this.#changed();
15433
+ }
15434
+ /**
15435
+ * Leaves the room and forgets the `collaboration` property. A session the host passed in stays
15436
+ * alive; one the editor made is destroyed.
15437
+ */
15438
+ stop() {
15439
+ this.#wanted = void 0;
15440
+ this.#leave();
15441
+ }
15442
+ #leave() {
15443
+ this.#epoch++;
15444
+ this.#starting = void 0;
15445
+ const active2 = this.#active;
15446
+ if (!active2) return;
15447
+ this.#active = void 0;
15448
+ for (const stop of active2.stop) stop();
15449
+ active2.binding.dispose();
15450
+ if (active2.owned) active2.session.destroy();
15451
+ this.#changed();
15452
+ }
15453
+ /** Restarts the connection, keeping the shared workbook and queued changes. */
15454
+ reconnect() {
15455
+ this.#active?.session.reconnect();
15456
+ }
15457
+ /** The binding's history while shared (only this window's edits), else undefined. */
15458
+ history() {
15459
+ const binding = this.#active?.binding;
15460
+ if (!binding) return void 0;
15461
+ return {
15462
+ undo: () => binding.undo(),
15463
+ redo: () => binding.redo(),
15464
+ canUndo: () => binding.canUndo(),
15465
+ canRedo: () => binding.canRedo(),
15466
+ undoLabel: () => void 0,
15467
+ redoLabel: () => void 0
15468
+ };
15469
+ }
15470
+ remoteSelections() {
15471
+ return this.#active?.binding.remoteSelections() ?? [];
15472
+ }
15473
+ onRemoteChange(listener) {
15474
+ this.#listeners.add(listener);
15475
+ return () => {
15476
+ this.#listeners.delete(listener);
15477
+ };
15478
+ }
15479
+ state() {
15480
+ const active2 = this.#active;
15481
+ if (!active2) return OFF_STATE;
15482
+ const { session } = active2;
15483
+ return {
15484
+ active: true,
15485
+ roomId: session.roomId,
15486
+ status: session.status,
15487
+ synced: session.synced,
15488
+ people: [
15489
+ {
15490
+ clientId: session.clientId,
15491
+ name: session.identity.userName,
15492
+ color: session.identity.userColor,
15493
+ self: true
15494
+ },
15495
+ ...session.peers().map((peer) => ({
15496
+ clientId: peer.clientId,
15497
+ name: peer.userName,
15498
+ color: peer.userColor,
15499
+ self: false
15500
+ }))
15501
+ ]
15502
+ };
15503
+ }
15504
+ #status(status) {
15505
+ const { t } = this.host;
15506
+ if (status === "connected") {
15507
+ if (this.#lost)
15508
+ this.host.toast(t("Reconnected. Changes made while offline were sent."), "info");
15509
+ this.#lost = false;
15510
+ this.#connected = true;
15511
+ } else if ((status === "disconnected" || status === "error") && this.#connected && !this.#lost) {
15512
+ this.#lost = true;
15513
+ this.host.toast(
15514
+ t("Connection lost. Keep working; your changes are sent when it returns."),
15515
+ "warning"
15516
+ );
15517
+ }
15518
+ this.#changed();
15519
+ }
15520
+ #notify() {
15521
+ for (const listener of [...this.#listeners]) listener();
15522
+ }
15523
+ #changed() {
15524
+ this.#notify();
15525
+ this.host.changed(this.state());
15526
+ }
15527
+ };
15528
+ function collaborationFor(core) {
15529
+ const { host } = core;
15530
+ return new XlsxCollaboration({
15531
+ edit: () => core.session,
15532
+ selection: core.selection,
15533
+ userName: () => core.authorName,
15534
+ t: core.ctx.t,
15535
+ toast: (message, kind) => core.shell?.toast(message, kind),
15536
+ error: (error) => emit(host, "workbook-error", { error, message: error.message }),
15537
+ changed: (state) => {
15538
+ emit(host, "collaboration-change", state);
15539
+ core.requestRender();
15540
+ },
15541
+ setReadOnly: (readOnly) => core.setReadOnly(readOnly)
15542
+ });
15543
+ }
15544
+ function asError(error) {
15545
+ return error instanceof Error ? error : new Error(String(error));
15546
+ }
15547
+
15548
+ // src/xlsx/editor-core.ts
14975
15549
  var EditorCore = class {
14976
15550
  constructor(host) {
14977
15551
  this.host = host;
@@ -15008,6 +15582,7 @@ var EditorCore = class {
15008
15582
  this.requestRender();
15009
15583
  });
15010
15584
  this.ctx = this.createContext();
15585
+ this.collab = collaborationFor(this);
15011
15586
  }
15012
15587
  host;
15013
15588
  workbook;
@@ -15031,6 +15606,8 @@ var EditorCore = class {
15031
15606
  dialogs;
15032
15607
  dirty;
15033
15608
  ctx;
15609
+ /** Co-editing over `ooxml-core/collab`; undo and redo go through it while shared. */
15610
+ collab;
15034
15611
  gridController;
15035
15612
  modelListeners = /* @__PURE__ */ new Set();
15036
15613
  stopSession;
@@ -15081,7 +15658,10 @@ var EditorCore = class {
15081
15658
  return emitEvent(core.host, type, detail);
15082
15659
  },
15083
15660
  authorName: () => core.authorName,
15084
- locale: () => core.locale
15661
+ locale: () => core.locale,
15662
+ history: () => core.collab.history() ?? core.session,
15663
+ remoteSelections: () => core.collab.remoteSelections(),
15664
+ onRemoteSelectionsChange: (listener) => core.collab.onRemoteChange(listener)
15085
15665
  };
15086
15666
  }
15087
15667
  /** Debounced refresh of the ribbon, title bar and status bar. */
@@ -15109,6 +15689,8 @@ var EditorCore = class {
15109
15689
  }
15110
15690
  /** Replaces the workbook (load, new, template, property), with a fresh edit session. */
15111
15691
  setWorkbook(workbook) {
15692
+ if (this.collab.suspend() && !this.collab.wanted)
15693
+ this.shell?.toast(this.ctx.t("Sharing stopped because another workbook was opened."));
15112
15694
  this.savePassword = void 0;
15113
15695
  this.stopSession?.();
15114
15696
  this.stopSession = void 0;
@@ -15134,6 +15716,7 @@ var EditorCore = class {
15134
15716
  this.selection.set(initialSelection(this.activeSheet, workbook));
15135
15717
  this.dirty.set(false);
15136
15718
  this.notifyModel({ kind: "load" });
15719
+ this.collab.resume();
15137
15720
  }
15138
15721
  createSession(workbook) {
15139
15722
  try {
@@ -15247,6 +15830,8 @@ var XlsxEditorApi = class extends HTMLElementBase {
15247
15830
  core = new EditorCore(this);
15248
15831
  /** Set by the shell once connected. */
15249
15832
  chrome;
15833
+ /** Set by the shell once connected: opens File > Share. */
15834
+ openShare;
15250
15835
  /** Opens .xlsx, .xlsm, .xltx, .xls or .csv bytes; `fileName` helps detection and names the file. */
15251
15836
  async load(bytes, fileName) {
15252
15837
  await loadInto(this.core, bytes, fileName, this.chrome);
@@ -15300,6 +15885,31 @@ var XlsxEditorApi = class extends HTMLElementBase {
15300
15885
  setActiveSheet(index) {
15301
15886
  this.core.setActiveSheet(index);
15302
15887
  }
15888
+ /**
15889
+ * Shares the open workbook in a room (see `XlsxCollaborationOptions`). Resolves once joined;
15890
+ * the room's workbook replaces this one when the room already has one. Rejects when sharing is
15891
+ * already active or no workbook is open.
15892
+ */
15893
+ startCollaboration(options) {
15894
+ return this.core.collab.start(options);
15895
+ }
15896
+ /** Leaves the room (and clears `collaboration`). The workbook stays as it is. */
15897
+ stopCollaboration() {
15898
+ this.core.collab.stop();
15899
+ }
15900
+ /** Restarts the connection, keeping the shared workbook and changes made offline. */
15901
+ reconnectCollaboration() {
15902
+ this.core.collab.reconnect();
15903
+ }
15904
+ /** Who is in the room and how the connection is. */
15905
+ get collaborationState() {
15906
+ return this.core.collab.state();
15907
+ }
15908
+ /** Opens File > Share, where the user starts or stops sharing and sees who is in the room. */
15909
+ share() {
15910
+ this.openShare?.();
15911
+ }
15912
+ /** Undo the last edit; while shared, only this window's edits are undone. */
15303
15913
  undo() {
15304
15914
  void this.core.commands.run("edit.undo");
15305
15915
  }
@@ -15323,6 +15933,7 @@ var XlsxEditorElement = class extends XlsxEditorApi {
15323
15933
  if (this.shell) return;
15324
15934
  this.shell = buildShell(this.core);
15325
15935
  this.chrome = this.shell.chrome;
15936
+ this.openShare = () => this.shell?.backstage.open("share");
15326
15937
  this.core.requestRender();
15327
15938
  }
15328
15939
  attributeChangedCallback(name, _old, value) {
@@ -15417,6 +16028,17 @@ var XlsxEditorElement = class extends XlsxEditorApi {
15417
16028
  get dirty() {
15418
16029
  return this.core.dirty.dirty;
15419
16030
  }
16031
+ /**
16032
+ * Shares the open workbook in a room while set: a workbook opened later joins the same room
16033
+ * (the room's content wins when it already has a workbook); `null` leaves it. A window joining
16034
+ * an existing room still needs a workbook open (`newWorkbook()` is enough).
16035
+ */
16036
+ get collaboration() {
16037
+ return this.core.collab.wanted ?? null;
16038
+ }
16039
+ set collaboration(value) {
16040
+ this.core.collab.want(value ?? void 0);
16041
+ }
15420
16042
  get activeSheet() {
15421
16043
  return this.core.activeSheet;
15422
16044
  }