visio-react-viewer 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +593 -351
- package/dist/types/src/backstage.d.ts +7 -1
- package/dist/types/src/contract.d.ts +1 -1
- package/dist/types/src/controller.d.ts +5 -0
- package/dist/types/src/ribbon.d.ts +3 -2
- package/dist/types/src/styles.d.ts +1 -1
- package/dist/types/src/viewer-backstage.d.ts +2 -5
- package/dist/types/src/viewer-input.d.ts +0 -2
- package/dist/types/src/viewer-options.d.ts +22 -0
- package/dist/types/src/viewer-ruler.d.ts +6 -0
- package/dist/types/src/viewer-search.d.ts +16 -16
- package/dist/types/src/viewer-share.d.ts +20 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { VISIO_RASTER_IMAGE_LIMITS, VISIO_VISIBILITY_LIMITS, getVisioPageLayers, inspectVisioRasterImage, loadVisio, resolveVisioPageVisibility, validateVisioForeignVector } from "ooxml-core/visio";
|
|
2
|
-
import { attachKeyTips, registerControls } from "ooxml-ui/controls";
|
|
2
|
+
import { attachKeyTips, readOfficeProfile, registerControls, sanitizeOfficeProfile, writeOfficeProfile } from "ooxml-ui/controls";
|
|
3
3
|
import { getIcon, registerIcon } from "ooxml-ui/icons";
|
|
4
|
+
import { definePresence } from "ooxml-ui/presence";
|
|
4
5
|
import { createElement, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useSyncExternalStore } from "react";
|
|
5
6
|
export * from "visio-core";
|
|
6
7
|
//#region src/document-history.ts
|
|
@@ -1072,6 +1073,13 @@ var ViewerController = class {
|
|
|
1072
1073
|
async undo() {
|
|
1073
1074
|
return this.#mutate("undo");
|
|
1074
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Adopt a package from a shared session as one undoable history step. Emits `document-change`
|
|
1078
|
+
* with kind `remote`, which a session must not publish back.
|
|
1079
|
+
*/
|
|
1080
|
+
async applyRemoteSource(bytes) {
|
|
1081
|
+
return this.#mutate("remote", void 0, bytes);
|
|
1082
|
+
}
|
|
1075
1083
|
async redo() {
|
|
1076
1084
|
return this.#mutate("redo");
|
|
1077
1085
|
}
|
|
@@ -1095,13 +1103,13 @@ var ViewerController = class {
|
|
|
1095
1103
|
#sourceEditState() {
|
|
1096
1104
|
return this.#sourceFormat === "vsdx" && this.#state.document?.format === "vsdx" ? this.#history?.state ?? EMPTY_EDIT_STATE : EMPTY_EDIT_STATE;
|
|
1097
1105
|
}
|
|
1098
|
-
async #mutate(kind, commands) {
|
|
1106
|
+
async #mutate(kind, commands, remote) {
|
|
1099
1107
|
this.#assertAlive();
|
|
1100
1108
|
const history = this.#history;
|
|
1101
1109
|
if (!history || this.#sourceFormat !== "vsdx" || this.#state.document?.format !== "vsdx") throw new Error("Load a VSDX file before editing. Model-only documents are read only.");
|
|
1102
1110
|
if (this.#state.loading || this.#state.edit.busy) throw new Error("Another document operation is in progress.");
|
|
1103
1111
|
const target = kind === "undo" ? history.undoTarget : kind === "redo" ? history.redoTarget : void 0;
|
|
1104
|
-
if (kind
|
|
1112
|
+
if ((kind === "undo" || kind === "redo") && !target) return;
|
|
1105
1113
|
const id = ++this.#editId;
|
|
1106
1114
|
const loadId = this.#loadId;
|
|
1107
1115
|
const current = () => !this.#destroyed && id === this.#editId && loadId === this.#loadId && history === this.#history;
|
|
@@ -1121,6 +1129,9 @@ var ViewerController = class {
|
|
|
1121
1129
|
assertCurrent();
|
|
1122
1130
|
if (edited.bytes.byteLength > 33554432) throw new Error("The modified VSDX exceeds 32 MiB.");
|
|
1123
1131
|
document = edited.document;
|
|
1132
|
+
} else if (kind === "remote") {
|
|
1133
|
+
if (remote.byteLength > 33554432) throw new Error("The shared VSDX exceeds 32 MiB.");
|
|
1134
|
+
document = await this.parser(Uint8Array.from(remote));
|
|
1124
1135
|
} else document = await this.parser(Uint8Array.from(target.bytes));
|
|
1125
1136
|
assertCurrent();
|
|
1126
1137
|
assertViewableDocument(document);
|
|
@@ -1135,6 +1146,7 @@ var ViewerController = class {
|
|
|
1135
1146
|
const selection = this.#state.selectedShape;
|
|
1136
1147
|
const selectedShape = selection && visibleSelection(document, this.#state.pageIndex, selection, visible) ? selection : null;
|
|
1137
1148
|
if (edited) history.append(edited.bytes, edited.diagnostics);
|
|
1149
|
+
else if (kind === "remote") history.append(Uint8Array.from(remote), []);
|
|
1138
1150
|
else history.move(target);
|
|
1139
1151
|
this.#visible = visible;
|
|
1140
1152
|
this.#searchIndex = searchIndex;
|
|
@@ -1373,28 +1385,10 @@ function selection(target) {
|
|
|
1373
1385
|
}
|
|
1374
1386
|
/** Own every DOM listener and abort them together when the surface is disposed. */
|
|
1375
1387
|
function wireViewerInputs(controls, controller, fit) {
|
|
1376
|
-
const { viewport,
|
|
1388
|
+
const { viewport, zoomSlider } = controls;
|
|
1377
1389
|
const events = new ((viewport.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
1378
1390
|
const options = { signal: events.signal };
|
|
1379
|
-
searchInput.addEventListener("input", () => controller.setSearchQuery(searchInput.value), options);
|
|
1380
|
-
searchInput.addEventListener("keydown", (event) => {
|
|
1381
|
-
if (event.isComposing) return;
|
|
1382
|
-
if (event.key === "Enter") {
|
|
1383
|
-
event.preventDefault();
|
|
1384
|
-
if (event.shiftKey) controller.previousSearchResult();
|
|
1385
|
-
else controller.nextSearchResult();
|
|
1386
|
-
}
|
|
1387
|
-
if (event.key === "Escape") {
|
|
1388
|
-
event.preventDefault();
|
|
1389
|
-
controller.setSearchQuery("");
|
|
1390
|
-
}
|
|
1391
|
-
}, options);
|
|
1392
1391
|
zoomSlider.addEventListener("input", () => controller.setZoom(zoomSlider.value / 100), options);
|
|
1393
|
-
commandRoot.addEventListener("click", (event) => {
|
|
1394
|
-
const action = event.target?.closest?.("button")?.dataset.action;
|
|
1395
|
-
if (action === "search-next") controller.nextSearchResult();
|
|
1396
|
-
if (action === "search-previous") controller.previousSearchResult();
|
|
1397
|
-
}, options);
|
|
1398
1392
|
viewport.addEventListener("click", (event) => controller.selectShape(selection(targetShape$1(event))), options);
|
|
1399
1393
|
viewport.addEventListener("keydown", (event) => {
|
|
1400
1394
|
const target = targetShape$1(event);
|
|
@@ -1535,22 +1529,12 @@ button:focus-visible,select:focus-visible,input:focus-visible,textarea:focus-vis
|
|
|
1535
1529
|
button[aria-pressed="true"] { color:var(--_vv-accent); background:var(--_vv-accent-soft); }
|
|
1536
1530
|
.toolbar { flex:none; min-width:0; border-bottom:1px solid var(--_vv-border); background:var(--_vv-surface); }
|
|
1537
1531
|
.spacer { flex:1; }
|
|
1538
|
-
.ribbon-tabs { height:35px; display:flex; align-items:stretch; padding:0 4px; border-bottom:1px solid var(--_vv-border); }
|
|
1539
|
-
.ribbon-tabs button { position:relative; min-height:34px; padding:7px 14px; border:0; border-radius:0; background:transparent; color:var(--_vv-muted); font-size:12px; font-weight:500; }
|
|
1540
|
-
.ribbon-tabs button[aria-selected="true"] { color:var(--_vv-accent); }
|
|
1541
|
-
.ribbon-tabs button[aria-selected="true"]::after { content:""; position:absolute; inset:auto 0 -1px; height:2.5px; background:var(--_vv-accent); }
|
|
1542
1532
|
.ribbon-content { min-height:82px; display:flex; align-items:stretch; gap:0; padding:2px 4px; overflow-x:auto; }
|
|
1543
1533
|
.ribbon-group { display:flex; flex-direction:column; justify-content:space-between; flex:none; gap:2px; padding:3px 10px 4px; border-right:1px solid var(--_vv-border); }
|
|
1544
1534
|
.group-label { font-size:10px; line-height:12px; color:var(--_vv-muted); text-align:center; }
|
|
1545
1535
|
.ribbon-actions { display:flex; align-items:stretch; gap:2px; flex:1; }
|
|
1546
1536
|
.ribbon-command { display:flex; flex-direction:column; justify-content:center; align-items:center; gap:2px; min-width:70px; padding:2px 6px; border-color:transparent; background:transparent; font-size:11px; white-space:nowrap; }
|
|
1547
1537
|
.ribbon-hint { align-self:center; flex:none; margin:0; padding:0 16px; font-size:11px; line-height:18px; color:var(--_vv-muted); }
|
|
1548
|
-
.search-group { width:330px; }
|
|
1549
|
-
.search-controls { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
|
|
1550
|
-
.search-controls label { display:flex; align-items:center; min-width:0; gap:6px; flex:1; font-size:11px; }
|
|
1551
|
-
.search-controls input { width:100%; min-width:0; padding:3px 6px; font-size:11px; }
|
|
1552
|
-
.search-controls button { padding:3px 6px; font-size:10px; background:var(--_vv-secondary); }
|
|
1553
|
-
.search-controls [role="status"] { display:block; width:100%; min-height:15px; font-size:10px; line-height:15px; color:var(--_vv-muted); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
|
|
1554
1538
|
.workspace { position:relative; display:flex; flex:1; min-width:0; min-height:0; overflow:hidden; }
|
|
1555
1539
|
.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; }
|
|
1556
1540
|
.inspector-kind { color:var(--_vv-muted); font-size:10px; font-weight:400; }
|
|
@@ -1609,17 +1593,8 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1609
1593
|
button,select,input,textarea { min-height:44px; }
|
|
1610
1594
|
button { min-width:44px; }
|
|
1611
1595
|
summary { min-height:44px; line-height:44px; }
|
|
1612
|
-
.ribbon-tabs { height:44px; }
|
|
1613
|
-
.ribbon-tabs button { min-height:44px; padding:8px 16px; }
|
|
1614
1596
|
.ribbon-content { min-height:86px; }
|
|
1615
1597
|
.ribbon-group { padding:4px 8px; }
|
|
1616
|
-
.search-group { width:100%; border-right:0; }
|
|
1617
|
-
.search-controls { gap:4px; }
|
|
1618
|
-
.search-controls label { font-size:12px; }
|
|
1619
|
-
.search-controls input { font-size:12px; }
|
|
1620
|
-
.search-controls button { min-width:44px; font-size:0; padding:3px; }
|
|
1621
|
-
.search-controls [data-action="search-previous"]::before { content:"‹"; font-size:22px; }
|
|
1622
|
-
.search-controls [data-action="search-next"]::before { content:"›"; font-size:22px; }
|
|
1623
1598
|
#home-panel>.ribbon-group:not(.search-group):not(.undo-group) { display:none; }
|
|
1624
1599
|
.ribbon-command { min-width:90px; }
|
|
1625
1600
|
.workspace { display:block; overflow:auto; }
|
|
@@ -1638,26 +1613,17 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1638
1613
|
button,select,input,textarea { min-height:44px; }
|
|
1639
1614
|
button { min-width:44px; }
|
|
1640
1615
|
summary,.layer-controls label { min-height:44px; line-height:44px; }
|
|
1641
|
-
.ribbon-tabs { min-height:44px; height:44px; }
|
|
1642
|
-
.ribbon-tabs button { min-height:44px; }
|
|
1643
1616
|
.ribbon-content { min-height:96px; }
|
|
1644
|
-
.search-group { width:380px; max-width:100%; }
|
|
1645
1617
|
.notes-strip button { min-height:44px; height:44px; }
|
|
1646
1618
|
.layer-controls input { min-height:20px; }
|
|
1647
1619
|
}
|
|
1648
1620
|
/* Suite ribbon: tabs lead directly into Visio command groups. */
|
|
1649
|
-
.ribbon-tabs { height:40px; }
|
|
1650
|
-
.ribbon-tabs button { min-height:39px; padding:8px 16px; }
|
|
1651
1621
|
.ribbon-content { min-height:104px; padding:4px 4px; }
|
|
1652
|
-
.search-group { order:2; width:320px; }
|
|
1653
1622
|
.ribbon-hint { order:3; padding:10px 18px; }
|
|
1654
1623
|
.ribbon-command { min-width:82px; }
|
|
1655
1624
|
.pane-close { display:none; }
|
|
1656
1625
|
@media(max-width:760px) {
|
|
1657
|
-
.ribbon-tabs { height:44px; }
|
|
1658
|
-
.ribbon-tabs button { min-height:44px; }
|
|
1659
1626
|
.ribbon-content { min-height:54px; padding:4px 0; }
|
|
1660
|
-
.search-group { flex:0 0 100%; width:100%; min-height:54px; }
|
|
1661
1627
|
.search-feedback,.group-label { display:none; }
|
|
1662
1628
|
.ribbon-content { overflow-x:auto; scroll-snap-type:x mandatory; }
|
|
1663
1629
|
.workspace { display:flex; position:relative; overflow:hidden; }
|
|
@@ -1693,12 +1659,10 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1693
1659
|
.ribbon-tools .ribbon-group { display:flex; width:100%; min-height:64px; border:0; padding:0; }
|
|
1694
1660
|
.ribbon-tools .ribbon-actions { justify-content:space-between; }
|
|
1695
1661
|
.ribbon-tools .ribbon-command { min-width:88px; font-size:12px; }
|
|
1696
|
-
.ribbon-tools .search-group { order:1; flex:auto; }
|
|
1697
1662
|
.ribbon-tools .search-feedback { display:block; }
|
|
1698
1663
|
.status { flex-wrap:nowrap; gap:4px; min-height:48px; }
|
|
1699
1664
|
.status .notes-strip { display:none; }
|
|
1700
1665
|
}
|
|
1701
|
-
@media(pointer:coarse) { .ribbon-tabs {height:45px;min-height:45px;} .ribbon-tabs button {min-height:44px;height:44px;} }
|
|
1702
1666
|
@media(prefers-reduced-motion:reduce) { * { scroll-behavior:auto !important; } }
|
|
1703
1667
|
`;
|
|
1704
1668
|
//#endregion
|
|
@@ -1708,9 +1672,9 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
1708
1672
|
* `styles.ts` string moves into this directory when it is next changed.
|
|
1709
1673
|
*/
|
|
1710
1674
|
var canvasAndRibbonStyles = [
|
|
1711
|
-
"/*\n * Microsoft Visio ribbon layout on the shared ooxml-ui controls. Viewer theme tokens feed the\n * shared --office-* tokens, so host themes still apply. Loaded after the legacy styles.ts.\n */\n:host {\n --office-foreground: var(--_vv-ink);\n --office-muted-foreground: var(--_vv-muted);\n --office-background: var(--_vv-surface);\n --office-surface: var(--_vv-accent-soft);\n --office-border: var(--_vv-border);\n --office-accent: var(--_vv-accent);\n --office-accent-foreground: var(--vv-accent-ink, #fff);\n --office-ring: var(--vv-focus, var(--_vv-accent));\n --office-font: 'Segoe UI', Arial, sans-serif;\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/*
|
|
1675
|
+
"/*\n * Microsoft Visio ribbon layout on the shared ooxml-ui controls. Viewer theme tokens feed the\n * shared --office-* tokens, so host themes still apply. Loaded after the legacy styles.ts.\n */\n:host {\n --office-foreground: var(--_vv-ink);\n --office-muted-foreground: var(--_vv-muted);\n --office-background: var(--_vv-surface);\n --office-surface: var(--_vv-accent-soft);\n --office-border: var(--_vv-border);\n --office-accent: var(--_vv-accent);\n --office-accent-foreground: var(--vv-accent-ink, #fff);\n --office-ring: var(--vv-focus, var(--_vv-accent));\n --office-font: 'Segoe UI', Arial, sans-serif;\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='rectangle'],\n.viewport[data-tool='rectangle'] .paper [data-shape-id] {\n cursor: crosshair;\n}\n.viewport[data-tool='rectangle'] {\n touch-action: 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",
|
|
1712
1676
|
"/* 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: '›';\n margin-left: auto;\n font-size: 15px;\n}\n.stencil-title {\n margin: 2px 0;\n padding: 6px 12px;\n border-left: 3px solid var(--_vv-accent);\n background: var(--_vv-accent-soft);\n font-size: 12px;\n font-weight: 600;\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",
|
|
1713
|
-
"/* Visio's File tab and backstage. */\n:host {\n position: relative;\n}\n
|
|
1677
|
+
"/* 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 {\n --office-accent: var(--_vv-accent);\n --office-accent-foreground: var(--vv-accent-ink, #fff);\n}\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",
|
|
1714
1678
|
"/*\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, var(--_vv-accent)) !important;\n stroke-width: 0.04 !important;\n stroke-dasharray: 0.08 0.05;\n}\n"
|
|
1715
1679
|
].join("\n");
|
|
1716
1680
|
//#endregion
|
|
@@ -2827,8 +2791,7 @@ function ribbonCommands(root) {
|
|
|
2827
2791
|
const commands = [];
|
|
2828
2792
|
const context = (node) => {
|
|
2829
2793
|
const group = node.closest("office-ui-ribbon-group")?.getAttribute("label") ?? "";
|
|
2830
|
-
|
|
2831
|
-
return [panel ? root.querySelector(`[aria-controls="${panel.id}"]`)?.textContent ?? "" : "", group].filter(Boolean).join(" › ");
|
|
2794
|
+
return [node.closest("[data-ribbon-tab]")?.dataset.label ?? "", group].filter(Boolean).join(" › ");
|
|
2832
2795
|
};
|
|
2833
2796
|
for (const node of root.querySelectorAll(".toolbar [command]")) {
|
|
2834
2797
|
const id = node.getAttribute("command");
|
|
@@ -2960,50 +2923,32 @@ function quickAccess(doc) {
|
|
|
2960
2923
|
return qat;
|
|
2961
2924
|
}
|
|
2962
2925
|
/**
|
|
2963
|
-
* The Visio ribbon
|
|
2964
|
-
*
|
|
2926
|
+
* The Visio ribbon on the shared `office-ui-ribbon`: Quick Access Toolbar, File, one panel per
|
|
2927
|
+
* tab and Tell me. The shared element owns the tab row, selection and arrow-key movement;
|
|
2928
|
+
* commands emit `ribbon-action` events for the router.
|
|
2965
2929
|
*/
|
|
2966
2930
|
function createRibbon(doc) {
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
const
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
file.type = "button";
|
|
2979
|
-
file.className = "file-tab";
|
|
2980
|
-
file.textContent = "File";
|
|
2981
|
-
file.setAttribute("aria-haspopup", "dialog");
|
|
2982
|
-
file.setAttribute("aria-expanded", "false");
|
|
2983
|
-
head.append(quickAccess(doc), file, tabs, createTellMe(doc));
|
|
2984
|
-
toolbar.append(head);
|
|
2931
|
+
const ribbon = doc.createElement("office-ui-ribbon");
|
|
2932
|
+
ribbon.className = "toolbar";
|
|
2933
|
+
ribbon.setAttribute("role", "group");
|
|
2934
|
+
ribbon.setAttribute("aria-label", "Diagram controls");
|
|
2935
|
+
ribbon.setAttribute("label", "Ribbon");
|
|
2936
|
+
ribbon.setAttribute("selected", "home");
|
|
2937
|
+
const qat = quickAccess(doc);
|
|
2938
|
+
qat.slot = "quick-access";
|
|
2939
|
+
const tellMe = createTellMe(doc);
|
|
2940
|
+
tellMe.slot = "search";
|
|
2941
|
+
ribbon.append(qat, tellMe);
|
|
2985
2942
|
for (const [key, name, build] of RIBBON_TABS) {
|
|
2986
|
-
const selected = key === "home";
|
|
2987
|
-
const tab = doc.createElement("button");
|
|
2988
|
-
tab.type = "button";
|
|
2989
|
-
tab.id = `${key}-tab`;
|
|
2990
|
-
tab.dataset.tab = key;
|
|
2991
|
-
tab.textContent = name;
|
|
2992
|
-
tab.setAttribute("role", "tab");
|
|
2993
|
-
tab.setAttribute("aria-selected", String(selected));
|
|
2994
|
-
tab.setAttribute("aria-controls", `${key}-panel`);
|
|
2995
|
-
if (!selected) tab.tabIndex = -1;
|
|
2996
2943
|
const panel = doc.createElement("div");
|
|
2997
2944
|
panel.className = "ribbon-content";
|
|
2998
2945
|
panel.id = `${key}-panel`;
|
|
2999
|
-
panel.
|
|
3000
|
-
panel.
|
|
3001
|
-
panel.hidden = !selected;
|
|
2946
|
+
panel.dataset.ribbonTab = key;
|
|
2947
|
+
panel.dataset.label = name;
|
|
3002
2948
|
build(doc, panel);
|
|
3003
|
-
|
|
3004
|
-
toolbar.append(panel);
|
|
2949
|
+
ribbon.append(panel);
|
|
3005
2950
|
}
|
|
3006
|
-
return
|
|
2951
|
+
return ribbon;
|
|
3007
2952
|
}
|
|
3008
2953
|
//#endregion
|
|
3009
2954
|
//#region src/ribbon-keytips.ts
|
|
@@ -3109,13 +3054,13 @@ function generated(label, used, singles) {
|
|
|
3109
3054
|
}
|
|
3110
3055
|
/** Annotate the ribbon for KeyTips. Labels and ids are static application strings. */
|
|
3111
3056
|
function applyKeyTips(toolbar) {
|
|
3112
|
-
toolbar.
|
|
3057
|
+
toolbar.setAttribute("file-keytip", "F");
|
|
3113
3058
|
toolbar.querySelector(".tell-me").dataset.keytip = "Q";
|
|
3114
3059
|
toolbar.querySelector(".qat [command=\"undo\"]").dataset.keytip = "1";
|
|
3115
3060
|
toolbar.querySelector(".qat [command=\"redo\"]").dataset.keytip = "2";
|
|
3116
|
-
for (const
|
|
3117
|
-
tab
|
|
3118
|
-
|
|
3061
|
+
for (const panel of toolbar.querySelectorAll("[data-ribbon-tab]")) {
|
|
3062
|
+
const tab = panel.dataset.ribbonTab;
|
|
3063
|
+
panel.dataset.tabKeytip = TABS[tab] ?? tab.slice(0, 1).toUpperCase();
|
|
3119
3064
|
}
|
|
3120
3065
|
for (const panel of toolbar.querySelectorAll(".ribbon-content")) {
|
|
3121
3066
|
panel.dataset.keytipLevel = "";
|
|
@@ -3220,98 +3165,42 @@ function createPageTabs(doc) {
|
|
|
3220
3165
|
}
|
|
3221
3166
|
//#endregion
|
|
3222
3167
|
//#region src/viewer-search.ts
|
|
3223
|
-
/** Builds the Find group: a search field, previous/next result buttons and a live status. */
|
|
3224
|
-
function createSearchGroup(doc) {
|
|
3225
|
-
const group = doc.createElement("div");
|
|
3226
|
-
group.className = "ribbon-group search-group";
|
|
3227
|
-
group.setAttribute("role", "group");
|
|
3228
|
-
group.setAttribute("aria-label", "Find");
|
|
3229
|
-
const controls = doc.createElement("div");
|
|
3230
|
-
controls.className = "search-controls";
|
|
3231
|
-
controls.setAttribute("role", "search");
|
|
3232
|
-
controls.setAttribute("aria-label", "Diagram text search");
|
|
3233
|
-
const label = doc.createElement("label");
|
|
3234
|
-
label.append("Find ");
|
|
3235
|
-
const input = doc.createElement("input");
|
|
3236
|
-
input.type = "search";
|
|
3237
|
-
input.setAttribute("aria-label", "Search diagram text");
|
|
3238
|
-
input.placeholder = "Text across pages";
|
|
3239
|
-
input.maxLength = TEXT_SEARCH_LIMITS.queryCharacters;
|
|
3240
|
-
input.autocomplete = "off";
|
|
3241
|
-
input.spellcheck = false;
|
|
3242
|
-
input.setAttribute("aria-describedby", "search-status");
|
|
3243
|
-
label.append(input);
|
|
3244
|
-
const step = (action, name, text) => {
|
|
3245
|
-
const button = doc.createElement("button");
|
|
3246
|
-
button.type = "button";
|
|
3247
|
-
button.dataset.action = action;
|
|
3248
|
-
button.setAttribute("aria-label", name);
|
|
3249
|
-
button.textContent = text;
|
|
3250
|
-
return button;
|
|
3251
|
-
};
|
|
3252
|
-
const status = doc.createElement("span");
|
|
3253
|
-
status.id = "search-status";
|
|
3254
|
-
status.setAttribute("role", "status");
|
|
3255
|
-
status.setAttribute("aria-live", "polite");
|
|
3256
|
-
status.setAttribute("aria-atomic", "true");
|
|
3257
|
-
controls.append(label, step("search-previous", "Previous matching shape", "Previous"), step("search-next", "Next matching shape", "Next"), status);
|
|
3258
|
-
const caption = doc.createElement("span");
|
|
3259
|
-
caption.className = "group-label";
|
|
3260
|
-
caption.textContent = "Find in drawing";
|
|
3261
|
-
group.append(controls, caption);
|
|
3262
|
-
return group;
|
|
3263
|
-
}
|
|
3264
3168
|
/**
|
|
3265
|
-
* Visio opens Find from Home > Editing rather than keeping a box in the ribbon. The
|
|
3266
|
-
* sits under the ribbon, hidden until Find... or Ctrl+F
|
|
3169
|
+
* Visio opens Find from Home > Editing rather than keeping a box in the ribbon. The shared
|
|
3170
|
+
* `office-ui-find-bar` sits under the ribbon, hidden until Find... or Ctrl+F; Enter and
|
|
3171
|
+
* Shift+Enter step through matching shapes and Escape clears, then closes.
|
|
3267
3172
|
*/
|
|
3268
3173
|
function createFindBar(doc) {
|
|
3269
|
-
const bar = doc.createElement("
|
|
3174
|
+
const bar = doc.createElement("office-ui-find-bar");
|
|
3270
3175
|
bar.className = "find-bar";
|
|
3271
|
-
bar.
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
close.title = "Close Find (Esc)";
|
|
3278
|
-
close.textContent = "×";
|
|
3279
|
-
bar.append(createSearchGroup(doc), close);
|
|
3176
|
+
bar.setAttribute("label", "Diagram text search");
|
|
3177
|
+
bar.setAttribute("input-label", "Search diagram text");
|
|
3178
|
+
bar.setAttribute("placeholder", "Text across pages");
|
|
3179
|
+
bar.setAttribute("maxlength", String(TEXT_SEARCH_LIMITS.queryCharacters));
|
|
3180
|
+
bar.setAttribute("previous-label", "Previous matching shape");
|
|
3181
|
+
bar.setAttribute("next-label", "Next matching shape");
|
|
3280
3182
|
return bar;
|
|
3281
3183
|
}
|
|
3282
|
-
/**
|
|
3283
|
-
function wireFindBar(bar,
|
|
3184
|
+
/** Route the find bar's queries and steps to the controller; `closed` restores focus. */
|
|
3185
|
+
function wireFindBar(bar, controller, closed) {
|
|
3284
3186
|
const events = new ((bar.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
3285
|
-
const
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
},
|
|
3292
|
-
bar.addEventListener("
|
|
3293
|
-
if (event.key === "Escape" && input.value === "") close();
|
|
3294
|
-
}, {
|
|
3295
|
-
signal: events.signal,
|
|
3296
|
-
capture: true
|
|
3297
|
-
});
|
|
3187
|
+
const options = { signal: events.signal };
|
|
3188
|
+
bar.addEventListener("office-find-input", (event) => controller.setSearchQuery(event.detail.query), options);
|
|
3189
|
+
bar.addEventListener("office-find-step", (event) => {
|
|
3190
|
+
const { direction } = event.detail;
|
|
3191
|
+
if (direction === "next") controller.nextSearchResult();
|
|
3192
|
+
else controller.previousSearchResult();
|
|
3193
|
+
}, options);
|
|
3194
|
+
bar.addEventListener("office-find-close", closed, options);
|
|
3298
3195
|
return () => events.abort();
|
|
3299
3196
|
}
|
|
3300
|
-
function
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
next: root.querySelector("[data-action=\"search-next\"]")
|
|
3306
|
-
};
|
|
3307
|
-
}
|
|
3308
|
-
function renderSearchControls(controls, state) {
|
|
3309
|
-
if (controls.input.value !== state.search.query) controls.input.value = state.search.query;
|
|
3310
|
-
controls.input.disabled = !state.document?.pages.length;
|
|
3311
|
-
controls.previous.disabled = controls.next.disabled = !state.search.results.length;
|
|
3312
|
-
controls.status.textContent = state.document ? textSearchStatus(state.search) : "Open a diagram to search";
|
|
3197
|
+
function renderFindBar(bar, state) {
|
|
3198
|
+
bar.value = state.search.query;
|
|
3199
|
+
bar.toggleAttribute("disabled", !state.document?.pages.length);
|
|
3200
|
+
bar.toggleAttribute("navigation-disabled", !state.search.results.length);
|
|
3201
|
+
bar.status = state.document ? textSearchStatus(state.search) : "Open a diagram to search";
|
|
3313
3202
|
const result = state.search.results[state.search.activeIndex];
|
|
3314
|
-
|
|
3203
|
+
bar.statusTitle = result ? `${result.pageName} · ${result.shapeName}: ${result.preview}` : "";
|
|
3315
3204
|
}
|
|
3316
3205
|
//#endregion
|
|
3317
3206
|
//#region src/viewer-fit.ts
|
|
@@ -3605,6 +3494,17 @@ var BACKSTAGE_FOOTER = [
|
|
|
3605
3494
|
["feedback", "Feedback"],
|
|
3606
3495
|
["options", "Options"]
|
|
3607
3496
|
];
|
|
3497
|
+
/** The navigation items, with optional disabled states (Save and Close follow the drawing). */
|
|
3498
|
+
function backstageItems(state = {}) {
|
|
3499
|
+
const item = (id, label, group) => ({
|
|
3500
|
+
id,
|
|
3501
|
+
label,
|
|
3502
|
+
...group ? { group } : {},
|
|
3503
|
+
...state[id]?.disabled ? { disabled: true } : {},
|
|
3504
|
+
...state[id]?.title ? { title: state[id].title } : {}
|
|
3505
|
+
});
|
|
3506
|
+
return [...BACKSTAGE_ITEMS.map(([id, label]) => item(id, label)), ...BACKSTAGE_FOOTER.map(([id, label]) => item(id, label, "footer"))];
|
|
3507
|
+
}
|
|
3608
3508
|
var LOCAL = "Documents stay in this browser; nothing is uploaded.";
|
|
3609
3509
|
function el(doc, tag, className, text) {
|
|
3610
3510
|
const node = doc.createElement(tag);
|
|
@@ -3628,7 +3528,6 @@ function action(doc, id, label, description, unsupported) {
|
|
|
3628
3528
|
function page(doc, id, title, ...content) {
|
|
3629
3529
|
const section = el(doc, "section", "backstage-page");
|
|
3630
3530
|
section.dataset.backstagePage = id;
|
|
3631
|
-
section.hidden = true;
|
|
3632
3531
|
section.setAttribute("aria-labelledby", `backstage-${id}-title`);
|
|
3633
3532
|
const heading = el(doc, "h1", "", title);
|
|
3634
3533
|
heading.id = `backstage-${id}-title`;
|
|
@@ -3636,31 +3535,55 @@ function page(doc, id, title, ...content) {
|
|
|
3636
3535
|
return section;
|
|
3637
3536
|
}
|
|
3638
3537
|
/**
|
|
3538
|
+
* Share with people: a live session (Yjs) between tabs and windows of this browser. The room name
|
|
3539
|
+
* is all a second window needs; nothing is uploaded.
|
|
3540
|
+
*/
|
|
3541
|
+
function share(doc) {
|
|
3542
|
+
const panel = el(doc, "section", "share-panel");
|
|
3543
|
+
panel.setAttribute("aria-labelledby", "share-title");
|
|
3544
|
+
const title = el(doc, "h2", "", "Share with people");
|
|
3545
|
+
title.id = "share-title";
|
|
3546
|
+
const field = el(doc, "label", "share-field");
|
|
3547
|
+
const room = el(doc, "input", "share-room");
|
|
3548
|
+
room.type = "text";
|
|
3549
|
+
room.maxLength = 64;
|
|
3550
|
+
room.autocomplete = "off";
|
|
3551
|
+
room.spellcheck = false;
|
|
3552
|
+
field.append(el(doc, "span", "", "Session name"), room);
|
|
3553
|
+
const buttons = el(doc, "div", "share-buttons");
|
|
3554
|
+
const start = el(doc, "button", "share-start", "Start sharing");
|
|
3555
|
+
start.type = "button";
|
|
3556
|
+
start.dataset.share = "start";
|
|
3557
|
+
const stop = el(doc, "button", "share-stop", "Stop sharing");
|
|
3558
|
+
stop.type = "button";
|
|
3559
|
+
stop.dataset.share = "stop";
|
|
3560
|
+
stop.hidden = true;
|
|
3561
|
+
buttons.append(start, stop);
|
|
3562
|
+
const status = el(doc, "p", "share-status");
|
|
3563
|
+
status.setAttribute("role", "status");
|
|
3564
|
+
const people = doc.createElement("office-ui-presence");
|
|
3565
|
+
people.className = "share-people";
|
|
3566
|
+
people.setAttribute("label", "People in this session");
|
|
3567
|
+
panel.append(title, el(doc, "p", "", "Edit this drawing together with other windows of this browser. Open the same session name in another window to join."), field, buttons, status, people, el(doc, "p", "backstage-muted", "The whole drawing is shared; when two people change it at once, the last change wins. Nothing leaves this device."));
|
|
3568
|
+
return panel;
|
|
3569
|
+
}
|
|
3570
|
+
/** Visio's Account page: the shared Office profile editor and product information. */
|
|
3571
|
+
function account(doc) {
|
|
3572
|
+
const profile = doc.createElement("office-ui-account");
|
|
3573
|
+
const product = el(doc, "section", "backstage-product");
|
|
3574
|
+
product.append(el(doc, "h2", "", "Product Information"), el(doc, "p", "", "Visio viewer (beta). No sign-in or subscription is used."), el(doc, "p", "backstage-muted", LOCAL));
|
|
3575
|
+
profile.append(product);
|
|
3576
|
+
return profile;
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3639
3579
|
* Visio's backstage: a full-window File view with a navigation column and one page per item.
|
|
3640
3580
|
* Static application structure only; document values are filled in later as text.
|
|
3641
3581
|
*/
|
|
3642
3582
|
function createBackstage(doc) {
|
|
3643
|
-
const root =
|
|
3644
|
-
root.
|
|
3645
|
-
root.setAttribute("
|
|
3646
|
-
root.
|
|
3647
|
-
root.setAttribute("aria-label", "File");
|
|
3648
|
-
const nav = el(doc, "nav", "backstage-nav");
|
|
3649
|
-
nav.setAttribute("aria-label", "File");
|
|
3650
|
-
const back = el(doc, "button", "backstage-back", "←");
|
|
3651
|
-
back.type = "button";
|
|
3652
|
-
back.dataset.backstage = "back";
|
|
3653
|
-
back.setAttribute("aria-label", "Back to the drawing");
|
|
3654
|
-
back.title = "Back (Esc)";
|
|
3655
|
-
const item = ([id, label]) => {
|
|
3656
|
-
const button = el(doc, "button", "backstage-item", label);
|
|
3657
|
-
button.type = "button";
|
|
3658
|
-
button.dataset.backstageItem = id;
|
|
3659
|
-
return button;
|
|
3660
|
-
};
|
|
3661
|
-
const footer = el(doc, "div", "backstage-footer");
|
|
3662
|
-
footer.append(...BACKSTAGE_FOOTER.map(item));
|
|
3663
|
-
nav.append(back, ...BACKSTAGE_ITEMS.map(item), footer);
|
|
3583
|
+
const root = doc.createElement("office-ui-backstage");
|
|
3584
|
+
root.className = "backstage";
|
|
3585
|
+
root.setAttribute("back-label", "Back to the drawing");
|
|
3586
|
+
root.items = backstageItems();
|
|
3664
3587
|
const facts = el(doc, "dl", "backstage-facts");
|
|
3665
3588
|
for (const [key, label] of [
|
|
3666
3589
|
["pages", "Pages"],
|
|
@@ -3686,67 +3609,61 @@ function createBackstage(doc) {
|
|
|
3686
3609
|
input.accept = ".vsdx,.vsd";
|
|
3687
3610
|
input.hidden = true;
|
|
3688
3611
|
input.setAttribute("aria-label", "Choose a Visio drawing");
|
|
3689
|
-
const preview =
|
|
3690
|
-
preview.
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
root.append(
|
|
3612
|
+
const preview = doc.createElement("office-ui-print-preview");
|
|
3613
|
+
preview.className = "backstage-print-preview";
|
|
3614
|
+
preview.setAttribute("label", "Print preview of the current page");
|
|
3615
|
+
preview.setAttribute("empty-label", "Open a drawing to print it.");
|
|
3616
|
+
root.append(page(doc, "info", "Info", file, facts, notes), page(doc, "new", "New", templates), page(doc, "open", "Open", action(doc, "open", "Browse", "Open a .vsdx drawing or a legacy .vsd preview from this device."), input, el(doc, "p", "backstage-muted", `Recent drawings are not tracked. ${LOCAL}`)), page(doc, "save-as", "Save As", action(doc, "download", "Download a copy", "Save the drawing, with your edits, as a .vsdx file."), action(doc, "export-svg", "Export the current page as SVG", "An approximate picture with compatibility notes.")), page(doc, "print", "Print", action(doc, "print", "Print", "Print the current page."), preview), page(doc, "share", "Share", share(doc), action(doc, "email", "Email", "Send the drawing as an attachment.", LOCAL)), page(doc, "export", "Export", action(doc, "export-svg", "Export the current page as SVG", "An approximate picture with compatibility notes."), action(doc, "export-pdf", "Create PDF/XPS Document", "A fixed-layout document.", "Needs PDF output."), action(doc, "change-type", "Change File Type", "Save in another Visio format.", "Needs core format conversion.")), page(doc, "account", "Account", account(doc)), page(doc, "feedback", "Feedback", el(doc, "p", "backstage-muted", "Feedback is not collected by this viewer.")));
|
|
3694
3617
|
return root;
|
|
3695
3618
|
}
|
|
3696
3619
|
//#endregion
|
|
3697
3620
|
//#region src/viewer-backstage.ts
|
|
3698
|
-
/**
|
|
3699
|
-
* Visio's File backstage: opened from the File tab, closed with Back or Escape (focus returns to
|
|
3700
|
-
* File). Open, Save, Save As, Export, Print and Close are real; the rest is shown disabled.
|
|
3701
|
-
*/
|
|
3702
3621
|
var ViewerBackstage = class {
|
|
3703
3622
|
host;
|
|
3704
3623
|
#root;
|
|
3705
|
-
#
|
|
3624
|
+
#ribbon;
|
|
3706
3625
|
#input;
|
|
3707
3626
|
#state;
|
|
3627
|
+
#items = "";
|
|
3708
3628
|
#urls = /* @__PURE__ */ new Set();
|
|
3709
3629
|
constructor(host) {
|
|
3710
3630
|
this.host = host;
|
|
3711
|
-
this.#root = host.root.querySelector("
|
|
3712
|
-
this.#
|
|
3631
|
+
this.#root = host.root.querySelector("office-ui-backstage");
|
|
3632
|
+
this.#ribbon = host.root.querySelector("office-ui-ribbon");
|
|
3713
3633
|
this.#input = this.#root.querySelector(".backstage-file-input");
|
|
3714
3634
|
}
|
|
3715
3635
|
get open() {
|
|
3716
|
-
return
|
|
3636
|
+
return this.#root.open;
|
|
3717
3637
|
}
|
|
3718
3638
|
show(page = "info") {
|
|
3719
|
-
this.#
|
|
3720
|
-
this.#
|
|
3721
|
-
this.#
|
|
3722
|
-
this.#root.querySelector(`[data-backstage-item="${page}"]`)?.focus();
|
|
3639
|
+
this.#ribbon.setAttribute("file-expanded", "true");
|
|
3640
|
+
this.#root.show(page);
|
|
3641
|
+
if (page === "print") this.#preview();
|
|
3723
3642
|
}
|
|
3724
3643
|
hide() {
|
|
3725
|
-
|
|
3726
|
-
this.#root.hidden = true;
|
|
3727
|
-
this.#file.setAttribute("aria-expanded", "false");
|
|
3728
|
-
this.#file.focus();
|
|
3644
|
+
this.#root.close();
|
|
3729
3645
|
}
|
|
3730
3646
|
wire() {
|
|
3731
3647
|
const events = new ((this.host.root.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
3732
3648
|
const options = { signal: events.signal };
|
|
3733
|
-
this.#
|
|
3649
|
+
this.#ribbon.addEventListener("office-ribbon-file", () => this.open ? this.hide() : this.show(), options);
|
|
3650
|
+
this.#root.addEventListener("office-backstage-select", (event) => {
|
|
3651
|
+
const id = event.detail.id;
|
|
3652
|
+
if (id === "save") this.#download();
|
|
3653
|
+
else if (id === "close") this.#close();
|
|
3654
|
+
else if (id === "options") {
|
|
3655
|
+
this.hide();
|
|
3656
|
+
this.host.showOptions();
|
|
3657
|
+
} else if (id === "print") this.#preview();
|
|
3658
|
+
}, options);
|
|
3659
|
+
this.#root.addEventListener("office-backstage-close", () => {
|
|
3660
|
+
this.#ribbon.setAttribute("file-expanded", "false");
|
|
3661
|
+
this.#ribbon.focusFile();
|
|
3662
|
+
}, options);
|
|
3734
3663
|
this.#root.addEventListener("click", (event) => {
|
|
3735
|
-
const
|
|
3736
|
-
if (target.closest?.("[data-backstage=\"back\"]")) return this.hide();
|
|
3737
|
-
const item = target.closest?.("[data-backstage-item]")?.dataset.backstageItem;
|
|
3738
|
-
if (item === "save") return this.#download();
|
|
3739
|
-
if (item === "close") return this.#close();
|
|
3740
|
-
if (item) return this.#select(item);
|
|
3741
|
-
const action = target.closest?.("[data-backstage-action]");
|
|
3664
|
+
const action = event.target.closest?.("[data-backstage-action]");
|
|
3742
3665
|
if (action && !action.disabled) this.#run(action.dataset.backstageAction);
|
|
3743
3666
|
}, options);
|
|
3744
|
-
this.#root.addEventListener("keydown", (event) => {
|
|
3745
|
-
if (event.key !== "Escape") return;
|
|
3746
|
-
event.preventDefault();
|
|
3747
|
-
event.stopPropagation();
|
|
3748
|
-
this.hide();
|
|
3749
|
-
}, options);
|
|
3750
3667
|
this.#input.addEventListener("change", () => {
|
|
3751
3668
|
const file = this.#input.files?.[0];
|
|
3752
3669
|
this.#input.value = "";
|
|
@@ -3760,11 +3677,6 @@ var ViewerBackstage = class {
|
|
|
3760
3677
|
this.#urls.clear();
|
|
3761
3678
|
};
|
|
3762
3679
|
}
|
|
3763
|
-
#select(page) {
|
|
3764
|
-
for (const item of this.#root.querySelectorAll("[data-backstage-item]")) item.toggleAttribute("aria-current", item.dataset.backstageItem === page);
|
|
3765
|
-
for (const section of this.#root.querySelectorAll("[data-backstage-page]")) section.hidden = section.dataset.backstagePage !== page;
|
|
3766
|
-
if (page === "print") this.#preview();
|
|
3767
|
-
}
|
|
3768
3680
|
#run(action) {
|
|
3769
3681
|
if (action === "open") this.#input.click();
|
|
3770
3682
|
else if (action === "download") this.#download();
|
|
@@ -3814,14 +3726,11 @@ var ViewerBackstage = class {
|
|
|
3814
3726
|
this.host.announce(error instanceof Error ? error.message : String(error));
|
|
3815
3727
|
}
|
|
3816
3728
|
}
|
|
3817
|
-
/**
|
|
3729
|
+
/** The shared print preview shows an inert clone of the rendered page, never parsed markup. */
|
|
3818
3730
|
#preview() {
|
|
3819
|
-
const
|
|
3731
|
+
const preview = this.#root.querySelector("office-ui-print-preview");
|
|
3820
3732
|
const paper = this.host.viewport.querySelector("svg.paper");
|
|
3821
|
-
|
|
3822
|
-
const clone = target.querySelector("svg");
|
|
3823
|
-
clone?.removeAttribute("style");
|
|
3824
|
-
clone?.querySelectorAll("[tabindex]").forEach((node) => node.removeAttribute("tabindex"));
|
|
3733
|
+
preview.pages = paper ? [paper] : [];
|
|
3825
3734
|
}
|
|
3826
3735
|
#print() {
|
|
3827
3736
|
const doc = this.host.root.ownerDocument;
|
|
@@ -3865,13 +3774,24 @@ var ViewerBackstage = class {
|
|
|
3865
3774
|
this.#root.querySelector(".backstage-note-count").textContent = notes ? `${notes} compatibility notes describe what this viewer approximates or omits.` : "No compatibility notes.";
|
|
3866
3775
|
const busy = state.loading || state.edit.busy;
|
|
3867
3776
|
const saveReason = state.document?.format === "vsd" ? "Legacy VSD drawings are read only here." : "Open a .vsdx file to save a copy.";
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3777
|
+
const saveDisabled = !state.edit.sourceAvailable || busy;
|
|
3778
|
+
const download = this.#root.querySelector("[data-backstage-action=\"download\"]");
|
|
3779
|
+
download.disabled = saveDisabled;
|
|
3780
|
+
download.title = saveDisabled ? saveReason : "";
|
|
3781
|
+
const items = backstageItems({
|
|
3782
|
+
save: {
|
|
3783
|
+
disabled: saveDisabled,
|
|
3784
|
+
...saveDisabled ? { title: saveReason } : {}
|
|
3785
|
+
},
|
|
3786
|
+
close: { disabled: !state.document }
|
|
3787
|
+
});
|
|
3788
|
+
const key = JSON.stringify(items);
|
|
3789
|
+
if (key !== this.#items) {
|
|
3790
|
+
this.#items = key;
|
|
3791
|
+
this.#root.items = items;
|
|
3871
3792
|
}
|
|
3872
3793
|
for (const node of this.#root.querySelectorAll("[data-backstage-action=\"export-svg\"], [data-backstage-action=\"print\"]")) node.disabled = !page || busy;
|
|
3873
3794
|
this.#root.querySelector("[data-backstage-action=\"notes\"]").disabled = notes === 0;
|
|
3874
|
-
this.#root.querySelector("[data-backstage-item=\"close\"]").disabled = !state.document;
|
|
3875
3795
|
}
|
|
3876
3796
|
};
|
|
3877
3797
|
//#endregion
|
|
@@ -4213,6 +4133,396 @@ var ViewerPanZoom = class {
|
|
|
4213
4133
|
}
|
|
4214
4134
|
};
|
|
4215
4135
|
//#endregion
|
|
4136
|
+
//#region src/viewer-options.ts
|
|
4137
|
+
var disabled = (reason) => ({ disabled: `Not available yet. ${reason}` });
|
|
4138
|
+
/**
|
|
4139
|
+
* Visio's File > Options categories, in Visio's order. Only User name and Initials are honoured
|
|
4140
|
+
* today (they are the local profile, also used as the identity in a shared session); every other
|
|
4141
|
+
* setting is shown disabled with the capability it is waiting for.
|
|
4142
|
+
*/
|
|
4143
|
+
var VISIO_OPTION_CATEGORIES = [
|
|
4144
|
+
{
|
|
4145
|
+
id: "general",
|
|
4146
|
+
label: "General",
|
|
4147
|
+
description: "General options for working with Visio.",
|
|
4148
|
+
sections: [{
|
|
4149
|
+
id: "ui",
|
|
4150
|
+
title: "User Interface options",
|
|
4151
|
+
controls: [
|
|
4152
|
+
{
|
|
4153
|
+
kind: "toggle",
|
|
4154
|
+
key: "miniToolbar",
|
|
4155
|
+
label: "Show Mini Toolbar on selection",
|
|
4156
|
+
...disabled("Needs rich text formatting.")
|
|
4157
|
+
},
|
|
4158
|
+
{
|
|
4159
|
+
kind: "toggle",
|
|
4160
|
+
key: "livePreview",
|
|
4161
|
+
label: "Enable Live Preview",
|
|
4162
|
+
...disabled("Needs Quick Styles and themes.")
|
|
4163
|
+
},
|
|
4164
|
+
{
|
|
4165
|
+
kind: "select",
|
|
4166
|
+
key: "screenTips",
|
|
4167
|
+
label: "ScreenTip style",
|
|
4168
|
+
choices: [{
|
|
4169
|
+
value: "descriptions",
|
|
4170
|
+
label: "Show feature descriptions in ScreenTips"
|
|
4171
|
+
}],
|
|
4172
|
+
...disabled("ScreenTips always describe the command.")
|
|
4173
|
+
}
|
|
4174
|
+
]
|
|
4175
|
+
}, {
|
|
4176
|
+
id: "personalize",
|
|
4177
|
+
title: "Personalize your copy of Microsoft Office",
|
|
4178
|
+
description: "Stored on this device only. Shared sessions show this name to others.",
|
|
4179
|
+
controls: [{
|
|
4180
|
+
kind: "text",
|
|
4181
|
+
key: "userName",
|
|
4182
|
+
label: "User name",
|
|
4183
|
+
maxLength: 64
|
|
4184
|
+
}, {
|
|
4185
|
+
kind: "text",
|
|
4186
|
+
key: "userInitials",
|
|
4187
|
+
label: "Initials",
|
|
4188
|
+
maxLength: 2
|
|
4189
|
+
}]
|
|
4190
|
+
}]
|
|
4191
|
+
},
|
|
4192
|
+
{
|
|
4193
|
+
id: "proofing",
|
|
4194
|
+
label: "Proofing",
|
|
4195
|
+
description: "Change how Visio corrects and formats your text.",
|
|
4196
|
+
disabled: "Not available yet. Spelling and AutoCorrect need rich text editing.",
|
|
4197
|
+
sections: []
|
|
4198
|
+
},
|
|
4199
|
+
{
|
|
4200
|
+
id: "save",
|
|
4201
|
+
label: "Save",
|
|
4202
|
+
description: "Customize how drawings are saved.",
|
|
4203
|
+
disabled: "Not available yet. Drawings are saved as downloaded copies; AutoRecover is not kept.",
|
|
4204
|
+
sections: []
|
|
4205
|
+
},
|
|
4206
|
+
{
|
|
4207
|
+
id: "language",
|
|
4208
|
+
label: "Language",
|
|
4209
|
+
description: "Set the Office Language Preferences.",
|
|
4210
|
+
disabled: "Not available yet. The viewer is English only.",
|
|
4211
|
+
sections: []
|
|
4212
|
+
},
|
|
4213
|
+
{
|
|
4214
|
+
id: "advanced",
|
|
4215
|
+
label: "Advanced",
|
|
4216
|
+
description: "Advanced options for working with Visio.",
|
|
4217
|
+
sections: [{
|
|
4218
|
+
id: "editing",
|
|
4219
|
+
title: "Editing options",
|
|
4220
|
+
controls: [{
|
|
4221
|
+
kind: "number",
|
|
4222
|
+
key: "undoLevels",
|
|
4223
|
+
label: "Number of undo levels",
|
|
4224
|
+
min: 1,
|
|
4225
|
+
max: 99,
|
|
4226
|
+
...disabled("The history depth is fixed.")
|
|
4227
|
+
}, {
|
|
4228
|
+
kind: "toggle",
|
|
4229
|
+
key: "autoSizeText",
|
|
4230
|
+
label: "Auto-size text while editing",
|
|
4231
|
+
...disabled("Needs core text layout.")
|
|
4232
|
+
}]
|
|
4233
|
+
}]
|
|
4234
|
+
},
|
|
4235
|
+
{
|
|
4236
|
+
id: "ribbon",
|
|
4237
|
+
label: "Customize Ribbon",
|
|
4238
|
+
description: "Customize the Ribbon and keyboard shortcuts.",
|
|
4239
|
+
disabled: "Not available yet. The ribbon layout is fixed.",
|
|
4240
|
+
sections: []
|
|
4241
|
+
},
|
|
4242
|
+
{
|
|
4243
|
+
id: "qat",
|
|
4244
|
+
label: "Quick Access Toolbar",
|
|
4245
|
+
description: "Customize the Quick Access Toolbar.",
|
|
4246
|
+
disabled: "Not available yet. The Quick Access Toolbar shows Undo and Redo.",
|
|
4247
|
+
sections: []
|
|
4248
|
+
},
|
|
4249
|
+
{
|
|
4250
|
+
id: "add-ins",
|
|
4251
|
+
label: "Add-ins",
|
|
4252
|
+
description: "View and manage Microsoft Office Add-ins.",
|
|
4253
|
+
disabled: "Not available. Add-ins and macros never run in this viewer.",
|
|
4254
|
+
sections: []
|
|
4255
|
+
},
|
|
4256
|
+
{
|
|
4257
|
+
id: "trust",
|
|
4258
|
+
label: "Trust Center",
|
|
4259
|
+
description: "Help keep your documents safe and your computer secure and healthy.",
|
|
4260
|
+
disabled: "Not available. Documents stay local and active content is never run.",
|
|
4261
|
+
sections: []
|
|
4262
|
+
}
|
|
4263
|
+
];
|
|
4264
|
+
function profileValues(profile) {
|
|
4265
|
+
return {
|
|
4266
|
+
miniToolbar: false,
|
|
4267
|
+
livePreview: false,
|
|
4268
|
+
screenTips: "descriptions",
|
|
4269
|
+
undoLevels: 20,
|
|
4270
|
+
autoSizeText: false,
|
|
4271
|
+
userName: profile.displayName,
|
|
4272
|
+
userInitials: profile.initial ?? ""
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
/** The Options dialog element. Built once and appended to the viewer's shadow root. */
|
|
4276
|
+
function createOptionsDialog(doc) {
|
|
4277
|
+
const dialog = doc.createElement("office-ui-options-dialog");
|
|
4278
|
+
dialog.setAttribute("heading", "Visio Options");
|
|
4279
|
+
dialog.categories = VISIO_OPTION_CATEGORIES;
|
|
4280
|
+
return dialog;
|
|
4281
|
+
}
|
|
4282
|
+
/**
|
|
4283
|
+
* File > Account and File > Options share one local profile. Edits in either are written to the
|
|
4284
|
+
* suite-wide profile store, mirrored into the other and reported through `onProfile`.
|
|
4285
|
+
*/
|
|
4286
|
+
var ViewerProfile = class {
|
|
4287
|
+
onProfile;
|
|
4288
|
+
#profile = readOfficeProfile();
|
|
4289
|
+
#dialog;
|
|
4290
|
+
#account;
|
|
4291
|
+
constructor(root, onProfile = () => {}) {
|
|
4292
|
+
this.onProfile = onProfile;
|
|
4293
|
+
this.#dialog = root.querySelector("office-ui-options-dialog");
|
|
4294
|
+
this.#account = root.querySelector("office-ui-account");
|
|
4295
|
+
this.#sync();
|
|
4296
|
+
}
|
|
4297
|
+
get profile() {
|
|
4298
|
+
return { ...this.#profile };
|
|
4299
|
+
}
|
|
4300
|
+
/** Open Visio Options, on General as Visio does. */
|
|
4301
|
+
showOptions(category = "general") {
|
|
4302
|
+
this.#dialog.values = profileValues(this.#profile);
|
|
4303
|
+
this.#dialog.category = category;
|
|
4304
|
+
this.#dialog.show();
|
|
4305
|
+
}
|
|
4306
|
+
wire() {
|
|
4307
|
+
const events = new ((this.#dialog.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
4308
|
+
const options = { signal: events.signal };
|
|
4309
|
+
this.#dialog.addEventListener("office-options-change", (event) => {
|
|
4310
|
+
const { values } = event.detail;
|
|
4311
|
+
const initial = String(values.userInitials ?? "").trim();
|
|
4312
|
+
this.#update({
|
|
4313
|
+
displayName: String(values.userName ?? "").trim(),
|
|
4314
|
+
avatarColor: this.#profile.avatarColor,
|
|
4315
|
+
...initial ? { initial } : {}
|
|
4316
|
+
});
|
|
4317
|
+
}, options);
|
|
4318
|
+
this.#account?.addEventListener("office-profile-change", (event) => this.#update(event.detail.profile), options);
|
|
4319
|
+
return () => events.abort();
|
|
4320
|
+
}
|
|
4321
|
+
#update(profile) {
|
|
4322
|
+
this.#profile = sanitizeOfficeProfile(profile);
|
|
4323
|
+
writeOfficeProfile(this.#profile);
|
|
4324
|
+
this.#sync();
|
|
4325
|
+
this.onProfile(this.profile);
|
|
4326
|
+
}
|
|
4327
|
+
#sync() {
|
|
4328
|
+
if (this.#account) this.#account.profile = this.#profile;
|
|
4329
|
+
this.#dialog.values = profileValues(this.#profile);
|
|
4330
|
+
}
|
|
4331
|
+
};
|
|
4332
|
+
//#endregion
|
|
4333
|
+
//#region src/viewer-share.ts
|
|
4334
|
+
/** Room names the core accepts: 1-64 letters, digits, hyphens or underscores. */
|
|
4335
|
+
var ROOM = /^[A-Za-z0-9_-]{1,64}$/;
|
|
4336
|
+
/**
|
|
4337
|
+
* File > Share: a live session over `ooxml-core/collab` (Yjs). Peers in other windows of this
|
|
4338
|
+
* browser share the whole VSDX package through a BroadcastChannel; remote changes arrive as one
|
|
4339
|
+
* undoable history step. The collab runtime loads only when sharing starts.
|
|
4340
|
+
*/
|
|
4341
|
+
var ViewerShare = class {
|
|
4342
|
+
controller;
|
|
4343
|
+
profile;
|
|
4344
|
+
loadCollab;
|
|
4345
|
+
#panel;
|
|
4346
|
+
#room;
|
|
4347
|
+
#start;
|
|
4348
|
+
#stop;
|
|
4349
|
+
#status;
|
|
4350
|
+
#people;
|
|
4351
|
+
#session = null;
|
|
4352
|
+
#teardown = null;
|
|
4353
|
+
#revision = 0;
|
|
4354
|
+
/** The package this window last published or adopted, to skip repeats of it. */
|
|
4355
|
+
#last = null;
|
|
4356
|
+
#adopting = false;
|
|
4357
|
+
#pending = null;
|
|
4358
|
+
#starting = false;
|
|
4359
|
+
constructor(root, controller, profile, loadCollab = () => import("ooxml-core/collab")) {
|
|
4360
|
+
this.controller = controller;
|
|
4361
|
+
this.profile = profile;
|
|
4362
|
+
this.loadCollab = loadCollab;
|
|
4363
|
+
this.#panel = root.querySelector(".share-panel");
|
|
4364
|
+
this.#room = this.#panel.querySelector(".share-room");
|
|
4365
|
+
this.#start = this.#panel.querySelector("[data-share=\"start\"]");
|
|
4366
|
+
this.#stop = this.#panel.querySelector("[data-share=\"stop\"]");
|
|
4367
|
+
this.#status = this.#panel.querySelector(".share-status");
|
|
4368
|
+
this.#people = this.#panel.querySelector("office-ui-presence");
|
|
4369
|
+
this.#room.value = `visio-${Math.random().toString(36).slice(2, 8)}`;
|
|
4370
|
+
}
|
|
4371
|
+
get active() {
|
|
4372
|
+
return this.#session !== null;
|
|
4373
|
+
}
|
|
4374
|
+
wire() {
|
|
4375
|
+
const events = new ((this.#panel.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
4376
|
+
this.#panel.addEventListener("click", (event) => {
|
|
4377
|
+
const action = event.target.closest?.("[data-share]");
|
|
4378
|
+
if (action?.dataset.share === "start") this.start(this.#room.value.trim());
|
|
4379
|
+
if (action?.dataset.share === "stop") this.stop();
|
|
4380
|
+
}, { signal: events.signal });
|
|
4381
|
+
return () => {
|
|
4382
|
+
events.abort();
|
|
4383
|
+
this.stop();
|
|
4384
|
+
};
|
|
4385
|
+
}
|
|
4386
|
+
async start(roomId) {
|
|
4387
|
+
if (this.#session || this.#starting) return;
|
|
4388
|
+
if (!ROOM.test(roomId)) return this.#say("Use 1 to 64 letters, digits, hyphens or underscores for the session name.");
|
|
4389
|
+
this.#starting = true;
|
|
4390
|
+
this.#render();
|
|
4391
|
+
try {
|
|
4392
|
+
const collab = await this.loadCollab();
|
|
4393
|
+
if (!collab.canBroadcast()) return this.#say("Sharing needs BroadcastChannel, which this browser does not provide.");
|
|
4394
|
+
this.#join(collab, roomId);
|
|
4395
|
+
} catch (error) {
|
|
4396
|
+
this.#say(error instanceof Error ? error.message : String(error));
|
|
4397
|
+
} finally {
|
|
4398
|
+
this.#starting = false;
|
|
4399
|
+
this.#render();
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
stop() {
|
|
4403
|
+
this.#teardown?.();
|
|
4404
|
+
this.#teardown = null;
|
|
4405
|
+
this.#session?.destroy();
|
|
4406
|
+
this.#session = null;
|
|
4407
|
+
this.#pending = null;
|
|
4408
|
+
this.#revision = 0;
|
|
4409
|
+
this.#last = null;
|
|
4410
|
+
this.#people.participants = [];
|
|
4411
|
+
this.#say("Not sharing.");
|
|
4412
|
+
this.#render();
|
|
4413
|
+
}
|
|
4414
|
+
#join(collab, roomId) {
|
|
4415
|
+
const adapter = collab.packageAdapter();
|
|
4416
|
+
const me = this.profile();
|
|
4417
|
+
const session = collab.createCollabSession({
|
|
4418
|
+
roomId: `visio-${roomId}`,
|
|
4419
|
+
provider: collab.transportProvider({ transport: collab.createBroadcastTransport({ roomId: `visio-${roomId}` }) }),
|
|
4420
|
+
user: {
|
|
4421
|
+
name: me.displayName || "Visio user",
|
|
4422
|
+
color: me.avatarColor
|
|
4423
|
+
},
|
|
4424
|
+
teardown: true
|
|
4425
|
+
});
|
|
4426
|
+
this.#session = session;
|
|
4427
|
+
const publish = () => {
|
|
4428
|
+
const bytes = this.#localBytes();
|
|
4429
|
+
if (!bytes || !session.canWrite()) return;
|
|
4430
|
+
adapter.write(session.doc, {
|
|
4431
|
+
bytes,
|
|
4432
|
+
revision: this.#revision
|
|
4433
|
+
}, collab.LOCAL_ORIGIN);
|
|
4434
|
+
this.#revision = adapter.read(session.doc).revision;
|
|
4435
|
+
this.#last = bytes;
|
|
4436
|
+
};
|
|
4437
|
+
const adopt = () => {
|
|
4438
|
+
if (adapter.isEmpty(session.doc)) return;
|
|
4439
|
+
const model = adapter.read(session.doc);
|
|
4440
|
+
if (model.revision < this.#revision) return;
|
|
4441
|
+
if (model.revision === this.#revision && same(model.bytes, this.#last)) return;
|
|
4442
|
+
this.#revision = model.revision;
|
|
4443
|
+
this.#last = model.bytes;
|
|
4444
|
+
this.#apply(model.bytes);
|
|
4445
|
+
};
|
|
4446
|
+
const map = session.doc.getMap("package");
|
|
4447
|
+
const observer = (_event, transaction) => {
|
|
4448
|
+
if (transaction.origin !== collab.LOCAL_ORIGIN) adopt();
|
|
4449
|
+
};
|
|
4450
|
+
map.observe(observer);
|
|
4451
|
+
const settle = () => adapter.isEmpty(session.doc) ? publish() : adopt();
|
|
4452
|
+
const stopReady = session.on("ready", settle);
|
|
4453
|
+
const stopPeers = session.on("peers", (peers) => this.#showPeers(peers));
|
|
4454
|
+
const stopEvents = this.controller.onEvent((name, detail) => {
|
|
4455
|
+
if (name === "document-change" && detail.kind !== "remote") publish();
|
|
4456
|
+
if (name === "document-load" && !this.#adopting) publish();
|
|
4457
|
+
});
|
|
4458
|
+
const stopState = this.controller.subscribe((state) => this.#flush(state));
|
|
4459
|
+
this.#teardown = () => {
|
|
4460
|
+
map.unobserve(observer);
|
|
4461
|
+
stopReady();
|
|
4462
|
+
stopPeers();
|
|
4463
|
+
stopEvents();
|
|
4464
|
+
stopState();
|
|
4465
|
+
};
|
|
4466
|
+
if (session.canWrite()) settle();
|
|
4467
|
+
this.#showPeers(session.peers());
|
|
4468
|
+
this.#say(`Sharing as ${me.displayName || "Visio user"} in session ${roomId}.`);
|
|
4469
|
+
}
|
|
4470
|
+
#localBytes() {
|
|
4471
|
+
const { edit, loading } = this.controller.state;
|
|
4472
|
+
if (!edit.sourceAvailable || edit.busy || loading) return null;
|
|
4473
|
+
try {
|
|
4474
|
+
return this.controller.exportVsdx().bytes;
|
|
4475
|
+
} catch {
|
|
4476
|
+
return null;
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
#apply(bytes) {
|
|
4480
|
+
const { edit, loading, document } = this.controller.state;
|
|
4481
|
+
if (edit.busy || loading) {
|
|
4482
|
+
this.#pending = bytes;
|
|
4483
|
+
return;
|
|
4484
|
+
}
|
|
4485
|
+
this.#pending = null;
|
|
4486
|
+
const done = (error) => {
|
|
4487
|
+
this.#adopting = false;
|
|
4488
|
+
if (error) this.#say(error instanceof Error ? error.message : String(error));
|
|
4489
|
+
};
|
|
4490
|
+
this.#adopting = true;
|
|
4491
|
+
(document && edit.sourceAvailable ? this.controller.applyRemoteSource(bytes) : this.controller.load(bytes)).then(() => done(), done);
|
|
4492
|
+
}
|
|
4493
|
+
#flush(state) {
|
|
4494
|
+
if (this.#pending && !state.edit.busy && !state.loading) this.#apply(this.#pending);
|
|
4495
|
+
}
|
|
4496
|
+
#showPeers(peers) {
|
|
4497
|
+
const me = this.profile();
|
|
4498
|
+
this.#people.participants = [{
|
|
4499
|
+
id: "self",
|
|
4500
|
+
name: me.displayName || "Visio user",
|
|
4501
|
+
color: me.avatarColor,
|
|
4502
|
+
self: true
|
|
4503
|
+
}, ...peers.map((peer) => ({
|
|
4504
|
+
id: String(peer.clientId),
|
|
4505
|
+
name: peer.userName,
|
|
4506
|
+
color: peer.userColor
|
|
4507
|
+
}))];
|
|
4508
|
+
}
|
|
4509
|
+
#say(message) {
|
|
4510
|
+
this.#status.textContent = message;
|
|
4511
|
+
}
|
|
4512
|
+
#render() {
|
|
4513
|
+
const active = this.active;
|
|
4514
|
+
this.#start.hidden = active;
|
|
4515
|
+
this.#stop.hidden = !active;
|
|
4516
|
+
this.#start.disabled = this.#starting;
|
|
4517
|
+
this.#room.disabled = active || this.#starting;
|
|
4518
|
+
}
|
|
4519
|
+
};
|
|
4520
|
+
function same(a, b) {
|
|
4521
|
+
if (!b || a.byteLength !== b.byteLength) return false;
|
|
4522
|
+
for (let i = 0; i < a.byteLength; i++) if (a[i] !== b[i]) return false;
|
|
4523
|
+
return true;
|
|
4524
|
+
}
|
|
4525
|
+
//#endregion
|
|
4216
4526
|
//#region src/edit-error.ts
|
|
4217
4527
|
/** Core error codes remain visible for a refused transaction; document content is plain text. */
|
|
4218
4528
|
function editErrorMessage(error) {
|
|
@@ -4443,12 +4753,6 @@ function wireStencil(pane, viewport, controller, announce) {
|
|
|
4443
4753
|
}
|
|
4444
4754
|
//#endregion
|
|
4445
4755
|
//#region src/viewer-ruler.ts
|
|
4446
|
-
/**
|
|
4447
|
-
* Visio's rulers: inch scales along the top and left of the drawing window with 0 at the page's
|
|
4448
|
-
* left and bottom edges (vertical values grow upward, as in Visio). Viewer-only presentation:
|
|
4449
|
-
* nothing is read from or written to the document beyond the rendered page box.
|
|
4450
|
-
*/
|
|
4451
|
-
var SIZE = 18;
|
|
4452
4756
|
/** Wraps the viewport in a grid with a corner, a top ruler and a left ruler. */
|
|
4453
4757
|
function createRulers(viewport) {
|
|
4454
4758
|
const doc = viewport.ownerDocument;
|
|
@@ -4456,79 +4760,35 @@ function createRulers(viewport) {
|
|
|
4456
4760
|
area.className = "canvas-area";
|
|
4457
4761
|
const corner = doc.createElement("div");
|
|
4458
4762
|
corner.className = "ruler-corner";
|
|
4459
|
-
|
|
4763
|
+
corner.setAttribute("aria-hidden", "true");
|
|
4764
|
+
const top = doc.createElement("office-ui-ruler");
|
|
4460
4765
|
top.className = "ruler ruler-h";
|
|
4461
|
-
const left = doc.createElement("
|
|
4766
|
+
const left = doc.createElement("office-ui-ruler");
|
|
4462
4767
|
left.className = "ruler ruler-v";
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
top,
|
|
4466
|
-
left
|
|
4467
|
-
]) ruler.setAttribute("aria-hidden", "true");
|
|
4768
|
+
left.setAttribute("orientation", "vertical");
|
|
4769
|
+
left.setAttribute("direction", "reverse");
|
|
4468
4770
|
viewport.replaceWith(area);
|
|
4469
4771
|
area.append(corner, top, left, viewport);
|
|
4470
4772
|
let visible = false;
|
|
4471
4773
|
let zoom = 1;
|
|
4472
4774
|
let frame = 0;
|
|
4473
|
-
const
|
|
4775
|
+
const place = () => {
|
|
4474
4776
|
frame = 0;
|
|
4475
4777
|
if (!visible) return;
|
|
4476
4778
|
const paper = viewport.querySelector("svg.paper");
|
|
4477
|
-
|
|
4478
|
-
const ink = (doc.defaultView?.getComputedStyle(area))?.getPropertyValue("--_vv-muted").trim() || "#605e5c";
|
|
4779
|
+
if (!paper) return;
|
|
4479
4780
|
const base = viewport.getBoundingClientRect();
|
|
4480
|
-
const page = paper
|
|
4481
|
-
const
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
canvas.height = Math.round(SIZE * ratio);
|
|
4487
|
-
if (!horizontal) [canvas.width, canvas.height] = [canvas.height, canvas.width];
|
|
4488
|
-
const context = canvas.getContext?.("2d");
|
|
4489
|
-
if (!context || !page) return;
|
|
4490
|
-
context.scale(ratio, ratio);
|
|
4491
|
-
context.strokeStyle = ink;
|
|
4492
|
-
context.fillStyle = ink;
|
|
4493
|
-
context.lineWidth = 1;
|
|
4494
|
-
context.font = "9px \"Segoe UI\", Arial, sans-serif";
|
|
4495
|
-
const origin = horizontal ? page.left - base.left : page.bottom - base.top;
|
|
4496
|
-
const step = inch / divisions;
|
|
4497
|
-
const first = Math.floor((horizontal ? -origin : origin - length) / step) - 1;
|
|
4498
|
-
const last = Math.ceil((horizontal ? length - origin : origin) / step) + 1;
|
|
4499
|
-
context.beginPath();
|
|
4500
|
-
for (let index = first; index <= last; index++) {
|
|
4501
|
-
const position = horizontal ? origin + index * step : origin - index * step;
|
|
4502
|
-
const tick = index % divisions === 0 ? SIZE : index % (divisions / 2) === 0 ? SIZE * .5 : SIZE * .3;
|
|
4503
|
-
const at = Math.round(position) + .5;
|
|
4504
|
-
if (horizontal) {
|
|
4505
|
-
context.moveTo(at, SIZE);
|
|
4506
|
-
context.lineTo(at, SIZE - tick);
|
|
4507
|
-
} else {
|
|
4508
|
-
context.moveTo(SIZE, at);
|
|
4509
|
-
context.lineTo(SIZE - tick, at);
|
|
4510
|
-
}
|
|
4511
|
-
if (index % divisions === 0) {
|
|
4512
|
-
const label = String(index / divisions);
|
|
4513
|
-
if (horizontal) context.fillText(label, at + 2, 9);
|
|
4514
|
-
else {
|
|
4515
|
-
context.save();
|
|
4516
|
-
context.translate(9, at - 2);
|
|
4517
|
-
context.rotate(-Math.PI / 2);
|
|
4518
|
-
context.fillText(label, 0, 0);
|
|
4519
|
-
context.restore();
|
|
4520
|
-
}
|
|
4521
|
-
}
|
|
4522
|
-
}
|
|
4523
|
-
context.stroke();
|
|
4524
|
-
};
|
|
4525
|
-
paint(top, true);
|
|
4526
|
-
paint(left, false);
|
|
4781
|
+
const page = paper.getBoundingClientRect();
|
|
4782
|
+
const scale = String(96 * zoom);
|
|
4783
|
+
top.setAttribute("scale", scale);
|
|
4784
|
+
left.setAttribute("scale", scale);
|
|
4785
|
+
top.setAttribute("origin", String(page.left - base.left));
|
|
4786
|
+
left.setAttribute("origin", String(page.bottom - base.top));
|
|
4527
4787
|
};
|
|
4528
4788
|
const schedule = () => {
|
|
4529
4789
|
if (!visible || frame) return;
|
|
4530
|
-
frame = doc.defaultView?.requestAnimationFrame?.(
|
|
4531
|
-
if (!frame)
|
|
4790
|
+
frame = doc.defaultView?.requestAnimationFrame?.(place) ?? 0;
|
|
4791
|
+
if (!frame) place();
|
|
4532
4792
|
};
|
|
4533
4793
|
return {
|
|
4534
4794
|
element: area,
|
|
@@ -4912,8 +5172,6 @@ var ViewerChrome = class {
|
|
|
4912
5172
|
this.#root.addEventListener("click", (event) => {
|
|
4913
5173
|
const button = event.target?.closest?.("button");
|
|
4914
5174
|
if (!button || button.disabled) return;
|
|
4915
|
-
const tab = button.dataset.tab;
|
|
4916
|
-
if (tab) this.showTab(tab);
|
|
4917
5175
|
const action = button.dataset.chrome;
|
|
4918
5176
|
if (action === "inspector" || action === "shapes") this.togglePane(action);
|
|
4919
5177
|
if (action === "notes") this.reveal("notes");
|
|
@@ -4929,33 +5187,13 @@ var ViewerChrome = class {
|
|
|
4929
5187
|
event.stopPropagation();
|
|
4930
5188
|
this.#controller.setPage(Number(index));
|
|
4931
5189
|
}, options);
|
|
4932
|
-
this.#root.addEventListener("
|
|
4933
|
-
const key = event;
|
|
4934
|
-
const button = event.target?.closest?.("button");
|
|
4935
|
-
if (!button?.dataset.tab || ![
|
|
4936
|
-
"ArrowLeft",
|
|
4937
|
-
"ArrowRight",
|
|
4938
|
-
"Home",
|
|
4939
|
-
"End"
|
|
4940
|
-
].includes(key.key)) return;
|
|
4941
|
-
key.preventDefault();
|
|
4942
|
-
const tabs = [...this.#root.querySelectorAll("[data-tab]")];
|
|
4943
|
-
const index = tabs.indexOf(button);
|
|
4944
|
-
const next = key.key === "Home" ? tabs[0] : key.key === "End" ? tabs.at(-1) : tabs[(index + (key.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length];
|
|
4945
|
-
if (!next?.dataset.tab) return;
|
|
4946
|
-
this.showTab(next.dataset.tab);
|
|
4947
|
-
next.focus();
|
|
4948
|
-
}, options);
|
|
5190
|
+
this.#root.addEventListener("office-ribbon-select", () => this.closeCompactTools(), options);
|
|
4949
5191
|
this.#notes.addEventListener("toggle", () => this.#syncNotes(), options);
|
|
4950
5192
|
return () => events.abort();
|
|
4951
5193
|
}
|
|
4952
5194
|
showTab(tab) {
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
button.setAttribute("aria-selected", String(selected));
|
|
4956
|
-
button.tabIndex = selected ? 0 : -1;
|
|
4957
|
-
}
|
|
4958
|
-
for (const panel of this.#root.querySelectorAll(".ribbon-content")) panel.hidden = panel.id !== `${tab}-panel`;
|
|
5195
|
+
const ribbon = this.#root.querySelector("office-ui-ribbon");
|
|
5196
|
+
if (ribbon) ribbon.selected = tab;
|
|
4959
5197
|
}
|
|
4960
5198
|
/** On phones, close the Tools sheet once a command runs. */
|
|
4961
5199
|
closeCompactTools() {
|
|
@@ -6202,6 +6440,7 @@ var VISIO_ICONS = {
|
|
|
6202
6440
|
function registerViewerControls(registry) {
|
|
6203
6441
|
for (const [name, path] of Object.entries(VISIO_ICONS)) if (!getIcon(name)) registerIcon(name, path);
|
|
6204
6442
|
registerControls(registry);
|
|
6443
|
+
definePresence(registry);
|
|
6205
6444
|
}
|
|
6206
6445
|
//#endregion
|
|
6207
6446
|
//#region src/export-svg.ts
|
|
@@ -6821,6 +7060,8 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6821
7060
|
#rulers;
|
|
6822
7061
|
#canvas;
|
|
6823
7062
|
#panZoom;
|
|
7063
|
+
#profile;
|
|
7064
|
+
#share;
|
|
6824
7065
|
#backstage;
|
|
6825
7066
|
#fileName = "";
|
|
6826
7067
|
#loadToken = 0;
|
|
@@ -6829,7 +7070,6 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6829
7070
|
#diagnostics;
|
|
6830
7071
|
#toolbar;
|
|
6831
7072
|
#chrome;
|
|
6832
|
-
#search;
|
|
6833
7073
|
#edit;
|
|
6834
7074
|
#layers;
|
|
6835
7075
|
#notes;
|
|
@@ -6855,7 +7095,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6855
7095
|
workspace.before(ribbon, this.#findBar);
|
|
6856
7096
|
workspace.prepend(createShapesStrip(document), createShapesWindow(document));
|
|
6857
7097
|
workspace.append(createPanZoom(document));
|
|
6858
|
-
this.#root.append(createBackstage(document), ...createContextMenus(document));
|
|
7098
|
+
this.#root.append(createBackstage(document), createOptionsDialog(document), ...createContextMenus(document));
|
|
6859
7099
|
workspace.after(createPageTabs(document), createStatusBar(document));
|
|
6860
7100
|
this.#viewport = this.#root.querySelector(".viewport");
|
|
6861
7101
|
this.#rulers = createRulers(this.#viewport);
|
|
@@ -6864,7 +7104,6 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6864
7104
|
this.#status = this.#root.querySelector("[data-status]");
|
|
6865
7105
|
this.#diagnostics = this.#root.querySelector("[data-diagnostics]");
|
|
6866
7106
|
this.#toolbar = this.#root.querySelector(".toolbar");
|
|
6867
|
-
this.#search = searchControls(this.#root);
|
|
6868
7107
|
this.#edit = new ViewerEditControls(this.#root, this.controller);
|
|
6869
7108
|
this.#layers = this.#root.querySelector(".layer-controls");
|
|
6870
7109
|
this.#notes = this.#root.querySelector(".notes ul");
|
|
@@ -6882,15 +7121,15 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6882
7121
|
togglePanZoom: () => this.#panZoom.toggle(),
|
|
6883
7122
|
focusSearch: () => {
|
|
6884
7123
|
this.#chrome.closeCompactTools();
|
|
6885
|
-
this.#findBar.
|
|
6886
|
-
this.#search.input.focus();
|
|
6887
|
-
this.#search.input.select();
|
|
7124
|
+
this.#findBar.show();
|
|
6888
7125
|
},
|
|
6889
7126
|
announce: (message) => {
|
|
6890
7127
|
this.#announcement = message;
|
|
6891
7128
|
this.#status.textContent = message;
|
|
6892
7129
|
}
|
|
6893
7130
|
});
|
|
7131
|
+
this.#profile = new ViewerProfile(this.#root);
|
|
7132
|
+
this.#share = new ViewerShare(this.#root, this.controller, () => this.#profile.profile);
|
|
6894
7133
|
this.#backstage = new ViewerBackstage({
|
|
6895
7134
|
root: this.#root,
|
|
6896
7135
|
viewport: this.#viewport,
|
|
@@ -6903,6 +7142,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
6903
7142
|
this.controller.setDocument(null);
|
|
6904
7143
|
},
|
|
6905
7144
|
revealNotes: () => this.#chrome.reveal("notes"),
|
|
7145
|
+
showOptions: () => this.#profile.showOptions(),
|
|
6906
7146
|
announce: (message) => {
|
|
6907
7147
|
this.#announcement = message;
|
|
6908
7148
|
this.#status.textContent = message;
|
|
@@ -7080,19 +7320,19 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
7080
7320
|
const disposeTellMe = wireTellMe(this.#root);
|
|
7081
7321
|
const keyTips = attachKeyTips(this.#root);
|
|
7082
7322
|
const disposePanZoom = this.#panZoom.wire();
|
|
7323
|
+
const disposeProfile = this.#profile.wire();
|
|
7324
|
+
const disposeShare = this.#share.wire();
|
|
7083
7325
|
const disposeRulers = this.#rulers.wire();
|
|
7084
7326
|
const disposeStencil = wireStencil(this.#root.querySelector(".shapes-pane"), this.#viewport, this.controller, (message) => {
|
|
7085
7327
|
this.#announcement = message;
|
|
7086
7328
|
this.#status.textContent = message;
|
|
7087
7329
|
});
|
|
7088
|
-
const disposeFind = wireFindBar(this.#findBar, this
|
|
7330
|
+
const disposeFind = wireFindBar(this.#findBar, this.controller, () => this.#viewport.focus({ preventScroll: true }));
|
|
7089
7331
|
const disposeEdit = this.#edit.wire();
|
|
7090
7332
|
const disposeLayers = wireLayerControls(this.#layers, this.controller);
|
|
7091
7333
|
const disposeInputs = wireViewerInputs({
|
|
7092
7334
|
viewport: this.#viewport,
|
|
7093
|
-
|
|
7094
|
-
zoomSlider: this.#zoomSlider,
|
|
7095
|
-
searchInput: this.#search.input
|
|
7335
|
+
zoomSlider: this.#zoomSlider
|
|
7096
7336
|
}, this.controller, (mode) => this.#fit(mode));
|
|
7097
7337
|
return () => {
|
|
7098
7338
|
disposeChrome();
|
|
@@ -7102,6 +7342,8 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
7102
7342
|
disposeTellMe();
|
|
7103
7343
|
keyTips.dispose();
|
|
7104
7344
|
disposePanZoom();
|
|
7345
|
+
disposeProfile();
|
|
7346
|
+
disposeShare();
|
|
7105
7347
|
disposeRulers();
|
|
7106
7348
|
disposeStencil();
|
|
7107
7349
|
disposeFind();
|
|
@@ -7124,7 +7366,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
7124
7366
|
const inches = (value) => `${+value.toFixed(3)} in`;
|
|
7125
7367
|
this.#shapeStatus.setAttribute("value", inspected ? `Width: ${inches(inspected.width)} Height: ${inches(inspected.height)}` : "");
|
|
7126
7368
|
for (const button of this.#root.querySelectorAll("[data-action]")) button.disabled = !page;
|
|
7127
|
-
|
|
7369
|
+
renderFindBar(this.#findBar, state);
|
|
7128
7370
|
this.#edit.render(state);
|
|
7129
7371
|
if (changed) renderLayerControls(this.#layers, state);
|
|
7130
7372
|
else this.#layers.querySelector("[data-layer-reset=\"all\"]").disabled = state.layerVisibilityOverrides.length === 0;
|