ooxml-ui 1.16.0 → 1.17.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.
@@ -1,5 +1,6 @@
1
1
  import { type OfficeProfile } from '../controls.js';
2
2
  import { VISIO_OPTION_CATEGORIES } from './viewer-options-categories.js';
3
+ import { type ScreenTipStyle } from './viewer-screen-tips.js';
3
4
  export { VISIO_OPTION_CATEGORIES };
4
5
  /** The status bar's look: Visio's product colour ("Colorful") or the neutral suite bar. */
5
6
  export type StatusBarTheme = 'colorful' | 'neutral';
@@ -8,6 +9,14 @@ export declare const isStatusBarTheme: (value: unknown) => value is StatusBarThe
8
9
  export declare function readStatusBarTheme(): StatusBarTheme | undefined;
9
10
  /** Office Theme as Options offers it: the two light looks, or the dark theme. */
10
11
  export type OfficeThemeChoice = StatusBarTheme | 'dark';
12
+ /** The Options that change how the viewer behaves, remembered on this device. */
13
+ export interface VisioBehaviour {
14
+ screenTips: ScreenTipStyle;
15
+ undoLevels: number;
16
+ }
17
+ export declare const DEFAULT_BEHAVIOUR: VisioBehaviour;
18
+ /** The behaviour Options saved on this device (storage may be unavailable or hold junk). */
19
+ export declare function readBehaviour(): VisioBehaviour;
11
20
  /** The Options dialog element. Built once and appended to the viewer's shadow root. */
12
21
  export declare function createOptionsDialog(doc: Document): HTMLElement;
13
22
  /**
@@ -19,12 +28,16 @@ export declare class ViewerProfile {
19
28
  private readonly onProfile;
20
29
  /** Reads and applies the Office Theme on the viewer element. */
21
30
  private readonly theme;
31
+ /** Applies the behaviour Options (ScreenTip style, undo depth), now and after each change. */
32
+ private readonly onBehaviour;
22
33
  constructor(root: ShadowRoot, onProfile?: (profile: OfficeProfile) => void,
23
34
  /** Reads and applies the Office Theme on the viewer element. */
24
35
  theme?: {
25
36
  get(): OfficeThemeChoice;
26
37
  set(theme: OfficeThemeChoice): void;
27
- });
38
+ },
39
+ /** Applies the behaviour Options (ScreenTip style, undo depth), now and after each change. */
40
+ onBehaviour?: (behaviour: VisioBehaviour) => void);
28
41
  get profile(): OfficeProfile;
29
42
  /** Open Visio Options, on General as Visio does. */
30
43
  showOptions(category?: string): void;
@@ -0,0 +1,10 @@
1
+ /** Office's ScreenTip style: full tips, the name alone, or none. */
2
+ export type ScreenTipStyle = 'descriptions' | 'plain' | 'off';
3
+ export declare const isScreenTipStyle: (value: unknown) => value is ScreenTipStyle;
4
+ /** Put back every tip `applyScreenTips` set aside (before the ribbon is translated again). */
5
+ export declare function restoreScreenTips(root: ParentNode): void;
6
+ /**
7
+ * Applies the ScreenTip style to a ribbon's `title` tooltips: "plain" drops the shortcut hint,
8
+ * "off" removes the tip. The original is kept so the style can change back.
9
+ */
10
+ export declare function applyScreenTips(root: ParentNode, style: ScreenTipStyle): void;
package/dist/visio.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { trackPointerPan, ownsCaptureLoss } from './chunk-GSXOZCAN.js';
2
- import { syncRibbonAddIns } from './chunk-UIVYZERK.js';
3
- export { RIBBON_ADD_IN_EVENT } from './chunk-UIVYZERK.js';
2
+ import { syncRibbonAddIns } from './chunk-VUSDTDZS.js';
3
+ export { RIBBON_ADD_IN_EVENT } from './chunk-VUSDTDZS.js';
4
4
  import { themeBridge, installOfficeUiTheme } from './chunk-7E3HU7RL.js';
5
- import { userInterfaceSection, personalizeSection, unavailableCategory, attachKeyTips, registerControls, readOfficeProfile, sanitizeOfficeProfile, writeOfficeProfile, attachRibbonGroupOverflow, commentInitials } from './chunk-CHFMXUPK.js';
5
+ import { personalizeSection, unavailableCategory, userInterfaceSection, attachKeyTips, registerControls, readOfficeProfile, sanitizeOfficeProfile, writeOfficeProfile, attachRibbonGroupOverflow, defineChartPicker, commentInitials } from './chunk-KRQ5T2GX.js';
6
6
  import { getIcon, registerIcon } from './chunk-M6GF5ED2.js';
7
7
  import { definePresence } from './chunk-KNC2UEUR.js';
8
8
  import './chunk-CWBSXK7P.js';
