visio-svelte-viewer 0.7.4 → 1.0.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.
@@ -0,0 +1,6 @@
1
+ <script lang="ts">
2
+ let { mount, class: className }: { mount(node: HTMLElement | null): void; class?: string } = $props();
3
+ let host: HTMLDivElement;
4
+ $effect(() => { const current = mount; current(host); return () => current(null); });
5
+ </script>
6
+ <div bind:this={host} class={className}></div>
@@ -0,0 +1,4 @@
1
+ import type { Component } from 'svelte';
2
+ import type { ViewerController, DiagramToolbarProps, ToolbarGroupId } from './runtime.js';
3
+ declare const component: Component<{ mount(node: HTMLElement | null): void; class?: string }>;
4
+ export default component;
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ import { buildingBlock, type ViewerController } from './runtime.js';
3
+ let { controller }: { controller: ViewerController } = $props();
4
+ let host: HTMLDivElement;
5
+ $effect(() => buildingBlock('mountDiagramInspector')(host, controller));
6
+ </script>
7
+ <div bind:this={host}></div>
@@ -0,0 +1,4 @@
1
+ import type { Component } from 'svelte';
2
+ import type { ViewerController, DiagramToolbarProps, ToolbarGroupId } from './runtime.js';
3
+ declare const component: Component<{ controller: ViewerController }>;
4
+ export default component;
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ import { prepareDiagramBlock } from './runtime.js';
3
+ function theme(host: HTMLElement) { prepareDiagramBlock(host, 'tabs'); }
4
+ import type { DiagramToolbarProps } from './runtime.js';
5
+ let { toolbarProps }: { toolbarProps: DiagramToolbarProps } = $props();
6
+ </script>
7
+ <select use:theme aria-label={toolbarProps.labels.Page} value={toolbarProps.pageIndex} onchange={event => toolbarProps.onPageChange(Number(event.currentTarget.value))}>
8
+ {#each toolbarProps.pages as page, index (page.id)}<option value={index}>{page.name}</option>{/each}
9
+ </select>
@@ -0,0 +1,4 @@
1
+ import type { Component } from 'svelte';
2
+ import type { ViewerController, DiagramToolbarProps, ToolbarGroupId } from './runtime.js';
3
+ declare const component: Component<{ toolbarProps: DiagramToolbarProps }>;
4
+ export default component;
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ import { buildingBlock, type ViewerController } from './runtime.js';
3
+ let { controller, locale }: { controller: ViewerController; locale?: string | undefined } = $props();
4
+ let host: HTMLDivElement;
5
+ $effect(() => buildingBlock('mountDiagramShapes')(host, controller, { locale }));
6
+ </script>
7
+ <div bind:this={host}></div>
@@ -0,0 +1,4 @@
1
+ import type { Component } from 'svelte';
2
+ import type { ViewerController, DiagramToolbarProps, ToolbarGroupId } from './runtime.js';
3
+ declare const component: Component<{ controller: ViewerController; locale?: string | undefined }>;
4
+ export default component;
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { prepareDiagramBlock } from './runtime.js';
3
+ import { toolbarGroups, type ToolbarGroupId } from './runtime.js';
4
+ function theme(host: HTMLElement) { prepareDiagramBlock(host, 'toolbar'); }
5
+ import type { DiagramToolbarProps } from './runtime.js';
6
+ let { toolbarProps, include }: { toolbarProps: DiagramToolbarProps; include?: readonly ToolbarGroupId[] } = $props();
7
+ const groups = $derived(toolbarGroups(toolbarProps, include));
8
+ </script>
9
+ <div role="toolbar" use:theme>
10
+ {#each groups as group (group.id)}
11
+ <span data-group={group.id}>
12
+ {#each group.controls as control (control.id)}
13
+ {#if control.kind === 'text'}
14
+ <span data-control={control.id}>{control.text}</span>
15
+ {:else if control.kind === 'button'}
16
+ <button type="button" data-control={control.id} disabled={control.disabled} aria-pressed={control.pressed} onclick={() => control.run()}>{control.label}</button>
17
+ {:else}
18
+ <span data-control="search">
19
+ <input type="search" aria-label={control.label} placeholder={control.label} value={control.query} oninput={(event) => control.change(event.currentTarget.value)} />
20
+ <span aria-live="polite">{control.summary}</span>
21
+ <button type="button" data-control={control.previous.id} disabled={control.previous.disabled} onclick={() => control.previous.run()}>{control.previous.label}</button>
22
+ <button type="button" data-control={control.next.id} disabled={control.next.disabled} onclick={() => control.next.run()}>{control.next.label}</button>
23
+ </span>
24
+ {/if}
25
+ {/each}
26
+ </span>
27
+ {/each}
28
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { Component } from 'svelte';
2
+ import type { ViewerController, DiagramToolbarProps, ToolbarGroupId } from './runtime.js';
3
+ declare const component: Component<{ toolbarProps: DiagramToolbarProps; include?: readonly ToolbarGroupId[] }>;
4
+ export default component;
@@ -3,8 +3,8 @@
3
3
  import { readonly, writable } from 'svelte/store';
4
4
  import { mountFrameworkViewer, viewerHandle, viewerOptions, viewerStateSource, type ViewerState, type MountedViewer, type ViewerProps, type VsdxSource, type VisioEdit, type VisioShapeSelection, type SvgExportOptions, type CurrentPagePrintSnapshotOptions } from './runtime.js';
5
5
  type Props = ViewerProps & { class?: string; style?: string; 'aria-label'?: string };
6
- let { document, pageIndex, zoom, showToolbar, ribbonAddIns, events, class: className, style, 'aria-label': ariaLabel }: Props = $props();
7
- const options = $derived({ document, pageIndex, zoom, showToolbar, ribbonAddIns, events } satisfies Required<ViewerProps>);
6
+ let { document, pageIndex, zoom, showToolbar, canEdit, ribbonAddIns, collaboration, events, class: className, style, 'aria-label': ariaLabel }: Props = $props();
7
+ const options = $derived({ document, pageIndex, zoom, showToolbar, canEdit, ribbonAddIns, collaboration, events } satisfies Required<ViewerProps>);
8
8
  let binding: MountedViewer | undefined;
9
9
  const handle = viewerHandle(() => binding);
10
10
  // The component owns its state store, so subscribers made before mount still see updates.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,9 @@
1
1
  export * from './types/src/index.js';
2
2
  export * from 'visio-core';
3
3
  export { default, default as VisioViewer } from './VisioViewer.svelte';
4
+ export * from './types/packages/svelte/src/building-blocks.js';
5
+ export { default as DiagramCanvas } from './DiagramCanvas.svelte';
6
+ export { default as Toolbar } from './Toolbar.svelte';
7
+ export { default as PageNavigation } from './PageNavigation.svelte';
8
+ export { default as Inspector } from './Inspector.svelte';
9
+ export { default as ShapesPane } from './ShapesPane.svelte';
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
- import { mountViewer, propertyKeys } from "ooxml-ui/visio";
1
+ import * as ui from "ooxml-ui/visio";
2
+ import { ViewerController, createWorkerParser, mountViewer, propertyKeys } from "ooxml-ui/visio";
2
3
  import "svelte/internal/disclose-version";
3
4
  import * as $ from "svelte/internal/client";
4
- import { readonly, writable } from "svelte/store";
5
+ import { derived, get, readonly, writable } from "svelte/store";
6
+ import { onDestroy } from "svelte";
7
+ import * as officeTheme from "ooxml-ui/theme";
5
8
  export * from "ooxml-ui/visio";
6
9
  export * from "visio-core";
7
10
  //#region packages/bindings/src/common.ts
@@ -146,7 +149,7 @@ function viewerStateSource(current) {
146
149
  }
147
150
  //#endregion
148
151
  //#region packages/bindings/src/VisioViewer.svelte
149
- var root = $.from_html(`<div></div>`);
152
+ var root$5 = $.from_html(`<div></div>`);
150
153
  function VisioViewer($$anchor, $$props) {
151
154
  $.push($$props, true);
152
155
  const options = $.derived(() => ({
@@ -154,7 +157,9 @@ function VisioViewer($$anchor, $$props) {
154
157
  pageIndex: $$props.pageIndex,
155
158
  zoom: $$props.zoom,
156
159
  showToolbar: $$props.showToolbar,
160
+ canEdit: $$props.canEdit,
157
161
  ribbonAddIns: $$props.ribbonAddIns,
162
+ collaboration: $$props.collaboration,
158
163
  events: $$props.events
159
164
  }));
160
165
  let binding;
@@ -269,7 +274,7 @@ function VisioViewer($$anchor, $$props) {
269
274
  exportSvg,
270
275
  createPrintSnapshot
271
276
  };
272
- var div = root();
277
+ var div = root$5();
273
278
  $.action(div, ($$node, $$action_arg) => attach?.($$node, $$action_arg), () => $.get(options));
274
279
  $.template_effect(() => {
275
280
  $.set_class(div, 1, $.clsx($$props.class));
@@ -280,4 +285,367 @@ function VisioViewer($$anchor, $$props) {
280
285
  return $.pop($$exports);
281
286
  }
282
287
  //#endregion
283
- export { VisioViewer, VisioViewer as default };
288
+ //#region packages/bindings/src/building-blocks.ts
289
+ function buildingBlock(key) {
290
+ const value = Reflect.get(ui, key);
291
+ if (typeof value !== "function") throw new Error("Update ooxml-ui to use Visio building blocks.");
292
+ return value;
293
+ }
294
+ /** The mode a host sees: 'edit' only when permission is granted and the file can be saved back. */
295
+ function effectiveMode(state) {
296
+ return buildingBlock("diagramMode")(state);
297
+ }
298
+ /** Framework-neutral ownership only; each adapter supplies its own reactive state. */
299
+ function createBuildingBlockSession(initial = {}) {
300
+ const controller = new ViewerController(createWorkerParser());
301
+ controller.canEdit = initial.canEdit === true;
302
+ let options = initial;
303
+ let source;
304
+ let host = null;
305
+ let disposeCanvas;
306
+ let destroyed = false;
307
+ const stopEvents = controller.onEvent((name, detail) => {
308
+ if (name === "document-change") {
309
+ const change = detail;
310
+ options.onDocumentChange?.(change.document);
311
+ options.onDirtyChange?.(change.dirty);
312
+ } else if (name === "document-error") options.onDocumentError?.(detail);
313
+ });
314
+ const fit = () => {
315
+ if (host) buildingBlock("fitDiagramCanvas")(host, controller);
316
+ };
317
+ return {
318
+ controller,
319
+ mount(node) {
320
+ if (destroyed || node === host) return;
321
+ disposeCanvas?.();
322
+ disposeCanvas = void 0;
323
+ host = node;
324
+ if (node) disposeCanvas = buildingBlock("mountDiagramCanvas")(node, controller);
325
+ },
326
+ /** Flat control props; pass the framework's latest options so callbacks stay current. */
327
+ toolbar(latest = options) {
328
+ return buildingBlock("diagramToolbarProps")(controller, fit, latest.locale, {
329
+ onOpenFile: latest.onOpenFile,
330
+ onOpenPageSetup: latest.onOpenPageSetup,
331
+ onOpenSettings: latest.onOpenSettings,
332
+ onOpenShareDialog: latest.onOpenShareDialog
333
+ });
334
+ },
335
+ /** Acknowledge a host save: clears dirty and emits the dirty change. */
336
+ markClean() {
337
+ controller.markClean();
338
+ },
339
+ update(next) {
340
+ if (destroyed) return;
341
+ options = next;
342
+ controller.canEdit = next.canEdit === true;
343
+ if (source === next.content) return;
344
+ source = next.content;
345
+ controller.cancelLoad();
346
+ if (!source) return;
347
+ const input = source;
348
+ controller.loadSource(async () => {
349
+ return input instanceof Blob ? new Uint8Array(await input.arrayBuffer()) : input;
350
+ }).catch(() => {});
351
+ },
352
+ destroy() {
353
+ if (destroyed) return;
354
+ destroyed = true;
355
+ disposeCanvas?.();
356
+ disposeCanvas = void 0;
357
+ host = null;
358
+ stopEvents();
359
+ controller.destroy();
360
+ }
361
+ };
362
+ }
363
+ /** Resolve the new optional-control theme API only when that control is mounted. */
364
+ function prepareDiagramBlock(host, kind) {
365
+ if (!host) return;
366
+ const prepare = Reflect.get(officeTheme, "prepareOfficeUiBlock");
367
+ if (typeof prepare !== "function") throw new Error("Update ooxml-ui to theme Visio building blocks.");
368
+ prepare(host, kind, "visio");
369
+ }
370
+ //#endregion
371
+ //#region packages/svelte/src/building-blocks.ts
372
+ /** Call during component initialization. Reactive option changes use blocks.update(options). */
373
+ function createViewerBuildingBlocks(options = {}) {
374
+ const source = "subscribe" in options ? options : void 0;
375
+ const initial = source ? get(source) : options;
376
+ const session = createBuildingBlockSession(initial);
377
+ const state = writable(session.controller.state);
378
+ const latest = writable(initial);
379
+ const stop = session.controller.subscribe((value) => state.set(value));
380
+ let stopOptions = () => {};
381
+ onDestroy(() => {
382
+ stopOptions();
383
+ stop();
384
+ session.destroy();
385
+ });
386
+ const update = (next) => {
387
+ latest.set(next);
388
+ session.update(next);
389
+ };
390
+ if (source) stopOptions = source.subscribe(update);
391
+ else update(initial);
392
+ return {
393
+ controller: session.controller,
394
+ state: { subscribe: state.subscribe },
395
+ toolbarProps: derived([state, latest], ([, next]) => session.toolbar(next)),
396
+ loading: derived(state, (value) => value.loading),
397
+ error: derived(state, (value) => value.error ?? value.edit.error),
398
+ /** Effective permission: host `canEdit` and a drawing that can be saved back. */
399
+ mode: derived(state, (value) => effectiveMode(value)),
400
+ canvasProps: { mount: session.mount },
401
+ inspectorProps: { controller: session.controller },
402
+ shapesProps: derived(latest, (next) => ({
403
+ controller: session.controller,
404
+ locale: next.locale
405
+ })),
406
+ /** Acknowledge a host save: clears dirty and fires `onDirtyChange(false)`. */
407
+ markClean: () => session.markClean(),
408
+ update
409
+ };
410
+ }
411
+ //#endregion
412
+ //#region packages/svelte/src/DiagramCanvas.svelte
413
+ var root$4 = $.from_html(`<div></div>`);
414
+ function DiagramCanvas($$anchor, $$props) {
415
+ $.push($$props, true);
416
+ let host;
417
+ $.user_effect(() => {
418
+ const current = $$props.mount;
419
+ current(host);
420
+ return () => current(null);
421
+ });
422
+ var div = root$4();
423
+ $.bind_this(div, ($$value) => host = $$value, () => host);
424
+ $.template_effect(() => $.set_class(div, 1, $.clsx($$props.class)));
425
+ $.append($$anchor, div);
426
+ $.pop();
427
+ }
428
+ //#endregion
429
+ //#region packages/bindings/src/toolbar-controls.ts
430
+ var TOOLBAR_GROUPS = [
431
+ "file",
432
+ "history",
433
+ "zoom",
434
+ "selection",
435
+ "clipboard",
436
+ "tools",
437
+ "search"
438
+ ];
439
+ var fire = (action) => () => {
440
+ Promise.resolve().then(action).catch(() => {});
441
+ };
442
+ var button = (id, label, enabled, action, pressed) => ({
443
+ kind: "button",
444
+ id,
445
+ label,
446
+ disabled: !enabled,
447
+ ...pressed === void 0 ? {} : { pressed },
448
+ run: fire(action)
449
+ });
450
+ /**
451
+ * What the library Toolbar shows, as plain data every framework maps onto its own elements.
452
+ * Every control reflects `toolbarProps`: edit actions are disabled while the diagram is read-only,
453
+ * viewing actions (zoom, fit, search) never are, and a host dialog button appears only when the
454
+ * host supplied its callback.
455
+ */
456
+ function toolbarGroups(props, include = TOOLBAR_GROUPS) {
457
+ const { labels } = props;
458
+ const has = props.pages.length > 0;
459
+ const groups = [];
460
+ const add = (id, controls) => {
461
+ if (include.includes(id) && controls.length) groups.push({
462
+ id,
463
+ controls
464
+ });
465
+ };
466
+ add("file", [
467
+ ...props.onOpenFile ? [button("open", labels.Open, true, props.onOpenFile)] : [],
468
+ ...props.onOpenPageSetup ? [button("page-setup", labels["Page Setup"], has, props.onOpenPageSetup)] : [],
469
+ ...props.onOpenShareDialog ? [button("share", labels.Share, has, props.onOpenShareDialog)] : [],
470
+ ...props.onOpenSettings ? [button("settings", labels.Options, true, props.onOpenSettings)] : []
471
+ ]);
472
+ add("history", [button("undo", labels.Undo, props.canUndo, props.onUndo), button("redo", labels.Redo, props.canRedo, props.onRedo)]);
473
+ add("zoom", [
474
+ button("zoom-out", labels["Zoom Out"], props.canZoomOut, props.onZoomOut),
475
+ {
476
+ kind: "text",
477
+ id: "zoom",
478
+ text: `${Math.round(props.zoom * 100)}%`
479
+ },
480
+ button("zoom-in", labels["Zoom In"], props.canZoomIn, props.onZoomIn),
481
+ button("fit", labels["Fit to Window"], has, props.onFit)
482
+ ]);
483
+ add("selection", [
484
+ button("duplicate", labels.Duplicate, props.canDuplicate, props.onDuplicate),
485
+ button("group", labels.Group, props.canGroup, props.onGroup),
486
+ button("ungroup", labels.Ungroup, props.canUngroup, props.onUngroup),
487
+ button("delete", labels.Delete, props.canDelete, props.onDelete)
488
+ ]);
489
+ add("clipboard", [
490
+ button("cut", labels.Cut, props.canCut, props.onCut),
491
+ button("copy", labels.Copy, props.canCopy, props.onCopy),
492
+ button("paste", labels.Paste, props.canPaste, props.onPaste)
493
+ ]);
494
+ add("tools", props.tools.map((tool) => button(`tool-${tool.id}`, tool.label, tool.id === "pointer" || props.canDraw, () => props.onToolChange(tool.id), tool.active)));
495
+ const { search } = props;
496
+ add("search", [{
497
+ kind: "search",
498
+ id: "search",
499
+ label: labels.Find,
500
+ query: search.query,
501
+ summary: search.query ? search.resultCount ? `${search.activeIndex >= 0 ? search.activeIndex + 1 : "-"} / ${search.resultCount}` : "0" : "",
502
+ previous: button("search-previous", labels["Previous Result"], search.resultCount > 0, props.onPreviousSearchResult),
503
+ next: button("search-next", labels["Next Result"], search.resultCount > 0, props.onNextSearchResult),
504
+ change: (query) => props.onSearchChange(query)
505
+ }]);
506
+ return groups;
507
+ }
508
+ //#endregion
509
+ //#region packages/svelte/src/Toolbar.svelte
510
+ var root$3 = $.from_html(`<span> </span>`);
511
+ var root_1$1 = $.from_html(`<button type="button"> </button>`);
512
+ var root_2 = $.from_html(`<span data-control="search"><input type="search"/> <span aria-live="polite"> </span> <button type="button"> </button> <button type="button"> </button></span>`);
513
+ var root_3 = $.from_html(`<span></span>`);
514
+ var root_4 = $.from_html(`<div role="toolbar"></div>`);
515
+ function Toolbar($$anchor, $$props) {
516
+ $.push($$props, true);
517
+ function theme(host) {
518
+ prepareDiagramBlock(host, "toolbar");
519
+ }
520
+ const groups = $.derived(() => toolbarGroups($$props.toolbarProps, $$props.include));
521
+ var div = root_4();
522
+ $.each(div, 21, () => $.get(groups), (group) => group.id, ($$anchor, group) => {
523
+ var span = root_3();
524
+ $.each(span, 21, () => $.get(group).controls, (control) => control.id, ($$anchor, control) => {
525
+ var fragment = $.comment();
526
+ var node = $.first_child(fragment);
527
+ var consequent = ($$anchor) => {
528
+ var span_1 = root$3();
529
+ var text = $.only_child(span_1, true);
530
+ $.template_effect(() => {
531
+ $.set_attribute(span_1, "data-control", $.get(control).id);
532
+ $.set_text(text, $.get(control).text);
533
+ });
534
+ $.append($$anchor, span_1);
535
+ };
536
+ var consequent_1 = ($$anchor) => {
537
+ var button = root_1$1();
538
+ var text_1 = $.only_child(button, true);
539
+ $.template_effect(() => {
540
+ $.set_attribute(button, "data-control", $.get(control).id);
541
+ button.disabled = $.get(control).disabled;
542
+ $.set_attribute(button, "aria-pressed", $.get(control).pressed);
543
+ $.set_text(text_1, $.get(control).label);
544
+ });
545
+ $.delegated("click", button, () => $.get(control).run());
546
+ $.append($$anchor, button);
547
+ };
548
+ var alternate = ($$anchor) => {
549
+ var span_2 = root_2();
550
+ var input = $.child(span_2);
551
+ $.remove_input_defaults(input);
552
+ var span_3 = $.sibling(input, 2);
553
+ var text_2 = $.only_child(span_3, true);
554
+ var button_1 = $.sibling(span_3, 2);
555
+ var text_3 = $.only_child(button_1, true);
556
+ var button_2 = $.sibling(button_1, 2);
557
+ var text_4 = $.only_child(button_2, true);
558
+ $.reset(span_2);
559
+ $.template_effect(() => {
560
+ $.set_attribute(input, "aria-label", $.get(control).label);
561
+ $.set_attribute(input, "placeholder", $.get(control).label);
562
+ $.set_value(input, $.get(control).query);
563
+ $.set_text(text_2, $.get(control).summary);
564
+ $.set_attribute(button_1, "data-control", $.get(control).previous.id);
565
+ button_1.disabled = $.get(control).previous.disabled;
566
+ $.set_text(text_3, $.get(control).previous.label);
567
+ $.set_attribute(button_2, "data-control", $.get(control).next.id);
568
+ button_2.disabled = $.get(control).next.disabled;
569
+ $.set_text(text_4, $.get(control).next.label);
570
+ });
571
+ $.delegated("input", input, (event) => $.get(control).change(event.currentTarget.value));
572
+ $.delegated("click", button_1, () => $.get(control).previous.run());
573
+ $.delegated("click", button_2, () => $.get(control).next.run());
574
+ $.append($$anchor, span_2);
575
+ };
576
+ $.if(node, ($$render) => {
577
+ if ($.get(control).kind === "text") $$render(consequent);
578
+ else if ($.get(control).kind === "button") $$render(consequent_1, 1);
579
+ else $$render(alternate, -1);
580
+ });
581
+ $.append($$anchor, fragment);
582
+ });
583
+ $.reset(span);
584
+ $.template_effect(() => $.set_attribute(span, "data-group", $.get(group).id));
585
+ $.append($$anchor, span);
586
+ });
587
+ $.reset(div);
588
+ $.action(div, ($$node) => theme?.($$node));
589
+ $.append($$anchor, div);
590
+ $.pop();
591
+ }
592
+ $.delegate(["click", "input"]);
593
+ //#endregion
594
+ //#region packages/svelte/src/PageNavigation.svelte
595
+ var root$2 = $.from_html(`<option> </option>`);
596
+ var root_1 = $.from_html(`<select></select>`);
597
+ function PageNavigation($$anchor, $$props) {
598
+ $.push($$props, true);
599
+ function theme(host) {
600
+ prepareDiagramBlock(host, "tabs");
601
+ }
602
+ var select = root_1();
603
+ $.each(select, 23, () => $$props.toolbarProps.pages, (page) => page.id, ($$anchor, page, index) => {
604
+ var option = root$2();
605
+ var text = $.only_child(option, true);
606
+ var option_value = {};
607
+ $.template_effect(() => {
608
+ $.set_text(text, $.get(page).name);
609
+ if (option_value !== (option_value = $.get(index))) option.value = (option.__value = option_value) ?? "";
610
+ });
611
+ $.append($$anchor, option);
612
+ });
613
+ $.reset(select);
614
+ $.action(select, ($$node) => theme?.($$node));
615
+ var select_value;
616
+ $.init_select(select);
617
+ $.template_effect(() => {
618
+ $.set_attribute(select, "aria-label", $$props.toolbarProps.labels.Page);
619
+ if (select_value !== (select_value = $$props.toolbarProps.pageIndex)) select.value = (select.__value = select_value) ?? "", $.select_option(select, select_value);
620
+ });
621
+ $.delegated("change", select, (event) => $$props.toolbarProps.onPageChange(Number(event.currentTarget.value)));
622
+ $.append($$anchor, select);
623
+ $.pop();
624
+ }
625
+ $.delegate(["change"]);
626
+ //#endregion
627
+ //#region packages/svelte/src/Inspector.svelte
628
+ var root$1 = $.from_html(`<div></div>`);
629
+ function Inspector($$anchor, $$props) {
630
+ $.push($$props, true);
631
+ let host;
632
+ $.user_effect(() => buildingBlock("mountDiagramInspector")(host, $$props.controller));
633
+ var div = root$1();
634
+ $.bind_this(div, ($$value) => host = $$value, () => host);
635
+ $.append($$anchor, div);
636
+ $.pop();
637
+ }
638
+ //#endregion
639
+ //#region packages/svelte/src/ShapesPane.svelte
640
+ var root = $.from_html(`<div></div>`);
641
+ function ShapesPane($$anchor, $$props) {
642
+ $.push($$props, true);
643
+ let host;
644
+ $.user_effect(() => buildingBlock("mountDiagramShapes")(host, $$props.controller, { locale: $$props.locale }));
645
+ var div = root();
646
+ $.bind_this(div, ($$value) => host = $$value, () => host);
647
+ $.append($$anchor, div);
648
+ $.pop();
649
+ }
650
+ //#endregion
651
+ export { DiagramCanvas, Inspector, PageNavigation, ShapesPane, Toolbar, VisioViewer, VisioViewer as default, createViewerBuildingBlocks };
@@ -1,6 +1,9 @@
1
- import "ooxml-ui/visio";
1
+ import * as ui from "ooxml-ui/visio";
2
+ import { ViewerController, createWorkerParser } from "ooxml-ui/visio";
2
3
  import * as $ from "svelte/internal/server";
3
- import { readonly, writable } from "svelte/store";
4
+ import { derived, get, readonly, writable } from "svelte/store";
5
+ import { onDestroy } from "svelte";
6
+ import "ooxml-ui/theme";
4
7
  export * from "ooxml-ui/visio";
5
8
  export * from "visio-core";
6
9
  //#region packages/bindings/src/common.ts
@@ -84,13 +87,15 @@ function viewerHandle(current) {
84
87
  //#region packages/bindings/src/VisioViewer.svelte
85
88
  function VisioViewer($$renderer, $$props) {
86
89
  $$renderer.component(($$renderer) => {
87
- let { document, pageIndex, zoom, showToolbar, ribbonAddIns, events, class: className, style, "aria-label": ariaLabel } = $$props;
90
+ let { document, pageIndex, zoom, showToolbar, canEdit, ribbonAddIns, collaboration, events, class: className, style, "aria-label": ariaLabel } = $$props;
88
91
  $.derived(() => ({
89
92
  document,
90
93
  pageIndex,
91
94
  zoom,
92
95
  showToolbar,
96
+ canEdit,
93
97
  ribbonAddIns,
98
+ collaboration,
94
99
  events
95
100
  }));
96
101
  let binding;
@@ -191,4 +196,271 @@ function VisioViewer($$renderer, $$props) {
191
196
  });
192
197
  }
193
198
  //#endregion
194
- export { VisioViewer, VisioViewer as default };
199
+ //#region packages/bindings/src/building-blocks.ts
200
+ function buildingBlock(key) {
201
+ const value = Reflect.get(ui, key);
202
+ if (typeof value !== "function") throw new Error("Update ooxml-ui to use Visio building blocks.");
203
+ return value;
204
+ }
205
+ /** The mode a host sees: 'edit' only when permission is granted and the file can be saved back. */
206
+ function effectiveMode(state) {
207
+ return buildingBlock("diagramMode")(state);
208
+ }
209
+ /** Framework-neutral ownership only; each adapter supplies its own reactive state. */
210
+ function createBuildingBlockSession(initial = {}) {
211
+ const controller = new ViewerController(createWorkerParser());
212
+ controller.canEdit = initial.canEdit === true;
213
+ let options = initial;
214
+ let source;
215
+ let host = null;
216
+ let disposeCanvas;
217
+ let destroyed = false;
218
+ const stopEvents = controller.onEvent((name, detail) => {
219
+ if (name === "document-change") {
220
+ const change = detail;
221
+ options.onDocumentChange?.(change.document);
222
+ options.onDirtyChange?.(change.dirty);
223
+ } else if (name === "document-error") options.onDocumentError?.(detail);
224
+ });
225
+ const fit = () => {
226
+ if (host) buildingBlock("fitDiagramCanvas")(host, controller);
227
+ };
228
+ return {
229
+ controller,
230
+ mount(node) {
231
+ if (destroyed || node === host) return;
232
+ disposeCanvas?.();
233
+ disposeCanvas = void 0;
234
+ host = node;
235
+ if (node) disposeCanvas = buildingBlock("mountDiagramCanvas")(node, controller);
236
+ },
237
+ /** Flat control props; pass the framework's latest options so callbacks stay current. */
238
+ toolbar(latest = options) {
239
+ return buildingBlock("diagramToolbarProps")(controller, fit, latest.locale, {
240
+ onOpenFile: latest.onOpenFile,
241
+ onOpenPageSetup: latest.onOpenPageSetup,
242
+ onOpenSettings: latest.onOpenSettings,
243
+ onOpenShareDialog: latest.onOpenShareDialog
244
+ });
245
+ },
246
+ /** Acknowledge a host save: clears dirty and emits the dirty change. */
247
+ markClean() {
248
+ controller.markClean();
249
+ },
250
+ update(next) {
251
+ if (destroyed) return;
252
+ options = next;
253
+ controller.canEdit = next.canEdit === true;
254
+ if (source === next.content) return;
255
+ source = next.content;
256
+ controller.cancelLoad();
257
+ if (!source) return;
258
+ const input = source;
259
+ controller.loadSource(async () => {
260
+ return input instanceof Blob ? new Uint8Array(await input.arrayBuffer()) : input;
261
+ }).catch(() => {});
262
+ },
263
+ destroy() {
264
+ if (destroyed) return;
265
+ destroyed = true;
266
+ disposeCanvas?.();
267
+ disposeCanvas = void 0;
268
+ host = null;
269
+ stopEvents();
270
+ controller.destroy();
271
+ }
272
+ };
273
+ }
274
+ //#endregion
275
+ //#region packages/svelte/src/building-blocks.ts
276
+ /** Call during component initialization. Reactive option changes use blocks.update(options). */
277
+ function createViewerBuildingBlocks(options = {}) {
278
+ const source = "subscribe" in options ? options : void 0;
279
+ const initial = source ? get(source) : options;
280
+ const session = createBuildingBlockSession(initial);
281
+ const state = writable(session.controller.state);
282
+ const latest = writable(initial);
283
+ const stop = session.controller.subscribe((value) => state.set(value));
284
+ let stopOptions = () => {};
285
+ onDestroy(() => {
286
+ stopOptions();
287
+ stop();
288
+ session.destroy();
289
+ });
290
+ const update = (next) => {
291
+ latest.set(next);
292
+ session.update(next);
293
+ };
294
+ if (source) stopOptions = source.subscribe(update);
295
+ else update(initial);
296
+ return {
297
+ controller: session.controller,
298
+ state: { subscribe: state.subscribe },
299
+ toolbarProps: derived([state, latest], ([, next]) => session.toolbar(next)),
300
+ loading: derived(state, (value) => value.loading),
301
+ error: derived(state, (value) => value.error ?? value.edit.error),
302
+ /** Effective permission: host `canEdit` and a drawing that can be saved back. */
303
+ mode: derived(state, (value) => effectiveMode(value)),
304
+ canvasProps: { mount: session.mount },
305
+ inspectorProps: { controller: session.controller },
306
+ shapesProps: derived(latest, (next) => ({
307
+ controller: session.controller,
308
+ locale: next.locale
309
+ })),
310
+ /** Acknowledge a host save: clears dirty and fires `onDirtyChange(false)`. */
311
+ markClean: () => session.markClean(),
312
+ update
313
+ };
314
+ }
315
+ //#endregion
316
+ //#region packages/svelte/src/DiagramCanvas.svelte
317
+ function DiagramCanvas($$renderer, $$props) {
318
+ $$renderer.component(($$renderer) => {
319
+ let { mount, class: className } = $$props;
320
+ $$renderer.push(`<div${$.attr_class($.clsx(className))}></div>`);
321
+ });
322
+ }
323
+ //#endregion
324
+ //#region packages/bindings/src/toolbar-controls.ts
325
+ var TOOLBAR_GROUPS = [
326
+ "file",
327
+ "history",
328
+ "zoom",
329
+ "selection",
330
+ "clipboard",
331
+ "tools",
332
+ "search"
333
+ ];
334
+ var fire = (action) => () => {
335
+ Promise.resolve().then(action).catch(() => {});
336
+ };
337
+ var button = (id, label, enabled, action, pressed) => ({
338
+ kind: "button",
339
+ id,
340
+ label,
341
+ disabled: !enabled,
342
+ ...pressed === void 0 ? {} : { pressed },
343
+ run: fire(action)
344
+ });
345
+ /**
346
+ * What the library Toolbar shows, as plain data every framework maps onto its own elements.
347
+ * Every control reflects `toolbarProps`: edit actions are disabled while the diagram is read-only,
348
+ * viewing actions (zoom, fit, search) never are, and a host dialog button appears only when the
349
+ * host supplied its callback.
350
+ */
351
+ function toolbarGroups(props, include = TOOLBAR_GROUPS) {
352
+ const { labels } = props;
353
+ const has = props.pages.length > 0;
354
+ const groups = [];
355
+ const add = (id, controls) => {
356
+ if (include.includes(id) && controls.length) groups.push({
357
+ id,
358
+ controls
359
+ });
360
+ };
361
+ add("file", [
362
+ ...props.onOpenFile ? [button("open", labels.Open, true, props.onOpenFile)] : [],
363
+ ...props.onOpenPageSetup ? [button("page-setup", labels["Page Setup"], has, props.onOpenPageSetup)] : [],
364
+ ...props.onOpenShareDialog ? [button("share", labels.Share, has, props.onOpenShareDialog)] : [],
365
+ ...props.onOpenSettings ? [button("settings", labels.Options, true, props.onOpenSettings)] : []
366
+ ]);
367
+ add("history", [button("undo", labels.Undo, props.canUndo, props.onUndo), button("redo", labels.Redo, props.canRedo, props.onRedo)]);
368
+ add("zoom", [
369
+ button("zoom-out", labels["Zoom Out"], props.canZoomOut, props.onZoomOut),
370
+ {
371
+ kind: "text",
372
+ id: "zoom",
373
+ text: `${Math.round(props.zoom * 100)}%`
374
+ },
375
+ button("zoom-in", labels["Zoom In"], props.canZoomIn, props.onZoomIn),
376
+ button("fit", labels["Fit to Window"], has, props.onFit)
377
+ ]);
378
+ add("selection", [
379
+ button("duplicate", labels.Duplicate, props.canDuplicate, props.onDuplicate),
380
+ button("group", labels.Group, props.canGroup, props.onGroup),
381
+ button("ungroup", labels.Ungroup, props.canUngroup, props.onUngroup),
382
+ button("delete", labels.Delete, props.canDelete, props.onDelete)
383
+ ]);
384
+ add("clipboard", [
385
+ button("cut", labels.Cut, props.canCut, props.onCut),
386
+ button("copy", labels.Copy, props.canCopy, props.onCopy),
387
+ button("paste", labels.Paste, props.canPaste, props.onPaste)
388
+ ]);
389
+ add("tools", props.tools.map((tool) => button(`tool-${tool.id}`, tool.label, tool.id === "pointer" || props.canDraw, () => props.onToolChange(tool.id), tool.active)));
390
+ const { search } = props;
391
+ add("search", [{
392
+ kind: "search",
393
+ id: "search",
394
+ label: labels.Find,
395
+ query: search.query,
396
+ summary: search.query ? search.resultCount ? `${search.activeIndex >= 0 ? search.activeIndex + 1 : "-"} / ${search.resultCount}` : "0" : "",
397
+ previous: button("search-previous", labels["Previous Result"], search.resultCount > 0, props.onPreviousSearchResult),
398
+ next: button("search-next", labels["Next Result"], search.resultCount > 0, props.onNextSearchResult),
399
+ change: (query) => props.onSearchChange(query)
400
+ }]);
401
+ return groups;
402
+ }
403
+ //#endregion
404
+ //#region packages/svelte/src/Toolbar.svelte
405
+ function Toolbar($$renderer, $$props) {
406
+ $$renderer.component(($$renderer) => {
407
+ let { toolbarProps, include } = $$props;
408
+ const groups = $.derived(() => toolbarGroups(toolbarProps, include));
409
+ $$renderer.push(`<div role="toolbar"><!--[-->`);
410
+ const each_array = $.ensure_array_like(groups());
411
+ for (let $$index_1 = 0, $$length = each_array.length; $$index_1 < $$length; $$index_1++) {
412
+ let group = each_array[$$index_1];
413
+ $$renderer.push(`<span${$.attr("data-group", group.id)}><!--[-->`);
414
+ const each_array_1 = $.ensure_array_like(group.controls);
415
+ for (let $$index = 0, $$length = each_array_1.length; $$index < $$length; $$index++) {
416
+ let control = each_array_1[$$index];
417
+ if (control.kind === "text") $$renderer.push(`<!--[0--><span${$.attr("data-control", control.id)}>${$.escape(control.text)}</span>`);
418
+ else if (control.kind === "button") $$renderer.push(`<!--[1--><button type="button"${$.attr("data-control", control.id)}${$.attr("disabled", control.disabled, true)}${$.attr("aria-pressed", control.pressed)}>${$.escape(control.label)}</button>`);
419
+ else $$renderer.push(`<!--[-1--><span data-control="search"><input type="search"${$.attr("aria-label", control.label)}${$.attr("placeholder", control.label)}${$.attr("value", control.query)}/> <span aria-live="polite">${$.escape(control.summary)}</span> <button type="button"${$.attr("data-control", control.previous.id)}${$.attr("disabled", control.previous.disabled, true)}>${$.escape(control.previous.label)}</button> <button type="button"${$.attr("data-control", control.next.id)}${$.attr("disabled", control.next.disabled, true)}>${$.escape(control.next.label)}</button></span>`);
420
+ $$renderer.push(`<!--]-->`);
421
+ }
422
+ $$renderer.push(`<!--]--></span>`);
423
+ }
424
+ $$renderer.push(`<!--]--></div>`);
425
+ });
426
+ }
427
+ //#endregion
428
+ //#region packages/svelte/src/PageNavigation.svelte
429
+ function PageNavigation($$renderer, $$props) {
430
+ $$renderer.component(($$renderer) => {
431
+ let { toolbarProps } = $$props;
432
+ $$renderer.select({
433
+ "aria-label": toolbarProps.labels.Page,
434
+ value: toolbarProps.pageIndex,
435
+ onchange: (event) => toolbarProps.onPageChange(Number(event.currentTarget.value))
436
+ }, ($$renderer) => {
437
+ $$renderer.push(`<!--[-->`);
438
+ const each_array = $.ensure_array_like(toolbarProps.pages);
439
+ for (let index = 0, $$length = each_array.length; index < $$length; index++) {
440
+ let page = each_array[index];
441
+ $$renderer.option({ value: index }, ($$renderer) => {
442
+ $$renderer.push(`${$.escape(page.name)}`);
443
+ });
444
+ }
445
+ $$renderer.push(`<!--]-->`);
446
+ });
447
+ });
448
+ }
449
+ //#endregion
450
+ //#region packages/svelte/src/Inspector.svelte
451
+ function Inspector($$renderer, $$props) {
452
+ $$renderer.component(($$renderer) => {
453
+ let { controller } = $$props;
454
+ $$renderer.push(`<div></div>`);
455
+ });
456
+ }
457
+ //#endregion
458
+ //#region packages/svelte/src/ShapesPane.svelte
459
+ function ShapesPane($$renderer, $$props) {
460
+ $$renderer.component(($$renderer) => {
461
+ let { controller, locale } = $$props;
462
+ $$renderer.push(`<div></div>`);
463
+ });
464
+ }
465
+ //#endregion
466
+ export { DiagramCanvas, Inspector, PageNavigation, ShapesPane, Toolbar, VisioViewer, VisioViewer as default, createViewerBuildingBlocks };
@@ -1,2 +1,8 @@
1
1
  export * from './runtime.js';
2
2
  export { default, default as VisioViewer } from './VisioViewer.svelte';
3
+ export { createViewerBuildingBlocks } from './runtime.js';
4
+ export { default as DiagramCanvas } from './DiagramCanvas.svelte';
5
+ export { default as Toolbar } from './Toolbar.svelte';
6
+ export { default as PageNavigation } from './PageNavigation.svelte';
7
+ export { default as Inspector } from './Inspector.svelte';
8
+ export { default as ShapesPane } from './ShapesPane.svelte';
package/dist/runtime.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './types/packages/bindings/src/common.js';
2
+ export * from './types/packages/bindings/src/building-blocks.js';
3
+ export * from './types/packages/bindings/src/toolbar-controls.js';
4
+ export * from './types/packages/svelte/src/building-blocks.js';
2
5
  export * from './types/src/index.js';
3
6
  export * from 'visio-core';
package/dist/runtime.js CHANGED
@@ -1,4 +1,8 @@
1
- import { ViewerController, eventKeys, mountViewer, propertyKeys } from "ooxml-ui/visio";
1
+ import * as ui from "ooxml-ui/visio";
2
+ import { ViewerController, ViewerController as ViewerController$1, createWorkerParser, eventKeys, mountViewer, propertyKeys } from "ooxml-ui/visio";
3
+ import * as officeTheme from "ooxml-ui/theme";
4
+ import { onDestroy } from "svelte";
5
+ import { derived, get, writable } from "svelte/store";
2
6
  export * from "ooxml-ui/visio";
3
7
  export * from "visio-core";
4
8
  //#region packages/bindings/src/common.ts
@@ -158,4 +162,208 @@ function withEventEmitter(props, emit) {
158
162
  };
159
163
  }
160
164
  //#endregion
161
- export { ViewerController, eventKeys, mountFrameworkViewer, mountViewer, propertyKeys, viewerHandle, viewerOptions, viewerStateSource, withEventEmitter };
165
+ //#region packages/bindings/src/building-blocks.ts
166
+ function buildingBlock(key) {
167
+ const value = Reflect.get(ui, key);
168
+ if (typeof value !== "function") throw new Error("Update ooxml-ui to use Visio building blocks.");
169
+ return value;
170
+ }
171
+ /** The mode a host sees: 'edit' only when permission is granted and the file can be saved back. */
172
+ function effectiveMode(state) {
173
+ return buildingBlock("diagramMode")(state);
174
+ }
175
+ /** Framework-neutral ownership only; each adapter supplies its own reactive state. */
176
+ function createBuildingBlockSession(initial = {}) {
177
+ const controller = new ViewerController$1(createWorkerParser());
178
+ controller.canEdit = initial.canEdit === true;
179
+ let options = initial;
180
+ let source;
181
+ let host = null;
182
+ let disposeCanvas;
183
+ let destroyed = false;
184
+ const stopEvents = controller.onEvent((name, detail) => {
185
+ if (name === "document-change") {
186
+ const change = detail;
187
+ options.onDocumentChange?.(change.document);
188
+ options.onDirtyChange?.(change.dirty);
189
+ } else if (name === "document-error") options.onDocumentError?.(detail);
190
+ });
191
+ const fit = () => {
192
+ if (host) buildingBlock("fitDiagramCanvas")(host, controller);
193
+ };
194
+ return {
195
+ controller,
196
+ mount(node) {
197
+ if (destroyed || node === host) return;
198
+ disposeCanvas?.();
199
+ disposeCanvas = void 0;
200
+ host = node;
201
+ if (node) disposeCanvas = buildingBlock("mountDiagramCanvas")(node, controller);
202
+ },
203
+ /** Flat control props; pass the framework's latest options so callbacks stay current. */
204
+ toolbar(latest = options) {
205
+ return buildingBlock("diagramToolbarProps")(controller, fit, latest.locale, {
206
+ onOpenFile: latest.onOpenFile,
207
+ onOpenPageSetup: latest.onOpenPageSetup,
208
+ onOpenSettings: latest.onOpenSettings,
209
+ onOpenShareDialog: latest.onOpenShareDialog
210
+ });
211
+ },
212
+ /** Acknowledge a host save: clears dirty and emits the dirty change. */
213
+ markClean() {
214
+ controller.markClean();
215
+ },
216
+ update(next) {
217
+ if (destroyed) return;
218
+ options = next;
219
+ controller.canEdit = next.canEdit === true;
220
+ if (source === next.content) return;
221
+ source = next.content;
222
+ controller.cancelLoad();
223
+ if (!source) return;
224
+ const input = source;
225
+ controller.loadSource(async () => {
226
+ return input instanceof Blob ? new Uint8Array(await input.arrayBuffer()) : input;
227
+ }).catch(() => {});
228
+ },
229
+ destroy() {
230
+ if (destroyed) return;
231
+ destroyed = true;
232
+ disposeCanvas?.();
233
+ disposeCanvas = void 0;
234
+ host = null;
235
+ stopEvents();
236
+ controller.destroy();
237
+ }
238
+ };
239
+ }
240
+ /** Resolve the new optional-control theme API only when that control is mounted. */
241
+ function prepareDiagramBlock(host, kind) {
242
+ if (!host) return;
243
+ const prepare = Reflect.get(officeTheme, "prepareOfficeUiBlock");
244
+ if (typeof prepare !== "function") throw new Error("Update ooxml-ui to theme Visio building blocks.");
245
+ prepare(host, kind, "visio");
246
+ }
247
+ //#endregion
248
+ //#region packages/bindings/src/toolbar-controls.ts
249
+ var TOOLBAR_GROUPS = [
250
+ "file",
251
+ "history",
252
+ "zoom",
253
+ "selection",
254
+ "clipboard",
255
+ "tools",
256
+ "search"
257
+ ];
258
+ var fire = (action) => () => {
259
+ Promise.resolve().then(action).catch(() => {});
260
+ };
261
+ var button = (id, label, enabled, action, pressed) => ({
262
+ kind: "button",
263
+ id,
264
+ label,
265
+ disabled: !enabled,
266
+ ...pressed === void 0 ? {} : { pressed },
267
+ run: fire(action)
268
+ });
269
+ /**
270
+ * What the library Toolbar shows, as plain data every framework maps onto its own elements.
271
+ * Every control reflects `toolbarProps`: edit actions are disabled while the diagram is read-only,
272
+ * viewing actions (zoom, fit, search) never are, and a host dialog button appears only when the
273
+ * host supplied its callback.
274
+ */
275
+ function toolbarGroups(props, include = TOOLBAR_GROUPS) {
276
+ const { labels } = props;
277
+ const has = props.pages.length > 0;
278
+ const groups = [];
279
+ const add = (id, controls) => {
280
+ if (include.includes(id) && controls.length) groups.push({
281
+ id,
282
+ controls
283
+ });
284
+ };
285
+ add("file", [
286
+ ...props.onOpenFile ? [button("open", labels.Open, true, props.onOpenFile)] : [],
287
+ ...props.onOpenPageSetup ? [button("page-setup", labels["Page Setup"], has, props.onOpenPageSetup)] : [],
288
+ ...props.onOpenShareDialog ? [button("share", labels.Share, has, props.onOpenShareDialog)] : [],
289
+ ...props.onOpenSettings ? [button("settings", labels.Options, true, props.onOpenSettings)] : []
290
+ ]);
291
+ add("history", [button("undo", labels.Undo, props.canUndo, props.onUndo), button("redo", labels.Redo, props.canRedo, props.onRedo)]);
292
+ add("zoom", [
293
+ button("zoom-out", labels["Zoom Out"], props.canZoomOut, props.onZoomOut),
294
+ {
295
+ kind: "text",
296
+ id: "zoom",
297
+ text: `${Math.round(props.zoom * 100)}%`
298
+ },
299
+ button("zoom-in", labels["Zoom In"], props.canZoomIn, props.onZoomIn),
300
+ button("fit", labels["Fit to Window"], has, props.onFit)
301
+ ]);
302
+ add("selection", [
303
+ button("duplicate", labels.Duplicate, props.canDuplicate, props.onDuplicate),
304
+ button("group", labels.Group, props.canGroup, props.onGroup),
305
+ button("ungroup", labels.Ungroup, props.canUngroup, props.onUngroup),
306
+ button("delete", labels.Delete, props.canDelete, props.onDelete)
307
+ ]);
308
+ add("clipboard", [
309
+ button("cut", labels.Cut, props.canCut, props.onCut),
310
+ button("copy", labels.Copy, props.canCopy, props.onCopy),
311
+ button("paste", labels.Paste, props.canPaste, props.onPaste)
312
+ ]);
313
+ add("tools", props.tools.map((tool) => button(`tool-${tool.id}`, tool.label, tool.id === "pointer" || props.canDraw, () => props.onToolChange(tool.id), tool.active)));
314
+ const { search } = props;
315
+ add("search", [{
316
+ kind: "search",
317
+ id: "search",
318
+ label: labels.Find,
319
+ query: search.query,
320
+ summary: search.query ? search.resultCount ? `${search.activeIndex >= 0 ? search.activeIndex + 1 : "-"} / ${search.resultCount}` : "0" : "",
321
+ previous: button("search-previous", labels["Previous Result"], search.resultCount > 0, props.onPreviousSearchResult),
322
+ next: button("search-next", labels["Next Result"], search.resultCount > 0, props.onNextSearchResult),
323
+ change: (query) => props.onSearchChange(query)
324
+ }]);
325
+ return groups;
326
+ }
327
+ //#endregion
328
+ //#region packages/svelte/src/building-blocks.ts
329
+ /** Call during component initialization. Reactive option changes use blocks.update(options). */
330
+ function createViewerBuildingBlocks(options = {}) {
331
+ const source = "subscribe" in options ? options : void 0;
332
+ const initial = source ? get(source) : options;
333
+ const session = createBuildingBlockSession(initial);
334
+ const state = writable(session.controller.state);
335
+ const latest = writable(initial);
336
+ const stop = session.controller.subscribe((value) => state.set(value));
337
+ let stopOptions = () => {};
338
+ onDestroy(() => {
339
+ stopOptions();
340
+ stop();
341
+ session.destroy();
342
+ });
343
+ const update = (next) => {
344
+ latest.set(next);
345
+ session.update(next);
346
+ };
347
+ if (source) stopOptions = source.subscribe(update);
348
+ else update(initial);
349
+ return {
350
+ controller: session.controller,
351
+ state: { subscribe: state.subscribe },
352
+ toolbarProps: derived([state, latest], ([, next]) => session.toolbar(next)),
353
+ loading: derived(state, (value) => value.loading),
354
+ error: derived(state, (value) => value.error ?? value.edit.error),
355
+ /** Effective permission: host `canEdit` and a drawing that can be saved back. */
356
+ mode: derived(state, (value) => effectiveMode(value)),
357
+ canvasProps: { mount: session.mount },
358
+ inspectorProps: { controller: session.controller },
359
+ shapesProps: derived(latest, (next) => ({
360
+ controller: session.controller,
361
+ locale: next.locale
362
+ })),
363
+ /** Acknowledge a host save: clears dirty and fires `onDirtyChange(false)`. */
364
+ markClean: () => session.markClean(),
365
+ update
366
+ };
367
+ }
368
+ //#endregion
369
+ export { TOOLBAR_GROUPS, ViewerController, buildingBlock, createBuildingBlockSession, createViewerBuildingBlocks, effectiveMode, eventKeys, mountFrameworkViewer, mountViewer, prepareDiagramBlock, propertyKeys, toolbarGroups, viewerHandle, viewerOptions, viewerStateSource, withEventEmitter };
@@ -0,0 +1,95 @@
1
+ import { ViewerController, type VsdxSource } from 'ooxml-ui/visio';
2
+ import * as ui from 'ooxml-ui/visio';
3
+ /** Host-owned dialogs, as in the PowerPoint building blocks; omitted ones report not-enabled. */
4
+ export interface BuildingBlockHostCallbacks {
5
+ onOpenFile?: (() => void) | undefined;
6
+ onOpenPageSetup?: (() => void) | undefined;
7
+ onOpenSettings?: (() => void) | undefined;
8
+ onOpenShareDialog?: (() => void) | undefined;
9
+ }
10
+ export interface BuildingBlockOptions extends BuildingBlockHostCallbacks {
11
+ content?: VsdxSource | undefined;
12
+ /** Host edit permission. Read-only until it is `true`, so nothing is editable before the host says so. */
13
+ canEdit?: boolean | undefined;
14
+ locale?: string | undefined;
15
+ onDocumentChange?: ((document: NonNullable<ViewerController['state']['document']>) => void) | undefined;
16
+ onDirtyChange?: ((dirty: boolean) => void) | undefined;
17
+ onDocumentError?: ((error: Error) => void) | undefined;
18
+ }
19
+ type Api = Pick<typeof ui, 'mountDiagramCanvas' | 'fitDiagramCanvas' | 'mountDiagramInspector' | 'mountDiagramShapes' | 'diagramToolbarProps' | 'diagramMode'>;
20
+ export declare function buildingBlock<K extends keyof Api>(key: K): Api[K];
21
+ /** The mode a host sees: 'edit' only when permission is granted and the file can be saved back. */
22
+ export declare function effectiveMode(state: ViewerController['state']): 'edit' | 'view';
23
+ /** Framework-neutral ownership only; each adapter supplies its own reactive state. */
24
+ export declare function createBuildingBlockSession(initial?: BuildingBlockOptions): {
25
+ controller: ViewerController;
26
+ mount(node: HTMLElement | null): void;
27
+ /** Flat control props; pass the framework's latest options so callbacks stay current. */
28
+ toolbar(latest?: BuildingBlockOptions): {
29
+ labels: Readonly<Record<"Arc" | "Copy" | "Cut" | "Delete" | "Duplicate" | "Ellipse" | "Find" | "Fit to Window" | "Freeform" | "Group" | "Line" | "Next Result" | "Open" | "Options" | "Page" | "Page Setup" | "Paste" | "Pencil" | "Pointer Tool" | "Previous Result" | "Rectangle" | "Redo" | "Shapes" | "Share" | "Undo" | "Ungroup" | "Zoom In" | "Zoom Out", string>>;
30
+ mode: ui.DiagramMode;
31
+ canEdit: boolean;
32
+ pageIndex: number;
33
+ pages: {
34
+ id: string;
35
+ name: string;
36
+ }[];
37
+ zoom: number;
38
+ selection: readonly import("ooxml-core/visio/ui").VisioShapeSelection[];
39
+ canUndo: boolean;
40
+ canRedo: boolean;
41
+ canDuplicate: boolean;
42
+ canGroup: boolean;
43
+ canUngroup: boolean;
44
+ canDelete: boolean;
45
+ canCut: boolean;
46
+ canCopy: boolean;
47
+ canPaste: boolean;
48
+ canDraw: boolean;
49
+ canZoomIn: boolean;
50
+ canZoomOut: boolean;
51
+ tool: ui.DiagramTool;
52
+ tools: ui.DiagramToolSummary[];
53
+ search: {
54
+ query: string;
55
+ resultCount: number;
56
+ activeIndex: number;
57
+ truncated: boolean;
58
+ };
59
+ onPageChange: (index: number) => void;
60
+ onZoomChange: (zoom: number) => void;
61
+ onZoomIn: () => void;
62
+ onZoomOut: () => void;
63
+ onFit: () => void;
64
+ onUndo: () => Promise<void>;
65
+ onRedo: () => Promise<void>;
66
+ onDuplicate: () => Promise<void>;
67
+ onGroup: () => Promise<void>;
68
+ onUngroup: () => Promise<void>;
69
+ onDelete: () => Promise<void>;
70
+ onCut: () => Promise<void>;
71
+ onCopy: () => Promise<void>;
72
+ onPaste: () => Promise<void>;
73
+ onToolChange: (tool: ui.DiagramTool) => void;
74
+ onTextChange: (pageId: string, shapeId: string, text: string) => Promise<void>;
75
+ onSearchChange: (query: string) => void;
76
+ onNextSearchResult: () => void;
77
+ onPreviousSearchResult: () => void;
78
+ canOpenFile: boolean;
79
+ canOpenPageSetup: boolean;
80
+ canOpenSettings: boolean;
81
+ canOpenShareDialog: boolean;
82
+ onOpenFile?: () => void;
83
+ onOpenPageSetup?: () => void;
84
+ onOpenSettings?: () => void;
85
+ onOpenShareDialog?: () => void;
86
+ };
87
+ /** Acknowledge a host save: clears dirty and emits the dirty change. */
88
+ markClean(): void;
89
+ update(next: BuildingBlockOptions): void;
90
+ destroy(): void;
91
+ };
92
+ export type BuildingBlockSession = ReturnType<typeof createBuildingBlockSession>;
93
+ export type { DiagramToolbarProps, DiagramToolbarLabels, DiagramToolbarHostCallbacks, DiagramMode, DiagramTool, ViewerController, } from 'ooxml-ui/visio';
94
+ /** Resolve the new optional-control theme API only when that control is mounted. */
95
+ export declare function prepareDiagramBlock(host: HTMLElement | null, kind: 'toolbar' | 'tabs'): void;
@@ -0,0 +1,41 @@
1
+ import type { DiagramToolbarProps } from 'ooxml-ui/visio';
2
+ /** The groups of the library toolbar; a host picks the ones it wants with `include`. */
3
+ export type ToolbarGroupId = 'file' | 'history' | 'zoom' | 'selection' | 'clipboard' | 'tools' | 'search';
4
+ export declare const TOOLBAR_GROUPS: readonly ToolbarGroupId[];
5
+ export interface ToolbarButton {
6
+ readonly kind: 'button';
7
+ readonly id: string;
8
+ readonly label: string;
9
+ readonly disabled: boolean;
10
+ /** Set for toggles such as the drawing tools. */
11
+ readonly pressed?: boolean;
12
+ run(): void;
13
+ }
14
+ export interface ToolbarText {
15
+ readonly kind: 'text';
16
+ readonly id: string;
17
+ readonly text: string;
18
+ }
19
+ export interface ToolbarSearch {
20
+ readonly kind: 'search';
21
+ readonly id: 'search';
22
+ readonly label: string;
23
+ readonly query: string;
24
+ /** Result summary for assistive technology, e.g. "2 of 5". */
25
+ readonly summary: string;
26
+ readonly previous: ToolbarButton;
27
+ readonly next: ToolbarButton;
28
+ change(query: string): void;
29
+ }
30
+ export type ToolbarControl = ToolbarButton | ToolbarText | ToolbarSearch;
31
+ export interface ToolbarGroup {
32
+ readonly id: ToolbarGroupId;
33
+ readonly controls: readonly ToolbarControl[];
34
+ }
35
+ /**
36
+ * What the library Toolbar shows, as plain data every framework maps onto its own elements.
37
+ * Every control reflects `toolbarProps`: edit actions are disabled while the diagram is read-only,
38
+ * viewing actions (zoom, fit, search) never are, and a host dialog button appears only when the
39
+ * host supplied its callback.
40
+ */
41
+ export declare function toolbarGroups(props: DiagramToolbarProps, include?: readonly ToolbarGroupId[]): ToolbarGroup[];
@@ -0,0 +1,87 @@
1
+ import { type Readable } from 'svelte/store';
2
+ import { type BuildingBlockOptions } from '../../bindings/src/building-blocks.js';
3
+ /** Call during component initialization. Reactive option changes use blocks.update(options). */
4
+ export declare function createViewerBuildingBlocks(options?: BuildingBlockOptions | Readable<BuildingBlockOptions>): {
5
+ controller: import("ooxml-ui/visio").ViewerController;
6
+ state: {
7
+ subscribe: (this: void, run: import("svelte/store").Subscriber<import("ooxml-ui/visio").ViewerState>, invalidate?: () => void) => import("svelte/store").Unsubscriber;
8
+ };
9
+ toolbarProps: Readable<{
10
+ labels: Readonly<Record<"Arc" | "Copy" | "Cut" | "Delete" | "Duplicate" | "Ellipse" | "Find" | "Fit to Window" | "Freeform" | "Group" | "Line" | "Next Result" | "Open" | "Options" | "Page" | "Page Setup" | "Paste" | "Pencil" | "Pointer Tool" | "Previous Result" | "Rectangle" | "Redo" | "Shapes" | "Share" | "Undo" | "Ungroup" | "Zoom In" | "Zoom Out", string>>;
11
+ mode: import("ooxml-ui/visio").DiagramMode;
12
+ canEdit: boolean;
13
+ pageIndex: number;
14
+ pages: {
15
+ id: string;
16
+ name: string;
17
+ }[];
18
+ zoom: number;
19
+ selection: readonly import("ooxml-ui/visio").VisioShapeSelection[];
20
+ canUndo: boolean;
21
+ canRedo: boolean;
22
+ canDuplicate: boolean;
23
+ canGroup: boolean;
24
+ canUngroup: boolean;
25
+ canDelete: boolean;
26
+ canCut: boolean;
27
+ canCopy: boolean;
28
+ canPaste: boolean;
29
+ canDraw: boolean;
30
+ canZoomIn: boolean;
31
+ canZoomOut: boolean;
32
+ tool: import("ooxml-ui/visio").DiagramTool;
33
+ tools: import("ooxml-ui/visio").DiagramToolSummary[];
34
+ search: {
35
+ query: string;
36
+ resultCount: number;
37
+ activeIndex: number;
38
+ truncated: boolean;
39
+ };
40
+ onPageChange: (index: number) => void;
41
+ onZoomChange: (zoom: number) => void;
42
+ onZoomIn: () => void;
43
+ onZoomOut: () => void;
44
+ onFit: () => void;
45
+ onUndo: () => Promise<void>;
46
+ onRedo: () => Promise<void>;
47
+ onDuplicate: () => Promise<void>;
48
+ onGroup: () => Promise<void>;
49
+ onUngroup: () => Promise<void>;
50
+ onDelete: () => Promise<void>;
51
+ onCut: () => Promise<void>;
52
+ onCopy: () => Promise<void>;
53
+ onPaste: () => Promise<void>;
54
+ onToolChange: (tool: import("ooxml-ui/visio").DiagramTool) => void;
55
+ onTextChange: (pageId: string, shapeId: string, text: string) => Promise<void>;
56
+ onSearchChange: (query: string) => void;
57
+ onNextSearchResult: () => void;
58
+ onPreviousSearchResult: () => void;
59
+ canOpenFile: boolean;
60
+ canOpenPageSetup: boolean;
61
+ canOpenSettings: boolean;
62
+ canOpenShareDialog: boolean;
63
+ onOpenFile?: () => void;
64
+ onOpenPageSetup?: () => void;
65
+ onOpenSettings?: () => void;
66
+ onOpenShareDialog?: () => void;
67
+ }>;
68
+ loading: Readable<boolean>;
69
+ error: Readable<Error | null>;
70
+ /** Effective permission: host `canEdit` and a drawing that can be saved back. */
71
+ mode: Readable<"edit" | "view">;
72
+ canvasProps: {
73
+ mount: (node: HTMLElement | null) => void;
74
+ };
75
+ inspectorProps: {
76
+ controller: import("ooxml-ui/visio").ViewerController;
77
+ };
78
+ shapesProps: Readable<{
79
+ controller: import("ooxml-ui/visio").ViewerController;
80
+ locale: string | undefined;
81
+ }>;
82
+ /** Acknowledge a host save: clears dirty and fires `onDirtyChange(false)`. */
83
+ markClean: () => void;
84
+ update: (next: BuildingBlockOptions) => void;
85
+ };
86
+ export type { BuildingBlockOptions, BuildingBlockHostCallbacks, DiagramToolbarProps, } from '../../bindings/src/building-blocks.js';
87
+ export type { ToolbarGroupId } from '../../bindings/src/toolbar-controls.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "visio-svelte-viewer",
3
- "version": "0.7.4",
3
+ "version": "1.0.0",
4
4
  "description": "Local-first Visio viewer for svelte",
5
5
  "homepage": "https://christophervr.github.io/ooxml/visio/",
6
6
  "bugs": {
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "emf-converter": "4.21.0",
41
- "ooxml-core": "^1.19.2",
42
- "ooxml-ui": "^1.18.3",
43
- "visio-core": "^0.4.1"
41
+ "ooxml-core": "^1.22.0",
42
+ "ooxml-ui": "^1.21.0",
43
+ "visio-core": "^1.0.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "svelte": "^5.0.0"