@inkbar/vue 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/components/InkFormAlert.js +7 -0
- package/dist/components/InkFormAlert.js.map +1 -0
- package/dist/components/InkFormAlert.vue.d.ts +16 -0
- package/dist/components/InkFormAlert.vue.d.ts.map +1 -0
- package/dist/components/InkFormAlert.vue_vue_type_script_setup_true_lang.js +90 -0
- package/dist/components/InkFormAlert.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/InkRoot.js.map +1 -1
- package/dist/components/InkRoot.vue.d.ts +15 -1
- package/dist/components/InkRoot.vue.d.ts.map +1 -1
- package/dist/components/InkRoot.vue_vue_type_script_setup_true_lang.js +178 -142
- package/dist/components/InkRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/InkSignFieldPopover.js +7 -0
- package/dist/components/InkSignFieldPopover.js.map +1 -0
- package/dist/components/InkSignFieldPopover.vue.d.ts +13 -0
- package/dist/components/InkSignFieldPopover.vue.d.ts.map +1 -0
- package/dist/components/InkSignFieldPopover.vue_vue_type_script_setup_true_lang.js +116 -0
- package/dist/components/InkSignFieldPopover.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/InkSignatureDialog.js.map +1 -1
- package/dist/components/InkSignatureDialog.vue.d.ts +9 -1
- package/dist/components/InkSignatureDialog.vue.d.ts.map +1 -1
- package/dist/components/InkSignatureDialog.vue_vue_type_script_setup_true_lang.js +9 -2
- package/dist/components/InkSignatureDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/InkSignatureMenu.js.map +1 -1
- package/dist/components/InkSignatureMenu.vue.d.ts.map +1 -1
- package/dist/components/InkSignatureMenu.vue_vue_type_script_setup_true_lang.js +72 -159
- package/dist/components/InkSignatureMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/InkViewport.js.map +1 -1
- package/dist/components/InkViewport.vue.d.ts.map +1 -1
- package/dist/components/InkViewport.vue_vue_type_script_setup_true_lang.js +117 -86
- package/dist/components/InkViewport.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/SignatureForm.js.map +1 -1
- package/dist/components/SignatureForm.vue.d.ts +5 -1
- package/dist/components/SignatureForm.vue.d.ts.map +1 -1
- package/dist/components/SignatureForm.vue_vue_type_script_setup_true_lang.js +27 -21
- package/dist/components/SignatureForm.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/SignatureMenuPanel.js +7 -0
- package/dist/components/SignatureMenuPanel.js.map +1 -0
- package/dist/components/SignatureMenuPanel.vue.d.ts +15 -0
- package/dist/components/SignatureMenuPanel.vue.d.ts.map +1 -0
- package/dist/components/SignatureMenuPanel.vue_vue_type_script_setup_true_lang.js +116 -0
- package/dist/components/SignatureMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/composables/useForm.d.ts +18 -0
- package/dist/composables/useForm.d.ts.map +1 -0
- package/dist/composables/useForm.js +17 -0
- package/dist/composables/useForm.js.map +1 -0
- package/dist/context.d.ts +7 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -27
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -114,6 +114,12 @@ Inside `InkRoot`, `useHistory()` returns the same object. Its `batch(fn)` runs `
|
|
|
114
114
|
|
|
115
115
|
`key` identifies the document. Reuse the same key when only the signed URL changes and the document is not reloaded.
|
|
116
116
|
|
|
117
|
+
## Forms
|
|
118
|
+
|
|
119
|
+
`InkViewport` fills the PDF's form fields and runs the form's scripts. `useForm()` returns the fields, their values, and `set` and `reset`. Bind `v-model:fields` to hold the values in your own state. The form never submits, opens a link, sends mail or prints: `@form-action` hands each request to you. `:form-highlight="false"` turns off the field tint, and a change applies to the open document in place.
|
|
120
|
+
|
|
121
|
+
The [forms guide](https://inkbar.dev/docs/guides/forms/) covers controlled fields, form actions, scripts, signature fields and saving values as Gloss.
|
|
122
|
+
|
|
117
123
|
## Thread popover
|
|
118
124
|
|
|
119
125
|
`InkThreadPopover` opens the comment thread of the activated note or text markup (a highlight, underline, strikeout or squiggly). A new note or highlight has no text yet, so the form first writes its own `contents` (labelled "Comment"); after that it adds replies. A highlight with a comment shows a small marker at its end. Replies show in the thread only; the page draws just the note or highlight they answer.
|
|
@@ -161,6 +167,7 @@ The scroll area takes focus. From there:
|
|
|
161
167
|
- With the `highlight` or `redact-area` tool active, `Enter` turns the text selection into a mark.
|
|
162
168
|
- With the `note` tool active, `Enter` places a note at the centre of the current page and opens it.
|
|
163
169
|
- With the `signature` tool active, `Enter` places the chosen signature at the centre of the current page.
|
|
170
|
+
- `Ctrl` or `Cmd` + `Z` undoes. `Ctrl` or `Cmd` + `Shift` + `Z`, or `Ctrl` + `Y`, redoes. `Ctrl` and `Cmd` both work on every platform. The keys do what `InkUndo` and `InkRedo` do, from anywhere in `InkRoot`, toolbars included, and act only while that button is enabled. In a text field or editable content the keys stay with the element, so a field keeps its own text undo. In a `<select>` they are left to the browser, which does nothing with them.
|
|
164
171
|
|
|
165
172
|
On a focused annotation:
|
|
166
173
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkFormAlert.js","names":["_sfc_main","InkFormAlert_vue_vue_type_script_setup_true_lang_default","InkFormAlert_default"],"sources":["../../src/components/InkFormAlert.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A form script's `app.alert`: the message and OK, shown without blocking (no overlay, not\n * modal), so the form stays usable around it. InkViewport mounts it; hosts compose nothing.\n * OK, or Escape while focus is in the alert, closes it, and focus goes back where it was. A\n * press outside leaves it open, and Escape anywhere else stays with the viewer. A new message\n * replaces the one shown.\n */\nimport { FORM_LABELS } from '@inkbar/core/dom'\nimport {\n DialogContent,\n DialogDescription,\n DialogPortal,\n DialogRoot,\n DialogTitle,\n} from 'reka-ui'\nimport { computed, nextTick, useTemplateRef, watch } from 'vue'\n\nconst props = defineProps<{\n /** The message to show (text, not markup); `null` while none is. */\n message: string | null\n /** Counts the times a message was shown, so the same text shown again takes focus again. */\n shown: number\n /** Where focus goes when nothing it can return to is left. */\n fallback?: HTMLElement | null | undefined\n}>()\nconst emit = defineEmits<{ close: [] }>()\n\nconst open = computed(() => props.message !== null)\nconst ok = useTemplateRef<HTMLButtonElement>('ok')\n/** Where focus was when the alert opened. */\nlet returnTo: HTMLElement | null = null\n\nwatch(\n () => props.message !== null,\n (isOpen) => {\n if (!isOpen) return\n const active = document.activeElement\n returnTo = active instanceof HTMLElement && active !== document.body ? active : null\n },\n { flush: 'sync', immediate: true }\n)\n// Another message while one shows: OK takes focus again.\nwatch(\n () => props.shown,\n () => void nextTick(() => ok.value?.focus({ preventScroll: true }))\n)\n\nfunction onOpenAutoFocus(e: Event): void {\n e.preventDefault()\n ok.value?.focus({ preventScroll: true })\n}\n\n/** Set while closing from inside the alert, which is when focus goes back. */\nlet hadFocus = false\nfunction close(): void {\n const content = ok.value?.closest('[data-ink-form-alert]')\n hadFocus = !!content?.contains(document.activeElement)\n emit('close')\n}\n\nfunction onCloseAutoFocus(e: Event): void {\n e.preventDefault()\n if (!hadFocus) return\n hadFocus = false\n const back = returnTo?.isConnected === true ? returnTo : (props.fallback ?? null)\n back?.focus({ preventScroll: true })\n}\n\nfunction onEscape(e: KeyboardEvent): void {\n const content = ok.value?.closest('[data-ink-form-alert]')\n if (!(e.target instanceof Node) || !content?.contains(e.target)) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n close()\n}\n\nfunction onOpenChange(next: boolean): void {\n if (!next) close()\n}\n\n// Reka's typed props stop at its own; ARIA goes through a bound object.\nconst contentAttrs = { role: 'alertdialog' }\n</script>\n\n<template>\n <DialogRoot :open=\"open\" :modal=\"false\" @update:open=\"onOpenChange\">\n <DialogPortal>\n <DialogContent\n class=\"ink-dialog ink-form-alert\"\n data-ink-form-alert\n v-bind=\"contentAttrs\"\n @open-auto-focus=\"onOpenAutoFocus\"\n @close-auto-focus=\"onCloseAutoFocus\"\n @interact-outside.prevent\n @escape-key-down=\"onEscape\"\n >\n <DialogTitle class=\"ink-dialog-title\">{{ FORM_LABELS.alert }}</DialogTitle>\n <!-- Set as text, without the whitespace a template would add around it. -->\n <DialogDescription as-child>\n <p\n class=\"ink-form-alert-message\"\n data-ink-form-alert-message\n v-text=\"props.message\"\n />\n </DialogDescription>\n <div class=\"ink-dialog-actions\">\n <button\n ref=\"ok\"\n type=\"button\"\n class=\"ink-button\"\n data-variant=\"primary\"\n data-ink-form-alert-ok\n @click=\"close\"\n >\n {{ FORM_LABELS.ok }}\n </button>\n </div>\n </DialogContent>\n </DialogPortal>\n </DialogRoot>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** The message to show (text, not markup); `null` while none is. */
|
|
3
|
+
message: string | null;
|
|
4
|
+
/** Counts the times a message was shown, so the same text shown again takes focus again. */
|
|
5
|
+
shown: number;
|
|
6
|
+
/** Where focus goes when nothing it can return to is left. */
|
|
7
|
+
fallback?: HTMLElement | null | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
10
|
+
close: () => any;
|
|
11
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onClose?: () => any;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=InkFormAlert.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkFormAlert.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkFormAlert.vue"],"names":[],"mappings":"AAgJA,KAAK,WAAW,GAAG;IACjB,oEAAoE;IACpE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAA;CAC1C,CAAC;AAsNF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { computed as e, createBlock as t, createElementVNode as n, createTextVNode as r, createVNode as i, defineComponent as a, mergeProps as o, nextTick as s, openBlock as c, toDisplayString as l, unref as u, useTemplateRef as d, watch as f, withCtx as p, withModifiers as m } from "vue";
|
|
2
|
+
import { FORM_LABELS as h } from "@inkbar/core/dom";
|
|
3
|
+
import { DialogContent as g, DialogDescription as _, DialogPortal as v, DialogRoot as y, DialogTitle as b } from "reka-ui";
|
|
4
|
+
//#region src/components/InkFormAlert.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
+
var x = ["textContent"], S = { class: "ink-dialog-actions" }, C = /*@__PURE__*/ a({
|
|
6
|
+
__name: "InkFormAlert",
|
|
7
|
+
props: {
|
|
8
|
+
message: {},
|
|
9
|
+
shown: {},
|
|
10
|
+
fallback: {}
|
|
11
|
+
},
|
|
12
|
+
emits: ["close"],
|
|
13
|
+
setup(a, { emit: C }) {
|
|
14
|
+
let w = a, T = C, E = e(() => w.message !== null), D = d("ok"), O = null;
|
|
15
|
+
f(() => w.message !== null, (e) => {
|
|
16
|
+
if (!e) return;
|
|
17
|
+
let t = document.activeElement;
|
|
18
|
+
O = t instanceof HTMLElement && t !== document.body ? t : null;
|
|
19
|
+
}, {
|
|
20
|
+
flush: "sync",
|
|
21
|
+
immediate: !0
|
|
22
|
+
}), f(() => w.shown, () => void s(() => D.value?.focus({ preventScroll: !0 })));
|
|
23
|
+
function k(e) {
|
|
24
|
+
e.preventDefault(), D.value?.focus({ preventScroll: !0 });
|
|
25
|
+
}
|
|
26
|
+
let A = !1;
|
|
27
|
+
function j() {
|
|
28
|
+
A = !!(D.value?.closest("[data-ink-form-alert]"))?.contains(document.activeElement), T("close");
|
|
29
|
+
}
|
|
30
|
+
function M(e) {
|
|
31
|
+
e.preventDefault(), A && (A = !1, (O?.isConnected === !0 ? O : w.fallback ?? null)?.focus({ preventScroll: !0 }));
|
|
32
|
+
}
|
|
33
|
+
function N(e) {
|
|
34
|
+
let t = D.value?.closest("[data-ink-form-alert]");
|
|
35
|
+
!(e.target instanceof Node) || !t?.contains(e.target) ? e.preventDefault() : (e.preventDefault(), j());
|
|
36
|
+
}
|
|
37
|
+
function P(e) {
|
|
38
|
+
e || j();
|
|
39
|
+
}
|
|
40
|
+
let F = { role: "alertdialog" };
|
|
41
|
+
return (e, a) => (c(), t(u(y), {
|
|
42
|
+
open: E.value,
|
|
43
|
+
modal: !1,
|
|
44
|
+
"onUpdate:open": P
|
|
45
|
+
}, {
|
|
46
|
+
default: p(() => [i(u(v), null, {
|
|
47
|
+
default: p(() => [i(u(g), o({
|
|
48
|
+
class: "ink-dialog ink-form-alert",
|
|
49
|
+
"data-ink-form-alert": ""
|
|
50
|
+
}, F, {
|
|
51
|
+
onOpenAutoFocus: k,
|
|
52
|
+
onCloseAutoFocus: M,
|
|
53
|
+
onInteractOutside: a[0] ||= m(() => {}, ["prevent"]),
|
|
54
|
+
onEscapeKeyDown: N
|
|
55
|
+
}), {
|
|
56
|
+
default: p(() => [
|
|
57
|
+
i(u(b), { class: "ink-dialog-title" }, {
|
|
58
|
+
default: p(() => [r(l(u(h).alert), 1)]),
|
|
59
|
+
_: 1
|
|
60
|
+
}),
|
|
61
|
+
i(u(_), { "as-child": "" }, {
|
|
62
|
+
default: p(() => [n("p", {
|
|
63
|
+
class: "ink-form-alert-message",
|
|
64
|
+
"data-ink-form-alert-message": "",
|
|
65
|
+
textContent: l(w.message)
|
|
66
|
+
}, null, 8, x)]),
|
|
67
|
+
_: 1
|
|
68
|
+
}),
|
|
69
|
+
n("div", S, [n("button", {
|
|
70
|
+
ref_key: "ok",
|
|
71
|
+
ref: D,
|
|
72
|
+
type: "button",
|
|
73
|
+
class: "ink-button",
|
|
74
|
+
"data-variant": "primary",
|
|
75
|
+
"data-ink-form-alert-ok": "",
|
|
76
|
+
onClick: j
|
|
77
|
+
}, l(u(h).ok), 513)])
|
|
78
|
+
]),
|
|
79
|
+
_: 1
|
|
80
|
+
}, 16)]),
|
|
81
|
+
_: 1
|
|
82
|
+
})]),
|
|
83
|
+
_: 1
|
|
84
|
+
}, 8, ["open"]));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
//#endregion
|
|
88
|
+
export { C as default };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=InkFormAlert.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InkFormAlert.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","_unref","_withCtx","_createVNode","_mergeProps","_withModifiers","_createTextVNode","_toDisplayString","_createElementVNode","_hoisted_1","_hoisted_2","InkFormAlert_vue_vue_type_script_setup_true_lang_default","__emit","props","__props","emit","open","computed","ok","useTemplateRef","returnTo","watch","isOpen","active","nextTick","onOpenAutoFocus","hadFocus","close","onCloseAutoFocus","onEscape","content","onOpenChange","next","contentAttrs","openBlock","createBlock","unref","DialogRoot","withCtx","createVNode","DialogPortal","DialogContent","mergeProps","_cache","withModifiers","DialogTitle","createTextVNode","toDisplayString","FORM_LABELS","DialogDescription","createElementVNode"],"sources":["../../src/components/InkFormAlert.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/*\n * A form script's `app.alert`: the message and OK, shown without blocking (no overlay, not\n * modal), so the form stays usable around it. InkViewport mounts it; hosts compose nothing.\n * OK, or Escape while focus is in the alert, closes it, and focus goes back where it was. A\n * press outside leaves it open, and Escape anywhere else stays with the viewer. A new message\n * replaces the one shown.\n */\nimport { FORM_LABELS } from '@inkbar/core/dom'\nimport {\n DialogContent,\n DialogDescription,\n DialogPortal,\n DialogRoot,\n DialogTitle,\n} from 'reka-ui'\nimport { computed, nextTick, useTemplateRef, watch } from 'vue'\n\nconst props = defineProps<{\n /** The message to show (text, not markup); `null` while none is. */\n message: string | null\n /** Counts the times a message was shown, so the same text shown again takes focus again. */\n shown: number\n /** Where focus goes when nothing it can return to is left. */\n fallback?: HTMLElement | null | undefined\n}>()\nconst emit = defineEmits<{ close: [] }>()\n\nconst open = computed(() => props.message !== null)\nconst ok = useTemplateRef<HTMLButtonElement>('ok')\n/** Where focus was when the alert opened. */\nlet returnTo: HTMLElement | null = null\n\nwatch(\n () => props.message !== null,\n (isOpen) => {\n if (!isOpen) return\n const active = document.activeElement\n returnTo = active instanceof HTMLElement && active !== document.body ? active : null\n },\n { flush: 'sync', immediate: true }\n)\n// Another message while one shows: OK takes focus again.\nwatch(\n () => props.shown,\n () => void nextTick(() => ok.value?.focus({ preventScroll: true }))\n)\n\nfunction onOpenAutoFocus(e: Event): void {\n e.preventDefault()\n ok.value?.focus({ preventScroll: true })\n}\n\n/** Set while closing from inside the alert, which is when focus goes back. */\nlet hadFocus = false\nfunction close(): void {\n const content = ok.value?.closest('[data-ink-form-alert]')\n hadFocus = !!content?.contains(document.activeElement)\n emit('close')\n}\n\nfunction onCloseAutoFocus(e: Event): void {\n e.preventDefault()\n if (!hadFocus) return\n hadFocus = false\n const back = returnTo?.isConnected === true ? returnTo : (props.fallback ?? null)\n back?.focus({ preventScroll: true })\n}\n\nfunction onEscape(e: KeyboardEvent): void {\n const content = ok.value?.closest('[data-ink-form-alert]')\n if (!(e.target instanceof Node) || !content?.contains(e.target)) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n close()\n}\n\nfunction onOpenChange(next: boolean): void {\n if (!next) close()\n}\n\n// Reka's typed props stop at its own; ARIA goes through a bound object.\nconst contentAttrs = { role: 'alertdialog' }\n</script>\n\n<template>\n <DialogRoot :open=\"open\" :modal=\"false\" @update:open=\"onOpenChange\">\n <DialogPortal>\n <DialogContent\n class=\"ink-dialog ink-form-alert\"\n data-ink-form-alert\n v-bind=\"contentAttrs\"\n @open-auto-focus=\"onOpenAutoFocus\"\n @close-auto-focus=\"onCloseAutoFocus\"\n @interact-outside.prevent\n @escape-key-down=\"onEscape\"\n >\n <DialogTitle class=\"ink-dialog-title\">{{ FORM_LABELS.alert }}</DialogTitle>\n <!-- Set as text, without the whitespace a template would add around it. -->\n <DialogDescription as-child>\n <p\n class=\"ink-form-alert-message\"\n data-ink-form-alert-message\n v-text=\"props.message\"\n />\n </DialogDescription>\n <div class=\"ink-dialog-actions\">\n <button\n ref=\"ok\"\n type=\"button\"\n class=\"ink-button\"\n data-variant=\"primary\"\n data-ink-form-alert-ok\n @click=\"close\"\n >\n {{ FORM_LABELS.ok }}\n </button>\n </div>\n </DialogContent>\n </DialogPortal>\n </DialogRoot>\n</template>\n"],"mappings":";;;;;;;;;;;;;EAkBA,IAAMc,IAAQC,GAQRC,IAAOH,GAEPI,IAAOC,QAAeJ,EAAM,YAAY,IAAI,GAC5CK,IAAKC,EAAkC,IAAI,GAE7CC,IAA+B;EAYnC,AAVAC,QACQR,EAAM,YAAY,OACvB,MAAW;GACV,IAAI,CAACS,GAAQ;GACb,IAAMC,IAAS,SAAS;GACxB,IAAWA,aAAkB,eAAeA,MAAW,SAAS,OAAOA,IAAS;EAClF,GACA;GAAE,OAAO;GAAQ,WAAW;EAAK,CACnC,GAEAF,QACQR,EAAM,aACN,KAAKW,QAAeN,EAAG,OAAO,MAAM,EAAE,eAAe,GAAK,CAAC,CAAC,CACpE;EAEA,SAASO,EAAgB,GAAgB;GAEvC,AADA,EAAE,eAAe,GACjBP,EAAG,OAAO,MAAM,EAAE,eAAe,GAAK,CAAC;EACzC;EAGA,IAAIQ,IAAW;EACf,SAASC,IAAc;GAGrB,AADA,IAAW,CAAC,EADIT,EAAG,OAAO,QAAQ,uBAAuB,EAAA,EACnC,SAAS,SAAS,aAAa,GACrDH,EAAK,OAAO;EACd;EAEA,SAASa,EAAiB,GAAgB;GAExC,AADA,EAAE,eAAe,GACZF,MACL,IAAW,KACEN,GAAU,gBAAgB,KAAOA,IAAYP,EAAM,YAAY,KAAA,EACtE,MAAM,EAAE,eAAe,GAAK,CAAC;EACrC;EAEA,SAASgB,EAAS,GAAwB;GACxC,IAAMC,IAAUZ,EAAG,OAAO,QAAQ,uBAAuB;GACzD,AAAI,EAAE,EAAE,kBAAkB,SAAS,CAACY,GAAS,SAAS,EAAE,MAAM,IAC5D,EAAE,eAAe,KAGnB,EAAE,eAAe,GACjBH,EAAM;EACR;EAEA,SAASI,EAAa,GAAqB;GACzC,AAAKC,KAAML,EAAM;EACnB;EAGA,IAAMM,IAAe,EAAE,MAAM,cAAc;oBAIzClC,EAAA,GAAAC,EAkCaC,EAAAoC,CAAA,GAAA;GAlCA,MAAMrB,EAAA;GAAO,OAAO;GAAQ,iBAAae;;GACpD,SAAA7B,QAgCe,CAhCfC,EAgCeF,EAAAuC,CAAA,GAAA,MAAA;IA/Bb,SAAAtC,QA8BgB,CA9BhBC,EA8BgBF,EAAAwC,CAAA,GA9BhBrC,EA8BgB;KA7Bd,OAAM;KACN,uBAAA;IACQ,GAAA6B,GAAY;KACF;KACC;KAClB,mBAAgB,AAAAU,EAAA,OAAAtC,QAAjB,CAAA,GAAyB,CAAA,SAAA,CAAA;KACxB,iBAAiBwB;;KAElB,SAAA3B,QAA2E;MAA3EC,EAA2EF,EAAA4C,CAAA,GAAA,EAA9D,OAAM,mBAAkB,GAAA;OAAC,SAAA3C,QAAuB,CAApBI,EAAAC,EAAAN,EAAA+C,CAAA,CAAW,CAAC,KAAK,GAAA,CAAA,CAAA,CAAA;;;MAE1D7C,EAMoBF,EAAAgD,CAAA,GAAA,EAND,YAAA,GAAQ,GAAA;OACzB,SAAA/C,QAIE,CAJFM,EAIE,KAAA;QAHA,OAAM;QACN,+BAAA;QACA,aAAAD,EAAQM,EAAM,OAAO;;;;MAGzBL,EAWM,OAXNE,GAWM,CAVJF,EASS,UAAA;OARH,SAAA;OAAJ,KAAIU;OACJ,MAAK;OACL,OAAM;OACN,gBAAa;OACb,0BAAA;OACC,SAAOS;MAEL,GAAApB,EAAAN,EAAA+C,CAAA,CAAW,CAAC,EAAE,GAAA,GAAA,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 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
|
+
{"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 attachHistoryKeys,\n createPluginHost,\n createSearchController,\n createToolOptions,\n handleFindKey,\n pageSelectionText,\n viewerSearchOf,\n cssVariableDefaults,\n FONT_FAMILIES,\n keepHistoryFocus,\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 FieldsChange,\n FieldValues,\n FormAction,\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 /**\n * Pass (with `v-model:fields`) to put the form's field values under the host's control. A\n * commit shows at once and is reported after; the host's push wins. The mode is fixed when\n * a document opens, as for `annotations`.\n */\n fields?: FieldValues | undefined\n /** Tints fillable form fields. Default true. A change applies to the open document in place. */\n formHighlight?: boolean | 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 fields: undefined,\n formHighlight: 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 /** Committed field values, in full, with what changed them. */\n 'update:fields': [next: FieldValues, change: FieldsChange]\n /** The form asked to submit, open a link, send mail, print or prompt. Inkbar does none. */\n 'form-action': [action: FormAction]\n 'update:tool': [tool: ToolId]\n 'update:page': [page: number]\n 'update:zoom': [zoom: ZoomValue]\n ready: [doc: InkDocument]\n /**\n * Opening failed, or later a form script failed or a signature field refused its signing.\n * An error from while the document opened comes after `ready`.\n */\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)\nconst fieldsControlled = computed(() => props.fields !== 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\nlet stopFormErrors: () => void = noop\nfunction release(): void {\n stopFormErrors()\n stopFormErrors = noop\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 fields: fieldsControlled.value\n ? {\n mode: 'controlled',\n initial: props.fields ?? {},\n onChange: (next, change) => emit('update:fields', next, change),\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 // The same for the fields; a document whose fields opened uncontrolled ignores the push.\n if (props.fields !== undefined) doc.form.setControlledValue(props.fields)\n // A form script that fails leaves the form fillable; the failure goes to `error`. Subscribed\n // before anyone hears of the document, so the form replays the errors from while it opened\n // here, not to a listener added in `ready`; they are held until `ready` has gone out.\n let held: InkError[] | undefined = []\n stopFormErrors = doc.form.on('error', (e) => {\n if (held) held.push(e)\n else emit('error', e)\n })\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 const early = held\n held = undefined\n if (gen === generation) for (const e of early) emit('error', e)\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// The undo and redo keys anywhere in the root do what InkUndo and InkRedo do. When either\n// button undoes or redoes the last step and disables itself, focus goes back to the document.\n// The find key listens here too.\nwatch(\n rootEl,\n (el, _prev, onCleanup) => {\n if (!el) return\n const stopKeys = attachHistoryKeys(el, () => ({\n canUndo: inkHistory.canUndo.value,\n canRedo: inkHistory.canRedo.value,\n undo: inkHistory.undo,\n redo: inkHistory.redo,\n }))\n const stopFocus = keepHistoryFocus(el, () =>\n el.querySelector<HTMLElement>('[data-ink-scroll]')\n )\n // Not a template listener: Vue leaves those on the element when it unmounts.\n el.addEventListener('keydown', onKeydown)\n onCleanup(() => {\n stopKeys()\n stopFocus()\n el.removeEventListener('keydown', onKeydown)\n })\n },\n { immediate: true }\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/** 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.fields,\n (next) => {\n if (next) document.value?.form.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 formHighlight: computed(() => props.formHighlight),\n reportFormAction: (a) => emit('form-action', a),\n reportError: (e) => emit('error', e),\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,4 +1,4 @@
|
|
|
1
|
-
import { InkError, Annotation, AnnotationChange, AnnotationDefaults, DocumentSource, InkDocument, PdfAssets, SignatureStore } from '@inkbar/core';
|
|
1
|
+
import { InkError, Annotation, AnnotationChange, AnnotationDefaults, DocumentSource, FieldsChange, FieldValues, FormAction, InkDocument, PdfAssets, SignatureStore } from '@inkbar/core';
|
|
2
2
|
import { InkHistory, InkStatus } from '../context';
|
|
3
3
|
import { DecorateAnnotation, InkPlugin, Tool, ToolId, ZoomValue } from '@inkbar/core/dom';
|
|
4
4
|
type __VLS_Props = {
|
|
@@ -12,6 +12,14 @@ type __VLS_Props = {
|
|
|
12
12
|
* document opens: passing or dropping this prop later takes effect on the next open.
|
|
13
13
|
*/
|
|
14
14
|
annotations?: Annotation[] | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Pass (with `v-model:fields`) to put the form's field values under the host's control. A
|
|
17
|
+
* commit shows at once and is reported after; the host's push wins. The mode is fixed when
|
|
18
|
+
* a document opens, as for `annotations`.
|
|
19
|
+
*/
|
|
20
|
+
fields?: FieldValues | undefined;
|
|
21
|
+
/** Tints fillable form fields. Default true. A change applies to the open document in place. */
|
|
22
|
+
formHighlight?: boolean | undefined;
|
|
15
23
|
tool?: ToolId | undefined;
|
|
16
24
|
/**
|
|
17
25
|
* Host tools, added to the built-ins; one with a built-in's id replaces it. Read when a
|
|
@@ -469,6 +477,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
|
|
|
469
477
|
custom?: Record<string, unknown> | undefined;
|
|
470
478
|
})[]) => any;
|
|
471
479
|
change: (change: AnnotationChange) => any;
|
|
480
|
+
"update:fields": (next: Readonly<Record<string, import('@inkbar/core').FieldValue>>, change: FieldsChange) => any;
|
|
481
|
+
"form-action": (action: FormAction) => any;
|
|
472
482
|
"update:tool": (tool: ToolId) => any;
|
|
473
483
|
"update:page": (page: number) => any;
|
|
474
484
|
"update:zoom": (zoom: ZoomValue) => any;
|
|
@@ -882,6 +892,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
|
|
|
882
892
|
custom?: Record<string, unknown> | undefined;
|
|
883
893
|
})[]) => any;
|
|
884
894
|
onChange?: (change: AnnotationChange) => any;
|
|
895
|
+
"onUpdate:fields"?: (next: Readonly<Record<string, import('@inkbar/core').FieldValue>>, change: FieldsChange) => any;
|
|
896
|
+
"onForm-action"?: (action: FormAction) => any;
|
|
885
897
|
"onUpdate:tool"?: (tool: ToolId) => any;
|
|
886
898
|
"onUpdate:page"?: (page: number) => any;
|
|
887
899
|
"onUpdate:zoom"?: (zoom: ZoomValue) => any;
|
|
@@ -1297,6 +1309,8 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {
|
|
|
1297
1309
|
intent?: "FreeText" | "FreeTextCallout" | "FreeTextTypeWriter" | "LineArrow" | "LineDimension" | "PolygonCloud" | "PolygonDimension" | "PolyLineDimension" | (string & {}) | undefined;
|
|
1298
1310
|
custom?: Record<string, unknown> | undefined;
|
|
1299
1311
|
})[] | undefined;
|
|
1312
|
+
fields: Readonly<Record<string, import('@inkbar/core').FieldValue>> | undefined;
|
|
1313
|
+
formHighlight: boolean | undefined;
|
|
1300
1314
|
tool: ToolId | undefined;
|
|
1301
1315
|
tools: Tool<object>[] | undefined;
|
|
1302
1316
|
plugins: InkPlugin[] | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InkRoot.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkRoot.vue"],"names":[],"mappings":"AA+
|
|
1
|
+
{"version":3,"file":"InkRoot.vue.d.ts","sourceRoot":"","sources":["../../src/components/InkRoot.vue"],"names":[],"mappings":"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"}
|