@@ -11,6 +11,7 @@ import { VISIO_TEXT_LANGUAGES, VISIO_BULLET_STYLES, FOREIGN_VECTOR_SCENE_LIMITS,
11
11
  export { TEXT_SEARCH_LIMITS, compatibilityNotes, compatibilityText, demoDocument, eventKeys, propertyKeys } from 'ooxml-core/visio/ui';
12
12
  import { VISIO_VISIBILITY_LIMITS, VISIO_BASIC_STENCIL, VISIO_STENCILS, visioOutlineShape, VISIO_SNAP, VISIO_GLUE, VISIO_SHADOW_PRESETS, loadVisio, createVsdx, deserializeVisioClipboard, getVisioPageLayers, resolveVisioPageVisibility, visioLineJumpPaths, visioSnapGlue, VISIO_CLIPBOARD_MAX_CHARS, VISIO_GLOW_TRANSPARENCY, VISIO_METADATA_TEXT_LIMIT, VISIO_PICTURE_MAX_BYTES, inspectVisioRasterImage, visioPageLayout, VISIO_BUILT_IN_THEMES, VISIO_CONNECTOR_ROUTES, visioFilledArrow, layoutVisioFilledArrowLine, visioOpenArrowPath, isVisioChangeShapeTarget, VISIO_CONTAINER_STYLES, VISIO_CALLOUT_STYLES, visioBuiltInStencil, visioFallbackQuickStyle, VISIO_GLOW_SIZES, VISIO_SOFT_EDGE_SIZES, VISIO_REFLECTION_PRESETS, VISIO_BACKGROUND_STYLES, visioBackgroundShapes, VISIO_BORDER_STYLES, visioBorderShapes, checkVisioAlternativeText, VISIO_SHAPE_DATA_TYPES, visioShapeDataRowName, VISIO_CLIPBOARD_MAGIC, visioThemeVariantColors, visioPageThemeColors, visioBuiltInMaster, VISIO_QUICK_STYLE_COLORS, isVisioBuiltInThemeId, readVisioStencil, VISIO_SCALE_UNITS } from 'ooxml-core/visio';
13
13
  import { normalizeLocale, interpolate } from 'ooxml-core/i18n';
14
+ import { INSERT_CHART_TYPES, GROUPED_CHART_TYPES, gridChartSvg, gridChart, parseChartGrid } from 'ooxml-core/xlsx/ui';
14
15
  import { pushRecentColor, normalizePaletteHex } from 'ooxml-core/color';
15
16
  import { resolveRotateHandlePlacement, createRotationDrag, snapAngle } from 'ooxml-core/geometry';
16
17
 
@@ -542,6 +543,7 @@ var ViewerController = class {
542
543
  #sourceGeneration = 0;
543
544
  #searchIndex = null;
544
545
  #history = null;
546
+ #undoLevels;
545
547
  #selectionHistory = /* @__PURE__ */ new WeakMap();
546
548
  #selectionIntent = 0;
547
549
  #viewIntent = 0;
@@ -792,7 +794,10 @@ var ViewerController = class {
792
794
  document2 = await this.parser(Uint8Array.from(owned));
793
795
  if (this.#destroyed || id !== this.#loadId) return;
794
796
  assertViewableDocument(document2);
795
- history = new DocumentHistory(owned);
797
+ history = new DocumentHistory(
798
+ owned,
799
+ this.#undoLevels === void 0 ? void 0 : this.#undoLevels + 1
800
+ );
796
801
  visible = documentVisibility(document2);
797
802
  } catch (cause) {
798
803
  if (this.#destroyed || id !== this.#loadId) return;
@@ -1084,6 +1089,16 @@ var ViewerController = class {
1084
1089
  return void 0;
1085
1090
  }
1086
1091
  }
1092
+ /** Number of undo steps kept (File > Options > Advanced); applies to the open drawing at once. */
1093
+ get undoLevels() {
1094
+ return this.#undoLevels;
1095
+ }
1096
+ set undoLevels(levels) {
1097
+ this.#undoLevels = levels;
1098
+ if (!this.#history || levels === void 0) return;
1099
+ this.#history.maxEntries = levels + 1;
1100
+ this.#change({ edit: this.#sourceEditState() });
1101
+ }
1087
1102
  async undo() {
1088
1103
  return this.#mutate("undo");
1089
1104
  }
@@ -1856,7 +1871,7 @@ var text_tool_default = ".viewport[data-tool='text'] {\n cursor: text;\n touch-a
1856
1871
  var presentation_default = "/* Visio Presentation Mode: the page fitted on a dark backdrop, chrome-free, read only. */\n.presentation {\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n display: grid;\n place-items: center;\n /* Visio presents on a dark backdrop in every theme. */\n background: #111;\n color: rgb(255 255 255 / 0.82);\n outline: none;\n cursor: default;\n}\n.presentation[hidden] {\n display: none;\n}\n.presentation:fullscreen {\n width: 100%;\n height: 100%;\n}\n.presentation-stage {\n position: absolute;\n inset: 2vmin;\n display: grid;\n place-items: center;\n container-type: size;\n}\n.presentation-stage > .paper {\n height: auto;\n max-width: 100%;\n max-height: 100%;\n margin: 0;\n box-shadow: none;\n}\n.presentation-bar {\n position: absolute;\n right: 12px;\n bottom: 12px;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 2px 4px;\n border-radius: 4px;\n background: rgb(0 0 0 / 0.45);\n font: 12px/1.4 var(--office-font-family, 'Segoe UI', system-ui, sans-serif);\n opacity: 0.35;\n transition: opacity 0.2s;\n}\n.presentation-bar:hover,\n.presentation-bar:focus-within {\n opacity: 1;\n}\n.presentation-bar button {\n display: inline-grid;\n place-items: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n.presentation-bar button:hover:not(:disabled) {\n background: rgb(255 255 255 / 0.16);\n}\n.presentation-bar button:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: -2px;\n}\n.presentation-bar button:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.presentation-page {\n min-width: 6em;\n max-width: 40vw;\n padding: 0 6px;\n overflow: hidden;\n text-align: center;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n@media (prefers-reduced-motion: reduce) {\n .presentation-bar {\n transition: none;\n }\n}\n";
1857
1872
 
1858
1873
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/insert.css?raw
1859
- var insert_default = ".link-dialog label,\n.alt-text-dialog label,\n.screen-tip-dialog label {\n display: grid;\n gap: 4px;\n min-width: min(360px, 80vw);\n margin: 0 0 12px;\n}\n.link-dialog input,\n.link-dialog select,\n.alt-text-dialog input,\n.alt-text-dialog textarea,\n.screen-tip-dialog input {\n box-sizing: border-box;\n width: 100%;\n padding: 6px;\n color: inherit;\n background: var(--_vv-surface);\n border: 1px solid var(--_vv-border);\n border-radius: 3px;\n font: inherit;\n}\n.alt-text-dialog textarea {\n resize: vertical;\n min-height: 90px;\n}\n.accessibility-dialog [aria-live] {\n max-height: 50vh;\n overflow: auto;\n}\n.accessibility-dialog [aria-live] > button {\n display: block;\n width: 100%;\n padding: 8px;\n text-align: left;\n color: inherit;\n background: var(--_vv-surface);\n border: 1px solid var(--_vv-border);\n font: inherit;\n cursor: pointer;\n}\n.link-dialog [role='alert'],\n.alt-text-dialog [role='alert'],\n.screen-tip-dialog [role='alert'] {\n margin: 0;\n color: var(--office-error, #b00020);\n}\n";
1874
+ var insert_default = ".link-dialog label,\n.alt-text-dialog label,\n.screen-tip-dialog label {\n display: grid;\n gap: 4px;\n min-width: min(360px, 80vw);\n margin: 0 0 12px;\n}\n.link-dialog input,\n.link-dialog select,\n.alt-text-dialog input,\n.alt-text-dialog textarea,\n.screen-tip-dialog input {\n box-sizing: border-box;\n width: 100%;\n padding: 6px;\n color: inherit;\n background: var(--_vv-surface);\n border: 1px solid var(--_vv-border);\n border-radius: 3px;\n font: inherit;\n}\n.alt-text-dialog textarea {\n resize: vertical;\n min-height: 90px;\n}\n.accessibility-dialog [aria-live] {\n max-height: 50vh;\n overflow: auto;\n}\n.accessibility-dialog [aria-live] > button {\n display: block;\n width: 100%;\n padding: 8px;\n text-align: left;\n color: inherit;\n background: var(--_vv-surface);\n border: 1px solid var(--_vv-border);\n font: inherit;\n cursor: pointer;\n}\n.link-dialog [role='alert'],\n.alt-text-dialog [role='alert'],\n.screen-tip-dialog [role='alert'] {\n margin: 0;\n color: var(--office-error, #b00020);\n}\n\n.chart-dialog {\n --office-dialog-width: min(640px, 94vw);\n --office-dialog-max-width: min(640px, 94vw);\n}\n.chart-dialog [role='alert'] {\n margin: 8px 0 0;\n color: var(--_vv-danger);\n}\n.chart-dialog [role='alert']:empty {\n display: none;\n}\n";
1860
1875
 
1861
1876
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/visio/styles/page-setup.css?raw
1862
1877
  var page_setup_default = ".page-setup-dialog .page-setup-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.page-setup-dialog [role='tab'] {\n padding: 6px 10px;\n color: inherit;\n font: inherit;\n background: transparent;\n border: 1px solid transparent;\n border-bottom: none;\n border-radius: 3px 3px 0 0;\n cursor: pointer;\n}\n.page-setup-dialog [role='tab'][aria-selected='true'] {\n background: var(--_vv-surface);\n border-color: var(--_vv-border);\n font-weight: 600;\n}\n.page-setup-dialog [role='tabpanel'] {\n display: grid;\n gap: 10px;\n min-width: min(420px, 80vw);\n}\n.page-setup-dialog [role='tabpanel'][hidden] {\n display: none;\n}\n.page-setup-dialog label {\n display: grid;\n grid-template-columns: minmax(130px, 1fr) minmax(150px, 1.4fr);\n align-items: center;\n gap: 12px;\n}\n.page-setup-dialog input {\n box-sizing: border-box;\n width: 100%;\n padding: 6px;\n color: inherit;\n background: var(--_vv-surface);\n border: 1px solid var(--_vv-border);\n border-radius: 3px;\n}\n.page-setup-dialog .page-setup-hint {\n max-width: 440px;\n margin: 0;\n font-size: 12px;\n}\n.page-setup-dialog [role='alert'] {\n color: var(--office-error, #b00020);\n}\n.paper .page-break {\n fill: none;\n stroke: #7a7a7a;\n stroke-width: 1.25;\n stroke-dasharray: 6 4;\n vector-effect: non-scaling-stroke;\n}\n@media (max-width: 480px) {\n .page-setup-dialog label {\n grid-template-columns: 1fr;\n gap: 5px;\n }\n}\n";
@@ -2113,6 +2128,8 @@ var visioThemeBridge = themeBridge(":host > *", {
2113
2128
  "--office-surface": "var(--_vv-accent-soft)",
2114
2129
  "--office-border": "var(--_vv-border)",
2115
2130
  "--office-accent": "var(--_vv-accent)",
2131
+ // Hover and selected rows: an opaque accent tint over the page, not the shared cool grey.
2132
+ "--office-selected": "color-mix(in srgb, var(--_vv-accent) 12%, var(--_vv-surface))",
2116
2133
  "--office-accent-foreground": "var(--_vv-accent-ink)",
2117
2134
  "--office-ring": "var(--_vv-focus)",
2118
2135
  "--office-font": "'Segoe UI', Arial, sans-serif"
@@ -5902,10 +5919,12 @@ function buildInsertPanel(doc, panel) {
5902
5919
  icon: "visioPicture",
5903
5920
  action: { type: "insert", item: "picture" }
5904
5921
  }),
5905
- command(
5906
- doc,
5907
- unsupported("chart", "Chart", "visioChart", "Embedded Excel charts are not supported.")
5908
- ),
5922
+ command(doc, {
5923
+ id: "chart",
5924
+ label: "Chart",
5925
+ icon: "visioChart",
5926
+ action: { type: "insert", item: "chart" }
5927
+ }),
5909
5928
  command(
5910
5929
  doc,
5911
5930
  unsupported(
@@ -8428,6 +8447,28 @@ function localizeRibbon(ribbon, value) {
8428
8447
  return locale;
8429
8448
  }
8430
8449
 
8450
+ // src/visio/viewer-screen-tips.ts
8451
+ var isScreenTipStyle = (value) => value === "descriptions" || value === "plain" || value === "off";
8452
+ var FULL = "data-screen-tip";
8453
+ var isReason = (tip) => tip.includes(": not available yet.");
8454
+ function restoreScreenTips(root) {
8455
+ for (const element of root.querySelectorAll(`[${FULL}]`)) {
8456
+ element.setAttribute("title", element.getAttribute(FULL));
8457
+ element.removeAttribute(FULL);
8458
+ }
8459
+ }
8460
+ function applyScreenTips(root, style2) {
8461
+ restoreScreenTips(root);
8462
+ if (style2 === "descriptions") return;
8463
+ for (const element of root.querySelectorAll("[title]")) {
8464
+ const tip = element.getAttribute("title");
8465
+ if (isReason(tip)) continue;
8466
+ element.setAttribute(FULL, tip);
8467
+ if (style2 === "off") element.removeAttribute("title");
8468
+ else element.setAttribute("title", tip.replace(/ \([^()]*\)$/, ""));
8469
+ }
8470
+ }
8471
+
8431
8472
  // src/visio/ribbon-overflow.ts
8432
8473
  var GROUP_ICONS = {
8433
8474
  Clipboard: "paste",
@@ -10856,14 +10897,34 @@ var ViewerPanZoom = class {
10856
10897
 
10857
10898
  // src/visio/viewer-options-categories.ts
10858
10899
  var visio = { product: "Visio", t: (text2) => text2 };
10900
+ var MAX_UNDO_LEVELS = 19;
10859
10901
  var disabled = (reason) => ({ disabled: `Not available yet. ${reason}` });
10902
+ function visioUserInterface() {
10903
+ const section2 = userInterfaceSection(visio);
10904
+ return {
10905
+ ...section2,
10906
+ controls: section2.controls.map(
10907
+ (control) => control.key === "screenTips" ? {
10908
+ kind: "select",
10909
+ key: "screenTips",
10910
+ label: control.label,
10911
+ info: "Applies to the ribbon. Commands that are not available always say why.",
10912
+ choices: [
10913
+ { value: "descriptions", label: "Show ScreenTips with shortcut keys" },
10914
+ { value: "plain", label: "Don't show shortcut keys in ScreenTips" },
10915
+ { value: "off", label: "Don't show ScreenTips" }
10916
+ ]
10917
+ } : control
10918
+ )
10919
+ };
10920
+ }
10860
10921
  var VISIO_OPTION_CATEGORIES = [
10861
10922
  {
10862
10923
  id: "general",
10863
10924
  label: "General",
10864
10925
  description: "General options for working with Visio.",
10865
10926
  sections: [
10866
- userInterfaceSection(visio),
10927
+ visioUserInterface(),
10867
10928
  personalizeSection(
10868
10929
  visio,
10869
10930
  [
@@ -10909,8 +10970,8 @@ var VISIO_OPTION_CATEGORIES = [
10909
10970
  key: "undoLevels",
10910
10971
  label: "Number of undo levels",
10911
10972
  min: 1,
10912
- max: 99,
10913
- ...disabled("The history depth is fixed.")
10973
+ max: MAX_UNDO_LEVELS,
10974
+ info: "Each step keeps a copy of the drawing, so very large drawings may keep fewer."
10914
10975
  },
10915
10976
  {
10916
10977
  kind: "toggle",
@@ -10948,13 +11009,39 @@ function writeStatusBarTheme(theme) {
10948
11009
  }
10949
11010
  }
10950
11011
  var isOfficeThemeChoice = (value) => value === "dark" || isStatusBarTheme(value);
10951
- function profileValues(profile, officeTheme) {
11012
+ var DEFAULT_BEHAVIOUR = {
11013
+ screenTips: "descriptions",
11014
+ undoLevels: MAX_UNDO_LEVELS
11015
+ };
11016
+ var BEHAVIOUR_KEY = "ooxml-visio-options";
11017
+ var clampUndo = (value) => {
11018
+ const levels = Math.round(Number(value));
11019
+ return Number.isFinite(levels) ? Math.min(MAX_UNDO_LEVELS, Math.max(1, levels)) : DEFAULT_BEHAVIOUR.undoLevels;
11020
+ };
11021
+ function readBehaviour() {
11022
+ try {
11023
+ const stored = JSON.parse(globalThis.localStorage?.getItem(BEHAVIOUR_KEY) ?? "{}");
11024
+ return {
11025
+ screenTips: isScreenTipStyle(stored.screenTips) ? stored.screenTips : DEFAULT_BEHAVIOUR.screenTips,
11026
+ undoLevels: clampUndo(stored.undoLevels ?? DEFAULT_BEHAVIOUR.undoLevels)
11027
+ };
11028
+ } catch {
11029
+ return { ...DEFAULT_BEHAVIOUR };
11030
+ }
11031
+ }
11032
+ function writeBehaviour(behaviour) {
11033
+ try {
11034
+ globalThis.localStorage?.setItem(BEHAVIOUR_KEY, JSON.stringify(behaviour));
11035
+ } catch {
11036
+ }
11037
+ }
11038
+ function profileValues(profile, officeTheme, behaviour) {
10952
11039
  return {
10953
11040
  officeTheme,
10954
11041
  miniToolbar: false,
10955
11042
  livePreview: false,
10956
- screenTips: "descriptions",
10957
- undoLevels: 20,
11043
+ screenTips: behaviour.screenTips,
11044
+ undoLevels: behaviour.undoLevels,
10958
11045
  autoSizeText: false,
10959
11046
  userName: profile.displayName,
10960
11047
  userInitials: profile.initial ?? ""
@@ -10964,7 +11051,12 @@ function createOptionsDialog(doc) {
10964
11051
  const dialog = doc.createElement("office-ui-options-dialog");
10965
11052
  dialog.setAttribute("heading", "Visio Options");
10966
11053
  dialog.categories = VISIO_OPTION_CATEGORIES;
10967
- dialog.defaults = { userName: "", userInitials: "", officeTheme: "neutral" };
11054
+ dialog.defaults = {
11055
+ userName: "",
11056
+ userInitials: "",
11057
+ officeTheme: "neutral",
11058
+ ...DEFAULT_BEHAVIOUR
11059
+ };
10968
11060
  return dialog;
10969
11061
  }
10970
11062
  var ViewerProfile = class {
@@ -10973,16 +11065,21 @@ var ViewerProfile = class {
10973
11065
  get: () => "neutral",
10974
11066
  set: () => {
10975
11067
  }
11068
+ }, onBehaviour = () => {
10976
11069
  }) {
10977
11070
  this.onProfile = onProfile;
10978
11071
  this.theme = theme;
11072
+ this.onBehaviour = onBehaviour;
10979
11073
  this.#dialog = root.querySelector("office-ui-options-dialog");
10980
11074
  this.#account = root.querySelector("office-ui-account");
10981
11075
  this.#sync();
11076
+ this.onBehaviour({ ...this.#behaviour });
10982
11077
  }
10983
11078
  onProfile;
10984
11079
  theme;
11080
+ onBehaviour;
10985
11081
  #profile = readOfficeProfile();
11082
+ #behaviour = readBehaviour();
10986
11083
  #dialog;
10987
11084
  #account;
10988
11085
  get profile() {
@@ -10990,7 +11087,7 @@ var ViewerProfile = class {
10990
11087
  }
10991
11088
  /** Open Visio Options, on General as Visio does. */
10992
11089
  showOptions(category = "general") {
10993
- this.#dialog.values = profileValues(this.#profile, this.theme.get());
11090
+ this.#dialog.values = profileValues(this.#profile, this.theme.get(), this.#behaviour);
10994
11091
  this.#dialog.category = category;
10995
11092
  this.#dialog.show();
10996
11093
  }
@@ -11006,6 +11103,13 @@ var ViewerProfile = class {
11006
11103
  this.theme.set(values.officeTheme);
11007
11104
  if (values.officeTheme !== "dark") writeStatusBarTheme(values.officeTheme);
11008
11105
  }
11106
+ const screenTips = isScreenTipStyle(values.screenTips) ? values.screenTips : this.#behaviour.screenTips;
11107
+ const undoLevels = clampUndo(values.undoLevels ?? this.#behaviour.undoLevels);
11108
+ if (screenTips !== this.#behaviour.screenTips || undoLevels !== this.#behaviour.undoLevels) {
11109
+ this.#behaviour = { screenTips, undoLevels };
11110
+ writeBehaviour(this.#behaviour);
11111
+ this.onBehaviour({ ...this.#behaviour });
11112
+ }
11009
11113
  const initial = String(values.userInitials ?? "").trim();
11010
11114
  this.#update({
11011
11115
  displayName: String(values.userName ?? "").trim(),
@@ -11030,7 +11134,7 @@ var ViewerProfile = class {
11030
11134
  }
11031
11135
  #sync() {
11032
11136
  if (this.#account) this.#account.profile = this.#profile;
11033
- this.#dialog.values = profileValues(this.#profile, this.theme.get());
11137
+ this.#dialog.values = profileValues(this.#profile, this.theme.get(), this.#behaviour);
11034
11138
  }
11035
11139
  };
11036
11140
 
@@ -13871,6 +13975,121 @@ var InsertDialog = class {
13871
13975
  for (const button of this.buttons.values()) button.disabled = value;
13872
13976
  }
13873
13977
  };
13978
+ var CHART_SIZE = { width: 640, height: 400 };
13979
+ var SCALE = 2;
13980
+ var SAMPLE = "Category Series 1 Series 2\nA 4 2\nB 3 5\nC 6 4\nD 2 6";
13981
+ var GROUPINGS = [
13982
+ { id: "clustered", label: "Clustered" },
13983
+ { id: "stacked", label: "Stacked" },
13984
+ { id: "percentStacked", label: "100% Stacked" }
13985
+ ];
13986
+ var rasterizeSvg = async (doc, svg2, width, height) => {
13987
+ const win = doc.defaultView;
13988
+ if (!win) throw new Error("There is no window to draw the chart in.");
13989
+ const url = win.URL.createObjectURL(new win.Blob([svg2], { type: "image/svg+xml" }));
13990
+ try {
13991
+ const image = new win.Image();
13992
+ image.src = url;
13993
+ await image.decode();
13994
+ const canvas2 = doc.createElement("canvas");
13995
+ canvas2.width = width * SCALE;
13996
+ canvas2.height = height * SCALE;
13997
+ const context = canvas2.getContext("2d");
13998
+ if (!context) throw new Error("This browser cannot draw the chart.");
13999
+ context.fillStyle = "#ffffff";
14000
+ context.fillRect(0, 0, canvas2.width, canvas2.height);
14001
+ context.drawImage(image, 0, 0, canvas2.width, canvas2.height);
14002
+ const blob2 = await new Promise((resolve) => canvas2.toBlob(resolve, "image/png"));
14003
+ if (!blob2) throw new Error("The chart could not be saved as a picture.");
14004
+ return new Uint8Array(await blob2.arrayBuffer());
14005
+ } finally {
14006
+ win.URL.revokeObjectURL(url);
14007
+ }
14008
+ };
14009
+ var InsertChartDialog = class {
14010
+ dialog;
14011
+ picker;
14012
+ error;
14013
+ #insert;
14014
+ #rasterize;
14015
+ #busy = false;
14016
+ constructor(root, insert, rasterize = rasterizeSvg) {
14017
+ this.#insert = insert;
14018
+ this.#rasterize = rasterize;
14019
+ const doc = root.ownerDocument;
14020
+ defineChartPicker(doc.defaultView?.customElements);
14021
+ this.dialog = doc.createElement("office-ui-dialog");
14022
+ this.dialog.className = "chart-dialog";
14023
+ this.dialog.setAttribute("heading", "Insert Chart");
14024
+ this.picker = doc.createElement("office-ui-chart-picker");
14025
+ this.picker.types = INSERT_CHART_TYPES.map(([id, label]) => ({ id, label }));
14026
+ this.picker.groupings = GROUPINGS;
14027
+ this.picker.groupedTypes = [...GROUPED_CHART_TYPES];
14028
+ this.picker.drawPreview = (value, size, tile) => {
14029
+ const grid = this.#grid(value);
14030
+ return grid ? gridChartSvg(grid, size.width, size.height, tile) : "";
14031
+ };
14032
+ this.error = doc.createElement("p");
14033
+ this.error.setAttribute("role", "alert");
14034
+ this.dialog.append(this.picker, this.error);
14035
+ const buttons = /* @__PURE__ */ new Map();
14036
+ for (const name of ["Cancel", "Insert"]) {
14037
+ const button = doc.createElement("office-ui-button");
14038
+ button.slot = "footer";
14039
+ button.setAttribute("label", name);
14040
+ button.setAttribute("command", `chart-dialog-${name.toLowerCase()}`);
14041
+ button.addEventListener("office-command", () => {
14042
+ if (button.disabled) return;
14043
+ if (name === "Cancel") this.dialog.close();
14044
+ else void this.#submit(buttons);
14045
+ });
14046
+ buttons.set(name, button);
14047
+ this.dialog.append(button);
14048
+ }
14049
+ root.append(this.dialog);
14050
+ }
14051
+ #grid(value) {
14052
+ return gridChart(parseChartGrid(value.data ?? ""), value.type, {
14053
+ grouping: value.grouping,
14054
+ ...value.title.trim() ? { title: value.title.trim() } : {}
14055
+ });
14056
+ }
14057
+ /** Open with the sample data, ready to be replaced. */
14058
+ open() {
14059
+ this.error.textContent = "";
14060
+ this.picker.value = { type: "column", grouping: "clustered", title: "", data: SAMPLE };
14061
+ this.dialog.show();
14062
+ }
14063
+ close() {
14064
+ this.dialog.close();
14065
+ }
14066
+ async #submit(buttons) {
14067
+ if (this.#busy) return;
14068
+ const grid = this.#grid(this.picker.value);
14069
+ if (!grid || grid.chart.series.length === 0) {
14070
+ this.error.textContent = "Enter a header row and at least one row of numbers, separated by tabs or commas.";
14071
+ return;
14072
+ }
14073
+ this.#busy = true;
14074
+ for (const button of buttons.values()) button.disabled = true;
14075
+ try {
14076
+ const svg2 = gridChartSvg(grid, CHART_SIZE.width, CHART_SIZE.height);
14077
+ const png = await this.#rasterize(
14078
+ this.dialog.ownerDocument,
14079
+ svg2,
14080
+ CHART_SIZE.width,
14081
+ CHART_SIZE.height
14082
+ );
14083
+ await this.#insert(png);
14084
+ this.dialog.close();
14085
+ } catch (error) {
14086
+ this.error.textContent = error instanceof Error ? error.message : "The chart was not inserted.";
14087
+ } finally {
14088
+ this.#busy = false;
14089
+ for (const button of buttons.values()) button.disabled = false;
14090
+ }
14091
+ }
14092
+ };
13874
14093
 
13875
14094
  // src/visio/viewer-insert.ts
13876
14095
  var ViewerInsert = class {
@@ -13903,6 +14122,7 @@ var ViewerInsert = class {
13903
14122
  ["OK", "Remove Link", "Cancel"],
13904
14123
  (button) => void this.#applyLink(button)
13905
14124
  );
14125
+ this.chart = new InsertChartDialog(root, (png) => this.#insertChart(png));
13906
14126
  this.tip = new InsertDialog(
13907
14127
  root,
13908
14128
  "screen-tip-dialog",
@@ -13919,6 +14139,7 @@ var ViewerInsert = class {
13919
14139
  input;
13920
14140
  link;
13921
14141
  tip;
14142
+ chart;
13922
14143
  #target;
13923
14144
  #editable(state) {
13924
14145
  return state.edit.sourceAvailable && !state.loading && !state.edit.busy;
@@ -13936,6 +14157,10 @@ var ViewerInsert = class {
13936
14157
  if (state.document?.pages[state.pageIndex]) this.input.click();
13937
14158
  return;
13938
14159
  }
14160
+ if (item === "chart") {
14161
+ if (state.document?.pages[state.pageIndex]) this.chart.open();
14162
+ return;
14163
+ }
13939
14164
  const shape = this.#shape(state);
13940
14165
  const page3 = state.document?.pages[state.pageIndex];
13941
14166
  if (!shape || !page3) {
@@ -13967,6 +14192,28 @@ var ViewerInsert = class {
13967
14192
  close() {
13968
14193
  this.link.dialog.close();
13969
14194
  this.tip.dialog.close();
14195
+ this.chart.close();
14196
+ }
14197
+ /** Place the chart dialog's PNG on the page; rejects (so the dialog can say why) when refused. */
14198
+ async #insertChart(png) {
14199
+ const state = this.controller.state;
14200
+ const page3 = state.document?.pages[state.pageIndex];
14201
+ if (!page3 || !this.#editable(state)) throw new Error("Open a .vsdx file to insert a chart.");
14202
+ if (png.byteLength > VISIO_PICTURE_MAX_BYTES)
14203
+ throw new Error("The chart is larger than the 8 MB picture limit.");
14204
+ const token = this.controller.captureCreationToken(page3.id);
14205
+ const command2 = visioPictureInsertCommand(page3, inspectVisioRasterImage(png), png);
14206
+ await new Promise(
14207
+ (resolve, reject) => this.run(async () => {
14208
+ try {
14209
+ await this.controller.applyCreationEdits([command2], token);
14210
+ resolve();
14211
+ } catch (error) {
14212
+ reject(error);
14213
+ throw error;
14214
+ }
14215
+ }, "Inserted chart.")
14216
+ );
13970
14217
  }
13971
14218
  async #picture(file) {
13972
14219
  const state = this.controller.state;
@@ -14090,6 +14337,11 @@ var ViewerInsert = class {
14090
14337
  pictures.disabled = !editing || !page3;
14091
14338
  pictures.title = editing ? "Insert a PNG, JPEG or GIF picture from this device (up to 8 MB)." : "Open a .vsdx file to insert pictures.";
14092
14339
  }
14340
+ const chart = button("chart");
14341
+ if (chart) {
14342
+ chart.disabled = !editing || !page3;
14343
+ chart.title = editing ? "Insert a chart drawn from data you type or paste. It is placed as a picture." : "Open a .vsdx file to insert a chart.";
14344
+ }
14093
14345
  for (const [name, label] of [
14094
14346
  ["link", "Link (Ctrl+K)"],
14095
14347
  ["ctx-hyperlink", "Hyperlink (Ctrl+K)"],
@@ -14100,6 +14352,8 @@ var ViewerInsert = class {
14100
14352
  control.disabled = !shape;
14101
14353
  control.title = shape ? label : `${label}: select one shape on an editable drawing.`;
14102
14354
  }
14355
+ if (this.chart.dialog.open && (!state.edit.sourceAvailable || state.loading))
14356
+ this.chart.close();
14103
14357
  for (const dialog of [this.link, this.tip]) {
14104
14358
  if (!dialog.dialog.open) continue;
14105
14359
  if (!state.edit.sourceAvailable || state.loading || this.controller.documentGeneration !== this.#target?.generation)
@@ -22860,6 +23114,7 @@ var VisioViewerElement = class extends BaseElement {
22860
23114
  #unsubscribe;
22861
23115
  #eventUnsubscribe;
22862
23116
  #disposed = false;
23117
+ #screenTips = "descriptions";
22863
23118
  #suspended = false;
22864
23119
  #fontEvents;
22865
23120
  #fontsChanged = () => {
@@ -22952,14 +23207,23 @@ var VisioViewerElement = class extends BaseElement {
22952
23207
  }
22953
23208
  });
22954
23209
  const profileChanged = () => renderTitleBar(this.#titleBar, this.controller.state, this.#fileName, this.#profile.profile);
22955
- this.#profile = new ViewerProfile(this.#root, profileChanged, {
22956
- get: () => this.#dark() ? "dark" : this.statusBar,
22957
- set: (theme) => {
22958
- if (theme !== "dark") this.statusBar = theme;
22959
- if (theme === "dark" !== this.#dark())
22960
- this.#requestScheme(theme === "dark" ? "dark" : "light");
23210
+ this.#profile = new ViewerProfile(
23211
+ this.#root,
23212
+ profileChanged,
23213
+ {
23214
+ get: () => this.#dark() ? "dark" : this.statusBar,
23215
+ set: (theme) => {
23216
+ if (theme !== "dark") this.statusBar = theme;
23217
+ if (theme === "dark" !== this.#dark())
23218
+ this.#requestScheme(theme === "dark" ? "dark" : "light");
23219
+ }
23220
+ },
23221
+ (behaviour) => {
23222
+ this.#screenTips = behaviour.screenTips;
23223
+ applyScreenTips(this.#toolbar, this.#screenTips);
23224
+ this.controller.undoLevels = behaviour.undoLevels;
22961
23225
  }
22962
- });
23226
+ );
22963
23227
  this.#pointer = new ViewerPointerGestures(this.#viewport, this.controller, {
22964
23228
  active: () => this.#commands.tool === "pointer",
22965
23229
  announce: (message) => {
@@ -23291,10 +23555,12 @@ var VisioViewerElement = class extends BaseElement {
23291
23555
  /** The ribbon speaks the `locale` attribute, else `lang`, else the page's, else the browser's. */
23292
23556
  #localizeRibbon() {
23293
23557
  if (this.#disposed || !this.#overflow) return;
23558
+ restoreScreenTips(this.#toolbar);
23294
23559
  localizeRibbon(
23295
23560
  this.#toolbar,
23296
23561
  this.getAttribute("locale") || this.lang || this.ownerDocument.documentElement.lang || navigator.language
23297
23562
  );
23563
+ applyScreenTips(this.#toolbar, this.#screenTips);
23298
23564
  this.#overflow.refit();
23299
23565
  }
23300
23566
  connectedCallback() {
@@ -1,6 +1,7 @@
1
1
  import { type ChartType } from 'ooxml-core/xlsx';
2
2
  import type { Command } from 'ooxml-core/xlsx/ui';
3
3
  import type { EditorContext } from 'ooxml-core/xlsx/ui';
4
+ /** The chart types of Insert Chart (shared with other products) with the ribbon glyph of each. */
4
5
  export declare const CHART_TYPES: ReadonlyArray<readonly [type: ChartType, label: string, glyph: string]>;
5
6
  /** Inserts a picture file at the active cell (scaled to at most 640 px wide, like Excel's fit). */
6
7
  export declare function insertPicture(ctx: EditorContext, file: File): Promise<void>;