visio-solid-viewer 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{edit-worker-DRgXgRud.js → edit-worker-BcXrV_St.js} +203 -31
- package/dist/index.js +870 -159
- package/dist/index.server.js +869 -158
- package/dist/types/packages/bindings/src/common.d.ts +11 -0
- package/dist/types/packages/bindings/src/solid.d.ts +8 -3
- package/dist/types/src/office-ui.d.ts +2 -0
- package/dist/types/src/ribbon-action.d.ts +34 -0
- package/dist/types/src/ribbon-home.d.ts +6 -0
- package/dist/types/src/ribbon-parts.d.ts +20 -0
- package/dist/types/src/ribbon-router.d.ts +17 -0
- package/dist/types/src/ribbon-view.d.ts +2 -0
- package/dist/types/src/ribbon.d.ts +6 -0
- package/dist/types/src/status-bar.d.ts +7 -0
- package/dist/types/src/styles/index.d.ts +5 -0
- package/dist/types/src/styles.d.ts +1 -1
- package/dist/types/src/viewer-chrome.d.ts +11 -2
- package/dist/types/src/viewer-commands.d.ts +31 -0
- package/dist/types/src/viewer-draw-tool.d.ts +17 -0
- package/dist/types/src/viewer-input.d.ts +4 -2
- package/dist/types/src/viewer-search.d.ts +2 -1
- package/package.json +4 -3
package/dist/index.server.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { VISIO_RASTER_IMAGE_LIMITS, VISIO_VISIBILITY_LIMITS, getVisioPageLayers, inspectVisioRasterImage, parseVsdx, resolveVisioPageVisibility, validateVisioForeignVector } from "ooxml-core/visio";
|
|
2
|
+
import { registerControls } from "ooxml-ui/controls";
|
|
3
|
+
import { getIcon, registerIcon } from "ooxml-ui/icons";
|
|
2
4
|
import { escape, ssr, ssrAttribute, ssrHydrationKey, ssrStyle } from "solid-js/web";
|
|
3
5
|
import { createEffect, createSignal, onCleanup, onMount } from "solid-js";
|
|
4
6
|
export * from "visio-core";
|
|
@@ -2136,7 +2138,7 @@ function selection(target) {
|
|
|
2136
2138
|
}
|
|
2137
2139
|
/** Own every DOM listener and abort them together when the surface is disposed. */
|
|
2138
2140
|
function wireViewerInputs(controls, controller, fit) {
|
|
2139
|
-
const { viewport, commandRoot,
|
|
2141
|
+
const { viewport, commandRoot, zoomSlider, searchInput } = controls;
|
|
2140
2142
|
const events = new ((viewport.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
2141
2143
|
const options = { signal: events.signal };
|
|
2142
2144
|
searchInput.addEventListener("input", () => controller.setSearchQuery(searchInput.value), options);
|
|
@@ -2152,13 +2154,9 @@ function wireViewerInputs(controls, controller, fit) {
|
|
|
2152
2154
|
controller.setSearchQuery("");
|
|
2153
2155
|
}
|
|
2154
2156
|
}, options);
|
|
2155
|
-
|
|
2157
|
+
zoomSlider.addEventListener("input", () => controller.setZoom(zoomSlider.value / 100), options);
|
|
2156
2158
|
commandRoot.addEventListener("click", (event) => {
|
|
2157
2159
|
const action = event.target?.closest?.("button")?.dataset.action;
|
|
2158
|
-
if (action === "in") controller.setZoom(controller.state.zoom * 1.25);
|
|
2159
|
-
if (action === "out") controller.setZoom(controller.state.zoom / 1.25);
|
|
2160
|
-
if (action === "fit") fit();
|
|
2161
|
-
if (action === "actual") controller.setZoom(1);
|
|
2162
2160
|
if (action === "search-next") controller.nextSearchResult();
|
|
2163
2161
|
if (action === "search-previous") controller.previousSearchResult();
|
|
2164
2162
|
}, options);
|
|
@@ -2197,7 +2195,7 @@ function wireViewerInputs(controls, controller, fit) {
|
|
|
2197
2195
|
}
|
|
2198
2196
|
if (event.key === "0") {
|
|
2199
2197
|
event.preventDefault();
|
|
2200
|
-
fit();
|
|
2198
|
+
fit("page");
|
|
2201
2199
|
}
|
|
2202
2200
|
}, options);
|
|
2203
2201
|
return () => events.abort();
|
|
@@ -2301,13 +2299,7 @@ button:disabled { opacity:.4; cursor:default; }
|
|
|
2301
2299
|
button:focus-visible,select:focus-visible,input:focus-visible,textarea:focus-visible,summary:focus-visible,[tabindex]:focus-visible { outline:2px solid var(--vv-focus,var(--_vv-accent)); outline-offset:2px; }
|
|
2302
2300
|
button[aria-pressed="true"] { color:var(--_vv-accent); background:var(--_vv-accent-soft); }
|
|
2303
2301
|
.toolbar { flex:none; min-width:0; border-bottom:1px solid var(--_vv-border); background:var(--_vv-surface); }
|
|
2304
|
-
.ribbon-primary { height:32px; display:flex; align-items:center; gap:6px; padding:2px 8px; }
|
|
2305
|
-
.page-picker { display:flex; min-width:0; align-items:center; gap:8px; color:var(--_vv-muted); font-size:11px; }
|
|
2306
|
-
.page-picker select { width:190px; max-width:42vw; height:26px; min-height:26px; padding:2px 6px; font-size:11px; color:var(--_vv-ink); }
|
|
2307
|
-
.page-stepper { display:flex; align-items:center; gap:2px; }
|
|
2308
|
-
.page-stepper button { width:26px; height:26px; min-height:26px; padding:0; font-size:18px; line-height:22px; background:transparent; border-color:transparent; }
|
|
2309
2302
|
.spacer { flex:1; }
|
|
2310
|
-
.local-label { color:var(--_vv-muted); font-size:10px; white-space:nowrap; }
|
|
2311
2303
|
.ribbon-tabs { height:35px; display:flex; align-items:stretch; padding:0 4px; border-bottom:1px solid var(--_vv-border); }
|
|
2312
2304
|
.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; }
|
|
2313
2305
|
.ribbon-tabs button[aria-selected="true"] { color:var(--_vv-accent); }
|
|
@@ -2317,15 +2309,6 @@ button[aria-pressed="true"] { color:var(--_vv-accent); background:var(--_vv-acce
|
|
|
2317
2309
|
.group-label { font-size:10px; line-height:12px; color:var(--_vv-muted); text-align:center; }
|
|
2318
2310
|
.ribbon-actions { display:flex; align-items:stretch; gap:2px; flex:1; }
|
|
2319
2311
|
.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; }
|
|
2320
|
-
.command-icon { display:grid; place-items:center; width:26px; height:26px; color:var(--_vv-accent); }
|
|
2321
|
-
.command-icon::before { content:""; width:24px; height:24px; background:currentColor; mask:var(--command-symbol) center / contain no-repeat; }
|
|
2322
|
-
[data-icon="selection"] { --command-symbol:url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m5%203%2013%209-6%201-3%206z%22%2F%3E%3C%2Fsvg%3E"); }
|
|
2323
|
-
[data-icon="edit"] { --command-symbol:url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%205h16M12%205v14M8%2019h8%22%2F%3E%3C%2Fsvg%3E"); }
|
|
2324
|
-
[data-icon="layers"] { --command-symbol:url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22m3%208%209-5%209%205-9%205zM3%2012l9%205%209-5M3%2016l9%205%209-5%22%2F%3E%3C%2Fsvg%3E"); }
|
|
2325
|
-
[data-icon="pages"] { --command-symbol:url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%225%22%20y%3D%223%22%20width%3D%2214%22%20height%3D%2218%22%20rx%3D%221%22%2F%3E%3Cpath%20d%3D%22M8%208h8M8%2012h8M8%2016h5%22%2F%3E%3C%2Fsvg%3E"); }
|
|
2326
|
-
[data-icon="inspector"] { --command-symbol:url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%223%22%20y%3D%224%22%20width%3D%2218%22%20height%3D%2216%22%20rx%3D%221%22%2F%3E%3Cpath%20d%3D%22M14%204v16M17%208h1M17%2012h1M17%2016h1%22%2F%3E%3C%2Fsvg%3E"); }
|
|
2327
|
-
[data-icon="notes"] { --command-symbol:url("data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.6%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%204h16v12H9l-5%204z%22%2F%3E%3Cpath%20d%3D%22M8%208h8M8%2012h5%22%2F%3E%3C%2Fsvg%3E"); }
|
|
2328
|
-
.ribbon-command[data-chrome="edit"] .command-icon { font-family:ui-serif,Georgia,serif; text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:3px; }
|
|
2329
2312
|
.ribbon-hint { align-self:center; flex:none; margin:0; padding:0 16px; font-size:11px; line-height:18px; color:var(--_vv-muted); }
|
|
2330
2313
|
.search-group { width:330px; }
|
|
2331
2314
|
.search-controls { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
|
|
@@ -2397,11 +2380,6 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
2397
2380
|
.status { flex:none; display:flex; align-items:center; justify-content:space-between; gap:12px; min-width:0; min-height:29px; padding:0 8px 0 12px; border-top:1px solid var(--_vv-border); color:var(--_vv-muted); background:var(--_vv-surface); font-size:10px; }
|
|
2398
2381
|
.status-message { display:flex; gap:16px; min-width:0; }
|
|
2399
2382
|
.status-message span { overflow:hidden; white-space:nowrap; text-overflow:ellipsis; }
|
|
2400
|
-
.zoom-controls { display:flex; flex:none; align-items:center; gap:3px; }
|
|
2401
|
-
.zoom-controls button { min-height:24px; height:24px; padding:1px 6px; border-color:transparent; background:transparent; font-size:10px; }
|
|
2402
|
-
.zoom-controls [data-action="in"],.zoom-controls [data-action="out"] { min-width:24px; padding:0; font-size:17px; line-height:20px; }
|
|
2403
|
-
.zoom-divider { width:1px; height:14px; background:var(--_vv-border); margin:0 3px; }
|
|
2404
|
-
.zoom { min-width:40px; font-size:10px; text-align:center; font-variant-numeric:tabular-nums; }
|
|
2405
2383
|
.empty { max-width:360px; margin:60px auto; padding:16px; text-align:center; color:var(--_vv-muted); font-size:12px; }
|
|
2406
2384
|
.empty strong { display:block; color:var(--_vv-ink); font-size:18px; font-weight:500; margin-bottom:8px; }
|
|
2407
2385
|
@media(max-width:1100px) { .ribbon-hint { display:none; } [data-diagnostics] { display:none; } }
|
|
@@ -2409,11 +2387,6 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
2409
2387
|
button,select,input,textarea { min-height:44px; }
|
|
2410
2388
|
button { min-width:44px; }
|
|
2411
2389
|
summary { min-height:44px; line-height:44px; }
|
|
2412
|
-
.ribbon-primary { height:52px; padding:4px 8px; }
|
|
2413
|
-
.page-picker { flex:1; }
|
|
2414
|
-
.page-picker select { width:100%; max-width:none; min-height:44px; height:44px; font-size:12px; }
|
|
2415
|
-
.page-stepper button { width:44px; min-height:44px; height:44px; }
|
|
2416
|
-
.local-label,.ribbon-primary>.spacer { display:none; }
|
|
2417
2390
|
.ribbon-tabs { height:44px; }
|
|
2418
2391
|
.ribbon-tabs button { min-height:44px; padding:8px 16px; }
|
|
2419
2392
|
.ribbon-content { min-height:86px; }
|
|
@@ -2425,7 +2398,7 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
2425
2398
|
.search-controls button { min-width:44px; font-size:0; padding:3px; }
|
|
2426
2399
|
.search-controls [data-action="search-previous"]::before { content:"‹"; font-size:22px; }
|
|
2427
2400
|
.search-controls [data-action="search-next"]::before { content:"›"; font-size:22px; }
|
|
2428
|
-
#home-panel>.ribbon-group:not(.search-group) { display:none; }
|
|
2401
|
+
#home-panel>.ribbon-group:not(.search-group):not(.undo-group) { display:none; }
|
|
2429
2402
|
.ribbon-command { min-width:90px; }
|
|
2430
2403
|
.workspace { display:block; overflow:auto; }
|
|
2431
2404
|
.page-rail { width:100%; max-height:190px; border-right:0; border-bottom:1px solid var(--_vv-border); }
|
|
@@ -2442,35 +2415,22 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
2442
2415
|
.notes-strip button { min-height:44px; }
|
|
2443
2416
|
.status { flex-wrap:wrap; gap:0; padding:4px 8px; }
|
|
2444
2417
|
.status-message { width:100%; min-height:18px; }
|
|
2445
|
-
.zoom-controls { justify-content:flex-end; width:100%; }
|
|
2446
|
-
.zoom-controls button { min-height:44px; height:44px; min-width:44px; font-size:11px; }
|
|
2447
|
-
.zoom-controls [data-action="in"],.zoom-controls [data-action="out"] { min-width:44px; font-size:20px; }
|
|
2448
|
-
.zoom { font-size:11px; }
|
|
2449
2418
|
}
|
|
2450
2419
|
@media(pointer:coarse) {
|
|
2451
2420
|
button,select,input,textarea { min-height:44px; }
|
|
2452
2421
|
button { min-width:44px; }
|
|
2453
2422
|
summary,.layer-controls label { min-height:44px; line-height:44px; }
|
|
2454
|
-
.ribbon-primary { min-height:52px; height:52px; }
|
|
2455
|
-
.page-picker select,.page-stepper button { min-height:44px; height:44px; }
|
|
2456
|
-
.page-stepper button { min-width:44px; }
|
|
2457
2423
|
.ribbon-tabs { min-height:44px; height:44px; }
|
|
2458
2424
|
.ribbon-tabs button { min-height:44px; }
|
|
2459
2425
|
.ribbon-content { min-height:96px; }
|
|
2460
2426
|
.search-group { width:380px; max-width:100%; }
|
|
2461
|
-
.
|
|
2462
|
-
.zoom-controls [data-action="in"],.zoom-controls [data-action="out"] { min-width:44px; }
|
|
2427
|
+
.notes-strip button { min-height:44px; height:44px; }
|
|
2463
2428
|
.layer-controls input { min-height:20px; }
|
|
2464
2429
|
}
|
|
2465
|
-
/* Suite ribbon: tabs lead directly into command groups
|
|
2430
|
+
/* Suite ribbon: tabs lead directly into Visio command groups. */
|
|
2466
2431
|
.ribbon-tabs { height:40px; }
|
|
2467
2432
|
.ribbon-tabs button { min-height:39px; padding:8px 16px; }
|
|
2468
2433
|
.ribbon-content { min-height:104px; padding:4px 4px; }
|
|
2469
|
-
.ribbon-primary { height:auto; min-height:92px; width:238px; flex:none; flex-wrap:wrap; justify-content:center; gap:2px; padding:10px 12px 18px; border-right:1px solid var(--_vv-border); position:relative; order:1; }
|
|
2470
|
-
.ribbon-primary .page-picker { flex-direction:column; align-items:start; gap:5px; }
|
|
2471
|
-
.ribbon-primary .page-picker select { width:160px; max-width:100%; }
|
|
2472
|
-
.ribbon-primary .spacer { display:none; }
|
|
2473
|
-
.ribbon-primary .local-label { position:absolute; bottom:4px; left:0; width:100%; text-align:center; }
|
|
2474
2434
|
.search-group { order:2; width:320px; }
|
|
2475
2435
|
.ribbon-hint { order:3; padding:10px 18px; }
|
|
2476
2436
|
.ribbon-command { min-width:82px; }
|
|
@@ -2479,13 +2439,9 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
2479
2439
|
.ribbon-tabs { height:44px; }
|
|
2480
2440
|
.ribbon-tabs button { min-height:44px; }
|
|
2481
2441
|
.ribbon-content { min-height:54px; padding:4px 0; }
|
|
2482
|
-
.ribbon-primary { min-height:44px; height:44px; width:100%; padding:0 8px; border:0; flex-wrap:nowrap; gap:4px; order:0; }
|
|
2483
|
-
.ribbon-primary .page-picker { flex-direction:row; flex:1; align-items:center; gap:8px; }
|
|
2484
|
-
.ribbon-primary .page-picker select { flex:1; width:100%; min-width:0; }
|
|
2485
2442
|
.search-group { flex:0 0 100%; width:100%; min-height:54px; }
|
|
2486
2443
|
.search-feedback,.group-label { display:none; }
|
|
2487
2444
|
.ribbon-content { overflow-x:auto; scroll-snap-type:x mandatory; }
|
|
2488
|
-
.ribbon-primary,.search-group { scroll-snap-align:start; }
|
|
2489
2445
|
.workspace { display:flex; position:relative; overflow:hidden; }
|
|
2490
2446
|
.viewport { flex:1; width:100%; height:100%; min-height:0; padding:16px 4px; }
|
|
2491
2447
|
.inspector-pane { position:absolute; inset:0 0 0 auto; width:min(320px,100%); z-index:4; overflow:auto; border-left:1px solid var(--_vv-border); box-shadow:-8px 0 24px #0002; }
|
|
@@ -2514,9 +2470,6 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
2514
2470
|
.status-message { flex:1; }
|
|
2515
2471
|
@media(max-width:760px) {
|
|
2516
2472
|
#home-panel { overflow:visible; min-height:54px; padding:4px; }
|
|
2517
|
-
.ribbon-primary { width:calc(100% - 92px); min-width:0; flex:1; padding:0 4px; }
|
|
2518
|
-
.ribbon-primary .page-picker { font-size:0; gap:0; }
|
|
2519
|
-
.ribbon-primary .page-picker select { font-size:12px; }
|
|
2520
2473
|
.ribbon-tools { display:block; flex:0 0 84px; width:84px; order:1; }
|
|
2521
2474
|
.ribbon-tools>summary { display:flex; min-height:44px; align-items:center; justify-content:center; gap:6px; padding:0 10px; border:1px solid var(--_vv-border); background:var(--_vv-surface); border-radius:4px; font-size:12px; cursor:pointer; list-style:none; }
|
|
2522
2475
|
.ribbon-tools>summary::-webkit-details-marker { display:none; }
|
|
@@ -2528,14 +2481,347 @@ summary::marker { color:var(--_vv-muted); font-size:10px; }
|
|
|
2528
2481
|
.ribbon-tools .search-group { order:1; flex:auto; }
|
|
2529
2482
|
.ribbon-tools .search-feedback { display:block; }
|
|
2530
2483
|
.status { flex-wrap:nowrap; gap:4px; min-height:48px; }
|
|
2531
|
-
.zoom-controls { width:auto; flex:1; gap:0; }
|
|
2532
|
-
.zoom-divider { margin:0; }
|
|
2533
2484
|
.status .notes-strip { display:none; }
|
|
2534
2485
|
}
|
|
2535
2486
|
@media(pointer:coarse) { .ribbon-tabs {height:45px;min-height:45px;} .ribbon-tabs button {min-height:44px;height:44px;} }
|
|
2536
2487
|
@media(prefers-reduced-motion:reduce) { * { scroll-behavior:auto !important; } }
|
|
2537
2488
|
`;
|
|
2538
2489
|
//#endregion
|
|
2490
|
+
//#region src/styles/index.ts
|
|
2491
|
+
/**
|
|
2492
|
+
* Viewer stylesheets as text for the shadow root. New styles are CSS files here; the legacy
|
|
2493
|
+
* `styles.ts` string moves into this directory when it is next changed.
|
|
2494
|
+
*/
|
|
2495
|
+
var canvasAndRibbonStyles = [":host{--office-foreground:var(--_vv-ink);--office-muted-foreground:var(--_vv-muted);--office-background:var(--_vv-surface);--office-surface:var(--_vv-accent-soft);--office-border:var(--_vv-border);--office-accent:var(--_vv-accent);--office-accent-foreground:var(--vv-accent-ink,#fff);--office-ring:var(--vv-focus,var(--_vv-accent));--office-font:\"Segoe UI\", Arial, sans-serif}.ribbon-row{background:0 0;border:0;flex-wrap:nowrap;align-items:stretch;gap:0;padding:0;display:flex}.ribbon-row office-ui-ribbon-group{min-width:0}.ribbon-row office-ui-button{--office-target-size:28px}.ribbon-row office-ui-button[variant=stacked]::part(button){min-width:64px;min-height:62px;font-size:12px}office-ui-button[disabled]::part(button){opacity:.45}.page-tabs{flex:none}office-ui-status-bar.status{background:var(--_vv-surface);min-height:29px;color:var(--_vv-muted);gap:16px;padding:0 8px 0 12px;font-size:11px}office-ui-status-item[value=\"\"]{display:none}office-ui-status-item[data-page-status]{color:var(--_vv-ink)}.status-message{flex:1;min-width:0}.viewport[data-tool=rectangle],.viewport[data-tool=rectangle] .paper [data-shape-id]{cursor:crosshair}.viewport[data-tool=rectangle]{touch-action:none}.draw-preview{fill:var(--_vv-accent-soft);stroke:var(--_vv-accent);stroke-width:1.5px;stroke-dasharray:4 3;pointer-events:none}.viewport[data-grid=true] .paper{--_vv-grid-minor:#00000012;--_vv-grid-major:#00000029;background-image:linear-gradient(to right, var(--_vv-grid-major) 1px, transparent 1px), linear-gradient(to top, var(--_vv-grid-major) 1px, transparent 1px), linear-gradient(to right, var(--_vv-grid-minor) 1px, transparent 1px), linear-gradient(to top, var(--_vv-grid-minor) 1px, transparent 1px);background-size:var(--vv-inch,96px) var(--vv-inch,96px), var(--vv-inch,96px) var(--vv-inch,96px), calc(var(--vv-inch,96px) / 4) calc(var(--vv-inch,96px) / 4), calc(var(--vv-inch,96px) / 4) calc(var(--vv-inch,96px) / 4);background-position:0 100%}:host(:fullscreen){width:100vw;height:100vh}@media (width<=760px){.ribbon-row{flex:1;min-width:0}.undo-group{flex:none}.ribbon-row office-ui-button[variant=stacked]::part(button){min-width:52px;min-height:44px}.ribbon-tools .tools-content office-ui-ribbon-group{width:100%}.search-group{scroll-snap-align:start}office-ui-status-bar.status{gap:4px;min-height:48px}office-ui-status-bar .notes-strip,office-ui-status-bar .status-message,office-ui-status-item[data-shape-status],office-ui-status-item[data-page-status]{display:none}.zoom-controls{--office-zoom-slider-width:0;flex:1;min-width:0}}", ".paper [data-shape-id]:focus,.paper [data-shape-id]:focus-visible{outline:none}.paper [data-shape-id]:focus-visible>[data-geometry]{stroke-dasharray:.08 .05;stroke:var(--vv-focus,var(--_vv-accent))!important;stroke-width:.04px!important}"].join("\n");
|
|
2496
|
+
//#endregion
|
|
2497
|
+
//#region src/ribbon-action.ts
|
|
2498
|
+
/** Event name shared by every ribbon control; detail is the typed action. */
|
|
2499
|
+
var RIBBON_ACTION_EVENT = "ribbon-action";
|
|
2500
|
+
/** Internal, non-composed event: it stays inside the viewer's shadow tree. */
|
|
2501
|
+
function emitRibbonAction(target, action) {
|
|
2502
|
+
target.dispatchEvent(new CustomEvent(RIBBON_ACTION_EVENT, {
|
|
2503
|
+
detail: action,
|
|
2504
|
+
bubbles: true
|
|
2505
|
+
}));
|
|
2506
|
+
}
|
|
2507
|
+
//#endregion
|
|
2508
|
+
//#region src/ribbon-parts.ts
|
|
2509
|
+
/**
|
|
2510
|
+
* A large stacked ribbon command. `id` is a stable name for state sync and tests; the typed
|
|
2511
|
+
* `action` is what runs. Labels, icons and shortcuts are static application strings.
|
|
2512
|
+
*/
|
|
2513
|
+
function tool(doc, id, label, icon, action, options = {}) {
|
|
2514
|
+
const el = doc.createElement("office-ui-button");
|
|
2515
|
+
el.setAttribute("variant", "stacked");
|
|
2516
|
+
el.setAttribute("command", id);
|
|
2517
|
+
el.setAttribute("icon", icon);
|
|
2518
|
+
el.setAttribute("label", label);
|
|
2519
|
+
el.setAttribute("title", options.keys ? `${label} (${options.keys[1]})` : label);
|
|
2520
|
+
if (options.keys) el.setAttribute("keyshortcuts", options.keys[0]);
|
|
2521
|
+
if (options.pressed !== void 0) el.setAttribute("pressed", String(options.pressed));
|
|
2522
|
+
if (options.controls) el.setAttribute("aria-controls", options.controls);
|
|
2523
|
+
el.addEventListener("office-command", (event) => {
|
|
2524
|
+
event.stopPropagation();
|
|
2525
|
+
emitRibbonAction(el, action);
|
|
2526
|
+
});
|
|
2527
|
+
return el;
|
|
2528
|
+
}
|
|
2529
|
+
/** A labelled ribbon group (the shared `office-ui-ribbon-group`). */
|
|
2530
|
+
function group(doc, label, children, className = "") {
|
|
2531
|
+
const el = doc.createElement("office-ui-ribbon-group");
|
|
2532
|
+
el.setAttribute("label", label);
|
|
2533
|
+
if (className) el.className = className;
|
|
2534
|
+
el.append(...children);
|
|
2535
|
+
return el;
|
|
2536
|
+
}
|
|
2537
|
+
/** A keyboard-navigable row of groups (the shared `office-ui-toolbar`). */
|
|
2538
|
+
function commandRow(doc, label, children) {
|
|
2539
|
+
const el = doc.createElement("office-ui-toolbar");
|
|
2540
|
+
el.className = "ribbon-row";
|
|
2541
|
+
el.setAttribute("aria-label", label);
|
|
2542
|
+
el.append(...children);
|
|
2543
|
+
return el;
|
|
2544
|
+
}
|
|
2545
|
+
/** Muted two-line guidance at the end of a ribbon panel. */
|
|
2546
|
+
function hint(doc, lines) {
|
|
2547
|
+
const el = doc.createElement("p");
|
|
2548
|
+
el.className = "ribbon-hint";
|
|
2549
|
+
lines.forEach((line, index) => {
|
|
2550
|
+
if (index) el.append(doc.createElement("br"));
|
|
2551
|
+
el.append(line);
|
|
2552
|
+
});
|
|
2553
|
+
return el;
|
|
2554
|
+
}
|
|
2555
|
+
//#endregion
|
|
2556
|
+
//#region src/viewer-search.ts
|
|
2557
|
+
/** Builds the Find group: a search field, previous/next result buttons and a live status. */
|
|
2558
|
+
function createSearchGroup(doc) {
|
|
2559
|
+
const group = doc.createElement("div");
|
|
2560
|
+
group.className = "ribbon-group search-group";
|
|
2561
|
+
group.setAttribute("role", "group");
|
|
2562
|
+
group.setAttribute("aria-label", "Find");
|
|
2563
|
+
const controls = doc.createElement("div");
|
|
2564
|
+
controls.className = "search-controls";
|
|
2565
|
+
controls.setAttribute("role", "search");
|
|
2566
|
+
controls.setAttribute("aria-label", "Diagram text search");
|
|
2567
|
+
const label = doc.createElement("label");
|
|
2568
|
+
label.append("Find ");
|
|
2569
|
+
const input = doc.createElement("input");
|
|
2570
|
+
input.type = "search";
|
|
2571
|
+
input.setAttribute("aria-label", "Search diagram text");
|
|
2572
|
+
input.placeholder = "Text across pages";
|
|
2573
|
+
input.maxLength = TEXT_SEARCH_LIMITS.queryCharacters;
|
|
2574
|
+
input.autocomplete = "off";
|
|
2575
|
+
input.spellcheck = false;
|
|
2576
|
+
input.setAttribute("aria-describedby", "search-status");
|
|
2577
|
+
label.append(input);
|
|
2578
|
+
const step = (action, name, text) => {
|
|
2579
|
+
const button = doc.createElement("button");
|
|
2580
|
+
button.type = "button";
|
|
2581
|
+
button.dataset.action = action;
|
|
2582
|
+
button.setAttribute("aria-label", name);
|
|
2583
|
+
button.textContent = text;
|
|
2584
|
+
return button;
|
|
2585
|
+
};
|
|
2586
|
+
const status = doc.createElement("span");
|
|
2587
|
+
status.id = "search-status";
|
|
2588
|
+
status.setAttribute("role", "status");
|
|
2589
|
+
status.setAttribute("aria-live", "polite");
|
|
2590
|
+
status.setAttribute("aria-atomic", "true");
|
|
2591
|
+
controls.append(label, step("search-previous", "Previous matching shape", "Previous"), step("search-next", "Next matching shape", "Next"), status);
|
|
2592
|
+
const caption = doc.createElement("span");
|
|
2593
|
+
caption.className = "group-label";
|
|
2594
|
+
caption.textContent = "Find in drawing";
|
|
2595
|
+
group.append(controls, caption);
|
|
2596
|
+
return group;
|
|
2597
|
+
}
|
|
2598
|
+
function searchControls(root) {
|
|
2599
|
+
return {
|
|
2600
|
+
input: root.querySelector("input[type=\"search\"]"),
|
|
2601
|
+
status: root.querySelector("#search-status"),
|
|
2602
|
+
previous: root.querySelector("[data-action=\"search-previous\"]"),
|
|
2603
|
+
next: root.querySelector("[data-action=\"search-next\"]")
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
function renderSearchControls(controls, state) {
|
|
2607
|
+
if (controls.input.value !== state.search.query) controls.input.value = state.search.query;
|
|
2608
|
+
controls.input.disabled = !state.document?.pages.length;
|
|
2609
|
+
controls.previous.disabled = controls.next.disabled = !state.search.results.length;
|
|
2610
|
+
controls.status.textContent = state.document ? textSearchStatus(state.search) : "Open a diagram to search";
|
|
2611
|
+
const result = state.search.results[state.search.activeIndex];
|
|
2612
|
+
controls.status.title = result ? `${result.pageName} · ${result.shapeName}: ${result.preview}` : "";
|
|
2613
|
+
}
|
|
2614
|
+
//#endregion
|
|
2615
|
+
//#region src/ribbon-home.ts
|
|
2616
|
+
/**
|
|
2617
|
+
* Visio's Home tab, limited to commands this build can honestly perform: Undo, Tools and
|
|
2618
|
+
* Editing. Clipboard, Font, Paragraph, Shape Styles and Arrange wait for core edit commands.
|
|
2619
|
+
* The compact (phone) layout folds every group but Undo into the Tools disclosure.
|
|
2620
|
+
*/
|
|
2621
|
+
function buildHomePanel(doc, panel) {
|
|
2622
|
+
const tools = doc.createElement("details");
|
|
2623
|
+
tools.className = "ribbon-tools";
|
|
2624
|
+
tools.open = true;
|
|
2625
|
+
const summary = doc.createElement("summary");
|
|
2626
|
+
const caret = doc.createElement("span");
|
|
2627
|
+
caret.setAttribute("aria-hidden", "true");
|
|
2628
|
+
caret.textContent = "⌄";
|
|
2629
|
+
summary.append("Tools ", caret);
|
|
2630
|
+
const content = doc.createElement("div");
|
|
2631
|
+
content.className = "tools-content";
|
|
2632
|
+
content.append(group(doc, "Tools", [
|
|
2633
|
+
tool(doc, "pointer", "Pointer Tool", "pointer", {
|
|
2634
|
+
type: "tool",
|
|
2635
|
+
tool: "pointer"
|
|
2636
|
+
}, {
|
|
2637
|
+
keys: ["Control+1", "Ctrl+1"],
|
|
2638
|
+
pressed: true
|
|
2639
|
+
}),
|
|
2640
|
+
tool(doc, "rectangle", "Rectangle", "rectangle", {
|
|
2641
|
+
type: "tool",
|
|
2642
|
+
tool: "rectangle"
|
|
2643
|
+
}, {
|
|
2644
|
+
keys: ["Control+8", "Ctrl+8"],
|
|
2645
|
+
pressed: false
|
|
2646
|
+
}),
|
|
2647
|
+
tool(doc, "edit", "Edit text", "pencil", {
|
|
2648
|
+
type: "reveal",
|
|
2649
|
+
panel: "edit"
|
|
2650
|
+
}, { keys: ["F2", "F2"] })
|
|
2651
|
+
]), createSearchGroup(doc), group(doc, "Editing", [
|
|
2652
|
+
tool(doc, "delete", "Delete", "trash", { type: "delete" }, { keys: ["Delete", "Del"] }),
|
|
2653
|
+
tool(doc, "layers", "Layers", "visioLayers", {
|
|
2654
|
+
type: "reveal",
|
|
2655
|
+
panel: "layers"
|
|
2656
|
+
}),
|
|
2657
|
+
tool(doc, "selection", "Shape details", "visioShapeData", {
|
|
2658
|
+
type: "reveal",
|
|
2659
|
+
panel: "selection"
|
|
2660
|
+
})
|
|
2661
|
+
]));
|
|
2662
|
+
tools.append(summary, content);
|
|
2663
|
+
panel.append(commandRow(doc, "Home commands", [group(doc, "Undo", [tool(doc, "undo", "Undo", "undo", {
|
|
2664
|
+
type: "history",
|
|
2665
|
+
key: "undo"
|
|
2666
|
+
}, { keys: ["Control+Z", "Ctrl+Z"] }), tool(doc, "redo", "Redo", "redo", {
|
|
2667
|
+
type: "history",
|
|
2668
|
+
key: "redo"
|
|
2669
|
+
}, { keys: ["Control+Y", "Ctrl+Y"] })], "undo-group"), tools]), hint(doc, ["Rectangle: drag on the page to draw.", "Editing is experimental. Keep your original file."]));
|
|
2670
|
+
}
|
|
2671
|
+
//#endregion
|
|
2672
|
+
//#region src/ribbon-view.ts
|
|
2673
|
+
/** Visio's View tab: Views, Show (grid and task panes) and Zoom. */
|
|
2674
|
+
function buildViewPanel(doc, panel) {
|
|
2675
|
+
panel.append(commandRow(doc, "View commands", [
|
|
2676
|
+
group(doc, "Views", [tool(doc, "fullscreen", "Full Screen", "fullscreen", { type: "fullscreen" }, {
|
|
2677
|
+
keys: ["F5", "F5"],
|
|
2678
|
+
pressed: false
|
|
2679
|
+
})]),
|
|
2680
|
+
group(doc, "Show", [
|
|
2681
|
+
tool(doc, "grid", "Grid", "grid", { type: "grid" }, { pressed: false }),
|
|
2682
|
+
tool(doc, "pages", "Pages pane", "visioPagesPane", {
|
|
2683
|
+
type: "pane",
|
|
2684
|
+
pane: "pages"
|
|
2685
|
+
}, {
|
|
2686
|
+
pressed: true,
|
|
2687
|
+
controls: "page-rail"
|
|
2688
|
+
}),
|
|
2689
|
+
tool(doc, "inspector", "Inspector pane", "visioInspectorPane", {
|
|
2690
|
+
type: "pane",
|
|
2691
|
+
pane: "inspector"
|
|
2692
|
+
}, {
|
|
2693
|
+
pressed: true,
|
|
2694
|
+
controls: "inspector-pane"
|
|
2695
|
+
}),
|
|
2696
|
+
tool(doc, "notes", "Review notes", "message", {
|
|
2697
|
+
type: "reveal",
|
|
2698
|
+
panel: "notes"
|
|
2699
|
+
})
|
|
2700
|
+
]),
|
|
2701
|
+
group(doc, "Zoom", [
|
|
2702
|
+
tool(doc, "zoom-fit", "Fit to Window", "fitPage", {
|
|
2703
|
+
type: "zoom",
|
|
2704
|
+
mode: "fit"
|
|
2705
|
+
}, { keys: ["Control+Shift+W", "Ctrl+Shift+W"] }),
|
|
2706
|
+
tool(doc, "page-width", "Page Width", "pageWidth", {
|
|
2707
|
+
type: "zoom",
|
|
2708
|
+
mode: "width"
|
|
2709
|
+
}),
|
|
2710
|
+
tool(doc, "actual-size", "100%", "search", {
|
|
2711
|
+
type: "zoom",
|
|
2712
|
+
mode: "actual"
|
|
2713
|
+
})
|
|
2714
|
+
])
|
|
2715
|
+
]), hint(doc, ["Ctrl+wheel or + / − to zoom.", "Ctrl+Page Up / Page Down changes page."]));
|
|
2716
|
+
}
|
|
2717
|
+
//#endregion
|
|
2718
|
+
//#region src/ribbon.ts
|
|
2719
|
+
var TABS = [[
|
|
2720
|
+
"home",
|
|
2721
|
+
"Home",
|
|
2722
|
+
buildHomePanel
|
|
2723
|
+
], [
|
|
2724
|
+
"view",
|
|
2725
|
+
"View",
|
|
2726
|
+
buildViewPanel
|
|
2727
|
+
]];
|
|
2728
|
+
/**
|
|
2729
|
+
* The Visio ribbon: a tab list and one panel per tab. Tab switching and keyboard movement
|
|
2730
|
+
* belong to the chrome; commands emit `ribbon-action` events for the router.
|
|
2731
|
+
*/
|
|
2732
|
+
function createRibbon(doc) {
|
|
2733
|
+
const toolbar = doc.createElement("div");
|
|
2734
|
+
toolbar.className = "toolbar";
|
|
2735
|
+
toolbar.setAttribute("role", "group");
|
|
2736
|
+
toolbar.setAttribute("aria-label", "Diagram controls");
|
|
2737
|
+
const tabs = doc.createElement("div");
|
|
2738
|
+
tabs.className = "ribbon-tabs";
|
|
2739
|
+
tabs.setAttribute("role", "tablist");
|
|
2740
|
+
tabs.setAttribute("aria-label", "Ribbon");
|
|
2741
|
+
toolbar.append(tabs);
|
|
2742
|
+
for (const [key, name, build] of TABS) {
|
|
2743
|
+
const selected = key === "home";
|
|
2744
|
+
const tab = doc.createElement("button");
|
|
2745
|
+
tab.type = "button";
|
|
2746
|
+
tab.id = `${key}-tab`;
|
|
2747
|
+
tab.dataset.tab = key;
|
|
2748
|
+
tab.textContent = name;
|
|
2749
|
+
tab.setAttribute("role", "tab");
|
|
2750
|
+
tab.setAttribute("aria-selected", String(selected));
|
|
2751
|
+
tab.setAttribute("aria-controls", `${key}-panel`);
|
|
2752
|
+
if (!selected) tab.tabIndex = -1;
|
|
2753
|
+
const panel = doc.createElement("div");
|
|
2754
|
+
panel.className = "ribbon-content";
|
|
2755
|
+
panel.id = `${key}-panel`;
|
|
2756
|
+
panel.setAttribute("role", "tabpanel");
|
|
2757
|
+
panel.setAttribute("aria-labelledby", tab.id);
|
|
2758
|
+
panel.hidden = !selected;
|
|
2759
|
+
build(doc, panel);
|
|
2760
|
+
tabs.append(tab);
|
|
2761
|
+
toolbar.append(panel);
|
|
2762
|
+
}
|
|
2763
|
+
return toolbar;
|
|
2764
|
+
}
|
|
2765
|
+
//#endregion
|
|
2766
|
+
//#region src/status-bar.ts
|
|
2767
|
+
/**
|
|
2768
|
+
* Visio's status bar on the shared `office-ui-status-bar`: page and shape metrics, a live
|
|
2769
|
+
* message region, compatibility notes, the host footer slot and the shared zoom slider.
|
|
2770
|
+
*/
|
|
2771
|
+
function createStatusBar(doc) {
|
|
2772
|
+
const bar = doc.createElement("office-ui-status-bar");
|
|
2773
|
+
bar.className = "status";
|
|
2774
|
+
bar.setAttribute("label", "Status bar");
|
|
2775
|
+
const item = (name) => {
|
|
2776
|
+
const el = doc.createElement("office-ui-status-item");
|
|
2777
|
+
el.setAttribute(name, "");
|
|
2778
|
+
el.setAttribute("label", "");
|
|
2779
|
+
el.setAttribute("value", "");
|
|
2780
|
+
return el;
|
|
2781
|
+
};
|
|
2782
|
+
const message = doc.createElement("div");
|
|
2783
|
+
message.className = "status-message";
|
|
2784
|
+
message.setAttribute("role", "status");
|
|
2785
|
+
const text = doc.createElement("span");
|
|
2786
|
+
text.dataset.status = "";
|
|
2787
|
+
const diagnostics = doc.createElement("span");
|
|
2788
|
+
diagnostics.dataset.diagnostics = "";
|
|
2789
|
+
message.append(text, diagnostics);
|
|
2790
|
+
const notes = doc.createElement("div");
|
|
2791
|
+
notes.className = "notes-strip";
|
|
2792
|
+
const notesButton = doc.createElement("button");
|
|
2793
|
+
notesButton.type = "button";
|
|
2794
|
+
notesButton.dataset.chrome = "notes";
|
|
2795
|
+
notesButton.setAttribute("aria-controls", "inspector-pane");
|
|
2796
|
+
notesButton.setAttribute("aria-expanded", "false");
|
|
2797
|
+
const count = doc.createElement("span");
|
|
2798
|
+
count.className = "notes-count";
|
|
2799
|
+
count.dataset.noteCount = "";
|
|
2800
|
+
notesButton.append("Notes", count);
|
|
2801
|
+
const local = doc.createElement("span");
|
|
2802
|
+
local.textContent = "Files stay in your browser";
|
|
2803
|
+
notes.append(notesButton, local);
|
|
2804
|
+
const footer = doc.createElement("slot");
|
|
2805
|
+
footer.name = "workspace-footer";
|
|
2806
|
+
const zoom = doc.createElement("office-ui-zoom-slider");
|
|
2807
|
+
zoom.slot = "end";
|
|
2808
|
+
zoom.className = "zoom-controls";
|
|
2809
|
+
zoom.setAttribute("label", "Canvas zoom");
|
|
2810
|
+
zoom.setAttribute("fit", "Fit page to current window");
|
|
2811
|
+
zoom.setAttribute("value", "100");
|
|
2812
|
+
bar.append(item("data-page-status"), item("data-shape-status"), message, notes, footer, zoom);
|
|
2813
|
+
return bar;
|
|
2814
|
+
}
|
|
2815
|
+
/** Visio's page tabs under the drawing, on the shared document tab strip. */
|
|
2816
|
+
function createPageTabs(doc) {
|
|
2817
|
+
const strip = doc.createElement("office-ui-tab-strip");
|
|
2818
|
+
strip.className = "page-tabs";
|
|
2819
|
+
strip.setAttribute("label", "Pages");
|
|
2820
|
+
strip.setAttribute("previous-label", "Previous page");
|
|
2821
|
+
strip.setAttribute("next-label", "Next page");
|
|
2822
|
+
return strip;
|
|
2823
|
+
}
|
|
2824
|
+
//#endregion
|
|
2539
2825
|
//#region src/edit-error.ts
|
|
2540
2826
|
/** Core error codes remain visible for a refused transaction; document content is plain text. */
|
|
2541
2827
|
function editErrorMessage(error) {
|
|
@@ -2823,59 +3109,13 @@ var ViewerEditControls = class {
|
|
|
2823
3109
|
}
|
|
2824
3110
|
};
|
|
2825
3111
|
//#endregion
|
|
2826
|
-
//#region src/viewer-search.ts
|
|
2827
|
-
var searchTemplate = `<div class="search-controls" role="search" aria-label="Diagram text search"><label>Find <input type="search" aria-label="Search diagram text" placeholder="Text across pages" maxlength="${TEXT_SEARCH_LIMITS.queryCharacters}" autocomplete="off" spellcheck="false" aria-describedby="search-status"></label><button type="button" data-action="search-previous" aria-label="Previous matching shape">Previous</button><button type="button" data-action="search-next" aria-label="Next matching shape">Next</button><span id="search-status" role="status" aria-live="polite" aria-atomic="true"></span></div>`;
|
|
2828
|
-
function searchControls(root) {
|
|
2829
|
-
return {
|
|
2830
|
-
input: root.querySelector("input[type=\"search\"]"),
|
|
2831
|
-
status: root.querySelector("#search-status"),
|
|
2832
|
-
previous: root.querySelector("[data-action=\"search-previous\"]"),
|
|
2833
|
-
next: root.querySelector("[data-action=\"search-next\"]")
|
|
2834
|
-
};
|
|
2835
|
-
}
|
|
2836
|
-
function renderSearchControls(controls, state) {
|
|
2837
|
-
if (controls.input.value !== state.search.query) controls.input.value = state.search.query;
|
|
2838
|
-
controls.input.disabled = !state.document?.pages.length;
|
|
2839
|
-
controls.previous.disabled = controls.next.disabled = !state.search.results.length;
|
|
2840
|
-
controls.status.textContent = state.document ? textSearchStatus(state.search) : "Open a diagram to search";
|
|
2841
|
-
const result = state.search.results[state.search.activeIndex];
|
|
2842
|
-
controls.status.title = result ? `${result.pageName} · ${result.shapeName}: ${result.preview}` : "";
|
|
2843
|
-
}
|
|
2844
|
-
//#endregion
|
|
2845
3112
|
//#region src/viewer-chrome.ts
|
|
2846
|
-
/**
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
<button type="button" id="view-tab" role="tab" aria-selected="false" aria-controls="view-panel" tabindex="-1" data-tab="view">View</button>
|
|
2853
|
-
</div>
|
|
2854
|
-
<div class="ribbon-content" id="home-panel" role="tabpanel" aria-labelledby="home-tab">
|
|
2855
|
-
<details class="ribbon-tools" open><summary>Tools <span aria-hidden="true">⌄</span></summary><div class="tools-content"><div class="ribbon-group search-group">${searchTemplate}<span class="group-label">Find in drawing</span></div>
|
|
2856
|
-
<div class="ribbon-group"><div class="ribbon-actions">
|
|
2857
|
-
<button class="ribbon-command" type="button" data-chrome="selection"><span class="command-icon" data-icon="selection" aria-hidden="true"></span><span>Shape details</span></button>
|
|
2858
|
-
<button class="ribbon-command" type="button" data-chrome="edit"><span class="command-icon" data-icon="edit" aria-hidden="true"></span><span>Edit text</span></button>
|
|
2859
|
-
<button class="ribbon-command" type="button" data-chrome="layers"><span class="command-icon" data-icon="layers" aria-hidden="true"></span><span>Layers</span></button>
|
|
2860
|
-
</div><span class="group-label">Inspect and edit</span></div>
|
|
2861
|
-
</div></details>
|
|
2862
|
-
<div class="ribbon-primary">
|
|
2863
|
-
<label class="page-picker">Page <select aria-label="Page"></select></label>
|
|
2864
|
-
<div class="page-stepper"><button type="button" data-chrome="previous-page" aria-label="Previous page">‹</button><button type="button" data-chrome="next-page" aria-label="Next page">›</button></div>
|
|
2865
|
-
<span class="spacer"></span><span class="local-label">Local workspace</span>
|
|
2866
|
-
</div>
|
|
2867
|
-
<p class="ribbon-hint">Select a shape to inspect its details.<br>Text editing is experimental.</p>
|
|
2868
|
-
</div>
|
|
2869
|
-
<div class="ribbon-content" id="view-panel" role="tabpanel" aria-labelledby="view-tab" hidden>
|
|
2870
|
-
<div class="ribbon-group"><div class="ribbon-actions">
|
|
2871
|
-
<button class="ribbon-command" type="button" data-chrome="pages" aria-pressed="true" aria-controls="page-rail"><span class="command-icon" data-icon="pages" aria-hidden="true"></span><span>Pages pane</span></button>
|
|
2872
|
-
<button class="ribbon-command" type="button" data-chrome="inspector" aria-pressed="true" aria-controls="inspector-pane"><span class="command-icon" data-icon="inspector" aria-hidden="true"></span><span>Inspector pane</span></button>
|
|
2873
|
-
<button class="ribbon-command" type="button" data-chrome="notes"><span class="command-icon" data-icon="notes" aria-hidden="true"></span><span>Review notes</span></button>
|
|
2874
|
-
</div><span class="group-label">Workspace panes</span></div>
|
|
2875
|
-
<p class="ribbon-hint">Canvas shortcuts: + / − to zoom, 0 to fit.<br>Arrow keys move between focused shapes.</p>
|
|
2876
|
-
</div>
|
|
2877
|
-
</div>
|
|
2878
|
-
<div class="workspace">
|
|
3113
|
+
/**
|
|
3114
|
+
* Static workspace markup (legacy; migrate to builders when next changed). The ribbon, page
|
|
3115
|
+
* tabs and status bar are built by `createRibbon`, `createPageTabs` and `createStatusBar`.
|
|
3116
|
+
* All document labels are inserted as text.
|
|
3117
|
+
*/
|
|
3118
|
+
var viewerChromeTemplate = `<div class="workspace">
|
|
2879
3119
|
<nav id="page-rail" class="page-rail" aria-label="Diagram pages"><div class="pane-heading"><span>Pages</span><span data-page-count>0</span></div><ol class="page-list"></ol><p class="page-empty">Open a drawing to see its pages.</p></nav>
|
|
2880
3120
|
<div class="viewport" tabindex="0" role="region" aria-label="Diagram canvas"></div>
|
|
2881
3121
|
<aside id="inspector-pane" class="inspector-pane" aria-label="Drawing inspector">
|
|
@@ -2888,14 +3128,15 @@ var viewerChromeTemplate = `
|
|
|
2888
3128
|
<details class="notes inspector-card"><summary>Compatibility notes</summary><ul></ul></details>
|
|
2889
3129
|
</div>
|
|
2890
3130
|
</aside>
|
|
2891
|
-
</div
|
|
2892
|
-
<div class="status"><div class="status-message" role="status"><span data-status></span><span data-diagnostics></span></div><div class="notes-strip"><button type="button" data-chrome="notes" aria-controls="inspector-pane" aria-expanded="false">Notes<span class="notes-count" data-note-count></span></button><span>Files stay in your browser</span></div><slot name="workspace-footer"></slot><div class="zoom-controls" role="group" aria-label="Canvas zoom"><button type="button" data-action="fit">Fit page</button><button type="button" data-action="actual">100%</button><span class="zoom-divider"></span><button type="button" data-action="out" aria-label="Zoom out">−</button><output class="zoom" aria-label="Zoom level">100%</output><button type="button" data-action="in" aria-label="Zoom in">+</button></div></div>`;
|
|
3131
|
+
</div>`;
|
|
2893
3132
|
/** Presentation state stays local; page navigation uses the same controller as every binding. */
|
|
2894
3133
|
var ViewerChrome = class {
|
|
2895
3134
|
#root;
|
|
2896
3135
|
#controller;
|
|
2897
3136
|
#document;
|
|
2898
3137
|
#pageList;
|
|
3138
|
+
#pageTabs;
|
|
3139
|
+
#tools;
|
|
2899
3140
|
#rail;
|
|
2900
3141
|
#inspector;
|
|
2901
3142
|
#notes;
|
|
@@ -2907,6 +3148,8 @@ var ViewerChrome = class {
|
|
|
2907
3148
|
this.#root = root;
|
|
2908
3149
|
this.#controller = controller;
|
|
2909
3150
|
this.#pageList = root.querySelector(".page-list");
|
|
3151
|
+
this.#pageTabs = root.querySelector("office-ui-tab-strip");
|
|
3152
|
+
this.#tools = root.querySelector(".ribbon-tools");
|
|
2910
3153
|
this.#rail = root.querySelector(".page-rail");
|
|
2911
3154
|
this.#inspector = root.querySelector(".inspector-pane");
|
|
2912
3155
|
this.#notes = root.querySelector(".notes");
|
|
@@ -2917,7 +3160,7 @@ var ViewerChrome = class {
|
|
|
2917
3160
|
this.#responsive = view?.matchMedia?.("(max-width: 980px)");
|
|
2918
3161
|
const compact = view?.matchMedia?.("(max-width: 760px)");
|
|
2919
3162
|
this.#compact = compact;
|
|
2920
|
-
const tools = this.#
|
|
3163
|
+
const tools = this.#tools;
|
|
2921
3164
|
const toolLayout = () => {
|
|
2922
3165
|
tools.open = !compact?.matches;
|
|
2923
3166
|
};
|
|
@@ -2944,22 +3187,16 @@ var ViewerChrome = class {
|
|
|
2944
3187
|
this.#root.addEventListener("click", (event) => {
|
|
2945
3188
|
const button = event.target?.closest?.("button");
|
|
2946
3189
|
if (!button || button.disabled) return;
|
|
2947
|
-
if (compact?.matches && button.dataset.chrome) tools.open = false;
|
|
2948
3190
|
const tab = button.dataset.tab;
|
|
2949
|
-
if (tab === "home" || tab === "view") this
|
|
3191
|
+
if (tab === "home" || tab === "view") this.showTab(tab);
|
|
2950
3192
|
if (button.dataset.pageIndex !== void 0) this.#controller.setPage(Number(button.dataset.pageIndex));
|
|
2951
3193
|
const action = button.dataset.chrome;
|
|
2952
|
-
if (action === "
|
|
2953
|
-
if (action === "
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
if (action === "inspector") {
|
|
2959
|
-
this.#inspectorManuallyToggled = true;
|
|
2960
|
-
this.#setPane("inspector", this.#inspector.hidden);
|
|
2961
|
-
}
|
|
2962
|
-
if (action === "notes" || action === "selection" || action === "edit" || action === "layers") this.#reveal(action);
|
|
3194
|
+
if (action === "inspector") this.togglePane("inspector");
|
|
3195
|
+
if (action === "notes") this.reveal("notes");
|
|
3196
|
+
}, options);
|
|
3197
|
+
this.#root.addEventListener("office-tab-select", (event) => {
|
|
3198
|
+
const id = Number(event.detail.id);
|
|
3199
|
+
if (Number.isSafeInteger(id)) this.#controller.setPage(id);
|
|
2963
3200
|
}, options);
|
|
2964
3201
|
this.#root.addEventListener("keydown", (event) => {
|
|
2965
3202
|
const key = event;
|
|
@@ -2976,11 +3213,11 @@ var ViewerChrome = class {
|
|
|
2976
3213
|
if (key.key === "ArrowUp" || key.key === "ArrowDown") return;
|
|
2977
3214
|
key.preventDefault();
|
|
2978
3215
|
const tab = key.key === "Home" ? "home" : key.key === "End" ? "view" : button.dataset.tab === "home" ? "view" : "home";
|
|
2979
|
-
this
|
|
3216
|
+
this.showTab(tab);
|
|
2980
3217
|
this.#root.querySelector(`[data-tab="${tab}"]`).focus();
|
|
2981
3218
|
} else if (button.dataset.pageIndex !== void 0) {
|
|
2982
3219
|
key.preventDefault();
|
|
2983
|
-
const pages = [...this.#pageList.querySelectorAll("
|
|
3220
|
+
const pages = [...this.#pageList.querySelectorAll("[data-page-index]")];
|
|
2984
3221
|
const current = Number(button.dataset.pageIndex);
|
|
2985
3222
|
const next = key.key === "Home" ? 0 : key.key === "End" ? pages.length - 1 : Math.max(0, Math.min(pages.length - 1, current + (["ArrowRight", "ArrowDown"].includes(key.key) ? 1 : -1)));
|
|
2986
3223
|
this.#controller.setPage(next);
|
|
@@ -2990,7 +3227,7 @@ var ViewerChrome = class {
|
|
|
2990
3227
|
this.#notes.addEventListener("toggle", () => this.#syncNotes(), options);
|
|
2991
3228
|
return () => events.abort();
|
|
2992
3229
|
}
|
|
2993
|
-
|
|
3230
|
+
showTab(tab) {
|
|
2994
3231
|
for (const button of this.#root.querySelectorAll("[data-tab]")) {
|
|
2995
3232
|
const selected = button.dataset.tab === tab;
|
|
2996
3233
|
button.setAttribute("aria-selected", String(selected));
|
|
@@ -2998,27 +3235,41 @@ var ViewerChrome = class {
|
|
|
2998
3235
|
}
|
|
2999
3236
|
for (const panel of this.#root.querySelectorAll("[role=\"tabpanel\"]")) panel.hidden = panel.id !== `${tab}-panel`;
|
|
3000
3237
|
}
|
|
3238
|
+
/** Show or hide a task pane; compact layouts close the Tools menu afterwards. */
|
|
3239
|
+
togglePane(pane) {
|
|
3240
|
+
if (this.#compact?.matches) this.#tools.open = false;
|
|
3241
|
+
if (pane === "pages") this.#pagesManuallyToggled = true;
|
|
3242
|
+
else this.#inspectorManuallyToggled = true;
|
|
3243
|
+
this.#setPane(pane, (pane === "pages" ? this.#rail : this.#inspector).hidden);
|
|
3244
|
+
}
|
|
3245
|
+
#command(name) {
|
|
3246
|
+
return this.#root.querySelector(`office-ui-button[command="${name}"]`);
|
|
3247
|
+
}
|
|
3001
3248
|
#setPane(pane, visible) {
|
|
3002
3249
|
if (visible && this.#compact?.matches) {
|
|
3003
3250
|
const other = pane === "pages" ? "inspector" : "pages";
|
|
3004
3251
|
(other === "pages" ? this.#rail : this.#inspector).hidden = true;
|
|
3005
|
-
this.#
|
|
3252
|
+
this.#command(other).setAttribute("pressed", "false");
|
|
3006
3253
|
}
|
|
3007
3254
|
(pane === "pages" ? this.#rail : this.#inspector).hidden = !visible;
|
|
3008
|
-
this.#
|
|
3255
|
+
this.#command(pane).setAttribute("pressed", String(visible));
|
|
3009
3256
|
this.#syncNotes();
|
|
3010
3257
|
}
|
|
3011
3258
|
#syncNotes() {
|
|
3012
3259
|
this.#root.querySelector(".notes-strip button").setAttribute("aria-expanded", String(!this.#inspector.hidden && this.#notes.open));
|
|
3013
3260
|
}
|
|
3014
|
-
|
|
3261
|
+
/** Open an inspector disclosure; text editing may focus the text field directly (F2). */
|
|
3262
|
+
reveal(kind, focusText = false) {
|
|
3263
|
+
if (this.#compact?.matches) this.#tools.open = false;
|
|
3015
3264
|
this.#inspectorManuallyToggled = true;
|
|
3016
3265
|
this.#setPane("inspector", true);
|
|
3017
3266
|
const selector = kind === "notes" ? ".notes" : kind === "edit" ? ".edit-controls" : kind === "layers" ? ".layer-controls" : ".shape-inspector";
|
|
3018
3267
|
const panel = this.#root.querySelector(selector);
|
|
3019
3268
|
if (panel.hidden) return;
|
|
3020
3269
|
panel.open = true;
|
|
3021
|
-
panel.querySelector("
|
|
3270
|
+
const text = panel.querySelector("textarea");
|
|
3271
|
+
if (focusText && text && !text.disabled) text.focus();
|
|
3272
|
+
else panel.querySelector("summary").focus();
|
|
3022
3273
|
panel.scrollIntoView?.({
|
|
3023
3274
|
block: "nearest",
|
|
3024
3275
|
inline: "nearest"
|
|
@@ -3059,14 +3310,21 @@ var ViewerChrome = class {
|
|
|
3059
3310
|
fragment.append(item);
|
|
3060
3311
|
}
|
|
3061
3312
|
this.#pageList.replaceChildren(fragment);
|
|
3313
|
+
this.#pageTabs.tabs = (state.document?.pages ?? []).map((candidate, index) => ({
|
|
3314
|
+
id: String(index),
|
|
3315
|
+
label: candidate.name,
|
|
3316
|
+
title: candidate.isBackground ? `${candidate.name} (background page)` : candidate.name
|
|
3317
|
+
}));
|
|
3062
3318
|
if (restoreFocus !== void 0) [...this.#pageList.querySelectorAll("button")].find((button) => button.dataset.pageIndex === restoreFocus)?.focus();
|
|
3063
3319
|
}
|
|
3320
|
+
this.#pageTabs.selected = page ? String(state.pageIndex) : "";
|
|
3064
3321
|
for (const button of this.#pageList.querySelectorAll("button")) {
|
|
3065
3322
|
const selected = Number(button.dataset.pageIndex) === state.pageIndex;
|
|
3066
3323
|
if (selected) button.setAttribute("aria-current", "page");
|
|
3067
3324
|
else button.removeAttribute("aria-current");
|
|
3068
3325
|
button.tabIndex = selected ? 0 : -1;
|
|
3069
3326
|
}
|
|
3327
|
+
this.#root.querySelector("[data-page-status]").setAttribute("value", page ? `Page ${state.pageIndex + 1} of ${state.document.pages.length}` : "");
|
|
3070
3328
|
const count = state.document?.pages.length ?? 0;
|
|
3071
3329
|
this.#root.querySelector("[data-page-count]").textContent = String(count);
|
|
3072
3330
|
this.#root.querySelector(".page-empty").hidden = count > 0;
|
|
@@ -3074,17 +3332,405 @@ var ViewerChrome = class {
|
|
|
3074
3332
|
this.#root.querySelector("[data-page-size]").textContent = page ? `${page.width} × ${page.height} in` : "No page";
|
|
3075
3333
|
this.#root.querySelector("[data-page-shapes]").textContent = page ? `${page.shapes.length} top-level` : "No shapes";
|
|
3076
3334
|
this.#root.querySelector(".selection-hint").hidden = !!state.selectedShape;
|
|
3077
|
-
this.#
|
|
3078
|
-
this.#
|
|
3079
|
-
this.#
|
|
3080
|
-
this.#
|
|
3081
|
-
this.#root.querySelector("
|
|
3082
|
-
for (const button of this.#root.querySelectorAll("[data-chrome=\"notes\"]")) button.disabled = noteCount === 0;
|
|
3335
|
+
this.#command("selection").disabled = !state.selectedShape;
|
|
3336
|
+
this.#command("edit").disabled = !page;
|
|
3337
|
+
this.#command("layers").disabled = this.#root.querySelector(".layer-controls").hidden;
|
|
3338
|
+
this.#command("notes").disabled = noteCount === 0;
|
|
3339
|
+
this.#root.querySelector(".notes-strip button").disabled = noteCount === 0;
|
|
3083
3340
|
this.#root.querySelector("[data-note-count]").textContent = noteCount ? ` · ${noteCount}` : "";
|
|
3084
3341
|
this.#syncNotes();
|
|
3085
3342
|
}
|
|
3086
3343
|
};
|
|
3087
3344
|
//#endregion
|
|
3345
|
+
//#region src/ribbon-router.ts
|
|
3346
|
+
/** Routes a ribbon, status-bar or shortcut action to the controller that owns it. */
|
|
3347
|
+
function routeRibbonAction(targets, action) {
|
|
3348
|
+
const { controller } = targets;
|
|
3349
|
+
const page = controller.state.document?.pages[controller.state.pageIndex];
|
|
3350
|
+
switch (action.type) {
|
|
3351
|
+
case "history": return targets.history(action.key);
|
|
3352
|
+
case "delete": return targets.deleteSelection();
|
|
3353
|
+
case "tool": return targets.setTool(action.tool);
|
|
3354
|
+
case "grid": return targets.toggleGrid();
|
|
3355
|
+
case "fullscreen": return targets.toggleFullscreen();
|
|
3356
|
+
case "pane": return targets.togglePane(action.pane);
|
|
3357
|
+
case "reveal": return targets.reveal(action.panel, action.focusText ?? false);
|
|
3358
|
+
case "search": return targets.focusSearch();
|
|
3359
|
+
case "page": return controller.setPage(controller.state.pageIndex + action.step);
|
|
3360
|
+
case "zoom":
|
|
3361
|
+
if (!page) return;
|
|
3362
|
+
if (action.mode === "actual") return controller.setZoom(1);
|
|
3363
|
+
return targets.fit(action.mode === "fit" ? "page" : "width");
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
//#endregion
|
|
3367
|
+
//#region src/viewer-draw-tool.ts
|
|
3368
|
+
/** Visio snaps new geometry to ruler subdivisions; 1/16 inch matches its default fine grid. */
|
|
3369
|
+
var SNAP = 1 / 16;
|
|
3370
|
+
var MIN_SIZE = SNAP;
|
|
3371
|
+
var snap = (value) => Math.round(value / SNAP) * SNAP;
|
|
3372
|
+
/** Next free numeric shape ID on the page tree. Core still rejects any collision. */
|
|
3373
|
+
function nextShapeId(page) {
|
|
3374
|
+
let max = 0;
|
|
3375
|
+
const pending = [...page.shapes];
|
|
3376
|
+
while (pending.length) {
|
|
3377
|
+
const shape = pending.pop();
|
|
3378
|
+
if (/^\d{1,9}$/.test(shape.id)) max = Math.max(max, Number(shape.id));
|
|
3379
|
+
pending.push(...shape.children);
|
|
3380
|
+
}
|
|
3381
|
+
return String(max + 1);
|
|
3382
|
+
}
|
|
3383
|
+
/** Page inches with a top-left origin (SVG user space) from a client point. */
|
|
3384
|
+
function pagePoint(svg, page, event) {
|
|
3385
|
+
const matrix = svg.getScreenCTM?.();
|
|
3386
|
+
if (!matrix) return void 0;
|
|
3387
|
+
const point = new DOMPoint(event.clientX, event.clientY).matrixTransform(matrix.inverse());
|
|
3388
|
+
return {
|
|
3389
|
+
x: snap(Math.max(0, Math.min(page.width, point.x))),
|
|
3390
|
+
y: snap(Math.max(0, Math.min(page.height, point.y)))
|
|
3391
|
+
};
|
|
3392
|
+
}
|
|
3393
|
+
/** Drag-to-draw rectangle tool. The preview is viewer-only; the shape is created by core. */
|
|
3394
|
+
var RectangleDrawTool = class {
|
|
3395
|
+
viewport;
|
|
3396
|
+
controller;
|
|
3397
|
+
options;
|
|
3398
|
+
#drag;
|
|
3399
|
+
#request = 0;
|
|
3400
|
+
constructor(viewport, controller, options) {
|
|
3401
|
+
this.viewport = viewport;
|
|
3402
|
+
this.controller = controller;
|
|
3403
|
+
this.options = options;
|
|
3404
|
+
}
|
|
3405
|
+
get drawing() {
|
|
3406
|
+
return !!this.#drag;
|
|
3407
|
+
}
|
|
3408
|
+
wire() {
|
|
3409
|
+
const events = new ((this.viewport.ownerDocument.defaultView?.AbortController) ?? AbortController)();
|
|
3410
|
+
const options = { signal: events.signal };
|
|
3411
|
+
const viewport = this.viewport;
|
|
3412
|
+
viewport.addEventListener("click", (event) => {
|
|
3413
|
+
if (this.options.active()) event.stopImmediatePropagation();
|
|
3414
|
+
}, {
|
|
3415
|
+
...options,
|
|
3416
|
+
capture: true
|
|
3417
|
+
});
|
|
3418
|
+
viewport.addEventListener("pointerdown", (event) => this.#start(event), options);
|
|
3419
|
+
viewport.addEventListener("pointermove", (event) => this.#move(event), options);
|
|
3420
|
+
viewport.addEventListener("pointerup", (event) => void this.#finish(event), options);
|
|
3421
|
+
viewport.addEventListener("pointercancel", () => this.#cancel(), options);
|
|
3422
|
+
viewport.addEventListener("keydown", (event) => {
|
|
3423
|
+
if (event.key === "Escape" && this.#drag) {
|
|
3424
|
+
event.preventDefault();
|
|
3425
|
+
this.#cancel();
|
|
3426
|
+
}
|
|
3427
|
+
}, options);
|
|
3428
|
+
return () => {
|
|
3429
|
+
++this.#request;
|
|
3430
|
+
this.#cancel();
|
|
3431
|
+
events.abort();
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
#start(event) {
|
|
3435
|
+
if (!this.options.active() || event.button !== 0 || this.#drag) return;
|
|
3436
|
+
const state = this.controller.state;
|
|
3437
|
+
const page = state.document?.pages[state.pageIndex];
|
|
3438
|
+
const svg = this.viewport.querySelector("svg.paper");
|
|
3439
|
+
if (!page || !svg || !state.edit.sourceAvailable || state.edit.busy) return;
|
|
3440
|
+
const start = pagePoint(svg, page, event);
|
|
3441
|
+
if (!start) return;
|
|
3442
|
+
event.preventDefault();
|
|
3443
|
+
const rect = this.viewport.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
3444
|
+
rect.classList.add("draw-preview");
|
|
3445
|
+
rect.setAttribute("vector-effect", "non-scaling-stroke");
|
|
3446
|
+
svg.append(rect);
|
|
3447
|
+
this.#drag = {
|
|
3448
|
+
pointer: event.pointerId,
|
|
3449
|
+
svg,
|
|
3450
|
+
page,
|
|
3451
|
+
...start,
|
|
3452
|
+
rect
|
|
3453
|
+
};
|
|
3454
|
+
this.viewport.setPointerCapture?.(event.pointerId);
|
|
3455
|
+
this.#update(start);
|
|
3456
|
+
}
|
|
3457
|
+
#move(event) {
|
|
3458
|
+
if (!this.#drag || event.pointerId !== this.#drag.pointer) return;
|
|
3459
|
+
const point = pagePoint(this.#drag.svg, this.#drag.page, event);
|
|
3460
|
+
if (point) this.#update(point);
|
|
3461
|
+
}
|
|
3462
|
+
#update(point) {
|
|
3463
|
+
const { x, y, rect } = this.#drag;
|
|
3464
|
+
rect.setAttribute("x", String(Math.min(x, point.x)));
|
|
3465
|
+
rect.setAttribute("y", String(Math.min(y, point.y)));
|
|
3466
|
+
rect.setAttribute("width", String(Math.abs(point.x - x)));
|
|
3467
|
+
rect.setAttribute("height", String(Math.abs(point.y - y)));
|
|
3468
|
+
}
|
|
3469
|
+
#cancel() {
|
|
3470
|
+
this.#drag?.rect.remove();
|
|
3471
|
+
this.#drag = void 0;
|
|
3472
|
+
}
|
|
3473
|
+
async #finish(event) {
|
|
3474
|
+
const drag = this.#drag;
|
|
3475
|
+
if (!drag || event.pointerId !== drag.pointer) return;
|
|
3476
|
+
const end = pagePoint(drag.svg, drag.page, event) ?? drag;
|
|
3477
|
+
this.#cancel();
|
|
3478
|
+
const width = Math.abs(end.x - drag.x), height = Math.abs(end.y - drag.y);
|
|
3479
|
+
if (width < MIN_SIZE || height < MIN_SIZE) {
|
|
3480
|
+
this.options.announce("Drag on the page to draw a rectangle.");
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
const state = this.controller.state;
|
|
3484
|
+
if (state.document?.pages[state.pageIndex] !== drag.page) return;
|
|
3485
|
+
const shapeId = nextShapeId(drag.page);
|
|
3486
|
+
const pageId = drag.page.id;
|
|
3487
|
+
const request = ++this.#request;
|
|
3488
|
+
try {
|
|
3489
|
+
await this.controller.applyEdits([{
|
|
3490
|
+
type: "create-rectangle",
|
|
3491
|
+
pageId,
|
|
3492
|
+
shapeId,
|
|
3493
|
+
x: (drag.x + end.x) / 2,
|
|
3494
|
+
y: drag.page.height - (drag.y + end.y) / 2,
|
|
3495
|
+
width,
|
|
3496
|
+
height
|
|
3497
|
+
}]);
|
|
3498
|
+
if (request !== this.#request) return;
|
|
3499
|
+
const created = this.controller.state.document?.pages.find((page) => page.id === pageId)?.shapes.find((shape) => shape.id === shapeId);
|
|
3500
|
+
if (created) this.controller.selectShape({
|
|
3501
|
+
id: shapeId,
|
|
3502
|
+
name: created.name,
|
|
3503
|
+
pageId
|
|
3504
|
+
});
|
|
3505
|
+
this.options.announce(`Rectangle ${shapeId} added (${+width.toFixed(4)} × ${+height.toFixed(4)} in).`);
|
|
3506
|
+
} catch (error) {
|
|
3507
|
+
if (request === this.#request && !isEditCancellation(error)) this.options.announce(editErrorMessage(error));
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
//#endregion
|
|
3512
|
+
//#region src/viewer-commands.ts
|
|
3513
|
+
var editable = (target) => target instanceof Element && !!target.closest("input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])");
|
|
3514
|
+
/**
|
|
3515
|
+
* Visio command state (tool, grid, pending edits) and keyboard shortcuts. Ribbon controls emit
|
|
3516
|
+
* typed `ribbon-action` events and shortcuts build the same actions; both go through
|
|
3517
|
+
* `routeRibbonAction`. Document mutation stays in the controller and core.
|
|
3518
|
+
*/
|
|
3519
|
+
var ViewerCommands = class {
|
|
3520
|
+
host;
|
|
3521
|
+
#tool = "pointer";
|
|
3522
|
+
#grid = false;
|
|
3523
|
+
#pending = 0;
|
|
3524
|
+
#draw;
|
|
3525
|
+
#targets;
|
|
3526
|
+
constructor(host) {
|
|
3527
|
+
this.host = host;
|
|
3528
|
+
this.#draw = new RectangleDrawTool(host.viewport, host.controller, {
|
|
3529
|
+
active: () => this.#tool === "rectangle",
|
|
3530
|
+
announce: host.announce
|
|
3531
|
+
});
|
|
3532
|
+
this.#targets = {
|
|
3533
|
+
controller: host.controller,
|
|
3534
|
+
history: (key) => this.#history(key),
|
|
3535
|
+
deleteSelection: () => this.#delete(),
|
|
3536
|
+
setTool: (tool) => this.setTool(tool),
|
|
3537
|
+
toggleGrid: () => {
|
|
3538
|
+
this.#grid = !this.#grid;
|
|
3539
|
+
this.render(host.controller.state);
|
|
3540
|
+
},
|
|
3541
|
+
toggleFullscreen: () => this.#toggleFullscreen(),
|
|
3542
|
+
togglePane: host.togglePane,
|
|
3543
|
+
reveal: host.reveal,
|
|
3544
|
+
fit: host.fit,
|
|
3545
|
+
focusSearch: host.focusSearch
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
get tool() {
|
|
3549
|
+
return this.#tool;
|
|
3550
|
+
}
|
|
3551
|
+
/** Run one typed action, exactly as a ribbon control or shortcut would. */
|
|
3552
|
+
run(action) {
|
|
3553
|
+
routeRibbonAction(this.#targets, action);
|
|
3554
|
+
}
|
|
3555
|
+
wire() {
|
|
3556
|
+
const { root, viewport } = this.host;
|
|
3557
|
+
const doc = root.ownerDocument;
|
|
3558
|
+
const events = new ((doc.defaultView?.AbortController) ?? AbortController)();
|
|
3559
|
+
const options = { signal: events.signal };
|
|
3560
|
+
root.addEventListener(RIBBON_ACTION_EVENT, (event) => this.run(event.detail), options);
|
|
3561
|
+
root.addEventListener("office-command", (event) => {
|
|
3562
|
+
if (event.detail?.command === "zoom-fit") this.run({
|
|
3563
|
+
type: "zoom",
|
|
3564
|
+
mode: "fit"
|
|
3565
|
+
});
|
|
3566
|
+
}, options);
|
|
3567
|
+
root.addEventListener("keydown", (event) => this.#shortcut(event), options);
|
|
3568
|
+
viewport.addEventListener("wheel", (event) => {
|
|
3569
|
+
if (!event.ctrlKey && !event.metaKey) return;
|
|
3570
|
+
event.preventDefault();
|
|
3571
|
+
const zoom = this.host.controller.state.zoom;
|
|
3572
|
+
this.host.controller.setZoom(event.deltaY < 0 ? zoom * 1.1 : zoom / 1.1);
|
|
3573
|
+
}, {
|
|
3574
|
+
...options,
|
|
3575
|
+
passive: false
|
|
3576
|
+
});
|
|
3577
|
+
viewport.addEventListener("pointerdown", () => {
|
|
3578
|
+
if (!viewport.contains(root.activeElement)) viewport.focus({ preventScroll: true });
|
|
3579
|
+
}, options);
|
|
3580
|
+
viewport.addEventListener("dblclick", (event) => {
|
|
3581
|
+
if (this.#tool !== "pointer") return;
|
|
3582
|
+
if (event.target?.closest?.("[data-shape-id]")) this.run({
|
|
3583
|
+
type: "reveal",
|
|
3584
|
+
panel: "edit",
|
|
3585
|
+
focusText: true
|
|
3586
|
+
});
|
|
3587
|
+
}, options);
|
|
3588
|
+
doc.addEventListener("fullscreenchange", () => this.render(this.host.controller.state), options);
|
|
3589
|
+
const disposeDraw = this.#draw.wire();
|
|
3590
|
+
return () => {
|
|
3591
|
+
++this.#pending;
|
|
3592
|
+
events.abort();
|
|
3593
|
+
disposeDraw();
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
setTool(tool) {
|
|
3597
|
+
if (tool === "rectangle" && !this.#canEdit(this.host.controller.state)) return;
|
|
3598
|
+
this.#tool = tool;
|
|
3599
|
+
this.render(this.host.controller.state);
|
|
3600
|
+
}
|
|
3601
|
+
#canEdit(state) {
|
|
3602
|
+
return state.edit.sourceAvailable && !state.loading && !state.edit.busy;
|
|
3603
|
+
}
|
|
3604
|
+
#history(key) {
|
|
3605
|
+
const { controller } = this.host;
|
|
3606
|
+
const { edit, loading } = controller.state;
|
|
3607
|
+
if ((key === "undo" ? edit.canUndo : edit.canRedo) && !edit.busy && !loading) this.#edit(() => controller[key]());
|
|
3608
|
+
}
|
|
3609
|
+
#delete() {
|
|
3610
|
+
const state = this.host.controller.state;
|
|
3611
|
+
const shape = state.selectedShape;
|
|
3612
|
+
const pageId = shape?.pageId ?? state.document?.pages[state.pageIndex]?.id;
|
|
3613
|
+
if (!shape || pageId === void 0 || !this.#canEdit(state)) return;
|
|
3614
|
+
this.#edit(() => this.host.controller.applyEdits([{
|
|
3615
|
+
type: "delete-shape",
|
|
3616
|
+
pageId,
|
|
3617
|
+
shapeId: shape.id
|
|
3618
|
+
}]), `Deleted ${shape.name || `shape ${shape.id}`}.`);
|
|
3619
|
+
}
|
|
3620
|
+
async #edit(action, success) {
|
|
3621
|
+
const request = ++this.#pending;
|
|
3622
|
+
const { root, viewport } = this.host;
|
|
3623
|
+
const canvasFocused = viewport.contains(root.activeElement);
|
|
3624
|
+
try {
|
|
3625
|
+
await action();
|
|
3626
|
+
if (canvasFocused && !viewport.contains(root.activeElement)) viewport.focus({ preventScroll: true });
|
|
3627
|
+
if (success && request === this.#pending) this.host.announce(success);
|
|
3628
|
+
} catch (error) {
|
|
3629
|
+
if (request === this.#pending && !isEditCancellation(error) && !this.host.controller.state.edit.error) this.host.announce(editErrorMessage(error));
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
#toggleFullscreen() {
|
|
3633
|
+
const host = this.host.root.host;
|
|
3634
|
+
const doc = this.host.root.ownerDocument;
|
|
3635
|
+
(doc.fullscreenElement === host ? doc.exitFullscreen?.() : host.requestFullscreen?.())?.catch?.(() => this.host.announce("Full screen is not available in this browser frame."));
|
|
3636
|
+
}
|
|
3637
|
+
/** Visio shortcuts as typed actions. Text fields keep native undo, deletion and editing keys. */
|
|
3638
|
+
#shortcutAction(event) {
|
|
3639
|
+
const state = this.host.controller.state;
|
|
3640
|
+
const control = event.ctrlKey || event.metaKey;
|
|
3641
|
+
const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
|
|
3642
|
+
if (control && key === "f") return { type: "search" };
|
|
3643
|
+
if (control && event.shiftKey && key === "w") return {
|
|
3644
|
+
type: "zoom",
|
|
3645
|
+
mode: "fit"
|
|
3646
|
+
};
|
|
3647
|
+
if (control && (key === "PageDown" || key === "PageUp")) return {
|
|
3648
|
+
type: "page",
|
|
3649
|
+
step: key === "PageDown" ? 1 : -1
|
|
3650
|
+
};
|
|
3651
|
+
if (key === "F5" && !control) return { type: "fullscreen" };
|
|
3652
|
+
if (editable(event.target)) return void 0;
|
|
3653
|
+
if (control && !event.shiftKey && key === "z") return {
|
|
3654
|
+
type: "history",
|
|
3655
|
+
key: "undo"
|
|
3656
|
+
};
|
|
3657
|
+
if (control && (key === "y" || event.shiftKey && key === "z")) return {
|
|
3658
|
+
type: "history",
|
|
3659
|
+
key: "redo"
|
|
3660
|
+
};
|
|
3661
|
+
if (control && key === "1") return {
|
|
3662
|
+
type: "tool",
|
|
3663
|
+
tool: "pointer"
|
|
3664
|
+
};
|
|
3665
|
+
if (control && key === "8") return {
|
|
3666
|
+
type: "tool",
|
|
3667
|
+
tool: "rectangle"
|
|
3668
|
+
};
|
|
3669
|
+
if (!control && key === "Delete" && state.selectedShape) return { type: "delete" };
|
|
3670
|
+
if (!control && key === "F2" && state.document) return {
|
|
3671
|
+
type: "reveal",
|
|
3672
|
+
panel: "edit",
|
|
3673
|
+
focusText: true
|
|
3674
|
+
};
|
|
3675
|
+
if (key === "Escape" && this.#tool !== "pointer" && !this.#draw.drawing) return {
|
|
3676
|
+
type: "tool",
|
|
3677
|
+
tool: "pointer"
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
#shortcut(event) {
|
|
3681
|
+
if (event.defaultPrevented || event.isComposing || event.altKey) return;
|
|
3682
|
+
const action = this.#shortcutAction(event);
|
|
3683
|
+
if (!action) return;
|
|
3684
|
+
event.preventDefault();
|
|
3685
|
+
this.run(action);
|
|
3686
|
+
}
|
|
3687
|
+
render(state) {
|
|
3688
|
+
const { root, viewport } = this.host;
|
|
3689
|
+
const button = (name) => root.querySelector(`office-ui-button[command="${name}"]`);
|
|
3690
|
+
const editing = this.#canEdit(state);
|
|
3691
|
+
if (this.#tool === "rectangle" && !state.edit.sourceAvailable) this.#tool = "pointer";
|
|
3692
|
+
const page = state.document?.pages[state.pageIndex];
|
|
3693
|
+
button("undo").disabled = !state.edit.canUndo || state.edit.busy || state.loading;
|
|
3694
|
+
button("redo").disabled = !state.edit.canRedo || state.edit.busy || state.loading;
|
|
3695
|
+
button("delete").disabled = !editing || !state.selectedShape;
|
|
3696
|
+
button("pointer").setAttribute("pressed", String(this.#tool === "pointer"));
|
|
3697
|
+
const rectangle = button("rectangle");
|
|
3698
|
+
rectangle.setAttribute("pressed", String(this.#tool === "rectangle"));
|
|
3699
|
+
rectangle.disabled = !editing || !page;
|
|
3700
|
+
rectangle.title = state.edit.sourceAvailable ? "Rectangle (Ctrl+8)" : "Rectangle (Ctrl+8): open a .vsdx file to draw. Model-only documents are read only.";
|
|
3701
|
+
button("grid").setAttribute("pressed", String(this.#grid));
|
|
3702
|
+
button("grid").disabled = !page;
|
|
3703
|
+
for (const name of [
|
|
3704
|
+
"zoom-fit",
|
|
3705
|
+
"page-width",
|
|
3706
|
+
"actual-size"
|
|
3707
|
+
]) button(name).disabled = !page;
|
|
3708
|
+
const fullscreen = button("fullscreen");
|
|
3709
|
+
const host = root.host;
|
|
3710
|
+
fullscreen.disabled = typeof host.requestFullscreen !== "function";
|
|
3711
|
+
fullscreen.setAttribute("pressed", String(root.ownerDocument.fullscreenElement === host));
|
|
3712
|
+
viewport.dataset.tool = this.#tool;
|
|
3713
|
+
viewport.dataset.grid = String(this.#grid);
|
|
3714
|
+
}
|
|
3715
|
+
};
|
|
3716
|
+
//#endregion
|
|
3717
|
+
//#region src/office-ui.ts
|
|
3718
|
+
/**
|
|
3719
|
+
* Visio-only glyphs (20x20 path data). Neutral Office glyphs come from ooxml-ui; add a glyph
|
|
3720
|
+
* there instead when another Office product could use it.
|
|
3721
|
+
*/
|
|
3722
|
+
var VISIO_ICONS = {
|
|
3723
|
+
visioLayers: "m2.5 7 7.5-4 7.5 4-7.5 4ZM2.5 10.5l7.5 4 7.5-4M2.5 14l7.5 4 7.5-4",
|
|
3724
|
+
visioShapeData: "M3 3.5h14v13H3ZM3 7.5h14M8 7.5v9M10.5 11h4M10.5 14h4",
|
|
3725
|
+
visioPagesPane: "M2.5 3h15v14h-15ZM7 3v14M3.8 6h1.9M3.8 9h1.9M3.8 12h1.9",
|
|
3726
|
+
visioInspectorPane: "M2.5 3h15v14h-15ZM13 3v14M14.5 6h1.5M14.5 9h1.5M14.5 12h1.5"
|
|
3727
|
+
};
|
|
3728
|
+
/** Define the shared Office controls the viewer chrome uses. Idempotent; browser only. */
|
|
3729
|
+
function registerViewerControls(registry) {
|
|
3730
|
+
for (const [name, path] of Object.entries(VISIO_ICONS)) if (!getIcon(name)) registerIcon(name, path);
|
|
3731
|
+
registerControls(registry);
|
|
3732
|
+
}
|
|
3733
|
+
//#endregion
|
|
3088
3734
|
//#region src/export-svg.ts
|
|
3089
3735
|
var MAX_SVG_EXPORT_BYTES = 16777216;
|
|
3090
3736
|
var APPROXIMATION = "Static SVG export is a rendered approximation, not an editable VSDX round-trip or a guarantee of Microsoft Visio fidelity. Fonts are not embedded.";
|
|
@@ -3695,8 +4341,10 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3695
4341
|
#root;
|
|
3696
4342
|
#disposeInputs;
|
|
3697
4343
|
#viewport;
|
|
3698
|
-
#
|
|
3699
|
-
#
|
|
4344
|
+
#zoomSlider;
|
|
4345
|
+
#shapeStatus;
|
|
4346
|
+
#announcement;
|
|
4347
|
+
#commands;
|
|
3700
4348
|
#status;
|
|
3701
4349
|
#diagnostics;
|
|
3702
4350
|
#toolbar;
|
|
@@ -3729,10 +4377,14 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3729
4377
|
constructor() {
|
|
3730
4378
|
super();
|
|
3731
4379
|
this.#root = this.attachShadow({ mode: "open" });
|
|
3732
|
-
|
|
4380
|
+
registerViewerControls();
|
|
4381
|
+
this.#root.innerHTML = `<style>${viewerStyles}${canvasAndRibbonStyles}</style>${viewerChromeTemplate}`;
|
|
4382
|
+
const workspace = this.#root.querySelector(".workspace");
|
|
4383
|
+
workspace.before(createRibbon(document));
|
|
4384
|
+
workspace.after(createPageTabs(document), createStatusBar(document));
|
|
3733
4385
|
this.#viewport = this.#root.querySelector(".viewport");
|
|
3734
|
-
this.#
|
|
3735
|
-
this.#
|
|
4386
|
+
this.#zoomSlider = this.#root.querySelector("office-ui-zoom-slider");
|
|
4387
|
+
this.#shapeStatus = this.#root.querySelector("[data-shape-status]");
|
|
3736
4388
|
this.#status = this.#root.querySelector("[data-status]");
|
|
3737
4389
|
this.#diagnostics = this.#root.querySelector("[data-diagnostics]");
|
|
3738
4390
|
this.#toolbar = this.#root.querySelector(".toolbar");
|
|
@@ -3743,6 +4395,23 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3743
4395
|
this.#notesPanel = this.#root.querySelector(".notes");
|
|
3744
4396
|
this.#inspector = this.#root.querySelector(".shape-inspector");
|
|
3745
4397
|
this.#chrome = new ViewerChrome(this.#root, this.controller);
|
|
4398
|
+
this.#commands = new ViewerCommands({
|
|
4399
|
+
root: this.#root,
|
|
4400
|
+
viewport: this.#viewport,
|
|
4401
|
+
controller: this.controller,
|
|
4402
|
+
fit: (mode) => this.#fit(mode),
|
|
4403
|
+
togglePane: (pane) => this.#chrome.togglePane(pane),
|
|
4404
|
+
reveal: (panel, focusText) => this.#chrome.reveal(panel, focusText),
|
|
4405
|
+
focusSearch: () => {
|
|
4406
|
+
this.#chrome.showTab("home");
|
|
4407
|
+
this.#search.input.focus();
|
|
4408
|
+
this.#search.input.select();
|
|
4409
|
+
},
|
|
4410
|
+
announce: (message) => {
|
|
4411
|
+
this.#announcement = message;
|
|
4412
|
+
this.#status.textContent = message;
|
|
4413
|
+
}
|
|
4414
|
+
});
|
|
3746
4415
|
this.#disposeInputs = this.#wireInputs();
|
|
3747
4416
|
this.#unsubscribe = this.controller.subscribe((state) => this.#render(state));
|
|
3748
4417
|
this.#eventUnsubscribe = this.controller.onEvent((name, detail) => {
|
|
@@ -3816,13 +4485,24 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3816
4485
|
}
|
|
3817
4486
|
fit() {
|
|
3818
4487
|
this.#assertAlive();
|
|
4488
|
+
this.#fit("page");
|
|
4489
|
+
}
|
|
4490
|
+
/** Visio Fit to Window shows the whole page; Page Width fills the canvas width. */
|
|
4491
|
+
#fit(mode) {
|
|
3819
4492
|
const page = this.document?.pages[this.pageIndex];
|
|
3820
4493
|
if (!page) return;
|
|
3821
4494
|
const style = this.ownerDocument.defaultView?.getComputedStyle(this.#viewport);
|
|
3822
4495
|
const padding = (value) => Number.parseFloat(value ?? "") || 0;
|
|
3823
4496
|
const width = Math.max(1, this.#viewport.clientWidth - padding(style?.paddingLeft) - padding(style?.paddingRight));
|
|
3824
4497
|
const height = Math.max(1, this.#viewport.clientHeight - padding(style?.paddingTop) - padding(style?.paddingBottom));
|
|
3825
|
-
|
|
4498
|
+
if (mode === "page") {
|
|
4499
|
+
this.zoom = Math.min(1, width / (page.width * 96), height / (page.height * 96));
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
const scrollbar = this.#viewport.offsetWidth - this.#viewport.clientWidth;
|
|
4503
|
+
const fitted = width / (page.width * 96);
|
|
4504
|
+
const reserve = page.height * 96 * fitted > height && scrollbar <= 0 ? 17 : 0;
|
|
4505
|
+
this.zoom = Math.max(1, width - reserve - 1) / (page.width * 96);
|
|
3826
4506
|
}
|
|
3827
4507
|
setLayerVisibility(pageId, layerId, visible) {
|
|
3828
4508
|
this.#assertAlive();
|
|
@@ -3892,16 +4572,18 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3892
4572
|
}
|
|
3893
4573
|
#wireInputs() {
|
|
3894
4574
|
const disposeChrome = this.#chrome.wire();
|
|
4575
|
+
const disposeCommands = this.#commands.wire();
|
|
3895
4576
|
const disposeEdit = this.#edit.wire();
|
|
3896
4577
|
const disposeLayers = wireLayerControls(this.#layers, this.controller);
|
|
3897
4578
|
const disposeInputs = wireViewerInputs({
|
|
3898
4579
|
viewport: this.#viewport,
|
|
3899
4580
|
commandRoot: this.#root,
|
|
3900
|
-
|
|
4581
|
+
zoomSlider: this.#zoomSlider,
|
|
3901
4582
|
searchInput: this.#search.input
|
|
3902
|
-
}, this.controller, () => this
|
|
4583
|
+
}, this.controller, (mode) => this.#fit(mode));
|
|
3903
4584
|
return () => {
|
|
3904
4585
|
disposeChrome();
|
|
4586
|
+
disposeCommands();
|
|
3905
4587
|
disposeInputs();
|
|
3906
4588
|
disposeLayers();
|
|
3907
4589
|
disposeEdit();
|
|
@@ -3920,14 +4602,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3920
4602
|
this.#document = state.document;
|
|
3921
4603
|
this.#renderedPage = state.pageIndex;
|
|
3922
4604
|
this.#renderedLayerOverrides = state.layerVisibilityOverrides;
|
|
3923
|
-
this.#
|
|
3924
|
-
for (const [index, candidate] of (state.document?.pages ?? []).entries()) {
|
|
3925
|
-
const option = document.createElement("option");
|
|
3926
|
-
option.value = String(index);
|
|
3927
|
-
option.textContent = candidate.name + (candidate.isBackground ? " (background)" : "");
|
|
3928
|
-
this.#pageSelect.append(option);
|
|
3929
|
-
}
|
|
3930
|
-
this.#pageSelect.value = String(state.pageIndex);
|
|
4605
|
+
this.#announcement = void 0;
|
|
3931
4606
|
this.#renderWarnings = [];
|
|
3932
4607
|
if (state.document && page) {
|
|
3933
4608
|
const result = renderPage(state.document, page, { layerVisibilityOverrides: state.layerVisibilityOverrides });
|
|
@@ -3954,6 +4629,7 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3954
4629
|
if (svg && page) {
|
|
3955
4630
|
svg.style.width = `${page.width * 96 * state.zoom}px`;
|
|
3956
4631
|
svg.style.height = `${page.height * 96 * state.zoom}px`;
|
|
4632
|
+
svg.style.setProperty("--vv-inch", `${96 * state.zoom}px`);
|
|
3957
4633
|
}
|
|
3958
4634
|
const searchResult = state.search.results[state.search.activeIndex];
|
|
3959
4635
|
const selection = state.selectedShape;
|
|
@@ -3981,16 +4657,20 @@ var VisioViewerElement = class extends BaseElement {
|
|
|
3981
4657
|
this.#renderedSelection = selection ? { ...selection } : null;
|
|
3982
4658
|
}
|
|
3983
4659
|
if (!searchResult) this.#revealedSearchResult = void 0;
|
|
3984
|
-
this.#
|
|
3985
|
-
this.#
|
|
4660
|
+
this.#zoomSlider.value = Math.round(state.zoom * 100);
|
|
4661
|
+
this.#zoomSlider.disabled = !page;
|
|
4662
|
+
const inspected = selectedShape(state.document, state.selectedShape, state.pageIndex);
|
|
4663
|
+
const inches = (value) => `${+value.toFixed(3)} in`;
|
|
4664
|
+
this.#shapeStatus.setAttribute("value", inspected ? `Width: ${inches(inspected.width)} Height: ${inches(inspected.height)}` : "");
|
|
3986
4665
|
for (const button of this.#root.querySelectorAll("[data-action]")) button.disabled = !page;
|
|
3987
4666
|
renderSearchControls(this.#search, state);
|
|
3988
4667
|
this.#edit.render(state);
|
|
3989
4668
|
if (changed) renderLayerControls(this.#layers, state);
|
|
3990
4669
|
else this.#layers.querySelector("[data-layer-reset=\"all\"]").disabled = state.layerVisibilityOverrides.length === 0;
|
|
3991
4670
|
this.#chrome.render(state, this.#notes.children.length);
|
|
4671
|
+
this.#commands.render(state);
|
|
3992
4672
|
this.#viewport.setAttribute("aria-busy", String(state.loading || state.edit.busy));
|
|
3993
|
-
this.#status.textContent = state.loading ? "Opening diagram…" : state.error?.message ?? (page ? `${page.name} · ${page.shapes.length} top-level shapes` : "No diagram open");
|
|
4673
|
+
this.#status.textContent = state.loading ? "Opening diagram…" : state.edit.busy ? "Updating diagram…" : state.error?.message ?? (state.edit.error ? `Edit rejected: ${editErrorMessage(state.edit.error)}` : void 0) ?? this.#announcement ?? (page ? `${page.name} · ${page.shapes.length} top-level shapes` : "No diagram open");
|
|
3994
4674
|
const warnings = (state.document?.diagnostics.length ?? 0) + this.#renderWarnings.length;
|
|
3995
4675
|
this.#diagnostics.textContent = warnings ? `${this.#notes.children.length} compatibility notes` : "Local-only viewing";
|
|
3996
4676
|
}
|
|
@@ -4200,6 +4880,23 @@ function viewerHandle(current) {
|
|
|
4200
4880
|
}
|
|
4201
4881
|
};
|
|
4202
4882
|
}
|
|
4883
|
+
function viewerStateSource(current) {
|
|
4884
|
+
const controller = () => {
|
|
4885
|
+
try {
|
|
4886
|
+
return current()?.controller;
|
|
4887
|
+
} catch {
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
};
|
|
4891
|
+
return {
|
|
4892
|
+
subscribe(onChange) {
|
|
4893
|
+
return controller()?.subscribe(() => onChange()) ?? (() => {});
|
|
4894
|
+
},
|
|
4895
|
+
getSnapshot() {
|
|
4896
|
+
return controller()?.state ?? null;
|
|
4897
|
+
}
|
|
4898
|
+
};
|
|
4899
|
+
}
|
|
4203
4900
|
//#endregion
|
|
4204
4901
|
//#region packages/bindings/src/solid.tsx
|
|
4205
4902
|
var _tmpl$ = [
|
|
@@ -4243,5 +4940,19 @@ function VisioViewer(props) {
|
|
|
4243
4940
|
});
|
|
4244
4941
|
return ssr(_tmpl$, ssrHydrationKey() + ssrAttribute("class", escape(props.class, true), false), ssrStyle(props.style));
|
|
4245
4942
|
}
|
|
4943
|
+
/**
|
|
4944
|
+
* Reactive viewer state as a Solid accessor for a handle accessor (for example a signal set by
|
|
4945
|
+
* `viewerRef`). Follows handle changes and cleans up with the owning scope.
|
|
4946
|
+
*/
|
|
4947
|
+
function createVisioViewerState(handle) {
|
|
4948
|
+
const [state, setState] = createSignal(null);
|
|
4949
|
+
createEffect(() => {
|
|
4950
|
+
const current = handle();
|
|
4951
|
+
const source = viewerStateSource(() => current);
|
|
4952
|
+
setState(() => source.getSnapshot());
|
|
4953
|
+
onCleanup(source.subscribe(() => setState(() => source.getSnapshot())));
|
|
4954
|
+
});
|
|
4955
|
+
return state;
|
|
4956
|
+
}
|
|
4246
4957
|
//#endregion
|
|
4247
|
-
export { MAX_SVG_EXPORT_BYTES, PRINT_SNAPSHOT_LIMITS, TEXT_SEARCH_LIMITS, VIEWER_LAYER_LIMITS, ViewerController, VisioViewer, VisioViewerElement, compatibilityNotes, compatibilityText, createPrintSnapshot, createWorkerParser, eventKeys, exportPageSvg, mountViewer, propertyKeys, registerVisioViewer, renderPage };
|
|
4958
|
+
export { MAX_SVG_EXPORT_BYTES, PRINT_SNAPSHOT_LIMITS, TEXT_SEARCH_LIMITS, VIEWER_LAYER_LIMITS, ViewerController, VisioViewer, VisioViewerElement, compatibilityNotes, compatibilityText, createPrintSnapshot, createVisioViewerState, createWorkerParser, eventKeys, exportPageSvg, mountViewer, propertyKeys, registerVisioViewer, renderPage };
|