@inkbar/vue 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +67 -12
  2. package/dist/components/InkBack.js +7 -0
  3. package/dist/components/InkBack.js.map +1 -0
  4. package/dist/components/InkBack.vue.d.ts +19 -0
  5. package/dist/components/InkBack.vue.d.ts.map +1 -0
  6. package/dist/components/InkBack.vue_vue_type_script_setup_true_lang.js +34 -0
  7. package/dist/components/InkBack.vue_vue_type_script_setup_true_lang.js.map +1 -0
  8. package/dist/components/InkForward.js +7 -0
  9. package/dist/components/InkForward.js.map +1 -0
  10. package/dist/components/InkForward.vue.d.ts +19 -0
  11. package/dist/components/InkForward.vue.d.ts.map +1 -0
  12. package/dist/components/InkForward.vue_vue_type_script_setup_true_lang.js +34 -0
  13. package/dist/components/InkForward.vue_vue_type_script_setup_true_lang.js.map +1 -0
  14. package/dist/components/InkOutline.js +7 -0
  15. package/dist/components/InkOutline.js.map +1 -0
  16. package/dist/components/InkOutline.vue.d.ts +23 -0
  17. package/dist/components/InkOutline.vue.d.ts.map +1 -0
  18. package/dist/components/InkOutline.vue_vue_type_script_setup_true_lang.js +67 -0
  19. package/dist/components/InkOutline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  20. package/dist/components/InkOutlineNodes.js +7 -0
  21. package/dist/components/InkOutlineNodes.js.map +1 -0
  22. package/dist/components/InkOutlineNodes.vue.d.ts +27 -0
  23. package/dist/components/InkOutlineNodes.vue.d.ts.map +1 -0
  24. package/dist/components/InkOutlineNodes.vue_vue_type_script_setup_true_lang.js +70 -0
  25. package/dist/components/InkOutlineNodes.vue_vue_type_script_setup_true_lang.js.map +1 -0
  26. package/dist/components/InkRoot.js.map +1 -1
  27. package/dist/components/InkRoot.vue.d.ts +16 -6
  28. package/dist/components/InkRoot.vue.d.ts.map +1 -1
  29. package/dist/components/InkRoot.vue_vue_type_script_setup_true_lang.js +163 -155
  30. package/dist/components/InkRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
  31. package/dist/components/InkSidebar.js.map +1 -1
  32. package/dist/components/InkSidebar.vue.d.ts +30 -0
  33. package/dist/components/InkSidebar.vue.d.ts.map +1 -1
  34. package/dist/components/InkSidebar.vue_vue_type_script_setup_true_lang.js +88 -50
  35. package/dist/components/InkSidebar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  36. package/dist/components/InkViewport.js.map +1 -1
  37. package/dist/components/InkViewport.vue.d.ts.map +1 -1
  38. package/dist/components/InkViewport.vue_vue_type_script_setup_true_lang.js +3 -3
  39. package/dist/components/InkViewport.vue_vue_type_script_setup_true_lang.js.map +1 -1
  40. package/dist/context.d.ts +15 -5
  41. package/dist/context.d.ts.map +1 -1
  42. package/dist/context.js.map +1 -1
  43. package/dist/index.d.ts +3 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +15 -12
  46. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"InkSidebar.js","names":["_sfc_main","InkSidebar_vue_vue_type_script_setup_true_lang_default","InkSidebar_default"],"sources":["../../src/components/InkSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A sidebar of `InkPanel`s: `createSidebar`'s markup on core's sidebar controller, which holds\n * the open state, the ARIA, the keys and the clicks. Rail and tabs render their triggers here;\n * in a stack each panel renders its own header. Panels register as they are set up and take\n * their place in DOM order once mounted, so a panel added later with `v-if` slots in where it\n * stands. A closed panel stays mounted and hidden, and the panels' content stays in place\n * across layouts, so its state survives switching.\n *\n * `v-model:open` is the open panel's id (or null) for rail and tabs, and the expanded ids for\n * stack. Left unbound, the first panel starts open. It needs no document, so it works outside\n * `InkRoot`. The server renders the root, the panels (the open one shown) and the stack\n * headers; rail and tab buttons follow once mounted, as the panels register after the root\n * has rendered.\n */\nimport {\n createSidebarController,\n type SidebarLayout,\n type SidebarOpen,\n type SidebarSide,\n} from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useId,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { useReadable } from '../use-readable'\n\nimport { provideSidebarContext, type SidebarPanelEntry } from './sidebar-context'\nimport SidebarTrigger from './SidebarTrigger.vue'\n\nconst props = withDefaults(\n defineProps<{\n side?: SidebarSide | undefined\n layout?: SidebarLayout | undefined\n /** Default true. False hides the whole sidebar and keeps its open panel, content and scroll for when it shows again. */\n visible?: boolean | undefined\n }>(),\n { side: 'left', layout: 'rail', visible: true }\n)\n/** Undefined while unbound and untouched: the controller then opens the first panel. */\nconst open = defineModel<SidebarOpen | undefined>('open')\n\nconst entries = shallowRef<readonly SidebarPanelEntry[]>([])\nconst panels = computed(() => entries.value.map((e) => e.info()))\n\n// Pending until mounted: the panels register one by one while they are set up, and an open id\n// not registered yet is not a removed panel.\nconst controller = createSidebarController({\n layout: props.layout,\n side: props.side,\n visible: props.visible,\n panels: [],\n ...(open.value !== undefined && { open: open.value }),\n pending: true,\n onOpenChange: (value) => {\n open.value = value\n },\n // The same ids on the server and the client.\n idPrefix: useId(),\n})\n// Sync, so a panel set up next reads the attributes its registration gives (on the server too).\nwatch(\n () => [props.layout, props.side, props.visible, panels.value, open.value] as const,\n ([layout, side, visible, list, value]) =>\n controller.update({\n layout,\n side,\n visible,\n panels: list,\n ...(value !== undefined && { open: value }),\n }),\n { flush: 'sync' }\n)\nconst state = useReadable(controller.state, controller.state.get())\n\nfunction register(entry: SidebarPanelEntry): () => void {\n entries.value = [...entries.value, entry]\n return () => {\n entries.value = entries.value.filter((e) => e !== entry)\n }\n}\n\nfunction arrange(): void {\n const list = entries.value\n const sorted = [...list].sort((a, b) => {\n const x = a.element()\n const y = b.element()\n if (!x || !y || x === y) return 0\n return x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n })\n if (sorted.some((e, i) => e !== list[i])) entries.value = sorted\n}\n\nprovideSidebarContext({ controller, state, register, arrange })\n\nconst view = computed(() => {\n const s = state.value\n return {\n root: controller.rootAttrs(),\n tablist: controller.tablistAttrs(),\n triggers: s.panels.map((panel) => ({\n panel,\n attrs: controller.triggerAttrs(panel.id),\n })),\n }\n})\n\nconst root = useTemplateRef<HTMLElement>('root')\n// The controller handles the triggers' clicks and keys, stack headers included.\nonMounted(() => {\n controller.bindTriggers(root.value!)\n controller.update({ pending: false })\n})\nonBeforeUnmount(() => controller.destroy())\n</script>\n\n<template>\n <div ref=\"root\" class=\"ink-sidebar\" v-bind=\"view.root\">\n <div\n v-if=\"state.layout !== 'stack'\"\n :class=\"state.layout === 'rail' ? 'ink-sidebar-rail' : 'ink-sidebar-tabs'\"\n v-bind=\"view.tablist\"\n >\n <button v-for=\"t in view.triggers\" :key=\"t.panel.id\" v-bind=\"t.attrs\">\n <SidebarTrigger :panel=\"t.panel\" :rail=\"state.layout === 'rail'\" />\n </button>\n </div>\n <!-- One element in every layout, so the panels are never remounted. -->\n <div :class=\"{ 'ink-sidebar-panels': state.layout !== 'stack' }\"><slot /></div>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"InkSidebar.js","names":["_sfc_main","InkSidebar_vue_vue_type_script_setup_true_lang_default","InkSidebar_default"],"sources":["../../src/components/InkSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A sidebar of `InkPanel`s: `createSidebar`'s markup on core's sidebar controller, which holds\n * the open state, the ARIA, the keys and the clicks. Rail and tabs render their triggers here;\n * in a stack each panel renders its own header. Panels register as they are set up and take\n * their place in DOM order once mounted, so a panel added later with `v-if` slots in where it\n * stands. A closed panel stays mounted and hidden, and the panels' content stays in place\n * across layouts, so its state survives switching.\n *\n * `v-model:open` is the open panel's id (or null) for rail and tabs, and the expanded ids for\n * stack. Left unbound, the first panel starts open. It needs no document, so it works outside\n * `InkRoot`. The server renders the root, the panels (the open one shown), the stack headers\n * and the resize handle; rail and tab buttons follow once mounted, as the panels register\n * after the root has rendered.\n *\n * The handle on the inner edge resizes the sidebar. Its width is saved in `storage`, under\n * `sidebar.<name>.width`; inside `InkRoot` the root's storage is the default. `v-model:width`\n * makes the width the host's: drags and keys then only update the model, and storage is not\n * read or written.\n */\nimport {\n createSidebarController,\n type SidebarLayout,\n type SidebarOpen,\n type SidebarSide,\n} from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useId,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { injectInkContext } from '../context'\nimport { useReadable } from '../use-readable'\n\nimport { provideSidebarContext, type SidebarPanelEntry } from './sidebar-context'\nimport SidebarTrigger from './SidebarTrigger.vue'\n\nimport type { InkStorage } from '@inkbar/core'\n\nconst props = withDefaults(\n defineProps<{\n side?: SidebarSide | undefined\n layout?: SidebarLayout | undefined\n /** Default true. False hides the whole sidebar and keeps its open panel, content and scroll for when it shows again. */\n visible?: boolean | undefined\n /**\n * Where the width is saved. Default: InkRoot's `storage` inside InkRoot, else none, and the\n * width lasts until unmount. Read once, when the sidebar is created.\n */\n storage?: InkStorage | undefined\n /**\n * The middle of the saved key, `sidebar.<name>.width`. Default: the side. Give two sidebars\n * on one side different names. Read once, when the sidebar is created.\n */\n name?: string | undefined\n /** In CSS px. Default 320; Enter or a double-click on the handle goes back to it. */\n defaultWidth?: number | undefined\n /** Default 240. */\n minWidth?: number | undefined\n /** Default 480. The shown width also stays within half the sidebar's container. */\n maxWidth?: number | undefined\n /**\n * Bind with `v-model:width` to control the width, in CSS px. Left unbound, the sidebar\n * keeps its own, saved in `storage`.\n */\n width?: number | undefined\n }>(),\n {\n side: 'left',\n layout: 'rail',\n visible: true,\n storage: undefined,\n name: undefined,\n defaultWidth: undefined,\n minWidth: undefined,\n maxWidth: undefined,\n width: undefined,\n }\n)\n// A prop and an event rather than `defineModel`: unbound, a model keeps the last width locally\n// and would hand it back as a controlled one, and the width would no longer be saved.\nconst emit = defineEmits<{ 'update:width': [width: number] }>()\n/** Undefined while unbound and untouched: the controller then opens the first panel. */\nconst open = defineModel<SidebarOpen | undefined>('open')\n\nconst entries = shallowRef<readonly SidebarPanelEntry[]>([])\nconst panels = computed(() => entries.value.map((e) => e.info()))\n\n// Read once: the controller loads the saved width as it is created.\nconst storage = props.storage ?? injectInkContext(null)?.storage\n// Pending until mounted: the panels register one by one while they are set up, and an open id\n// not registered yet is not a removed panel.\nconst controller = createSidebarController({\n layout: props.layout,\n side: props.side,\n visible: props.visible,\n panels: [],\n ...(open.value !== undefined && { open: open.value }),\n pending: true,\n onOpenChange: (value) => {\n open.value = value\n },\n ...(storage && { storage }),\n ...(props.name !== undefined && { name: props.name }),\n ...widthOptions(),\n onWidthChange: (value) => emit('update:width', value),\n // The same ids on the server and the client.\n idPrefix: useId(),\n})\n// Sync, so a panel set up next reads the attributes its registration gives (on the server too).\nwatch(\n () =>\n [\n props.layout,\n props.side,\n props.visible,\n panels.value,\n open.value,\n widthOptions(),\n ] as const,\n ([layout, side, visible, list, value, widths]) =>\n controller.update({\n layout,\n side,\n visible,\n panels: list,\n ...(value !== undefined && { open: value }),\n ...widths,\n }),\n { flush: 'sync' }\n)\nconst state = useReadable(controller.state, controller.state.get())\n\n/** The width props the host gave; an undefined one keeps the controller's. */\nfunction widthOptions(): Partial<\n Record<'defaultWidth' | 'minWidth' | 'maxWidth' | 'width', number>\n> {\n const { defaultWidth, minWidth, maxWidth, width } = props\n return {\n ...(defaultWidth !== undefined && { defaultWidth }),\n ...(minWidth !== undefined && { minWidth }),\n ...(maxWidth !== undefined && { maxWidth }),\n ...(width !== undefined && { width }),\n }\n}\n\nfunction register(entry: SidebarPanelEntry): () => void {\n entries.value = [...entries.value, entry]\n return () => {\n entries.value = entries.value.filter((e) => e !== entry)\n }\n}\n\nfunction arrange(): void {\n const list = entries.value\n const sorted = [...list].sort((a, b) => {\n const x = a.element()\n const y = b.element()\n if (!x || !y || x === y) return 0\n return x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n })\n if (sorted.some((e, i) => e !== list[i])) entries.value = sorted\n}\n\nprovideSidebarContext({ controller, state, register, arrange })\n\n// The panels register after this component has rendered. So the server's and the first\n// render's handle is shown, as the root is (`data-state` is open while pending), and the real\n// `hidden` follows once mounted.\nconst mounted = shallowRef(false)\n\nconst view = computed(() => {\n const s = state.value\n const handle = controller.handleAttrs()\n if (!mounted.value) delete handle.hidden\n return {\n root: controller.rootAttrs(),\n style: controller.rootStyle(),\n panels: controller.panelsAttrs(),\n handle,\n tablist: controller.tablistAttrs(),\n triggers: s.panels.map((panel) => ({\n panel,\n attrs: controller.triggerAttrs(panel.id),\n })),\n }\n})\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst handle = useTemplateRef<HTMLElement>('handle')\n// The controller handles the triggers' clicks and keys, stack headers included, and the\n// handle's drags and keys. `destroy` unbinds them.\nonMounted(() => {\n controller.bindTriggers(root.value!)\n controller.bindHandle(handle.value!)\n controller.update({ pending: false })\n mounted.value = true\n // Hydration keeps the server's markup: a width the client's storage gave applies here.\n for (const [name, value] of Object.entries(controller.rootStyle()))\n root.value!.style.setProperty(name, value)\n for (const [name, value] of Object.entries(controller.handleAttrs()))\n if (name.startsWith('aria-value')) handle.value!.setAttribute(name, value)\n})\nonBeforeUnmount(() => controller.destroy())\n</script>\n\n<template>\n <!-- The client's storage may hold another width than the server showed; see onMounted. -->\n <div\n ref=\"root\"\n class=\"ink-sidebar\"\n v-bind=\"view.root\"\n :style=\"view.style\"\n data-allow-mismatch=\"style\"\n >\n <div\n v-if=\"state.layout !== 'stack'\"\n :class=\"state.layout === 'rail' ? 'ink-sidebar-rail' : 'ink-sidebar-tabs'\"\n v-bind=\"view.tablist\"\n >\n <button v-for=\"t in view.triggers\" :key=\"t.panel.id\" v-bind=\"t.attrs\">\n <SidebarTrigger :panel=\"t.panel\" :rail=\"state.layout === 'rail'\" />\n </button>\n </div>\n <!-- One element in every layout, so the panels are never remounted. -->\n <!-- The stack has no panels element: its id and class apply in rail and tabs only. -->\n <div\n :class=\"{ 'ink-sidebar-panels': state.layout !== 'stack' }\"\n v-bind=\"state.layout !== 'stack' ? view.panels : {}\"\n >\n <slot />\n </div>\n <!-- The last child, on the inner edge. -->\n <div\n ref=\"handle\"\n class=\"ink-sidebar-handle\"\n v-bind=\"view.handle\"\n data-allow-mismatch=\"attribute\"\n />\n </div>\n</template>\n"],"mappings":""}
@@ -1,9 +1,31 @@
1
1
  import { SidebarLayout, SidebarOpen, SidebarSide } from '@inkbar/core/dom';
2
+ import { InkStorage } from '@inkbar/core';
2
3
  type __VLS_Props = {
3
4
  side?: SidebarSide | undefined;
4
5
  layout?: SidebarLayout | undefined;
5
6
  /** Default true. False hides the whole sidebar and keeps its open panel, content and scroll for when it shows again. */
6
7
  visible?: boolean | undefined;
8
+ /**
9
+ * Where the width is saved. Default: InkRoot's `storage` inside InkRoot, else none, and the
10
+ * width lasts until unmount. Read once, when the sidebar is created.
11
+ */
12
+ storage?: InkStorage | undefined;
13
+ /**
14
+ * The middle of the saved key, `sidebar.<name>.width`. Default: the side. Give two sidebars
15
+ * on one side different names. Read once, when the sidebar is created.
16
+ */
17
+ name?: string | undefined;
18
+ /** In CSS px. Default 320; Enter or a double-click on the handle goes back to it. */
19
+ defaultWidth?: number | undefined;
20
+ /** Default 240. */
21
+ minWidth?: number | undefined;
22
+ /** Default 480. The shown width also stays within half the sidebar's container. */
23
+ maxWidth?: number | undefined;
24
+ /**
25
+ * Bind with `v-model:width` to control the width, in CSS px. Left unbound, the sidebar
26
+ * keeps its own, saved in `storage`.
27
+ */
28
+ width?: number | undefined;
7
29
  };
8
30
  type __VLS_ModelProps = {
9
31
  /** Undefined while unbound and untouched: the controller then opens the first panel. */
@@ -16,12 +38,20 @@ type __VLS_Slots = {} & {
16
38
  };
17
39
  declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
18
40
  "update:open": (value: SidebarOpen | undefined) => any;
41
+ "update:width": (width: number) => any;
19
42
  }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
20
43
  "onUpdate:open"?: (value: SidebarOpen | undefined) => any;
44
+ "onUpdate:width"?: (width: number) => any;
21
45
  }>, {
46
+ width: number | undefined;
47
+ storage: InkStorage | undefined;
48
+ name: string | undefined;
22
49
  side: SidebarSide | undefined;
23
50
  layout: SidebarLayout | undefined;
24
51
  visible: boolean | undefined;
52
+ defaultWidth: number | undefined;
53
+ minWidth: number | undefined;
54
+ maxWidth: number | undefined;
25
55
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
26
56
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
27
57
  declare const _default: typeof __VLS_export;
@@ -1 +1 @@
1
- {"version":3,"file":"InkSidebar.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkSidebar.vue"],"names":[],"mappings":"AAyJA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAA;AAiBzB,KAAK,WAAW,GAAG;IACf,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IAC9B,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IAClC,wHAAwH;IACxH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC9B,CAAC;AAgFJ,KAAK,gBAAgB,GAAG;IACxB,wFAAwF;IACxF,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAChC,CAAC;AAOF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAyDxD,QAAA,IAAI,OAAO,IAAW,CAAC;AACvB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAG/C,QAAA,MAAM,UAAU;;;;;;;;6EAId,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"InkSidebar.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkSidebar.vue"],"names":[],"mappings":"AA2QA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAA;AAiBzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,KAAK,WAAW,GAAG;IACf,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IAC9B,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IAClC,wHAAwH;IACxH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,mBAAmB;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B,CAAC;AA6IJ,KAAK,gBAAgB,GAAG;IACxB,wFAAwF;IACxF,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAChC,CAAC;AAiBF,KAAK,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAiExD,QAAA,IAAI,OAAO,IAAW,CAAC;AACvB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAG/C,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;6EAId,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -1,92 +1,130 @@
1
- import { useReadable as e } from "../use-readable.js";
2
- import { provideSidebarContext as t } from "./sidebar-context.js";
3
- import n from "./SidebarTrigger.js";
4
- import { Fragment as r, computed as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, createVNode as c, defineComponent as l, mergeModels as u, mergeProps as d, normalizeClass as f, onBeforeUnmount as p, onMounted as m, openBlock as h, renderList as g, renderSlot as _, shallowRef as v, unref as y, useId as b, useModel as x, useTemplateRef as S, watch as C } from "vue";
1
+ import { injectInkContext as e } from "../context.js";
2
+ import { useReadable as t } from "../use-readable.js";
3
+ import { provideSidebarContext as n } from "./sidebar-context.js";
4
+ import r from "./SidebarTrigger.js";
5
+ import { Fragment as i, computed as a, createCommentVNode as o, createElementBlock as s, createElementVNode as c, createVNode as l, defineComponent as u, mergeModels as d, mergeProps as f, onBeforeUnmount as p, onMounted as m, openBlock as h, renderList as g, renderSlot as _, shallowRef as v, unref as y, useId as b, useModel as x, useTemplateRef as S, watch as C } from "vue";
5
6
  import { createSidebarController as w } from "@inkbar/core/dom";
6
7
  //#region src/components/InkSidebar.vue?vue&type=script&setup=true&lang.ts
7
- var T = /*@__PURE__*/ l({
8
+ var T = /*@__PURE__*/ u({
8
9
  __name: "InkSidebar",
9
- props: /*@__PURE__*/ u({
10
+ props: /*@__PURE__*/ d({
10
11
  side: { default: "left" },
11
12
  layout: { default: "rail" },
12
13
  visible: {
13
14
  type: Boolean,
14
15
  default: !0
15
- }
16
+ },
17
+ storage: { default: void 0 },
18
+ name: { default: void 0 },
19
+ defaultWidth: { default: void 0 },
20
+ minWidth: { default: void 0 },
21
+ maxWidth: { default: void 0 },
22
+ width: { default: void 0 }
16
23
  }, {
17
24
  open: {},
18
25
  openModifiers: {}
19
26
  }),
20
- emits: ["update:open"],
21
- setup(l) {
22
- let u = l, T = x(l, "open"), E = v([]), D = i(() => E.value.map((e) => e.info())), O = w({
23
- layout: u.layout,
24
- side: u.side,
25
- visible: u.visible,
27
+ emits: /*@__PURE__*/ d(["update:width"], ["update:open"]),
28
+ setup(u, { emit: d }) {
29
+ let T = u, E = d, D = x(u, "open"), O = v([]), k = a(() => O.value.map((e) => e.info())), A = T.storage ?? e(null)?.storage, j = w({
30
+ layout: T.layout,
31
+ side: T.side,
32
+ visible: T.visible,
26
33
  panels: [],
27
- ...T.value !== void 0 && { open: T.value },
34
+ ...D.value !== void 0 && { open: D.value },
28
35
  pending: !0,
29
36
  onOpenChange: (e) => {
30
- T.value = e;
37
+ D.value = e;
31
38
  },
39
+ ...A && { storage: A },
40
+ ...T.name !== void 0 && { name: T.name },
41
+ ...N(),
42
+ onWidthChange: (e) => E("update:width", e),
32
43
  idPrefix: b()
33
44
  });
34
45
  C(() => [
35
- u.layout,
36
- u.side,
37
- u.visible,
46
+ T.layout,
47
+ T.side,
48
+ T.visible,
49
+ k.value,
38
50
  D.value,
39
- T.value
40
- ], ([e, t, n, r, i]) => O.update({
51
+ N()
52
+ ], ([e, t, n, r, i, a]) => j.update({
41
53
  layout: e,
42
54
  side: t,
43
55
  visible: n,
44
56
  panels: r,
45
- ...i !== void 0 && { open: i }
57
+ ...i !== void 0 && { open: i },
58
+ ...a
46
59
  }), { flush: "sync" });
47
- let k = e(O.state, O.state.get());
48
- function A(e) {
49
- return E.value = [...E.value, e], () => {
50
- E.value = E.value.filter((t) => t !== e);
60
+ let M = t(j.state, j.state.get());
61
+ function N() {
62
+ let { defaultWidth: e, minWidth: t, maxWidth: n, width: r } = T;
63
+ return {
64
+ ...e !== void 0 && { defaultWidth: e },
65
+ ...t !== void 0 && { minWidth: t },
66
+ ...n !== void 0 && { maxWidth: n },
67
+ ...r !== void 0 && { width: r }
51
68
  };
52
69
  }
53
- function j() {
54
- let e = E.value, t = [...e].sort((e, t) => {
70
+ function P(e) {
71
+ return O.value = [...O.value, e], () => {
72
+ O.value = O.value.filter((t) => t !== e);
73
+ };
74
+ }
75
+ function F() {
76
+ let e = O.value, t = [...e].sort((e, t) => {
55
77
  let n = e.element(), r = t.element();
56
78
  return !n || !r || n === r ? 0 : n.compareDocumentPosition(r) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
57
79
  });
58
- t.some((t, n) => t !== e[n]) && (E.value = t);
80
+ t.some((t, n) => t !== e[n]) && (O.value = t);
59
81
  }
60
- t({
61
- controller: O,
62
- state: k,
63
- register: A,
64
- arrange: j
82
+ n({
83
+ controller: j,
84
+ state: M,
85
+ register: P,
86
+ arrange: F
65
87
  });
66
- let M = i(() => {
67
- let e = k.value;
68
- return {
69
- root: O.rootAttrs(),
70
- tablist: O.tablistAttrs(),
88
+ let I = v(!1), L = a(() => {
89
+ let e = M.value, t = j.handleAttrs();
90
+ return I.value || delete t.hidden, {
91
+ root: j.rootAttrs(),
92
+ style: j.rootStyle(),
93
+ panels: j.panelsAttrs(),
94
+ handle: t,
95
+ tablist: j.tablistAttrs(),
71
96
  triggers: e.panels.map((e) => ({
72
97
  panel: e,
73
- attrs: O.triggerAttrs(e.id)
98
+ attrs: j.triggerAttrs(e.id)
74
99
  }))
75
100
  };
76
- }), N = S("root");
101
+ }), R = S("root"), z = S("handle");
77
102
  return m(() => {
78
- O.bindTriggers(N.value), O.update({ pending: !1 });
79
- }), p(() => O.destroy()), (e, t) => (h(), o("div", d({
103
+ j.bindTriggers(R.value), j.bindHandle(z.value), j.update({ pending: !1 }), I.value = !0;
104
+ for (let [e, t] of Object.entries(j.rootStyle())) R.value.style.setProperty(e, t);
105
+ for (let [e, t] of Object.entries(j.handleAttrs())) e.startsWith("aria-value") && z.value.setAttribute(e, t);
106
+ }), p(() => j.destroy()), (e, t) => (h(), s("div", f({
80
107
  ref_key: "root",
81
- ref: N,
108
+ ref: R,
82
109
  class: "ink-sidebar"
83
- }, M.value.root), [y(k).layout === "stack" ? a("", !0) : (h(), o("div", d({
84
- key: 0,
85
- class: y(k).layout === "rail" ? "ink-sidebar-rail" : "ink-sidebar-tabs"
86
- }, M.value.tablist), [(h(!0), o(r, null, g(M.value.triggers, (e) => (h(), o("button", d({ key: e.panel.id }, { ref_for: !0 }, e.attrs), [c(n, {
87
- panel: e.panel,
88
- rail: y(k).layout === "rail"
89
- }, null, 8, ["panel", "rail"])], 16))), 128))], 16)), s("div", { class: f({ "ink-sidebar-panels": y(k).layout !== "stack" }) }, [_(e.$slots, "default")], 2)], 16));
110
+ }, L.value.root, {
111
+ style: L.value.style,
112
+ "data-allow-mismatch": "style"
113
+ }), [
114
+ y(M).layout === "stack" ? o("", !0) : (h(), s("div", f({
115
+ key: 0,
116
+ class: y(M).layout === "rail" ? "ink-sidebar-rail" : "ink-sidebar-tabs"
117
+ }, L.value.tablist), [(h(!0), s(i, null, g(L.value.triggers, (e) => (h(), s("button", f({ key: e.panel.id }, { ref_for: !0 }, e.attrs), [l(r, {
118
+ panel: e.panel,
119
+ rail: y(M).layout === "rail"
120
+ }, null, 8, ["panel", "rail"])], 16))), 128))], 16)),
121
+ c("div", f({ class: { "ink-sidebar-panels": y(M).layout !== "stack" } }, y(M).layout === "stack" ? {} : L.value.panels), [_(e.$slots, "default")], 16),
122
+ c("div", f({
123
+ ref_key: "handle",
124
+ ref: z,
125
+ class: "ink-sidebar-handle"
126
+ }, L.value.handle, { "data-allow-mismatch": "attribute" }), null, 16)
127
+ ], 16));
90
128
  }
91
129
  });
92
130
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"InkSidebar.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createElementBlock","_mergeProps","_unref","_Fragment","_renderList","_createVNode","SidebarTrigger","_createCommentVNode","_createElementVNode","_normalizeClass","_renderSlot","SidebarTrigger_default","InkSidebar_vue_vue_type_script_setup_true_lang_default","\"open\"","\"openModifiers\"","props","__props","open","useModel","entries","shallowRef","panels","computed","controller","createSidebarController","value","useId","layout","side","visible","list","state","useReadable","register","entry","e","arrange","sorted","x","a","y","b","i","view","s","panel","root","useTemplateRef","onMounted","onBeforeUnmount","openBlock","createElementBlock","mergeProps","unref","createCommentVNode","Fragment","renderList","t","createVNode","createElementVNode","normalizeClass","renderSlot","_ctx"],"sources":["../../src/components/InkSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A sidebar of `InkPanel`s: `createSidebar`'s markup on core's sidebar controller, which holds\n * the open state, the ARIA, the keys and the clicks. Rail and tabs render their triggers here;\n * in a stack each panel renders its own header. Panels register as they are set up and take\n * their place in DOM order once mounted, so a panel added later with `v-if` slots in where it\n * stands. A closed panel stays mounted and hidden, and the panels' content stays in place\n * across layouts, so its state survives switching.\n *\n * `v-model:open` is the open panel's id (or null) for rail and tabs, and the expanded ids for\n * stack. Left unbound, the first panel starts open. It needs no document, so it works outside\n * `InkRoot`. The server renders the root, the panels (the open one shown) and the stack\n * headers; rail and tab buttons follow once mounted, as the panels register after the root\n * has rendered.\n */\nimport {\n createSidebarController,\n type SidebarLayout,\n type SidebarOpen,\n type SidebarSide,\n} from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useId,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { useReadable } from '../use-readable'\n\nimport { provideSidebarContext, type SidebarPanelEntry } from './sidebar-context'\nimport SidebarTrigger from './SidebarTrigger.vue'\n\nconst props = withDefaults(\n defineProps<{\n side?: SidebarSide | undefined\n layout?: SidebarLayout | undefined\n /** Default true. False hides the whole sidebar and keeps its open panel, content and scroll for when it shows again. */\n visible?: boolean | undefined\n }>(),\n { side: 'left', layout: 'rail', visible: true }\n)\n/** Undefined while unbound and untouched: the controller then opens the first panel. */\nconst open = defineModel<SidebarOpen | undefined>('open')\n\nconst entries = shallowRef<readonly SidebarPanelEntry[]>([])\nconst panels = computed(() => entries.value.map((e) => e.info()))\n\n// Pending until mounted: the panels register one by one while they are set up, and an open id\n// not registered yet is not a removed panel.\nconst controller = createSidebarController({\n layout: props.layout,\n side: props.side,\n visible: props.visible,\n panels: [],\n ...(open.value !== undefined && { open: open.value }),\n pending: true,\n onOpenChange: (value) => {\n open.value = value\n },\n // The same ids on the server and the client.\n idPrefix: useId(),\n})\n// Sync, so a panel set up next reads the attributes its registration gives (on the server too).\nwatch(\n () => [props.layout, props.side, props.visible, panels.value, open.value] as const,\n ([layout, side, visible, list, value]) =>\n controller.update({\n layout,\n side,\n visible,\n panels: list,\n ...(value !== undefined && { open: value }),\n }),\n { flush: 'sync' }\n)\nconst state = useReadable(controller.state, controller.state.get())\n\nfunction register(entry: SidebarPanelEntry): () => void {\n entries.value = [...entries.value, entry]\n return () => {\n entries.value = entries.value.filter((e) => e !== entry)\n }\n}\n\nfunction arrange(): void {\n const list = entries.value\n const sorted = [...list].sort((a, b) => {\n const x = a.element()\n const y = b.element()\n if (!x || !y || x === y) return 0\n return x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n })\n if (sorted.some((e, i) => e !== list[i])) entries.value = sorted\n}\n\nprovideSidebarContext({ controller, state, register, arrange })\n\nconst view = computed(() => {\n const s = state.value\n return {\n root: controller.rootAttrs(),\n tablist: controller.tablistAttrs(),\n triggers: s.panels.map((panel) => ({\n panel,\n attrs: controller.triggerAttrs(panel.id),\n })),\n }\n})\n\nconst root = useTemplateRef<HTMLElement>('root')\n// The controller handles the triggers' clicks and keys, stack headers included.\nonMounted(() => {\n controller.bindTriggers(root.value!)\n controller.update({ pending: false })\n})\nonBeforeUnmount(() => controller.destroy())\n</script>\n\n<template>\n <div ref=\"root\" class=\"ink-sidebar\" v-bind=\"view.root\">\n <div\n v-if=\"state.layout !== 'stack'\"\n :class=\"state.layout === 'rail' ? 'ink-sidebar-rail' : 'ink-sidebar-tabs'\"\n v-bind=\"view.tablist\"\n >\n <button v-for=\"t in view.triggers\" :key=\"t.panel.id\" v-bind=\"t.attrs\">\n <SidebarTrigger :panel=\"t.panel\" :rail=\"state.layout === 'rail'\" />\n </button>\n </div>\n <!-- One element in every layout, so the panels are never remounted. -->\n <div :class=\"{ 'ink-sidebar-panels': state.layout !== 'stack' }\"><slot /></div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"InkSidebar.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createElementBlock","_mergeProps","_unref","_Fragment","_renderList","_createVNode","SidebarTrigger","_createCommentVNode","_createElementVNode","_renderSlot","SidebarTrigger_default","InkSidebar_vue_vue_type_script_setup_true_lang_default","\"open\"","\"openModifiers\"","__emit","props","__props","emit","open","useModel","entries","shallowRef","panels","computed","storage","injectInkContext","controller","createSidebarController","value","widthOptions","useId","layout","side","visible","list","widths","state","useReadable","defaultWidth","minWidth","maxWidth","width","register","entry","e","arrange","sorted","x","a","y","b","i","mounted","view","s","handle","panel","root","useTemplateRef","onMounted","name","onBeforeUnmount","openBlock","createElementBlock","mergeProps","unref","createCommentVNode","Fragment","renderList","t","createVNode","createElementVNode","renderSlot","_ctx"],"sources":["../../src/components/InkSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A sidebar of `InkPanel`s: `createSidebar`'s markup on core's sidebar controller, which holds\n * the open state, the ARIA, the keys and the clicks. Rail and tabs render their triggers here;\n * in a stack each panel renders its own header. Panels register as they are set up and take\n * their place in DOM order once mounted, so a panel added later with `v-if` slots in where it\n * stands. A closed panel stays mounted and hidden, and the panels' content stays in place\n * across layouts, so its state survives switching.\n *\n * `v-model:open` is the open panel's id (or null) for rail and tabs, and the expanded ids for\n * stack. Left unbound, the first panel starts open. It needs no document, so it works outside\n * `InkRoot`. The server renders the root, the panels (the open one shown), the stack headers\n * and the resize handle; rail and tab buttons follow once mounted, as the panels register\n * after the root has rendered.\n *\n * The handle on the inner edge resizes the sidebar. Its width is saved in `storage`, under\n * `sidebar.<name>.width`; inside `InkRoot` the root's storage is the default. `v-model:width`\n * makes the width the host's: drags and keys then only update the model, and storage is not\n * read or written.\n */\nimport {\n createSidebarController,\n type SidebarLayout,\n type SidebarOpen,\n type SidebarSide,\n} from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useId,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { injectInkContext } from '../context'\nimport { useReadable } from '../use-readable'\n\nimport { provideSidebarContext, type SidebarPanelEntry } from './sidebar-context'\nimport SidebarTrigger from './SidebarTrigger.vue'\n\nimport type { InkStorage } from '@inkbar/core'\n\nconst props = withDefaults(\n defineProps<{\n side?: SidebarSide | undefined\n layout?: SidebarLayout | undefined\n /** Default true. False hides the whole sidebar and keeps its open panel, content and scroll for when it shows again. */\n visible?: boolean | undefined\n /**\n * Where the width is saved. Default: InkRoot's `storage` inside InkRoot, else none, and the\n * width lasts until unmount. Read once, when the sidebar is created.\n */\n storage?: InkStorage | undefined\n /**\n * The middle of the saved key, `sidebar.<name>.width`. Default: the side. Give two sidebars\n * on one side different names. Read once, when the sidebar is created.\n */\n name?: string | undefined\n /** In CSS px. Default 320; Enter or a double-click on the handle goes back to it. */\n defaultWidth?: number | undefined\n /** Default 240. */\n minWidth?: number | undefined\n /** Default 480. The shown width also stays within half the sidebar's container. */\n maxWidth?: number | undefined\n /**\n * Bind with `v-model:width` to control the width, in CSS px. Left unbound, the sidebar\n * keeps its own, saved in `storage`.\n */\n width?: number | undefined\n }>(),\n {\n side: 'left',\n layout: 'rail',\n visible: true,\n storage: undefined,\n name: undefined,\n defaultWidth: undefined,\n minWidth: undefined,\n maxWidth: undefined,\n width: undefined,\n }\n)\n// A prop and an event rather than `defineModel`: unbound, a model keeps the last width locally\n// and would hand it back as a controlled one, and the width would no longer be saved.\nconst emit = defineEmits<{ 'update:width': [width: number] }>()\n/** Undefined while unbound and untouched: the controller then opens the first panel. */\nconst open = defineModel<SidebarOpen | undefined>('open')\n\nconst entries = shallowRef<readonly SidebarPanelEntry[]>([])\nconst panels = computed(() => entries.value.map((e) => e.info()))\n\n// Read once: the controller loads the saved width as it is created.\nconst storage = props.storage ?? injectInkContext(null)?.storage\n// Pending until mounted: the panels register one by one while they are set up, and an open id\n// not registered yet is not a removed panel.\nconst controller = createSidebarController({\n layout: props.layout,\n side: props.side,\n visible: props.visible,\n panels: [],\n ...(open.value !== undefined && { open: open.value }),\n pending: true,\n onOpenChange: (value) => {\n open.value = value\n },\n ...(storage && { storage }),\n ...(props.name !== undefined && { name: props.name }),\n ...widthOptions(),\n onWidthChange: (value) => emit('update:width', value),\n // The same ids on the server and the client.\n idPrefix: useId(),\n})\n// Sync, so a panel set up next reads the attributes its registration gives (on the server too).\nwatch(\n () =>\n [\n props.layout,\n props.side,\n props.visible,\n panels.value,\n open.value,\n widthOptions(),\n ] as const,\n ([layout, side, visible, list, value, widths]) =>\n controller.update({\n layout,\n side,\n visible,\n panels: list,\n ...(value !== undefined && { open: value }),\n ...widths,\n }),\n { flush: 'sync' }\n)\nconst state = useReadable(controller.state, controller.state.get())\n\n/** The width props the host gave; an undefined one keeps the controller's. */\nfunction widthOptions(): Partial<\n Record<'defaultWidth' | 'minWidth' | 'maxWidth' | 'width', number>\n> {\n const { defaultWidth, minWidth, maxWidth, width } = props\n return {\n ...(defaultWidth !== undefined && { defaultWidth }),\n ...(minWidth !== undefined && { minWidth }),\n ...(maxWidth !== undefined && { maxWidth }),\n ...(width !== undefined && { width }),\n }\n}\n\nfunction register(entry: SidebarPanelEntry): () => void {\n entries.value = [...entries.value, entry]\n return () => {\n entries.value = entries.value.filter((e) => e !== entry)\n }\n}\n\nfunction arrange(): void {\n const list = entries.value\n const sorted = [...list].sort((a, b) => {\n const x = a.element()\n const y = b.element()\n if (!x || !y || x === y) return 0\n return x.compareDocumentPosition(y) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n })\n if (sorted.some((e, i) => e !== list[i])) entries.value = sorted\n}\n\nprovideSidebarContext({ controller, state, register, arrange })\n\n// The panels register after this component has rendered. So the server's and the first\n// render's handle is shown, as the root is (`data-state` is open while pending), and the real\n// `hidden` follows once mounted.\nconst mounted = shallowRef(false)\n\nconst view = computed(() => {\n const s = state.value\n const handle = controller.handleAttrs()\n if (!mounted.value) delete handle.hidden\n return {\n root: controller.rootAttrs(),\n style: controller.rootStyle(),\n panels: controller.panelsAttrs(),\n handle,\n tablist: controller.tablistAttrs(),\n triggers: s.panels.map((panel) => ({\n panel,\n attrs: controller.triggerAttrs(panel.id),\n })),\n }\n})\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst handle = useTemplateRef<HTMLElement>('handle')\n// The controller handles the triggers' clicks and keys, stack headers included, and the\n// handle's drags and keys. `destroy` unbinds them.\nonMounted(() => {\n controller.bindTriggers(root.value!)\n controller.bindHandle(handle.value!)\n controller.update({ pending: false })\n mounted.value = true\n // Hydration keeps the server's markup: a width the client's storage gave applies here.\n for (const [name, value] of Object.entries(controller.rootStyle()))\n root.value!.style.setProperty(name, value)\n for (const [name, value] of Object.entries(controller.handleAttrs()))\n if (name.startsWith('aria-value')) handle.value!.setAttribute(name, value)\n})\nonBeforeUnmount(() => controller.destroy())\n</script>\n\n<template>\n <!-- The client's storage may hold another width than the server showed; see onMounted. -->\n <div\n ref=\"root\"\n class=\"ink-sidebar\"\n v-bind=\"view.root\"\n :style=\"view.style\"\n data-allow-mismatch=\"style\"\n >\n <div\n v-if=\"state.layout !== 'stack'\"\n :class=\"state.layout === 'rail' ? 'ink-sidebar-rail' : 'ink-sidebar-tabs'\"\n v-bind=\"view.tablist\"\n >\n <button v-for=\"t in view.triggers\" :key=\"t.panel.id\" v-bind=\"t.attrs\">\n <SidebarTrigger :panel=\"t.panel\" :rail=\"state.layout === 'rail'\" />\n </button>\n </div>\n <!-- One element in every layout, so the panels are never remounted. -->\n <!-- The stack has no panels element: its id and class apply in rail and tabs only. -->\n <div\n :class=\"{ 'ink-sidebar-panels': state.layout !== 'stack' }\"\n v-bind=\"state.layout !== 'stack' ? view.panels : {}\"\n >\n <slot />\n </div>\n <!-- The last child, on the inner edge. -->\n <div\n ref=\"handle\"\n class=\"ink-sidebar-handle\"\n v-bind=\"view.handle\"\n data-allow-mismatch=\"attribute\"\n />\n </div>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InkViewport.js","names":["_sfc_main","InkViewport_vue_vue_type_script_setup_true_lang_default","InkViewport_default"],"sources":["../../src/components/InkViewport.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * The scroll element is handed to core's `mountViewer`, which builds the viewport, the page\n * layers, the tool controller and the keyboard shortcuts in it; Vue never renders children\n * into it. The engine reports each page host it mounts, and InkPage components rendered here\n * (they render nothing in place) teleport the page-error and custom-annotation slots into them.\n *\n * Two-way zoom and page: the viewport's own changes are reported through the context, and\n * the host's v-model values are applied only when they differ from the viewport's state, so\n * the echo of a reported value does nothing.\n *\n * Form fields render in the engine's form layer. A signature field's \"Sign\" opens\n * `InkSignFieldPopover`, and a form script's alert shows in `InkFormAlert`; both are mounted\n * here, so hosts compose nothing for them.\n */\nimport { mountViewer } from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onBeforeUpdate,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { toolIdsOf } from '../composables/useRegisteredTool'\nimport { injectInkContext } from '../context'\n\nimport InkFormAlert from './InkFormAlert.vue'\nimport InkPage from './InkPage.vue'\nimport InkSignFieldPopover from './InkSignFieldPopover.vue'\n\nimport type { AnnotationSlotProps } from './annotation-slot'\nimport type { InkDocument, InkError } from '@inkbar/core'\nimport type {\n LayoutMode,\n PageMount,\n SignFieldRequest,\n ToolId,\n ViewerEngine,\n ViewRotation,\n ZoomValue,\n} from '@inkbar/core/dom'\n\nconst props = withDefaults(\n defineProps<{\n mode?: LayoutMode\n rotation?: ViewRotation\n /** Gap between pages, CSS px. */\n gap?: number\n /** Pages kept mounted either side of the visible ones. */\n buffer?: number\n /** Pinch to zoom the pages. Off leaves the browser's own touch handling alone. */\n pinchZoom?: boolean\n }>(),\n { mode: 'continuous', rotation: 0, gap: 16, buffer: 2, pinchZoom: true }\n)\n\nconst slots = defineSlots<{\n loading?(): unknown\n error?(props: { error: InkError }): unknown\n empty?(): unknown\n annotation?(props: AnnotationSlotProps): unknown\n /** Shown inside a page that failed to render. The rest of the document stays usable. */\n 'page-error'?(props: { error: InkError; page: number }): unknown\n}>()\n\nconst ctx = injectInkContext()\nconst scroll = useTemplateRef<HTMLElement>('scroll')\n\ninterface MountedPage {\n /** The engine build the host belongs to; part of the InkPage key. */\n build: number\n page: number\n host: HTMLElement\n mount: PageMount\n error: InkError | null\n /** Whether the build that mounted the page renders the annotation slot. */\n custom: boolean\n}\n/** A key per document, so the field menu and its dialog start afresh for each one. */\nconst documentKeys = new WeakMap<InkDocument, number>()\nlet documentKeyCount = 0\nfunction documentKey(doc: InkDocument | null): number {\n if (!doc) return 0\n let key = documentKeys.get(doc)\n if (key === undefined) {\n key = ++documentKeyCount\n documentKeys.set(doc, key)\n }\n return key\n}\n\n/** Page hosts, mounts and errors the engine reported; InkPage renders Teleports for them. */\nconst pages = shallowRef<MountedPage[]>([])\n\nconst state = computed(() => ctx.status.value.state)\nconst error = computed<InkError | null>(() =>\n ctx.status.value.state === 'error' ? ctx.status.value.error : null\n)\n\nconst engine = shallowRef<ViewerEngine | null>(null)\n/** The signature field whose menu is open. */\nconst signRequest = shallowRef<SignFieldRequest | null>(null)\n/** The form script's message on show, and how many have been shown. */\nconst alertMessage = shallowRef<string | null>(null)\nconst alertsShown = shallowRef(0)\n/**\n * Numbered before each build starts, so the pages a build mounts carry its own number. A\n * rebuild gives each page a new host and mount, so pages from an earlier build must remount,\n * not be reused.\n */\nlet build = 0\n/** Whether the current build renders the annotation slot. */\nlet custom = false\nlet stops: (() => void)[] = []\n/** The zoom and page last reported to the host. */\nlet lastReportedZoom: ZoomValue | undefined\nlet lastReportedPage: number | undefined\n\nfunction teardown(): void {\n for (const stop of stops) stop()\n stops = []\n // Unmounts every page, which empties `pages` through `onPageUnmount`.\n engine.value?.destroy()\n engine.value = null\n if (ctx.viewport.value) ctx.viewport.value = null\n if (ctx.tools.value) ctx.tools.value = null\n ctx.openedToolIds.value = null\n pages.value = []\n // Their fields and the document they came from are gone.\n signRequest.value = null\n alertMessage.value = null\n}\n\nfunction rebuild(opts: Parameters<ViewerEngine['rebuild']>[0]): void {\n const e = engine.value\n if (!e) return\n build += 1\n if (opts.customAnnotations !== undefined) custom = opts.customAnnotations\n e.rebuild(opts)\n}\n\n// Build for a new document or status; the previous engine is torn down first.\nfunction setup(): void {\n teardown()\n lastReportedZoom = undefined\n lastReportedPage = undefined\n const doc = ctx.document.value\n const el = scroll.value\n if (!doc || !el || ctx.status.value.state !== 'ready') return\n let first = true\n build += 1\n custom = !!slots.annotation\n // The tools this viewer registers, from the same inputs; a later `tools` waits for a rebuild.\n ctx.openedToolIds.value = toolIdsOf(ctx.hostTools.value, ctx.pluginHost.value)\n engine.value = mountViewer({\n container: el,\n document: doc,\n zoom: ctx.zoomModel.value ?? 'fit-width',\n ...(ctx.pageModel.value === undefined ? {} : { page: ctx.pageModel.value }),\n mode: props.mode,\n rotation: props.rotation,\n gap: props.gap,\n buffer: props.buffer,\n pinchZoom: props.pinchZoom,\n tool: ctx.tool.value,\n ...(ctx.hostTools.value ? { tools: ctx.hostTools.value } : {}),\n // The engine adds the plugins' tools and layers, and decorates through the host, which\n // reads `decorateAnnotation` first.\n ...(ctx.pluginHost.value ? { plugins: ctx.pluginHost.value } : {}),\n // The engine draws the search matches on the pages.\n ...(ctx.search.value ? { search: ctx.search.value } : {}),\n // Read through, so the watch below can re-apply a new function without a rebuild.\n decorateAnnotation: (a) => ctx.decorateAnnotation.value?.(a),\n ...(ctx.toolStyle.value ? { toolStyle: ctx.toolStyle.value } : {}),\n toolHistory: { batch: (fn) => ctx.history.batch(fn) },\n toolOptions: ctx.toolOptions,\n fonts: [...ctx.fonts.value],\n // Read through, so a new `signatures` store reaches the tool as well as the menus.\n signatureLibrary: () => ctx.signatures.value,\n customAnnotations: custom,\n ...(ctx.formHighlight.value === undefined\n ? {}\n : { formHighlight: ctx.formHighlight.value }),\n // The field's own button again, while its menu is open, closes the menu.\n onSignField: (req) => {\n signRequest.value = signRequest.value?.widget === req.widget ? null : req\n },\n onFormAction: (a) => ctx.reportFormAction(a),\n onFormAlert: (message) => {\n alertMessage.value = message\n alertsShown.value += 1\n },\n onActivate: (id) => ctx.setActivated(id),\n onBuild: (vp, tc) => {\n ctx.viewport.value = vp\n ctx.tools.value = tc\n if (first) {\n // A new document starts from where the viewport opens (its zoom, on the first page:\n // `createViewport` always starts at page 0); only later changes are reported, the\n // restored page among them, since the engine has already scrolled to it.\n first = false\n lastReportedZoom = vp.zoom.get()\n lastReportedPage = 0\n }\n for (const stop of stops) stop()\n const reportZoom = (z: ZoomValue): void => {\n if (z === lastReportedZoom) return\n lastReportedZoom = z\n ctx.reportZoom(z)\n }\n const reportPage = (p: number): void => {\n if (p === lastReportedPage) return\n lastReportedPage = p\n ctx.reportPage(p)\n }\n // A tool that switches itself (a one-shot tool once used) is reported as a pick.\n const followTool = (id: ToolId): void => {\n if (id !== ctx.tool.value) ctx.setTool(id)\n }\n stops = [\n vp.zoom.subscribe(reportZoom),\n vp.currentPage.subscribe(reportPage),\n tc.active.subscribe(followTool),\n ]\n // Whatever the build restored is reported once; nothing is when it matched.\n reportZoom(vp.zoom.get())\n reportPage(vp.currentPage.get())\n },\n onPageMount: (page, mount, host) => {\n pages.value = [...pages.value, { build, page, host, mount, error: null, custom }]\n },\n onPageUnmount: (page) => {\n pages.value = pages.value.filter((p) => p.page !== page)\n },\n onPageError: (page, err) => {\n pages.value = pages.value.map((p) => (p.page === page ? { ...p, error: err } : p))\n },\n })\n}\n\nonMounted(() => {\n ctx.viewportEl.value = scroll.value\n watch([() => ctx.document.value, () => ctx.status.value.state], setup, {\n immediate: true,\n })\n})\n\n// A new decorateAnnotation re-applies to the mounted annotation elements at once.\nwatch(\n () => ctx.decorateAnnotation.value,\n () => engine.value?.redecorate()\n)\n// The tint follows `formHighlight` in place, without a new document.\nwatch(\n () => ctx.formHighlight.value,\n (on) => engine.value?.setFormHighlight(on ?? true)\n)\nwatch(\n () => ctx.zoomModel.value,\n (z: ZoomValue | undefined) => {\n const vp = engine.value?.viewport\n if (vp && z !== undefined && z !== vp.zoom.get()) vp.setZoom(z)\n }\n)\nwatch(\n () => ctx.pageModel.value,\n (p) => {\n const vp = engine.value?.viewport\n if (!vp || p === undefined || p === lastReportedPage) return\n if (p !== vp.currentPage.get()) vp.scrollToPage(p)\n }\n)\nwatch(\n () => ctx.tool.value,\n (t) => {\n const tc = engine.value?.tools\n if (tc && tc.active.get() !== t) tc.set(t)\n }\n)\nwatch(\n () => props.mode,\n (m) => engine.value?.viewport.setMode(m)\n)\nwatch(\n () => props.rotation,\n (r) => engine.value?.viewport.setRotation(r)\n)\n// Gap, buffer and pinch zoom are construction options: rebuild for the same document, keeping zoom and page.\n// The slots are already updated when this runs, so it carries the annotation slot too: a slot\n// toggle in the same tick is then recorded here and `onBeforeUpdate` finds nothing left to do.\nwatch(\n [() => props.gap, () => props.buffer, () => props.pinchZoom],\n ([gap, buffer, pinchZoom]) =>\n rebuild({ gap, buffer, pinchZoom, customAnnotations: !!slots.annotation })\n)\n\n// Slots are not reactive, so a change to whether the annotation slot is given is caught here.\n// `onBeforeUpdate` runs after the new slots are in place and before this render: the rebuild\n// swaps `pages` for the new build's pages synchronously, so the render that follows never pairs\n// the annotation slot with an overlay built without it (or the reverse). The rebuild keeps the\n// scroll position.\nonBeforeUpdate(() => {\n const next = !!slots.annotation\n if (engine.value && next !== custom) rebuild({ customAnnotations: next })\n})\n\nonBeforeUnmount(() => {\n teardown()\n if (ctx.viewportEl.value === scroll.value) ctx.viewportEl.value = null\n})\n</script>\n\n<template>\n <div class=\"ink-viewport-root\" data-ink-viewport-root :data-state=\"state\">\n <div v-if=\"state === 'loading'\" class=\"ink-status\" role=\"status\" aria-live=\"polite\">\n <slot name=\"loading\">Loading document…</slot>\n </div>\n <div v-else-if=\"error\" class=\"ink-status ink-error\" role=\"alert\">\n <slot name=\"error\" :error=\"error\">\n <p class=\"ink-error-title\">This document could not be opened.</p>\n <!-- The message comes from the PDF engine and is not written for readers: it stays\n off the page, on the code's tooltip. -->\n <p>\n <code class=\"ink-error-code\" :title=\"error.message\">{{ error.code }}</code>\n </p>\n </slot>\n </div>\n <div v-else-if=\"state === 'idle'\" class=\"ink-status ink-empty\">\n <slot name=\"empty\" />\n </div>\n <div\n ref=\"scroll\"\n class=\"ink-scroll\"\n data-ink-scroll\n tabindex=\"0\"\n role=\"document\"\n aria-label=\"Document\"\n />\n <InkPage\n v-for=\"p in pages\"\n :key=\"`${p.build}:${p.page}`\"\n :page=\"p.page\"\n :host=\"p.host\"\n :mount=\"p.mount\"\n :error=\"p.error\"\n :custom-annotations=\"p.custom\"\n >\n <template v-if=\"slots.annotation\" #annotation=\"slotProps\">\n <slot name=\"annotation\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"slots['page-error']\" #page-error=\"slotProps\">\n <slot name=\"page-error\" v-bind=\"slotProps\" />\n </template>\n </InkPage>\n <!-- Keyed by document, so the menu and its dialog start afresh for each one. -->\n <InkSignFieldPopover\n :key=\"documentKey(ctx.document.value)\"\n :request=\"signRequest\"\n @close=\"signRequest = null\"\n />\n <InkFormAlert\n :message=\"alertMessage\"\n :shown=\"alertsShown\"\n :fallback=\"scroll\"\n @close=\"alertMessage = null\"\n />\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"InkViewport.js","names":["_sfc_main","InkViewport_vue_vue_type_script_setup_true_lang_default","InkViewport_default"],"sources":["../../src/components/InkViewport.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * The scroll element is handed to core's `mountViewer`, which builds the viewport, the page\n * layers, the tool controller and the keyboard shortcuts in it; Vue never renders children\n * into it. The engine reports each page host it mounts, and InkPage components rendered here\n * (they render nothing in place) teleport the page-error and custom-annotation slots into them.\n *\n * Two-way zoom and page: the viewport's own changes are reported through the context, and\n * the host's v-model values are applied only when they differ from the viewport's state, so\n * the echo of a reported value does nothing.\n *\n * Form fields render in the engine's form layer. A signature field's \"Sign\" opens\n * `InkSignFieldPopover`, and a form script's alert shows in `InkFormAlert`; both are mounted\n * here, so hosts compose nothing for them.\n */\nimport { mountViewer } from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onBeforeUpdate,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { toolIdsOf } from '../composables/useRegisteredTool'\nimport { injectInkContext } from '../context'\n\nimport InkFormAlert from './InkFormAlert.vue'\nimport InkPage from './InkPage.vue'\nimport InkSignFieldPopover from './InkSignFieldPopover.vue'\n\nimport type { AnnotationSlotProps } from './annotation-slot'\nimport type { InkDocument, InkError } from '@inkbar/core'\nimport type {\n LayoutMode,\n PageMount,\n SignFieldRequest,\n ToolId,\n ViewerEngine,\n ViewRotation,\n ZoomValue,\n} from '@inkbar/core/dom'\n\nconst props = withDefaults(\n defineProps<{\n mode?: LayoutMode\n rotation?: ViewRotation\n /** Gap between pages, CSS px. */\n gap?: number\n /** Pages kept mounted either side of the visible ones. */\n buffer?: number\n /** Pinch to zoom the pages. Off leaves the browser's own touch handling alone. */\n pinchZoom?: boolean\n }>(),\n { mode: 'continuous', rotation: 0, gap: 16, buffer: 2, pinchZoom: true }\n)\n\nconst slots = defineSlots<{\n loading?(): unknown\n error?(props: { error: InkError }): unknown\n empty?(): unknown\n annotation?(props: AnnotationSlotProps): unknown\n /** Shown inside a page that failed to render. The rest of the document stays usable. */\n 'page-error'?(props: { error: InkError; page: number }): unknown\n}>()\n\nconst ctx = injectInkContext()\nconst scroll = useTemplateRef<HTMLElement>('scroll')\n\ninterface MountedPage {\n /** The engine build the host belongs to; part of the InkPage key. */\n build: number\n page: number\n host: HTMLElement\n mount: PageMount\n error: InkError | null\n /** Whether the build that mounted the page renders the annotation slot. */\n custom: boolean\n}\n/** A key per document, so the field menu and its dialog start afresh for each one. */\nconst documentKeys = new WeakMap<InkDocument, number>()\nlet documentKeyCount = 0\nfunction documentKey(doc: InkDocument | null): number {\n if (!doc) return 0\n let key = documentKeys.get(doc)\n if (key === undefined) {\n key = ++documentKeyCount\n documentKeys.set(doc, key)\n }\n return key\n}\n\n/** Page hosts, mounts and errors the engine reported; InkPage renders Teleports for them. */\nconst pages = shallowRef<MountedPage[]>([])\n\nconst state = computed(() => ctx.status.value.state)\nconst error = computed<InkError | null>(() =>\n ctx.status.value.state === 'error' ? ctx.status.value.error : null\n)\n\nconst engine = shallowRef<ViewerEngine | null>(null)\n/** The signature field whose menu is open. */\nconst signRequest = shallowRef<SignFieldRequest | null>(null)\n/** The form script's message on show, and how many have been shown. */\nconst alertMessage = shallowRef<string | null>(null)\nconst alertsShown = shallowRef(0)\n/**\n * Numbered before each build starts, so the pages a build mounts carry its own number. A\n * rebuild gives each page a new host and mount, so pages from an earlier build must remount,\n * not be reused.\n */\nlet build = 0\n/** Whether the current build renders the annotation slot. */\nlet custom = false\nlet stops: (() => void)[] = []\n/** The zoom and page last reported to the host. */\nlet lastReportedZoom: ZoomValue | undefined\nlet lastReportedPage: number | undefined\n\nfunction teardown(): void {\n for (const stop of stops) stop()\n stops = []\n // Unmounts every page, which empties `pages` through `onPageUnmount`.\n engine.value?.destroy()\n engine.value = null\n if (ctx.viewport.value) ctx.viewport.value = null\n if (ctx.tools.value) ctx.tools.value = null\n if (ctx.navigation.value) ctx.navigation.value = null\n ctx.openedToolIds.value = null\n pages.value = []\n // Their fields and the document they came from are gone.\n signRequest.value = null\n alertMessage.value = null\n}\n\nfunction rebuild(opts: Parameters<ViewerEngine['rebuild']>[0]): void {\n const e = engine.value\n if (!e) return\n build += 1\n if (opts.customAnnotations !== undefined) custom = opts.customAnnotations\n e.rebuild(opts)\n}\n\n// Build for a new document or status; the previous engine is torn down first.\nfunction setup(): void {\n teardown()\n lastReportedZoom = undefined\n lastReportedPage = undefined\n const doc = ctx.document.value\n const el = scroll.value\n if (!doc || !el || ctx.status.value.state !== 'ready') return\n let first = true\n build += 1\n custom = !!slots.annotation\n // The tools this viewer registers, from the same inputs; a later `tools` waits for a rebuild.\n ctx.openedToolIds.value = toolIdsOf(ctx.hostTools.value, ctx.pluginHost.value)\n engine.value = mountViewer({\n container: el,\n document: doc,\n zoom: ctx.zoomModel.value ?? 'fit-width',\n ...(ctx.pageModel.value === undefined ? {} : { page: ctx.pageModel.value }),\n mode: props.mode,\n rotation: props.rotation,\n gap: props.gap,\n buffer: props.buffer,\n pinchZoom: props.pinchZoom,\n tool: ctx.tool.value,\n ...(ctx.hostTools.value ? { tools: ctx.hostTools.value } : {}),\n // The engine adds the plugins' tools and layers, and decorates through the host, which\n // reads `decorateAnnotation` first.\n ...(ctx.pluginHost.value ? { plugins: ctx.pluginHost.value } : {}),\n // The engine draws the search matches on the pages.\n ...(ctx.search.value ? { search: ctx.search.value } : {}),\n // Read through, so the watch below can re-apply a new function without a rebuild.\n decorateAnnotation: (a) => ctx.decorateAnnotation.value?.(a),\n ...(ctx.toolStyle.value ? { toolStyle: ctx.toolStyle.value } : {}),\n toolHistory: { batch: (fn) => ctx.history.batch(fn) },\n toolOptions: ctx.toolOptions,\n fonts: [...ctx.fonts.value],\n // Read through, so a new `signatures` store reaches the tool as well as the menus.\n signatureLibrary: () => ctx.signatures.value,\n customAnnotations: custom,\n ...(ctx.formHighlight.value === undefined\n ? {}\n : { formHighlight: ctx.formHighlight.value }),\n // The field's own button again, while its menu is open, closes the menu.\n onSignField: (req) => {\n signRequest.value = signRequest.value?.widget === req.widget ? null : req\n },\n onAction: (a) => ctx.reportAction(a),\n onFormAlert: (message) => {\n alertMessage.value = message\n alertsShown.value += 1\n },\n onActivate: (id) => ctx.setActivated(id),\n onBuild: (vp, tc) => {\n ctx.viewport.value = vp\n ctx.tools.value = tc\n if (first) {\n // A new document starts from where the viewport opens (its zoom, on the first page:\n // `createViewport` always starts at page 0); only later changes are reported, the\n // restored page among them, since the engine has already scrolled to it.\n first = false\n lastReportedZoom = vp.zoom.get()\n lastReportedPage = 0\n }\n for (const stop of stops) stop()\n const reportZoom = (z: ZoomValue): void => {\n if (z === lastReportedZoom) return\n lastReportedZoom = z\n ctx.reportZoom(z)\n }\n const reportPage = (p: number): void => {\n if (p === lastReportedPage) return\n lastReportedPage = p\n ctx.reportPage(p)\n }\n // A tool that switches itself (a one-shot tool once used) is reported as a pick.\n const followTool = (id: ToolId): void => {\n if (id !== ctx.tool.value) ctx.setTool(id)\n }\n stops = [\n vp.zoom.subscribe(reportZoom),\n vp.currentPage.subscribe(reportPage),\n tc.active.subscribe(followTool),\n ]\n // Whatever the build restored is reported once; nothing is when it matched.\n reportZoom(vp.zoom.get())\n reportPage(vp.currentPage.get())\n },\n onPageMount: (page, mount, host) => {\n pages.value = [...pages.value, { build, page, host, mount, error: null, custom }]\n },\n onPageUnmount: (page) => {\n pages.value = pages.value.filter((p) => p.page !== page)\n },\n onPageError: (page, err) => {\n pages.value = pages.value.map((p) => (p.page === page ? { ...p, error: err } : p))\n },\n })\n // The engine exists from here; its navigation lasts for its life, across rebuilds.\n ctx.navigation.value = engine.value.navigation\n}\n\nonMounted(() => {\n ctx.viewportEl.value = scroll.value\n watch([() => ctx.document.value, () => ctx.status.value.state], setup, {\n immediate: true,\n })\n})\n\n// A new decorateAnnotation re-applies to the mounted annotation elements at once.\nwatch(\n () => ctx.decorateAnnotation.value,\n () => engine.value?.redecorate()\n)\n// The tint follows `formHighlight` in place, without a new document.\nwatch(\n () => ctx.formHighlight.value,\n (on) => engine.value?.setFormHighlight(on ?? true)\n)\nwatch(\n () => ctx.zoomModel.value,\n (z: ZoomValue | undefined) => {\n const vp = engine.value?.viewport\n if (vp && z !== undefined && z !== vp.zoom.get()) vp.setZoom(z)\n }\n)\nwatch(\n () => ctx.pageModel.value,\n (p) => {\n const vp = engine.value?.viewport\n if (!vp || p === undefined || p === lastReportedPage) return\n if (p !== vp.currentPage.get()) vp.scrollToPage(p)\n }\n)\nwatch(\n () => ctx.tool.value,\n (t) => {\n const tc = engine.value?.tools\n if (tc && tc.active.get() !== t) tc.set(t)\n }\n)\nwatch(\n () => props.mode,\n (m) => engine.value?.viewport.setMode(m)\n)\nwatch(\n () => props.rotation,\n (r) => engine.value?.viewport.setRotation(r)\n)\n// Gap, buffer and pinch zoom are construction options: rebuild for the same document, keeping zoom and page.\n// The slots are already updated when this runs, so it carries the annotation slot too: a slot\n// toggle in the same tick is then recorded here and `onBeforeUpdate` finds nothing left to do.\nwatch(\n [() => props.gap, () => props.buffer, () => props.pinchZoom],\n ([gap, buffer, pinchZoom]) =>\n rebuild({ gap, buffer, pinchZoom, customAnnotations: !!slots.annotation })\n)\n\n// Slots are not reactive, so a change to whether the annotation slot is given is caught here.\n// `onBeforeUpdate` runs after the new slots are in place and before this render: the rebuild\n// swaps `pages` for the new build's pages synchronously, so the render that follows never pairs\n// the annotation slot with an overlay built without it (or the reverse). The rebuild keeps the\n// scroll position.\nonBeforeUpdate(() => {\n const next = !!slots.annotation\n if (engine.value && next !== custom) rebuild({ customAnnotations: next })\n})\n\nonBeforeUnmount(() => {\n teardown()\n if (ctx.viewportEl.value === scroll.value) ctx.viewportEl.value = null\n})\n</script>\n\n<template>\n <div class=\"ink-viewport-root\" data-ink-viewport-root :data-state=\"state\">\n <div v-if=\"state === 'loading'\" class=\"ink-status\" role=\"status\" aria-live=\"polite\">\n <slot name=\"loading\">Loading document…</slot>\n </div>\n <div v-else-if=\"error\" class=\"ink-status ink-error\" role=\"alert\">\n <slot name=\"error\" :error=\"error\">\n <p class=\"ink-error-title\">This document could not be opened.</p>\n <!-- The message comes from the PDF engine and is not written for readers: it stays\n off the page, on the code's tooltip. -->\n <p>\n <code class=\"ink-error-code\" :title=\"error.message\">{{ error.code }}</code>\n </p>\n </slot>\n </div>\n <div v-else-if=\"state === 'idle'\" class=\"ink-status ink-empty\">\n <slot name=\"empty\" />\n </div>\n <div\n ref=\"scroll\"\n class=\"ink-scroll\"\n data-ink-scroll\n tabindex=\"0\"\n role=\"document\"\n aria-label=\"Document\"\n />\n <InkPage\n v-for=\"p in pages\"\n :key=\"`${p.build}:${p.page}`\"\n :page=\"p.page\"\n :host=\"p.host\"\n :mount=\"p.mount\"\n :error=\"p.error\"\n :custom-annotations=\"p.custom\"\n >\n <template v-if=\"slots.annotation\" #annotation=\"slotProps\">\n <slot name=\"annotation\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"slots['page-error']\" #page-error=\"slotProps\">\n <slot name=\"page-error\" v-bind=\"slotProps\" />\n </template>\n </InkPage>\n <!-- Keyed by document, so the menu and its dialog start afresh for each one. -->\n <InkSignFieldPopover\n :key=\"documentKey(ctx.document.value)\"\n :request=\"signRequest\"\n @close=\"signRequest = null\"\n />\n <InkFormAlert\n :message=\"alertMessage\"\n :shown=\"alertsShown\"\n :fallback=\"scroll\"\n @close=\"alertMessage = null\"\n />\n </div>\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"InkViewport.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkViewport.vue"],"names":[],"mappings":"AAqZA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EACV,UAAU,EAKV,YAAY,EAEb,MAAM,kBAAkB,CAAA;AAGzB,KAAK,WAAW,GAAG;IACf,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAC;AAMJ,KAAK,WAAW,GAAG;IACjB,OAAO,CAAC,IAAI,OAAO,CAAA;IACnB,KAAK,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAA;IAC3C,KAAK,CAAC,IAAI,OAAO,CAAA;IACjB,UAAU,CAAC,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAA;IAChD,wFAAwF;IACxF,YAAY,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAA;CACjE,CAAC;AA2aF,QAAA,MAAM,UAAU;;;;;;6EAGd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"InkViewport.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkViewport.vue"],"names":[],"mappings":"AAwZA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,KAAK,EAAe,QAAQ,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EACV,UAAU,EAKV,YAAY,EAEb,MAAM,kBAAkB,CAAA;AAGzB,KAAK,WAAW,GAAG;IACf,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAC;AAMJ,KAAK,WAAW,GAAG;IACjB,OAAO,CAAC,IAAI,OAAO,CAAA;IACnB,KAAK,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAA;IAC3C,KAAK,CAAC,IAAI,OAAO,CAAA;IACjB,UAAU,CAAC,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAA;IAChD,wFAAwF;IACxF,YAAY,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAA;CACjE,CAAC;AA8aF,QAAA,MAAM,UAAU;;;;;;6EAGd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -40,7 +40,7 @@ var O = ["data-state"], k = {
40
40
  let B = x([]), V = o(() => F.status.value.state), H = o(() => F.status.value.state === "error" ? F.status.value.error : null), U = x(null), W = x(null), G = x(null), K = x(0), q = 0, J = !1, Y = [], X, Z;
41
41
  function Q() {
42
42
  for (let e of Y) e();
43
- Y = [], U.value?.destroy(), U.value = null, F.viewport.value && (F.viewport.value = null), F.tools.value && (F.tools.value = null), F.openedToolIds.value = null, B.value = [], W.value = null, G.value = null;
43
+ Y = [], U.value?.destroy(), U.value = null, F.viewport.value && (F.viewport.value = null), F.tools.value && (F.tools.value = null), F.navigation.value && (F.navigation.value = null), F.openedToolIds.value = null, B.value = [], W.value = null, G.value = null;
44
44
  }
45
45
  function $(e) {
46
46
  let t = U.value;
@@ -76,7 +76,7 @@ var O = ["data-state"], k = {
76
76
  onSignField: (e) => {
77
77
  W.value = W.value?.widget === e.widget ? null : e;
78
78
  },
79
- onFormAction: (e) => F.reportFormAction(e),
79
+ onAction: (e) => F.reportAction(e),
80
80
  onFormAlert: (e) => {
81
81
  G.value = e, K.value += 1;
82
82
  },
@@ -116,7 +116,7 @@ var O = ["data-state"], k = {
116
116
  error: t
117
117
  } : n);
118
118
  }
119
- });
119
+ }), F.navigation.value = U.value.navigation;
120
120
  }
121
121
  return _(() => {
122
122
  F.viewportEl.value = I.value, T([() => F.document.value, () => F.status.value.state], ne, { immediate: !0 });
@@ -1 +1 @@
1
- {"version":3,"file":"InkViewport.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_renderSlot","_createTextVNode","_createElementVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_createBlock","InkPage","_createSlots","_withCtx","_mergeProps","InkSignFieldPopover","_unref","_createVNode","InkFormAlert","InkFormAlert_default","InkPage_default","InkSignFieldPopover_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","InkViewport_vue_vue_type_script_setup_true_lang_default","props","__props","slots","useSlots","ctx","injectInkContext","scroll","useTemplateRef","documentKeys","documentKeyCount","documentKey","doc","key","pages","shallowRef","state","computed","error","engine","signRequest","alertMessage","alertsShown","build","custom","stops","lastReportedZoom","lastReportedPage","teardown","stop","rebuild","e","opts","setup","el","first","toolIdsOf","mountViewer","a","fn","req","message","id","vp","tc","reportZoom","z","reportPage","p","page","err","onMounted","watch","on","t","m","r","gap","buffer","pinchZoom","onBeforeUpdate","next","onBeforeUnmount","openBlock","createElementBlock","renderSlot","_ctx","_cache","createTextVNode","createElementVNode","toDisplayString","createCommentVNode","Fragment","renderList","createBlock","createSlots","withCtx","mergeProps","slotProps","unref","createVNode"],"sources":["../../src/components/InkViewport.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * The scroll element is handed to core's `mountViewer`, which builds the viewport, the page\n * layers, the tool controller and the keyboard shortcuts in it; Vue never renders children\n * into it. The engine reports each page host it mounts, and InkPage components rendered here\n * (they render nothing in place) teleport the page-error and custom-annotation slots into them.\n *\n * Two-way zoom and page: the viewport's own changes are reported through the context, and\n * the host's v-model values are applied only when they differ from the viewport's state, so\n * the echo of a reported value does nothing.\n *\n * Form fields render in the engine's form layer. A signature field's \"Sign\" opens\n * `InkSignFieldPopover`, and a form script's alert shows in `InkFormAlert`; both are mounted\n * here, so hosts compose nothing for them.\n */\nimport { mountViewer } from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onBeforeUpdate,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { toolIdsOf } from '../composables/useRegisteredTool'\nimport { injectInkContext } from '../context'\n\nimport InkFormAlert from './InkFormAlert.vue'\nimport InkPage from './InkPage.vue'\nimport InkSignFieldPopover from './InkSignFieldPopover.vue'\n\nimport type { AnnotationSlotProps } from './annotation-slot'\nimport type { InkDocument, InkError } from '@inkbar/core'\nimport type {\n LayoutMode,\n PageMount,\n SignFieldRequest,\n ToolId,\n ViewerEngine,\n ViewRotation,\n ZoomValue,\n} from '@inkbar/core/dom'\n\nconst props = withDefaults(\n defineProps<{\n mode?: LayoutMode\n rotation?: ViewRotation\n /** Gap between pages, CSS px. */\n gap?: number\n /** Pages kept mounted either side of the visible ones. */\n buffer?: number\n /** Pinch to zoom the pages. Off leaves the browser's own touch handling alone. */\n pinchZoom?: boolean\n }>(),\n { mode: 'continuous', rotation: 0, gap: 16, buffer: 2, pinchZoom: true }\n)\n\nconst slots = defineSlots<{\n loading?(): unknown\n error?(props: { error: InkError }): unknown\n empty?(): unknown\n annotation?(props: AnnotationSlotProps): unknown\n /** Shown inside a page that failed to render. The rest of the document stays usable. */\n 'page-error'?(props: { error: InkError; page: number }): unknown\n}>()\n\nconst ctx = injectInkContext()\nconst scroll = useTemplateRef<HTMLElement>('scroll')\n\ninterface MountedPage {\n /** The engine build the host belongs to; part of the InkPage key. */\n build: number\n page: number\n host: HTMLElement\n mount: PageMount\n error: InkError | null\n /** Whether the build that mounted the page renders the annotation slot. */\n custom: boolean\n}\n/** A key per document, so the field menu and its dialog start afresh for each one. */\nconst documentKeys = new WeakMap<InkDocument, number>()\nlet documentKeyCount = 0\nfunction documentKey(doc: InkDocument | null): number {\n if (!doc) return 0\n let key = documentKeys.get(doc)\n if (key === undefined) {\n key = ++documentKeyCount\n documentKeys.set(doc, key)\n }\n return key\n}\n\n/** Page hosts, mounts and errors the engine reported; InkPage renders Teleports for them. */\nconst pages = shallowRef<MountedPage[]>([])\n\nconst state = computed(() => ctx.status.value.state)\nconst error = computed<InkError | null>(() =>\n ctx.status.value.state === 'error' ? ctx.status.value.error : null\n)\n\nconst engine = shallowRef<ViewerEngine | null>(null)\n/** The signature field whose menu is open. */\nconst signRequest = shallowRef<SignFieldRequest | null>(null)\n/** The form script's message on show, and how many have been shown. */\nconst alertMessage = shallowRef<string | null>(null)\nconst alertsShown = shallowRef(0)\n/**\n * Numbered before each build starts, so the pages a build mounts carry its own number. A\n * rebuild gives each page a new host and mount, so pages from an earlier build must remount,\n * not be reused.\n */\nlet build = 0\n/** Whether the current build renders the annotation slot. */\nlet custom = false\nlet stops: (() => void)[] = []\n/** The zoom and page last reported to the host. */\nlet lastReportedZoom: ZoomValue | undefined\nlet lastReportedPage: number | undefined\n\nfunction teardown(): void {\n for (const stop of stops) stop()\n stops = []\n // Unmounts every page, which empties `pages` through `onPageUnmount`.\n engine.value?.destroy()\n engine.value = null\n if (ctx.viewport.value) ctx.viewport.value = null\n if (ctx.tools.value) ctx.tools.value = null\n ctx.openedToolIds.value = null\n pages.value = []\n // Their fields and the document they came from are gone.\n signRequest.value = null\n alertMessage.value = null\n}\n\nfunction rebuild(opts: Parameters<ViewerEngine['rebuild']>[0]): void {\n const e = engine.value\n if (!e) return\n build += 1\n if (opts.customAnnotations !== undefined) custom = opts.customAnnotations\n e.rebuild(opts)\n}\n\n// Build for a new document or status; the previous engine is torn down first.\nfunction setup(): void {\n teardown()\n lastReportedZoom = undefined\n lastReportedPage = undefined\n const doc = ctx.document.value\n const el = scroll.value\n if (!doc || !el || ctx.status.value.state !== 'ready') return\n let first = true\n build += 1\n custom = !!slots.annotation\n // The tools this viewer registers, from the same inputs; a later `tools` waits for a rebuild.\n ctx.openedToolIds.value = toolIdsOf(ctx.hostTools.value, ctx.pluginHost.value)\n engine.value = mountViewer({\n container: el,\n document: doc,\n zoom: ctx.zoomModel.value ?? 'fit-width',\n ...(ctx.pageModel.value === undefined ? {} : { page: ctx.pageModel.value }),\n mode: props.mode,\n rotation: props.rotation,\n gap: props.gap,\n buffer: props.buffer,\n pinchZoom: props.pinchZoom,\n tool: ctx.tool.value,\n ...(ctx.hostTools.value ? { tools: ctx.hostTools.value } : {}),\n // The engine adds the plugins' tools and layers, and decorates through the host, which\n // reads `decorateAnnotation` first.\n ...(ctx.pluginHost.value ? { plugins: ctx.pluginHost.value } : {}),\n // The engine draws the search matches on the pages.\n ...(ctx.search.value ? { search: ctx.search.value } : {}),\n // Read through, so the watch below can re-apply a new function without a rebuild.\n decorateAnnotation: (a) => ctx.decorateAnnotation.value?.(a),\n ...(ctx.toolStyle.value ? { toolStyle: ctx.toolStyle.value } : {}),\n toolHistory: { batch: (fn) => ctx.history.batch(fn) },\n toolOptions: ctx.toolOptions,\n fonts: [...ctx.fonts.value],\n // Read through, so a new `signatures` store reaches the tool as well as the menus.\n signatureLibrary: () => ctx.signatures.value,\n customAnnotations: custom,\n ...(ctx.formHighlight.value === undefined\n ? {}\n : { formHighlight: ctx.formHighlight.value }),\n // The field's own button again, while its menu is open, closes the menu.\n onSignField: (req) => {\n signRequest.value = signRequest.value?.widget === req.widget ? null : req\n },\n onFormAction: (a) => ctx.reportFormAction(a),\n onFormAlert: (message) => {\n alertMessage.value = message\n alertsShown.value += 1\n },\n onActivate: (id) => ctx.setActivated(id),\n onBuild: (vp, tc) => {\n ctx.viewport.value = vp\n ctx.tools.value = tc\n if (first) {\n // A new document starts from where the viewport opens (its zoom, on the first page:\n // `createViewport` always starts at page 0); only later changes are reported, the\n // restored page among them, since the engine has already scrolled to it.\n first = false\n lastReportedZoom = vp.zoom.get()\n lastReportedPage = 0\n }\n for (const stop of stops) stop()\n const reportZoom = (z: ZoomValue): void => {\n if (z === lastReportedZoom) return\n lastReportedZoom = z\n ctx.reportZoom(z)\n }\n const reportPage = (p: number): void => {\n if (p === lastReportedPage) return\n lastReportedPage = p\n ctx.reportPage(p)\n }\n // A tool that switches itself (a one-shot tool once used) is reported as a pick.\n const followTool = (id: ToolId): void => {\n if (id !== ctx.tool.value) ctx.setTool(id)\n }\n stops = [\n vp.zoom.subscribe(reportZoom),\n vp.currentPage.subscribe(reportPage),\n tc.active.subscribe(followTool),\n ]\n // Whatever the build restored is reported once; nothing is when it matched.\n reportZoom(vp.zoom.get())\n reportPage(vp.currentPage.get())\n },\n onPageMount: (page, mount, host) => {\n pages.value = [...pages.value, { build, page, host, mount, error: null, custom }]\n },\n onPageUnmount: (page) => {\n pages.value = pages.value.filter((p) => p.page !== page)\n },\n onPageError: (page, err) => {\n pages.value = pages.value.map((p) => (p.page === page ? { ...p, error: err } : p))\n },\n })\n}\n\nonMounted(() => {\n ctx.viewportEl.value = scroll.value\n watch([() => ctx.document.value, () => ctx.status.value.state], setup, {\n immediate: true,\n })\n})\n\n// A new decorateAnnotation re-applies to the mounted annotation elements at once.\nwatch(\n () => ctx.decorateAnnotation.value,\n () => engine.value?.redecorate()\n)\n// The tint follows `formHighlight` in place, without a new document.\nwatch(\n () => ctx.formHighlight.value,\n (on) => engine.value?.setFormHighlight(on ?? true)\n)\nwatch(\n () => ctx.zoomModel.value,\n (z: ZoomValue | undefined) => {\n const vp = engine.value?.viewport\n if (vp && z !== undefined && z !== vp.zoom.get()) vp.setZoom(z)\n }\n)\nwatch(\n () => ctx.pageModel.value,\n (p) => {\n const vp = engine.value?.viewport\n if (!vp || p === undefined || p === lastReportedPage) return\n if (p !== vp.currentPage.get()) vp.scrollToPage(p)\n }\n)\nwatch(\n () => ctx.tool.value,\n (t) => {\n const tc = engine.value?.tools\n if (tc && tc.active.get() !== t) tc.set(t)\n }\n)\nwatch(\n () => props.mode,\n (m) => engine.value?.viewport.setMode(m)\n)\nwatch(\n () => props.rotation,\n (r) => engine.value?.viewport.setRotation(r)\n)\n// Gap, buffer and pinch zoom are construction options: rebuild for the same document, keeping zoom and page.\n// The slots are already updated when this runs, so it carries the annotation slot too: a slot\n// toggle in the same tick is then recorded here and `onBeforeUpdate` finds nothing left to do.\nwatch(\n [() => props.gap, () => props.buffer, () => props.pinchZoom],\n ([gap, buffer, pinchZoom]) =>\n rebuild({ gap, buffer, pinchZoom, customAnnotations: !!slots.annotation })\n)\n\n// Slots are not reactive, so a change to whether the annotation slot is given is caught here.\n// `onBeforeUpdate` runs after the new slots are in place and before this render: the rebuild\n// swaps `pages` for the new build's pages synchronously, so the render that follows never pairs\n// the annotation slot with an overlay built without it (or the reverse). The rebuild keeps the\n// scroll position.\nonBeforeUpdate(() => {\n const next = !!slots.annotation\n if (engine.value && next !== custom) rebuild({ customAnnotations: next })\n})\n\nonBeforeUnmount(() => {\n teardown()\n if (ctx.viewportEl.value === scroll.value) ctx.viewportEl.value = null\n})\n</script>\n\n<template>\n <div class=\"ink-viewport-root\" data-ink-viewport-root :data-state=\"state\">\n <div v-if=\"state === 'loading'\" class=\"ink-status\" role=\"status\" aria-live=\"polite\">\n <slot name=\"loading\">Loading document…</slot>\n </div>\n <div v-else-if=\"error\" class=\"ink-status ink-error\" role=\"alert\">\n <slot name=\"error\" :error=\"error\">\n <p class=\"ink-error-title\">This document could not be opened.</p>\n <!-- The message comes from the PDF engine and is not written for readers: it stays\n off the page, on the code's tooltip. -->\n <p>\n <code class=\"ink-error-code\" :title=\"error.message\">{{ error.code }}</code>\n </p>\n </slot>\n </div>\n <div v-else-if=\"state === 'idle'\" class=\"ink-status ink-empty\">\n <slot name=\"empty\" />\n </div>\n <div\n ref=\"scroll\"\n class=\"ink-scroll\"\n data-ink-scroll\n tabindex=\"0\"\n role=\"document\"\n aria-label=\"Document\"\n />\n <InkPage\n v-for=\"p in pages\"\n :key=\"`${p.build}:${p.page}`\"\n :page=\"p.page\"\n :host=\"p.host\"\n :mount=\"p.mount\"\n :error=\"p.error\"\n :custom-annotations=\"p.custom\"\n >\n <template v-if=\"slots.annotation\" #annotation=\"slotProps\">\n <slot name=\"annotation\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"slots['page-error']\" #page-error=\"slotProps\">\n <slot name=\"page-error\" v-bind=\"slotProps\" />\n </template>\n </InkPage>\n <!-- Keyed by document, so the menu and its dialog start afresh for each one. -->\n <InkSignFieldPopover\n :key=\"documentKey(ctx.document.value)\"\n :request=\"signRequest\"\n @close=\"signRequest = null\"\n />\n <InkFormAlert\n :message=\"alertMessage\"\n :shown=\"alertsShown\"\n :fallback=\"scroll\"\n @close=\"alertMessage = null\"\n />\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"InkViewport.vue_vue_type_script_setup_true_lang.js","names":["_useSlots","_openBlock","_createElementBlock","_renderSlot","_createTextVNode","_createElementVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_createBlock","InkPage","_createSlots","_withCtx","_mergeProps","InkSignFieldPopover","_unref","_createVNode","InkFormAlert","InkFormAlert_default","InkPage_default","InkSignFieldPopover_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","InkViewport_vue_vue_type_script_setup_true_lang_default","props","__props","slots","useSlots","ctx","injectInkContext","scroll","useTemplateRef","documentKeys","documentKeyCount","documentKey","doc","key","pages","shallowRef","state","computed","error","engine","signRequest","alertMessage","alertsShown","build","custom","stops","lastReportedZoom","lastReportedPage","teardown","stop","rebuild","e","opts","setup","el","first","toolIdsOf","mountViewer","a","fn","req","message","id","vp","tc","reportZoom","z","reportPage","p","page","err","onMounted","watch","on","t","m","r","gap","buffer","pinchZoom","onBeforeUpdate","next","onBeforeUnmount","openBlock","createElementBlock","renderSlot","_ctx","_cache","createTextVNode","createElementVNode","toDisplayString","createCommentVNode","Fragment","renderList","createBlock","createSlots","withCtx","mergeProps","slotProps","unref","createVNode"],"sources":["../../src/components/InkViewport.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * The scroll element is handed to core's `mountViewer`, which builds the viewport, the page\n * layers, the tool controller and the keyboard shortcuts in it; Vue never renders children\n * into it. The engine reports each page host it mounts, and InkPage components rendered here\n * (they render nothing in place) teleport the page-error and custom-annotation slots into them.\n *\n * Two-way zoom and page: the viewport's own changes are reported through the context, and\n * the host's v-model values are applied only when they differ from the viewport's state, so\n * the echo of a reported value does nothing.\n *\n * Form fields render in the engine's form layer. A signature field's \"Sign\" opens\n * `InkSignFieldPopover`, and a form script's alert shows in `InkFormAlert`; both are mounted\n * here, so hosts compose nothing for them.\n */\nimport { mountViewer } from '@inkbar/core/dom'\nimport {\n computed,\n onBeforeUnmount,\n onBeforeUpdate,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { toolIdsOf } from '../composables/useRegisteredTool'\nimport { injectInkContext } from '../context'\n\nimport InkFormAlert from './InkFormAlert.vue'\nimport InkPage from './InkPage.vue'\nimport InkSignFieldPopover from './InkSignFieldPopover.vue'\n\nimport type { AnnotationSlotProps } from './annotation-slot'\nimport type { InkDocument, InkError } from '@inkbar/core'\nimport type {\n LayoutMode,\n PageMount,\n SignFieldRequest,\n ToolId,\n ViewerEngine,\n ViewRotation,\n ZoomValue,\n} from '@inkbar/core/dom'\n\nconst props = withDefaults(\n defineProps<{\n mode?: LayoutMode\n rotation?: ViewRotation\n /** Gap between pages, CSS px. */\n gap?: number\n /** Pages kept mounted either side of the visible ones. */\n buffer?: number\n /** Pinch to zoom the pages. Off leaves the browser's own touch handling alone. */\n pinchZoom?: boolean\n }>(),\n { mode: 'continuous', rotation: 0, gap: 16, buffer: 2, pinchZoom: true }\n)\n\nconst slots = defineSlots<{\n loading?(): unknown\n error?(props: { error: InkError }): unknown\n empty?(): unknown\n annotation?(props: AnnotationSlotProps): unknown\n /** Shown inside a page that failed to render. The rest of the document stays usable. */\n 'page-error'?(props: { error: InkError; page: number }): unknown\n}>()\n\nconst ctx = injectInkContext()\nconst scroll = useTemplateRef<HTMLElement>('scroll')\n\ninterface MountedPage {\n /** The engine build the host belongs to; part of the InkPage key. */\n build: number\n page: number\n host: HTMLElement\n mount: PageMount\n error: InkError | null\n /** Whether the build that mounted the page renders the annotation slot. */\n custom: boolean\n}\n/** A key per document, so the field menu and its dialog start afresh for each one. */\nconst documentKeys = new WeakMap<InkDocument, number>()\nlet documentKeyCount = 0\nfunction documentKey(doc: InkDocument | null): number {\n if (!doc) return 0\n let key = documentKeys.get(doc)\n if (key === undefined) {\n key = ++documentKeyCount\n documentKeys.set(doc, key)\n }\n return key\n}\n\n/** Page hosts, mounts and errors the engine reported; InkPage renders Teleports for them. */\nconst pages = shallowRef<MountedPage[]>([])\n\nconst state = computed(() => ctx.status.value.state)\nconst error = computed<InkError | null>(() =>\n ctx.status.value.state === 'error' ? ctx.status.value.error : null\n)\n\nconst engine = shallowRef<ViewerEngine | null>(null)\n/** The signature field whose menu is open. */\nconst signRequest = shallowRef<SignFieldRequest | null>(null)\n/** The form script's message on show, and how many have been shown. */\nconst alertMessage = shallowRef<string | null>(null)\nconst alertsShown = shallowRef(0)\n/**\n * Numbered before each build starts, so the pages a build mounts carry its own number. A\n * rebuild gives each page a new host and mount, so pages from an earlier build must remount,\n * not be reused.\n */\nlet build = 0\n/** Whether the current build renders the annotation slot. */\nlet custom = false\nlet stops: (() => void)[] = []\n/** The zoom and page last reported to the host. */\nlet lastReportedZoom: ZoomValue | undefined\nlet lastReportedPage: number | undefined\n\nfunction teardown(): void {\n for (const stop of stops) stop()\n stops = []\n // Unmounts every page, which empties `pages` through `onPageUnmount`.\n engine.value?.destroy()\n engine.value = null\n if (ctx.viewport.value) ctx.viewport.value = null\n if (ctx.tools.value) ctx.tools.value = null\n if (ctx.navigation.value) ctx.navigation.value = null\n ctx.openedToolIds.value = null\n pages.value = []\n // Their fields and the document they came from are gone.\n signRequest.value = null\n alertMessage.value = null\n}\n\nfunction rebuild(opts: Parameters<ViewerEngine['rebuild']>[0]): void {\n const e = engine.value\n if (!e) return\n build += 1\n if (opts.customAnnotations !== undefined) custom = opts.customAnnotations\n e.rebuild(opts)\n}\n\n// Build for a new document or status; the previous engine is torn down first.\nfunction setup(): void {\n teardown()\n lastReportedZoom = undefined\n lastReportedPage = undefined\n const doc = ctx.document.value\n const el = scroll.value\n if (!doc || !el || ctx.status.value.state !== 'ready') return\n let first = true\n build += 1\n custom = !!slots.annotation\n // The tools this viewer registers, from the same inputs; a later `tools` waits for a rebuild.\n ctx.openedToolIds.value = toolIdsOf(ctx.hostTools.value, ctx.pluginHost.value)\n engine.value = mountViewer({\n container: el,\n document: doc,\n zoom: ctx.zoomModel.value ?? 'fit-width',\n ...(ctx.pageModel.value === undefined ? {} : { page: ctx.pageModel.value }),\n mode: props.mode,\n rotation: props.rotation,\n gap: props.gap,\n buffer: props.buffer,\n pinchZoom: props.pinchZoom,\n tool: ctx.tool.value,\n ...(ctx.hostTools.value ? { tools: ctx.hostTools.value } : {}),\n // The engine adds the plugins' tools and layers, and decorates through the host, which\n // reads `decorateAnnotation` first.\n ...(ctx.pluginHost.value ? { plugins: ctx.pluginHost.value } : {}),\n // The engine draws the search matches on the pages.\n ...(ctx.search.value ? { search: ctx.search.value } : {}),\n // Read through, so the watch below can re-apply a new function without a rebuild.\n decorateAnnotation: (a) => ctx.decorateAnnotation.value?.(a),\n ...(ctx.toolStyle.value ? { toolStyle: ctx.toolStyle.value } : {}),\n toolHistory: { batch: (fn) => ctx.history.batch(fn) },\n toolOptions: ctx.toolOptions,\n fonts: [...ctx.fonts.value],\n // Read through, so a new `signatures` store reaches the tool as well as the menus.\n signatureLibrary: () => ctx.signatures.value,\n customAnnotations: custom,\n ...(ctx.formHighlight.value === undefined\n ? {}\n : { formHighlight: ctx.formHighlight.value }),\n // The field's own button again, while its menu is open, closes the menu.\n onSignField: (req) => {\n signRequest.value = signRequest.value?.widget === req.widget ? null : req\n },\n onAction: (a) => ctx.reportAction(a),\n onFormAlert: (message) => {\n alertMessage.value = message\n alertsShown.value += 1\n },\n onActivate: (id) => ctx.setActivated(id),\n onBuild: (vp, tc) => {\n ctx.viewport.value = vp\n ctx.tools.value = tc\n if (first) {\n // A new document starts from where the viewport opens (its zoom, on the first page:\n // `createViewport` always starts at page 0); only later changes are reported, the\n // restored page among them, since the engine has already scrolled to it.\n first = false\n lastReportedZoom = vp.zoom.get()\n lastReportedPage = 0\n }\n for (const stop of stops) stop()\n const reportZoom = (z: ZoomValue): void => {\n if (z === lastReportedZoom) return\n lastReportedZoom = z\n ctx.reportZoom(z)\n }\n const reportPage = (p: number): void => {\n if (p === lastReportedPage) return\n lastReportedPage = p\n ctx.reportPage(p)\n }\n // A tool that switches itself (a one-shot tool once used) is reported as a pick.\n const followTool = (id: ToolId): void => {\n if (id !== ctx.tool.value) ctx.setTool(id)\n }\n stops = [\n vp.zoom.subscribe(reportZoom),\n vp.currentPage.subscribe(reportPage),\n tc.active.subscribe(followTool),\n ]\n // Whatever the build restored is reported once; nothing is when it matched.\n reportZoom(vp.zoom.get())\n reportPage(vp.currentPage.get())\n },\n onPageMount: (page, mount, host) => {\n pages.value = [...pages.value, { build, page, host, mount, error: null, custom }]\n },\n onPageUnmount: (page) => {\n pages.value = pages.value.filter((p) => p.page !== page)\n },\n onPageError: (page, err) => {\n pages.value = pages.value.map((p) => (p.page === page ? { ...p, error: err } : p))\n },\n })\n // The engine exists from here; its navigation lasts for its life, across rebuilds.\n ctx.navigation.value = engine.value.navigation\n}\n\nonMounted(() => {\n ctx.viewportEl.value = scroll.value\n watch([() => ctx.document.value, () => ctx.status.value.state], setup, {\n immediate: true,\n })\n})\n\n// A new decorateAnnotation re-applies to the mounted annotation elements at once.\nwatch(\n () => ctx.decorateAnnotation.value,\n () => engine.value?.redecorate()\n)\n// The tint follows `formHighlight` in place, without a new document.\nwatch(\n () => ctx.formHighlight.value,\n (on) => engine.value?.setFormHighlight(on ?? true)\n)\nwatch(\n () => ctx.zoomModel.value,\n (z: ZoomValue | undefined) => {\n const vp = engine.value?.viewport\n if (vp && z !== undefined && z !== vp.zoom.get()) vp.setZoom(z)\n }\n)\nwatch(\n () => ctx.pageModel.value,\n (p) => {\n const vp = engine.value?.viewport\n if (!vp || p === undefined || p === lastReportedPage) return\n if (p !== vp.currentPage.get()) vp.scrollToPage(p)\n }\n)\nwatch(\n () => ctx.tool.value,\n (t) => {\n const tc = engine.value?.tools\n if (tc && tc.active.get() !== t) tc.set(t)\n }\n)\nwatch(\n () => props.mode,\n (m) => engine.value?.viewport.setMode(m)\n)\nwatch(\n () => props.rotation,\n (r) => engine.value?.viewport.setRotation(r)\n)\n// Gap, buffer and pinch zoom are construction options: rebuild for the same document, keeping zoom and page.\n// The slots are already updated when this runs, so it carries the annotation slot too: a slot\n// toggle in the same tick is then recorded here and `onBeforeUpdate` finds nothing left to do.\nwatch(\n [() => props.gap, () => props.buffer, () => props.pinchZoom],\n ([gap, buffer, pinchZoom]) =>\n rebuild({ gap, buffer, pinchZoom, customAnnotations: !!slots.annotation })\n)\n\n// Slots are not reactive, so a change to whether the annotation slot is given is caught here.\n// `onBeforeUpdate` runs after the new slots are in place and before this render: the rebuild\n// swaps `pages` for the new build's pages synchronously, so the render that follows never pairs\n// the annotation slot with an overlay built without it (or the reverse). The rebuild keeps the\n// scroll position.\nonBeforeUpdate(() => {\n const next = !!slots.annotation\n if (engine.value && next !== custom) rebuild({ customAnnotations: next })\n})\n\nonBeforeUnmount(() => {\n teardown()\n if (ctx.viewportEl.value === scroll.value) ctx.viewportEl.value = null\n})\n</script>\n\n<template>\n <div class=\"ink-viewport-root\" data-ink-viewport-root :data-state=\"state\">\n <div v-if=\"state === 'loading'\" class=\"ink-status\" role=\"status\" aria-live=\"polite\">\n <slot name=\"loading\">Loading document…</slot>\n </div>\n <div v-else-if=\"error\" class=\"ink-status ink-error\" role=\"alert\">\n <slot name=\"error\" :error=\"error\">\n <p class=\"ink-error-title\">This document could not be opened.</p>\n <!-- The message comes from the PDF engine and is not written for readers: it stays\n off the page, on the code's tooltip. -->\n <p>\n <code class=\"ink-error-code\" :title=\"error.message\">{{ error.code }}</code>\n </p>\n </slot>\n </div>\n <div v-else-if=\"state === 'idle'\" class=\"ink-status ink-empty\">\n <slot name=\"empty\" />\n </div>\n <div\n ref=\"scroll\"\n class=\"ink-scroll\"\n data-ink-scroll\n tabindex=\"0\"\n role=\"document\"\n aria-label=\"Document\"\n />\n <InkPage\n v-for=\"p in pages\"\n :key=\"`${p.build}:${p.page}`\"\n :page=\"p.page\"\n :host=\"p.host\"\n :mount=\"p.mount\"\n :error=\"p.error\"\n :custom-annotations=\"p.custom\"\n >\n <template v-if=\"slots.annotation\" #annotation=\"slotProps\">\n <slot name=\"annotation\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"slots['page-error']\" #page-error=\"slotProps\">\n <slot name=\"page-error\" v-bind=\"slotProps\" />\n </template>\n </InkPage>\n <!-- Keyed by document, so the menu and its dialog start afresh for each one. -->\n <InkSignFieldPopover\n :key=\"documentKey(ctx.document.value)\"\n :request=\"signRequest\"\n @close=\"signRequest = null\"\n />\n <InkFormAlert\n :message=\"alertMessage\"\n :shown=\"alertsShown\"\n :fallback=\"scroll\"\n @close=\"alertMessage = null\"\n />\n </div>\n</template>\n"],"mappings":"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"}
package/dist/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Annotation, FormAction, InkDocument, InkError, InkStatus, PdfAssets, PluginHistory, SignatureLibrary } from '@inkbar/core';
2
- import { DecorateAnnotation, PluginHost, SearchController, Tool, ToolController, ToolId, ToolOptions, ToolStyle, Viewport, ZoomValue } from '@inkbar/core/dom';
1
+ import { Annotation, ViewerAction, InkDocument, InkError, InkStatus, InkStorage, PdfAssets, PluginHistory, SignatureLibrary } from '@inkbar/core';
2
+ import { DecorateAnnotation, Navigation, PluginHost, SearchController, Tool, ToolController, ToolId, ToolOptions, ToolStyle, Viewport, ZoomValue } from '@inkbar/core/dom';
3
3
  import { Ref, ShallowRef } from 'vue';
4
4
  export type { InkStatus };
5
5
  /**
@@ -44,6 +44,11 @@ export interface InkContext {
44
44
  /** Set by InkViewport once it has mounted its viewport and tool controller. */
45
45
  viewport: ShallowRef<Viewport | null>;
46
46
  tools: ShallowRef<ToolController | null>;
47
+ /**
48
+ * The engine's navigation (jumps with Back and Forward), set by InkViewport when it builds
49
+ * the engine and cleared when it is torn down.
50
+ */
51
+ navigation: ShallowRef<Navigation | null>;
47
52
  /** Desired tool; InkViewport applies it to the controller. */
48
53
  tool: Readonly<Ref<ToolId>>;
49
54
  setTool(id: ToolId): void;
@@ -60,7 +65,12 @@ export interface InkContext {
60
65
  fonts: Readonly<Ref<readonly string[]>>;
61
66
  /** The tools' options for InkRoot's life, so they persist across documents. */
62
67
  toolOptions: ToolOptions;
63
- /** The saved signatures, from InkRoot's `signatures` store (memory by default). */
68
+ /**
69
+ * InkRoot's `storage` (memory by default), fixed for the root's life. The signatures, the
70
+ * plugins' settings and each `InkSidebar`'s width are kept in it.
71
+ */
72
+ storage: InkStorage;
73
+ /** The saved signatures: InkRoot's `signatures` store, else kept in `storage`. */
64
74
  signatures: Readonly<ShallowRef<SignatureLibrary>>;
65
75
  /** The fonts a typed signature offers: InkRoot's `signatureFonts`, else `SIGNATURE_FONTS`. */
66
76
  signatureFonts: Readonly<Ref<readonly string[]>>;
@@ -83,8 +93,8 @@ export interface InkContext {
83
93
  rootEl: Readonly<Ref<HTMLElement | null>>;
84
94
  /** InkRoot's `formHighlight`; InkViewport reads it when it builds the viewer. */
85
95
  formHighlight: Readonly<Ref<boolean | undefined>>;
86
- /** Emits InkRoot's `form-action`. */
87
- reportFormAction(action: FormAction): void;
96
+ /** Emits InkRoot's `action`, or the deprecated `form-action` when only that has a listener. */
97
+ reportAction(action: ViewerAction): void;
88
98
  /** Emits InkRoot's `error`, for failures after the document opened (a refused signing). */
89
99
  reportError(error: InkError): void;
90
100
  }