@inkbar/core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -49
- package/README.md +29 -5
- package/dist/dom.d.mts +47 -4
- package/dist/dom.d.mts.map +1 -1
- package/dist/dom.mjs +317 -317
- package/dist/dom.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -1
package/dist/dom.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { B as mergeLineRects, F as confidenceBucket, H as readonly, K as invertMatrix, L as cssVariableDefaults, Q as rectFromPoints, U as writable, W as applyMatrix, a as acceptSuggestion, at as InkError, et as rectsIntersect, i as threadRoot, nt as translateRect, o as openDocument, ot as isInkError, r as threadOf, t as isAbandonedNote, tt as translateQuad } from "./threads-BtvtrAhL.mjs";
|
|
2
|
+
import { ChevronLeft, ChevronRight, Hand, Highlighter, ICON_ATTRIBUTES, MessageSquare, MousePointer2, Redo2, Square, Undo2, ZoomIn, ZoomOut } from "@inkbar/icons";
|
|
2
3
|
//#region src/dom/transform.ts
|
|
3
4
|
/** Folds any integer multiple of 90 onto 0, 90, 180 or 270. Throws `RangeError` otherwise. */
|
|
4
5
|
function normalizeRotation(deg) {
|
|
@@ -2497,7 +2498,7 @@ function h(tag, attrs = {}, ...children) {
|
|
|
2497
2498
|
el.append(...children);
|
|
2498
2499
|
return el;
|
|
2499
2500
|
}
|
|
2500
|
-
function button(label, onClick) {
|
|
2501
|
+
function button$1(label, onClick) {
|
|
2501
2502
|
const b = h("button", { type: "button" }, label);
|
|
2502
2503
|
b.addEventListener("click", onClick);
|
|
2503
2504
|
return b;
|
|
@@ -2757,11 +2758,11 @@ function threadItem(a) {
|
|
|
2757
2758
|
function createThreadForm(n, actions) {
|
|
2758
2759
|
const textarea = h("textarea", { rows: "2" });
|
|
2759
2760
|
const send = h("button", { type: "submit" });
|
|
2760
|
-
const remove = button("Delete", actions.remove);
|
|
2761
|
+
const remove = button$1("Delete", actions.remove);
|
|
2761
2762
|
const element = h("form", {
|
|
2762
2763
|
class: "ink-thread-reply",
|
|
2763
2764
|
"data-ink-thread-reply": ""
|
|
2764
|
-
}, textarea, h("div", { class: "ink-thread-actions" }, send, remove, button("Close", actions.close)));
|
|
2765
|
+
}, textarea, h("div", { class: "ink-thread-actions" }, send, remove, button$1("Close", actions.close)));
|
|
2765
2766
|
const syncSend = () => {
|
|
2766
2767
|
send.disabled = !textarea.value.trim();
|
|
2767
2768
|
};
|
|
@@ -2835,7 +2836,7 @@ function createSuggestionCard(opts) {
|
|
|
2835
2836
|
popover.panel.replaceChildren(h("p", { class: "ink-suggestion-reason" }, h("strong", {}, s?.reason ?? "Suggested redaction"), ...bucket ? [h("span", {
|
|
2836
2837
|
class: "ink-suggestion-confidence",
|
|
2837
2838
|
"data-confidence": bucket
|
|
2838
|
-
}, `${bucket} confidence`)] : []), ...s?.explanation ? [h("p", { class: "ink-suggestion-explanation" }, s.explanation)] : [], h("div", { class: "ink-suggestion-actions" }, button("Accept", accept), ...a.locked ? [] : [button("Dismiss", dismiss)]));
|
|
2839
|
+
}, `${bucket} confidence`)] : []), ...s?.explanation ? [h("p", { class: "ink-suggestion-explanation" }, s.explanation)] : [], h("div", { class: "ink-suggestion-actions" }, button$1("Accept", accept), ...a.locked ? [] : [button$1("Dismiss", dismiss)]));
|
|
2839
2840
|
}
|
|
2840
2841
|
function close() {
|
|
2841
2842
|
activated.set(null);
|
|
@@ -2864,315 +2865,14 @@ function createSuggestionCard(opts) {
|
|
|
2864
2865
|
} };
|
|
2865
2866
|
}
|
|
2866
2867
|
//#endregion
|
|
2867
|
-
//#region src/dom/viewer/icons.ts
|
|
2868
|
-
const ICONS = {
|
|
2869
|
-
hand: [
|
|
2870
|
-
["path", { d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }],
|
|
2871
|
-
["path", { d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" }],
|
|
2872
|
-
["path", { d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8" }],
|
|
2873
|
-
["path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15" }]
|
|
2874
|
-
],
|
|
2875
|
-
"mouse-pointer-2": [["path", { d: "M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z" }]],
|
|
2876
|
-
highlighter: [["path", { d: "m9 11-6 6v3h9l3-3" }], ["path", { d: "m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" }]],
|
|
2877
|
-
square: [["rect", {
|
|
2878
|
-
width: "18",
|
|
2879
|
-
height: "18",
|
|
2880
|
-
x: "3",
|
|
2881
|
-
y: "3",
|
|
2882
|
-
rx: "2"
|
|
2883
|
-
}]],
|
|
2884
|
-
"message-square": [["path", { d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" }]],
|
|
2885
|
-
"zoom-in": [
|
|
2886
|
-
["circle", {
|
|
2887
|
-
cx: "11",
|
|
2888
|
-
cy: "11",
|
|
2889
|
-
r: "8"
|
|
2890
|
-
}],
|
|
2891
|
-
["line", {
|
|
2892
|
-
x1: "21",
|
|
2893
|
-
x2: "16.65",
|
|
2894
|
-
y1: "21",
|
|
2895
|
-
y2: "16.65"
|
|
2896
|
-
}],
|
|
2897
|
-
["line", {
|
|
2898
|
-
x1: "11",
|
|
2899
|
-
x2: "11",
|
|
2900
|
-
y1: "8",
|
|
2901
|
-
y2: "14"
|
|
2902
|
-
}],
|
|
2903
|
-
["line", {
|
|
2904
|
-
x1: "8",
|
|
2905
|
-
x2: "14",
|
|
2906
|
-
y1: "11",
|
|
2907
|
-
y2: "11"
|
|
2908
|
-
}]
|
|
2909
|
-
],
|
|
2910
|
-
"zoom-out": [
|
|
2911
|
-
["circle", {
|
|
2912
|
-
cx: "11",
|
|
2913
|
-
cy: "11",
|
|
2914
|
-
r: "8"
|
|
2915
|
-
}],
|
|
2916
|
-
["line", {
|
|
2917
|
-
x1: "21",
|
|
2918
|
-
x2: "16.65",
|
|
2919
|
-
y1: "21",
|
|
2920
|
-
y2: "16.65"
|
|
2921
|
-
}],
|
|
2922
|
-
["line", {
|
|
2923
|
-
x1: "8",
|
|
2924
|
-
x2: "14",
|
|
2925
|
-
y1: "11",
|
|
2926
|
-
y2: "11"
|
|
2927
|
-
}]
|
|
2928
|
-
]
|
|
2929
|
-
};
|
|
2930
|
-
const SVG = "http://www.w3.org/2000/svg";
|
|
2931
|
-
/** An 18px Lucide icon, hidden from assistive technology: its button carries the name. */
|
|
2932
|
-
function icon(name) {
|
|
2933
|
-
const svg = document.createElementNS(SVG, "svg");
|
|
2934
|
-
const attrs = {
|
|
2935
|
-
xmlns: SVG,
|
|
2936
|
-
width: "18",
|
|
2937
|
-
height: "18",
|
|
2938
|
-
viewBox: "0 0 24 24",
|
|
2939
|
-
fill: "none",
|
|
2940
|
-
stroke: "currentColor",
|
|
2941
|
-
"stroke-width": "2",
|
|
2942
|
-
"stroke-linecap": "round",
|
|
2943
|
-
"stroke-linejoin": "round",
|
|
2944
|
-
"aria-hidden": "true"
|
|
2945
|
-
};
|
|
2946
|
-
for (const [k, v] of Object.entries(attrs)) svg.setAttribute(k, v);
|
|
2947
|
-
for (const [tag, shape] of ICONS[name]) {
|
|
2948
|
-
const child = document.createElementNS(SVG, tag);
|
|
2949
|
-
for (const [k, v] of Object.entries(shape)) child.setAttribute(k, v);
|
|
2950
|
-
svg.append(child);
|
|
2951
|
-
}
|
|
2952
|
-
return svg;
|
|
2953
|
-
}
|
|
2954
|
-
//#endregion
|
|
2955
|
-
//#region src/dom/viewer/toolbar.ts
|
|
2956
|
-
const TOOLS = [
|
|
2957
|
-
["view", "hand"],
|
|
2958
|
-
["select", "mouse-pointer-2"],
|
|
2959
|
-
["highlight", "highlighter"],
|
|
2960
|
-
["redact-area", "square"],
|
|
2961
|
-
["note", "message-square"]
|
|
2962
|
-
];
|
|
2963
|
-
/** A toolbar button with an icon; the label is its accessible name and tooltip. */
|
|
2964
|
-
function iconButton(label, attrs, name, onClick) {
|
|
2965
|
-
const b = h("button", {
|
|
2966
|
-
type: "button",
|
|
2967
|
-
...attrs,
|
|
2968
|
-
"aria-label": label,
|
|
2969
|
-
title: label
|
|
2970
|
-
});
|
|
2971
|
-
b.append(icon(name));
|
|
2972
|
-
b.addEventListener("click", onClick);
|
|
2973
|
-
return b;
|
|
2974
|
-
}
|
|
2975
|
-
const group = (name, label, ...children) => h("div", {
|
|
2976
|
-
class: "ink-toolbar-group",
|
|
2977
|
-
"data-ink-toolbar-group": name,
|
|
2978
|
-
role: "group",
|
|
2979
|
-
"aria-label": label
|
|
2980
|
-
}, ...children);
|
|
2981
|
-
function createToolbar() {
|
|
2982
|
-
let stops = [];
|
|
2983
|
-
let bound = null;
|
|
2984
|
-
const watch = (r, fn) => {
|
|
2985
|
-
fn(r.get());
|
|
2986
|
-
stops.push(r.subscribe(fn));
|
|
2987
|
-
};
|
|
2988
|
-
const toolButtons = TOOLS.map(([id, name]) => iconButton(TOOL_LABELS[id], {
|
|
2989
|
-
class: "ink-tool-button",
|
|
2990
|
-
"data-ink-tool-button": "",
|
|
2991
|
-
"data-tool": id
|
|
2992
|
-
}, name, () => bound?.tools.set(id)));
|
|
2993
|
-
const showTool = (active) => {
|
|
2994
|
-
for (const b of toolButtons) {
|
|
2995
|
-
const on = b.dataset.tool === active;
|
|
2996
|
-
b.dataset.state = on ? "on" : "off";
|
|
2997
|
-
b.setAttribute("aria-pressed", String(on));
|
|
2998
|
-
}
|
|
2999
|
-
};
|
|
3000
|
-
const zoomOut = iconButton("Zoom out", {
|
|
3001
|
-
class: "ink-zoom-out",
|
|
3002
|
-
"data-ink-zoom-out": ""
|
|
3003
|
-
}, "zoom-out", () => bound?.viewport.zoomBy(.8));
|
|
3004
|
-
const zoomIn = iconButton("Zoom in", {
|
|
3005
|
-
class: "ink-zoom-in",
|
|
3006
|
-
"data-ink-zoom-in": ""
|
|
3007
|
-
}, "zoom-in", () => bound?.viewport.zoomBy(1.25));
|
|
3008
|
-
const custom = h("option", { value: "custom" });
|
|
3009
|
-
const zoomSelect = h("select", {
|
|
3010
|
-
class: "ink-zoom-select",
|
|
3011
|
-
"data-ink-zoom-select": "",
|
|
3012
|
-
"aria-label": "Zoom"
|
|
3013
|
-
}, h("option", { value: "fit-width" }, "Fit width"), h("option", { value: "fit-page" }, "Fit page"), ...ZOOM_LEVELS.map((l) => h("option", { value: String(l) }, `${Math.round(l * 100)}%`)));
|
|
3014
|
-
zoomSelect.addEventListener("change", () => {
|
|
3015
|
-
const v = zoomSelect.value;
|
|
3016
|
-
if (v === "fit-width" || v === "fit-page") bound?.viewport.setZoom(v);
|
|
3017
|
-
else if (v !== "custom") bound?.viewport.setZoom(Number(v));
|
|
3018
|
-
});
|
|
3019
|
-
const pageInput = h("input", {
|
|
3020
|
-
class: "ink-page-input",
|
|
3021
|
-
"data-ink-page-input": "",
|
|
3022
|
-
type: "number",
|
|
3023
|
-
inputmode: "numeric",
|
|
3024
|
-
"aria-label": "Page",
|
|
3025
|
-
min: "1"
|
|
3026
|
-
});
|
|
3027
|
-
const commitPage = () => {
|
|
3028
|
-
if (!bound) return;
|
|
3029
|
-
const { viewport } = bound;
|
|
3030
|
-
const n = Number(pageInput.value);
|
|
3031
|
-
const current = viewport.currentPage.get();
|
|
3032
|
-
if (Number.isInteger(n) && n >= 1 && n <= bound.doc.pages.length) {
|
|
3033
|
-
if (n - 1 !== current) viewport.scrollToPage(n - 1);
|
|
3034
|
-
} else pageInput.value = String(current + 1);
|
|
3035
|
-
};
|
|
3036
|
-
pageInput.addEventListener("change", commitPage);
|
|
3037
|
-
pageInput.addEventListener("keydown", (e) => {
|
|
3038
|
-
if (e.key !== "Enter" || e.defaultPrevented) return;
|
|
3039
|
-
e.preventDefault();
|
|
3040
|
-
commitPage();
|
|
3041
|
-
});
|
|
3042
|
-
const pageCount = h("span", {
|
|
3043
|
-
class: "ink-page-count",
|
|
3044
|
-
"data-ink-page-count": "",
|
|
3045
|
-
"aria-live": "polite"
|
|
3046
|
-
});
|
|
3047
|
-
const controls = [
|
|
3048
|
-
...toolButtons,
|
|
3049
|
-
zoomOut,
|
|
3050
|
-
zoomSelect,
|
|
3051
|
-
zoomIn,
|
|
3052
|
-
pageInput
|
|
3053
|
-
];
|
|
3054
|
-
const element = h("div", {
|
|
3055
|
-
class: "ink-toolbar ink-default-toolbar",
|
|
3056
|
-
"data-ink-toolbar": "",
|
|
3057
|
-
"data-ink-default-toolbar": "",
|
|
3058
|
-
role: "toolbar",
|
|
3059
|
-
"aria-label": "Document tools",
|
|
3060
|
-
"aria-orientation": "horizontal"
|
|
3061
|
-
}, group("tools", "Tools", ...toolButtons), group("zoom", "Zoom", zoomOut, zoomSelect, zoomIn), group("page", "Page", pageInput, pageCount));
|
|
3062
|
-
const roving = rovingTabindex(element);
|
|
3063
|
-
function stop() {
|
|
3064
|
-
for (const s of stops) s();
|
|
3065
|
-
stops = [];
|
|
3066
|
-
bound = null;
|
|
3067
|
-
}
|
|
3068
|
-
function unbind() {
|
|
3069
|
-
stop();
|
|
3070
|
-
for (const c of controls) c.disabled = true;
|
|
3071
|
-
showTool(null);
|
|
3072
|
-
custom.remove();
|
|
3073
|
-
zoomSelect.value = "fit-width";
|
|
3074
|
-
pageInput.value = "";
|
|
3075
|
-
pageCount.textContent = "";
|
|
3076
|
-
roving.sync();
|
|
3077
|
-
}
|
|
3078
|
-
unbind();
|
|
3079
|
-
return {
|
|
3080
|
-
element,
|
|
3081
|
-
bind(doc, viewport, tools) {
|
|
3082
|
-
stop();
|
|
3083
|
-
bound = {
|
|
3084
|
-
doc,
|
|
3085
|
-
viewport,
|
|
3086
|
-
tools
|
|
3087
|
-
};
|
|
3088
|
-
for (const c of controls) c.disabled = false;
|
|
3089
|
-
watch(tools.active, showTool);
|
|
3090
|
-
watch(viewport.zoom, (z) => {
|
|
3091
|
-
const level = typeof z === "number" ? ZOOM_LEVELS.find((l) => Math.abs(l - z) < .001) : void 0;
|
|
3092
|
-
if (typeof z === "number" && level === void 0) {
|
|
3093
|
-
custom.textContent = `${Math.round(z * 100)}%`;
|
|
3094
|
-
zoomSelect.append(custom);
|
|
3095
|
-
zoomSelect.value = "custom";
|
|
3096
|
-
} else {
|
|
3097
|
-
custom.remove();
|
|
3098
|
-
zoomSelect.value = typeof z === "number" ? String(level) : z;
|
|
3099
|
-
}
|
|
3100
|
-
});
|
|
3101
|
-
const count = doc.pages.length;
|
|
3102
|
-
pageInput.max = String(count);
|
|
3103
|
-
pageCount.textContent = `of ${count}`;
|
|
3104
|
-
watch(viewport.currentPage, (p) => {
|
|
3105
|
-
pageInput.value = String(p + 1);
|
|
3106
|
-
});
|
|
3107
|
-
roving.sync();
|
|
3108
|
-
},
|
|
3109
|
-
unbind,
|
|
3110
|
-
destroy() {
|
|
3111
|
-
stop();
|
|
3112
|
-
roving.destroy();
|
|
3113
|
-
element.remove();
|
|
3114
|
-
}
|
|
3115
|
-
};
|
|
3116
|
-
}
|
|
3117
|
-
/** Roving items, as in the bindings' Radix toolbar: buttons only. */
|
|
3118
|
-
const ITEMS = "button";
|
|
3119
|
-
/**
|
|
3120
|
-
* One Tab stop across the enabled buttons in `bar`: the current one has `tabindex="0"`, the
|
|
3121
|
-
* rest `-1`. ArrowLeft and ArrowRight move between them and wrap, Home and End go to the ends.
|
|
3122
|
-
* Inputs, selects and textareas, built in or added by the host, keep their own tabindex (a
|
|
3123
|
-
* Tab stop of their own by default) and their own arrow, Home and End keys.
|
|
3124
|
-
*/
|
|
3125
|
-
function rovingTabindex(bar) {
|
|
3126
|
-
let current = null;
|
|
3127
|
-
const items = () => [...bar.querySelectorAll(ITEMS)].filter((el) => !el.disabled && !el.closest("[hidden]"));
|
|
3128
|
-
const sync = () => {
|
|
3129
|
-
const list = items();
|
|
3130
|
-
if (!current || !list.includes(current)) current = list[0] ?? null;
|
|
3131
|
-
for (const el of bar.querySelectorAll(ITEMS)) el.tabIndex = el === current ? 0 : -1;
|
|
3132
|
-
};
|
|
3133
|
-
const onFocusIn = (e) => {
|
|
3134
|
-
const target = e.target;
|
|
3135
|
-
if (!items().includes(target)) return;
|
|
3136
|
-
current = target;
|
|
3137
|
-
sync();
|
|
3138
|
-
};
|
|
3139
|
-
const onKeyDown = (e) => {
|
|
3140
|
-
if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
3141
|
-
const list = items();
|
|
3142
|
-
const i = list.indexOf(e.target);
|
|
3143
|
-
if (i < 0) return;
|
|
3144
|
-
const last = list.length - 1;
|
|
3145
|
-
const next = e.key === "ArrowRight" ? i === last ? 0 : i + 1 : e.key === "ArrowLeft" ? i === 0 ? last : i - 1 : e.key === "Home" ? 0 : e.key === "End" ? last : -1;
|
|
3146
|
-
if (next < 0) return;
|
|
3147
|
-
e.preventDefault();
|
|
3148
|
-
list[next].focus();
|
|
3149
|
-
};
|
|
3150
|
-
const observer = new MutationObserver(sync);
|
|
3151
|
-
observer.observe(bar, {
|
|
3152
|
-
childList: true,
|
|
3153
|
-
subtree: true,
|
|
3154
|
-
attributes: true,
|
|
3155
|
-
attributeFilter: ["disabled", "hidden"]
|
|
3156
|
-
});
|
|
3157
|
-
bar.addEventListener("focusin", onFocusIn);
|
|
3158
|
-
bar.addEventListener("keydown", onKeyDown);
|
|
3159
|
-
sync();
|
|
3160
|
-
return {
|
|
3161
|
-
sync,
|
|
3162
|
-
destroy() {
|
|
3163
|
-
observer.disconnect();
|
|
3164
|
-
bar.removeEventListener("focusin", onFocusIn);
|
|
3165
|
-
bar.removeEventListener("keydown", onKeyDown);
|
|
3166
|
-
}
|
|
3167
|
-
};
|
|
3168
|
-
}
|
|
3169
|
-
//#endregion
|
|
3170
2868
|
//#region src/dom/viewer/create-viewer.ts
|
|
3171
2869
|
function createViewer(el, opts) {
|
|
3172
|
-
const { source: initial, assets, password, defaults, history,
|
|
2870
|
+
const { source: initial, assets, password, defaults, history, notes: withNotes = true, suggestions: withSuggestions = true, onReady, onError, onActivate, onBuild, onPageMount, onPageUnmount, onPageError, ...view } = opts;
|
|
3173
2871
|
const status = writable({ state: "idle" });
|
|
3174
2872
|
const current = writable(null);
|
|
3175
2873
|
const engine = writable(null);
|
|
2874
|
+
const viewport = writable(null);
|
|
2875
|
+
const tools = writable(null);
|
|
3176
2876
|
/** The activated annotation: the note thread and the suggestion card open from it. */
|
|
3177
2877
|
const activated = writable(null);
|
|
3178
2878
|
const root = h("div", {
|
|
@@ -3191,8 +2891,6 @@ function createViewer(el, opts) {
|
|
|
3191
2891
|
"data-ink-viewport-root": "",
|
|
3192
2892
|
"data-state": "idle"
|
|
3193
2893
|
}, scroll);
|
|
3194
|
-
const toolbar = withToolbar ? createToolbar() : null;
|
|
3195
|
-
if (toolbar) root.append(toolbar.element);
|
|
3196
2894
|
root.append(viewportRoot);
|
|
3197
2895
|
el.append(root);
|
|
3198
2896
|
const focusReturn = createFocusReturn({
|
|
@@ -3255,9 +2953,10 @@ function createViewer(el, opts) {
|
|
|
3255
2953
|
activated.set(id);
|
|
3256
2954
|
onActivate?.(id);
|
|
3257
2955
|
},
|
|
3258
|
-
onBuild: (
|
|
3259
|
-
|
|
3260
|
-
|
|
2956
|
+
onBuild: (vp, tc) => {
|
|
2957
|
+
tools.set(tc);
|
|
2958
|
+
viewport.set(vp);
|
|
2959
|
+
onBuild?.(vp, tc);
|
|
3261
2960
|
},
|
|
3262
2961
|
onPageMount: (page, mount, host) => {
|
|
3263
2962
|
hosts.set(page, host);
|
|
@@ -3290,7 +2989,8 @@ function createViewer(el, opts) {
|
|
|
3290
2989
|
activated.set(null);
|
|
3291
2990
|
engine.set(null);
|
|
3292
2991
|
mounted.destroy();
|
|
3293
|
-
|
|
2992
|
+
viewport.set(null);
|
|
2993
|
+
tools.set(null);
|
|
3294
2994
|
};
|
|
3295
2995
|
}
|
|
3296
2996
|
let loads = 0;
|
|
@@ -3346,19 +3046,319 @@ function createViewer(el, opts) {
|
|
|
3346
3046
|
status: readonly(status),
|
|
3347
3047
|
document: readonly(current),
|
|
3348
3048
|
engine: readonly(engine),
|
|
3349
|
-
|
|
3049
|
+
viewport: readonly(viewport),
|
|
3050
|
+
tools: readonly(tools),
|
|
3350
3051
|
open,
|
|
3351
3052
|
destroy() {
|
|
3352
3053
|
if (destroyed) return;
|
|
3353
3054
|
destroyed = true;
|
|
3354
3055
|
loads++;
|
|
3355
3056
|
close();
|
|
3356
|
-
toolbar?.destroy();
|
|
3357
3057
|
root.remove();
|
|
3358
3058
|
}
|
|
3359
3059
|
};
|
|
3360
3060
|
}
|
|
3361
3061
|
//#endregion
|
|
3362
|
-
|
|
3062
|
+
//#region src/dom/viewer/controls.ts
|
|
3063
|
+
/**
|
|
3064
|
+
* Runs `connect` for every value `source` takes while it is set, undoing the previous run's
|
|
3065
|
+
* subscriptions first, and disables `control` while it is `null`.
|
|
3066
|
+
*/
|
|
3067
|
+
function follow(source, control, connect, reset) {
|
|
3068
|
+
let stops = [];
|
|
3069
|
+
const watch = (r, fn) => {
|
|
3070
|
+
fn(r.get());
|
|
3071
|
+
stops.push(r.subscribe(fn));
|
|
3072
|
+
};
|
|
3073
|
+
const run = (value) => {
|
|
3074
|
+
for (const s of stops) s();
|
|
3075
|
+
stops = [];
|
|
3076
|
+
control.disabled = value === null;
|
|
3077
|
+
if (value === null) reset?.();
|
|
3078
|
+
else connect(value, watch);
|
|
3079
|
+
};
|
|
3080
|
+
run(source.get());
|
|
3081
|
+
source.subscribe(run);
|
|
3082
|
+
}
|
|
3083
|
+
const SVG = "http://www.w3.org/2000/svg";
|
|
3084
|
+
const TOOL_ICONS = {
|
|
3085
|
+
view: Hand,
|
|
3086
|
+
select: MousePointer2,
|
|
3087
|
+
highlight: Highlighter,
|
|
3088
|
+
"redact-area": Square,
|
|
3089
|
+
note: MessageSquare
|
|
3090
|
+
};
|
|
3091
|
+
/**
|
|
3092
|
+
* An `<svg>` drawing `icon` from `@inkbar/icons`, hidden from assistive technology: the
|
|
3093
|
+
* control it sits in carries the name. The theme sizes it with `--ink-icon-size`.
|
|
3094
|
+
*/
|
|
3095
|
+
function createIcon(icon) {
|
|
3096
|
+
const svg = document.createElementNS(SVG, "svg");
|
|
3097
|
+
const attrs = {
|
|
3098
|
+
...ICON_ATTRIBUTES,
|
|
3099
|
+
width: "18",
|
|
3100
|
+
height: "18",
|
|
3101
|
+
class: "ink-icon",
|
|
3102
|
+
"aria-hidden": "true"
|
|
3103
|
+
};
|
|
3104
|
+
for (const [k, v] of Object.entries(attrs)) svg.setAttribute(k, v);
|
|
3105
|
+
for (const [tag, shape] of icon) {
|
|
3106
|
+
const child = document.createElementNS(SVG, tag);
|
|
3107
|
+
for (const [k, v] of Object.entries(shape)) child.setAttribute(k, v);
|
|
3108
|
+
svg.append(child);
|
|
3109
|
+
}
|
|
3110
|
+
return svg;
|
|
3111
|
+
}
|
|
3112
|
+
function button(label, attrs, icon) {
|
|
3113
|
+
const b = h("button", {
|
|
3114
|
+
type: "button",
|
|
3115
|
+
...attrs,
|
|
3116
|
+
"aria-label": label,
|
|
3117
|
+
title: label
|
|
3118
|
+
});
|
|
3119
|
+
b.append(createIcon(icon));
|
|
3120
|
+
return b;
|
|
3121
|
+
}
|
|
3122
|
+
/**
|
|
3123
|
+
* A toolbar following the WAI-ARIA toolbar pattern: its buttons share one Tab stop, with
|
|
3124
|
+
* arrow keys, Home and End to move between them. Fields and selects are Tab stops of their
|
|
3125
|
+
* own and keep their keys. Buttons added later join in.
|
|
3126
|
+
*/
|
|
3127
|
+
function createToolbar(opts = {}) {
|
|
3128
|
+
const bar = h("div", {
|
|
3129
|
+
class: "ink-toolbar",
|
|
3130
|
+
"data-ink-toolbar": "",
|
|
3131
|
+
role: "toolbar",
|
|
3132
|
+
"aria-label": opts.label ?? "Document tools",
|
|
3133
|
+
"aria-orientation": "horizontal"
|
|
3134
|
+
});
|
|
3135
|
+
rovingTabindex(bar);
|
|
3136
|
+
return bar;
|
|
3137
|
+
}
|
|
3138
|
+
/** A divider between groups of controls in a horizontal toolbar. Not focusable. */
|
|
3139
|
+
function createToolbarSeparator() {
|
|
3140
|
+
return h("div", {
|
|
3141
|
+
role: "separator",
|
|
3142
|
+
"aria-orientation": "vertical",
|
|
3143
|
+
class: "ink-toolbar-separator",
|
|
3144
|
+
"data-ink-toolbar-separator": ""
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
/** Undoes the last change to the document's annotations; disabled when there is none. */
|
|
3148
|
+
function createUndo(viewer) {
|
|
3149
|
+
return historyButton(viewer, "undo");
|
|
3150
|
+
}
|
|
3151
|
+
/** Redoes the last undone change; disabled when there is none. */
|
|
3152
|
+
function createRedo(viewer) {
|
|
3153
|
+
return historyButton(viewer, "redo");
|
|
3154
|
+
}
|
|
3155
|
+
function historyButton(viewer, action) {
|
|
3156
|
+
const b = button(action === "undo" ? "Undo" : "Redo", {
|
|
3157
|
+
class: `ink-${action}`,
|
|
3158
|
+
[`data-ink-${action}`]: ""
|
|
3159
|
+
}, action === "undo" ? Undo2 : Redo2);
|
|
3160
|
+
b.addEventListener("click", () => viewer.document.get()?.history[action]());
|
|
3161
|
+
follow(viewer.document, b, (doc, watch) => watch(action === "undo" ? doc.history.canUndo : doc.history.canRedo, (can) => {
|
|
3162
|
+
b.disabled = !can;
|
|
3163
|
+
}));
|
|
3164
|
+
return b;
|
|
3165
|
+
}
|
|
3166
|
+
/** Sets the tool; pressed while it is the active one. */
|
|
3167
|
+
function createToolButton(viewer, tool, opts = {}) {
|
|
3168
|
+
const b = button(opts.label ?? TOOL_LABELS[tool], {
|
|
3169
|
+
class: "ink-tool-button",
|
|
3170
|
+
"data-ink-tool-button": "",
|
|
3171
|
+
"data-tool": tool
|
|
3172
|
+
}, TOOL_ICONS[tool]);
|
|
3173
|
+
const show = (active) => {
|
|
3174
|
+
const on = active === tool;
|
|
3175
|
+
b.dataset.state = on ? "on" : "off";
|
|
3176
|
+
b.setAttribute("aria-pressed", String(on));
|
|
3177
|
+
};
|
|
3178
|
+
b.addEventListener("click", () => viewer.tools.get()?.set(tool));
|
|
3179
|
+
follow(viewer.tools, b, (tools, watch) => watch(tools.active, show), () => show(null));
|
|
3180
|
+
return b;
|
|
3181
|
+
}
|
|
3182
|
+
function createZoomIn(viewer) {
|
|
3183
|
+
const b = button("Zoom in", {
|
|
3184
|
+
class: "ink-zoom-in",
|
|
3185
|
+
"data-ink-zoom-in": ""
|
|
3186
|
+
}, ZoomIn);
|
|
3187
|
+
b.addEventListener("click", () => viewer.viewport.get()?.zoomBy(1.25));
|
|
3188
|
+
follow(viewer.viewport, b, () => void 0);
|
|
3189
|
+
return b;
|
|
3190
|
+
}
|
|
3191
|
+
function createZoomOut(viewer) {
|
|
3192
|
+
const b = button("Zoom out", {
|
|
3193
|
+
class: "ink-zoom-out",
|
|
3194
|
+
"data-ink-zoom-out": ""
|
|
3195
|
+
}, ZoomOut);
|
|
3196
|
+
b.addEventListener("click", () => viewer.viewport.get()?.zoomBy(.8));
|
|
3197
|
+
follow(viewer.viewport, b, () => void 0);
|
|
3198
|
+
return b;
|
|
3199
|
+
}
|
|
3200
|
+
/** Fit width, fit page and the zoom levels; a zoom between levels gets an option of its own. */
|
|
3201
|
+
function createZoomSelect(viewer) {
|
|
3202
|
+
const custom = h("option", { value: "custom" });
|
|
3203
|
+
const select = h("select", {
|
|
3204
|
+
class: "ink-zoom-select",
|
|
3205
|
+
"data-ink-zoom-select": "",
|
|
3206
|
+
"aria-label": "Zoom"
|
|
3207
|
+
}, h("option", { value: "fit-width" }, "Fit width"), h("option", { value: "fit-page" }, "Fit page"), ...ZOOM_LEVELS.map((l) => h("option", { value: String(l) }, `${Math.round(l * 100)}%`)));
|
|
3208
|
+
select.addEventListener("change", () => {
|
|
3209
|
+
const v = select.value;
|
|
3210
|
+
const viewport = viewer.viewport.get();
|
|
3211
|
+
if (v === "fit-width" || v === "fit-page") viewport?.setZoom(v);
|
|
3212
|
+
else if (v !== "custom") viewport?.setZoom(Number(v));
|
|
3213
|
+
});
|
|
3214
|
+
follow(viewer.viewport, select, (viewport, watch) => watch(viewport.zoom, (z) => {
|
|
3215
|
+
const level = typeof z === "number" ? ZOOM_LEVELS.find((l) => Math.abs(l - z) < .001) : void 0;
|
|
3216
|
+
if (typeof z === "number" && level === void 0) {
|
|
3217
|
+
custom.textContent = `${Math.round(z * 100)}%`;
|
|
3218
|
+
const after = [...select.options].find((o) => o !== custom && Number(o.value) > z);
|
|
3219
|
+
select.insertBefore(custom, after ?? null);
|
|
3220
|
+
select.value = "custom";
|
|
3221
|
+
} else {
|
|
3222
|
+
custom.remove();
|
|
3223
|
+
select.value = typeof z === "number" ? String(level) : z;
|
|
3224
|
+
}
|
|
3225
|
+
}), () => {
|
|
3226
|
+
custom.remove();
|
|
3227
|
+
select.value = "fit-width";
|
|
3228
|
+
});
|
|
3229
|
+
return select;
|
|
3230
|
+
}
|
|
3231
|
+
/**
|
|
3232
|
+
* The current page, 1-based. Enter or leaving the field goes to the page typed; a number out
|
|
3233
|
+
* of range puts the current page back.
|
|
3234
|
+
*/
|
|
3235
|
+
function createPageInput(viewer) {
|
|
3236
|
+
const input = h("input", {
|
|
3237
|
+
class: "ink-page-input",
|
|
3238
|
+
"data-ink-page-input": "",
|
|
3239
|
+
type: "number",
|
|
3240
|
+
inputmode: "numeric",
|
|
3241
|
+
"aria-label": "Page",
|
|
3242
|
+
min: "1"
|
|
3243
|
+
});
|
|
3244
|
+
const commit = () => {
|
|
3245
|
+
const viewport = viewer.viewport.get();
|
|
3246
|
+
const doc = viewer.document.get();
|
|
3247
|
+
if (!viewport || !doc) return;
|
|
3248
|
+
const n = Number(input.value);
|
|
3249
|
+
const current = viewport.currentPage.get();
|
|
3250
|
+
if (Number.isInteger(n) && n >= 1 && n <= doc.pages.length) {
|
|
3251
|
+
if (n - 1 !== current) viewport.scrollToPage(n - 1);
|
|
3252
|
+
} else input.value = String(current + 1);
|
|
3253
|
+
};
|
|
3254
|
+
input.addEventListener("change", commit);
|
|
3255
|
+
input.addEventListener("keydown", (e) => {
|
|
3256
|
+
if (e.key !== "Enter" || e.defaultPrevented) return;
|
|
3257
|
+
e.preventDefault();
|
|
3258
|
+
commit();
|
|
3259
|
+
});
|
|
3260
|
+
follow(viewer.viewport, input, (viewport, watch) => {
|
|
3261
|
+
input.max = String(viewer.document.get()?.pages.length ?? 1);
|
|
3262
|
+
watch(viewport.currentPage, (p) => input.value = String(p + 1));
|
|
3263
|
+
}, () => input.value = "");
|
|
3264
|
+
return input;
|
|
3265
|
+
}
|
|
3266
|
+
/** "of 12": the page count, announced when it changes. */
|
|
3267
|
+
function createPageCount(viewer) {
|
|
3268
|
+
const span = h("span", {
|
|
3269
|
+
class: "ink-page-count",
|
|
3270
|
+
"data-ink-page-count": "",
|
|
3271
|
+
"aria-live": "polite"
|
|
3272
|
+
});
|
|
3273
|
+
const show = () => {
|
|
3274
|
+
const doc = viewer.document.get();
|
|
3275
|
+
span.textContent = doc && viewer.viewport.get() ? `of ${doc.pages.length}` : "";
|
|
3276
|
+
};
|
|
3277
|
+
show();
|
|
3278
|
+
viewer.viewport.subscribe(show);
|
|
3279
|
+
return span;
|
|
3280
|
+
}
|
|
3281
|
+
/** Disabled on the first page. */
|
|
3282
|
+
function createPrevPage(viewer) {
|
|
3283
|
+
return pageButton(viewer, -1);
|
|
3284
|
+
}
|
|
3285
|
+
/** Disabled on the last page. */
|
|
3286
|
+
function createNextPage(viewer) {
|
|
3287
|
+
return pageButton(viewer, 1);
|
|
3288
|
+
}
|
|
3289
|
+
function pageButton(viewer, step) {
|
|
3290
|
+
const b = step < 0 ? button("Previous page", {
|
|
3291
|
+
class: "ink-prev-page",
|
|
3292
|
+
"data-ink-prev-page": ""
|
|
3293
|
+
}, ChevronLeft) : button("Next page", {
|
|
3294
|
+
class: "ink-next-page",
|
|
3295
|
+
"data-ink-next-page": ""
|
|
3296
|
+
}, ChevronRight);
|
|
3297
|
+
b.addEventListener("click", () => {
|
|
3298
|
+
const viewport = viewer.viewport.get();
|
|
3299
|
+
viewport?.scrollToPage(viewport.currentPage.get() + step);
|
|
3300
|
+
});
|
|
3301
|
+
follow(viewer.viewport, b, (viewport, watch) => {
|
|
3302
|
+
const last = (viewer.document.get()?.pages.length ?? 1) - 1;
|
|
3303
|
+
watch(viewport.currentPage, (p) => {
|
|
3304
|
+
b.disabled = step < 0 ? p <= 0 : p >= last;
|
|
3305
|
+
});
|
|
3306
|
+
});
|
|
3307
|
+
return b;
|
|
3308
|
+
}
|
|
3309
|
+
/** Roving items, as in the bindings' Radix toolbar: buttons only. */
|
|
3310
|
+
const ITEMS = "button";
|
|
3311
|
+
/**
|
|
3312
|
+
* One Tab stop across the enabled buttons in `bar`: the current one has `tabindex="0"`, the
|
|
3313
|
+
* rest `-1`. ArrowLeft and ArrowRight move between them and wrap, Home and End go to the ends.
|
|
3314
|
+
* Inputs, selects and textareas, built in or added by the host, keep their own tabindex (a
|
|
3315
|
+
* Tab stop of their own by default) and their own arrow, Home and End keys.
|
|
3316
|
+
*/
|
|
3317
|
+
function rovingTabindex(bar) {
|
|
3318
|
+
let current = null;
|
|
3319
|
+
const items = () => [...bar.querySelectorAll(ITEMS)].filter((el) => !el.disabled && !el.closest("[hidden]"));
|
|
3320
|
+
const sync = () => {
|
|
3321
|
+
const list = items();
|
|
3322
|
+
if (!current || !list.includes(current)) current = list[0] ?? null;
|
|
3323
|
+
for (const el of bar.querySelectorAll(ITEMS)) el.tabIndex = el === current ? 0 : -1;
|
|
3324
|
+
};
|
|
3325
|
+
const onFocusIn = (e) => {
|
|
3326
|
+
const target = e.target;
|
|
3327
|
+
if (!items().includes(target)) return;
|
|
3328
|
+
current = target;
|
|
3329
|
+
sync();
|
|
3330
|
+
};
|
|
3331
|
+
const onKeyDown = (e) => {
|
|
3332
|
+
if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey) return;
|
|
3333
|
+
const list = items();
|
|
3334
|
+
const i = list.indexOf(e.target);
|
|
3335
|
+
if (i < 0) return;
|
|
3336
|
+
const last = list.length - 1;
|
|
3337
|
+
const next = e.key === "ArrowRight" ? i === last ? 0 : i + 1 : e.key === "ArrowLeft" ? i === 0 ? last : i - 1 : e.key === "Home" ? 0 : e.key === "End" ? last : -1;
|
|
3338
|
+
if (next < 0) return;
|
|
3339
|
+
e.preventDefault();
|
|
3340
|
+
list[next].focus();
|
|
3341
|
+
};
|
|
3342
|
+
const observer = new MutationObserver(sync);
|
|
3343
|
+
observer.observe(bar, {
|
|
3344
|
+
childList: true,
|
|
3345
|
+
subtree: true,
|
|
3346
|
+
attributes: true,
|
|
3347
|
+
attributeFilter: ["disabled", "hidden"]
|
|
3348
|
+
});
|
|
3349
|
+
bar.addEventListener("focusin", onFocusIn);
|
|
3350
|
+
bar.addEventListener("keydown", onKeyDown);
|
|
3351
|
+
sync();
|
|
3352
|
+
return {
|
|
3353
|
+
sync,
|
|
3354
|
+
destroy() {
|
|
3355
|
+
observer.disconnect();
|
|
3356
|
+
bar.removeEventListener("focusin", onFocusIn);
|
|
3357
|
+
bar.removeEventListener("keydown", onKeyDown);
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
}
|
|
3361
|
+
//#endregion
|
|
3362
|
+
export { TOOL_LABELS, ZOOM_LEVELS, annotationElementFor, attachViewportKeys, builtinTools, computeCssLayout, confidenceAttr, createFocusReturn, createIcon, createNextPage, createOverlay, createPageCount, createPageInput, createPageMount, createPageRenderer, createPageTransform, createPrevPage, createRedo, createTextLayer, createToolButton, createToolController, createToolbar, createToolbarSeparator, createUndo, createViewer, createViewport, createZoomIn, createZoomOut, createZoomSelect, cssRectsToUserRects, cssVariableDefaults, fitZoomCss, getSelectionQuads, highlightTool, hitFor, mostVisiblePageCss, mountViewer, normalizeRotation, noteTool, observeAnnotationElement, redactAreaTool, renderAnnotationContent, selectTool, shouldKeepOpenOnAnnotation, viewTool, visibleRangeCss };
|
|
3363
3363
|
|
|
3364
3364
|
//# sourceMappingURL=dom.mjs.map
|