ooxml-ui 1.6.2 → 1.7.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 (54) hide show
  1. package/README.md +1 -1
  2. package/custom-elements.json +53 -1
  3. package/dist/chrome/ruler.d.ts.map +1 -1
  4. package/dist/chrome/status-bar.d.ts.map +1 -1
  5. package/dist/chrome/tab-strip.d.ts +3 -1
  6. package/dist/chrome/tab-strip.d.ts.map +1 -1
  7. package/dist/chrome/task-pane.d.ts +27 -0
  8. package/dist/chrome/task-pane.d.ts.map +1 -0
  9. package/dist/chrome/title-bar.d.ts.map +1 -1
  10. package/dist/{chunk-QAIVGTQZ.js → chunk-6UPGM3X2.js} +1 -1
  11. package/dist/{chunk-QHAE6Y5B.js → chunk-SCWCZSTE.js} +71 -32
  12. package/dist/{chunk-Z7CS3UUK.js → chunk-VAR6O7II.js} +3 -2
  13. package/dist/controls.d.ts +3 -2
  14. package/dist/controls.d.ts.map +1 -1
  15. package/dist/controls.js +1 -1
  16. package/dist/docx.js +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +3 -3
  19. package/dist/pptx/{chunk-E55NU4BF.mjs → chunk-RCTRRS6E.mjs} +28 -49
  20. package/dist/pptx/{chunk-BLAJIEXA.cjs → chunk-XGPLA2OY.cjs} +30 -48
  21. package/dist/pptx/dom/index.cjs +199 -199
  22. package/dist/pptx/dom/index.mjs +2 -2
  23. package/dist/pptx/index.cjs +2708 -2708
  24. package/dist/pptx/index.mjs +1 -1
  25. package/dist/pptx/render/editor-keymap-arrows.d.ts +0 -7
  26. package/dist/pptx/render/editor-keymap-arrows.d.ts.map +1 -1
  27. package/dist/pptx/render/element-interaction.d.ts +2 -6
  28. package/dist/pptx/render/element-interaction.d.ts.map +1 -1
  29. package/dist/teams.js +2 -2
  30. package/dist/visio/ribbon-action.d.ts +3 -1
  31. package/dist/visio/ribbon-router.d.ts +1 -1
  32. package/dist/visio/ribbon.d.ts +2 -2
  33. package/dist/visio/shapes-window.d.ts +2 -2
  34. package/dist/visio/status-bar.d.ts +7 -4
  35. package/dist/visio/styles/theme.d.ts +1 -1
  36. package/dist/visio/styles.d.ts +1 -1
  37. package/dist/visio/title-bar.d.ts +9 -0
  38. package/dist/visio/viewer-backstage.d.ts +2 -1
  39. package/dist/visio/viewer-canvas.d.ts +1 -1
  40. package/dist/visio/viewer-chrome.d.ts +12 -5
  41. package/dist/visio/viewer-commands.d.ts +3 -1
  42. package/dist/visio/viewer-element.d.ts +8 -0
  43. package/dist/visio/viewer-guides.d.ts +5 -2
  44. package/dist/visio/viewer-inline-text.d.ts +31 -0
  45. package/dist/visio/viewer-input.d.ts +5 -1
  46. package/dist/visio/viewer-layer-properties.d.ts +18 -0
  47. package/dist/visio/viewer-layout-commands.d.ts +3 -1
  48. package/dist/visio/viewer-options.d.ts +18 -4
  49. package/dist/visio.js +974 -708
  50. package/dist/xlsx.js +4 -4
  51. package/package.json +2 -2
  52. package/dist/visio/viewer-edit-controls.d.ts +0 -12
  53. package/dist/visio/viewer-geometry-controls.d.ts +0 -14
  54. package/dist/visio/viewer-layer-controls.d.ts +0 -5
package/dist/visio.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { themeBridge, installOfficeUiTheme } from './chunk-BCH2ZLWI.js';
2
- import { attachKeyTips, registerControls, readOfficeProfile, sanitizeOfficeProfile, writeOfficeProfile, OFFICE_COLOR_SWATCHES, commentInitials } from './chunk-QHAE6Y5B.js';
2
+ import { attachKeyTips, registerControls, readOfficeProfile, sanitizeOfficeProfile, writeOfficeProfile, OFFICE_COLOR_SWATCHES, commentInitials } from './chunk-SCWCZSTE.js';
3
3
  import { getIcon, registerIcon } from './chunk-M6GF5ED2.js';
4
4
  import { definePresence } from './chunk-L7JV5FVE.js';
5
5
  import './chunk-OGNNNTKI.js';
6
6
  import './chunk-SPNWHKSR.js';
7
- import { VISIO_TEXT_LANGUAGES, VISIO_BULLET_STYLES, FOREIGN_VECTOR_SCENE_LIMITS, EMPTY_TEXT_SEARCH, EMPTY_SELECTION, EMPTY_EDIT_STATE, assertViewableDocument, snapshotSelection, selectionKey, indexDocumentText, searchDocumentText, sameSelection, validateSearchQuery, MAX_INPUT_BYTES, DocumentHistory, snapshotEdits, visioWithContainerMembers, visioSelectionIsOnPage, visioDuplicateCommand, visioGroupCommand, visioUngroupCommand, visioContainerCommand, visioCalloutCommand, visioPasteCommand, visioTextOccurrenceSelection, ForeignVectorBudget, VISIO_IDENTITY_TRANSFORM, hasVisibleShapeContent, composeVisioTransform, compatibilityNotes, compatibilityText, visioGradientInstances, visioRenderedGradientStopCount, inspectForeignVectorResource, copySnapshotScene, editErrorMessage, eventKeys, propertyKeys, VISIO_TEXT_REPLACE_LIMITS, visioTextReplaceOccurrences, visioTextReplacePlan, visioTextReplaceCommands, TEXT_SEARCH_LIMITS, visioTextShape, visioTextDraftEdit, isEditCancellation, visioSizePositionCommand, visioSizePositionState, visioWithCalloutLeaders, visioPageInsertCommand, visioQuarterTurnCommand, visioLocalRotationShape, visioMovementShape, visioMoveCommands, visioMoveTargets, visioMarqueeBox, visioPageDragDelta, visioMarqueeSelection, visioConnectorRouteOf, visioPageEditToDrawing, visioResizeShape, visioResizeDrag, textSearchStatus, visioSvgStrokeStyle, visioLineDashLengths, VISIO_DIAGRAM_RULE_SETS, visioClipboardShape, visioArrangeCommands, visioChangeShapeRefusal, visioChangeShapeCommand, visioChangeCaseShape, visioChangeCaseCommand, visioOrderingShape, visioStyleFormattingShape, visioFormattingShape, visioTextFormattingState, visioTextIndentCommand, visioTextFontStepCommand, visioFontFamilies, visioPictureInsertCommand, normalizeVisioHyperlinkAddress, visioFitToDrawingEdits, VISIO_PAPER_SIZES, visioPageSizePresetEdits, visioOrientationEdits, visioPageDecorationState, visioNewPageId, visioMatchingPaperSize, visioPageBreaks, visioRemoveDataGraphicEdits, visioDataGraphicFields, visioDataGraphicEdits, visioShapeDataRow, VISIO_DATA_GRAPHIC_FILL_ROW, visioColorRules, visioLegendEdits, visioTextDialogValues, visioTextDialogPatch, visioShapeFormattingState, visioFormatPainterSnapshot, visioFormatPainterEdits, VISIO_FORMAT_PAINTER_LIMITS, isVisioPathTool, visioPathDrawPlan, visioDrawPlan, visioDrawIsLargeEnough, visioConnectorCreationCommand, visioConnectableShape, visioAddConnectionPointCommand, visioDrawBounds, visioDrawingPoint, visioMovePreviewTransform, visioStraightLineHandles, visioConnectorEndHandles, visioNearestConnectionPoint, visioRotationPreviewTransform, VISIO_RESIZE_HANDLES, visioFillPatternTransform, visioRenderedGradientStops, visioStrokeGradient, VISIO_LAYOUT_STYLES, visioGlowKey, visioShapeEffectValues, visioReflectionKey, visioFollowTarget, visioPaperSize, visioKeepAutoSize, checkVisioDiagram, visioShapeReport, visioShapeReportTable, visioShapeReportCsv, visioShapeReportText, visioSubprocessCommand, visioShapeDataText, VISIO_LAYOUT_LABELS, visioAutoAlignCommands, visioReLayoutCommands, visioLayerAssignState, visioNewLayerNames, visioLayerAssignCommands, VISIO_SELECT_TYPES, VISIO_SELECT_TYPE_LABELS, visioPageMasters, visioSelectByType, visioPasteTextCommand, visioSnapMoveDelta, visioPageGuides, VISIO_FIELD_CATEGORIES, visioTextAppendEdit, visioTextSource, visioTextBlockShape, visioTextBlockFrame, visioTextBlockMatrix, VISIO_TEXT_BLOCK_HANDLES, visioShapeLocalPoint, visioTextBlockDrag, visioTextBlockEdit, visioPathPreview, visioConnectorMovePreviews, visioConnectorPreviewPath, visioShapePageBox, visioDataGraphicMarker, visioParseRange, visioDataTable, visioWorkbookGrid, visioGuideCreateCommand, visioGuideMoveCommand, visioBoxCreationCommand } from 'ooxml-core/visio/ui';
7
+ import { VISIO_TEXT_LANGUAGES, VISIO_BULLET_STYLES, FOREIGN_VECTOR_SCENE_LIMITS, EMPTY_TEXT_SEARCH, EMPTY_SELECTION, EMPTY_EDIT_STATE, assertViewableDocument, snapshotSelection, selectionKey, indexDocumentText, searchDocumentText, sameSelection, validateSearchQuery, MAX_INPUT_BYTES, DocumentHistory, snapshotEdits, visioWithContainerMembers, visioSelectionIsOnPage, visioDuplicateCommand, visioGroupCommand, visioUngroupCommand, visioContainerCommand, visioCalloutCommand, visioPasteCommand, visioTextOccurrenceSelection, ForeignVectorBudget, VISIO_IDENTITY_TRANSFORM, hasVisibleShapeContent, composeVisioTransform, compatibilityNotes, compatibilityText, visioGradientInstances, visioRenderedGradientStopCount, inspectForeignVectorResource, copySnapshotScene, editErrorMessage, eventKeys, propertyKeys, VISIO_TEXT_REPLACE_LIMITS, visioTextReplaceOccurrences, visioTextReplacePlan, visioTextReplaceCommands, TEXT_SEARCH_LIMITS, visioTextShape, visioTextDraftEdit, isEditCancellation, visioSizePositionCommand, visioSizePositionState, visioWithCalloutLeaders, visioPageInsertCommand, visioQuarterTurnCommand, visioLocalRotationShape, visioMovementShape, visioMoveCommands, visioMoveTargets, visioMarqueeBox, visioPageDragDelta, visioMarqueeSelection, visioConnectorRouteOf, visioPageEditToDrawing, visioResizeShape, visioResizeDrag, textSearchStatus, visioSvgStrokeStyle, visioLineDashLengths, VISIO_DIAGRAM_RULE_SETS, visioClipboardShape, visioArrangeCommands, visioChangeShapeRefusal, visioChangeShapeCommand, visioChangeCaseShape, visioChangeCaseCommand, visioOrderingShape, visioStyleFormattingShape, visioFormattingShape, visioTextFormattingState, visioTextIndentCommand, visioTextFontStepCommand, visioFontFamilies, visioPictureInsertCommand, normalizeVisioHyperlinkAddress, visioFitToDrawingEdits, VISIO_PAPER_SIZES, visioPageSizePresetEdits, visioOrientationEdits, visioPageDecorationState, visioNewPageId, visioMatchingPaperSize, visioPageBreaks, visioRemoveDataGraphicEdits, visioDataGraphicFields, visioDataGraphicEdits, visioShapeDataRow, VISIO_DATA_GRAPHIC_FILL_ROW, visioColorRules, visioLegendEdits, visioTextDialogValues, visioTextDialogPatch, visioShapeFormattingState, visioFormatPainterSnapshot, visioFormatPainterEdits, VISIO_FORMAT_PAINTER_LIMITS, isVisioPathTool, visioPathDrawPlan, visioDrawPlan, visioDrawIsLargeEnough, visioConnectorCreationCommand, visioConnectableShape, visioAddConnectionPointCommand, visioDrawBounds, visioDrawingPoint, visioMovePreviewTransform, visioStraightLineHandles, visioConnectorEndHandles, visioNearestConnectionPoint, visioRotationPreviewTransform, VISIO_RESIZE_HANDLES, visioNudgeCommands, visioFillPatternTransform, visioRenderedGradientStops, visioStrokeGradient, VISIO_LAYOUT_STYLES, visioGlowKey, visioShapeEffectValues, visioReflectionKey, visioFollowTarget, visioPaperSize, visioKeepAutoSize, checkVisioDiagram, visioShapeReport, visioShapeReportTable, visioShapeReportCsv, visioShapeReportText, visioSubprocessCommand, visioShapeDataText, VISIO_LAYOUT_LABELS, visioAutoAlignCommands, visioReLayoutCommands, visioLayerAssignState, visioNewLayerNames, visioLayerAssignCommands, VISIO_SELECT_TYPES, VISIO_SELECT_TYPE_LABELS, visioPageMasters, visioSelectByType, visioPasteTextCommand, visioSnapMoveDelta, visioPageGuides, VISIO_FIELD_CATEGORIES, visioTextAppendEdit, visioTextSource, visioTextBlockShape, visioTextBlockFrame, visioTextBlockMatrix, VISIO_TEXT_BLOCK_HANDLES, visioShapeLocalPoint, visioTextBlockDrag, visioTextBlockEdit, visioPathPreview, visioConnectorMovePreviews, visioConnectorPreviewPath, visioShapePageBox, visioDataGraphicMarker, visioParseRange, visioDataTable, visioWorkbookGrid, visioGuideCreateCommand, visioGuideMoveCommand, visioBoxCreationCommand } from 'ooxml-core/visio/ui';
8
8
  export { TEXT_SEARCH_LIMITS, compatibilityNotes, compatibilityText, demoDocument, eventKeys, propertyKeys } from 'ooxml-core/visio/ui';
9
9
  import { VISIO_VISIBILITY_LIMITS, VISIO_STENCILS, visioOutlineShape, loadVisio, createVsdx, deserializeVisioClipboard, getVisioPageLayers, resolveVisioPageVisibility, VISIO_CLIPBOARD_MAX_CHARS, VISIO_GLOW_TRANSPARENCY, VISIO_METADATA_TEXT_LIMIT, VISIO_PICTURE_MAX_BYTES, inspectVisioRasterImage, VISIO_BUILT_IN_THEMES, VISIO_CONNECTOR_ROUTES, visioFilledArrow, layoutVisioFilledArrowLine, visioOpenArrowPath, VISIO_SHADOW_PRESETS, isVisioChangeShapeTarget, VISIO_CONTAINER_STYLES, VISIO_CALLOUT_STYLES, visioFallbackQuickStyle, VISIO_GLOW_SIZES, VISIO_SOFT_EDGE_SIZES, VISIO_REFLECTION_PRESETS, VISIO_BACKGROUND_STYLES, visioBackgroundShapes, VISIO_BORDER_STYLES, visioBorderShapes, VISIO_SHAPE_DATA_TYPES, visioShapeDataRowName, VISIO_CLIPBOARD_MAGIC, visioThemeVariantColors, isVisioBuiltInThemeId, VISIO_SCALE_UNITS, VISIO_QUICK_STYLE_COLORS } from 'ooxml-core/visio';
10
- import { resolveRotateHandlePlacement, createRotationDrag } from 'ooxml-core/geometry';
10
+ import { resolveRotateHandlePlacement, createRotationDrag, snapAngle } from 'ooxml-core/geometry';
11
11
 
