@inkbar/vue 0.5.0 → 0.7.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 (56) hide show
  1. package/README.md +24 -2
  2. package/dist/components/InkCommand.js +7 -0
  3. package/dist/components/InkCommand.js.map +1 -0
  4. package/dist/components/InkCommand.vue.d.ts +23 -0
  5. package/dist/components/InkCommand.vue.d.ts.map +1 -0
  6. package/dist/components/InkCommand.vue_vue_type_script_setup_true_lang.js +51 -0
  7. package/dist/components/InkCommand.vue_vue_type_script_setup_true_lang.js.map +1 -0
  8. package/dist/components/InkFindBar.js +7 -0
  9. package/dist/components/InkFindBar.js.map +1 -0
  10. package/dist/components/InkFindBar.vue.d.ts +4 -0
  11. package/dist/components/InkFindBar.vue.d.ts.map +1 -0
  12. package/dist/components/InkFindBar.vue_vue_type_script_setup_true_lang.js +133 -0
  13. package/dist/components/InkFindBar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  14. package/dist/components/InkPageInput.js.map +1 -1
  15. package/dist/components/InkPageInput.vue.d.ts.map +1 -1
  16. package/dist/components/InkPageInput.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/components/InkPageInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/components/InkRoot.js.map +1 -1
  19. package/dist/components/InkRoot.vue.d.ts +8 -1
  20. package/dist/components/InkRoot.vue.d.ts.map +1 -1
  21. package/dist/components/InkRoot.vue_vue_type_script_setup_true_lang.js +154 -106
  22. package/dist/components/InkRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
  23. package/dist/components/InkSearchButton.js +7 -0
  24. package/dist/components/InkSearchButton.js.map +1 -0
  25. package/dist/components/InkSearchButton.vue.d.ts +19 -0
  26. package/dist/components/InkSearchButton.vue.d.ts.map +1 -0
  27. package/dist/components/InkSearchButton.vue_vue_type_script_setup_true_lang.js +43 -0
  28. package/dist/components/InkSearchButton.vue_vue_type_script_setup_true_lang.js.map +1 -0
  29. package/dist/components/InkToolButton.js.map +1 -1
  30. package/dist/components/InkToolButton.vue.d.ts.map +1 -1
  31. package/dist/components/InkToolButton.vue_vue_type_script_setup_true_lang.js +36 -35
  32. package/dist/components/InkToolButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  33. package/dist/components/InkViewport.js.map +1 -1
  34. package/dist/components/InkViewport.vue.d.ts.map +1 -1
  35. package/dist/components/InkViewport.vue_vue_type_script_setup_true_lang.js +6 -2
  36. package/dist/components/InkViewport.vue_vue_type_script_setup_true_lang.js.map +1 -1
  37. package/dist/components/SignatureForm.vue.d.ts +2 -2
  38. package/dist/composables/useCommand.d.ts +23 -0
  39. package/dist/composables/useCommand.d.ts.map +1 -0
  40. package/dist/composables/useCommand.js +22 -0
  41. package/dist/composables/useCommand.js.map +1 -0
  42. package/dist/composables/useSearch.d.ts +12 -0
  43. package/dist/composables/useSearch.d.ts.map +1 -0
  44. package/dist/composables/useSearch.js +27 -0
  45. package/dist/composables/useSearch.js.map +1 -0
  46. package/dist/context.d.ts +21 -4
  47. package/dist/context.d.ts.map +1 -1
  48. package/dist/context.js.map +1 -1
  49. package/dist/index.d.ts +7 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +30 -25
  52. package/dist/ref-history.d.ts +12 -3
  53. package/dist/ref-history.d.ts.map +1 -1
  54. package/dist/ref-history.js +27 -16
  55. package/dist/ref-history.js.map +1 -1
  56. package/package.json +6 -5
package/README.md CHANGED
@@ -126,11 +126,33 @@ The highlight tool leaves `contents` free for the comment. `doc.text(page)` retu
126
126
 
127
127
  `InkSignatureMenu` lists saved signatures, picks one for the `signature` tool, removes them, and opens `InkSignatureDialog` to draw, type or upload a new one. These are marks on the page, not cryptographic signatures. Pass `signatures` to `InkRoot` to keep them somewhere: `createLocalSignatureStore(key)` from `@inkbar/core` uses `localStorage`, and without it they last until reload. `signatureFonts` lists the handwriting fonts the Type tab offers; load them with `@font-face`. A placed signature keeps its aspect ratio when resized.
128
128
 