12
12
  function createWorkerEditor(factory, timeoutMs = 2e4) {
13
13
  const create = (() => {
@@ -190,7 +190,7 @@ var ViewerClipboardCapture = class {
190
190
  token() {
191
191
  const context = this.context();
192
192
  if (this.#destroyed || !context)
193
- throw new Error("Load an idle source-backed VSDX before using the clipboard.");
193
+ throw new Error("Open a .vsdx file before using the clipboard.");
194
194
  const token = Object.freeze({ [tokenBrand]: true });
195
195
  this.#tokens.set(token, context);
196
196
  return token;
@@ -400,9 +400,9 @@ var ViewerTextReplacement = class {
400
400
  return this.#issue(context, Object.freeze({ scope, pageId: context.pageId, selection: owned }));
401
401
  }
402
402
  current(token) {
403
- const saved = this.#tokens.get(token);
404
- const context = saved && this.bridge.context();
405
- return !!saved && !!context && sameReplacementContext(saved.context, context);
403
+ const saved2 = this.#tokens.get(token);
404
+ const context = saved2 && this.bridge.context();
405
+ return !!saved2 && !!context && sameReplacementContext(saved2.context, context);
406
406
  }
407
407
  /** Cancellation is bound to this token's application, even after scope intent changes. */
408
408
  cancel(token) {
@@ -474,9 +474,9 @@ var ViewerTextReplacement = class {
474
474
  this.#tokens.set(token, { context, scope });
475
475
  return token;
476
476
  }
477
- #requireContext(saved) {
477
+ #requireContext(saved2) {
478
478
  const current = this.bridge.context();
479
- if (!current || !sameReplacementContext(saved, current)) throw replacementCancelled();
479
+ if (!current || !sameReplacementContext(saved2, current)) throw replacementCancelled();
480
480
  }
481
481
  #require(token) {
482
482
  const record = this.#tokens.get(token);
@@ -1105,7 +1105,7 @@ var ViewerController = class {
1105
1105
  exportVsdx() {
1106
1106
  this.#assertAlive();
1107
1107
  if (!this.#history || this.#sourceFormat !== "vsdx" || this.#state.document?.format !== "vsdx")
1108
- throw new Error("Load a VSDX file before downloading a source-backed copy.");
1108
+ throw new Error("Open a .vsdx file before downloading a copy.");
1109
1109
  if (this.#state.edit.busy || this.#state.loading)
1110
1110
  throw new Error("Wait for the current document operation before downloading.");
1111
1111
  return this.#history.export();
@@ -1220,7 +1220,7 @@ var ViewerController = class {
1220
1220
  this.#assertAlive();
1221
1221
  const history = this.#history;
1222
1222
  if (!history || this.#sourceFormat !== "vsdx" || this.#state.document?.format !== "vsdx")
1223
- throw new Error("Load a VSDX file before editing. Model-only documents are read only.");
1223
+ throw new Error("Open a .vsdx file before editing.");
1224
1224
  if (this.#state.loading || this.#state.edit.busy)
1225
1225
  throw new Error("Another document operation is in progress.");
1226
1226
  const target = kind === "undo" ? history.undoTarget : kind === "redo" ? history.redoTarget : void 0;
@@ -1574,7 +1574,7 @@ function shapeDetails(shape) {
1574
1574
  fragment.append(list);
1575
1575
  } else {
1576
1576
  const empty = document.createElement("p");
1577
- empty.textContent = "No visible shape data.";
1577
+ empty.textContent = "No Shape Data";
1578
1578
  fragment.append(empty);
1579
1579
  }
1580
1580
  const define = document.createElement("button");
@@ -1642,7 +1642,31 @@ function selection(target) {
1642
1642
  ...target.dataset.pageId ? { pageId: target.dataset.pageId } : {}
1643
1643
  } : null;
1644
1644
  }
1645
- function wireViewerInputs(controls, controller, fit) {
1645
+ var NUDGE_STEP = 1 / 16;
1646
+ var editableTarget = (event) => event.target?.closest?.("input, textarea, select, [contenteditable]");
1647
+ function nudge(event, controller, announce) {
1648
+ const state = controller.state;
1649
+ const page3 = state.document?.pages[state.pageIndex];
1650
+ if (!page3 || !state.selectedShapes.length || !state.edit.sourceAvailable || state.loading || event.ctrlKey || event.metaKey || event.altKey || editableTarget(event))
1651
+ return false;
1652
+ const step = event.shiftKey ? 1 / (96 * (state.zoom || 1)) : NUDGE_STEP;
1653
+ const ids = state.selectedShapes.map((shape) => shape.id);
1654
+ const edits = visioNudgeCommands(page3, ids, event.key, step);
1655
+ if (edits === null) return false;
1656
+ event.preventDefault();
1657
+ if (state.edit.busy) return true;
1658
+ if (!edits) {
1659
+ announce("The selection cannot be nudged.");
1660
+ return true;
1661
+ }
1662
+ if (edits.length)
1663
+ controller.applySelectionEdits(edits).catch((error) => {
1664
+ if (!isEditCancellation(error)) announce(editErrorMessage(error));
1665
+ });
1666
+ return true;
1667
+ }
1668
+ function wireViewerInputs(controls, controller, fit, announce = () => {
1669
+ }, typeText = () => false) {
1646
1670
  const { viewport, zoomSlider } = controls;
1647
1671
  const Abort = viewport.ownerDocument.defaultView?.AbortController ?? AbortController, events = new Abort();
1648
1672
  const options2 = { signal: events.signal };
@@ -1666,6 +1690,12 @@ function wireViewerInputs(controls, controller, fit) {
1666
1690
  viewport.addEventListener(
1667
1691
  "keydown",
1668
1692
  (event) => {
1693
+ if (nudge(event, controller, announce)) return;
1694
+ if (event.key.length === 1 && event.key !== " " && !event.ctrlKey && !event.metaKey && !event.altKey && !event.isComposing && !editableTarget(event) && controller.state.selectedShapes.length === 1 && controller.state.edit.sourceAvailable && typeText(event.key)) {
1695
+ event.preventDefault();
1696
+ event.stopPropagation();
1697
+ return;
1698
+ }
1669
1699
  const target = targetShape(event);
1670
1700
  if (target && ["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft", "Home", "End"].includes(event.key)) {
1671
1701
  event.preventDefault();
@@ -1699,98 +1729,6 @@ function wireViewerInputs(controls, controller, fit) {
1699
1729
  );
1700
1730
  return () => events.abort();
1701
1731
  }
1702
- var layerControlsTemplate = `<details class="layer-controls" hidden><summary>Layers</summary><p>Display changes only. SVG and print snapshots use saved visibility. Guides, NoShow and unknown hidden reasons remain hidden.</p><div data-layer-list></div><p data-layer-status role="status"></p><button type="button" data-layer-reset="all">Reset all layers</button></details>`;
1703
- function renderLayerControls(panel, state) {
1704
- const model = state.document, page3 = model?.pages[state.pageIndex];
1705
- const list = panel.querySelector("[data-layer-list]");
1706
- const active = panel.getRootNode();
1707
- const focused = active.activeElement;
1708
- const focus = focused && panel.contains(focused) ? { ...focused.dataset } : void 0;
1709
- const fragment = panel.ownerDocument.createDocumentFragment();
1710
- const overrides = layerOverrideMaps(model, state.layerVisibilityOverrides);
1711
- let total = 0, shown = 0;
1712
- for (const source of model && page3 ? getVisioPageLayers(model, page3.id) : []) {
1713
- const ids = /* @__PURE__ */ new Set();
1714
- const fieldset2 = panel.ownerDocument.createElement("fieldset");
1715
- const legend = panel.ownerDocument.createElement("legend");
1716
- const pageLabel = `${source.name.slice(0, 256)} (page ${source.id})`;
1717
- legend.textContent = `${pageLabel}${source.id === page3.id ? "" : " (background)"}`;
1718
- fieldset2.append(legend);
1719
- let rows = 0;
1720
- for (const layer of source.layers ?? []) {
1721
- if (ids.has(layer.id)) continue;
1722
- ids.add(layer.id);
1723
- ++total;
1724
- if (shown >= VIEWER_LAYER_LIMITS.controls) continue;
1725
- ++shown;
1726
- ++rows;
1727
- const row = panel.ownerDocument.createElement("div");
1728
- const label = panel.ownerDocument.createElement("label");
1729
- const input = panel.ownerDocument.createElement("input");
1730
- const override = overrides.get(source.id)?.get(layer.id);
1731
- input.type = "checkbox";
1732
- input.dataset.layerId = layer.id;
1733
- input.dataset.pageId = source.id;
1734
- input.checked = override ?? layer.visible;
1735
- const name = `${layer.name.slice(0, 256) || "Layer"} (ID ${layer.id})`;
1736
- input.setAttribute("aria-label", `${pageLabel}: ${name}`);
1737
- const description = `${name} \xB7 saved ${layer.visible ? "visible" : "hidden"}${override === void 0 ? "" : ` \xB7 override ${override ? "visible" : "hidden"}`}`;
1738
- label.append(input, description);
1739
- row.append(label);
1740
- if (override !== void 0) {
1741
- const reset = panel.ownerDocument.createElement("button");
1742
- reset.type = "button";
1743
- reset.textContent = "Reset";
1744
- reset.dataset.layerReset = "one";
1745
- reset.dataset.layerId = layer.id;
1746
- reset.dataset.pageId = source.id;
1747
- reset.setAttribute("aria-label", `Reset ${pageLabel}: ${name}`);
1748
- row.append(reset);
1749
- }
1750
- fieldset2.append(row);
1751
- }
1752
- if (rows) fragment.append(fieldset2);
1753
- }
1754
- list.replaceChildren(fragment);
1755
- panel.hidden = total === 0 && state.layerVisibilityOverrides.length === 0;
1756
- panel.querySelector("[data-layer-status]").textContent = total > shown ? `Showing ${shown} of ${total} layers. Other layers are available through the viewer API.` : `${total} layers in this drawing \xB7 ${state.layerVisibilityOverrides.length} document display overrides`;
1757
- panel.querySelector('[data-layer-reset="all"]').disabled = state.layerVisibilityOverrides.length === 0;
1758
- if (focus) {
1759
- const controls = [...panel.querySelectorAll("input,button")];
1760
- const match = (control) => control.dataset.layerId === focus.layerId && control.dataset.pageId === focus.pageId;
1761
- const target = controls.find(
1762
- (control) => match(control) && control.dataset.layerReset === focus.layerReset
1763
- ) ?? controls.find(match);
1764
- target?.focus();
1765
- }
1766
- }
1767
- function wireLayerControls(panel, controller) {
1768
- const Abort = panel.ownerDocument.defaultView?.AbortController ?? AbortController;
1769
- const events = new Abort(), options2 = { signal: events.signal };
1770
- panel.addEventListener(
1771
- "change",
1772
- (event) => {
1773
- const input = event.target;
1774
- if (input.matches('input[type="checkbox"]') && input.dataset.pageId !== void 0 && input.dataset.layerId !== void 0)
1775
- controller.setLayerVisibility(input.dataset.pageId, input.dataset.layerId, input.checked);
1776
- },
1777
- options2
1778
- );
1779
- panel.addEventListener(
1780
- "click",
1781
- (event) => {
1782
- const button = event.target.closest(
1783
- "button[data-layer-reset]"
1784
- );
1785
- if (!button || !panel.contains(button)) return;
1786
- if (button.dataset.layerReset === "all") controller.resetLayerVisibility();
1787
- else if (button.dataset.pageId !== void 0 && button.dataset.layerId !== void 0)
1788
- controller.setLayerVisibility(button.dataset.pageId, button.dataset.layerId, null);
1789
- },
1790
- options2
1791
- );
1792
- return () => events.abort();
1793
- }
1794
1732
 
1795
1733
  // src/visio/styles.ts
1796
1734
  var viewerStyles = `
@@ -1816,21 +1754,10 @@ button[aria-pressed="true"] { color:var(--_vv-accent); background:var(--_vv-acce
1816
1754
  .ribbon-hint { align-self:center; flex:none; margin:0; padding:0 16px; font-size:11px; line-height:18px; color:var(--_vv-muted); }
1817
1755
  .workspace { position:relative; display:flex; flex:1; min-width:0; min-height:0; overflow:hidden; }
1818
1756
  .pane-heading { height:37px; display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 10px; border-bottom:1px solid var(--_vv-border); font-size:11px; font-weight:600; }
1819
- .inspector-kind { color:var(--_vv-muted); font-size:10px; font-weight:400; }
1820
1757
  .viewport { position:relative; display:flex; flex-direction:column; flex:1; overflow:auto; min-width:0; min-height:0; padding:16px 4px; background:var(--_vv-bg); }
1821
1758
  .paper { display:block; flex:none; background:white; box-shadow:var(--_vv-shadow); margin:auto; }
1822
1759
  .paper [data-shape-id] { cursor:pointer; }
1823
- .paper [data-selected="true"] > [data-geometry] { stroke:var(--_vv-accent) !important; stroke-width:.025 !important; }
1824
- .paper [role="group"][data-selected="true"] [data-geometry] { stroke:var(--_vv-accent) !important; stroke-dasharray:.06 .04; }
1825
1760
  .inspector-pane { flex:0 0 288px; width:288px; min-width:0; overflow:auto; border-left:1px solid var(--_vv-border); background:var(--_vv-secondary); }
1826
- .inspector-body { display:flex; flex-direction:column; gap:12px; padding:12px 10px; }
1827
- .inspector-card,.edit-controls,.layer-controls { min-width:0; padding:8px; margin:0; border:1px solid var(--_vv-border); border-radius:4px; background:var(--_vv-surface); font-size:11px; line-height:17px; }
1828
- .document-card h2 { margin:0 0 8px; font-size:10px; font-weight:500; letter-spacing:.5px; text-transform:uppercase; color:var(--_vv-muted); }
1829
- .current-page-name { margin:0 0 8px; font-size:12px; font-weight:500; overflow-wrap:anywhere; }
1830
- .document-card dl { display:grid; grid-template-columns:1fr auto; gap:4px 10px; margin:0; font-size:11px; }
1831
- .document-card dt { color:var(--_vv-muted); }
1832
- .document-card dd { margin:0; text-align:right; overflow-wrap:anywhere; }
1833
- .selection-hint { margin:0; padding:0 2px; color:var(--_vv-muted); font-size:11px; }
1834
1761
  summary { cursor:pointer; min-height:22px; line-height:22px; font-weight:500; }
1835
1762
  summary::marker { color:var(--_vv-muted); font-size:10px; }
1836
1763
  .shape-inspector div { max-height:260px; overflow:auto; }
@@ -1840,35 +1767,12 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
1840
1767
  .shape-inspector dd { margin:0; overflow-wrap:anywhere; white-space:pre-wrap; }
1841
1768
  .shape-inspector h3 { margin:12px 0 5px; font-size:11px; }
1842
1769
  .shape-inspector a { color:var(--_vv-accent); overflow-wrap:anywhere; }
1843
- .edit-controls p,.layer-controls p { margin:6px 0; color:var(--_vv-muted); overflow-wrap:anywhere; }
1844
- .edit-controls textarea { display:block; width:100%; resize:vertical; max-height:180px; margin-top:4px; background:var(--_vv-secondary); }
1845
- .edit-controls label { display:block; margin-top:8px; }
1846
- .edit-actions { display:flex; flex-wrap:wrap; gap:4px; margin-top:8px; }
1847
- .edit-actions button { font-size:11px; padding:3px 7px; }
1848
- .edit-actions [data-edit="apply"]:enabled { background:var(--_vv-accent); border-color:var(--_vv-accent); color:var(--_vv-accent-ink); }
1849
- .edit-controls [data-geometry] { min-width:0; margin:8px 0 0; padding:6px; border:1px solid var(--_vv-border); }
1850
- .edit-controls [data-geometry-field] { display:block; width:100%; min-width:0; margin-top:4px; }
1851
- .edit-controls [data-geometry-error] { color:var(--_vv-danger); }
1852
- .edit-controls [data-edit-error] { color:var(--_vv-danger); }
1853
- .edit-controls [data-edit-diagnostics] { max-height:100px; overflow:auto; padding-left:16px; }
1854
- .layer-controls [data-layer-list] { max-height:240px; overflow:auto; }
1855
- .layer-controls fieldset { border:1px solid var(--_vv-border); margin:6px 0; padding:6px; min-width:0; }
1856
- .layer-controls legend { max-width:100%; overflow-wrap:anywhere; }
1857
- .layer-controls fieldset div { display:flex; align-items:center; gap:6px; margin:3px 0; }
1858
- .layer-controls label { display:flex; align-items:center; gap:6px; overflow-wrap:anywhere; }
1859
- .layer-controls input { flex-shrink:0; width:16px; height:16px; min-height:16px; padding:0; accent-color:var(--_vv-accent); }
1860
- .notes ul { max-height:220px; overflow:auto; padding-left:17px; margin:6px 0 0; color:var(--_vv-muted); }
1861
- .notes li { margin:5px 0; overflow-wrap:anywhere; }
1862
- .notes-strip { flex:none; display:flex; align-items:center; justify-content:space-between; min-height:25.5px; padding:0 12px; border-top:1px solid var(--_vv-border); background:var(--_vv-bg); color:var(--_vv-muted); font-size:10px; }
1863
- .notes-strip button { min-height:24.5px; padding:2px 0; border:0; background:transparent; color:var(--_vv-muted); font-size:11px; }
1864
- .notes-strip button::before { content:"\u25B8"; margin-right:6px; }
1865
- .notes-strip button[aria-expanded="true"]::before { content:"\u25BE"; }
1866
1770
  .status { flex:none; display:flex; align-items:center; justify-content:space-between; gap:12px; min-width:0; min-height:29px; padding:0 8px 0 12px; border-top:1px solid var(--_vv-border); color:var(--_vv-muted); background:var(--_vv-surface); font-size:10px; }
1867
1771
  .status-message { display:flex; gap:16px; min-width:0; }
1868
1772
  .status-message span { overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
1869
1773
  .empty { max-width:360px; margin:60px auto; padding:16px; text-align:center; color:var(--_vv-muted); font-size:12px; }
1870
1774
  .empty strong { display:block; color:var(--_vv-ink); font-size:18px; font-weight:500; margin-bottom:8px; }
1871
- @media(max-width:1100px) { .ribbon-hint { display:none; } [data-diagnostics] { display:none; } }
1775
+ @media(max-width:1100px) { .ribbon-hint { display:none; } }
1872
1776
  @media(max-width:760px) {
1873
1777
  button,select,input,textarea { min-height:44px; }
1874
1778
  button { min-width:44px; }
@@ -1880,22 +1784,14 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
1880
1784
  .workspace { display:block; overflow:auto; }
1881
1785
  .viewport { height:340px; min-height:260px; padding:16px 4px; }
1882
1786
  .inspector-pane { width:100%; overflow:visible; border-left:0; border-top:1px solid var(--_vv-border); }
1883
- .inspector-body { gap:8px; padding:10px; }
1884
- .inspector-card,.edit-controls,.layer-controls { font-size:12px; }
1885
- .layer-controls label { min-height:44px; }
1886
- .layer-controls input { min-width:20px; width:20px; height:20px; min-height:20px; }
1887
- .notes-strip { min-height:44px; padding:0 10px; }
1888
- .notes-strip button { min-height:44px; }
1889
1787
  .status { flex-wrap:wrap; gap:0; padding:4px 8px; }
1890
1788
  .status-message { width:100%; min-height:18px; }
1891
1789
  }
1892
1790
  @media(pointer:coarse) {
1893
1791
  button,select,input,textarea { min-height:44px; }
1894
1792
  button { min-width:44px; }
1895
- summary,.layer-controls label { min-height:44px; line-height:44px; }
1793
+ summary { min-height:44px; line-height:44px; }
1896
1794
  .ribbon-content { min-height:96px; }
1897
- .notes-strip button { min-height:44px; height:44px; }
1898
- .layer-controls input { min-height:20px; }
1899
1795
  }
1900
1796
  /* Suite ribbon: tabs lead directly into Visio command groups. */
1901
1797
  .ribbon-content { min-height:104px; padding:4px 4px; }
@@ -1910,8 +1806,6 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
1910
1806
  .viewport { flex:1; width:100%; height:100%; min-height:0; padding:16px 4px; }
1911
1807
  .inspector-pane { position:absolute; inset:0 0 0 auto; width:min(320px,100%); z-index:4; overflow:auto; border-left:1px solid var(--_vv-border); box-shadow:-8px 0 24px #0002; }
1912
1808
  .pane-close { display:block; width:44px; height:44px; padding:0; font-size:22px; background:transparent; border:0; }
1913
- .inspector-kind { margin-left:auto; }
1914
- .notes-strip { display:none; }
1915
1809
  .status { min-height:48px; padding:2px 8px; }
1916
1810
  .status-message { display:none; }
1917
1811
  }
@@ -1922,12 +1816,7 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
1922
1816
  .ribbon-command { font-size:12px; min-width:88px; }
1923
1817
  .ribbon-hint { font-size:12px; line-height:20px; flex:1; }
1924
1818
  .group-label { font-size:11px; line-height:14px; }
1925
- .inspector-card,.edit-controls,.layer-controls { font-size:12px; line-height:18px; }
1926
- .selection-hint,.document-card dl { font-size:12px; }
1927
1819
  .pane-heading { font-size:12px; }
1928
- .inspector-pane .pane-heading { position:sticky; top:0; z-index:1; background:var(--_vv-secondary); flex:none; }
1929
- .status .notes-strip { border:0; padding:0 8px; background:transparent; min-height:28px; }
1930
- .status .notes-strip>span { display:none; }
1931
1820
  .status-message { flex:1; }
1932
1821
  @media(max-width:760px) {
1933
1822
  #home-panel { overflow:visible; min-height:54px; padding:4px; }
@@ -1941,7 +1830,6 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
1941
1830
  .ribbon-tools .ribbon-command { min-width:88px; font-size:12px; }
1942
1831
  .ribbon-tools .search-feedback { display:block; }
1943
1832
  .status { flex-wrap:nowrap; gap:4px; min-height:48px; }
1944
- .status .notes-strip { display:none; }
1945
1833
  }
1946
1834
  @media(prefers-reduced-motion:reduce) { * { scroll-behavior:auto !important; } }
1947
1835
  `;
@@ -1950,16 +1838,16 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
1950
1838
  var backstage_default = "/* Visio's File tab and backstage. */\n:host {\n position: relative;\n}\n/* The shared office-ui-backstage draws the File column; these style Visio's page content. */\n.backstage-page h1 {\n margin: 0 0 24px;\n font-size: 34px;\n font-weight: 300;\n}\n.backstage-page h2 {\n margin: 20px 0 8px;\n font-size: 16px;\n font-weight: 600;\n}\n.backstage-file {\n margin-top: 0 !important;\n font-size: 20px !important;\n font-weight: 400 !important;\n overflow-wrap: anywhere;\n}\n.backstage-facts {\n display: grid;\n grid-template-columns: max-content 1fr;\n gap: 8px 24px;\n max-width: 520px;\n margin: 0;\n font-size: 13px;\n}\n.backstage-facts dt {\n color: var(--_vv-muted);\n}\n.backstage-facts dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n.backstage-action {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n width: min(420px, 100%);\n min-height: 64px;\n margin: 0 0 10px;\n padding: 10px 16px;\n text-align: left;\n background: var(--_vv-surface);\n}\n.backstage-action strong {\n font-size: 14px;\n font-weight: 600;\n}\n.backstage-action span,\n.backstage-muted,\n.backstage-note-count {\n color: var(--_vv-muted);\n font-size: 12px;\n}\n.backstage-templates {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n}\n.backstage-templates .backstage-action,\n.backstage-templates ::slotted(*) {\n width: 200px;\n min-height: 120px;\n}\n/* Account: the shared profile editor, then Visio's product information. */\n.backstage-page office-ui-account {\n max-width: 560px;\n}\n.backstage-product {\n margin-top: 24px;\n}\n/* Share with people: a live session between windows of this browser. */\n.share-panel {\n max-width: 560px;\n margin-bottom: 16px;\n}\n.share-panel h2 {\n margin-top: 0 !important;\n}\n.share-field {\n display: grid;\n gap: 4px;\n max-width: 320px;\n margin: 12px 0;\n font-size: 13px;\n}\n.share-room {\n min-height: 30px;\n padding: 2px 8px;\n border: 1px solid var(--_vv-border);\n border-radius: 3px;\n background: var(--_vv-surface);\n color: inherit;\n font: inherit;\n}\n.share-buttons button {\n min-height: 32px;\n padding: 0 16px;\n}\n.share-buttons button[hidden] {\n display: none;\n}\n.share-status {\n min-height: 20px;\n font-size: 13px;\n}\n";
1951
1839
 
1952
1840
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/canvas.css?raw
1953
- var canvas_default = "/*\n * Drawing canvas focus. SVG outlines are drawn in drawing units (inches here), so a browser\n * focus ring on a shape becomes a page-sized blob. Shapes show focus with a dashed accent\n * stroke instead, only for keyboard focus; selection keeps its solid accent stroke.\n */\n.paper [data-shape-id]:focus,\n.paper [data-shape-id]:focus-visible {\n outline: none;\n}\n.paper [data-shape-id]:focus-visible > [data-geometry] {\n stroke: var(--_vv-focus) !important;\n stroke-width: 0.04 !important;\n stroke-dasharray: 0.08 0.05;\n}\n\n.paper [data-line-endpoint],\n.paper [data-resize-handle],\n.rotation-overlay [data-rotation-handle] {\n fill: var(--_vv-accent);\n stroke: white;\n stroke-width: 1.5px;\n cursor: crosshair;\n touch-action: none;\n pointer-events: all;\n}\n.paper [data-resize-frame] {\n fill: none;\n stroke: var(--_vv-accent);\n stroke-width: 1px;\n pointer-events: none;\n}\n.paper [data-resize-frame-preview] {\n stroke-dasharray: 5 3;\n}\n.paper .endpoint-preview,\n.paper .rotation-preview,\n.rotation-overlay .rotation-stem {\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n stroke-dasharray: 5 3;\n pointer-events: none;\n}\n\n.rotation-overlay {\n position: absolute;\n z-index: 1;\n pointer-events: none;\n}\n/* View > Visual Aids > Connection Points: blue x markers (presentation only, never exported). */\n.paper [data-connection-points] path {\n fill: none;\n stroke: #1f6fd1;\n stroke-width: 1.25px;\n pointer-events: none;\n}\n.paper [data-connection-points] path[data-inherited='true'] {\n stroke-opacity: 0.55;\n}\n.paper [data-connection-points] path[data-selected='true'] {\n stroke: var(--_vv-accent);\n stroke-width: 2.5px;\n}\n/* A connector end over a connection point glues to that point. */\n.paper .connect-point-target {\n fill: none;\n stroke: #2e9d43;\n stroke-width: 2px;\n pointer-events: none;\n}\n/* Connectors glued to dragged shapes, re-routed live until the move is committed. */\n.paper .connector-preview {\n fill: none;\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n stroke-dasharray: 4 3;\n pointer-events: none;\n}\n";
1841
+ var canvas_default = "/*\n * Drawing canvas focus. SVG outlines are drawn in drawing units (inches here), so a browser\n * focus ring on a shape becomes a page-sized blob. Shapes show focus with a dashed accent\n * stroke instead, only for keyboard focus; selection keeps its solid accent stroke.\n */\n.paper [data-shape-id]:focus,\n.paper [data-shape-id]:focus-visible {\n outline: none;\n}\n.paper [data-shape-id]:focus-visible > [data-geometry] {\n stroke: var(--_vv-focus) !important;\n stroke-width: 0.04 !important;\n stroke-dasharray: 0.08 0.05;\n}\n\n/* Visio's selection: white handles with a grey outline on a thin grey frame. */\n.paper [data-line-endpoint],\n.paper [data-resize-handle],\n.rotation-overlay [data-rotation-handle] {\n fill: #fff;\n stroke: var(--_vv-handle, #7a7a7a);\n stroke-width: 1.25px;\n cursor: crosshair;\n touch-action: none;\n pointer-events: all;\n}\n.paper [data-resize-handle]:hover,\n.paper [data-line-endpoint]:hover,\n.rotation-overlay [data-rotation-handle]:hover {\n fill: var(--_vv-accent-soft, #dfe5f6);\n}\n.paper [data-resize-handle='n'],\n.paper [data-resize-handle='s'] {\n cursor: ns-resize;\n}\n.paper [data-resize-handle='e'],\n.paper [data-resize-handle='w'] {\n cursor: ew-resize;\n}\n.paper [data-resize-handle='ne'],\n.paper [data-resize-handle='sw'] {\n cursor: nesw-resize;\n}\n.paper [data-resize-handle='nw'],\n.paper [data-resize-handle='se'] {\n cursor: nwse-resize;\n}\n.rotation-overlay [data-rotation-handle] {\n stroke: transparent;\n cursor: grab;\n}\n.rotation-overlay .rotation-glyph {\n fill: none;\n stroke: var(--_vv-handle, #7a7a7a);\n stroke-width: 1.5px;\n stroke-linecap: round;\n stroke-linejoin: round;\n pointer-events: none;\n}\n.rotation-overlay [data-rotation-handle]:hover + .rotation-glyph {\n stroke: var(--_vv-accent);\n}\n.paper [data-resize-frame] {\n fill: none;\n stroke: var(--_vv-handle, #7a7a7a);\n stroke-width: 1px;\n pointer-events: none;\n}\n/*\n * A selection with no frame or endpoint handles (several shapes, a group, a glued connector) is\n * marked on the shapes themselves; a framed shape keeps its own outline, as in Visio.\n */\n.viewport:not([data-selection-frame]) .paper [data-selected='true'] > [data-geometry] {\n stroke: var(--_vv-accent) !important;\n stroke-width: 0.025 !important;\n}\n.viewport:not([data-selection-frame]) .paper [role='group'][data-selected='true'] [data-geometry] {\n stroke: var(--_vv-accent) !important;\n stroke-dasharray: 0.06 0.04;\n}\n/* Handles step aside while a shape's text is edited in place. */\n.viewport[data-text-editing] [data-resize-overlay],\n.viewport[data-text-editing] .rotation-overlay,\n.viewport[data-text-editing] [data-line-endpoint] {\n display: none;\n}\n.paper [data-resize-frame-preview] {\n stroke-dasharray: 5 3;\n}\n.paper .endpoint-preview,\n.paper .rotation-preview {\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n stroke-dasharray: 5 3;\n pointer-events: none;\n}\n/* Visio shows no line from the pin to the rotation handle. */\n.rotation-overlay .rotation-stem {\n display: none;\n}\n\n.rotation-overlay {\n position: absolute;\n z-index: 1;\n pointer-events: none;\n}\n/* View > Visual Aids > Connection Points: blue x markers (presentation only, never exported). */\n.paper [data-connection-points] path {\n fill: none;\n stroke: #1f6fd1;\n stroke-width: 1.25px;\n pointer-events: none;\n}\n.paper [data-connection-points] path[data-inherited='true'] {\n stroke-opacity: 0.55;\n}\n.paper [data-connection-points] path[data-selected='true'] {\n stroke: var(--_vv-accent);\n stroke-width: 2.5px;\n}\n/* A connector end over a connection point glues to that point. */\n.paper .connect-point-target {\n fill: none;\n stroke: #2e9d43;\n stroke-width: 2px;\n pointer-events: none;\n}\n/* Connectors glued to dragged shapes, re-routed live until the move is committed. */\n.paper .connector-preview {\n fill: none;\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n stroke-dasharray: 4 3;\n pointer-events: none;\n}\n";
1954
1842
 
1955
1843
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/ribbon.css?raw
1956
- var ribbon_default = "/*\n * Microsoft Visio ribbon layout on the shared ooxml-ui controls. The shared --office-* tokens come\n * from the bridge in styles/theme.ts. Loaded after the legacy styles.ts.\n */\n\n/* Tab row: Quick Access Toolbar, then Visio's tabs. */\n.ribbon-head {\n display: flex;\n align-items: stretch;\n min-width: 0;\n border-bottom: 1px solid var(--_vv-border);\n}\n.qat {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 6px;\n border-right: 1px solid var(--_vv-border);\n}\n.qat office-ui-button {\n --office-target-size: 30px;\n}\n.ribbon-head .ribbon-tabs {\n flex: 0 1 auto;\n min-width: 0;\n height: 36px;\n border-bottom: 0;\n overflow-x: auto;\n scrollbar-width: none;\n}\n.ribbon-head .ribbon-tabs button {\n flex: none;\n min-height: 35px;\n padding: 6px 12px;\n font-size: 13px;\n}\n\n/* Panels: groups of large commands and three-row stacks, as dense as Visio's. */\n.ribbon-content {\n min-height: 96px;\n padding: 2px 2px 0;\n}\n.ribbon-row {\n display: flex;\n flex-wrap: nowrap;\n align-items: stretch;\n gap: 0;\n padding: 0;\n border: 0;\n background: transparent;\n}\n.ribbon-row office-ui-ribbon-group,\n.tools-content office-ui-ribbon-group {\n min-width: 0;\n}\noffice-ui-ribbon-group office-ui-button,\noffice-ui-ribbon-group office-ui-menu-button {\n --office-target-size: 24px;\n}\noffice-ui-ribbon-group [data-size='large'] {\n --office-target-size: 30px;\n}\noffice-ui-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 66px;\n padding: 2px 6px;\n font-size: 12px;\n}\noffice-ui-menu-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 50px;\n font-size: 12px;\n}\noffice-ui-ribbon-group [data-size='small']::part(button) {\n justify-content: flex-start;\n font-size: 12px;\n}\noffice-ui-ribbon-stack:not([orientation]) {\n justify-content: flex-start;\n padding-top: 2px;\n}\noffice-ui-ribbon-stack:not([orientation]) > * {\n min-height: 24px;\n}\noffice-ui-ribbon-group > office-ui-ribbon-stack > office-ui-ribbon-stack[orientation] {\n min-height: 32px;\n}\noffice-ui-select[data-combo] {\n --office-target-size: 24px;\n}\noffice-ui-menu-button[data-active]::part(button) {\n background: var(--_vv-accent-soft);\n box-shadow: inset 0 0 0 1px var(--_vv-accent);\n}\n[data-unsupported] {\n cursor: not-allowed;\n}\n.ribbon-check {\n display: flex;\n align-items: center;\n gap: 6px;\n min-height: 24px;\n padding: 0 6px;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n.ribbon-check[data-unsupported] {\n color: var(--_vv-muted);\n opacity: 0.6;\n}\n.ribbon-hint {\n display: none;\n}\n\n/* Drawing window with optional rulers. */\n.canvas-area {\n display: grid;\n flex: 1;\n min-width: 0;\n min-height: 0;\n grid-template-columns: 0 1fr;\n grid-template-rows: 0 1fr;\n}\n.canvas-area[data-ruler='true'] {\n grid-template-columns: 18px 1fr;\n grid-template-rows: 18px 1fr;\n}\n.canvas-area > .viewport {\n grid-column: 2;\n grid-row: 2;\n}\n.ruler,\n.ruler-corner {\n display: none;\n background: var(--_vv-surface);\n}\n.canvas-area[data-ruler='true'] > .ruler,\n.canvas-area[data-ruler='true'] > .ruler-corner {\n display: block;\n}\n.ruler-corner {\n grid-column: 1;\n grid-row: 1;\n border-right: 1px solid var(--_vv-border);\n border-bottom: 1px solid var(--_vv-border);\n}\n/* The shared office-ui-ruler draws ticks, labels and its own edge. */\n.ruler-h {\n grid-column: 2;\n grid-row: 1;\n}\n.ruler-v {\n grid-column: 1;\n grid-row: 2;\n}\n\n.page-bar {\n display: flex;\n flex: none;\n align-items: stretch;\n min-width: 0;\n border-top: 1px solid var(--_vv-border);\n background: var(--_vv-secondary);\n}\n.page-bar office-ui-menu-button {\n --office-target-size: 28px;\n flex: none;\n padding: 0 2px;\n}\n.page-tabs {\n flex: 1;\n min-width: 0;\n border-top: 0;\n}\noffice-ui-status-bar.status {\n gap: 16px;\n min-height: 29px;\n padding: 0 8px 0 12px;\n font-size: 11px;\n background: var(--_vv-surface);\n color: var(--_vv-muted);\n}\noffice-ui-status-item[value=''] {\n display: none;\n}\noffice-ui-status-item[data-page-status] {\n color: var(--_vv-ink);\n}\n.status-message {\n flex: 1;\n min-width: 0;\n}\n.viewport[data-tool='ellipse'],\n.viewport[data-tool='ellipse'] .paper [data-shape-id],\n.viewport[data-tool='rectangle'],\n.viewport[data-tool='rectangle'] .paper [data-shape-id],\n.viewport[data-tool='line'],\n.viewport[data-tool='line'] .paper [data-shape-id],\n.viewport[data-tool='freeform'],\n.viewport[data-tool='freeform'] .paper [data-shape-id],\n.viewport[data-tool='arc'],\n.viewport[data-tool='arc'] .paper [data-shape-id],\n.viewport[data-tool='pencil'],\n.viewport[data-tool='pencil'] .paper [data-shape-id],\n.viewport[data-tool='connector'],\n.viewport[data-tool='connector'] .paper [data-shape-id],\n.viewport[data-tool='connection-point'],\n.viewport[data-tool='connection-point'] .paper [data-shape-id] {\n cursor: crosshair;\n}\n.viewport[data-tool='ellipse'],\n.viewport[data-tool='rectangle'],\n.viewport[data-tool='line'],\n.viewport[data-tool='freeform'],\n.viewport[data-tool='arc'],\n.viewport[data-tool='pencil'],\n.viewport[data-tool='connector'] {\n touch-action: none;\n}\n/* Connector tool: the shape under the pointer is the glue target (Visio's green glue box). */\n.viewport .paper [data-connect-target='true'] > path[data-geometry] {\n /* Drawing units (inches), like the canvas focus stroke; wins over a focused shape's dashes. */\n stroke: var(--_vv-accent) !important;\n stroke-width: 0.05 !important;\n stroke-opacity: 1 !important;\n stroke-dasharray: none !important;\n}\npath.draw-preview {\n fill: none;\n}\n.draw-preview {\n fill: var(--_vv-accent-soft);\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n stroke-dasharray: 4 3;\n pointer-events: none;\n}\n.viewport[data-grid='true'] .paper {\n --_vv-grid-minor: rgb(0 0 0 / 7%);\n --_vv-grid-major: rgb(0 0 0 / 16%);\n background-image:\n linear-gradient(to right, var(--_vv-grid-major) 1px, transparent 1px),\n linear-gradient(to top, var(--_vv-grid-major) 1px, transparent 1px),\n linear-gradient(to right, var(--_vv-grid-minor) 1px, transparent 1px),\n linear-gradient(to top, var(--_vv-grid-minor) 1px, transparent 1px);\n background-size:\n var(--vv-inch, 96px) var(--vv-inch, 96px),\n var(--vv-inch, 96px) var(--vv-inch, 96px),\n calc(var(--vv-inch, 96px) / 4) calc(var(--vv-inch, 96px) / 4),\n calc(var(--vv-inch, 96px) / 4) calc(var(--vv-inch, 96px) / 4);\n background-position: left bottom;\n}\n:host(:fullscreen) {\n width: 100vw;\n height: 100vh;\n}\n\n@media (max-width: 760px) {\n .ribbon-row {\n flex: 1;\n min-width: 0;\n }\n .qat office-ui-button {\n --office-target-size: 44px;\n }\n .ribbon-tools .tools-content office-ui-ribbon-group {\n width: 100%;\n }\n office-ui-status-bar.status {\n gap: 4px;\n min-height: 48px;\n }\n office-ui-status-bar .notes-strip,\n office-ui-status-bar .status-message,\n office-ui-status-item[data-shape-status],\n office-ui-status-item[data-page-status] {\n display: none;\n }\n .zoom-controls {\n flex: 1;\n min-width: 0;\n --office-zoom-slider-width: 0;\n }\n}\n\n/* Visio's Tell me box sits right after the tabs. */\n.tell-me {\n flex: 0 1 280px;\n min-width: 140px;\n margin: 0 8px;\n --office-target-size: 28px;\n}\n.presentation-mode {\n --office-target-size: 24px;\n}\n@media (max-width: 760px) {\n .tell-me {\n display: none;\n }\n}\n";
1844
+ var ribbon_default = "/*\n * Microsoft Visio ribbon layout on the shared ooxml-ui controls. The shared --office-* tokens come\n * from the bridge in styles/theme.ts. Loaded after the legacy styles.ts.\n */\n\n/* Tab row: Quick Access Toolbar, then Visio's tabs. */\n.ribbon-head {\n display: flex;\n align-items: stretch;\n min-width: 0;\n border-bottom: 1px solid var(--_vv-border);\n}\n.ribbon-head .ribbon-tabs {\n flex: 0 1 auto;\n min-width: 0;\n height: 36px;\n border-bottom: 0;\n overflow-x: auto;\n scrollbar-width: none;\n}\n.ribbon-head .ribbon-tabs button {\n flex: none;\n min-height: 35px;\n padding: 6px 12px;\n font-size: 13px;\n}\n\n/* Panels: groups of large commands and three-row stacks, as dense as Visio's. */\n.ribbon-content {\n min-height: 96px;\n padding: 2px 2px 0;\n}\n.ribbon-row {\n display: flex;\n flex-wrap: nowrap;\n align-items: stretch;\n gap: 0;\n padding: 0;\n border: 0;\n background: transparent;\n}\n.ribbon-row office-ui-ribbon-group,\n.tools-content office-ui-ribbon-group {\n min-width: 0;\n}\noffice-ui-ribbon-group office-ui-button,\noffice-ui-ribbon-group office-ui-menu-button {\n --office-target-size: 24px;\n}\noffice-ui-ribbon-group [data-size='large'] {\n --office-target-size: 30px;\n}\noffice-ui-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 66px;\n padding: 2px 6px;\n font-size: 12px;\n}\noffice-ui-menu-button[variant='stacked']::part(button) {\n min-width: 48px;\n min-height: 50px;\n font-size: 12px;\n}\noffice-ui-ribbon-group [data-size='small']::part(button) {\n justify-content: flex-start;\n font-size: 12px;\n}\noffice-ui-ribbon-stack:not([orientation]) {\n justify-content: flex-start;\n padding-top: 2px;\n}\noffice-ui-ribbon-stack:not([orientation]) > * {\n min-height: 24px;\n}\noffice-ui-ribbon-group > office-ui-ribbon-stack > office-ui-ribbon-stack[orientation] {\n min-height: 32px;\n}\noffice-ui-select[data-combo] {\n --office-target-size: 24px;\n}\noffice-ui-menu-button[data-active]::part(button) {\n background: var(--_vv-accent-soft);\n box-shadow: inset 0 0 0 1px var(--_vv-accent);\n}\n[data-unsupported] {\n cursor: not-allowed;\n}\n.ribbon-check {\n display: flex;\n align-items: center;\n gap: 6px;\n min-height: 24px;\n padding: 0 6px;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n.ribbon-check[data-unsupported] {\n color: var(--_vv-muted);\n opacity: 0.6;\n}\n.ribbon-hint {\n display: none;\n}\n\n/* Drawing window with optional rulers. */\n.canvas-area {\n display: grid;\n flex: 1;\n min-width: 0;\n min-height: 0;\n grid-template-columns: 0 1fr;\n grid-template-rows: 0 1fr;\n}\n.canvas-area[data-ruler='true'] {\n grid-template-columns: 18px 1fr;\n grid-template-rows: 18px 1fr;\n}\n.canvas-area > .viewport {\n grid-column: 2;\n grid-row: 2;\n}\n.ruler,\n.ruler-corner {\n display: none;\n background: var(--_vv-surface);\n}\n.canvas-area[data-ruler='true'] > .ruler,\n.canvas-area[data-ruler='true'] > .ruler-corner {\n display: block;\n}\n.ruler-corner {\n grid-column: 1;\n grid-row: 1;\n border-right: 1px solid var(--_vv-border);\n border-bottom: 1px solid var(--_vv-border);\n}\n/* The shared office-ui-ruler draws ticks, labels and its own edge. */\n.ruler-h {\n grid-column: 2;\n grid-row: 1;\n}\n.ruler-v {\n grid-column: 1;\n grid-row: 2;\n}\n\n.page-bar {\n display: flex;\n flex: none;\n align-items: stretch;\n min-width: 0;\n border-top: 1px solid var(--_vv-border);\n background: var(--_vv-secondary);\n}\n.page-bar office-ui-menu-button {\n --office-target-size: 28px;\n flex: none;\n padding: 0 2px;\n}\n.page-tabs {\n flex: 1;\n min-width: 0;\n border-top: 0;\n}\noffice-ui-status-bar.status {\n gap: 16px;\n min-height: 29px;\n padding: 0 8px 0 12px;\n font-size: 11px;\n background: var(--_vv-surface);\n color: var(--_vv-ink);\n}\noffice-ui-status-item[value=''] {\n display: none;\n}\n.viewport[data-tool='ellipse'],\n.viewport[data-tool='ellipse'] .paper [data-shape-id],\n.viewport[data-tool='rectangle'],\n.viewport[data-tool='rectangle'] .paper [data-shape-id],\n.viewport[data-tool='line'],\n.viewport[data-tool='line'] .paper [data-shape-id],\n.viewport[data-tool='freeform'],\n.viewport[data-tool='freeform'] .paper [data-shape-id],\n.viewport[data-tool='arc'],\n.viewport[data-tool='arc'] .paper [data-shape-id],\n.viewport[data-tool='pencil'],\n.viewport[data-tool='pencil'] .paper [data-shape-id],\n.viewport[data-tool='connector'],\n.viewport[data-tool='connector'] .paper [data-shape-id],\n.viewport[data-tool='connection-point'],\n.viewport[data-tool='connection-point'] .paper [data-shape-id] {\n cursor: crosshair;\n}\n.viewport[data-tool='ellipse'],\n.viewport[data-tool='rectangle'],\n.viewport[data-tool='line'],\n.viewport[data-tool='freeform'],\n.viewport[data-tool='arc'],\n.viewport[data-tool='pencil'],\n.viewport[data-tool='connector'] {\n touch-action: none;\n}\n/* Connector tool: the shape under the pointer is the glue target (Visio's green glue box). */\n.viewport .paper [data-connect-target='true'] > path[data-geometry] {\n /* Drawing units (inches), like the canvas focus stroke; wins over a focused shape's dashes. */\n stroke: var(--_vv-accent) !important;\n stroke-width: 0.05 !important;\n stroke-opacity: 1 !important;\n stroke-dasharray: none !important;\n}\npath.draw-preview {\n fill: none;\n}\n.draw-preview {\n fill: var(--_vv-accent-soft);\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n stroke-dasharray: 4 3;\n pointer-events: none;\n}\n.viewport[data-grid='true'] .paper {\n --_vv-grid-minor: rgb(0 0 0 / 7%);\n --_vv-grid-major: rgb(0 0 0 / 16%);\n background-image:\n linear-gradient(to right, var(--_vv-grid-major) 1px, transparent 1px),\n linear-gradient(to top, var(--_vv-grid-major) 1px, transparent 1px),\n linear-gradient(to right, var(--_vv-grid-minor) 1px, transparent 1px),\n linear-gradient(to top, var(--_vv-grid-minor) 1px, transparent 1px);\n background-size:\n var(--vv-inch, 96px) var(--vv-inch, 96px),\n var(--vv-inch, 96px) var(--vv-inch, 96px),\n calc(var(--vv-inch, 96px) / 4) calc(var(--vv-inch, 96px) / 4),\n calc(var(--vv-inch, 96px) / 4) calc(var(--vv-inch, 96px) / 4);\n background-position: left bottom;\n}\n:host(:fullscreen) {\n width: 100vw;\n height: 100vh;\n}\n\n@media (max-width: 760px) {\n .ribbon-row {\n flex: 1;\n min-width: 0;\n }\n .ribbon-tools .tools-content office-ui-ribbon-group {\n width: 100%;\n }\n office-ui-status-bar.status {\n gap: 4px;\n min-height: 48px;\n }\n office-ui-status-item:not([data-page-status]) {\n display: none;\n }\n .zoom-controls {\n flex: 1;\n min-width: 0;\n --office-zoom-slider-width: 0;\n }\n}\n\n/* Visio's Tell me box sits right after the tabs. */\n.tell-me {\n flex: 0 1 280px;\n min-width: 140px;\n margin: 0 8px;\n --office-target-size: 28px;\n}\n.presentation-mode {\n --office-target-size: 24px;\n}\n@media (max-width: 760px) {\n .tell-me {\n display: none;\n }\n}\n";
1957
1845
 
1958
1846
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/shapes.css?raw
1959
- var shapes_default = "/* Visio's Shapes window: stencil views, More/Quick Shapes rows and a two-column master grid. */\n.shapes-pane {\n display: flex;\n flex: 0 0 232px;\n flex-direction: column;\n width: 232px;\n min-width: 0;\n overflow: hidden;\n border-right: 1px solid var(--_vv-border);\n background: var(--_vv-secondary);\n}\n.shapes-pane .pane-heading {\n height: 42px;\n font-size: 18px;\n font-weight: 400;\n color: var(--_vv-accent);\n}\n.pane-collapse {\n min-height: 28px;\n width: 28px;\n padding: 0;\n border-color: transparent;\n background: transparent;\n font-size: 18px;\n line-height: 1;\n}\n.shapes-views {\n display: flex;\n gap: 14px;\n padding: 4px 12px 0;\n border-bottom: 1px solid var(--_vv-border);\n}\n.shapes-views button {\n position: relative;\n min-height: 28px;\n padding: 2px 0 6px;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: var(--_vv-muted);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.shapes-views button[aria-selected='true'] {\n color: var(--_vv-ink);\n}\n.shapes-views button[aria-selected='true']::after {\n content: '';\n position: absolute;\n inset: auto 0 -1px;\n height: 2px;\n background: var(--_vv-accent);\n}\n#shapes-stencils,\n#shapes-search {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 4px 0 8px;\n}\n.shapes-row {\n display: flex;\n align-items: center;\n width: 100%;\n min-height: 30px;\n padding: 0 12px;\n border: 0;\n border-radius: 0;\n background: transparent;\n font-size: 12px;\n text-align: left;\n}\n.shapes-row::after {\n content: '\u203A';\n margin-left: auto;\n font-size: 15px;\n}\n.shapes-row[aria-expanded='true']::after {\n transform: rotate(90deg);\n}\n.stencil-header {\n display: flex;\n align-items: stretch;\n margin: 2px 0;\n border-left: 3px solid var(--_vv-accent);\n background: var(--_vv-accent-soft);\n}\n.stencil-title {\n flex: 1;\n min-width: 0;\n padding: 6px 12px;\n border: 0;\n border-radius: 0;\n background: transparent;\n font-size: 12px;\n font-weight: 600;\n text-align: left;\n}\n.stencil-title::before {\n content: '\\25BE';\n display: inline-block;\n width: 12px;\n color: var(--_vv-muted);\n}\n.stencil-title[aria-expanded='false']::before {\n content: '\\25B8';\n}\n.stencil-close {\n min-height: 0;\n width: 26px;\n padding: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: var(--_vv-muted);\n font-size: 14px;\n}\n#shapes-quick {\n border-bottom: 1px solid var(--_vv-border);\n}\n#shapes-quick[hidden],\n.masters[hidden] {\n display: none;\n}\n.masters {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2px;\n margin: 0;\n padding: 4px 6px;\n list-style: none;\n}\n.masters li[hidden] {\n display: none;\n}\n.master {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 40px;\n padding: 4px 6px;\n border-color: transparent;\n background: transparent;\n font-size: 11px;\n line-height: 1.2;\n text-align: left;\n cursor: grab;\n}\n.master svg {\n flex: none;\n width: 28px;\n height: 28px;\n fill: var(--_vv-surface);\n stroke: var(--_vv-accent);\n stroke-width: 1.2;\n stroke-linejoin: round;\n}\n.master[data-unsupported] {\n cursor: not-allowed;\n opacity: 0.45;\n}\n.master[data-unsupported]:hover {\n border-color: transparent;\n background: transparent;\n}\n.shapes-search-field {\n display: block;\n width: calc(100% - 24px);\n margin: 6px 12px;\n}\n.shapes-empty {\n margin: 8px 12px;\n color: var(--_vv-muted);\n font-size: 12px;\n}\n@media (max-width: 760px) {\n .shapes-pane {\n position: absolute;\n inset: 0 auto 0 0;\n z-index: 3;\n width: min(260px, 100%);\n box-shadow: 8px 0 24px #0002;\n }\n .master {\n min-height: 44px;\n }\n}\n.shapes-strip {\n display: flex;\n flex: 0 0 28px;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n width: 28px;\n min-width: 28px;\n padding: 8px 0;\n border: 0;\n border-right: 1px solid var(--_vv-border);\n border-radius: 0;\n background: var(--_vv-secondary);\n color: var(--_vv-accent);\n font-size: 16px;\n}\n.shapes-strip[hidden] {\n display: none;\n}\n.shapes-strip-label {\n writing-mode: vertical-rl;\n color: var(--_vv-ink);\n font-size: 12px;\n}\n/* Visio's floating Pan & Zoom window. */\n.pan-zoom {\n position: absolute;\n right: 16px;\n bottom: 16px;\n z-index: 5;\n width: 240px;\n border: 1px solid var(--_vv-border);\n background: var(--_vv-surface);\n box-shadow: 0 6px 20px rgb(0 0 0 / 18%);\n}\n.pan-zoom[hidden] {\n display: none;\n}\n.pan-zoom-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 2px 4px 2px 10px;\n border-bottom: 1px solid var(--_vv-border);\n font-size: 12px;\n font-weight: 600;\n}\n.pan-zoom-close {\n min-height: 24px;\n width: 24px;\n padding: 0;\n border-color: transparent;\n background: transparent;\n}\n.pan-zoom-view {\n display: grid;\n place-items: center;\n height: 180px;\n padding: 8px;\n background: var(--_vv-bg);\n cursor: crosshair;\n touch-action: none;\n}\n.pan-zoom-view svg {\n max-width: 100%;\n max-height: 100%;\n background: #fff;\n box-shadow: 0 1px 3px rgb(0 0 0 / 20%);\n}\n.pan-zoom-frame {\n fill: rgb(220 38 38 / 8%);\n stroke: #dc2626;\n stroke-width: 2px;\n}\n";
1847
+ var shapes_default = "/* Visio's Shapes window: Search shapes, More/Quick Shapes rows and a two-column master grid. */\n.shapes-pane {\n display: flex;\n flex: 0 0 232px;\n flex-direction: column;\n width: 232px;\n min-width: 0;\n overflow: hidden;\n border-right: 1px solid var(--_vv-border);\n background: var(--_vv-surface);\n}\n.shapes-pane .pane-heading {\n height: 46px;\n padding: 10px 8px 4px 12px;\n border-bottom: 0;\n font-size: 21px;\n font-weight: 300;\n color: var(--_vv-accent);\n}\n.pane-collapse {\n min-height: 28px;\n width: 28px;\n padding: 0;\n border-color: transparent;\n background: transparent;\n font-size: 18px;\n line-height: 1;\n}\n.shapes-search-box {\n position: relative;\n margin: 6px 12px 10px;\n}\n.shapes-search-box office-ui-icon {\n position: absolute;\n top: 50%;\n right: 8px;\n translate: 0 -50%;\n color: var(--_vv-accent);\n font-size: 15px;\n pointer-events: none;\n}\n#shapes-stencils,\n#shapes-search {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 4px 0 8px;\n}\n.shapes-row {\n display: flex;\n align-items: center;\n width: 100%;\n min-height: 30px;\n padding: 0 12px;\n border: 0;\n border-radius: 0;\n background: transparent;\n font-size: 12px;\n text-align: left;\n}\n/* More Shapes opens a menu, so it alone carries Visio's small arrow, right after its label. */\n.shapes-row[aria-haspopup]::after {\n content: '';\n margin-left: 12px;\n border: 4px solid transparent;\n border-left-color: currentColor;\n}\n.shapes-row:hover,\n.stencil-header:hover {\n background: var(--_vv-secondary);\n}\n.stencil-header {\n display: flex;\n align-items: stretch;\n margin: 1px 6px;\n}\n/* The open stencil is Visio's filled, bold title bar. */\n.stencil-header:has(.stencil-title[aria-expanded='true']),\n.stencil-header:has(.stencil-title[aria-expanded='true']):hover {\n background: color-mix(in srgb, var(--_vv-accent) 28%, var(--_vv-surface));\n}\n.stencil-title {\n flex: 1;\n min-width: 0;\n padding: 5px 6px;\n border: 0;\n border-radius: 0;\n background: transparent;\n font-size: 12px;\n font-weight: 400;\n text-align: left;\n}\n.stencil-title[aria-expanded='true'] {\n font-weight: 600;\n}\n.stencil-close {\n min-height: 0;\n width: 26px;\n padding: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: var(--_vv-muted);\n font-size: 14px;\n}\n#shapes-quick {\n border-bottom: 1px solid var(--_vv-border);\n}\n.stencil-header + .masters {\n margin-top: 6px;\n border-top: 1px solid var(--_vv-border);\n}\n#shapes-quick[hidden],\n.masters[hidden] {\n display: none;\n}\n.masters {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2px;\n margin: 0;\n padding: 4px 6px;\n list-style: none;\n}\n.masters li[hidden] {\n display: none;\n}\n.master {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 40px;\n padding: 4px 6px;\n border-color: transparent;\n background: transparent;\n font-size: 12px;\n line-height: 1.2;\n text-align: left;\n cursor: grab;\n}\n.master svg {\n flex: none;\n width: 28px;\n height: 28px;\n fill: var(--_vv-surface);\n stroke: var(--_vv-ink);\n stroke-width: 1;\n stroke-linejoin: round;\n}\n.master[data-unsupported] {\n cursor: not-allowed;\n opacity: 0.45;\n}\n.master[data-unsupported]:hover {\n border-color: transparent;\n background: transparent;\n}\n.shapes-search-field {\n display: block;\n width: 100%;\n padding-right: 28px;\n border-radius: 0;\n}\n.shapes-empty {\n margin: 8px 12px;\n color: var(--_vv-muted);\n font-size: 12px;\n}\n@media (max-width: 760px) {\n .shapes-pane {\n position: absolute;\n inset: 0 auto 0 0;\n z-index: 3;\n width: min(260px, 100%);\n box-shadow: 8px 0 24px #0002;\n }\n .master {\n min-height: 44px;\n }\n}\n.shapes-strip {\n display: flex;\n flex: 0 0 28px;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n width: 28px;\n min-width: 28px;\n padding: 8px 0;\n border: 0;\n border-right: 1px solid var(--_vv-border);\n border-radius: 0;\n background: var(--_vv-secondary);\n color: var(--_vv-accent);\n font-size: 16px;\n}\n.shapes-strip[hidden] {\n display: none;\n}\n.shapes-strip-label {\n writing-mode: vertical-rl;\n color: var(--_vv-ink);\n font-size: 12px;\n}\n/* Visio's floating Pan & Zoom window. */\n.pan-zoom {\n position: absolute;\n right: 16px;\n bottom: 16px;\n z-index: 5;\n width: 240px;\n border: 1px solid var(--_vv-border);\n background: var(--_vv-surface);\n box-shadow: 0 6px 20px rgb(0 0 0 / 18%);\n}\n.pan-zoom[hidden] {\n display: none;\n}\n.pan-zoom-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 2px 4px 2px 10px;\n border-bottom: 1px solid var(--_vv-border);\n font-size: 12px;\n font-weight: 600;\n}\n.pan-zoom-close {\n min-height: 24px;\n width: 24px;\n padding: 0;\n border-color: transparent;\n background: transparent;\n}\n.pan-zoom-view {\n display: grid;\n place-items: center;\n height: 180px;\n padding: 8px;\n background: var(--_vv-bg);\n cursor: crosshair;\n touch-action: none;\n}\n.pan-zoom-view svg {\n max-width: 100%;\n max-height: 100%;\n background: #fff;\n box-shadow: 0 1px 3px rgb(0 0 0 / 20%);\n}\n.pan-zoom-frame {\n fill: rgb(220 38 38 / 8%);\n stroke: #dc2626;\n stroke-width: 2px;\n}\n";
1960
1848
 
1961
1849
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/size-position.css?raw
1962
- var size_position_default = ".size-position {\n position: absolute;\n right: 16px;\n bottom: 16px;\n z-index: 8;\n width: 250px;\n max-width: calc(100% - 32px);\n max-height: calc(100% - 32px);\n overflow: auto;\n padding: 12px;\n background: var(--_vv-surface);\n color: var(--_vv-ink);\n border: 1px solid var(--_vv-border);\n border-radius: 4px;\n box-shadow: 0 4px 16px #0002;\n}\n.size-position[hidden] {\n display: none;\n}\n.size-position .pane-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n}\n.size-position label {\n display: grid;\n grid-template-columns: 90px 1fr;\n align-items: center;\n gap: 8px;\n margin: 8px 0;\n}\n.size-position input {\n min-width: 0;\n width: 100%;\n box-sizing: border-box;\n padding: 5px;\n font: inherit;\n color: inherit;\n background: var(--_vv-surface);\n border: 1px solid var(--_vv-border);\n}\n.size-position p {\n font-size: 12px;\n margin: 8px 0 0;\n}\n.size-position [role='alert'] {\n color: var(--_vv-error, #a4262c);\n}\n";
1850
+ var size_position_default = "/* Visio's Size & Position window: anchored at the bottom left of the drawing, a vertical caption\n strip and rows of label, value and unit. */\n.size-position {\n position: absolute;\n left: 16px;\n bottom: 16px;\n z-index: 8;\n display: flex;\n width: 236px;\n max-width: calc(100% - 32px);\n max-height: calc(100% - 32px);\n overflow: auto;\n background: var(--_vv-surface);\n color: var(--_vv-ink);\n border: 1px solid var(--_vv-border);\n box-shadow: 0 2px 8px #0002;\n font-size: 12px;\n}\n.size-position[hidden] {\n display: none;\n}\n/* Anchored to the drawing window, so it clears the Shapes window or its minimised strip. */\n.shapes-pane:not([hidden]) ~ .size-position {\n left: 248px;\n}\n.shapes-strip:not([hidden]) ~ .size-position {\n left: 44px;\n}\n@media (max-width: 760px) {\n .shapes-pane:not([hidden]) ~ .size-position {\n left: 16px;\n }\n}\n.size-caption {\n display: flex;\n flex: none;\n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n width: 24px;\n padding: 8px 0 4px;\n}\n.size-caption strong {\n writing-mode: vertical-rl;\n color: var(--_vv-accent);\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n white-space: nowrap;\n}\n.size-caption button {\n width: 22px;\n min-width: 0;\n min-height: 22px;\n padding: 0;\n border: 0;\n background: transparent;\n color: var(--_vv-muted);\n font-size: 15px;\n line-height: 1;\n}\n.size-body {\n flex: 1;\n min-width: 0;\n padding: 4px 6px 4px 0;\n}\n.size-position label {\n display: grid;\n grid-template-columns: 58px 1fr auto;\n align-items: center;\n gap: 4px;\n border-bottom: 1px solid var(--_vv-border);\n}\n.size-position label > span:first-child {\n padding: 4px 6px;\n border-right: 1px solid var(--_vv-border);\n color: var(--_vv-muted);\n}\n.size-position input {\n min-width: 0;\n width: 100%;\n min-height: 0;\n box-sizing: border-box;\n padding: 3px 4px;\n font: inherit;\n color: inherit;\n background: transparent;\n border: 0;\n border-radius: 0;\n}\n.size-position input:focus-visible {\n outline: 1px solid var(--_vv-focus);\n outline-offset: -1px;\n}\n.size-unit {\n padding-right: 4px;\n color: var(--_vv-muted);\n}\n.size-position p {\n margin: 6px 6px 2px;\n color: var(--_vv-muted);\n}\n.size-position p:empty {\n display: none;\n}\n.size-position [role='alert'] {\n color: var(--_vv-danger);\n}\n";
1963
1851
 
1964
1852
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/text-tool.css?raw
1965
1853
  var text_tool_default = ".viewport[data-tool='text'] {\n cursor: text;\n touch-action: none;\n}\n.text-box-draft {\n box-sizing: border-box;\n overflow: auto;\n position: absolute;\n z-index: 3;\n width: 300px;\n max-width: calc(100% - 16px);\n padding: 12px;\n background: var(--_vv-surface);\n color: var(--_vv-ink);\n border: 1px solid var(--_vv-accent);\n border-radius: 4px;\n box-shadow: 0 2px 12px #0003;\n}\n.text-box-draft label {\n display: grid;\n gap: 6px;\n font-weight: 600;\n}\n.text-box-draft textarea {\n box-sizing: border-box;\n width: 100%;\n resize: vertical;\n font: inherit;\n}\n.text-box-draft p {\n margin: 8px 0;\n font-size: 12px;\n}\n.text-box-draft > div {\n display: flex;\n gap: 8px;\n}\n.text-box-draft-frame {\n pointer-events: none;\n}\n.paper [data-shape-id][data-selected='true'] > [data-text-hit],\n.paper [data-shape-id]:focus-visible > [data-text-hit] {\n stroke: var(--_vv-accent);\n stroke-width: 1px;\n}\n";
@@ -1988,6 +1876,194 @@ var layout_default = "/* Layout, Assign to Layer, Select by Type, Paste Special
1988
1876
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/text-features.css?raw
1989
1877
  var text_features_default = "/* Text dialog, Symbol, Field and Language dialogs, and the Text Block tool overlay. */\n.text-dialog-tabs {\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n margin: 0 0 12px;\n border-bottom: 1px solid var(--_vv-border);\n}\n.text-dialog-tabs [role='tab'] {\n padding: 6px 10px;\n color: inherit;\n background: transparent;\n border: 1px solid transparent;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n font: inherit;\n}\n.text-dialog-tabs [role='tab'][aria-selected='true'] {\n border-bottom-color: var(--_vv-accent);\n font-weight: 600;\n}\n.text-dialog-panel {\n display: grid;\n gap: 10px;\n min-width: min(420px, 80vw);\n}\n.field-dialog label,\n.language-dialog label {\n min-width: min(420px, 80vw);\n margin: 0 0 10px;\n}\n.text-dialog-panel label,\n.field-dialog label,\n.language-dialog label {\n display: grid;\n grid-template-columns: minmax(150px, 1fr) minmax(140px, 1fr);\n align-items: center;\n gap: 12px;\n}\n.text-dialog-panel label.text-dialog-check {\n display: flex;\n gap: 8px;\n}\n.text-dialog-panel input:not([type='checkbox']),\n.text-dialog-panel select,\n.field-dialog input,\n.field-dialog select,\n.language-dialog select {\n box-sizing: border-box;\n width: 100%;\n min-height: 28px;\n padding: 4px 6px;\n color: inherit;\n background: var(--_vv-surface);\n border: 1px solid var(--_vv-border);\n border-radius: 3px;\n}\n.text-dialog .text-dialog-note,\n.field-dialog .text-dialog-note,\n.symbol-dialog .text-dialog-note {\n max-width: 440px;\n margin: 8px 0 0;\n font-size: 12px;\n}\n.text-dialog [role='alert'],\n.field-dialog [role='alert'],\n.symbol-dialog [role='alert'],\n.language-dialog [role='alert'] {\n margin: 6px 0 0;\n color: var(--office-error, #b00020);\n}\n.symbol-dialog office-ui-symbol-picker {\n min-width: min(360px, 80vw);\n}\n@media (max-width: 480px) {\n .text-dialog-panel label,\n .field-dialog label,\n .language-dialog label {\n grid-template-columns: 1fr;\n gap: 5px;\n }\n}\n\n/* Text Block tool: the text block frame replaces the shape's resize and rotation handles. */\n.viewport[data-text-block='true'] [data-resize-overlay],\n.viewport[data-text-block='true'] .rotation-overlay {\n display: none;\n}\n.paper [data-text-block-frame] {\n fill: transparent;\n stroke: var(--_vv-accent);\n stroke-width: 1px;\n stroke-dasharray: 4 3;\n cursor: move;\n pointer-events: all;\n touch-action: none;\n}\n.paper circle[data-text-block-handle] {\n fill: white;\n stroke: var(--_vv-accent);\n stroke-width: 1.5px;\n cursor: crosshair;\n pointer-events: all;\n touch-action: none;\n}\n.paper circle[data-text-block-handle='rotate'] {\n fill: var(--_vv-accent);\n cursor: grab;\n}\n.paper [data-text-block-preview] [data-text-block-frame] {\n stroke-dasharray: 2 2;\n}\n.text-dialog-panel[hidden],\n.field-dialog label[hidden] {\n display: none;\n}\n";
1990
1878
 
1879
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/task-pane.css?raw
1880
+ var task_pane_default = `/* The right task pane (Shape Data, Layers): the shared office-ui-task-pane, one view at a time. */
1881
+ .inspector-pane {
1882
+ flex: 0 0 288px;
1883
+ width: 288px;
1884
+ overflow: hidden;
1885
+ border-left: 1px solid var(--_vv-border);
1886
+ background: var(--_vv-surface);
1887
+ --office-background: var(--_vv-surface);
1888
+ --office-surface: var(--_vv-secondary);
1889
+ --office-foreground: var(--_vv-ink);
1890
+ }
1891
+ .inspector-pane [data-pane-view]:not([data-active]) {
1892
+ display: none;
1893
+ }
1894
+ .selection-hint {
1895
+ margin: 24px 0;
1896
+ text-align: center;
1897
+ color: var(--_vv-muted);
1898
+ font-size: 12px;
1899
+ }
1900
+ /* Shape Data reads as Visio's two-column window: a label column and a value column in rows. */
1901
+ .shape-inspector {
1902
+ font-size: 12px;
1903
+ line-height: 18px;
1904
+ }
1905
+ .shape-inspector div {
1906
+ max-height: none;
1907
+ overflow: visible;
1908
+ }
1909
+ .shape-inspector dl {
1910
+ gap: 0;
1911
+ margin: 6px 0;
1912
+ border-top: 1px solid var(--_vv-border);
1913
+ }
1914
+ .shape-inspector dt,
1915
+ .shape-inspector dd {
1916
+ padding: 4px 6px;
1917
+ border-bottom: 1px solid var(--_vv-border);
1918
+ }
1919
+ .shape-inspector dt {
1920
+ font-weight: 400;
1921
+ color: var(--_vv-muted);
1922
+ border-right: 1px solid var(--_vv-border);
1923
+ }
1924
+ .shape-inspector h3 {
1925
+ font-size: 12px;
1926
+ }
1927
+
1928
+ /* Status bar: Visio's Page, Width, Height, Angle and language; messages are for screen readers. */
1929
+ office-ui-status-bar.status {
1930
+ gap: 18px;
1931
+ /* A host may recolour the bar: see the status aliases in theme.ts. */
1932
+ background: var(--_vv-status-bg);
1933
+ color: var(--_vv-status-ink);
1934
+ }
1935
+ /*
1936
+ * Office Theme "Colorful" (File > Options, or the status-bar attribute): the bar takes the product
1937
+ * colour, as desktop Visio's does, and its controls draw in the bar's own ink.
1938
+ */
1939
+ :host([status-bar='colorful']) office-ui-status-bar.status {
1940
+ border-top-color: transparent;
1941
+ accent-color: var(--_vv-status-ink);
1942
+ --office-foreground: currentColor;
1943
+ --office-muted-foreground: currentColor;
1944
+ --office-accent: currentColor;
1945
+ --office-ring: currentColor;
1946
+ --office-border: color-mix(in srgb, currentColor 45%, transparent);
1947
+ --office-surface: color-mix(in srgb, currentColor 18%, transparent);
1948
+ --office-background: color-mix(in srgb, currentColor 18%, transparent);
1949
+ }
1950
+ :host([status-bar='colorful']) .status-message[data-error] {
1951
+ color: inherit;
1952
+ font-weight: 600;
1953
+ }
1954
+ /* Presentation Mode sits beside the zoom, at the trailing edge. */
1955
+ office-ui-status-bar.status .zoom-controls {
1956
+ margin-inline-start: 0;
1957
+ }
1958
+ .status-message:not([data-error]) {
1959
+ position: absolute;
1960
+ width: 1px;
1961
+ height: 1px;
1962
+ margin: -1px;
1963
+ overflow: hidden;
1964
+ clip-path: inset(50%);
1965
+ white-space: nowrap;
1966
+ }
1967
+ .status-message[data-error] {
1968
+ flex: 1;
1969
+ min-width: 0;
1970
+ color: var(--_vv-danger);
1971
+ }
1972
+
1973
+ /* In-place text editing: the editor sits on the shape and replaces its drawn text. */
1974
+ .inline-text {
1975
+ position: absolute;
1976
+ z-index: 3;
1977
+ box-sizing: border-box;
1978
+ margin: 0;
1979
+ padding: 0 4px;
1980
+ overflow: hidden;
1981
+ resize: none;
1982
+ border: 0;
1983
+ border-radius: 0;
1984
+ outline: 1px dashed var(--_vv-accent);
1985
+ outline-offset: 2px;
1986
+ background: transparent;
1987
+ line-height: 1.2;
1988
+ min-height: 0;
1989
+ }
1990
+ .paper [data-editing-text] text {
1991
+ visibility: hidden;
1992
+ }
1993
+
1994
+ /* File > Info lists what the drawing shows approximately. */
1995
+ .backstage-notes-list {
1996
+ max-height: 240px;
1997
+ margin: 6px 0 0;
1998
+ padding-left: 18px;
1999
+ overflow: auto;
2000
+ color: var(--_vv-muted);
2001
+ }
2002
+ .backstage-notes-list li {
2003
+ margin: 5px 0;
2004
+ overflow-wrap: anywhere;
2005
+ }
2006
+ @media (max-width: 760px) {
2007
+ .inspector-pane {
2008
+ position: absolute;
2009
+ inset: 0 0 0 auto;
2010
+ z-index: 4;
2011
+ width: min(320px, 100%);
2012
+ box-shadow: -8px 0 24px #0002;
2013
+ }
2014
+ }
2015
+
2016
+ /* Layer Properties: Visio's table of layers with their flags. */
2017
+ .layer-table {
2018
+ width: 100%;
2019
+ border-collapse: collapse;
2020
+ font-size: 12px;
2021
+ }
2022
+ .layer-table th,
2023
+ .layer-table td {
2024
+ padding: 4px 8px;
2025
+ border: 1px solid var(--_vv-border);
2026
+ text-align: center;
2027
+ }
2028
+ .layer-table thead th {
2029
+ background: var(--_vv-secondary);
2030
+ font-weight: 600;
2031
+ }
2032
+ .layer-table tbody th {
2033
+ text-align: left;
2034
+ font-weight: 400;
2035
+ overflow-wrap: anywhere;
2036
+ }
2037
+ .layer-table .layer-page th {
2038
+ background: var(--_vv-secondary);
2039
+ color: var(--_vv-muted);
2040
+ font-size: 11px;
2041
+ }
2042
+ .layer-table input {
2043
+ width: 16px;
2044
+ height: 16px;
2045
+ min-height: 16px;
2046
+ padding: 0;
2047
+ accent-color: var(--_vv-accent);
2048
+ }
2049
+ .layer-colour {
2050
+ display: inline-block;
2051
+ width: 28px;
2052
+ height: 12px;
2053
+ border: 1px solid var(--_vv-border);
2054
+ }
2055
+ .layer-note:empty {
2056
+ display: none;
2057
+ }
2058
+ .layer-note {
2059
+ color: var(--_vv-muted);
2060
+ font-size: 12px;
2061
+ }
2062
+ `;
2063
+
2064
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/title-bar.css?raw
2065
+ var title_bar_default = "/* Visio's title bar: the shared office-ui-title-bar, with the signed-in name at its right end. */\n.title-bar {\n flex: none;\n}\n.title-bar[hidden] {\n display: none;\n}\n.title-account {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--_vv-ink);\n}\n.title-account[hidden] {\n display: none;\n}\n.title-account-avatar {\n display: inline-grid;\n place-items: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n color: #fff;\n font-size: 10px;\n font-weight: 600;\n}\n@media (max-width: 760px) {\n .title-account-name {\n display: none;\n }\n}\n";
2066
+
1991
2067
  // src/visio/styles/theme.ts
1992
2068
  var visioThemeAliases = `:host {
1993
2069
  --_vv-bg:var(--vv-background,var(--office-surface,#eceae8));
@@ -2002,6 +2078,12 @@ var visioThemeAliases = `:host {
2002
2078
  --_vv-focus:var(--vv-focus,var(--office-ring,var(--_vv-accent)));
2003
2079
  --_vv-danger:var(--vv-danger,var(--office-danger,#b42318));
2004
2080
  --_vv-shadow:var(--vv-shadow,var(--office-shadow,0 2px 8px rgb(0 0 0 / 14%)));
2081
+ --_vv-status-bg:var(--vv-status-background,var(--_vv-surface));
2082
+ --_vv-status-ink:var(--vv-status-ink,var(--_vv-ink));
2083
+ }
2084
+ :host([status-bar='colorful']) {
2085
+ --_vv-status-bg:var(--vv-status-background,var(--_vv-accent));
2086
+ --_vv-status-ink:var(--vv-status-ink,var(--_vv-accent-ink));
2005
2087
  }
2006
2088
  `;
2007
2089
  var visioThemeBridge = themeBridge(":host > *", {
@@ -2032,6 +2114,8 @@ var canvasAndRibbonStyles = [
2032
2114
  data_default,
2033
2115
  layout_default,
2034
2116
  text_features_default,
2117
+ task_pane_default,
2118
+ title_bar_default,
2035
2119
  visioThemeBridge
2036
2120
  ].join("\n");
2037
2121
 
@@ -2858,27 +2942,27 @@ function storage(doc) {
2858
2942
  }
2859
2943
  }
2860
2944
  function load(doc) {
2861
- const saved = { open: [], quick: {} };
2945
+ const saved2 = { open: [], quick: {} };
2862
2946
  try {
2863
2947
  const raw = storage(doc)?.getItem(SHAPES_STORAGE_KEY);
2864
2948
  const value = raw ? JSON.parse(raw) : {};
2865
2949
  const known = new Set(STENCILS.map((stencil) => stencil.id));
2866
2950
  if (Array.isArray(value.open))
2867
- saved.open = value.open.filter(
2951
+ saved2.open = value.open.filter(
2868
2952
  (id) => typeof id === "string" && known.has(id) && id !== BASIC_STENCIL_ID
2869
2953
  );
2870
2954
  for (const [stencil, ids] of Object.entries(value.quick ?? {}))
2871
2955
  if (known.has(stencil) && Array.isArray(ids))
2872
- saved.quick[stencil] = ids.filter(
2956
+ saved2.quick[stencil] = ids.filter(
2873
2957
  (id) => typeof id === "string" && findMaster(id)?.stencil.id === stencil
2874
2958
  );
2875
2959
  } catch {
2876
2960
  }
2877
- return saved;
2961
+ return saved2;
2878
2962
  }
2879
- function save(doc, saved) {
2963
+ function save(doc, saved2) {
2880
2964
  try {
2881
- storage(doc)?.setItem(SHAPES_STORAGE_KEY, JSON.stringify(saved));
2965
+ storage(doc)?.setItem(SHAPES_STORAGE_KEY, JSON.stringify(saved2));
2882
2966
  } catch {
2883
2967
  }
2884
2968
  }
@@ -2923,7 +3007,7 @@ function unsupportedItem(item, reason) {
2923
3007
  return item;
2924
3008
  }
2925
3009
  function buildStencilsView(doc, panel, pane) {
2926
- const saved = load(doc);
3010
+ const saved2 = load(doc);
2927
3011
  const row = (id, label) => {
2928
3012
  const button = doc.createElement("button");
2929
3013
  button.type = "button";
@@ -2934,7 +3018,7 @@ function buildStencilsView(doc, panel, pane) {
2934
3018
  };
2935
3019
  const more = row("shapes-more", "More Shapes");
2936
3020
  more.setAttribute("aria-haspopup", "menu");
2937
- more.title = "More Shapes: open a built-in stencil. Stencil files (.vssx) cannot be opened.";
3021
+ more.title = "More Shapes: open another stencil.";
2938
3022
  const quickToggle = row("shapes-quick-toggle", "Quick Shapes");
2939
3023
  quickToggle.setAttribute("aria-expanded", "false");
2940
3024
  quickToggle.setAttribute("aria-controls", "shapes-quick");
@@ -2951,8 +3035,8 @@ function buildStencilsView(doc, panel, pane) {
2951
3035
  masterMenu.dataset.shapesMenu = "master";
2952
3036
  masterMenu.setAttribute("label", "Master");
2953
3037
  let menuMaster;
2954
- const openIds = () => [BASIC_STENCIL_ID, ...saved.open];
2955
- const quickIds = (stencil) => saved.quick[stencil.id] ?? stencil.masters.slice(0, DEFAULT_QUICK).map((master) => master.id);
3038
+ const openIds = () => [BASIC_STENCIL_ID, ...saved2.open];
3039
+ const quickIds = (stencil) => saved2.quick[stencil.id] ?? stencil.masters.slice(0, DEFAULT_QUICK).map((master) => master.id);
2956
3040
  const renderQuick = () => {
2957
3041
  if (quick.hidden) return quick.replaceChildren();
2958
3042
  const masters = openIds().map((id) => STENCILS.find((stencil) => stencil.id === id)).flatMap((stencil) => quickIds(stencil).map((id) => findMaster(id).master));
@@ -3004,7 +3088,7 @@ function buildStencilsView(doc, panel, pane) {
3004
3088
  };
3005
3089
  const renderMenu = () => moreMenu.replaceChildren(
3006
3090
  ...STENCILS.filter((stencil) => stencil.id !== BASIC_STENCIL_ID).map(
3007
- (stencil) => menuItem(doc, `stencil:${stencil.id}`, stencil.name, saved.open.includes(stencil.id))
3091
+ (stencil) => menuItem(doc, `stencil:${stencil.id}`, stencil.name, saved2.open.includes(stencil.id))
3008
3092
  ),
3009
3093
  doc.createElement("office-ui-menu-separator"),
3010
3094
  unsupportedItem(
@@ -3018,8 +3102,8 @@ function buildStencilsView(doc, panel, pane) {
3018
3102
  renderQuick();
3019
3103
  };
3020
3104
  const toggleStencil = (id) => {
3021
- saved.open = saved.open.includes(id) ? saved.open.filter((candidate) => candidate !== id) : [...saved.open, id];
3022
- save(doc, saved);
3105
+ saved2.open = saved2.open.includes(id) ? saved2.open.filter((candidate) => candidate !== id) : [...saved2.open, id];
3106
+ save(doc, saved2);
3023
3107
  render();
3024
3108
  };
3025
3109
  more.addEventListener("click", () => {
@@ -3065,8 +3149,8 @@ function buildStencilsView(doc, panel, pane) {
3065
3149
  const found = menuMaster && findMaster(menuMaster);
3066
3150
  if (!found) return;
3067
3151
  const ids = quickIds(found.stencil);
3068
- saved.quick[found.stencil.id] = ids.includes(found.master.id) ? ids.filter((id) => id !== found.master.id) : [...ids, found.master.id];
3069
- save(doc, saved);
3152
+ saved2.quick[found.stencil.id] = ids.includes(found.master.id) ? ids.filter((id) => id !== found.master.id) : [...ids, found.master.id];
3153
+ save(doc, saved2);
3070
3154
  renderQuick();
3071
3155
  });
3072
3156
  render();
@@ -3093,70 +3177,48 @@ function createShapesWindow(doc) {
3093
3177
  collapse.title = "Close Shapes";
3094
3178
  collapse.textContent = "\u2039";
3095
3179
  heading.append(title2, collapse);
3096
- const views = doc.createElement("div");
3097
- views.className = "shapes-views";
3098
- views.setAttribute("role", "tablist");
3099
- views.setAttribute("aria-label", "Shapes views");
3100
- const view = (key, label, selected) => {
3101
- const tab = doc.createElement("button");
3102
- tab.type = "button";
3103
- tab.id = `shapes-${key}-tab`;
3104
- tab.dataset.shapesView = key;
3105
- tab.textContent = label;
3106
- tab.setAttribute("role", "tab");
3107
- tab.setAttribute("aria-selected", String(selected));
3108
- tab.setAttribute("aria-controls", `shapes-${key}`);
3109
- tab.tabIndex = selected ? 0 : -1;
3110
- return tab;
3111
- };
3112
- views.append(view("stencils", "Stencils", true), view("search", "Search", false));
3180
+ const box2 = doc.createElement("div");
3181
+ box2.className = "shapes-search-box";
3182
+ const field = doc.createElement("input");
3183
+ field.type = "search";
3184
+ field.className = "shapes-search-field";
3185
+ field.placeholder = "Search shapes";
3186
+ field.setAttribute("aria-label", "Search shapes");
3187
+ field.setAttribute("aria-controls", "shapes-search");
3188
+ field.autocomplete = "off";
3189
+ const glass = doc.createElement("office-ui-icon");
3190
+ glass.setAttribute("name", "search");
3191
+ glass.setAttribute("aria-hidden", "true");
3192
+ box2.append(field, glass);
3113
3193
  const stencils = doc.createElement("div");
3114
3194
  stencils.id = "shapes-stencils";
3115
- stencils.setAttribute("role", "tabpanel");
3116
- stencils.setAttribute("aria-labelledby", "shapes-stencils-tab");
3117
3195
  const menus = buildStencilsView(doc, stencils, pane);
3118
3196
  const search = doc.createElement("div");
3119
3197
  search.id = "shapes-search";
3120
3198
  search.hidden = true;
3121
- search.setAttribute("role", "tabpanel");
3122
- search.setAttribute("aria-labelledby", "shapes-search-tab");
3123
- const field = doc.createElement("input");
3124
- field.type = "search";
3125
- field.className = "shapes-search-field";
3126
- field.placeholder = "Search for shapes";
3127
- field.setAttribute("aria-label", "Search for shapes");
3128
- field.autocomplete = "off";
3199
+ search.setAttribute("role", "region");
3200
+ search.setAttribute("aria-label", "Search results");
3129
3201
  const results = masterList(
3130
3202
  doc,
3131
3203
  STENCILS.flatMap((stencil) => stencil.masters)
3132
3204
  );
3133
3205
  const empty = doc.createElement("p");
3134
3206
  empty.className = "shapes-empty";
3135
- empty.textContent = "No matching shapes in the built-in stencils.";
3207
+ empty.textContent = "No shapes match your search.";
3136
3208
  empty.hidden = true;
3137
3209
  field.addEventListener("input", () => {
3138
3210
  const query = field.value.trim().toLowerCase();
3139
3211
  let shown = 0;
3140
3212
  for (const item of results.querySelectorAll("li")) {
3141
- item.hidden = !!query && !item.dataset.name.toLowerCase().includes(query);
3213
+ item.hidden = !item.dataset.name.toLowerCase().includes(query);
3142
3214
  if (!item.hidden) shown++;
3143
3215
  }
3144
3216
  empty.hidden = shown > 0;
3217
+ search.hidden = !query;
3218
+ stencils.hidden = !!query;
3145
3219
  });
3146
- search.append(field, results, empty);
3147
- views.addEventListener("click", (event) => {
3148
- const tab = event.target.closest("[data-shapes-view]");
3149
- if (!tab) return;
3150
- for (const candidate of views.querySelectorAll("[data-shapes-view]")) {
3151
- const selected = candidate === tab;
3152
- candidate.setAttribute("aria-selected", String(selected));
3153
- candidate.tabIndex = selected ? 0 : -1;
3154
- }
3155
- stencils.hidden = tab.dataset.shapesView !== "stencils";
3156
- search.hidden = !stencils.hidden;
3157
- if (!search.hidden) field.focus();
3158
- });
3159
- pane.append(heading, views, stencils, search, ...menus);
3220
+ search.append(results, empty);
3221
+ pane.append(heading, box2, stencils, search, ...menus);
3160
3222
  return pane;
3161
3223
  }
3162
3224
  function createShapesStrip(doc) {
@@ -3519,7 +3581,7 @@ function buildHomePanel(doc, panel) {
3519
3581
  {
3520
3582
  id: "layer-properties",
3521
3583
  label: "Layer Properties...",
3522
- action: { type: "reveal", panel: "layers" }
3584
+ action: { type: "layer-properties" }
3523
3585
  },
3524
3586
  {
3525
3587
  id: "assign-layer",
@@ -4408,22 +4470,11 @@ function buildViewPanel(doc, panel) {
4408
4470
  action: { type: "pane", pane: "shapes" },
4409
4471
  checked: true
4410
4472
  },
4411
- {
4412
- id: "inspector",
4413
- label: "Inspector",
4414
- action: { type: "pane", pane: "inspector" },
4415
- checked: true
4416
- },
4417
4473
  {
4418
4474
  id: "shape-data",
4419
4475
  label: "Shape Data",
4420
- action: { type: "reveal", panel: "selection" }
4421
- },
4422
- { id: "layers-pane", label: "Layers", action: { type: "reveal", panel: "layers" } },
4423
- {
4424
- id: "notes",
4425
- label: "Compatibility Notes",
4426
- action: { type: "reveal", panel: "notes" }
4476
+ action: { type: "reveal", panel: "selection" },
4477
+ checked: false
4427
4478
  },
4428
4479
  { id: "pan-zoom", label: "Pan & Zoom", action: { type: "panZoom" }, checked: false },
4429
4480
  {
@@ -4615,31 +4666,6 @@ var RIBBON_TABS = [
4615
4666
  ["view", "View", buildViewPanel],
4616
4667
  ["help", "Help", buildHelpPanel]
4617
4668
  ];
4618
- function quickAccess(doc) {
4619
- const qat = doc.createElement("div");
4620
- qat.className = "qat";
4621
- qat.setAttribute("role", "toolbar");
4622
- qat.setAttribute("aria-label", "Quick Access Toolbar");
4623
- qat.append(
4624
- command(doc, {
4625
- id: "undo",
4626
- label: "Undo",
4627
- icon: "undo",
4628
- size: "icon",
4629
- action: { type: "history", key: "undo" },
4630
- keys: ["Control+Z", "Ctrl+Z"]
4631
- }),
4632
- command(doc, {
4633
- id: "redo",
4634
- label: "Redo",
4635
- icon: "redo",
4636
- size: "icon",
4637
- action: { type: "history", key: "redo" },
4638
- keys: ["Control+Y", "Ctrl+Y"]
4639
- })
4640
- );
4641
- return qat;
4642
- }
4643
4669
  function createRibbon(doc) {
4644
4670
  const ribbon = doc.createElement("office-ui-ribbon");
4645
4671
  ribbon.className = "toolbar";
@@ -4647,11 +4673,9 @@ function createRibbon(doc) {
4647
4673
  ribbon.setAttribute("aria-label", "Diagram controls");
4648
4674
  ribbon.setAttribute("label", "Ribbon");
4649
4675
  ribbon.setAttribute("selected", "home");
4650
- const qat = quickAccess(doc);
4651
- qat.slot = "quick-access";
4652
4676
  const tellMe = createTellMe(doc);
4653
4677
  tellMe.slot = "search";
4654
- ribbon.append(qat, tellMe);
4678
+ ribbon.append(tellMe);
4655
4679
  for (const [key, name, build] of RIBBON_TABS) {
4656
4680
  const panel = doc.createElement("div");
4657
4681
  panel.className = "ribbon-content";
@@ -4763,8 +4787,6 @@ function generated(label, used, singles) {
4763
4787
  function applyKeyTips(toolbar) {
4764
4788
  toolbar.setAttribute("file-keytip", "F");
4765
4789
  toolbar.querySelector(".tell-me").dataset.keytip = "Q";
4766
- toolbar.querySelector('.qat [command="undo"]').dataset.keytip = "1";
4767
- toolbar.querySelector('.qat [command="redo"]').dataset.keytip = "2";
4768
4790
  for (const panel of toolbar.querySelectorAll("[data-ribbon-tab]")) {
4769
4791
  const tab = panel.dataset.ribbonTab;
4770
4792
  panel.dataset.tabKeytip = TABS[tab] ?? tab.slice(0, 1).toUpperCase();
@@ -4814,23 +4836,7 @@ function createStatusBar(doc) {
4814
4836
  message.setAttribute("role", "status");
4815
4837
  const text = doc.createElement("span");
4816
4838
  text.dataset.status = "";
4817
- const diagnostics = doc.createElement("span");
4818
- diagnostics.dataset.diagnostics = "";
4819
- message.append(text, diagnostics);
4820
- const notes = doc.createElement("div");
4821
- notes.className = "notes-strip";
4822
- const notesButton = doc.createElement("button");
4823
- notesButton.type = "button";
4824
- notesButton.dataset.chrome = "notes";
4825
- notesButton.setAttribute("aria-controls", "inspector-pane");
4826
- notesButton.setAttribute("aria-expanded", "false");
4827
- const count = doc.createElement("span");
4828
- count.className = "notes-count";
4829
- count.dataset.noteCount = "";
4830
- notesButton.append("Notes", count);
4831
- const local = doc.createElement("span");
4832
- local.textContent = "Files stay in your browser";
4833
- notes.append(notesButton, local);
4839
+ message.append(text);
4834
4840
  const footer2 = doc.createElement("slot");
4835
4841
  footer2.name = "workspace-footer";
4836
4842
  const zoom = doc.createElement("office-ui-zoom-slider");
@@ -4849,15 +4855,28 @@ function createStatusBar(doc) {
4849
4855
  presentation.setAttribute("title", "Presentation Mode: open a drawing with a foreground page.");
4850
4856
  bar.append(
4851
4857
  item("data-page-status"),
4852
- item("data-shape-status"),
4858
+ item("data-width-status"),
4859
+ item("data-height-status"),
4860
+ item("data-angle-status"),
4861
+ item("data-language-status"),
4853
4862
  message,
4854
- notes,
4855
4863
  footer2,
4856
4864
  presentation,
4857
4865
  zoom
4858
4866
  );
4859
4867
  return bar;
4860
4868
  }
4869
+ function statusLanguage(tag) {
4870
+ try {
4871
+ const locale = new Intl.Locale(tag || "en-US").maximize();
4872
+ const names = new Intl.DisplayNames(["en"], { type: "language" });
4873
+ const language = names.of(locale.language) ?? "";
4874
+ const region = locale.region ? new Intl.DisplayNames(["en"], { type: "region" }).of(locale.region) : "";
4875
+ return region ? `${language} (${region})` : language;
4876
+ } catch {
4877
+ return "";
4878
+ }
4879
+ }
4861
4880
  function createPageTabs(doc) {
4862
4881
  const bar = doc.createElement("div");
4863
4882
  bar.className = "page-bar";
@@ -4874,9 +4893,82 @@ function createPageTabs(doc) {
4874
4893
  strip.setAttribute("add-label", "Insert Page");
4875
4894
  strip.setAttribute("add-disabled", "");
4876
4895
  strip.setAttribute("add-title", "Insert Page: open a .vsdx file to edit pages.");
4877
- bar.append(all, strip);
4896
+ strip.toggleAttribute("plain", true);
4897
+ all.slot = "after-tabs";
4898
+ strip.append(all);
4899
+ bar.append(strip);
4900
+ return bar;
4901
+ }
4902
+
4903
+ // src/visio/title-bar.ts
4904
+ function createTitleBar(doc) {
4905
+ const bar = doc.createElement("office-ui-title-bar");
4906
+ bar.className = "title-bar";
4907
+ const account2 = doc.createElement("span");
4908
+ account2.slot = "account";
4909
+ account2.className = "title-account";
4910
+ const name = doc.createElement("span");
4911
+ name.className = "title-account-name";
4912
+ const avatar = doc.createElement("span");
4913
+ avatar.className = "title-account-avatar";
4914
+ avatar.setAttribute("aria-hidden", "true");
4915
+ account2.append(name, avatar);
4916
+ bar.append(account2);
4917
+ bar.state = { appMark: "V", fileName: "Visio" };
4878
4918
  return bar;
4879
4919
  }
4920
+ function initials(profile) {
4921
+ if (profile.initial) return profile.initial.slice(0, 2).toUpperCase();
4922
+ const words = profile.displayName.trim().split(/\s+/).filter(Boolean);
4923
+ return ((words[0]?.[0] ?? "") + (words.length > 1 ? words.at(-1)?.[0] ?? "" : "")).toUpperCase();
4924
+ }
4925
+ function status(state) {
4926
+ if (!state.document) return "";
4927
+ if (state.loading) return "Opening...";
4928
+ if (state.document.format === "vsd" || !state.edit.sourceAvailable) return "Read-only";
4929
+ return state.edit.dirty ? "Unsaved changes" : "";
4930
+ }
4931
+ function renderTitleBar(bar, state, fileName, profile) {
4932
+ const busy = state.loading || state.edit.busy;
4933
+ bar.state = {
4934
+ appMark: "V",
4935
+ fileName: fileName || (state.document ? "Drawing1" : "Visio"),
4936
+ status: status(state),
4937
+ tone: state.loading ? "saving" : "idle",
4938
+ quickAccess: {
4939
+ label: "Quick Access Toolbar",
4940
+ items: [
4941
+ {
4942
+ id: "save",
4943
+ icon: "save",
4944
+ label: "Save",
4945
+ title: state.edit.sourceAvailable ? "Save (Ctrl+S): download a copy" : "Save: open a .vsdx file to save a copy",
4946
+ disabled: !state.edit.sourceAvailable || busy
4947
+ },
4948
+ {
4949
+ id: "undo",
4950
+ icon: "undo",
4951
+ label: "Undo",
4952
+ title: "Undo (Ctrl+Z)",
4953
+ disabled: !state.edit.canUndo || busy
4954
+ },
4955
+ {
4956
+ id: "redo",
4957
+ icon: "redo",
4958
+ label: "Redo",
4959
+ title: "Redo (Ctrl+Y)",
4960
+ disabled: !state.edit.canRedo || busy
4961
+ }
4962
+ ]
4963
+ }
4964
+ };
4965
+ const account2 = bar.querySelector(".title-account");
4966
+ account2.hidden = !profile.displayName;
4967
+ account2.querySelector(".title-account-name").textContent = profile.displayName;
4968
+ const avatar = account2.querySelector(".title-account-avatar");
4969
+ avatar.textContent = initials(profile);
4970
+ avatar.style.background = profile.avatarColor;
4971
+ }
4880
4972
  function createFindBar(doc) {
4881
4973
  const bar = doc.createElement("office-ui-find-bar");
4882
4974
  bar.className = "find-bar";
@@ -5267,8 +5359,8 @@ function share(doc) {
5267
5359
  stop.dataset.share = "stop";
5268
5360
  stop.hidden = true;
5269
5361
  buttons.append(start, stop);
5270
- const status = el(doc, "p", "share-status");
5271
- status.setAttribute("role", "status");
5362
+ const status2 = el(doc, "p", "share-status");
5363
+ status2.setAttribute("role", "status");
5272
5364
  const people = doc.createElement("office-ui-presence");
5273
5365
  people.className = "share-people";
5274
5366
  people.setAttribute("label", "People in this session");
@@ -5282,7 +5374,7 @@ function share(doc) {
5282
5374
  ),
5283
5375
  field,
5284
5376
  buttons,
5285
- status,
5377
+ status2,
5286
5378
  people,
5287
5379
  el(
5288
5380
  doc,
@@ -5322,10 +5414,11 @@ function createBackstage(doc) {
5322
5414
  facts.append(el(doc, "dt", "", label), value);
5323
5415
  }
5324
5416
  const notes = el(doc, "div", "backstage-notes");
5417
+ notes.hidden = true;
5325
5418
  notes.append(
5326
5419
  el(doc, "h2", "", "Compatibility"),
5327
5420
  el(doc, "p", "backstage-note-count"),
5328
- action(doc, "notes", "View compatibility notes", "What this viewer approximates or omits.")
5421
+ el(doc, "ul", "backstage-notes-list")
5329
5422
  );
5330
5423
  const file = el(doc, "h2", "backstage-file");
5331
5424
  file.dataset.info = "name";
@@ -5584,9 +5677,6 @@ var ViewerBackstage = class {
5584
5677
  else if (action2 === "page-setup") {
5585
5678
  this.hide();
5586
5679
  this.host.pageSetup?.();
5587
- } else if (action2 === "notes") {
5588
- this.hide();
5589
- this.host.revealNotes();
5590
5680
  }
5591
5681
  }
5592
5682
  #save(blob2, name) {
@@ -5607,6 +5697,10 @@ var ViewerBackstage = class {
5607
5697
  #base() {
5608
5698
  return this.host.fileName().replace(/\.vsdx?$/i, "") || "Drawing";
5609
5699
  }
5700
+ /** Save: download the edited drawing as a copy (File > Save and the Quick Access Toolbar). */
5701
+ download() {
5702
+ this.#download();
5703
+ }
5610
5704
  #download() {
5611
5705
  if (!this.#state?.edit.sourceAvailable) return;
5612
5706
  try {
@@ -5616,9 +5710,7 @@ var ViewerBackstage = class {
5616
5710
  `${this.#base()}-${result.dirty ? "edited" : "original"}-copy.vsdx`
5617
5711
  );
5618
5712
  this.hide();
5619
- this.host.announce(
5620
- "VSDX copy download requested. Native Visio compatibility is not verified."
5621
- );
5713
+ this.host.announce("Downloaded a copy of the drawing.");
5622
5714
  } catch (error) {
5623
5715
  this.host.announce(error instanceof Error ? error.message : String(error));
5624
5716
  }
@@ -5697,10 +5789,10 @@ var ViewerBackstage = class {
5697
5789
  set("shapes", page3 ? `${page3.shapes.length} top-level` : "None");
5698
5790
  set(
5699
5791
  "state",
5700
- state.document?.format === "vsd" ? "Legacy VSD preview (read only)" : !state.edit.sourceAvailable ? state.document ? "Model-only preview (read only)" : "None" : state.edit.dirty ? "Edited copy (not saved)" : "Original"
5792
+ state.document?.format === "vsd" ? "Legacy VSD preview (read only)" : !state.edit.sourceAvailable ? state.document ? "Preview (read-only)" : "None" : state.edit.dirty ? "Edited copy (not saved)" : "Original"
5701
5793
  );
5702
5794
  const notes = this.host.noteCount();
5703
- this.#root.querySelector(".backstage-note-count").textContent = notes ? `${notes} compatibility notes describe what this viewer approximates or omits.` : "No compatibility notes.";
5795
+ this.#root.querySelector(".backstage-note-count").textContent = notes ? `${notes === 1 ? "One part" : `${notes} parts`} of this drawing ${notes === 1 ? "is" : "are"} shown approximately or not at all:` : "No compatibility notes.";
5704
5796
  const busy = state.loading || state.edit.busy;
5705
5797
  const blank = this.#root.querySelector(
5706
5798
  '[data-backstage-action="new-blank"]'
@@ -5735,7 +5827,6 @@ var ViewerBackstage = class {
5735
5827
  '[data-backstage-action^="export-"], [data-backstage-action="print"]'
5736
5828
  ))
5737
5829
  node.disabled = !page3 || busy;
5738
- this.#root.querySelector('[data-backstage-action="notes"]').disabled = notes === 0;
5739
5830
  }
5740
5831
  };
5741
5832
  function pagePoint(svg2, page3, event, options2 = {}) {
@@ -7564,45 +7655,53 @@ var labels = {
7564
7655
  height: "Height (in)",
7565
7656
  angle: "Angle (\xB0)"
7566
7657
  };
7658
+ var captions = { x: "X", y: "Y", width: "Width", height: "Height", angle: "Angle" };
7567
7659
  function createSizePosition(doc) {
7568
7660
  const pane = doc.createElement("section");
7569
7661
  pane.className = "size-position";
7570
7662
  pane.hidden = true;
7571
7663
  pane.setAttribute("aria-label", "Size & Position");
7572
7664
  const heading = doc.createElement("div");
7573
- heading.className = "pane-heading";
7665
+ heading.className = "size-caption";
7574
7666
  const title2 = doc.createElement("strong");
7575
7667
  title2.textContent = "Size & Position";
7576
7668
  const close = doc.createElement("button");
7577
7669
  close.type = "button";
7578
7670
  close.setAttribute("aria-label", "Close Size & Position");
7671
+ close.title = "Close";
7579
7672
  close.textContent = "\xD7";
7580
7673
  heading.append(title2, close);
7674
+ const body = doc.createElement("div");
7675
+ body.className = "size-body";
7581
7676
  const target = doc.createElement("p");
7582
7677
  target.dataset.sizeTarget = "";
7583
- const hint = doc.createElement("p");
7584
- hint.textContent = "Drawing inches. X/Y locate the rotation pin; Y increases upward.";
7585
7678
  const controls = doc.createElement("div");
7586
7679
  controls.dataset.sizeFields = "";
7587
- const status = doc.createElement("p");
7588
- status.dataset.sizeStatus = "";
7589
- status.setAttribute("role", "status");
7590
- status.setAttribute("aria-live", "polite");
7680
+ const status2 = doc.createElement("p");
7681
+ status2.dataset.sizeStatus = "";
7682
+ status2.setAttribute("role", "status");
7683
+ status2.setAttribute("aria-live", "polite");
7591
7684
  const error = doc.createElement("p");
7592
7685
  error.dataset.sizeError = "";
7593
7686
  error.setAttribute("role", "alert");
7594
7687
  error.hidden = true;
7595
- pane.append(heading, target, hint, controls, status, error);
7688
+ body.append(controls, target, status2, error);
7689
+ pane.append(heading, body);
7596
7690
  for (const field of fields) {
7597
7691
  const label = doc.createElement("label");
7598
- label.textContent = labels[field];
7692
+ const caption = doc.createElement("span");
7693
+ caption.textContent = captions[field];
7694
+ const unit2 = doc.createElement("span");
7695
+ unit2.className = "size-unit";
7696
+ unit2.setAttribute("aria-hidden", "true");
7697
+ unit2.textContent = field === "angle" ? "deg." : "in.";
7599
7698
  const input = doc.createElement("input");
7600
7699
  input.type = "number";
7601
7700
  input.step = "any";
7602
7701
  input.dataset.sizeField = field;
7603
7702
  input.setAttribute("aria-label", labels[field]);
7604
7703
  if (field === "width" || field === "height") input.min = "0";
7605
- label.append(input);
7704
+ label.append(caption, input, unit2);
7606
7705
  controls.append(label);
7607
7706
  }
7608
7707
  return pane;
@@ -7757,7 +7856,7 @@ var ViewerSizePosition = class {
7757
7856
  input.disabled = !values || !state.edit.sourceAvailable || state.loading || state.edit.busy || this.#pending;
7758
7857
  }
7759
7858
  this.#pane.setAttribute("aria-busy", String(state.loading || state.edit.busy || this.#pending));
7760
- this.#pane.querySelector("[data-size-target]").textContent = values ? `Shape ${values.shapeId}` : "Select one local, unconnected 2D shape.";
7859
+ this.#pane.querySelector("[data-size-target]").textContent = values ? "" : "Select a shape to see its size and position.";
7761
7860
  }
7762
7861
  };
7763
7862
  function createContextMenus(doc) {
@@ -8179,7 +8278,18 @@ var VISIO_OPTION_CATEGORIES = [
8179
8278
  description: "Stored on this device only. Shared sessions show this name to others.",
8180
8279
  controls: [
8181
8280
  { kind: "text", key: "userName", label: "User name", maxLength: 64 },
8182
- { kind: "text", key: "userInitials", label: "Initials", maxLength: 2 }
8281
+ { kind: "text", key: "userInitials", label: "Initials", maxLength: 2 },
8282
+ {
8283
+ kind: "select",
8284
+ key: "officeTheme",
8285
+ label: "Office Theme",
8286
+ info: "Colorful gives the status bar the Visio colour, White keeps it neutral and Black is the dark theme.",
8287
+ choices: [
8288
+ { value: "colorful", label: "Colorful" },
8289
+ { value: "neutral", label: "White" },
8290
+ { value: "dark", label: "Black" }
8291
+ ]
8292
+ }
8183
8293
  ]
8184
8294
  }
8185
8295
  ]
@@ -8261,8 +8371,26 @@ var VISIO_OPTION_CATEGORIES = [
8261
8371
  sections: []
8262
8372
  }
8263
8373
  ];
8264
- function profileValues(profile) {
8374
+ var THEME_KEY = "ooxml-visio-office-theme";
8375
+ var isStatusBarTheme = (value) => value === "colorful" || value === "neutral";
8376
+ function readStatusBarTheme() {
8377
+ try {
8378
+ const stored = globalThis.localStorage?.getItem(THEME_KEY);
8379
+ return isStatusBarTheme(stored) ? stored : void 0;
8380
+ } catch {
8381
+ return void 0;
8382
+ }
8383
+ }
8384
+ function writeStatusBarTheme(theme) {
8385
+ try {
8386
+ globalThis.localStorage?.setItem(THEME_KEY, theme);
8387
+ } catch {
8388
+ }
8389
+ }
8390
+ var isOfficeThemeChoice = (value) => value === "dark" || isStatusBarTheme(value);
8391
+ function profileValues(profile, officeTheme) {
8265
8392
  return {
8393
+ officeTheme,
8266
8394
  miniToolbar: false,
8267
8395
  livePreview: false,
8268
8396
  screenTips: "descriptions",
@@ -8280,13 +8408,19 @@ function createOptionsDialog(doc) {
8280
8408
  }
8281
8409
  var ViewerProfile = class {
8282
8410
  constructor(root, onProfile = () => {
8411
+ }, theme = {
8412
+ get: () => "neutral",
8413
+ set: () => {
8414
+ }
8283
8415
  }) {
8284
8416
  this.onProfile = onProfile;
8417
+ this.theme = theme;
8285
8418
  this.#dialog = root.querySelector("office-ui-options-dialog");
8286
8419
  this.#account = root.querySelector("office-ui-account");
8287
8420
  this.#sync();
8288
8421
  }
8289
8422
  onProfile;
8423
+ theme;
8290
8424
  #profile = readOfficeProfile();
8291
8425
  #dialog;
8292
8426
  #account;
@@ -8295,7 +8429,7 @@ var ViewerProfile = class {
8295
8429
  }
8296
8430
  /** Open Visio Options, on General as Visio does. */
8297
8431
  showOptions(category = "general") {
8298
- this.#dialog.values = profileValues(this.#profile);
8432
+ this.#dialog.values = profileValues(this.#profile, this.theme.get());
8299
8433
  this.#dialog.category = category;
8300
8434
  this.#dialog.show();
8301
8435
  }
@@ -8307,6 +8441,10 @@ var ViewerProfile = class {
8307
8441
  "office-options-change",
8308
8442
  (event) => {
8309
8443
  const { values } = event.detail;
8444
+ if (isOfficeThemeChoice(values.officeTheme) && values.officeTheme !== this.theme.get()) {
8445
+ this.theme.set(values.officeTheme);
8446
+ if (values.officeTheme !== "dark") writeStatusBarTheme(values.officeTheme);
8447
+ }
8310
8448
  const initial = String(values.userInitials ?? "").trim();
8311
8449
  this.#update({
8312
8450
  displayName: String(values.userName ?? "").trim(),
@@ -8331,7 +8469,7 @@ var ViewerProfile = class {
8331
8469
  }
8332
8470
  #sync() {
8333
8471
  if (this.#account) this.#account.profile = this.#profile;
8334
- this.#dialog.values = profileValues(this.#profile);
8472
+ this.#dialog.values = profileValues(this.#profile, this.theme.get());
8335
8473
  }
8336
8474
  };
8337
8475
 
@@ -8566,7 +8704,7 @@ function wireStencil(pane, viewport, controller, announce) {
8566
8704
  const page3 = state.document?.pages[state.pageIndex];
8567
8705
  if (!master || !page3) return queue;
8568
8706
  if (!editable3()) {
8569
- announce("Open a .vsdx file to add shapes. Model-only documents are read only.");
8707
+ announce("Open a .vsdx file to add shapes.");
8570
8708
  return queue;
8571
8709
  }
8572
8710
  const { size } = master;
@@ -8697,336 +8835,215 @@ function createRulers(viewport) {
8697
8835
  }
8698
8836
  };
8699
8837
  }
8700
- var geometryControlsTemplate = `<fieldset data-geometry><legend>Geometry (experimental)</legend><p>Pin X/Y use drawing inches, bottom-left origin, Y up. Resize holds the rotation pin fixed. Core rejects unsupported formulas, protected cells, unsafe group/master dependencies and referenced deletion. Native Visio reopen fidelity is unverified.</p><label>New rectangle ID <input data-geometry-field="id" type="text" maxlength="256"></label><label>Rectangle text (optional) <input data-geometry-field="text" type="text" maxlength="1000000"></label><label>Pin X (inches) <input data-geometry-field="x" type="number" step="any"></label><label>Pin Y (inches) <input data-geometry-field="y" type="number" step="any"></label><label>Width (inches) <input data-geometry-field="width" type="number" min="0" step="any"></label><label>Height (inches) <input data-geometry-field="height" type="number" min="0" step="any"></label><div class="edit-actions"><button type="button" data-geometry-action="create-rectangle">Create rectangle</button><button type="button" data-geometry-action="move-shape">Move selected</button><button type="button" data-geometry-action="resize-shape">Resize selected</button><button type="button" data-geometry-action="delete-shape">Delete selected</button></div><p data-geometry-error role="alert" hidden></p></fieldset>`;
8701
- var ViewerGeometryControls = class {
8702
- constructor(panel, controller, draftChanged = () => {
8703
- }) {
8704
- this.panel = panel;
8838
+ var ViewerInlineText = class {
8839
+ constructor(viewport, controller, announce) {
8840
+ this.viewport = viewport;
8705
8841
  this.controller = controller;
8706
- this.draftChanged = draftChanged;
8707
- const field = (name) => panel.querySelector(`[data-geometry-field="${name}"]`);
8708
- const doc = panel.ownerDocument;
8709
- const label = doc.createElement("label");
8710
- label.textContent = "Angle (degrees) ";
8711
- const angle = doc.createElement("input");
8712
- angle.type = "number";
8713
- angle.step = "any";
8714
- angle.dataset.geometryField = "angle";
8715
- label.append(angle);
8716
- const actions = panel.querySelector("[data-geometry] .edit-actions");
8717
- actions.before(label);
8718
- const rotate = doc.createElement("button");
8719
- rotate.type = "button";
8720
- rotate.dataset.geometryAction = "rotate-shape";
8721
- rotate.textContent = "Rotate selected";
8722
- actions.append(rotate);
8723
- this.#fields = {
8724
- id: field("id"),
8725
- text: field("text"),
8726
- x: field("x"),
8727
- y: field("y"),
8728
- width: field("width"),
8729
- height: field("height"),
8730
- angle
8731
- };
8732
- this.#buttons = [...panel.querySelectorAll("[data-geometry-action]")];
8733
- this.#error = panel.querySelector("[data-geometry-error]");
8842
+ this.announce = announce;
8843
+ const doc = viewport.ownerDocument;
8844
+ this.input = doc.createElement("textarea");
8845
+ this.input.id = "edit-text";
8846
+ this.input.className = "inline-text";
8847
+ this.input.spellcheck = true;
8848
+ this.input.hidden = true;
8849
+ this.input.setAttribute("aria-label", "Shape text");
8734
8850
  }
8735
- panel;
8851
+ viewport;
8736
8852
  controller;
8737
- draftChanged;
8738
- #fields;
8739
- #buttons;
8740
- #error;
8741
- #identity = "";
8742
- #request = 0;
8743
- wire() {
8744
- const Abort = this.panel.ownerDocument.defaultView?.AbortController ?? AbortController;
8745
- const events = new Abort();
8746
- for (const field of Object.values(this.#fields))
8747
- field.addEventListener(
8748
- "input",
8749
- () => {
8750
- this.render(this.controller.state);
8751
- this.draftChanged();
8752
- },
8753
- {
8754
- signal: events.signal
8755
- }
8756
- );
8757
- for (const button of this.#buttons)
8758
- button.addEventListener(
8759
- "click",
8760
- () => {
8761
- void this.#run(button);
8762
- },
8763
- { signal: events.signal }
8764
- );
8765
- return () => {
8766
- ++this.#request;
8767
- events.abort();
8768
- };
8769
- }
8770
- async #run(button) {
8771
- if (button.disabled) return;
8772
- const state = this.controller.state;
8773
- const pageId = state.selectedShape?.pageId ?? state.document?.pages[state.pageIndex]?.id;
8774
- if (pageId === void 0) return;
8775
- const type = button.dataset.geometryAction;
8776
- if (type !== "create-rectangle" && state.selectedShapes.length !== 1) return;
8777
- const shapeId = type === "create-rectangle" ? this.#fields.id.value : state.selectedShape?.id;
8778
- if (!shapeId) return;
8779
- const x = this.#fields.x.valueAsNumber, y = this.#fields.y.valueAsNumber;
8780
- const width = this.#fields.width.valueAsNumber, height = this.#fields.height.valueAsNumber;
8781
- const target = { pageId, shapeId };
8782
- const command2 = type === "create-rectangle" ? {
8783
- type,
8784
- ...target,
8785
- x,
8786
- y,
8787
- width,
8788
- height,
8789
- ...this.#fields.text.value ? { text: this.#fields.text.value } : {}
8790
- } : type === "move-shape" ? { type, ...target, x, y } : type === "resize-shape" ? { type, ...target, width, height } : type === "rotate-shape" ? { type, ...target, angle: this.#fields.angle.valueAsNumber * Math.PI / 180 } : { type: "delete-shape", ...target };
8791
- const request = ++this.#request;
8792
- this.#error.hidden = true;
8793
- try {
8794
- await this.controller.applyEdits([command2]);
8795
- if (request === this.#request) {
8796
- this.reset();
8797
- this.render(this.controller.state);
8798
- this.draftChanged();
8799
- }
8800
- } catch (error) {
8801
- if (request === this.#request && !isEditCancellation(error)) {
8802
- this.#error.textContent = editErrorMessage(error);
8803
- this.#error.hidden = false;
8804
- }
8805
- }
8806
- }
8807
- get hasDraft() {
8808
- return Object.values(this.#fields).some((field) => field.value !== "");
8809
- }
8810
- reset() {
8811
- ++this.#request;
8812
- this.#error.hidden = true;
8813
- for (const field of Object.values(this.#fields)) field.value = "";
8814
- }
8815
- render(state) {
8816
- const pageId = state.selectedShape?.pageId ?? state.document?.pages[state.pageIndex]?.id;
8817
- const identity = JSON.stringify([
8818
- this.controller.sourceGeneration,
8819
- state.pageIndex,
8820
- pageId,
8821
- state.selectedShape?.id,
8822
- state.selectedShapes.length
8823
- ]);
8824
- if (identity !== this.#identity) {
8825
- this.#identity = identity;
8826
- ++this.#request;
8827
- this.#error.hidden = true;
8828
- for (const field of Object.values(this.#fields)) field.value = "";
8829
- }
8830
- const available = state.edit.sourceAvailable && pageId !== void 0 && !state.loading && !state.edit.busy;
8831
- for (const field of Object.values(this.#fields)) field.disabled = !available;
8832
- const position = [this.#fields.x, this.#fields.y].every(
8833
- (field) => Number.isFinite(field.valueAsNumber)
8834
- );
8835
- const size = [this.#fields.width, this.#fields.height].every(
8836
- (field) => Number.isFinite(field.valueAsNumber) && field.valueAsNumber > 0
8837
- );
8838
- const selected = selectedShape(state.document, state.selectedShape, state.pageIndex);
8839
- const lineSize = selected?.kind === "connector" && selected.height === 0 && Number.isFinite(this.#fields.width.valueAsNumber) && this.#fields.width.valueAsNumber > 0 && this.#fields.height.valueAsNumber === 0;
8840
- for (const button of this.#buttons) {
8841
- const action2 = button.dataset.geometryAction;
8842
- button.disabled = !available || (action2 === "create-rectangle" ? !this.#fields.id.value || !position || !size : !state.selectedShape || state.selectedShapes.length !== 1 || action2 === "move-shape" && !position || action2 === "resize-shape" && !size && !lineSize || action2 === "rotate-shape" && (!Number.isFinite(this.#fields.angle.valueAsNumber) || selected?.kind === "connector"));
8843
- }
8844
- }
8845
- };
8846
-
8847
- // src/visio/viewer-edit-controls.ts
8848
- var editControlsTemplate = `<details class="edit-controls"><summary>Edit diagram (experimental)</summary><p id="edit-warning">Experimental source-backed editing. Unsupported targets are rejected. Native Visio compatibility is not verified. Keep your original file.</p><p data-edit-target></p><label for="edit-text">Selected shape text</label><textarea id="edit-text" rows="3" spellcheck="true" aria-describedby="edit-warning edit-target edit-status"></textarea><div class="edit-actions"><button type="button" data-edit="apply">Apply text</button><button type="button" data-edit="cancel">Cancel</button><button type="button" data-edit="undo">Undo</button><button type="button" data-edit="redo">Redo</button></div><p id="edit-status" role="status" aria-live="polite"></p><p data-edit-error role="alert" hidden></p><ul data-edit-diagnostics></ul>${geometryControlsTemplate}</details>`;
8849
- var ViewerEditControls = class {
8850
- panel;
8853
+ announce;
8851
8854
  input;
8852
- #target;
8853
- #status;
8854
- #error;
8855
- #diagnostics;
8856
- #buttons;
8857
- #identity = "";
8858
- #initial = "";
8859
- #pageId;
8860
- #shapeId;
8855
+ #session;
8861
8856
  #request = 0;
8862
- #pending = false;
8863
- #localError;
8864
- #controller;
8865
- #geometry;
8866
- constructor(root, controller) {
8867
- this.#controller = controller;
8868
- this.panel = root.querySelector(".edit-controls");
8869
- this.#geometry = new ViewerGeometryControls(
8870
- this.panel,
8871
- controller,
8872
- () => this.render(controller.state)
8873
- );
8874
- this.input = this.panel.querySelector("textarea");
8875
- this.#target = this.panel.querySelector("[data-edit-target]");
8876
- this.#target.id = "edit-target";
8877
- this.#status = this.panel.querySelector("#edit-status");
8878
- this.#error = this.panel.querySelector("[data-edit-error]");
8879
- this.#diagnostics = this.panel.querySelector("[data-edit-diagnostics]");
8880
- const button = (name) => this.panel.querySelector(`[data-edit="${name}"]`);
8881
- this.#buttons = {
8882
- apply: button("apply"),
8883
- cancel: button("cancel"),
8884
- undo: button("undo"),
8885
- redo: button("redo")
8886
- };
8857
+ get editing() {
8858
+ return !!this.#session;
8887
8859
  }
8888
8860
  wire() {
8889
- const disposeGeometry = this.#geometry.wire();
8890
- const Abort = this.panel.ownerDocument.defaultView?.AbortController ?? AbortController;
8861
+ const Abort = this.viewport.ownerDocument.defaultView?.AbortController ?? AbortController;
8891
8862
  const events = new Abort();
8892
8863
  const options2 = { signal: events.signal };
8893
- this.input.addEventListener("input", () => this.render(this.#controller.state), options2);
8894
- this.input.addEventListener(
8864
+ const input = this.input;
8865
+ input.addEventListener("input", () => this.#layout(), options2);
8866
+ input.addEventListener(
8895
8867
  "keydown",
8896
8868
  (event) => {
8869
+ event.stopPropagation();
8897
8870
  if (event.isComposing) return;
8898
- if (event.key === "Escape") {
8871
+ if (event.key === "Escape" || event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
8899
8872
  event.preventDefault();
8900
- this.reset();
8901
- }
8902
- if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
8903
- event.preventDefault();
8904
- this.#buttons.apply.click();
8873
+ void this.commit(true);
8905
8874
  }
8906
8875
  },
8907
8876
  options2
8908
8877
  );
8909
- this.#buttons.cancel.addEventListener("click", () => this.reset(), options2);
8910
- for (const action2 of ["apply", "undo", "redo"])
8911
- this.#buttons[action2].addEventListener(
8912
- "click",
8913
- () => {
8914
- void this.#run(action2);
8915
- },
8916
- options2
8917
- );
8878
+ for (const name of ["pointerdown", "click", "dblclick", "contextmenu"])
8879
+ input.addEventListener(name, (event) => event.stopPropagation(), options2);
8880
+ this.viewport.addEventListener(
8881
+ "pointerdown",
8882
+ (event) => {
8883
+ if (this.#session && event.target !== input) void this.commit(false);
8884
+ },
8885
+ { ...options2, capture: true }
8886
+ );
8918
8887
  return () => {
8919
- disposeGeometry();
8920
8888
  events.abort();
8921
8889
  ++this.#request;
8922
- this.#pending = false;
8923
- this.#localError = void 0;
8924
- this.#identity = "";
8925
- this.input.value = this.#initial;
8890
+ this.#close();
8891
+ input.remove();
8926
8892
  };
8927
8893
  }
8928
- reset(focus = true) {
8929
- this.#geometry.reset();
8930
- ++this.#request;
8931
- this.#pending = false;
8932
- this.#localError = void 0;
8933
- this.#controller.cancelEdit();
8934
- this.input.value = this.#initial;
8935
- this.render(this.#controller.state);
8936
- if (focus && !this.input.disabled) this.input.focus();
8937
- }
8938
- async #run(action2) {
8939
- if (this.#buttons[action2].disabled) return;
8940
- if (action2 === "apply" && this.#controller.state.selectedShapes.length !== 1) return;
8894
+ /**
8895
+ * Start editing the one selected shape's text. `typed` replaces the text with a typed character,
8896
+ * as typing on a selected shape does in Visio. False when the selection has no editable text.
8897
+ */
8898
+ start(typed) {
8899
+ const state = this.controller.state;
8900
+ const page3 = state.document?.pages[state.pageIndex];
8901
+ const selected = state.selectedShape;
8902
+ if (this.#session) {
8903
+ this.input.focus();
8904
+ return true;
8905
+ }
8906
+ if (!page3 || !selected || state.selectedShapes.length !== 1) {
8907
+ this.announce("Select one shape to edit its text.");
8908
+ return false;
8909
+ }
8910
+ if (!state.edit.sourceAvailable || state.edit.busy || state.loading) {
8911
+ this.announce("This drawing cannot be edited.");
8912
+ return false;
8913
+ }
8914
+ const shape = visioTextShape(page3, selected.id);
8915
+ if (!shape) return false;
8916
+ const group2 = this.viewport.querySelector(
8917
+ `svg.paper [data-shape-id="${CSS.escape(shape.id)}"]`
8918
+ );
8919
+ const initial = shape.text?.plainText ?? "";
8920
+ this.#session = {
8921
+ pageId: page3.id,
8922
+ shapeId: shape.id,
8923
+ initial,
8924
+ generation: this.controller.sourceGeneration,
8925
+ document: state.document,
8926
+ group: group2
8927
+ };
8928
+ group2?.setAttribute("data-editing-text", "");
8929
+ this.viewport.dataset.textEditing = "";
8930
+ this.#style(shape, state.zoom || 1);
8931
+ this.input.value = typed ?? initial;
8932
+ this.input.hidden = false;
8933
+ this.viewport.append(this.input);
8934
+ this.#layout();
8935
+ this.input.focus({ preventScroll: true });
8936
+ const end = this.input.value.length;
8937
+ this.input.setSelectionRange(end, end);
8938
+ return true;
8939
+ }
8940
+ /** Save the draft as one edit; `focusCanvas` returns keyboard focus to the drawing. */
8941
+ async commit(focusCanvas) {
8942
+ const session = this.#session;
8943
+ if (!session) return;
8944
+ const draft = this.input.value;
8945
+ this.#close();
8946
+ if (focusCanvas) this.viewport.focus({ preventScroll: true });
8947
+ const state = this.controller.state;
8948
+ const page3 = state.document?.pages.find((item) => item.id === session.pageId);
8949
+ if (!page3 || this.controller.sourceGeneration !== session.generation || draft === session.initial)
8950
+ return;
8941
8951
  const request = ++this.#request;
8942
- this.#pending = true;
8943
- this.#localError = void 0;
8944
- this.render(this.#controller.state);
8945
8952
  try {
8946
- if (action2 === "apply") {
8947
- if (this.#pageId === void 0 || this.#shapeId === void 0) return;
8948
- const page3 = this.#controller.state.document?.pages.find(
8949
- (item) => item.id === this.#pageId
8950
- );
8951
- if (page3 && visioTextShape(page3, this.#shapeId)?.text.fields?.length) {
8952
- const edit = visioTextDraftEdit(page3, this.#shapeId, this.input.value);
8953
- if (edit) await this.#controller.applyEdits([edit]);
8954
- } else
8955
- await this.#controller.replacePlainText(this.#pageId, this.#shapeId, this.input.value);
8956
- } else await this.#controller[action2]();
8953
+ const shape = visioTextShape(page3, session.shapeId);
8954
+ const rich = !!shape && shape.masterId === void 0 && shape.text.runs.length > 1;
8955
+ const edit = visioTextDraftEdit(page3, session.shapeId, draft, rich ? { ranges: true } : {});
8956
+ if (edit) await this.controller.applyEdits([edit]);
8957
8957
  } catch (error) {
8958
8958
  if (request === this.#request && !isEditCancellation(error))
8959
- this.#localError = editErrorMessage(error);
8960
- } finally {
8961
- if (request === this.#request) {
8962
- this.#pending = false;
8963
- this.render(this.#controller.state);
8964
- }
8959
+ this.announce(editErrorMessage(error));
8965
8960
  }
8966
8961
  }
8962
+ /**
8963
+ * Selecting something else or changing page saves; another edit to the same drawing (formatting,
8964
+ * a move, undo) keeps the draft on the redrawn shape; a different file discards it.
8965
+ */
8967
8966
  render(state) {
8968
- this.#geometry.render(state);
8969
- const selection2 = state.selectedShape;
8970
- const pageId = selection2?.pageId ?? state.document?.pages[state.pageIndex]?.id;
8971
- const shape = selectedShape(state.document, selection2, state.pageIndex);
8972
- const identity = JSON.stringify([
8973
- this.#controller.sourceGeneration,
8974
- state.pageIndex,
8975
- pageId,
8976
- selection2?.id,
8977
- state.selectedShapes.length
8978
- ]);
8979
- if (identity !== this.#identity) {
8980
- this.#identity = identity;
8981
- ++this.#request;
8982
- this.#pending = false;
8983
- this.#localError = void 0;
8984
- this.#pageId = shape ? pageId : void 0;
8985
- this.#shapeId = shape?.id;
8986
- this.#initial = shape?.text?.plainText ?? "";
8987
- this.input.value = this.#initial;
8988
- } else if (shape) {
8989
- const dirty = this.input.value !== this.#initial;
8990
- this.#initial = shape.text?.plainText ?? "";
8991
- if (!dirty) this.input.value = this.#initial;
8992
- }
8993
- const busy = state.loading || state.edit.busy || this.#pending;
8994
- const available = state.edit.sourceAvailable && !!shape && pageId !== void 0 && state.selectedShapes.length === 1;
8995
- this.panel.setAttribute("aria-busy", String(busy));
8996
- this.#target.textContent = state.selectedShapes.length > 1 ? "Select one shape to edit its plain text." : shape ? `Page ID: ${pageId} \xB7 Shape ID: ${shape.id} \xB7 ${shape.name || "Unnamed shape"}${shape.text?.fields?.length ? ` \xB7 ${shape.text.fields.length} text field${shape.text.fields.length > 1 ? "s" : ""} (kept as whole values; edit the text around them)` : ""}` : "Select a shape to replace its plain text.";
8997
- this.input.disabled = !available || busy;
8998
- this.#buttons.apply.disabled = !available || busy || this.input.value === this.#initial;
8999
- this.#buttons.cancel.disabled = !state.edit.busy && !this.#pending && this.input.value === this.#initial && !this.#geometry.hasDraft;
9000
- this.#buttons.undo.disabled = busy || !state.edit.canUndo;
9001
- this.#buttons.redo.disabled = busy || !state.edit.canRedo;
9002
- this.#status.textContent = state.loading ? "Opening diagram\u2026" : busy ? "Updating diagram\u2026" : !state.edit.sourceAvailable ? "Open a .vsdx file to edit or download a VSDX copy. Model-only documents cannot be saved." : `${state.edit.dirty ? "Edited copy" : "Original bytes"}${state.edit.historyTruncated ? " \xB7 Earlier undo history was discarded." : ""}`;
9003
- const error = this.#localError ?? (state.edit.error ? editErrorMessage(state.edit.error) : void 0);
9004
- this.#error.hidden = !error;
9005
- this.#error.textContent = error ?? "";
9006
- this.#diagnostics.replaceChildren(
9007
- ...state.edit.diagnostics.map((diagnostic) => {
9008
- const item = this.panel.ownerDocument.createElement("li");
9009
- item.textContent = `${diagnostic.code}: ${diagnostic.message}`;
9010
- return item;
9011
- })
9012
- );
8967
+ const session = this.#session;
8968
+ if (!session) return;
8969
+ const page3 = state.document?.pages[state.pageIndex];
8970
+ const shape = page3?.id === session.pageId ? visioTextShape(page3, session.shapeId) : void 0;
8971
+ if (this.controller.sourceGeneration !== session.generation || !state.document) {
8972
+ this.#close();
8973
+ return;
8974
+ }
8975
+ if (!shape || state.selectedShapes.length !== 1 || state.selectedShape?.id !== session.shapeId) {
8976
+ void this.commit(false);
8977
+ return;
8978
+ }
8979
+ if (state.document !== session.document) {
8980
+ const dirty = this.input.value !== session.initial;
8981
+ session.document = state.document;
8982
+ session.initial = shape.text?.plainText ?? "";
8983
+ if (!dirty) this.input.value = session.initial;
8984
+ session.group = this.viewport.querySelector(
8985
+ `svg.paper [data-shape-id="${CSS.escape(shape.id)}"]`
8986
+ );
8987
+ session.group?.setAttribute("data-editing-text", "");
8988
+ this.#style(shape, state.zoom || 1);
8989
+ }
8990
+ if (!this.input.isConnected) {
8991
+ const { selectionStart, selectionEnd } = this.input;
8992
+ this.viewport.append(this.input);
8993
+ this.viewport.dataset.textEditing = "";
8994
+ this.input.focus({ preventScroll: true });
8995
+ this.input.setSelectionRange(selectionStart, selectionEnd);
8996
+ }
8997
+ this.#layout();
8998
+ }
8999
+ #close() {
9000
+ const session = this.#session;
9001
+ this.#session = void 0;
9002
+ session?.group?.removeAttribute("data-editing-text");
9003
+ delete this.viewport.dataset.textEditing;
9004
+ this.input.hidden = true;
9005
+ this.input.remove();
9006
+ }
9007
+ /** Match the shape's text: family, size at this zoom, colour and alignment. */
9008
+ #style(shape, zoom) {
9009
+ const text = shape.text;
9010
+ const style = this.input.style;
9011
+ style.fontFamily = text?.fontFamily ? `"${text.fontFamily}", var(--office-font)` : "";
9012
+ style.fontSize = `${Math.max(8, (text?.fontSize ?? 12 / 72) * 96 * zoom)}px`;
9013
+ style.color = text?.color ?? "";
9014
+ style.textAlign = text?.horizontalAlign === "justify" ? "justify" : text?.horizontalAlign ?? "center";
9015
+ this.input.dataset.verticalAlign = text?.verticalAlign ?? "middle";
9016
+ }
9017
+ /** Cover the shape's drawn box and keep the text vertically placed as the shape aligns it. */
9018
+ #layout() {
9019
+ const session = this.#session;
9020
+ if (!session) return;
9021
+ const box2 = this.viewport.getBoundingClientRect();
9022
+ const rect = session.group?.getBoundingClientRect();
9023
+ const width = Math.max(48, rect?.width ?? 160);
9024
+ const height = Math.max(24, rect?.height ?? 48);
9025
+ const style = this.input.style;
9026
+ style.left = `${(rect ? rect.left - box2.left : 16) + this.viewport.scrollLeft}px`;
9027
+ style.top = `${(rect ? rect.top - box2.top : 16) + this.viewport.scrollTop}px`;
9028
+ style.width = `${width}px`;
9029
+ style.paddingTop = "0px";
9030
+ style.height = "0px";
9031
+ const content = this.input.scrollHeight;
9032
+ const free = Math.max(0, height - content);
9033
+ style.height = `${Math.max(height, content)}px`;
9034
+ const align = this.input.dataset.verticalAlign;
9035
+ style.paddingTop = `${align === "top" ? 0 : align === "bottom" ? free : free / 2}px`;
9013
9036
  }
9014
9037
  };
9015
9038
 
9016
9039
  // src/visio/viewer-chrome.ts
9017
9040
  var viewerChromeTemplate = `<div class="workspace">
9018
9041
  <div class="viewport" tabindex="0" role="region" aria-label="Diagram canvas"></div>
9019
- <aside id="inspector-pane" class="inspector-pane" aria-label="Drawing inspector">
9020
- <div class="pane-heading"><span>Inspector</span><span class="inspector-kind">Drawing</span><button class="pane-close" type="button" data-chrome="inspector" aria-label="Close inspector">\xD7</button></div>
9021
- <div class="inspector-body">
9022
- <section class="inspector-card document-card" aria-label="Current page"><h2>Current page</h2><p class="current-page-name" data-page-name>No diagram open</p><dl><dt>Size</dt><dd data-page-size>No page</dd><dt>Shapes</dt><dd data-page-shapes>No shapes</dd></dl></section>
9023
- <p class="selection-hint">Select a shape on the canvas to see its data and links.</p>
9024
- <details class="shape-inspector inspector-card" hidden><summary>Selected shape</summary><div></div></details>
9025
- ${editControlsTemplate}${layerControlsTemplate}
9026
- <details class="notes inspector-card"><summary>Compatibility notes</summary><ul></ul></details>
9027
- </div>
9028
- </aside>
9042
+ <office-ui-task-pane id="inspector-pane" class="inspector-pane" label="Shape Data" hidden>
9043
+ <section class="shape-inspector" data-pane-view="selection"><p class="selection-hint">No Shape Data</p><div></div></section>
9044
+ </office-ui-task-pane>
9029
9045
  </div>`;
9046
+ var PANE_TITLES = { selection: "Shape Data" };
9030
9047
  var ViewerChrome = class {
9031
9048
  #root;
9032
9049
  #controller;
@@ -9036,7 +9053,7 @@ var ViewerChrome = class {
9036
9053
  #tools;
9037
9054
  #inspector;
9038
9055
  #panes;
9039
- #notes;
9056
+ #view = "selection";
9040
9057
  #responsive;
9041
9058
  #compact;
9042
9059
  /** Panes the user showed or hid; responsive defaults leave them alone afterwards. */
@@ -9049,7 +9066,6 @@ var ViewerChrome = class {
9049
9066
  this.#tools = root.querySelector(".ribbon-tools");
9050
9067
  this.#inspector = root.querySelector(".inspector-pane");
9051
9068
  this.#panes = { shapes: root.querySelector(".shapes-pane"), inspector: this.#inspector };
9052
- this.#notes = root.querySelector(".notes");
9053
9069
  }
9054
9070
  wire() {
9055
9071
  const view = this.#root.ownerDocument.defaultView;
@@ -9078,7 +9094,7 @@ var ViewerChrome = class {
9078
9094
  );
9079
9095
  const responsive = () => {
9080
9096
  if (!this.#manual.has("shapes")) this.#setPane("shapes", !this.#responsive?.matches);
9081
- if (!this.#manual.has("inspector")) this.#setPane("inspector", !compact?.matches);
9097
+ if (!this.#manual.has("inspector")) this.#setPane("inspector", false);
9082
9098
  };
9083
9099
  responsive();
9084
9100
  this.#responsive?.addEventListener("change", responsive, options2);
@@ -9090,7 +9106,15 @@ var ViewerChrome = class {
9090
9106
  if (!button || button.disabled) return;
9091
9107
  const action2 = button.dataset.chrome;
9092
9108
  if (action2 === "inspector" || action2 === "shapes") this.togglePane(action2);
9093
- if (action2 === "notes") this.reveal("notes");
9109
+ },
9110
+ options2
9111
+ );
9112
+ this.#inspector.addEventListener(
9113
+ "office-pane-close",
9114
+ () => {
9115
+ this.#manual.add("inspector");
9116
+ this.#setPane("inspector", false);
9117
+ this.#root.querySelector(".viewport")?.focus({ preventScroll: true });
9094
9118
  },
9095
9119
  options2
9096
9120
  );
@@ -9114,7 +9138,6 @@ var ViewerChrome = class {
9114
9138
  options2
9115
9139
  );
9116
9140
  this.#root.addEventListener("office-ribbon-select", () => this.closeCompactTools(), options2);
9117
- this.#notes.addEventListener("toggle", () => this.#syncNotes(), options2);
9118
9141
  return () => events.abort();
9119
9142
  }
9120
9143
  showTab(tab) {
@@ -9140,36 +9163,33 @@ var ViewerChrome = class {
9140
9163
  for (const other of Object.keys(this.#panes))
9141
9164
  if (other !== pane) {
9142
9165
  this.#panes[other].hidden = true;
9143
- this.#command(other).setAttribute("checked", "false");
9166
+ this.#command(other)?.setAttribute("checked", "false");
9144
9167
  }
9145
9168
  }
9146
9169
  this.#panes[pane].hidden = !visible;
9147
- this.#command(pane).setAttribute("checked", String(visible));
9170
+ this.#command(pane)?.setAttribute("checked", String(visible));
9171
+ this.#command("shape-data")?.setAttribute(
9172
+ "checked",
9173
+ String(!this.#inspector.hidden && this.#view === "selection")
9174
+ );
9148
9175
  if (pane === "shapes") {
9149
9176
  const strip = this.#root.querySelector(".shapes-strip");
9150
9177
  if (strip) strip.hidden = visible || !!this.#compact?.matches;
9151
9178
  }
9152
- this.#syncNotes();
9153
- }
9154
- #syncNotes() {
9155
- this.#root.querySelector(".notes-strip button").setAttribute("aria-expanded", String(!this.#inspector.hidden && this.#notes.open));
9156
9179
  }
9157
- /** Open an inspector disclosure; text editing may focus the text field directly (F2). */
9158
- reveal(kind, focusText = false) {
9180
+ /** Show a task pane view (Shape Data) and move focus into it. */
9181
+ reveal(kind) {
9159
9182
  if (this.#compact?.matches) this.#tools.open = false;
9183
+ this.#view = kind;
9184
+ for (const view of this.#inspector.querySelectorAll("[data-pane-view]"))
9185
+ view.toggleAttribute("data-active", view.dataset.paneView === kind);
9186
+ this.#inspector.setAttribute("label", PANE_TITLES[kind]);
9160
9187
  this.#manual.add("inspector");
9161
9188
  this.#setPane("inspector", true);
9162
- const selector = kind === "notes" ? ".notes" : kind === "edit" ? ".edit-controls" : kind === "layers" ? ".layer-controls" : ".shape-inspector";
9163
- const panel = this.#root.querySelector(selector);
9164
- if (panel.hidden) return;
9165
- panel.open = true;
9166
- const text = panel.querySelector("textarea");
9167
- if (focusText && text && !text.disabled) text.focus();
9168
- else panel.querySelector("summary").focus();
9169
- panel.scrollIntoView?.({ block: "nearest", inline: "nearest" });
9170
- this.#syncNotes();
9171
- }
9172
- render(state, noteCount) {
9189
+ const panel = this.#inspector.querySelector(`[data-pane-view="${kind}"]`);
9190
+ (panel.querySelector("input, button, a[href]") ?? this.#inspector).focus();
9191
+ }
9192
+ render(state) {
9173
9193
  const page3 = state.document?.pages[state.pageIndex];
9174
9194
  const doc = this.#root.ownerDocument;
9175
9195
  if (state.document !== this.#document) {
@@ -9235,18 +9255,7 @@ var ViewerChrome = class {
9235
9255
  "value",
9236
9256
  page3 ? `Page ${state.pageIndex + 1} of ${state.document.pages.length}` : ""
9237
9257
  );
9238
- this.#root.querySelector("[data-page-name]").textContent = page3?.name ?? "No diagram open";
9239
- this.#root.querySelector("[data-page-size]").textContent = page3 ? `${page3.width} \xD7 ${page3.height} in` : "No page";
9240
- this.#root.querySelector("[data-page-shapes]").textContent = page3 ? `${page3.shapes.length} top-level` : "No shapes";
9241
9258
  this.#root.querySelector(".selection-hint").hidden = !!state.selectedShape;
9242
- const layers = Boolean(this.#root.querySelector(".layer-controls").hidden);
9243
- this.#command("shape-data").disabled = !state.selectedShape;
9244
- this.#command("layer-properties").disabled = layers;
9245
- this.#command("layers-pane").disabled = layers;
9246
- this.#command("notes").disabled = noteCount === 0;
9247
- this.#root.querySelector(".notes-strip button").disabled = noteCount === 0;
9248
- this.#root.querySelector("[data-note-count]").textContent = noteCount ? ` \xB7 ${noteCount}` : "";
9249
- this.#syncNotes();
9250
9259
  }
9251
9260
  };
9252
9261
  var ViewerCanvas = class {
@@ -9366,8 +9375,6 @@ var ViewerCanvas = class {
9366
9375
  const inspected = selectedShape(state.document, selection2, state.pageIndex);
9367
9376
  if (inspected !== this.#inspected) {
9368
9377
  this.#inspected = inspected;
9369
- this.inspector.hidden = !inspected;
9370
- if (inspected) this.inspector.open = true;
9371
9378
  this.inspector.querySelector("div").replaceChildren(...inspected ? [shapeDetails(inspected)] : []);
9372
9379
  }
9373
9380
  this.#selection = selection2 ? { ...selection2 } : null;
@@ -9388,6 +9395,7 @@ function routeRibbonAction(targets, action2) {
9388
9395
  case "re-layout":
9389
9396
  case "layout-options":
9390
9397
  case "assign-layers":
9398
+ case "layer-properties":
9391
9399
  case "select-by-type":
9392
9400
  case "paste-special":
9393
9401
  case "guides":
@@ -11064,8 +11072,7 @@ var ViewerDuplication = class {
11064
11072
  controller;
11065
11073
  edit;
11066
11074
  reason(state) {
11067
- if (!state.edit.sourceAvailable)
11068
- return "Open a .vsdx file to duplicate shapes. Model-only documents are read only.";
11075
+ if (!state.edit.sourceAvailable) return "Open a .vsdx file to duplicate shapes.";
11069
11076
  if (state.loading || state.edit.busy) return "Wait for the current operation to finish.";
11070
11077
  if (!state.selectedShapes.length) return "Select shapes to duplicate.";
11071
11078
  const page3 = state.document?.pages[state.pageIndex];
@@ -11106,8 +11113,7 @@ var ViewerGrouping = class {
11106
11113
  controller;
11107
11114
  edit;
11108
11115
  reason(state, operation) {
11109
- if (!state.edit.sourceAvailable)
11110
- return "Open a .vsdx file to group shapes. Model-only documents are read only.";
11116
+ if (!state.edit.sourceAvailable) return "Open a .vsdx file to group shapes.";
11111
11117
  if (state.loading || state.edit.busy) return "Wait for the current operation to finish.";
11112
11118
  const page3 = state.document?.pages[state.pageIndex];
11113
11119
  if (!page3 || state.selectedShapes.some((shape) => !visioSelectionIsOnPage(shape, page3.id)))
@@ -11170,8 +11176,7 @@ var ViewerClipboard = class {
11170
11176
  return operation === "paste" ? typeof this.clipboard?.readText === "function" : this.hasPromiseWrite() || typeof this.clipboard?.writeText === "function";
11171
11177
  }
11172
11178
  baseReason(state) {
11173
- if (!state.edit.sourceAvailable)
11174
- return "Open a .vsdx file. Model-only documents are read only.";
11179
+ if (!state.edit.sourceAvailable) return "Open a .vsdx file.";
11175
11180
  if (state.loading || state.edit.busy || this.#pending)
11176
11181
  return "Wait for the current operation to finish.";
11177
11182
  if (!state.document?.pages[state.pageIndex]) return "Open a page before using the clipboard.";
@@ -11978,8 +11983,8 @@ var ViewerPageSetupDialog = class {
11978
11983
  const paper = f("paper");
11979
11984
  if (paper && Number(paper) !== setup?.paperKind) print.paperKind = Number(paper);
11980
11985
  const orientation = f("print-orientation");
11981
- const saved = setup?.printPageOrientation === 1 ? "portrait" : setup?.printPageOrientation === 2 ? "landscape" : "";
11982
- if ((orientation === "portrait" || orientation === "landscape") && orientation !== saved)
11986
+ const saved2 = setup?.printPageOrientation === 1 ? "portrait" : setup?.printPageOrientation === 2 ? "landscape" : "";
11987
+ if ((orientation === "portrait" || orientation === "landscape") && orientation !== saved2)
11983
11988
  print.printOrientation = orientation;
11984
11989
  if (Object.keys(print).length > 2) edits.push(print);
11985
11990
  return visioKeepAutoSize(page3, edits);
@@ -12844,7 +12849,7 @@ var ViewerComments = class {
12844
12849
  if (this.pane.labels.newComment !== newComment)
12845
12850
  this.pane.labels = { empty: "No comments on this page.", newComment };
12846
12851
  const button = (name) => this.root.querySelector(`[command="${name}"]`);
12847
- const reason = !page3 ? "Open a drawing first." : !state.edit.sourceAvailable ? "Open a .vsdx file to add comments. Model-only documents are read only." : !editable3 ? "Wait for the current operation to finish." : "";
12852
+ const reason = !page3 ? "Open a drawing first." : !state.edit.sourceAvailable ? "Open a .vsdx file to add comments." : !editable3 ? "Wait for the current operation to finish." : "";
12848
12853
  for (const name of ["new-comment", "ctx-comment", "ctx-page-comment"]) {
12849
12854
  const control = button(name);
12850
12855
  if (!control) continue;
@@ -13235,8 +13240,7 @@ var ViewerSubprocess = class {
13235
13240
  return state.selectedShapes.map((shape) => shape.id);
13236
13241
  }
13237
13242
  reason(state, mode) {
13238
- if (!state.edit.sourceAvailable)
13239
- return "Open a .vsdx file to create subprocesses. Model-only documents are read only.";
13243
+ if (!state.edit.sourceAvailable) return "Open a .vsdx file to create subprocesses.";
13240
13244
  if (state.loading || state.edit.busy) return "Wait for the current operation to finish.";
13241
13245
  const page3 = this.#page(state);
13242
13246
  if (!page3 || page3.isBackground) return "Open a foreground page.";
@@ -13427,8 +13431,7 @@ var ViewerDiagramParts = class {
13427
13431
  edit;
13428
13432
  revealText;
13429
13433
  reason(state, part) {
13430
- if (!state.edit.sourceAvailable)
13431
- return "Open a .vsdx file to insert diagram parts. Model-only documents are read only.";
13434
+ if (!state.edit.sourceAvailable) return "Open a .vsdx file to insert diagram parts.";
13432
13435
  if (state.loading || state.edit.busy) return "Wait for the current operation to finish.";
13433
13436
  const page3 = state.document?.pages[state.pageIndex];
13434
13437
  if (!page3 || state.selectedShapes.some((shape) => !visioSelectionIsOnPage(shape, page3.id)))
@@ -14711,7 +14714,8 @@ var ViewerGuides = class {
14711
14714
  controller;
14712
14715
  announce;
14713
14716
  #shown = false;
14714
- #dynamic = false;
14717
+ /** On by default, as in Visio: moving shapes snap to other shapes' edges and centres. */
14718
+ #dynamic = true;
14715
14719
  #selected;
14716
14720
  #drag;
14717
14721
  get shown() {
@@ -14736,16 +14740,20 @@ var ViewerGuides = class {
14736
14740
  #editable(state) {
14737
14741
  return state.edit.sourceAvailable && !state.loading && !state.edit.busy;
14738
14742
  }
14739
- /** Snap a shape drag; draws alignment hints. Unchanged when no snapping is active. */
14740
- snap(page3, ids, delta) {
14743
+ /**
14744
+ * Snap a shape drag; draws alignment hints. `grid` is the Snap to Grid step in page inches (0
14745
+ * while the grid is hidden). Unchanged when no snapping is active.
14746
+ */
14747
+ snap(page3, ids, delta, grid = 0) {
14741
14748
  const svg2 = this.#svg();
14742
14749
  this.clearHints();
14743
- if (!this.#shown && !this.#dynamic) return delta;
14750
+ if (!this.#shown && !this.#dynamic && !grid) return delta;
14744
14751
  const zoom = this.controller.state.zoom || 1;
14745
14752
  const result = visioSnapMoveDelta(page3, ids, delta, {
14746
14753
  shapes: this.#dynamic,
14747
14754
  guides: this.#shown,
14748
- threshold: SNAP_PIXELS / (96 * zoom)
14755
+ threshold: SNAP_PIXELS / (96 * zoom),
14756
+ grid
14749
14757
  });
14750
14758
  if (!svg2 || !result.lines.length) return result.delta;
14751
14759
  const hints = svg2.ownerDocument.createElementNS(SVG3, "g");
@@ -15101,6 +15109,180 @@ var ViewerLayerAssign = class {
15101
15109
  this.dialog.close();
15102
15110
  }
15103
15111
  };
15112
+ var LABEL4 = "Layer Properties";
15113
+ var FLAGS = [
15114
+ ["visible", "Visible"],
15115
+ ["print", "Print"],
15116
+ ["lock", "Lock"]
15117
+ ];
15118
+ var saved = (layer, flag) => flag === "visible" ? layer.visible : flag === "print" ? layer.printable : layer.locked;
15119
+ var ViewerLayerProperties = class {
15120
+ constructor(root, controller, announce) {
15121
+ this.root = root;
15122
+ this.controller = controller;
15123
+ this.announce = announce;
15124
+ const doc = root.ownerDocument;
15125
+ this.dialog = new ViewerDialog(
15126
+ root,
15127
+ "layer-properties-dialog",
15128
+ LABEL4,
15129
+ ["OK", "Cancel"],
15130
+ (b) => b === "OK" ? void this.#apply() : this.dialog.close()
15131
+ );
15132
+ this.#table = doc.createElement("table");
15133
+ this.#table.className = "layer-table";
15134
+ this.#note = doc.createElement("p");
15135
+ this.#note.className = "layer-note";
15136
+ this.dialog.body.append(this.#table, this.#note);
15137
+ }
15138
+ root;
15139
+ controller;
15140
+ announce;
15141
+ dialog;
15142
+ #table;
15143
+ #note;
15144
+ #generation;
15145
+ #sources(state) {
15146
+ const model = state.document;
15147
+ const page3 = model?.pages[state.pageIndex];
15148
+ return model && page3 ? getVisioPageLayers(model, page3.id) : [];
15149
+ }
15150
+ open() {
15151
+ const state = this.controller.state;
15152
+ const model = state.document;
15153
+ const sources = this.#sources(state);
15154
+ if (!model || !sources.some((source) => source.layers?.length)) {
15155
+ this.announce(`${LABEL4}: this page has no layers. Use Assign to Layer to add one.`);
15156
+ return;
15157
+ }
15158
+ const doc = this.root.ownerDocument;
15159
+ const editable3 = state.edit.sourceAvailable;
15160
+ const overrides = layerOverrideMaps(model, state.layerVisibilityOverrides);
15161
+ const cell = (tag, text = "") => Object.assign(doc.createElement(tag), { textContent: text });
15162
+ const head = doc.createElement("thead");
15163
+ const titles = doc.createElement("tr");
15164
+ for (const title2 of ["Name", "#", "Visible", "Print", "Lock", "Color"])
15165
+ titles.append(Object.assign(cell("th", title2), { scope: "col" }));
15166
+ head.append(titles);
15167
+ const body = doc.createElement("tbody");
15168
+ let shown = 0;
15169
+ for (const source of sources) {
15170
+ const page3 = model.pages.find((candidate) => candidate.id === source.id);
15171
+ const layers = source.layers ?? [];
15172
+ if (!layers.length) continue;
15173
+ if (sources.length > 1) {
15174
+ const caption = doc.createElement("tr");
15175
+ const label = cell(
15176
+ "th",
15177
+ source.id === sources[0].id ? source.name : `${source.name} (background)`
15178
+ );
15179
+ label.colSpan = 6;
15180
+ label.scope = "rowgroup";
15181
+ caption.className = "layer-page";
15182
+ caption.append(label);
15183
+ body.append(caption);
15184
+ }
15185
+ for (const layer of layers) {
15186
+ if (shown >= VIEWER_LAYER_LIMITS.controls) break;
15187
+ ++shown;
15188
+ const name = layer.name.slice(0, 256) || "Layer";
15189
+ const row = doc.createElement("tr");
15190
+ row.dataset.pageId = source.id;
15191
+ row.dataset.layerId = layer.id;
15192
+ const count = (page3?.shapes ?? []).filter(
15193
+ (shape) => shape.layerIds?.includes(layer.id)
15194
+ ).length;
15195
+ row.append(Object.assign(cell("th", name), { scope: "row" }), cell("td", String(count)));
15196
+ for (const [flag, title2] of FLAGS) {
15197
+ const box2 = doc.createElement("input");
15198
+ box2.type = "checkbox";
15199
+ box2.dataset.flag = flag;
15200
+ box2.setAttribute("aria-label", `${name}: ${title2}`);
15201
+ const override = flag === "visible" ? overrides.get(source.id)?.get(layer.id) : void 0;
15202
+ box2.checked = override ?? saved(layer, flag);
15203
+ box2.disabled = !editable3 && flag !== "visible";
15204
+ const holder = cell("td");
15205
+ holder.append(box2);
15206
+ row.append(holder);
15207
+ }
15208
+ const colour = cell("td");
15209
+ if (layer.color) {
15210
+ const swatch = doc.createElement("span");
15211
+ swatch.className = "layer-colour";
15212
+ swatch.style.background = layer.color;
15213
+ swatch.title = layer.color;
15214
+ colour.append(swatch);
15215
+ }
15216
+ row.append(colour);
15217
+ body.append(row);
15218
+ }
15219
+ }
15220
+ this.#table.replaceChildren(head, body);
15221
+ this.#note.textContent = editable3 ? "" : "This drawing is read-only: Visible changes how it is shown here and is not saved.";
15222
+ this.#generation = this.controller.documentGeneration;
15223
+ this.dialog.show();
15224
+ this.#table.querySelector("input:not([disabled])")?.focus();
15225
+ }
15226
+ async #apply() {
15227
+ const state = this.controller.state;
15228
+ const model = state.document;
15229
+ if (!model || this.controller.documentGeneration !== this.#generation) {
15230
+ this.dialog.close();
15231
+ return;
15232
+ }
15233
+ const editable3 = state.edit.sourceAvailable;
15234
+ const sources = new Map(this.#sources(state).map((source) => [source.id, source]));
15235
+ const edits = [];
15236
+ const shown = [];
15237
+ for (const row of this.#table.querySelectorAll("tr[data-layer-id]")) {
15238
+ const pageId = row.dataset.pageId, layerId = row.dataset.layerId;
15239
+ const layer = sources.get(pageId)?.layers?.find((candidate) => candidate.id === layerId);
15240
+ if (!layer) continue;
15241
+ const value = (flag) => row.querySelector(`input[data-flag="${flag}"]`).checked;
15242
+ if (!editable3) {
15243
+ shown.push({
15244
+ pageId,
15245
+ layerId,
15246
+ visible: value("visible") === layer.visible ? null : value("visible")
15247
+ });
15248
+ continue;
15249
+ }
15250
+ const changed = FLAGS.filter(([flag]) => value(flag) !== saved(layer, flag));
15251
+ if (changed.length)
15252
+ edits.push({
15253
+ type: "set-layer-properties",
15254
+ pageId,
15255
+ layerId,
15256
+ ...Object.fromEntries(changed.map(([flag]) => [flag, value(flag)]))
15257
+ });
15258
+ shown.push({ pageId, layerId, visible: null });
15259
+ }
15260
+ this.dialog.busy(true);
15261
+ try {
15262
+ if (edits.length) await this.controller.applyEdits(edits);
15263
+ for (const item of shown)
15264
+ this.controller.setLayerVisibility(item.pageId, item.layerId, item.visible);
15265
+ this.dialog.close();
15266
+ if (edits.length)
15267
+ this.announce(`Updated ${edits.length} layer${edits.length === 1 ? "" : "s"}.`);
15268
+ } catch (error) {
15269
+ if (this.dialog.open && !isEditCancellation(error))
15270
+ this.dialog.error.textContent = editErrorMessage(error);
15271
+ } finally {
15272
+ this.dialog.busy(false);
15273
+ }
15274
+ }
15275
+ render(state) {
15276
+ const none = !this.#sources(state).some((source) => source.layers?.length);
15277
+ const command2 = this.root.querySelector('[command="layer-properties"]');
15278
+ if (command2) {
15279
+ command2.disabled = none;
15280
+ command2.title = none ? `${LABEL4}: this page has no layers.` : `${LABEL4}...`;
15281
+ }
15282
+ if (this.dialog.open && (state.loading || this.controller.documentGeneration !== this.#generation))
15283
+ this.dialog.close();
15284
+ }
15285
+ };
15104
15286
  var AUTO_ALIGN = "Auto Align & Space";
15105
15287
  var ViewerLayout = class {
15106
15288
  constructor(root, controller, run) {
@@ -15513,6 +15695,7 @@ var ViewerLayoutCommands = class {
15513
15695
  const { root, controller, announce } = host;
15514
15696
  this.layout = new ViewerLayout(root, controller, host.edit);
15515
15697
  this.layers = new ViewerLayerAssign(root, controller, announce);
15698
+ this.layerProperties = new ViewerLayerProperties(root, controller, announce);
15516
15699
  this.select = new ViewerSelectType(root, controller, announce);
15517
15700
  this.paste = new ViewerPasteSpecial(root, controller, announce, host.pasteShapes);
15518
15701
  this.guides = new ViewerGuides(root, host.viewport, host.rulers, controller, announce);
@@ -15521,6 +15704,7 @@ var ViewerLayoutCommands = class {
15521
15704
  host;
15522
15705
  layout;
15523
15706
  layers;
15707
+ layerProperties;
15524
15708
  select;
15525
15709
  paste;
15526
15710
  guides;
@@ -15535,6 +15719,8 @@ var ViewerLayoutCommands = class {
15535
15719
  return this.layout.showOptions();
15536
15720
  case "assign-layers":
15537
15721
  return this.layers.open();
15722
+ case "layer-properties":
15723
+ return this.layerProperties.open();
15538
15724
  case "select-by-type":
15539
15725
  return this.select.open();
15540
15726
  case "paste-special":
@@ -15571,6 +15757,7 @@ var ViewerLayoutCommands = class {
15571
15757
  render(state) {
15572
15758
  this.layout.render(state);
15573
15759
  this.layers.render(state);
15760
+ this.layerProperties.render(state);
15574
15761
  this.select.render(state);
15575
15762
  this.paste.render(state);
15576
15763
  this.guides.render(state);
@@ -16371,7 +16558,7 @@ var ViewerTextFeatures = class {
16371
16558
  /** The shape text editor, when it holds the caret for the selected shape. */
16372
16559
  #activeEditor() {
16373
16560
  const editor = this.#editor;
16374
- return editor?.isConnected && !editor.disabled && editor.closest("details")?.open ? editor : void 0;
16561
+ return editor?.isConnected && !editor.disabled && !editor.hidden ? editor : void 0;
16375
16562
  }
16376
16563
  run(feature) {
16377
16564
  const { state, page: page3, editing, shapes, formattable, single } = this.#state();
@@ -16477,7 +16664,6 @@ var ViewerTextFeatures = class {
16477
16664
  this.#language.dialog.close();
16478
16665
  }
16479
16666
  #spelling(page3) {
16480
- const editor = this.root.querySelector(EDITOR);
16481
16667
  const state = this.controller.state;
16482
16668
  const selected = state.selectedShape;
16483
16669
  const texts = [];
@@ -16489,17 +16675,15 @@ var ViewerTextFeatures = class {
16489
16675
  };
16490
16676
  visit(page3.shapes);
16491
16677
  const shape = texts.find((item) => item.id === selected?.id) ?? texts[0];
16492
- if (!editor || !shape) {
16493
- this.announce(
16494
- editor ? "This page has no shape text to check." : "Spelling needs the shape text editor."
16495
- );
16678
+ if (!shape) {
16679
+ this.announce("This page has no shape text to check.");
16496
16680
  return;
16497
16681
  }
16498
16682
  if (shape.id !== selected?.id || state.selectedShapes.length !== 1)
16499
16683
  this.controller.selectShape({ id: shape.id, name: shape.name, pageId: page3.id });
16500
16684
  this.reveal("edit", true);
16501
16685
  this.announce(
16502
- `Spelling uses your browser's spell checker: misspelled words in "${shape.name || `Shape ${shape.id}`}" are underlined in the shape text editor. Right-click a word for suggestions, then Apply text. ${texts.length > 1 ? `${texts.length - 1} more shapes on this page have text.` : ""}`.trim()
16686
+ `Spelling uses your browser's spell checker: misspelled words in "${shape.name || `Shape ${shape.id}`}" are underlined in the shape. Right-click a word for suggestions, then press Esc to keep the change. ${texts.length > 1 ? `${texts.length - 1} more shapes on this page have text.` : ""}`.trim()
16503
16687
  );
16504
16688
  }
16505
16689
  wire() {
@@ -16507,8 +16691,8 @@ var ViewerTextFeatures = class {
16507
16691
  const target = event.composedPath()[0];
16508
16692
  if (target instanceof HTMLTextAreaElement && target.matches(EDITOR)) this.#editor = target;
16509
16693
  };
16510
- const pointer = () => {
16511
- this.#editor = void 0;
16694
+ const pointer = (event) => {
16695
+ if (event.target !== this.#editor) this.#editor = void 0;
16512
16696
  };
16513
16697
  this.root.addEventListener("focusin", focus);
16514
16698
  this.viewport.addEventListener("pointerdown", pointer, true);
@@ -16573,6 +16757,7 @@ var ViewerTextFeatures = class {
16573
16757
  var editable2 = (target) => target instanceof Element && !!target.closest(
16574
16758
  'input, textarea, select, office-ui-select, [contenteditable]:not([contenteditable="false"])'
16575
16759
  );
16760
+ var GRID_STEP = 1 / 4;
16576
16761
  var ViewerCommands = class {
16577
16762
  constructor(host) {
16578
16763
  this.host = host;
@@ -16767,6 +16952,10 @@ var ViewerCommands = class {
16767
16952
  layoutCommands;
16768
16953
  #textFeatures;
16769
16954
  #targets;
16955
+ /** Page inches between minor grid lines while View > Grid is on (Snap to Grid), else 0. */
16956
+ get gridStep() {
16957
+ return this.#grid ? GRID_STEP : 0;
16958
+ }
16770
16959
  get tool() {
16771
16960
  return this.#tool;
16772
16961
  }
@@ -17090,8 +17279,6 @@ var ViewerCommands = class {
17090
17279
  root.querySelector('[data-menu="position"]').disabled = !rotating && !distributing && !this.layoutCommands.layout.available(state);
17091
17280
  button("select-all").disabled = !page3 || state.loading;
17092
17281
  button("clear-selection").disabled = !state.selectedShapes.length;
17093
- button("undo").disabled = !state.edit.canUndo || state.edit.busy || state.loading;
17094
- button("redo").disabled = !state.edit.canRedo || state.edit.busy || state.loading;
17095
17282
  button("pointer").setAttribute("pressed", String(this.#tool === "pointer"));
17096
17283
  button("connector").setAttribute("pressed", String(this.#tool === "connector"));
17097
17284
  button("connector").disabled = !editing || !page3;
@@ -17119,7 +17306,7 @@ var ViewerCommands = class {
17119
17306
  button("replace").title = editing ? "Replace literal, case-sensitive text occurrences (Ctrl+H)." : "Open a .vsdx file to replace text.";
17120
17307
  for (const name of ["text-tool", "text-box"])
17121
17308
  button(name).title = editing ? "Drag a fixed-size text box, or click a shape to edit plain text." : "Open a .vsdx file to add text boxes.";
17122
- rectangle.title = state.edit.sourceAvailable ? "Rectangle (Ctrl+8)" : "Rectangle (Ctrl+8): open a .vsdx file to draw. Model-only documents are read only.";
17309
+ rectangle.title = state.edit.sourceAvailable ? "Rectangle (Ctrl+8)" : "Rectangle (Ctrl+8): open a .vsdx file to draw.";
17123
17310
  box2("grid").checked = this.#grid;
17124
17311
  box2("grid").disabled = !page3;
17125
17312
  box2("ruler").checked = this.#ruler;
@@ -17674,6 +17861,7 @@ function createRotationPreview(model, page3, shape, source) {
17674
17861
  // src/visio/viewer-rotation-handle.ts
17675
17862
  var SVG6 = "http://www.w3.org/2000/svg";
17676
17863
  var pointOptions3 = { snap: false, bounded: false };
17864
+ var radians = (degrees, snap) => -(snap ? snapAngle(degrees) : degrees) * Math.PI / 180;
17677
17865
  var ViewerRotationHandle = class {
17678
17866
  constructor(viewport, controller, options2) {
17679
17867
  this.viewport = viewport;
@@ -17738,10 +17926,14 @@ var ViewerRotationHandle = class {
17738
17926
  handle.dataset.rotationHandle = shape.id;
17739
17927
  handle.setAttribute("cx", String(point.x));
17740
17928
  handle.setAttribute("cy", String(point.y));
17741
- handle.setAttribute("r", "6");
17929
+ handle.setAttribute("r", "8");
17742
17930
  handle.setAttribute("vector-effect", "non-scaling-stroke");
17743
17931
  handle.setAttribute("aria-label", "Drag rotation handle");
17744
- overlay.append(stem, handle);
17932
+ const glyph = this.viewport.ownerDocument.createElementNS(SVG6, "path");
17933
+ glyph.classList.add("rotation-glyph");
17934
+ glyph.setAttribute("transform", `translate(${point.x} ${point.y})`);
17935
+ glyph.setAttribute("d", "M4.6 -2.9A5.4 5.4 0 1 0 5.4 .6M5.6 -5.9V-2.4H2.1");
17936
+ overlay.append(stem, handle, glyph);
17745
17937
  this.viewport.append(overlay);
17746
17938
  this.#overlay = overlay;
17747
17939
  }
@@ -17829,7 +18021,7 @@ var ViewerRotationHandle = class {
17829
18021
  }
17830
18022
  const point = pagePoint(drag.svg, drag.page, event, pointOptions3);
17831
18023
  if (point) {
17832
- const angle = -drag.rotate(point) * Math.PI / 180;
18024
+ const angle = radians(drag.rotate(point), event.shiftKey);
17833
18025
  try {
17834
18026
  drag.shapePreview.update(angle);
17835
18027
  } catch (error) {
@@ -17861,7 +18053,7 @@ var ViewerRotationHandle = class {
17861
18053
  event.preventDefault();
17862
18054
  event.stopImmediatePropagation();
17863
18055
  const point = pagePoint(drag.svg, drag.page, event, pointOptions3);
17864
- const angle = point ? -drag.rotate(point) * Math.PI / 180 : void 0;
18056
+ const angle = point ? radians(drag.rotate(point), event.shiftKey) : void 0;
17865
18057
  this.#cancel();
17866
18058
  if (angle === void 0 || Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < 0.5 || this.controller.state.document !== drag.document)
17867
18059
  return;
@@ -18679,7 +18871,6 @@ var VisioViewerElement = class extends BaseElement {
18679
18871
  #disposeInputs;
18680
18872
  #viewport;
18681
18873
  #zoomSlider;
18682
- #shapeStatus;
18683
18874
  #announcement;
18684
18875
  #commands;
18685
18876
  #presentation;
@@ -18700,11 +18891,10 @@ var VisioViewerElement = class extends BaseElement {
18700
18891
  #findBar;
18701
18892
  #replace;
18702
18893
  #status;
18703
- #diagnostics;
18894
+ #titleBar;
18704
18895
  #toolbar;
18705
18896
  #chrome;
18706
18897
  #edit;
18707
- #layers;
18708
18898
  #notes;
18709
18899
  #unsubscribe;
18710
18900
  #eventUnsubscribe;
@@ -18726,7 +18916,8 @@ var VisioViewerElement = class extends BaseElement {
18726
18916
  this.#findBar = createFindBar(document);
18727
18917
  const ribbon = createRibbon(document);
18728
18918
  applyKeyTips(ribbon);
18729
- workspace.before(ribbon, this.#findBar);
18919
+ this.#titleBar = createTitleBar(document);
18920
+ workspace.before(this.#titleBar, ribbon, this.#findBar);
18730
18921
  workspace.prepend(createShapesStrip(document), createShapesWindow(document));
18731
18922
  workspace.append(createPanZoom(document), createSizePosition(document));
18732
18923
  this.#root.append(
@@ -18738,17 +18929,17 @@ var VisioViewerElement = class extends BaseElement {
18738
18929
  this.#viewport = this.#root.querySelector(".viewport");
18739
18930
  this.#rulers = createRulers(this.#viewport);
18740
18931
  this.#zoomSlider = this.#root.querySelector("office-ui-zoom-slider");
18741
- this.#shapeStatus = this.#root.querySelector("[data-shape-status]");
18742
18932
  this.#status = this.#root.querySelector("[data-status]");
18743
- this.#diagnostics = this.#root.querySelector("[data-diagnostics]");
18744
18933
  this.#toolbar = this.#root.querySelector(".toolbar");
18745
- this.#edit = new ViewerEditControls(this.#root, this.controller);
18746
- this.#layers = this.#root.querySelector(".layer-controls");
18747
- this.#notes = this.#root.querySelector(".notes ul");
18934
+ this.#edit = new ViewerInlineText(this.#viewport, this.controller, (message) => {
18935
+ this.#announcement = message;
18936
+ this.#status.textContent = message;
18937
+ });
18938
+ this.#notes = this.#root.querySelector(".backstage-notes ul");
18748
18939
  this.#canvas = new ViewerCanvas(
18749
18940
  this.#viewport,
18750
18941
  this.#notes,
18751
- this.#root.querySelector(".notes"),
18942
+ this.#root.querySelector(".backstage-notes"),
18752
18943
  this.#root.querySelector(".shape-inspector")
18753
18944
  );
18754
18945
  this.#chrome = new ViewerChrome(this.#root, this.controller);
@@ -18780,7 +18971,7 @@ var VisioViewerElement = class extends BaseElement {
18780
18971
  },
18781
18972
  fit: (mode) => this.#fit(mode),
18782
18973
  togglePane: (pane) => this.#chrome.togglePane(pane),
18783
- reveal: (panel, focusText) => this.#chrome.reveal(panel, focusText),
18974
+ reveal: (panel) => this.#reveal(panel),
18784
18975
  rulers: this.#rulers,
18785
18976
  togglePanZoom: () => this.#panZoom.toggle(),
18786
18977
  present: () => this.#presentation.start(),
@@ -18799,14 +18990,22 @@ var VisioViewerElement = class extends BaseElement {
18799
18990
  this.#status.textContent = message;
18800
18991
  }
18801
18992
  });
18802
- this.#profile = new ViewerProfile(this.#root);
18993
+ const profileChanged = () => renderTitleBar(this.#titleBar, this.controller.state, this.#fileName, this.#profile.profile);
18994
+ this.#profile = new ViewerProfile(this.#root, profileChanged, {
18995
+ get: () => this.#dark() ? "dark" : this.statusBar,
18996
+ set: (theme) => {
18997
+ if (theme !== "dark") this.statusBar = theme;
18998
+ if (theme === "dark" !== this.#dark())
18999
+ this.#requestScheme(theme === "dark" ? "dark" : "light");
19000
+ }
19001
+ });
18803
19002
  this.#pointer = new ViewerPointerGestures(this.#viewport, this.controller, {
18804
19003
  active: () => this.#commands.tool === "pointer",
18805
19004
  announce: (message) => {
18806
19005
  this.#announcement = message;
18807
19006
  this.#status.textContent = message;
18808
19007
  },
18809
- snap: (page3, ids, delta) => this.#commands.layoutCommands.guides.snap(page3, ids, delta),
19008
+ snap: (page3, ids, delta) => this.#commands.layoutCommands.guides.snap(page3, ids, delta, this.#commands.gridStep),
18810
19009
  clearSnap: () => this.#commands.layoutCommands.guides.clearHints()
18811
19010
  });
18812
19011
  this.#lineEndpoints = new ViewerLineEndpoints(this.#viewport, this.controller, {
@@ -18852,7 +19051,6 @@ var VisioViewerElement = class extends BaseElement {
18852
19051
  this.#fileName = "";
18853
19052
  this.controller.setDocument(null);
18854
19053
  },
18855
- revealNotes: () => this.#chrome.reveal("notes"),
18856
19054
  showOptions: () => this.#profile.showOptions(),
18857
19055
  announce: (message) => {
18858
19056
  this.#announcement = message;
@@ -18887,12 +19085,43 @@ var VisioViewerElement = class extends BaseElement {
18887
19085
  set zoom(value) {
18888
19086
  this.controller.setZoom(value);
18889
19087
  }
19088
+ /**
19089
+ * The status bar's look: `'neutral'` (the suite's bar, the default) or `'colorful'` (Visio's
19090
+ * product colour). Reflects the `status-bar` attribute; `--vv-status-background` and
19091
+ * `--vv-status-ink` recolour either.
19092
+ */
19093
+ get statusBar() {
19094
+ return this.getAttribute("status-bar") === "colorful" ? "colorful" : "neutral";
19095
+ }
19096
+ set statusBar(value) {
19097
+ this.setAttribute("status-bar", value === "colorful" ? "colorful" : "neutral");
19098
+ }
19099
+ /** Whether the page the viewer sits in is using the dark theme. */
19100
+ #dark() {
19101
+ const root = this.ownerDocument.documentElement;
19102
+ return (root.dataset.officeTheme ?? root.dataset.theme) === "dark";
19103
+ }
19104
+ /**
19105
+ * Light or dark belongs to the page, which every editor on it shares. Ask the host with a
19106
+ * cancelable `office-theme-request` (`{ scheme }`); when no host takes it, set the shared
19107
+ * theme's `data-office-theme` on the document.
19108
+ */
19109
+ #requestScheme(scheme) {
19110
+ const event = new CustomEvent("office-theme-request", {
19111
+ detail: { scheme },
19112
+ bubbles: true,
19113
+ composed: true,
19114
+ cancelable: true
19115
+ });
19116
+ if (this.dispatchEvent(event)) this.ownerDocument.documentElement.dataset.officeTheme = scheme;
19117
+ }
18890
19118
  get showToolbar() {
18891
19119
  return !this.#toolbar.hidden;
18892
19120
  }
18893
19121
  set showToolbar(value) {
18894
19122
  this.#assertAlive();
18895
19123
  this.#toolbar.hidden = !value;
19124
+ this.#titleBar.hidden = !value;
18896
19125
  this.#root.querySelector(".zoom-controls").hidden = !value;
18897
19126
  }
18898
19127
  get renderWarnings() {
@@ -18984,7 +19213,32 @@ var VisioViewerElement = class extends BaseElement {
18984
19213
  }
18985
19214
  cancelEdit() {
18986
19215
  this.#assertAlive();
18987
- this.#edit.reset(false);
19216
+ this.controller.cancelEdit();
19217
+ }
19218
+ /** Visio's status bar: Page n of m, then the selection's Width, Height and Angle, and language. */
19219
+ #renderStatus(state) {
19220
+ const set = (name, value) => this.#root.querySelector(`[data-${name}-status]`).setAttribute("value", value);
19221
+ const shape = selectedShape(state.document, state.selectedShape, state.pageIndex);
19222
+ const inches2 = (value) => `${+value.toFixed(3)} in.`;
19223
+ const many = state.selectedShapes.length > 1;
19224
+ set(
19225
+ "width",
19226
+ many ? `${state.selectedShapes.length} shapes selected` : shape ? `Width: ${inches2(shape.width)}` : ""
19227
+ );
19228
+ set("height", !many && shape ? `Height: ${inches2(shape.height)}` : "");
19229
+ const turn = shape?.rotation ? (shape.rotation.angle * 180 / Math.PI % 360 + 360) % 360 : 0;
19230
+ const angle = turn > 180 ? turn - 360 : turn;
19231
+ set("angle", !many && shape ? `Angle: ${+angle.toFixed(2)}\xB0` : "");
19232
+ set(
19233
+ "language",
19234
+ state.document ? statusLanguage(this.lang || this.ownerDocument.documentElement.lang || navigator.language) : ""
19235
+ );
19236
+ }
19237
+ /** Shape text edits in place on the canvas; compatibility notes live in File > Info. */
19238
+ #reveal(panel) {
19239
+ if (panel === "edit") this.#edit.start();
19240
+ else if (panel === "notes") this.#backstage.show("info");
19241
+ else this.#chrome.reveal(panel);
18988
19242
  }
18989
19243
  exportVsdx() {
18990
19244
  this.#assertAlive();
@@ -19056,6 +19310,8 @@ var VisioViewerElement = class extends BaseElement {
19056
19310
  }
19057
19311
  connectedCallback() {
19058
19312
  if (this.#disposed) return;
19313
+ const savedTheme = readStatusBarTheme();
19314
+ if (!this.hasAttribute("status-bar") && savedTheme) this.statusBar = savedTheme;
19059
19315
  if (this.#suspended) this.#disposeInputs = this.#wireInputs();
19060
19316
  this.#suspended = false;
19061
19317
  this.#fontEvents = this.ownerDocument.fonts;
@@ -19089,6 +19345,13 @@ var VisioViewerElement = class extends BaseElement {
19089
19345
  const disposePanZoom = this.#panZoom.wire();
19090
19346
  const disposeSizePosition = this.#sizePosition.wire();
19091
19347
  const disposeProfile = this.#profile.wire();
19348
+ const quickAccess = (event) => {
19349
+ const id = event.detail.command;
19350
+ event.stopPropagation();
19351
+ if (id === "save") this.#backstage.download();
19352
+ else if (id === "undo" || id === "redo") this.#commands.run({ type: "history", key: id });
19353
+ };
19354
+ this.#titleBar.addEventListener("office-command", quickAccess);
19092
19355
  const disposeShare = this.#share.wire();
19093
19356
  const disposeRulers = this.#rulers.wire();
19094
19357
  const disposeStencil = wireStencil(
@@ -19107,14 +19370,18 @@ var VisioViewerElement = class extends BaseElement {
19107
19370
  );
19108
19371
  const disposeReplace = this.#replace.wire();
19109
19372
  const disposeEdit = this.#edit.wire();
19110
- const disposeLayers = wireLayerControls(this.#layers, this.controller);
19111
19373
  const disposeInputs = wireViewerInputs(
19112
19374
  {
19113
19375
  viewport: this.#viewport,
19114
19376
  zoomSlider: this.#zoomSlider
19115
19377
  },
19116
19378
  this.controller,
19117
- (mode) => this.#fit(mode)
19379
+ (mode) => this.#fit(mode),
19380
+ (message) => {
19381
+ this.#announcement = message;
19382
+ this.#status.textContent = message;
19383
+ },
19384
+ (character) => this.#commands.tool === "pointer" && this.#edit.start(character)
19118
19385
  );
19119
19386
  return () => {
19120
19387
  disposeChrome();
@@ -19131,13 +19398,13 @@ var VisioViewerElement = class extends BaseElement {
19131
19398
  disposePanZoom();
19132
19399
  disposeSizePosition();
19133
19400
  disposeProfile();
19401
+ this.#titleBar.removeEventListener("office-command", quickAccess);
19134
19402
  disposeShare();
19135
19403
  disposeRulers();
19136
19404
  disposeStencil();
19137
19405
  disposeFind();
19138
19406
  disposeReplace();
19139
19407
  disposeInputs();
19140
- disposeLayers();
19141
19408
  disposeEdit();
19142
19409
  };
19143
19410
  }
@@ -19154,25 +19421,22 @@ var VisioViewerElement = class extends BaseElement {
19154
19421
  this.#lineEndpoints.render(state);
19155
19422
  this.#rotationHandle.render(state);
19156
19423
  this.#resizeHandles.render(state);
19424
+ this.#viewport.toggleAttribute(
19425
+ "data-selection-frame",
19426
+ !!this.#viewport.querySelector("[data-resize-overlay], [data-line-endpoint]")
19427
+ );
19157
19428
  }
19158
19429
  if (changed) this.#announcement = void 0;
19159
19430
  this.#zoomSlider.value = Math.round(state.zoom * 100);
19160
19431
  this.#zoomSlider.disabled = !page3;
19161
- const inspected = selectedShape(state.document, state.selectedShape, state.pageIndex);
19162
- const inches2 = (value) => `${+value.toFixed(3)} in`;
19163
- this.#shapeStatus.setAttribute(
19164
- "value",
19165
- state.selectedShapes.length > 1 ? `${state.selectedShapes.length} shapes selected` : inspected ? `Width: ${inches2(inspected.width)} Height: ${inches2(inspected.height)}` : ""
19166
- );
19432
+ this.#renderStatus(state);
19433
+ renderTitleBar(this.#titleBar, state, this.#fileName, this.#profile.profile);
19167
19434
  for (const button of this.#root.querySelectorAll("[data-action]"))
19168
19435
  button.disabled = !page3;
19169
19436
  renderFindBar(this.#findBar, state);
19170
19437
  this.#replace.render(state);
19171
19438
  this.#edit.render(state);
19172
- if (changed) renderLayerControls(this.#layers, state);
19173
- else
19174
- this.#layers.querySelector('[data-layer-reset="all"]').disabled = state.layerVisibilityOverrides.length === 0;
19175
- this.#chrome.render(state, this.#notes.children.length);
19439
+ this.#chrome.render(state);
19176
19440
  this.#commands.render(state);
19177
19441
  this.#presentation.render(state);
19178
19442
  this.#backstage.render(state);
@@ -19180,9 +19444,11 @@ var VisioViewerElement = class extends BaseElement {
19180
19444
  this.#panZoom.render();
19181
19445
  this.#sizePosition.render(state);
19182
19446
  this.#viewport.setAttribute("aria-busy", String(state.loading || state.edit.busy));
19183
- this.#status.textContent = state.loading ? "Opening diagram\u2026" : state.edit.busy ? "Updating diagram\u2026" : state.error?.message ?? (state.edit.error ? `Edit rejected: ${editErrorMessage(state.edit.error)}` : void 0) ?? this.#announcement ?? (page3 ? `${page3.name} \xB7 ${page3.shapes.length} top-level shapes` : "No diagram open");
19184
- const warnings = (state.document?.diagnostics.length ?? 0) + this.#canvas.warnings.length;
19185
- this.#diagnostics.textContent = warnings ? `${this.#notes.children.length} compatibility notes` : "Local-only viewing";
19447
+ this.#status.textContent = state.loading ? "Opening diagram\u2026" : state.edit.busy ? "Updating diagram\u2026" : state.error?.message ?? (state.edit.error ? `Edit rejected: ${editErrorMessage(state.edit.error)}` : void 0) ?? this.#announcement ?? "";
19448
+ this.#status.parentElement.toggleAttribute(
19449
+ "data-error",
19450
+ !state.loading && !state.edit.busy && !!(state.error ?? state.edit.error)
19451
+ );
19186
19452
  }
19187
19453
  };
19188
19454
  function registerVisioViewer() {