129
+ ## Plugins
130
+
131
+ A plugin from `definePlugin` (in `@inkbar/core/dom`) adds commands, tools, decorations, page layers and its own record stores. Pass plugins to `InkRoot` once: the prop is read when the root is created. Nothing a plugin registers shows until you place it:
132
+
133
+ ```vue
134
+ <InkRoot :src="src" :assets="assets" :plugins="[suggestions]">
135
+ <InkToolbar>
136
+ <InkCommand id="sandbox.suggest" />
137
+ <InkToolButton tool="sandbox.stamp" />
138
+ </InkToolbar>
139
+ <InkViewport />
140
+ </InkRoot>
141
+ ```
142
+
143
+ `InkCommand` is a button named by the command's label, disabled while the command is, and its status in a live region beside it. While the command runs, the button keeps focus and has `aria-busy` and `aria-disabled`, and pressing it does nothing. It shows the command's icon, or its label as text when the command has no icon. To build your own, `useCommand(id)` returns `run`, `enabled`, `busy`, `status`, `label` and `icon`. Plugins are set up once `InkRoot` has mounted in the browser, never on the server. Until then the command is disabled and has no label, and `InkCommand` keeps its button out of the accessibility tree (`aria-hidden`). `label` and `icon` are refs, like `enabled`, so they fill in then. An id no plugin registered stays that way: `InkCommand` and `useCommand` log an `InkError('UnknownCommand')` to the console once, naming the id and the registered ones, and nothing throws. A plugin whose `setup` throws does not stop the document opening; its error is rethrown to Vue's error handling (`onErrorCaptured`, `app.config.errorHandler`). `InkToolButton` takes a plugin tool's own label and icon.
144
+
145
+ Plugin stores undo with annotations. In controlled mode with `:history="true"`, each undo step also snapshots the plugin stores, so accepting something and undoing it restores both.
146
+
147
+ ## Search
148
+
149
+ `InkFindBar` is the find bar and `InkSearchButton` opens it. Place the bar anywhere inside `InkRoot`, once `InkViewport` is there; Ctrl/Cmd+F opens it while it is mounted. `useSearch()` returns the search `state` and its actions (`open`, `setQuery`, `setMatchCase`, `next`, `previous`, `close`) for your own control. See the [`@inkbar/core` README](https://github.com/inkbar/inkbar/tree/main/packages/core#search) for the API.
150
+
129
151
  ## Custom rendering
130
152
 
131
153
  Slots let you render parts yourself. `InkViewport` has `loading`, `empty`, `error`, `annotation` and `page-error` slots. `InkThreadPopover` has a scoped default slot that receives the popover's props, and the default slot of `InkToolButton` receives `{ active, label }`. The props are typed as `AnnotationSlotProps` and `ThreadPopoverSlotProps`, the same types the React and Svelte packages export.
132
154
 
133
- `InkRoot`, `InkToolbar`, `InkToolButton`, `InkUndo`, `InkRedo`, `InkPrevPage`, `InkNextPage`, `InkZoomIn` and `InkZoomOut` take `as-child` to render your own element in place of theirs, such as a button from your design system. Put that element in the default slot. The control keeps its behaviour and accessible name.
155
+ `InkRoot`, `InkToolbar`, `InkToolButton`, `InkCommand`, `InkUndo`, `InkRedo`, `InkPrevPage`, `InkNextPage`, `InkZoomIn` and `InkZoomOut` take `as-child` to render your own element in place of theirs, such as a button from your design system. Put that element in the default slot. The control keeps its behaviour and accessible name.
134
156
 
135
157
  ## Keyboard
136
158
 
@@ -156,7 +178,7 @@ Components carry state on data attributes, for example `[data-annotation][data-s
156
178
 
157
179
  ## Composables
158
180
 
159
- `useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory` and `useEmbedded` read the state of the nearest `InkRoot`. Build your own toolbar or side panel from them. Tool options, such as the shape kind, last as long as `InkRoot`. The tool style that `useToolStyle` reads and sets (colours, line style, text style) belongs to the document: opening another one starts again from the theme's defaults.
181
+ `useDocument`, `useAnnotations`, `useTool`, `useSelection`, `useZoom`, `usePage`, `useHistory`, `useEmbedded` and `useCommand` read the state of the nearest `InkRoot`. Build your own toolbar or side panel from them. Tool options, such as the shape kind, last as long as `InkRoot`. The tool style that `useToolStyle` reads and sets (colours, line style, text style) belongs to the document: opening another one starts again from the theme's defaults.
160
182
 
161
183
  ## Licence
162
184
 
@@ -0,0 +1,7 @@
1
+ import e from "./InkCommand.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/InkCommand.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
6
+
7
+ //# sourceMappingURL=InkCommand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkCommand.js","names":["_sfc_main","InkCommand_vue_vue_type_script_setup_true_lang_default","InkCommand_default"],"sources":["../../src/components/InkCommand.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A plugin command as a toolbar button with its status beside it: the markup of core's\n * `createCommandButton`, so the theme styles both alike. The button is named by the command's\n * label and disabled while the command is, except while it runs: then it keeps focus, with\n * `aria-busy` and `aria-disabled`, and a press does nothing. The status is a live region. A command without an icon shows its label as the button's text. Until the plugins\n * are set up (once InkRoot has mounted; never on the server) the button is disabled and\n * `aria-hidden`. An id no plugin registered stays that way and logs `InkError('UnknownCommand')`\n * to the console once.\n */\nimport { ToolbarButton } from 'reka-ui'\nimport { computed } from 'vue'\n\nimport { useCommand } from '../composables/useCommand'\n\nimport InkIcon from './InkIcon.vue'\n\nconst props = withDefaults(defineProps<{ id: string; asChild?: boolean }>(), {\n asChild: false,\n})\n\n// The id is read once, as `useCommand` does: a different command is a different component.\nconst command = useCommand(props.id)\nconst { enabled, busy, status, label, icon } = command\n// Reka's typed props stop at its own; ARIA and listeners go through a bound object.\nconst attrs = computed(() => ({\n type: 'button',\n // No label until the plugins are set up: the disabled button stays out of the\n // accessibility tree rather than go unnamed.\n 'aria-label': label.value || undefined,\n title: label.value || undefined,\n 'aria-hidden': label.value ? undefined : 'true',\n 'aria-busy': String(busy.value),\n // Disabled while it runs, but not `disabled`, so the button keeps focus.\n 'aria-disabled': busy.value ? 'true' : undefined,\n onClick: () => void command.run(),\n}))\n</script>\n\n<template>\n <div class=\"ink-command\">\n <ToolbarButton\n :as-child=\"asChild\"\n :disabled=\"!enabled && !busy\"\n :data-command=\"id\"\n v-bind=\"attrs\"\n >\n <slot :label=\"label\" :busy=\"busy\">\n <InkIcon v-if=\"icon\" :icon=\"icon\" />\n <template v-else>{{ label }}</template>\n </slot>\n </ToolbarButton>\n <span class=\"ink-command-status\" role=\"status\">{{ status }}</span>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,23 @@
1
+ type __VLS_Props = {
2
+ id: string;
3
+ asChild?: boolean;
4
+ };
5
+ declare var __VLS_7: {
6
+ label: string;
7
+ busy: boolean;
8
+ };
9
+ type __VLS_Slots = {} & {
10
+ default?: (props: typeof __VLS_7) => any;
11
+ };
12
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
13
+ asChild: boolean;
14
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
15
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;
18
+ type __VLS_WithSlots<T, S> = T & {
19
+ new (): {
20
+ $slots: S;
21
+ };
22
+ };
23
+ //# sourceMappingURL=InkCommand.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkCommand.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkCommand.vue"],"names":[],"mappings":"AA0EA,KAAK,WAAW,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA2FrD,QAAA,IAAI,OAAO;;;CAAW,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;;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"}
@@ -0,0 +1,51 @@
1
+ import { useCommand as e } from "../composables/useCommand.js";
2
+ import t from "./InkIcon.js";
3
+ import { Fragment as n, computed as r, createBlock as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, createVNode as c, defineComponent as l, mergeProps as u, openBlock as d, renderSlot as f, toDisplayString as p, unref as m, withCtx as h } from "vue";
4
+ import { ToolbarButton as g } from "reka-ui";
5
+ //#region src/components/InkCommand.vue?vue&type=script&setup=true&lang.ts
6
+ var _ = { class: "ink-command" }, v = {
7
+ class: "ink-command-status",
8
+ role: "status"
9
+ }, y = /*@__PURE__*/ l({
10
+ __name: "InkCommand",
11
+ props: {
12
+ id: {},
13
+ asChild: {
14
+ type: Boolean,
15
+ default: !1
16
+ }
17
+ },
18
+ setup(l) {
19
+ let y = e(l.id), { enabled: b, busy: x, status: S, label: C, icon: w } = y, T = r(() => ({
20
+ type: "button",
21
+ "aria-label": C.value || void 0,
22
+ title: C.value || void 0,
23
+ "aria-hidden": C.value ? void 0 : "true",
24
+ "aria-busy": String(x.value),
25
+ "aria-disabled": x.value ? "true" : void 0,
26
+ onClick: () => void y.run()
27
+ }));
28
+ return (e, r) => (d(), a("div", _, [c(m(g), u({
29
+ "as-child": l.asChild,
30
+ disabled: !m(b) && !m(x),
31
+ "data-command": l.id
32
+ }, T.value), {
33
+ default: h(() => [f(e.$slots, "default", {
34
+ label: m(C),
35
+ busy: m(x)
36
+ }, () => [m(w) ? (d(), i(t, {
37
+ key: 0,
38
+ icon: m(w)
39
+ }, null, 8, ["icon"])) : (d(), a(n, { key: 1 }, [s(p(m(C)), 1)], 64))])]),
40
+ _: 3
41
+ }, 16, [
42
+ "as-child",
43
+ "disabled",
44
+ "data-command"
45
+ ]), o("span", v, p(m(S)), 1)]));
46
+ }
47
+ });
48
+ //#endregion
49
+ export { y as default };
50
+
51
+ //# sourceMappingURL=InkCommand.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkCommand.vue_vue_type_script_setup_true_lang.js","names":["props","_openBlock","_createElementBlock","_createVNode","_unref","_mergeProps","_withCtx","_renderSlot","_createBlock","InkIcon","_Fragment","_createTextVNode","_toDisplayString","_createElementVNode","InkIcon_default","_hoisted_1","_hoisted_2","InkCommand_vue_vue_type_script_setup_true_lang_default","command","useCommand","__props","attrs","computed","label","busy","openBlock","createElementBlock","createVNode","unref","ToolbarButton","mergeProps","enabled","withCtx","renderSlot","_ctx","icon","createBlock","Fragment","createTextVNode","toDisplayString","createElementVNode","status"],"sources":["../../src/components/InkCommand.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A plugin command as a toolbar button with its status beside it: the markup of core's\n * `createCommandButton`, so the theme styles both alike. The button is named by the command's\n * label and disabled while the command is, except while it runs: then it keeps focus, with\n * `aria-busy` and `aria-disabled`, and a press does nothing. The status is a live region. A command without an icon shows its label as the button's text. Until the plugins\n * are set up (once InkRoot has mounted; never on the server) the button is disabled and\n * `aria-hidden`. An id no plugin registered stays that way and logs `InkError('UnknownCommand')`\n * to the console once.\n */\nimport { ToolbarButton } from 'reka-ui'\nimport { computed } from 'vue'\n\nimport { useCommand } from '../composables/useCommand'\n\nimport InkIcon from './InkIcon.vue'\n\nconst props = withDefaults(defineProps<{ id: string; asChild?: boolean }>(), {\n asChild: false,\n})\n\n// The id is read once, as `useCommand` does: a different command is a different component.\nconst command = useCommand(props.id)\nconst { enabled, busy, status, label, icon } = command\n// Reka's typed props stop at its own; ARIA and listeners go through a bound object.\nconst attrs = computed(() => ({\n type: 'button',\n // No label until the plugins are set up: the disabled button stays out of the\n // accessibility tree rather than go unnamed.\n 'aria-label': label.value || undefined,\n title: label.value || undefined,\n 'aria-hidden': label.value ? undefined : 'true',\n 'aria-busy': String(busy.value),\n // Disabled while it runs, but not `disabled`, so the button keeps focus.\n 'aria-disabled': busy.value ? 'true' : undefined,\n onClick: () => void command.run(),\n}))\n</script>\n\n<template>\n <div class=\"ink-command\">\n <ToolbarButton\n :as-child=\"asChild\"\n :disabled=\"!enabled && !busy\"\n :data-command=\"id\"\n v-bind=\"attrs\"\n >\n <slot :label=\"label\" :busy=\"busy\">\n <InkIcon v-if=\"icon\" :icon=\"icon\" />\n <template v-else>{{ label }}</template>\n </slot>\n </ToolbarButton>\n <span class=\"ink-command-status\" role=\"status\">{{ status }}</span>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAsBA,IAAMkB,IAAUC,EAAWnB,EAAM,EAAE,GAC7B,EAAE,YAAS,SAAM,WAAQ,UAAO,YAASkB,GAEzCG,IAAQC,SAAgB;GAC5B,MAAM;GAGN,cAAcC,EAAM,SAAS,KAAA;GAC7B,OAAOA,EAAM,SAAS,KAAA;GACtB,eAAeA,EAAM,QAAQ,KAAA,IAAY;GACzC,aAAa,OAAOC,EAAK,KAAK;GAE9B,iBAAiBA,EAAK,QAAQ,SAAS,KAAA;GACvC,eAAe,KAAKN,EAAQ,IAAI;EAClC,EAAE;oBAIAjB,EAAA,GAAAC,EAaM,OAbNa,GAaM,CAZJZ,EAUgBC,EAAAyB,CAAA,GAVhBxB,EAUgB;GATb,YAAUe,EAAA;GACV,UAAQ,CAAGhB,EAAA2B,CAAA,KAAO,CAAK3B,EAAAoB,CAAA;GACvB,gBAAcJ,EAAA;EACP,GAAAC,EAAA,KAAK,GAAA;GAEb,SAAAf,QAGO,CAHPC,EAGO2B,EAAA,QAAA,WAAA;IAHA,OAAO9B,EAAAmB,CAAA;IAAQ,MAAMnB,EAAAoB,CAAA;GAA5B,SAGO,CAFUpB,EAAA+B,CAAA,KAAflC,EAAA,GAAAO,EAAoCC,GAAA;;IAAd,MAAML,EAAA+B,CAAA;GAC5BlC,GAAAA,MAAAA,GAAAA,CAAAA,MAAAA,CAAAA,MAAAA,EAAA,GAAAC,EAAuCQ,GAAA,EAAA,KAAA,EAAA,GAAA,CAAnBC,EAAAC,EAAAR,EAAAmB,CAAA,CAAK,GAAA,CAAA,CAAA,GAAA,EAAA,EAAA,CAAA,CAAA,CAAA;;;;;;EAG7BV,CAAAA,GAAAA,EAAkE,QAAlEG,GAAkEJ,EAAhBR,EAAAqC,CAAA,CAAM,GAAA,CAAA,CAAA,CAAA"}
@@ -0,0 +1,7 @@
1
+ import e from "./InkFindBar.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/InkFindBar.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
6
+
7
+ //# sourceMappingURL=InkFindBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkFindBar.js","names":["_sfc_main","InkFindBar_vue_vue_type_script_setup_true_lang_default","InkFindBar_default"],"sources":["../../src/components/InkFindBar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * The find bar: a field, the match count, previous and next, a match-case toggle and close,\n * a view of the root's search state. Place it anywhere inside `InkRoot`: it teleports itself\n * into `[data-ink-viewport-root]`, the positioned ancestor the theme floats it from over the\n * top-right of the pages (as `InkThreadPopover` anchors to the root's overlay rather than\n * where it is written). It is hidden while the search is closed. While it is mounted it\n * registers with the search controller, which is what lets Ctrl/Cmd+F open it.\n *\n * Enter in the field steps to the next match, Shift+Enter to the previous, Escape closes.\n * Closing returns focus to where it was before the bar opened, else to the document.\n */\nimport { findAnnouncer, findCountText } from '@inkbar/core/dom'\nimport { CaseSensitive, ChevronDown, ChevronUp, X } from '@inkbar/icons'\nimport { computed, onBeforeUnmount, useTemplateRef, watch } from 'vue'\n\nimport { useSearch } from '../composables/useSearch'\nimport { useInkContext } from '../context'\n\nimport InkIcon from './InkIcon.vue'\n\nimport type { SearchState } from '@inkbar/core/dom'\n\n// The root is a Teleport, so attributes such as `class` are bound to the bar itself.\ndefineOptions({ inheritAttrs: false })\n\nconst ctx = useInkContext('InkFindBar')\nconst { state, setQuery, setMatchCase, next, previous, close } = useSearch()\n\n/** The viewport root: the scroll element's parent, once InkViewport has mounted. */\nconst target = computed(() => ctx.viewportEl.value?.parentElement ?? null)\nconst bar = useTemplateRef<HTMLElement>('bar')\nconst field = useTemplateRef<HTMLInputElement>('field')\nconst live = useTemplateRef<HTMLElement>('live')\n\nconst count = computed(() => findCountText(state.value))\n\n// The live region follows the controller's state change by change, as the vanilla bar does,\n// and is written directly. A render shows only the last of the changes since the one before,\n// so two steps in one tick that end where they began would otherwise say nothing.\nwatch(\n [() => ctx.search.value, live],\n ([controller, el], _, onCleanup) => {\n if (!controller || !el) return\n const announce = findAnnouncer()\n const say = (s: SearchState): void => {\n const text = announce(s)\n if (el.textContent !== text) el.textContent = text\n }\n say(controller.state.get())\n onCleanup(controller.state.subscribe(say))\n },\n { flush: 'post', immediate: true }\n)\n\nfunction onFieldKeydown(e: KeyboardEvent): void {\n if (e.key !== 'Enter' || e.isComposing) return\n e.preventDefault()\n if (e.shiftKey) previous()\n else next()\n}\nfunction onBarKeydown(e: KeyboardEvent): void {\n if (e.key !== 'Escape' || e.defaultPrevented || e.isComposing) return\n e.preventDefault()\n e.stopPropagation()\n close()\n}\n\n/** Where focus was when the bar opened. */\nlet before: HTMLElement | null = null\nlet wasOpen = false\n\nfunction restoreFocus(): void {\n const el = bar.value\n const scroll = ctx.viewportEl.value\n const to = before?.isConnected && !el?.contains(before) ? before : scroll\n before = null\n to?.focus({ preventScroll: true })\n}\n\nfunction show(s: SearchState): void {\n const el = bar.value\n const input = field.value\n if (!el || !input) return\n if (s.open !== wasOpen) {\n wasOpen = s.open\n const doc = el.ownerDocument\n if (s.open) {\n const active = doc.activeElement\n before =\n active instanceof HTMLElement && active !== doc.body && !el.contains(active)\n ? active\n : null\n input.focus()\n input.select()\n } else {\n // Focus inside the bar is about to be lost with it; focus elsewhere is left alone.\n const hadFocus = el.contains(doc.activeElement) || doc.activeElement === doc.body\n if (hadFocus) restoreFocus()\n else before = null\n }\n }\n if (input.value !== s.query) {\n input.value = s.query\n // A query set from code arrives after the bar opened: keep the new text selected.\n if (el.ownerDocument.activeElement === input) input.select()\n }\n}\n\n// After the render, so the bar is shown (and can take focus) when it opens, and the field is\n// updated once it exists.\nwatch([state, bar, field], ([s]) => show(s), { flush: 'post', immediate: true })\n\n// Registered while mounted.\nlet detach: (() => void) | null = null\nwatch(\n [() => ctx.search.value, bar],\n ([controller, el]) => {\n detach?.()\n detach = controller && el ? controller.attachBar() : null\n },\n { flush: 'post', immediate: true }\n)\nonBeforeUnmount(() => {\n detach?.()\n detach = null\n})\n</script>\n\n<template>\n <Teleport v-if=\"target\" :to=\"target\">\n <div\n v-bind=\"$attrs\"\n ref=\"bar\"\n class=\"ink-find-bar\"\n data-ink-find-bar\n role=\"search\"\n :hidden=\"!state.open\"\n @keydown=\"onBarKeydown\"\n >\n <input\n ref=\"field\"\n type=\"text\"\n aria-label=\"Find in document\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n @input=\"setQuery(($event.target as HTMLInputElement).value)\"\n @keydown=\"onFieldKeydown\"\n />\n <span class=\"ink-find-count\" data-ink-find-count>{{ count }}</span>\n <button\n type=\"button\"\n data-ink-find-previous\n aria-label=\"Previous match\"\n title=\"Previous match\"\n @click=\"previous()\"\n >\n <InkIcon :icon=\"ChevronUp\" />\n </button>\n <button\n type=\"button\"\n data-ink-find-next\n aria-label=\"Next match\"\n title=\"Next match\"\n @click=\"next()\"\n >\n <InkIcon :icon=\"ChevronDown\" />\n </button>\n <button\n type=\"button\"\n data-ink-find-match-case\n aria-label=\"Match case\"\n title=\"Match case\"\n :aria-pressed=\"state.matchCase\"\n @click=\"setMatchCase(!state.matchCase)\"\n >\n <InkIcon :icon=\"CaseSensitive\" />\n </button>\n <button\n type=\"button\"\n data-ink-find-close\n aria-label=\"Close find\"\n title=\"Close find\"\n @click=\"close()\"\n >\n <InkIcon :icon=\"X\" />\n </button>\n <div\n ref=\"live\"\n class=\"ink-visually-hidden\"\n data-ink-find-status\n aria-live=\"polite\"\n />\n </div>\n </Teleport>\n</template>\n"],"mappings":""}
@@ -0,0 +1,4 @@
1
+ declare const __VLS_export: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
4
+ //# sourceMappingURL=InkFindBar.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkFindBar.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkFindBar.vue"],"names":[],"mappings":"AA8dA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -0,0 +1,133 @@
1
+ import { useInkContext as e } from "../context.js";
2
+ import { useSearch as t } from "../composables/useSearch.js";
3
+ import n from "./InkIcon.js";
4
+ import { Teleport as r, computed as i, createBlock as a, createCommentVNode as o, createElementVNode as s, createVNode as c, defineComponent as l, mergeProps as u, onBeforeUnmount as d, openBlock as f, toDisplayString as p, unref as m, useTemplateRef as h, watch as g } from "vue";
5
+ import { findAnnouncer as _, findCountText as v } from "@inkbar/core/dom";
6
+ import { CaseSensitive as y, ChevronDown as b, ChevronUp as x, X as S } from "@inkbar/icons";
7
+ //#region src/components/InkFindBar.vue?vue&type=script&setup=true&lang.ts
8
+ var C = ["hidden"], w = {
9
+ class: "ink-find-count",
10
+ "data-ink-find-count": ""
11
+ }, T = ["aria-pressed"], E = /*@__PURE__*/ l({
12
+ inheritAttrs: !1,
13
+ __name: "InkFindBar",
14
+ setup(l) {
15
+ let E = e("InkFindBar"), { state: D, setQuery: O, setMatchCase: k, next: A, previous: j, close: M } = t(), N = i(() => E.viewportEl.value?.parentElement ?? null), P = h("bar"), F = h("field"), I = h("live"), L = i(() => v(D.value));
16
+ g([() => E.search.value, I], ([e, t], n, r) => {
17
+ if (!e || !t) return;
18
+ let i = _(), a = (e) => {
19
+ let n = i(e);
20
+ t.textContent !== n && (t.textContent = n);
21
+ };
22
+ a(e.state.get()), r(e.state.subscribe(a));
23
+ }, {
24
+ flush: "post",
25
+ immediate: !0
26
+ });
27
+ function R(e) {
28
+ e.key !== "Enter" || e.isComposing || (e.preventDefault(), e.shiftKey ? j() : A());
29
+ }
30
+ function z(e) {
31
+ e.key !== "Escape" || e.defaultPrevented || e.isComposing || (e.preventDefault(), e.stopPropagation(), M());
32
+ }
33
+ let B = null, V = !1;
34
+ function H() {
35
+ let e = P.value, t = E.viewportEl.value, n = B?.isConnected && !e?.contains(B) ? B : t;
36
+ B = null, n?.focus({ preventScroll: !0 });
37
+ }
38
+ function U(e) {
39
+ let t = P.value, n = F.value;
40
+ if (t && n) {
41
+ if (e.open !== V) {
42
+ V = e.open;
43
+ let r = t.ownerDocument;
44
+ if (e.open) {
45
+ let e = r.activeElement;
46
+ B = e instanceof HTMLElement && e !== r.body && !t.contains(e) ? e : null, n.focus(), n.select();
47
+ } else t.contains(r.activeElement) || r.activeElement === r.body ? H() : B = null;
48
+ }
49
+ n.value !== e.query && (n.value = e.query, t.ownerDocument.activeElement === n && n.select());
50
+ }
51
+ }
52
+ g([
53
+ D,
54
+ P,
55
+ F
56
+ ], ([e]) => U(e), {
57
+ flush: "post",
58
+ immediate: !0
59
+ });
60
+ let W = null;
61
+ return g([() => E.search.value, P], ([e, t]) => {
62
+ W?.(), W = e && t ? e.attachBar() : null;
63
+ }, {
64
+ flush: "post",
65
+ immediate: !0
66
+ }), d(() => {
67
+ W?.(), W = null;
68
+ }), (e, t) => N.value ? (f(), a(r, {
69
+ key: 0,
70
+ to: N.value
71
+ }, [s("div", u(e.$attrs, {
72
+ ref_key: "bar",
73
+ ref: P,
74
+ class: "ink-find-bar",
75
+ "data-ink-find-bar": "",
76
+ role: "search",
77
+ hidden: !m(D).open,
78
+ onKeydown: z
79
+ }), [
80
+ s("input", {
81
+ ref_key: "field",
82
+ ref: F,
83
+ type: "text",
84
+ "aria-label": "Find in document",
85
+ autocomplete: "off",
86
+ spellcheck: "false",
87
+ onInput: t[0] ||= (e) => m(O)(e.target.value),
88
+ onKeydown: R
89
+ }, null, 544),
90
+ s("span", w, p(L.value), 1),
91
+ s("button", {
92
+ type: "button",
93
+ "data-ink-find-previous": "",
94
+ "aria-label": "Previous match",
95
+ title: "Previous match",
96
+ onClick: t[1] ||= (e) => m(j)()
97
+ }, [c(n, { icon: m(x) }, null, 8, ["icon"])]),
98
+ s("button", {
99
+ type: "button",
100
+ "data-ink-find-next": "",
101
+ "aria-label": "Next match",
102
+ title: "Next match",
103
+ onClick: t[2] ||= (e) => m(A)()
104
+ }, [c(n, { icon: m(b) }, null, 8, ["icon"])]),
105
+ s("button", {
106
+ type: "button",
107
+ "data-ink-find-match-case": "",
108
+ "aria-label": "Match case",
109
+ title: "Match case",
110
+ "aria-pressed": m(D).matchCase,
111
+ onClick: t[3] ||= (e) => m(k)(!m(D).matchCase)
112
+ }, [c(n, { icon: m(y) }, null, 8, ["icon"])], 8, T),
113
+ s("button", {
114
+ type: "button",
115
+ "data-ink-find-close": "",
116
+ "aria-label": "Close find",
117
+ title: "Close find",
118
+ onClick: t[4] ||= (e) => m(M)()
119
+ }, [c(n, { icon: m(S) }, null, 8, ["icon"])]),
120
+ s("div", {
121
+ ref_key: "live",
122
+ ref: I,
123
+ class: "ink-visually-hidden",
124
+ "data-ink-find-status": "",
125
+ "aria-live": "polite"
126
+ }, null, 512)
127
+ ], 16, C)], 8, ["to"])) : o("", !0);
128
+ }
129
+ });
130
+ //#endregion
131
+ export { E as default };
132
+
133
+ //# sourceMappingURL=InkFindBar.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InkFindBar.vue_vue_type_script_setup_true_lang.js","names":["$attrs","_openBlock","_createBlock","_Teleport","_createElementVNode","_mergeProps","_unref","_toDisplayString","_createVNode","InkIcon","_createCommentVNode","InkIcon_default","_hoisted_1","_hoisted_2","_hoisted_3","InkFindBar_vue_vue_type_script_setup_true_lang_default","ctx","useInkContext","useSearch","target","computed","bar","useTemplateRef","field","live","count","findCountText","state","controller","el","announce","findAnnouncer","say","text","s","onCleanup","onFieldKeydown","previous","next","onBarKeydown","close","before","wasOpen","restoreFocus","scroll","to","show","input","doc","active","detach","watch","onBeforeUnmount","openBlock","createBlock","Teleport","createElementVNode","mergeProps","_ctx","unref","_cache","setQuery","$event","toDisplayString","createVNode","ChevronUp","ChevronDown","setMatchCase","CaseSensitive","X","createCommentVNode"],"sources":["../../src/components/InkFindBar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * The find bar: a field, the match count, previous and next, a match-case toggle and close,\n * a view of the root's search state. Place it anywhere inside `InkRoot`: it teleports itself\n * into `[data-ink-viewport-root]`, the positioned ancestor the theme floats it from over the\n * top-right of the pages (as `InkThreadPopover` anchors to the root's overlay rather than\n * where it is written). It is hidden while the search is closed. While it is mounted it\n * registers with the search controller, which is what lets Ctrl/Cmd+F open it.\n *\n * Enter in the field steps to the next match, Shift+Enter to the previous, Escape closes.\n * Closing returns focus to where it was before the bar opened, else to the document.\n */\nimport { findAnnouncer, findCountText } from '@inkbar/core/dom'\nimport { CaseSensitive, ChevronDown, ChevronUp, X } from '@inkbar/icons'\nimport { computed, onBeforeUnmount, useTemplateRef, watch } from 'vue'\n\nimport { useSearch } from '../composables/useSearch'\nimport { useInkContext } from '../context'\n\nimport InkIcon from './InkIcon.vue'\n\nimport type { SearchState } from '@inkbar/core/dom'\n\n// The root is a Teleport, so attributes such as `class` are bound to the bar itself.\ndefineOptions({ inheritAttrs: false })\n\nconst ctx = useInkContext('InkFindBar')\nconst { state, setQuery, setMatchCase, next, previous, close } = useSearch()\n\n/** The viewport root: the scroll element's parent, once InkViewport has mounted. */\nconst target = computed(() => ctx.viewportEl.value?.parentElement ?? null)\nconst bar = useTemplateRef<HTMLElement>('bar')\nconst field = useTemplateRef<HTMLInputElement>('field')\nconst live = useTemplateRef<HTMLElement>('live')\n\nconst count = computed(() => findCountText(state.value))\n\n// The live region follows the controller's state change by change, as the vanilla bar does,\n// and is written directly. A render shows only the last of the changes since the one before,\n// so two steps in one tick that end where they began would otherwise say nothing.\nwatch(\n [() => ctx.search.value, live],\n ([controller, el], _, onCleanup) => {\n if (!controller || !el) return\n const announce = findAnnouncer()\n const say = (s: SearchState): void => {\n const text = announce(s)\n if (el.textContent !== text) el.textContent = text\n }\n say(controller.state.get())\n onCleanup(controller.state.subscribe(say))\n },\n { flush: 'post', immediate: true }\n)\n\nfunction onFieldKeydown(e: KeyboardEvent): void {\n if (e.key !== 'Enter' || e.isComposing) return\n e.preventDefault()\n if (e.shiftKey) previous()\n else next()\n}\nfunction onBarKeydown(e: KeyboardEvent): void {\n if (e.key !== 'Escape' || e.defaultPrevented || e.isComposing) return\n e.preventDefault()\n e.stopPropagation()\n close()\n}\n\n/** Where focus was when the bar opened. */\nlet before: HTMLElement | null = null\nlet wasOpen = false\n\nfunction restoreFocus(): void {\n const el = bar.value\n const scroll = ctx.viewportEl.value\n const to = before?.isConnected && !el?.contains(before) ? before : scroll\n before = null\n to?.focus({ preventScroll: true })\n}\n\nfunction show(s: SearchState): void {\n const el = bar.value\n const input = field.value\n if (!el || !input) return\n if (s.open !== wasOpen) {\n wasOpen = s.open\n const doc = el.ownerDocument\n if (s.open) {\n const active = doc.activeElement\n before =\n active instanceof HTMLElement && active !== doc.body && !el.contains(active)\n ? active\n : null\n input.focus()\n input.select()\n } else {\n // Focus inside the bar is about to be lost with it; focus elsewhere is left alone.\n const hadFocus = el.contains(doc.activeElement) || doc.activeElement === doc.body\n if (hadFocus) restoreFocus()\n else before = null\n }\n }\n if (input.value !== s.query) {\n input.value = s.query\n // A query set from code arrives after the bar opened: keep the new text selected.\n if (el.ownerDocument.activeElement === input) input.select()\n }\n}\n\n// After the render, so the bar is shown (and can take focus) when it opens, and the field is\n// updated once it exists.\nwatch([state, bar, field], ([s]) => show(s), { flush: 'post', immediate: true })\n\n// Registered while mounted.\nlet detach: (() => void) | null = null\nwatch(\n [() => ctx.search.value, bar],\n ([controller, el]) => {\n detach?.()\n detach = controller && el ? controller.attachBar() : null\n },\n { flush: 'post', immediate: true }\n)\nonBeforeUnmount(() => {\n detach?.()\n detach = null\n})\n</script>\n\n<template>\n <Teleport v-if=\"target\" :to=\"target\">\n <div\n v-bind=\"$attrs\"\n ref=\"bar\"\n class=\"ink-find-bar\"\n data-ink-find-bar\n role=\"search\"\n :hidden=\"!state.open\"\n @keydown=\"onBarKeydown\"\n >\n <input\n ref=\"field\"\n type=\"text\"\n aria-label=\"Find in document\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n @input=\"setQuery(($event.target as HTMLInputElement).value)\"\n @keydown=\"onFieldKeydown\"\n />\n <span class=\"ink-find-count\" data-ink-find-count>{{ count }}</span>\n <button\n type=\"button\"\n data-ink-find-previous\n aria-label=\"Previous match\"\n title=\"Previous match\"\n @click=\"previous()\"\n >\n <InkIcon :icon=\"ChevronUp\" />\n </button>\n <button\n type=\"button\"\n data-ink-find-next\n aria-label=\"Next match\"\n title=\"Next match\"\n @click=\"next()\"\n >\n <InkIcon :icon=\"ChevronDown\" />\n </button>\n <button\n type=\"button\"\n data-ink-find-match-case\n aria-label=\"Match case\"\n title=\"Match case\"\n :aria-pressed=\"state.matchCase\"\n @click=\"setMatchCase(!state.matchCase)\"\n >\n <InkIcon :icon=\"CaseSensitive\" />\n </button>\n <button\n type=\"button\"\n data-ink-find-close\n aria-label=\"Close find\"\n title=\"Close find\"\n @click=\"close()\"\n >\n <InkIcon :icon=\"X\" />\n </button>\n <div\n ref=\"live\"\n class=\"ink-visually-hidden\"\n data-ink-find-status\n aria-live=\"polite\"\n />\n </div>\n </Teleport>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InkPageInput.js","names":["_sfc_main","InkPageInput_vue_vue_type_script_setup_true_lang_default","InkPageInput_default"],"sources":["../../src/components/InkPageInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\n\nimport { usePage } from '../composables/usePage'\n\nconst { current, count, goTo } = usePage()\n// `v-model` on a number input hands back a number once the user types.\nconst draft = ref<string | number>(String(current.value + 1))\nwatch(current, (c) => (draft.value = String(c + 1)))\n\n/** 1-based in the input, 0-based in the viewport. Out of range resets to the current page. */\nfunction commit(): void {\n const n = Number(draft.value)\n if (Number.isInteger(n) && n >= 1 && n <= count.value) goTo(n - 1)\n else draft.value = String(current.value + 1)\n}\n</script>\n\n<template>\n <input\n v-model=\"draft\"\n class=\"ink-page-input\"\n data-ink-page-input\n type=\"number\"\n inputmode=\"numeric\"\n aria-label=\"Page\"\n :min=\"1\"\n :max=\"count\"\n @change=\"commit\"\n @keydown.enter.prevent=\"commit\"\n />\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"InkPageInput.js","names":["_sfc_main","InkPageInput_vue_vue_type_script_setup_true_lang_default","InkPageInput_default"],"sources":["../../src/components/InkPageInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\n\nimport { usePage } from '../composables/usePage'\n\nconst { current, count, goTo } = usePage()\n// `v-model` on a number input hands back a number once the user types.\nconst draft = ref<string | number>(String(current.value + 1))\nwatch(current, (c) => (draft.value = String(c + 1)))\n\n/**\n * 1-based in the input, 0-based in the viewport. Out of range resets to the current page. The\n * page on show is left alone, so the `change` that a blur fires never scrolls back to its top.\n */\nfunction commit(): void {\n const n = Number(draft.value)\n if (Number.isInteger(n) && n >= 1 && n <= count.value) {\n if (n - 1 !== current.value) goTo(n - 1)\n } else draft.value = String(current.value + 1)\n}\n</script>\n\n<template>\n <input\n v-model=\"draft\"\n class=\"ink-page-input\"\n data-ink-page-input\n type=\"number\"\n inputmode=\"numeric\"\n aria-label=\"Page\"\n :min=\"1\"\n :max=\"count\"\n @change=\"commit\"\n @keydown.enter.prevent=\"commit\"\n />\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"InkPageInput.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkPageInput.vue"],"names":[],"mappings":"AAuEA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"InkPageInput.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkPageInput.vue"],"names":[],"mappings":"AA+EA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -8,7 +8,7 @@ var d = ["max", "onKeydown"], f = /*@__PURE__*/ n({
8
8
  s(f, (e) => h.value = String(e + 1));
9
9
  function g() {
10
10
  let e = Number(h.value);
11
- Number.isInteger(e) && e >= 1 && e <= p.value ? m(e - 1) : h.value = String(f.value + 1);
11
+ Number.isInteger(e) && e >= 1 && e <= p.value ? e - 1 !== f.value && m(e - 1) : h.value = String(f.value + 1);
12
12
  }
13
13
  return (e, n) => c((r(), t("input", {
14
14
  "onUpdate:modelValue": n[0] ||= (e) => h.value = e,
@@ -1 +1 @@
1
- {"version":3,"file":"InkPageInput.vue_vue_type_script_setup_true_lang.js","names":["_withDirectives","_openBlock","_createElementBlock","_unref","_withKeys","_withModifiers","_vModelText","_hoisted_1","InkPageInput_vue_vue_type_script_setup_true_lang_default","usePage","draft","ref","current","c","commit","n","count","goTo","withDirectives","openBlock","createElementBlock","_cache","$event","unref","withKeys","withModifiers","vModelText"],"sources":["../../src/components/InkPageInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\n\nimport { usePage } from '../composables/usePage'\n\nconst { current, count, goTo } = usePage()\n// `v-model` on a number input hands back a number once the user types.\nconst draft = ref<string | number>(String(current.value + 1))\nwatch(current, (c) => (draft.value = String(c + 1)))\n\n/** 1-based in the input, 0-based in the viewport. Out of range resets to the current page. */\nfunction commit(): void {\n const n = Number(draft.value)\n if (Number.isInteger(n) && n >= 1 && n <= count.value) goTo(n - 1)\n else draft.value = String(current.value + 1)\n}\n</script>\n\n<template>\n <input\n v-model=\"draft\"\n class=\"ink-page-input\"\n data-ink-page-input\n type=\"number\"\n inputmode=\"numeric\"\n aria-label=\"Page\"\n :min=\"1\"\n :max=\"count\"\n @change=\"commit\"\n @keydown.enter.prevent=\"commit\"\n />\n</template>\n"],"mappings":";;;;;;EAKA,IAAM,EAAE,YAAS,UAAO,YAASS,EAAQ,GAEnCC,IAAQC,EAAqB,OAAOC,EAAQ,QAAQ,CAAC,CAAC;EAC5D,EAAMA,IAAU,MAAO,EAAM,QAAQ,OAAOC,IAAI,CAAC,CAAE;EAGnD,SAASC,IAAe;GACtB,IAAMC,IAAI,OAAOL,EAAM,KAAK;GAC5B,AAAI,OAAO,UAAUK,CAAC,KAAKA,KAAK,KAAKA,KAAKC,EAAM,QAAOC,EAAKF,IAAI,CAAC,IAC5D,EAAM,QAAQ,OAAOH,EAAQ,QAAQ,CAAC;EAC7C;mBAIEZ,GAAAC,EAAA,GAAAC,EAWE,SAAA;GAVS,uBAAA,AAAAmB,EAAA,QAAA,MAAA,EAAK,QAAAC;GACd,OAAM;GACN,uBAAA;GACA,MAAK;GACL,WAAU;GACV,cAAW;GACV,KAAK;GACL,KAAKnB,EAAAa,CAAA;GACL,UAAQF;GACR,WAAOV,EAAAC,EAAgBS,GAAM,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;EATrB,GAAA,MAAA,IAAAP,CAAA,IAAA,CAAA,CAAAD,GAAAI,EAAA,KAAK,CAAA,CAAA"}
1
+ {"version":3,"file":"InkPageInput.vue_vue_type_script_setup_true_lang.js","names":["_withDirectives","_openBlock","_createElementBlock","_unref","_withKeys","_withModifiers","_vModelText","_hoisted_1","InkPageInput_vue_vue_type_script_setup_true_lang_default","usePage","draft","ref","current","c","commit","n","count","goTo","withDirectives","openBlock","createElementBlock","_cache","$event","unref","withKeys","withModifiers","vModelText"],"sources":["../../src/components/InkPageInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\n\nimport { usePage } from '../composables/usePage'\n\nconst { current, count, goTo } = usePage()\n// `v-model` on a number input hands back a number once the user types.\nconst draft = ref<string | number>(String(current.value + 1))\nwatch(current, (c) => (draft.value = String(c + 1)))\n\n/**\n * 1-based in the input, 0-based in the viewport. Out of range resets to the current page. The\n * page on show is left alone, so the `change` that a blur fires never scrolls back to its top.\n */\nfunction commit(): void {\n const n = Number(draft.value)\n if (Number.isInteger(n) && n >= 1 && n <= count.value) {\n if (n - 1 !== current.value) goTo(n - 1)\n } else draft.value = String(current.value + 1)\n}\n</script>\n\n<template>\n <input\n v-model=\"draft\"\n class=\"ink-page-input\"\n data-ink-page-input\n type=\"number\"\n inputmode=\"numeric\"\n aria-label=\"Page\"\n :min=\"1\"\n :max=\"count\"\n @change=\"commit\"\n @keydown.enter.prevent=\"commit\"\n />\n</template>\n"],"mappings":";;;;;;EAKA,IAAM,EAAE,YAAS,UAAO,YAASS,EAAQ,GAEnCC,IAAQC,EAAqB,OAAOC,EAAQ,QAAQ,CAAC,CAAC;EAC5D,EAAMA,IAAU,MAAO,EAAM,QAAQ,OAAOC,IAAI,CAAC,CAAE;EAMnD,SAASC,IAAe;GACtB,IAAMC,IAAI,OAAOL,EAAM,KAAK;GAC5B,AAAI,OAAO,UAAUK,CAAC,KAAKA,KAAK,KAAKA,KAAKC,EAAM,QAC1CD,IAAI,MAAMH,EAAQ,SAAOK,EAAKF,IAAI,CAAC,IAClC,EAAM,QAAQ,OAAOH,EAAQ,QAAQ,CAAC;EAC/C;mBAIEZ,GAAAC,EAAA,GAAAC,EAWE,SAAA;GAVS,uBAAA,AAAAmB,EAAA,QAAA,MAAA,EAAK,QAAAC;GACd,OAAM;GACN,uBAAA;GACA,MAAK;GACL,WAAU;GACV,cAAW;GACV,KAAK;GACL,KAAKnB,EAAAa,CAAA;GACL,UAAQF;GACR,WAAOV,EAAAC,EAAgBS,GAAM,CAAA,SAAA,CAAA,GAAA,CAAA,OAAA,CAAA;EATrB,GAAA,MAAA,IAAAP,CAAA,IAAA,CAAA,CAAAD,GAAAI,EAAA,KAAK,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"InkRoot.js","names":["_sfc_main","InkRoot_vue_vue_type_script_setup_true_lang_default","InkRoot_default"],"sources":["../../src/components/InkRoot.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n createMemorySignatureStore,\n createSignatureLibrary,\n InkError,\n openDocument,\n} from '@inkbar/core'\nimport {\n createToolOptions,\n cssVariableDefaults,\n FONT_FAMILIES,\n SIGNATURE_FONTS,\n trackToolStyle,\n} from '@inkbar/core/dom'\nimport { Primitive } from 'reka-ui'\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n onUpdated,\n ref,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { provideInkContext, type InkHistory, type InkStatus } from '../context'\nimport { createRefHistory } from '../ref-history'\nimport { useReadable } from '../use-readable'\n\nimport type {\n Annotation,\n AnnotationChange,\n AnnotationDefaults,\n DocumentSource,\n InkDocument,\n PdfAssets,\n SignatureStore,\n} from '@inkbar/core'\nimport type {\n DecorateAnnotation,\n Tool,\n ToolController,\n ToolId,\n ToolStyle,\n Viewport,\n ZoomValue,\n} from '@inkbar/core/dom'\n\nconst props = withDefaults(\n defineProps<{\n src: DocumentSource | null\n assets: PdfAssets\n password?: string | undefined\n /** Colour defaults. Without it, the theme's CSS variables on the root element apply. */\n defaults?: AnnotationDefaults | undefined\n /**\n * Pass (with v-model) to put annotations under the host's control. The mode is fixed when a\n * document opens: passing or dropping this prop later takes effect on the next open.\n */\n annotations?: Annotation[] | undefined\n tool?: ToolId | undefined\n /**\n * Host tools, added to the built-ins; one with a built-in's id replaces it. Read when a\n * document opens.\n */\n tools?: Tool[] | undefined\n /**\n * Adds `data-*` attributes, classes and a screen-reader description to each annotation's\n * element (see `DecorateAnnotation`). Decorations re-apply whenever the annotations change\n * and when this prop changes, without rebuilding the viewer. Pass a new function when its\n * output would change for any other reason.\n */\n decorateAnnotation?: DecorateAnnotation | undefined\n /**\n * The font families text boxes offer, replacing the PDF standard families\n * (`FONT_FAMILIES`). Fonts beyond those are the PDF writer's to embed.\n */\n fonts?: string[] | undefined\n /**\n * Where saved signatures live. Default: memory, gone on reload.\n * `createLocalSignatureStore` keeps them in the browser; a host can keep them anywhere.\n * A new store takes effect at once, for the menus and the `signature` tool alike.\n */\n signatures?: SignatureStore | undefined\n /** The handwriting fonts a typed signature offers; the host loads them. */\n signatureFonts?: string[] | undefined\n page?: number | undefined\n zoom?: ZoomValue | undefined\n /** Undo history. Defaults to on for uncontrolled, off for controlled. */\n history?: boolean | undefined\n asChild?: boolean\n }>(),\n {\n password: undefined,\n defaults: undefined,\n annotations: undefined,\n tool: undefined,\n tools: undefined,\n decorateAnnotation: undefined,\n fonts: undefined,\n signatures: undefined,\n signatureFonts: undefined,\n page: undefined,\n zoom: undefined,\n history: undefined,\n asChild: false,\n }\n)\n\nconst emit = defineEmits<{\n 'update:annotations': [next: Annotation[]]\n change: [change: AnnotationChange]\n 'update:tool': [tool: ToolId]\n 'update:page': [page: number]\n 'update:zoom': [zoom: ZoomValue]\n ready: [doc: InkDocument]\n error: [error: InkError]\n activate: [id: string]\n}>()\n\ntype TestHookElement = HTMLElement & { __inkDocument?: InkDocument | undefined }\n\n// A template ref on Primitive is a component instance; the context needs its element.\nconst primitive = useTemplateRef<{ $el: unknown }>('primitive')\nconst rootEl = shallowRef<HTMLElement | null>(null)\nfunction syncRootEl(): void {\n const el = primitive.value?.$el\n rootEl.value = el instanceof HTMLElement ? el : null\n}\n\nconst document = shallowRef<InkDocument | null>(null)\n/** Overrides the document's status while opening, and when opening itself failed. */\nconst override = shallowRef<InkStatus | null>(null)\nconst idle: InkStatus = { state: 'idle' }\nconst documentStatus = useReadable<InkStatus>(() => document.value?.status ?? null, idle)\nconst status = computed<InkStatus>(() => override.value ?? documentStatus.value)\nconst annotationList = useReadable<readonly Annotation[]>(\n () => document.value?.annotations.list ?? null,\n []\n)\nconst selection = useReadable<readonly string[]>(\n () => document.value?.annotations.selection ?? null,\n []\n)\nconst viewport = shallowRef<Viewport | null>(null)\nconst toolController = shallowRef<ToolController | null>(null)\nconst internalTool = ref<ToolId>(props.tool ?? 'select-text')\nconst activated = ref<string | null>(null)\n\nconst controlled = computed(() => props.annotations !== undefined)\n\n/** Signed URLs change; the key does not. Bytes and Blobs are their own identity. */\nfunction identity(src: DocumentSource | null): unknown {\n if (src === null) return null\n if (typeof src === 'string') return src\n if ('key' in src) return src.key\n return src\n}\n\nfunction setTestHook(doc: InkDocument | undefined): void {\n if (import.meta.env.DEV && rootEl.value)\n (rootEl.value as TestHookElement).__inkDocument = doc\n}\n\nlet generation = 0\nfunction release(): void {\n document.value?.destroy()\n document.value = null\n setTestHook(undefined)\n}\n\nasync function open(): Promise<void> {\n const gen = ++generation\n const src = props.src\n release()\n refHistory.clear()\n if (src === null) {\n override.value = null\n return\n }\n override.value = { state: 'loading' }\n syncRootEl()\n let doc: InkDocument\n try {\n doc = await openDocument(src, {\n assets: props.assets,\n password: props.password,\n defaults:\n props.defaults ?? (rootEl.value ? cssVariableDefaults(rootEl.value) : undefined),\n // Controlled hosts get the ref history below; core's own stack stays off.\n history: controlled.value ? false : (props.history ?? true),\n annotations: controlled.value\n ? {\n mode: 'controlled',\n initial: props.annotations ?? [],\n onChange: (next, change) => {\n emit('update:annotations', next)\n emit('change', change)\n },\n }\n : { mode: 'uncontrolled' },\n })\n } catch (cause) {\n if (gen !== generation) return\n const error =\n cause instanceof InkError\n ? cause\n : new InkError(\n 'InvalidPdf',\n cause instanceof Error ? cause.message : String(cause),\n {},\n cause\n )\n override.value = { state: 'error', error }\n emit('error', error)\n return\n }\n if (gen !== generation) {\n doc.destroy()\n return\n }\n // The prop may have changed while the file loaded, when there was no store to apply it to.\n if (doc.annotations.mode === 'controlled' && props.annotations !== undefined)\n doc.annotations.setControlledValue(props.annotations)\n document.value = doc\n override.value = null\n setTestHook(doc)\n const s = doc.status.get()\n if (s.state === 'ready') emit('ready', doc)\n else if (s.state === 'error') emit('error', s.error)\n}\n\nconst refHistory = createRefHistory({\n source: () => props.annotations,\n apply: (list) => document.value?.annotations.replaceAll(list, 'history'),\n enabled: computed(() => controlled.value && props.history === true),\n})\nconst coreCanUndo = useReadable(() => document.value?.history.canUndo ?? null, false)\nconst coreCanRedo = useReadable(() => document.value?.history.canRedo ?? null, false)\nconst inkHistory: InkHistory = {\n canUndo: computed(() =>\n controlled.value ? refHistory.canUndo.value : coreCanUndo.value\n ),\n canRedo: computed(() =>\n controlled.value ? refHistory.canRedo.value : coreCanRedo.value\n ),\n undo: () => (controlled.value ? refHistory.undo() : document.value?.history.undo()),\n redo: () => (controlled.value ? refHistory.redo() : document.value?.history.redo()),\n clear: () => (controlled.value ? refHistory.clear() : document.value?.history.clear()),\n batch: (fn) => {\n if (controlled.value) refHistory.batch(fn)\n else if (document.value) document.value.history.batch(fn)\n else fn()\n },\n}\n\n/** The document's tool style; it batches through `inkHistory`, whichever mode is on. */\nconst toolStyle = shallowRef<ToolStyle | null>(null)\nlet setStyleTool: ((id: ToolId) => void) | undefined\nwatch(\n document,\n (doc) => {\n if (!doc) {\n toolStyle.value = null\n setStyleTool = undefined\n return\n }\n const tracker = trackToolStyle({\n store: doc.annotations,\n history: { batch: (fn) => inkHistory.batch(fn) },\n tools: props.tools,\n tool: internalTool.value,\n })\n setStyleTool = (id) => tracker.setTool(id)\n toolStyle.value = tracker.toolStyle\n },\n // Sync: the style is in place before InkViewport's watcher mounts the engine with it.\n { immediate: true, flush: 'sync' }\n)\nwatch(internalTool, (id) => setStyleTool?.(id))\n\n// Open on the client only: on the server InkRoot renders its empty slot.\nonMounted(() => {\n syncRootEl()\n void open()\n})\nonUpdated(syncRootEl)\n\n// Separate sources, so a new `{ url, key }` object with the same key compares equal.\nwatch([() => identity(props.src), () => props.password], () => void open())\n\nwatch(\n () => props.annotations,\n (next) => {\n if (next && document.value?.annotations.mode === 'controlled')\n document.value.annotations.setControlledValue(next)\n }\n)\n\nwatch(\n () => props.tool,\n (t) => {\n if (t) internalTool.value = t\n }\n)\n\nfunction setTool(id: ToolId): void {\n // Picking the active tool again starts it afresh, as after a one-shot tool's use.\n const tc = toolController.value\n if (internalTool.value === id && tc?.active.get() === id) tc.set(id)\n internalTool.value = id\n emit('update:tool', id)\n}\n\nonBeforeUnmount(() => {\n generation++\n release()\n})\n\nconst signatures = computed(() =>\n createSignatureLibrary(props.signatures ?? createMemorySignatureStore())\n)\nwatch(signatures, (lib) => void lib.refresh(), { immediate: true })\n/** Tool options last as long as the root; the tool style is per document. */\nconst toolOptions = createToolOptions()\n\nprovideInkContext({\n assets: computed(() => props.assets),\n document,\n status,\n annotations: annotationList,\n selection,\n viewport,\n tools: toolController,\n tool: internalTool,\n setTool,\n hostTools: computed(() => props.tools),\n decorateAnnotation: computed(() => props.decorateAnnotation),\n fonts: computed(() => props.fonts ?? FONT_FAMILIES),\n toolOptions,\n signatures,\n signatureFonts: computed(() => props.signatureFonts ?? SIGNATURE_FONTS),\n toolStyle,\n zoomModel: computed(() => props.zoom),\n pageModel: computed(() => props.page),\n reportZoom: (z) => emit('update:zoom', z),\n reportPage: (p) => emit('update:page', p),\n activated,\n setActivated(id) {\n activated.value = id\n if (id) emit('activate', id)\n },\n history: inkHistory,\n rootEl,\n})\n\n// `history` is what `useHistory()` returns inside the root, for hosts that hold a template ref:\n// a controlled host calls `history.clear()` after loading its saved list.\ndefineExpose({ document, status, rootEl, history: inkHistory })\n</script>\n\n<template>\n <Primitive\n ref=\"primitive\"\n :as-child=\"props.asChild\"\n as=\"div\"\n data-ink-root\n :data-state=\"status.state\"\n >\n <slot :status=\"status\" :document=\"document\" />\n </Primitive>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"InkRoot.js","names":["_sfc_main","InkRoot_vue_vue_type_script_setup_true_lang_default","InkRoot_default"],"sources":["../../src/components/InkRoot.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n createMemorySignatureStore,\n createSignatureLibrary,\n InkError,\n openDocument,\n readonly,\n writable,\n} from '@inkbar/core'\nimport {\n createPluginHost,\n createSearchController,\n createToolOptions,\n handleFindKey,\n pageSelectionText,\n viewerSearchOf,\n cssVariableDefaults,\n FONT_FAMILIES,\n SIGNATURE_FONTS,\n trackToolStyle,\n} from '@inkbar/core/dom'\nimport { Primitive } from 'reka-ui'\nimport {\n computed,\n onBeforeUnmount,\n onUnmounted,\n onMounted,\n onUpdated,\n ref,\n shallowRef,\n useTemplateRef,\n watch,\n} from 'vue'\n\nimport { provideInkContext, type InkHistory, type InkStatus } from '../context'\nimport { createRefHistory } from '../ref-history'\nimport { useReadable } from '../use-readable'\n\nimport type {\n Annotation,\n AnnotationChange,\n AnnotationDefaults,\n DocumentSource,\n InkDocument,\n PdfAssets,\n SignatureStore,\n} from '@inkbar/core'\nimport type {\n DecorateAnnotation,\n InkPlugin,\n PluginHost,\n SearchController,\n Tool,\n ToolController,\n ToolId,\n ToolStyle,\n Viewport,\n ZoomValue,\n} from '@inkbar/core/dom'\n\nconst props = withDefaults(\n defineProps<{\n src: DocumentSource | null\n assets: PdfAssets\n password?: string | undefined\n /** Colour defaults. Without it, the theme's CSS variables on the root element apply. */\n defaults?: AnnotationDefaults | undefined\n /**\n * Pass (with v-model) to put annotations under the host's control. The mode is fixed when a\n * document opens: passing or dropping this prop later takes effect on the next open.\n */\n annotations?: Annotation[] | undefined\n tool?: ToolId | undefined\n /**\n * Host tools, added to the built-ins; one with a built-in's id replaces it. Read when a\n * document opens.\n */\n tools?: Tool[] | undefined\n /**\n * Plugins, set up once when the root is created; a later change to this prop is not\n * applied. Their tools join `tools`, their decoration and page layers apply to every\n * document, and their commands are placed with `InkCommand` or `useCommand`.\n */\n plugins?: InkPlugin[] | undefined\n /**\n * Adds `data-*` attributes, classes and a screen-reader description to each annotation's\n * element (see `DecorateAnnotation`). Decorations re-apply whenever the annotations change\n * and when this prop changes, without rebuilding the viewer. Pass a new function when its\n * output would change for any other reason.\n */\n decorateAnnotation?: DecorateAnnotation | undefined\n /**\n * The font families text boxes offer, replacing the PDF standard families\n * (`FONT_FAMILIES`). Fonts beyond those are the PDF writer's to embed.\n */\n fonts?: string[] | undefined\n /**\n * Where saved signatures live. Default: memory, gone on reload.\n * `createLocalSignatureStore` keeps them in the browser; a host can keep them anywhere.\n * A new store takes effect at once, for the menus and the `signature` tool alike.\n */\n signatures?: SignatureStore | undefined\n /** The handwriting fonts a typed signature offers; the host loads them. */\n signatureFonts?: string[] | undefined\n page?: number | undefined\n zoom?: ZoomValue | undefined\n /** Undo history. Defaults to on for uncontrolled, off for controlled. */\n history?: boolean | undefined\n asChild?: boolean\n }>(),\n {\n password: undefined,\n defaults: undefined,\n annotations: undefined,\n tool: undefined,\n tools: undefined,\n plugins: undefined,\n decorateAnnotation: undefined,\n fonts: undefined,\n signatures: undefined,\n signatureFonts: undefined,\n page: undefined,\n zoom: undefined,\n history: undefined,\n asChild: false,\n }\n)\n\nconst emit = defineEmits<{\n 'update:annotations': [next: Annotation[]]\n change: [change: AnnotationChange]\n 'update:tool': [tool: ToolId]\n 'update:page': [page: number]\n 'update:zoom': [zoom: ZoomValue]\n ready: [doc: InkDocument]\n error: [error: InkError]\n activate: [id: string]\n}>()\n\ntype TestHookElement = HTMLElement & { __inkDocument?: InkDocument | undefined }\n\n// A template ref on Primitive is a component instance; the context needs its element.\nconst primitive = useTemplateRef<{ $el: unknown }>('primitive')\nconst rootEl = shallowRef<HTMLElement | null>(null)\nfunction syncRootEl(): void {\n const el = primitive.value?.$el\n rootEl.value = el instanceof HTMLElement ? el : null\n}\n\nconst document = shallowRef<InkDocument | null>(null)\n/** Overrides the document's status while opening, and when opening itself failed. */\nconst override = shallowRef<InkStatus | null>(null)\nconst idle: InkStatus = { state: 'idle' }\nconst documentStatus = useReadable<InkStatus>(() => document.value?.status ?? null, idle)\nconst status = computed<InkStatus>(() => override.value ?? documentStatus.value)\nconst annotationList = useReadable<readonly Annotation[]>(\n () => document.value?.annotations.list ?? null,\n []\n)\nconst selection = useReadable<readonly string[]>(\n () => document.value?.annotations.selection ?? null,\n []\n)\nconst viewport = shallowRef<Viewport | null>(null)\nconst toolController = shallowRef<ToolController | null>(null)\nconst internalTool = ref<ToolId>(props.tool ?? 'select-text')\nconst activated = ref<string | null>(null)\n\nconst controlled = computed(() => props.annotations !== undefined)\n\nfunction setActivated(id: string | null): void {\n activated.value = id\n if (id) emit('activate', id)\n}\n\nconst noop = () => {}\n\n/** Signed URLs change; the key does not. Bytes and Blobs are their own identity. */\nfunction identity(src: DocumentSource | null): unknown {\n if (src === null) return null\n if (typeof src === 'string') return src\n if ('key' in src) return src.key\n return src\n}\n\nfunction setTestHook(doc: InkDocument | undefined): void {\n if (import.meta.env.DEV && rootEl.value)\n (rootEl.value as TestHookElement).__inkDocument = doc\n}\n\nlet generation = 0\nfunction release(): void {\n // Cleared first, so the plugins close their stores while the document is still alive.\n const doc = document.value\n document.value = null\n doc?.destroy()\n setTestHook(undefined)\n}\n\nasync function open(): Promise<void> {\n const gen = ++generation\n const src = props.src\n release()\n refHistory.clear()\n if (src === null) {\n override.value = null\n return\n }\n override.value = { state: 'loading' }\n syncRootEl()\n let doc: InkDocument\n try {\n doc = await openDocument(src, {\n assets: props.assets,\n password: props.password,\n defaults:\n props.defaults ?? (rootEl.value ? cssVariableDefaults(rootEl.value) : undefined),\n // Controlled hosts get the ref history below; core's own stack stays off.\n history: controlled.value ? false : (props.history ?? true),\n annotations: controlled.value\n ? {\n mode: 'controlled',\n initial: props.annotations ?? [],\n onChange: (next, change) => {\n emit('update:annotations', next)\n emit('change', change)\n },\n }\n : { mode: 'uncontrolled' },\n })\n } catch (cause) {\n if (gen !== generation) return\n const error =\n cause instanceof InkError\n ? cause\n : new InkError(\n 'InvalidPdf',\n cause instanceof Error ? cause.message : String(cause),\n {},\n cause\n )\n override.value = { state: 'error', error }\n emit('error', error)\n return\n }\n if (gen !== generation) {\n doc.destroy()\n return\n }\n // The prop may have changed while the file loaded, when there was no store to apply it to.\n if (doc.annotations.mode === 'controlled' && props.annotations !== undefined)\n doc.annotations.setControlledValue(props.annotations)\n document.value = doc\n override.value = null\n setTestHook(doc)\n const s = doc.status.get()\n if (s.state === 'ready') emit('ready', doc)\n else if (s.state === 'error') emit('error', s.error)\n}\n\nconst refHistory = createRefHistory({\n source: () => props.annotations,\n apply: (list) => document.value?.annotations.replaceAll(list, 'history'),\n enabled: computed(() => controlled.value && props.history === true),\n store: () =>\n document.value?.annotations.mode === 'controlled' ? document.value.annotations : null,\n})\nconst coreCanUndo = useReadable(() => document.value?.history.canUndo ?? null, false)\nconst coreCanRedo = useReadable(() => document.value?.history.canRedo ?? null, false)\nconst inkHistory: InkHistory = {\n canUndo: computed(() =>\n controlled.value ? refHistory.canUndo.value : coreCanUndo.value\n ),\n canRedo: computed(() =>\n controlled.value ? refHistory.canRedo.value : coreCanRedo.value\n ),\n undo: () => (controlled.value ? refHistory.undo() : document.value?.history.undo()),\n redo: () => (controlled.value ? refHistory.redo() : document.value?.history.redo()),\n clear: () => (controlled.value ? refHistory.clear() : document.value?.history.clear()),\n batch: (fn) => {\n if (controlled.value) refHistory.batch(fn)\n else if (document.value) document.value.history.batch(fn)\n else fn()\n },\n track: (store) =>\n controlled.value\n ? refHistory.track(store)\n : (document.value?.history.track(store) ?? noop),\n react: (fn) =>\n controlled.value ? refHistory.react(fn) : (document.value?.history.react(fn) ?? noop),\n}\n\n/*\n * One plugin host for the root's life, built once mounted so that no `setup` runs on the\n * server, where nothing would clean it up. Until then `pluginHost` is null, and InkCommand\n * renders its command disabled. The host follows `hostDocument`, which is set after `document`\n * (a sync watch): when the host reads `inkHistory` for a new document, it is that document's\n * history.\n */\nconst hostDocument = writable<InkDocument | null>(null)\nwatch(document, (doc) => hostDocument.set(doc), { flush: 'sync' })\nconst viewportEl = shallowRef<HTMLElement | null>(null)\nconst hostRoot = writable<HTMLElement | null>(null)\nwatch(viewportEl, (el) => hostRoot.set(el), { flush: 'sync' })\nconst hostActivated = writable<string | null>(null)\nwatch(activated, (id) => hostActivated.set(id), { flush: 'sync' })\nconst pluginHost = shallowRef<PluginHost | null>(null)\n/** One search controller for the root's life, built with the plugin host that shares it. */\nconst search = shallowRef<SearchController | null>(null)\nfunction setUpPlugins(): void {\n const controller = createSearchController({\n document: readonly(hostDocument),\n viewport: () => viewport.value,\n })\n search.value = controller\n pluginHost.value = createPluginHost({\n plugins: props.plugins ?? [],\n document: readonly(hostDocument),\n history: () => inkHistory,\n activated: readonly(hostActivated),\n // As in `createViewer`: an id not in the open document is ignored.\n setActivated: (id) => {\n if (id === null || document.value?.annotations.get(id)) setActivated(id)\n },\n root: readonly(hostRoot),\n decorate: () => props.decorateAnnotation,\n viewport: () => viewport.value,\n search: viewerSearchOf(controller),\n })\n}\n\n// Reka's typed props stop at its own; listeners go through a bound object.\nconst rootListeners = { onKeydown }\n\n/** Ctrl/Cmd+F while focus is in the root and a find bar is mounted; else the browser's. */\nfunction onKeydown(e: KeyboardEvent): void {\n const controller = search.value\n const root = e.currentTarget\n if (controller && root instanceof HTMLElement)\n handleFindKey(e, controller, () => pageSelectionText(root))\n}\n\n/** The document's tool style; it batches through `inkHistory`, whichever mode is on. */\nconst toolStyle = shallowRef<ToolStyle | null>(null)\nlet setStyleTool: ((id: ToolId) => void) | undefined\nwatch(\n document,\n (doc) => {\n if (!doc) {\n toolStyle.value = null\n setStyleTool = undefined\n return\n }\n const tracker = trackToolStyle({\n store: doc.annotations,\n history: { batch: (fn) => inkHistory.batch(fn) },\n // The plugins' tools too, so a plugin tool's style group applies (the host is set by now:\n // it is built on mount, before the first document opens).\n tools: [...(props.tools ?? []), ...(pluginHost.value?.tools ?? [])],\n tool: internalTool.value,\n })\n setStyleTool = (id) => tracker.setTool(id)\n toolStyle.value = tracker.toolStyle\n },\n // Sync: the style is in place before InkViewport's watcher mounts the engine with it.\n { immediate: true, flush: 'sync' }\n)\nwatch(internalTool, (id) => setStyleTool?.(id))\n\n// Plugins and the document on the client only: on the server InkRoot renders its empty slot.\n// The plugins first, so the first document opens with their stores and reactions.\nonMounted(() => {\n syncRootEl()\n // A plugin whose setup throws still leaves the document to open; the error then goes to\n // Vue's error handling (`onErrorCaptured`, `app.config.errorHandler`).\n try {\n setUpPlugins()\n } finally {\n void open()\n }\n})\nonUpdated(syncRootEl)\n\n// Separate sources, so a new `{ url, key }` object with the same key compares equal.\nwatch([() => identity(props.src), () => props.password], () => void open())\n\nwatch(\n () => props.annotations,\n (next) => {\n if (next && document.value?.annotations.mode === 'controlled')\n document.value.annotations.setControlledValue(next)\n }\n)\n\nwatch(\n () => props.tool,\n (t) => {\n if (t) internalTool.value = t\n }\n)\n\nfunction setTool(id: ToolId): void {\n // Picking the active tool again starts it afresh, as after a one-shot tool's use.\n const tc = toolController.value\n if (internalTool.value === id && tc?.active.get() === id) tc.set(id)\n internalTool.value = id\n emit('update:tool', id)\n}\n\nonBeforeUnmount(() => {\n generation++\n release()\n})\n\n// After the children: InkViewport's engine, and the plugin layers with it, are gone by now, as\n// in `createViewer`, so no layer `destroy` runs after the plugins' cleanups.\nonUnmounted(() => {\n pluginHost.value?.destroy()\n pluginHost.value = null\n search.value?.destroy()\n search.value = null\n})\n\nconst signatures = computed(() =>\n createSignatureLibrary(props.signatures ?? createMemorySignatureStore())\n)\nwatch(signatures, (lib) => void lib.refresh(), { immediate: true })\n/** Tool options last as long as the root; the tool style is per document. */\nconst toolOptions = createToolOptions()\n\nprovideInkContext({\n assets: computed(() => props.assets),\n document,\n status,\n annotations: annotationList,\n selection,\n viewport,\n tools: toolController,\n tool: internalTool,\n setTool,\n hostTools: computed(() => props.tools),\n decorateAnnotation: computed(() => props.decorateAnnotation),\n fonts: computed(() => props.fonts ?? FONT_FAMILIES),\n toolOptions,\n signatures,\n signatureFonts: computed(() => props.signatureFonts ?? SIGNATURE_FONTS),\n toolStyle,\n zoomModel: computed(() => props.zoom),\n pageModel: computed(() => props.page),\n reportZoom: (z) => emit('update:zoom', z),\n reportPage: (p) => emit('update:page', p),\n activated,\n setActivated,\n pluginHost,\n search,\n viewportEl,\n history: inkHistory,\n rootEl,\n})\n\n// `history` is what `useHistory()` returns inside the root, for hosts that hold a template ref:\n// a controlled host calls `history.clear()` after loading its saved list.\ndefineExpose({ document, status, rootEl, history: inkHistory })\n</script>\n\n<template>\n <Primitive\n ref=\"primitive\"\n :as-child=\"props.asChild\"\n as=\"div\"\n data-ink-root\n :data-state=\"status.state\"\n v-bind=\"rootListeners\"\n >\n <slot :status=\"status\" :document=\"document\" />\n </Primitive>\n</template>\n"],"mappings":""}
@@ -1,6 +1,6 @@
1
1
  import { InkError, Annotation, AnnotationChange, AnnotationDefaults, DocumentSource, InkDocument, PdfAssets, SignatureStore } from '@inkbar/core';
2
2
  import { InkHistory, InkStatus } from '../context';
3
- import { DecorateAnnotation, Tool, ToolId, ZoomValue } from '@inkbar/core/dom';
3
+ import { DecorateAnnotation, InkPlugin, Tool, ToolId, ZoomValue } from '@inkbar/core/dom';
4
4
  type __VLS_Props = {
5
5
  src: DocumentSource | null;
6
6
  assets: PdfAssets;
@@ -18,6 +18,12 @@ type __VLS_Props = {
18
18
  * document opens.
19
19
  */
20
20
  tools?: Tool[] | undefined;
21
+ /**
22
+ * Plugins, set up once when the root is created; a later change to this prop is not
23
+ * applied. Their tools join `tools`, their decoration and page layers apply to every
24
+ * document, and their commands are placed with `InkCommand` or `useCommand`.
25
+ */
26
+ plugins?: InkPlugin[] | undefined;
21
27
  /**
22
28
  * Adds `data-*` attributes, classes and a screen-reader description to each annotation's
23
29
  * element (see `DecorateAnnotation`). Decorations re-apply whenever the annotations change
@@ -1293,6 +1299,7 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
1293
1299
  })[] | undefined;
1294
1300
  tool: ToolId | undefined;
1295
1301
  tools: Tool<object>[] | undefined;
1302
+ plugins: InkPlugin[] | undefined;
1296
1303
  decorateAnnotation: DecorateAnnotation | undefined;
1297
1304
  fonts: string[] | undefined;
1298
1305
  signatures: SignatureStore | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"InkRoot.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkRoot.vue"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"InkRoot.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkRoot.vue"],"names":[],"mappings":"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"}