@inkbar/react 0.6.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 +13 -0
- package/dist/index.d.mts +98 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1561 -993
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, createContext, createElement, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
-
import { InkError, createMemorySignatureStore, createSignatureLibrary, createSnapshotHistory, isAbandonedNote, openDocument, readonly, repliesTo, threadOf, threadRoot, writable } from "@inkbar/core";
|
|
4
|
-
import { COLOR_COLUMNS, FONT_FAMILIES, MARKUP_ICONS, MARKUP_KINDS, SHAPE_COLUMNS, SHAPE_ICONS, SHAPE_KINDS, SIGNATURE_FONTS, SIGNATURE_LABELS, SIGNATURE_TABS, TOOL_LABELS, ZOOM_LEVELS, colorMenuModel, createFocusReturn, createPluginHost, createSignatureDraft, createToolOptions, cssVariableDefaults, focusInitialMenuItem, fontFamilyValue, handleMenuKeys, handleRadioKeys, handleTabKeys, highlightMenuModel, lineStyleMenuModel, menuItems, mountViewer, observeAnnotationElement, shapeMenuModel, shouldKeepOpenOnAnnotation, signatureMenuModel, signaturePreview, textStyleMenuModel, threadDeleteLabel, threadPopoverLabel, trackToolStyle, unknownCommand } from "@inkbar/core/dom";
|
|
3
|
+
import { InkError, createMemorySignatureStore, createSignatureLibrary, createSnapshotHistory, isAbandonedNote, openDocument, readonly, repliesTo, signField, threadOf, threadRoot, writable } from "@inkbar/core";
|
|
4
|
+
import { COLOR_COLUMNS, FONT_FAMILIES, FORM_LABELS, MARKUP_ICONS, MARKUP_KINDS, SHAPE_COLUMNS, SHAPE_ICONS, SHAPE_KINDS, SIGNATURE_FONTS, SIGNATURE_LABELS, SIGNATURE_TABS, TOOL_LABELS, ZOOM_LEVELS, attachHistoryKeys, colorMenuModel, createFocusReturn, createPluginHost, createSearchController, createSignatureDraft, createToolOptions, cssVariableDefaults, findAnnouncer, findCountText, focusInitialMenuItem, fontFamilyValue, handleFindKey, handleMenuKeys, handleRadioKeys, handleTabKeys, highlightMenuModel, keepHistoryFocus, lineStyleMenuModel, menuItems, mountViewer, observeAnnotationElement, pageSelectionText, shapeMenuModel, shouldKeepOpenOnAnnotation, signatureMenuModel, signaturePreview, textStyleMenuModel, threadDeleteLabel, threadPopoverLabel, trackToolStyle, unknownCommand, viewerSearchOf, watchDetached } from "@inkbar/core/dom";
|
|
5
5
|
import { Dialog, Popover, Select, Slot, Toolbar } from "radix-ui";
|
|
6
6
|
import { createPortal, flushSync } from "react-dom";
|
|
7
7
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { ALargeSmall, AlignCenter, AlignJustify, AlignLeft, AlignRight, Bold, Check, ChevronDown, ChevronLeft, ChevronRight, Highlighter, ICON_ATTRIBUTES, Italic, MessageSquare, Minus, PenLine, PenTool, Plus, Redo2, Signature, Square, SquareDashedMousePointer, TextCursor, Trash2, Type, Underline, Undo2, ZoomIn, ZoomOut } from "@inkbar/icons";
|
|
8
|
+
import { ALargeSmall, AlignCenter, AlignJustify, AlignLeft, AlignRight, Bold, CaseSensitive, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Highlighter, ICON_ATTRIBUTES, Italic, MessageSquare, Minus, PenLine, PenTool, Plus, Redo2, Search, Signature, Square, SquareDashedMousePointer, TextCursor, Trash2, Type, Underline, Undo2, X, ZoomIn, ZoomOut } from "@inkbar/icons";
|
|
9
9
|
//#region src/context.ts
|
|
10
10
|
const InkContextObject = createContext(null);
|
|
11
11
|
/**
|
|
@@ -19,14 +19,14 @@ function useInkContext(consumer) {
|
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/use-readable.ts
|
|
22
|
-
const noop$
|
|
22
|
+
const noop$2 = () => void 0;
|
|
23
23
|
/**
|
|
24
24
|
* Mirrors a core `Readable` into React. A missing readable yields `fallback`, which must be a
|
|
25
25
|
* stable reference (a module constant): `useSyncExternalStore` compares snapshots by identity
|
|
26
26
|
* and loops on a fresh value. On the server the fallback is rendered.
|
|
27
27
|
*/
|
|
28
28
|
function useReadable(readable, fallback) {
|
|
29
|
-
const subscribe = useCallback((onChange) => readable ? readable.subscribe(() => onChange()) : noop$
|
|
29
|
+
const subscribe = useCallback((onChange) => readable ? readable.subscribe(() => onChange()) : noop$2, [readable]);
|
|
30
30
|
const get = useCallback(() => readable ? readable.get() : fallback, [readable, fallback]);
|
|
31
31
|
const server = useCallback(() => fallback, [fallback]);
|
|
32
32
|
return useSyncExternalStore(subscribe, get, server);
|
|
@@ -147,6 +147,35 @@ function useZoom() {
|
|
|
147
147
|
]);
|
|
148
148
|
}
|
|
149
149
|
//#endregion
|
|
150
|
+
//#region src/hooks/useSearch.ts
|
|
151
|
+
const CLOSED = {
|
|
152
|
+
open: false,
|
|
153
|
+
query: "",
|
|
154
|
+
matchCase: false,
|
|
155
|
+
hits: [],
|
|
156
|
+
current: -1,
|
|
157
|
+
scanning: false
|
|
158
|
+
};
|
|
159
|
+
const noop$1 = () => void 0;
|
|
160
|
+
/**
|
|
161
|
+
* The document search for hosts building their own control: the state, and the actions the
|
|
162
|
+
* find bar uses. Search is set up once InkRoot has mounted, so until then (and on the server)
|
|
163
|
+
* the state is closed and the actions do nothing. `open()` runs the search and draws the
|
|
164
|
+
* highlights even when no find bar is mounted.
|
|
165
|
+
*/
|
|
166
|
+
function useSearch() {
|
|
167
|
+
const search = useInkContext("useSearch()").search;
|
|
168
|
+
return {
|
|
169
|
+
state: useReadable(search?.state, CLOSED),
|
|
170
|
+
open: search ? (query) => search.open(query) : noop$1,
|
|
171
|
+
setQuery: search ? (query) => search.setQuery(query) : noop$1,
|
|
172
|
+
setMatchCase: search ? (on) => search.setMatchCase(on) : noop$1,
|
|
173
|
+
next: search ? () => search.next() : noop$1,
|
|
174
|
+
previous: search ? () => search.previous() : noop$1,
|
|
175
|
+
close: search ? () => search.close() : noop$1
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
150
179
|
//#region src/hooks/usePage.ts
|
|
151
180
|
function usePage() {
|
|
152
181
|
const { viewport, document } = useInkContext("usePage()");
|
|
@@ -224,6 +253,30 @@ function useCommand(id) {
|
|
|
224
253
|
};
|
|
225
254
|
}
|
|
226
255
|
//#endregion
|
|
256
|
+
//#region src/hooks/useForm.ts
|
|
257
|
+
const NO_FIELDS = [];
|
|
258
|
+
const NO_VALUES = Object.freeze({});
|
|
259
|
+
/** The open document's form: its fields, their values, and `set` and `reset`. */
|
|
260
|
+
function useForm() {
|
|
261
|
+
const { document } = useInkContext("useForm()");
|
|
262
|
+
const form = document?.form;
|
|
263
|
+
const fields = useReadable(form?.fields, NO_FIELDS);
|
|
264
|
+
const values = useReadable(form?.values, NO_VALUES);
|
|
265
|
+
const actions = useMemo(() => ({
|
|
266
|
+
set: (v) => form?.set(v),
|
|
267
|
+
reset: (names) => form?.reset(names)
|
|
268
|
+
}), [form]);
|
|
269
|
+
return useMemo(() => ({
|
|
270
|
+
fields,
|
|
271
|
+
values,
|
|
272
|
+
...actions
|
|
273
|
+
}), [
|
|
274
|
+
fields,
|
|
275
|
+
values,
|
|
276
|
+
actions
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
227
280
|
//#region src/use-latest.ts
|
|
228
281
|
/**
|
|
229
282
|
* A ref that always holds the latest render's value, for callbacks that outlive a render
|
|
@@ -261,7 +314,7 @@ function isDevelopment() {
|
|
|
261
314
|
function setTestHook(el, doc) {
|
|
262
315
|
if (el && isDevelopment()) el.__inkDocument = doc;
|
|
263
316
|
}
|
|
264
|
-
function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsChange, onChange, tool, onToolChange, tools: hostTools, plugins: pluginsProp, decorateAnnotation, fonts: fontsProp, signatures: signatureStore, signatureFonts: signatureFontsProp, page, onPageChange, zoom, onZoomChange, history, asChild = false, onReady, onError, onActivate, children, ref, ...rest }) {
|
|
317
|
+
function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsChange, onChange, fields, onFieldsChange, onFormAction, formHighlight, tool, onToolChange, tools: hostTools, plugins: pluginsProp, decorateAnnotation, fonts: fontsProp, signatures: signatureStore, signatureFonts: signatureFontsProp, page, onPageChange, zoom, onZoomChange, history, asChild = false, onReady, onError, onActivate, children, ref, ...rest }) {
|
|
265
318
|
const rootElRef = useRef(null);
|
|
266
319
|
const [rootEl, setRootElState] = useState(null);
|
|
267
320
|
const setRootEl = useCallback((el) => {
|
|
@@ -291,6 +344,9 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
291
344
|
defaults,
|
|
292
345
|
annotations,
|
|
293
346
|
controlled: controlledProp,
|
|
347
|
+
fields,
|
|
348
|
+
onFieldsChange,
|
|
349
|
+
onFormAction,
|
|
294
350
|
history,
|
|
295
351
|
historyEnabled,
|
|
296
352
|
onAnnotationsChange,
|
|
@@ -307,6 +363,16 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
307
363
|
tool: internalTool,
|
|
308
364
|
tools
|
|
309
365
|
});
|
|
366
|
+
/**
|
|
367
|
+
* Hands `e` to `onError` one microtask later, as `onFieldsChange` and `onFormAction` are: a
|
|
368
|
+
* script or a press reports from inside its own work. Dropped once `doc` is no longer open.
|
|
369
|
+
*/
|
|
370
|
+
const reportErrorFor = useCallback((doc, e) => {
|
|
371
|
+
queueMicrotask(() => {
|
|
372
|
+
if (documentRef.current !== doc) return;
|
|
373
|
+
flushSync(() => latest.current.onError?.(e));
|
|
374
|
+
});
|
|
375
|
+
}, [latest]);
|
|
310
376
|
const snapshotHistory = useMemo(() => createSnapshotHistory({
|
|
311
377
|
apply: (list) => documentRef.current?.annotations.replaceAll(list, "history"),
|
|
312
378
|
enabled: () => latest.current.historyEnabled
|
|
@@ -347,10 +413,15 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
347
413
|
}, [snapshotHistory]);
|
|
348
414
|
const hostRef = useRef(null);
|
|
349
415
|
const [pluginHost, setPluginHost] = useState(null);
|
|
416
|
+
const [search, setSearch] = useState(null);
|
|
350
417
|
const hostMounts = useRef(0);
|
|
351
418
|
useLayoutEffect(() => {
|
|
352
419
|
hostMounts.current += 1;
|
|
353
420
|
if (!hostRef.current || hostRef.current.destroyed) {
|
|
421
|
+
const search = createSearchController({
|
|
422
|
+
document: readonly(plugins.document),
|
|
423
|
+
viewport: () => viewportRef.current
|
|
424
|
+
});
|
|
354
425
|
const host = createPluginHost({
|
|
355
426
|
plugins: latest.current.plugins ?? [],
|
|
356
427
|
document: readonly(plugins.document),
|
|
@@ -361,13 +432,16 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
361
432
|
},
|
|
362
433
|
root: readonly(plugins.root),
|
|
363
434
|
decorate: () => latest.current.decorateAnnotation,
|
|
364
|
-
viewport: () => viewportRef.current
|
|
435
|
+
viewport: () => viewportRef.current,
|
|
436
|
+
search: viewerSearchOf(search)
|
|
365
437
|
});
|
|
366
438
|
hostRef.current = {
|
|
367
439
|
host,
|
|
440
|
+
search,
|
|
368
441
|
destroyed: false
|
|
369
442
|
};
|
|
370
443
|
setPluginHost(host);
|
|
444
|
+
setSearch(search);
|
|
371
445
|
}
|
|
372
446
|
return () => {
|
|
373
447
|
hostMounts.current -= 1;
|
|
@@ -376,6 +450,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
376
450
|
if (hostMounts.current > 0 || !current || current.destroyed) return;
|
|
377
451
|
current.destroyed = true;
|
|
378
452
|
current.host.destroy();
|
|
453
|
+
current.search.destroy();
|
|
379
454
|
});
|
|
380
455
|
};
|
|
381
456
|
}, []);
|
|
@@ -393,6 +468,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
393
468
|
/** The plugins told of `opened`, and the snapshot history's hold on its store. */
|
|
394
469
|
let told = null;
|
|
395
470
|
let detach = noop;
|
|
471
|
+
let stopFormErrors = noop;
|
|
396
472
|
const run = async () => {
|
|
397
473
|
try {
|
|
398
474
|
const doc = await openDocument(source, {
|
|
@@ -412,6 +488,16 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
412
488
|
});
|
|
413
489
|
});
|
|
414
490
|
}
|
|
491
|
+
} : { mode: "uncontrolled" },
|
|
492
|
+
fields: latest.current.fields !== void 0 ? {
|
|
493
|
+
mode: "controlled",
|
|
494
|
+
initial: latest.current.fields,
|
|
495
|
+
onChange: (next, change) => {
|
|
496
|
+
queueMicrotask(() => {
|
|
497
|
+
if (cancelled) return;
|
|
498
|
+
flushSync(() => latest.current.onFieldsChange?.(next, change));
|
|
499
|
+
});
|
|
500
|
+
}
|
|
415
501
|
} : { mode: "uncontrolled" }
|
|
416
502
|
});
|
|
417
503
|
if (cancelled) {
|
|
@@ -421,6 +507,13 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
421
507
|
opened = doc;
|
|
422
508
|
const current = latest.current.annotations;
|
|
423
509
|
if (doc.annotations.mode === "controlled" && current !== void 0) doc.annotations.setControlledValue(current);
|
|
510
|
+
const currentFields = latest.current.fields;
|
|
511
|
+
if (currentFields !== void 0) doc.form.setControlledValue(currentFields);
|
|
512
|
+
let held = [];
|
|
513
|
+
stopFormErrors = doc.form.on("error", (e) => {
|
|
514
|
+
if (held) held.push(e);
|
|
515
|
+
else reportErrorFor(doc, e);
|
|
516
|
+
});
|
|
424
517
|
documentRef.current = doc;
|
|
425
518
|
if (doc.annotations.mode === "controlled") detach = snapshotHistory.attach(doc.annotations);
|
|
426
519
|
told = plugins;
|
|
@@ -429,8 +522,14 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
429
522
|
setOverride(null);
|
|
430
523
|
setTestHook(rootElRef.current, doc);
|
|
431
524
|
const s = doc.status.get();
|
|
432
|
-
|
|
433
|
-
|
|
525
|
+
try {
|
|
526
|
+
if (s.state === "ready") latest.current.onReady?.(doc);
|
|
527
|
+
else if (s.state === "error") latest.current.onError?.(s.error);
|
|
528
|
+
} finally {
|
|
529
|
+
const early = held;
|
|
530
|
+
held = void 0;
|
|
531
|
+
for (const e of early) reportErrorFor(doc, e);
|
|
532
|
+
}
|
|
434
533
|
} catch (cause) {
|
|
435
534
|
if (cancelled) return;
|
|
436
535
|
const error = cause instanceof InkError ? cause : new InkError("InvalidPdf", cause instanceof Error ? cause.message : String(cause), {}, cause);
|
|
@@ -448,6 +547,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
448
547
|
cancelled = true;
|
|
449
548
|
told?.document.set(null);
|
|
450
549
|
detach();
|
|
550
|
+
stopFormErrors();
|
|
451
551
|
opened?.destroy();
|
|
452
552
|
documentRef.current = null;
|
|
453
553
|
setDocument(null);
|
|
@@ -458,11 +558,15 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
458
558
|
password,
|
|
459
559
|
snapshotHistory,
|
|
460
560
|
latest,
|
|
461
|
-
plugins
|
|
561
|
+
plugins,
|
|
562
|
+
reportErrorFor
|
|
462
563
|
]);
|
|
463
564
|
useLayoutEffect(() => {
|
|
464
565
|
if (annotations && document?.annotations.mode === "controlled") document.annotations.setControlledValue(annotations);
|
|
465
566
|
}, [annotations, document]);
|
|
567
|
+
useLayoutEffect(() => {
|
|
568
|
+
if (fields && document) document.form.setControlledValue(fields);
|
|
569
|
+
}, [fields, document]);
|
|
466
570
|
useEffect(() => {
|
|
467
571
|
if (tool) setInternalTool(tool);
|
|
468
572
|
}, [tool]);
|
|
@@ -474,6 +578,16 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
474
578
|
}, [latest]);
|
|
475
579
|
const reportZoom = useCallback((z) => latest.current.onZoomChange?.(z), [latest]);
|
|
476
580
|
const reportPage = useCallback((p) => latest.current.onPageChange?.(p), [latest]);
|
|
581
|
+
const reportFormAction = useCallback((a) => {
|
|
582
|
+
const doc = documentRef.current;
|
|
583
|
+
queueMicrotask(() => {
|
|
584
|
+
if (documentRef.current !== doc) return;
|
|
585
|
+
flushSync(() => latest.current.onFormAction?.(a));
|
|
586
|
+
});
|
|
587
|
+
}, [latest]);
|
|
588
|
+
const reportError = useCallback((e) => {
|
|
589
|
+
if (documentRef.current) reportErrorFor(documentRef.current, e);
|
|
590
|
+
}, [reportErrorFor]);
|
|
477
591
|
const setViewport = useCallback((vp, tc) => {
|
|
478
592
|
viewportRef.current = vp;
|
|
479
593
|
setViewportState(vp);
|
|
@@ -507,6 +621,31 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
507
621
|
pluginHistory
|
|
508
622
|
]);
|
|
509
623
|
const latestHistory = useLatest(inkHistory);
|
|
624
|
+
useEffect(() => {
|
|
625
|
+
if (!rootEl) return;
|
|
626
|
+
const stopKeys = attachHistoryKeys(rootEl, () => {
|
|
627
|
+
const doc = documentRef.current;
|
|
628
|
+
if (!doc) return null;
|
|
629
|
+
const snap = doc.annotations.mode === "controlled";
|
|
630
|
+
const on = latest.current.historyEnabled;
|
|
631
|
+
return {
|
|
632
|
+
canUndo: snap ? on && snapshotHistory.canUndo.get() : doc.history.canUndo.get(),
|
|
633
|
+
canRedo: snap ? on && snapshotHistory.canRedo.get() : doc.history.canRedo.get(),
|
|
634
|
+
undo: () => latestHistory.current.undo(),
|
|
635
|
+
redo: () => latestHistory.current.redo()
|
|
636
|
+
};
|
|
637
|
+
});
|
|
638
|
+
const stopFocus = keepHistoryFocus(rootEl, () => rootEl.querySelector("[data-ink-scroll]"));
|
|
639
|
+
return () => {
|
|
640
|
+
stopKeys();
|
|
641
|
+
stopFocus();
|
|
642
|
+
};
|
|
643
|
+
}, [
|
|
644
|
+
rootEl,
|
|
645
|
+
latestHistory,
|
|
646
|
+
latest,
|
|
647
|
+
snapshotHistory
|
|
648
|
+
]);
|
|
510
649
|
/** The document's tool style; it batches through `history`, whichever mode is on. */
|
|
511
650
|
const styleTracker = useMemo(() => document ? trackToolStyle({
|
|
512
651
|
store: document.annotations,
|
|
@@ -582,10 +721,14 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
582
721
|
activated,
|
|
583
722
|
setActivated,
|
|
584
723
|
pluginHost,
|
|
724
|
+
search,
|
|
585
725
|
viewportEl: plugins.root,
|
|
586
726
|
history: inkHistory,
|
|
587
727
|
rootEl,
|
|
588
|
-
setViewport
|
|
728
|
+
setViewport,
|
|
729
|
+
formHighlight,
|
|
730
|
+
reportFormAction,
|
|
731
|
+
reportError
|
|
589
732
|
}), [
|
|
590
733
|
assets,
|
|
591
734
|
document,
|
|
@@ -611,11 +754,23 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
611
754
|
activated,
|
|
612
755
|
setActivated,
|
|
613
756
|
pluginHost,
|
|
757
|
+
search,
|
|
614
758
|
plugins,
|
|
615
759
|
inkHistory,
|
|
616
760
|
rootEl,
|
|
617
|
-
setViewport
|
|
761
|
+
setViewport,
|
|
762
|
+
formHighlight,
|
|
763
|
+
reportFormAction,
|
|
764
|
+
reportError
|
|
618
765
|
]);
|
|
766
|
+
useEffect(() => {
|
|
767
|
+
if (!rootEl || !search) return;
|
|
768
|
+
const onKeyDown = (e) => {
|
|
769
|
+
handleFindKey(e, search, () => pageSelectionText(rootEl));
|
|
770
|
+
};
|
|
771
|
+
rootEl.addEventListener("keydown", onKeyDown);
|
|
772
|
+
return () => rootEl.removeEventListener("keydown", onKeyDown);
|
|
773
|
+
}, [rootEl, search]);
|
|
619
774
|
const Comp = asChild ? Slot.Root : "div";
|
|
620
775
|
return /* @__PURE__ */ jsx(InkContextObject.Provider, {
|
|
621
776
|
value: ctx,
|
|
@@ -639,6 +794,77 @@ function cx(...parts) {
|
|
|
639
794
|
return out === "" ? void 0 : out;
|
|
640
795
|
}
|
|
641
796
|
//#endregion
|
|
797
|
+
//#region src/components/InkFormAlert.tsx
|
|
798
|
+
function InkFormAlert({ message, shown, fallback, onClose }) {
|
|
799
|
+
const uid = useId();
|
|
800
|
+
const panel = useRef(null);
|
|
801
|
+
const ok = useRef(null);
|
|
802
|
+
/** Where focus was when the alert opened. */
|
|
803
|
+
const returnTo = useRef(null);
|
|
804
|
+
const open = message !== null;
|
|
805
|
+
const wasOpen = useRef(false);
|
|
806
|
+
useLayoutEffect(() => {
|
|
807
|
+
if (!open) {
|
|
808
|
+
wasOpen.current = false;
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (!wasOpen.current) {
|
|
812
|
+
wasOpen.current = true;
|
|
813
|
+
const active = document.activeElement;
|
|
814
|
+
returnTo.current = active instanceof HTMLElement && active !== document.body ? active : null;
|
|
815
|
+
}
|
|
816
|
+
ok.current?.focus({ preventScroll: true });
|
|
817
|
+
}, [open, shown]);
|
|
818
|
+
const close = () => {
|
|
819
|
+
const hadFocus = !!panel.current?.contains(document.activeElement);
|
|
820
|
+
onClose();
|
|
821
|
+
if (!hadFocus) return;
|
|
822
|
+
(returnTo.current?.isConnected === true ? returnTo.current : fallback())?.focus({ preventScroll: true });
|
|
823
|
+
};
|
|
824
|
+
if (!open) return null;
|
|
825
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
826
|
+
ref: panel,
|
|
827
|
+
role: "alertdialog",
|
|
828
|
+
"aria-labelledby": `${uid}-title`,
|
|
829
|
+
"aria-describedby": `${uid}-message`,
|
|
830
|
+
className: "ink-dialog ink-form-alert",
|
|
831
|
+
"data-ink-form-alert": "",
|
|
832
|
+
"data-state": "open",
|
|
833
|
+
tabIndex: -1,
|
|
834
|
+
onKeyDown: (e) => {
|
|
835
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
836
|
+
e.preventDefault();
|
|
837
|
+
e.stopPropagation();
|
|
838
|
+
close();
|
|
839
|
+
},
|
|
840
|
+
children: [
|
|
841
|
+
/* @__PURE__ */ jsx("h2", {
|
|
842
|
+
id: `${uid}-title`,
|
|
843
|
+
className: "ink-dialog-title",
|
|
844
|
+
children: FORM_LABELS.alert
|
|
845
|
+
}),
|
|
846
|
+
/* @__PURE__ */ jsx("p", {
|
|
847
|
+
id: `${uid}-message`,
|
|
848
|
+
className: "ink-form-alert-message",
|
|
849
|
+
"data-ink-form-alert-message": "",
|
|
850
|
+
children: message
|
|
851
|
+
}),
|
|
852
|
+
/* @__PURE__ */ jsx("div", {
|
|
853
|
+
className: "ink-dialog-actions",
|
|
854
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
855
|
+
ref: ok,
|
|
856
|
+
type: "button",
|
|
857
|
+
className: "ink-button",
|
|
858
|
+
"data-variant": "primary",
|
|
859
|
+
"data-ink-form-alert-ok": "",
|
|
860
|
+
onClick: close,
|
|
861
|
+
children: FORM_LABELS.ok
|
|
862
|
+
})
|
|
863
|
+
})
|
|
864
|
+
]
|
|
865
|
+
}), document.body);
|
|
866
|
+
}
|
|
867
|
+
//#endregion
|
|
642
868
|
//#region src/components/InkAnnotation.tsx
|
|
643
869
|
function readState(el) {
|
|
644
870
|
const s = el.dataset.state;
|
|
@@ -734,243 +960,214 @@ function InkPage({ page, host, mount, error, customAnnotations = false, renderAn
|
|
|
734
960
|
}), t.el, t.annotation.id)) : null] });
|
|
735
961
|
}
|
|
736
962
|
//#endregion
|
|
737
|
-
//#region src/components/
|
|
738
|
-
function
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
963
|
+
//#region src/components/InkSignatureDialog.tsx
|
|
964
|
+
function InkSignatureDialog({ open, onOpenChange, fonts, select = true, onSave, onCloseAutoFocus, className }) {
|
|
965
|
+
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
966
|
+
open,
|
|
967
|
+
onOpenChange,
|
|
968
|
+
children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, {
|
|
969
|
+
className: "ink-dialog-overlay",
|
|
970
|
+
"data-ink-dialog-overlay": ""
|
|
971
|
+
}), /* @__PURE__ */ jsxs(Dialog.Content, {
|
|
972
|
+
className: cx("ink-dialog ink-signature-dialog", className),
|
|
973
|
+
"data-ink-signature-dialog": "",
|
|
974
|
+
"aria-modal": "true",
|
|
975
|
+
"aria-describedby": void 0,
|
|
976
|
+
onOpenAutoFocus: (e) => {
|
|
977
|
+
e.preventDefault();
|
|
978
|
+
e.currentTarget.querySelector("[role=\"tab\"][aria-selected=\"true\"]")?.focus({ preventScroll: true });
|
|
979
|
+
},
|
|
980
|
+
onCloseAutoFocus,
|
|
981
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
982
|
+
className: "ink-dialog-title",
|
|
983
|
+
children: SIGNATURE_LABELS.dialog
|
|
984
|
+
}), /* @__PURE__ */ jsx(SignatureForm, {
|
|
985
|
+
fonts,
|
|
986
|
+
select,
|
|
987
|
+
onSave,
|
|
988
|
+
onClose: () => onOpenChange(false)
|
|
989
|
+
})]
|
|
990
|
+
})] })
|
|
749
991
|
});
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
992
|
+
}
|
|
993
|
+
const IDLE = {
|
|
994
|
+
tab: "draw",
|
|
995
|
+
padEmpty: true,
|
|
996
|
+
name: "",
|
|
997
|
+
font: "",
|
|
998
|
+
image: void 0,
|
|
999
|
+
imageError: void 0,
|
|
1000
|
+
saveError: void 0,
|
|
1001
|
+
canSave: false,
|
|
1002
|
+
saving: false
|
|
1003
|
+
};
|
|
1004
|
+
/** The dialog's body, mounted while it is open: one draft per opening. */
|
|
1005
|
+
function SignatureForm({ fonts, select, onSave, onClose }) {
|
|
1006
|
+
const ctx = useInkContext("InkSignatureDialog");
|
|
1007
|
+
const { setTool } = useTool();
|
|
1008
|
+
const [, setOptions] = useToolOptions("signature");
|
|
1009
|
+
const [draft] = useState(() => createSignatureDraft({ fonts: fonts ?? ctx.signatureFonts }));
|
|
1010
|
+
const s = useReadable(draft.state, IDLE);
|
|
1011
|
+
const uid = useId();
|
|
1012
|
+
const padHost = useRef(null);
|
|
764
1013
|
useEffect(() => {
|
|
765
|
-
const
|
|
766
|
-
const
|
|
767
|
-
const el = scroll.current;
|
|
768
|
-
lastReportedZoom.current = void 0;
|
|
769
|
-
lastReportedPage.current = void 0;
|
|
770
|
-
if (!doc || !el || c.status.state !== "ready") return;
|
|
771
|
-
let first = true;
|
|
772
|
-
let stops = [];
|
|
773
|
-
buildCount.current += 1;
|
|
774
|
-
builtWith.current = {
|
|
775
|
-
gap: g,
|
|
776
|
-
buffer: b,
|
|
777
|
-
custom: cu
|
|
778
|
-
};
|
|
779
|
-
const e = mountViewer({
|
|
780
|
-
container: el,
|
|
781
|
-
document: doc,
|
|
782
|
-
zoom: c.zoomModel ?? "fit-width",
|
|
783
|
-
...c.pageModel === void 0 ? {} : { page: c.pageModel },
|
|
784
|
-
mode: m,
|
|
785
|
-
rotation: r,
|
|
786
|
-
gap: g,
|
|
787
|
-
buffer: b,
|
|
788
|
-
tool: c.tool,
|
|
789
|
-
...c.hostTools ? { tools: c.hostTools } : {},
|
|
790
|
-
...c.pluginHost ? { plugins: c.pluginHost } : {},
|
|
791
|
-
decorateAnnotation: (a) => latest.current.ctx.decorateAnnotation(a),
|
|
792
|
-
...c.toolStyle ? { toolStyle: c.toolStyle } : {},
|
|
793
|
-
toolHistory: { batch: (fn) => latest.current.ctx.history.batch(fn) },
|
|
794
|
-
toolOptions: c.toolOptions,
|
|
795
|
-
fonts: [...c.fonts],
|
|
796
|
-
signatureLibrary: () => latest.current.ctx.signatures,
|
|
797
|
-
customAnnotations: cu,
|
|
798
|
-
onActivate: (id) => latest.current.ctx.setActivated(id),
|
|
799
|
-
onBuild: (vp, tc) => {
|
|
800
|
-
latest.current.ctx.setViewport(vp, tc);
|
|
801
|
-
if (first) {
|
|
802
|
-
first = false;
|
|
803
|
-
lastReportedZoom.current = vp.zoom.get();
|
|
804
|
-
lastReportedPage.current = 0;
|
|
805
|
-
}
|
|
806
|
-
for (const stop of stops) stop();
|
|
807
|
-
const reportZoom = (z) => {
|
|
808
|
-
if (z === lastReportedZoom.current) return;
|
|
809
|
-
lastReportedZoom.current = z;
|
|
810
|
-
latest.current.ctx.reportZoom(z);
|
|
811
|
-
};
|
|
812
|
-
const reportPage = (p) => {
|
|
813
|
-
if (p === lastReportedPage.current) return;
|
|
814
|
-
lastReportedPage.current = p;
|
|
815
|
-
latest.current.ctx.reportPage(p);
|
|
816
|
-
};
|
|
817
|
-
const followTool = (id) => {
|
|
818
|
-
if (id !== latest.current.ctx.tool) latest.current.ctx.setTool(id);
|
|
819
|
-
};
|
|
820
|
-
stops = [
|
|
821
|
-
vp.zoom.subscribe(reportZoom),
|
|
822
|
-
vp.currentPage.subscribe(reportPage),
|
|
823
|
-
tc.active.subscribe(followTool)
|
|
824
|
-
];
|
|
825
|
-
reportZoom(vp.zoom.get());
|
|
826
|
-
reportPage(vp.currentPage.get());
|
|
827
|
-
},
|
|
828
|
-
onPageMount: (page, mount, host) => {
|
|
829
|
-
const build = buildCount.current;
|
|
830
|
-
const custom = builtWith.current?.custom ?? false;
|
|
831
|
-
setPages((prev) => [...prev, {
|
|
832
|
-
build,
|
|
833
|
-
page,
|
|
834
|
-
host,
|
|
835
|
-
mount,
|
|
836
|
-
error: null,
|
|
837
|
-
custom
|
|
838
|
-
}]);
|
|
839
|
-
},
|
|
840
|
-
onPageUnmount: (page) => setPages((prev) => prev.filter((p) => p.page !== page)),
|
|
841
|
-
onPageError: (page, err) => setPages((prev) => prev.map((p) => p.page === page ? {
|
|
842
|
-
...p,
|
|
843
|
-
error: err
|
|
844
|
-
} : p))
|
|
845
|
-
});
|
|
846
|
-
engine.current = e;
|
|
847
|
-
return () => {
|
|
848
|
-
for (const stop of stops) stop();
|
|
849
|
-
e.destroy();
|
|
850
|
-
engine.current = null;
|
|
851
|
-
builtWith.current = null;
|
|
852
|
-
c.setViewport(null, null);
|
|
853
|
-
setPages((prev) => prev.length === 0 ? prev : []);
|
|
854
|
-
};
|
|
855
|
-
}, [
|
|
856
|
-
ctx.document,
|
|
857
|
-
ctx.status.state,
|
|
858
|
-
ctx.pluginHost,
|
|
859
|
-
latest
|
|
860
|
-
]);
|
|
861
|
-
const { viewportEl } = ctx;
|
|
862
|
-
useLayoutEffect(() => {
|
|
863
|
-
const el = scroll.current;
|
|
864
|
-
viewportEl.set(el);
|
|
1014
|
+
const host = padHost.current;
|
|
1015
|
+
const detach = host ? draft.attachPad(host) : void 0;
|
|
865
1016
|
return () => {
|
|
866
|
-
|
|
1017
|
+
detach?.();
|
|
1018
|
+
draft.destroy();
|
|
867
1019
|
};
|
|
868
|
-
}, [
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
custom
|
|
890
|
-
]);
|
|
891
|
-
useEffect(() => {
|
|
892
|
-
const vp = engine.current?.viewport;
|
|
893
|
-
const z = ctx.zoomModel;
|
|
894
|
-
if (vp && z !== void 0 && z !== vp.zoom.get()) vp.setZoom(z);
|
|
895
|
-
}, [ctx.zoomModel]);
|
|
896
|
-
useEffect(() => {
|
|
897
|
-
const vp = engine.current?.viewport;
|
|
898
|
-
const p = ctx.pageModel;
|
|
899
|
-
if (!vp || p === void 0 || p === lastReportedPage.current) return;
|
|
900
|
-
if (p !== vp.currentPage.get()) vp.scrollToPage(p);
|
|
901
|
-
}, [ctx.pageModel]);
|
|
902
|
-
useEffect(() => {
|
|
903
|
-
const tc = engine.current?.tools;
|
|
904
|
-
if (tc && tc.active.get() !== ctx.tool) tc.set(ctx.tool);
|
|
905
|
-
}, [ctx.tool]);
|
|
906
|
-
useEffect(() => {
|
|
907
|
-
const vp = engine.current?.viewport;
|
|
908
|
-
if (vp && vp.mode.get() !== mode) vp.setMode(mode);
|
|
909
|
-
}, [mode]);
|
|
910
|
-
useEffect(() => {
|
|
911
|
-
const vp = engine.current?.viewport;
|
|
912
|
-
if (vp && vp.rotation.get() !== rotation) vp.setRotation(rotation);
|
|
913
|
-
}, [rotation]);
|
|
914
|
-
const state = ctx.status.state;
|
|
915
|
-
const err = ctx.status.state === "error" ? ctx.status.error : null;
|
|
916
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
917
|
-
...rest,
|
|
918
|
-
className: cx("ink-viewport-root", className),
|
|
919
|
-
"data-ink-viewport-root": "",
|
|
920
|
-
"data-state": state,
|
|
921
|
-
children: [
|
|
922
|
-
state === "loading" ? /* @__PURE__ */ jsx("div", {
|
|
923
|
-
className: "ink-status",
|
|
924
|
-
role: "status",
|
|
925
|
-
"aria-live": "polite",
|
|
926
|
-
children: loading === void 0 ? "Loading document…" : loading
|
|
927
|
-
}) : err ? /* @__PURE__ */ jsx("div", {
|
|
928
|
-
className: "ink-status ink-error",
|
|
929
|
-
role: "alert",
|
|
930
|
-
children: typeof error === "function" ? error({ error: err }) : error !== void 0 ? error : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
|
|
931
|
-
className: "ink-error-title",
|
|
932
|
-
children: "This document could not be opened."
|
|
933
|
-
}), /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("code", {
|
|
934
|
-
className: "ink-error-code",
|
|
935
|
-
title: err.message,
|
|
936
|
-
children: err.code
|
|
937
|
-
}) })] })
|
|
938
|
-
}) : state === "idle" ? /* @__PURE__ */ jsx("div", {
|
|
939
|
-
className: "ink-status ink-empty",
|
|
940
|
-
children: empty
|
|
941
|
-
}) : null,
|
|
942
|
-
/* @__PURE__ */ jsx("div", {
|
|
943
|
-
ref: scroll,
|
|
944
|
-
className: "ink-scroll",
|
|
945
|
-
"data-ink-scroll": "",
|
|
946
|
-
tabIndex: 0,
|
|
947
|
-
role: "document",
|
|
948
|
-
"aria-label": "Document"
|
|
949
|
-
}),
|
|
950
|
-
pages.map((p) => /* @__PURE__ */ jsx(InkPage, {
|
|
951
|
-
page: p.page,
|
|
952
|
-
host: p.host,
|
|
953
|
-
mount: p.mount,
|
|
954
|
-
error: p.error,
|
|
955
|
-
customAnnotations: p.custom,
|
|
956
|
-
renderAnnotation,
|
|
957
|
-
renderPageError
|
|
958
|
-
}, `${p.build}:${p.page}`))
|
|
959
|
-
]
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
//#endregion
|
|
963
|
-
//#region src/components/InkToolbar.tsx
|
|
964
|
-
function InkToolbar({ label = "Document tools", asChild = false, className, children, ...rest }) {
|
|
965
|
-
return /* @__PURE__ */ jsx(Toolbar.Root, {
|
|
966
|
-
asChild,
|
|
967
|
-
orientation: "horizontal",
|
|
968
|
-
"aria-label": label,
|
|
969
|
-
"data-ink-toolbar": "",
|
|
970
|
-
className: cx("ink-toolbar", className),
|
|
971
|
-
...rest,
|
|
972
|
-
children
|
|
1020
|
+
}, [draft]);
|
|
1021
|
+
const save = () => {
|
|
1022
|
+
draft.save(ctx.signatures).then((t) => {
|
|
1023
|
+
if (!t) return;
|
|
1024
|
+
if (select) {
|
|
1025
|
+
setOptions({ templateId: t.id });
|
|
1026
|
+
setTool("signature");
|
|
1027
|
+
}
|
|
1028
|
+
onSave?.(t);
|
|
1029
|
+
onClose();
|
|
1030
|
+
}, () => {});
|
|
1031
|
+
};
|
|
1032
|
+
const tabId = (tab) => `${uid}-tab-${tab}`;
|
|
1033
|
+
const panelId = (tab) => `${uid}-panel-${tab}`;
|
|
1034
|
+
const panel = (tab) => ({
|
|
1035
|
+
role: "tabpanel",
|
|
1036
|
+
id: panelId(tab),
|
|
1037
|
+
"aria-labelledby": tabId(tab),
|
|
1038
|
+
className: "ink-tab-panel",
|
|
1039
|
+
"data-key": tab,
|
|
1040
|
+
hidden: s.tab !== tab
|
|
973
1041
|
});
|
|
1042
|
+
const shown = s.name.trim() || SIGNATURE_LABELS.namePlaceholder;
|
|
1043
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1044
|
+
/* @__PURE__ */ jsx("div", {
|
|
1045
|
+
role: "tablist",
|
|
1046
|
+
"aria-label": SIGNATURE_LABELS.tabs,
|
|
1047
|
+
className: "ink-tabs",
|
|
1048
|
+
onKeyDown: (e) => handleTabKeys(e.nativeEvent, e.currentTarget),
|
|
1049
|
+
children: SIGNATURE_TABS.map((tab) => /* @__PURE__ */ jsx("button", {
|
|
1050
|
+
type: "button",
|
|
1051
|
+
role: "tab",
|
|
1052
|
+
id: tabId(tab),
|
|
1053
|
+
"aria-controls": panelId(tab),
|
|
1054
|
+
"aria-selected": s.tab === tab,
|
|
1055
|
+
tabIndex: s.tab === tab ? 0 : -1,
|
|
1056
|
+
className: "ink-tab",
|
|
1057
|
+
"data-key": tab,
|
|
1058
|
+
"data-state": s.tab === tab ? "active" : "inactive",
|
|
1059
|
+
onClick: () => draft.setTab(tab),
|
|
1060
|
+
children: SIGNATURE_LABELS[tab]
|
|
1061
|
+
}, tab))
|
|
1062
|
+
}),
|
|
1063
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1064
|
+
...panel("draw"),
|
|
1065
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1066
|
+
ref: padHost,
|
|
1067
|
+
className: "ink-signature-pad-host",
|
|
1068
|
+
"data-ink-signature-pad-host": "",
|
|
1069
|
+
role: "img",
|
|
1070
|
+
"aria-label": SIGNATURE_LABELS.pad
|
|
1071
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1072
|
+
className: "ink-dialog-row",
|
|
1073
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1074
|
+
type: "button",
|
|
1075
|
+
className: "ink-button",
|
|
1076
|
+
"data-ink-signature-clear": "",
|
|
1077
|
+
disabled: s.padEmpty,
|
|
1078
|
+
onClick: () => draft.clearPad(),
|
|
1079
|
+
children: SIGNATURE_LABELS.clear
|
|
1080
|
+
})
|
|
1081
|
+
})]
|
|
1082
|
+
}),
|
|
1083
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1084
|
+
...panel("type"),
|
|
1085
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
1086
|
+
className: "ink-field",
|
|
1087
|
+
children: [/* @__PURE__ */ jsx("span", { children: SIGNATURE_LABELS.name }), /* @__PURE__ */ jsx("input", {
|
|
1088
|
+
type: "text",
|
|
1089
|
+
className: "ink-input",
|
|
1090
|
+
"data-ink-signature-name": "",
|
|
1091
|
+
autoComplete: "name",
|
|
1092
|
+
placeholder: SIGNATURE_LABELS.namePlaceholder,
|
|
1093
|
+
value: s.name,
|
|
1094
|
+
onChange: (e) => draft.setName(e.target.value)
|
|
1095
|
+
})]
|
|
1096
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1097
|
+
role: "radiogroup",
|
|
1098
|
+
"aria-label": SIGNATURE_LABELS.fonts,
|
|
1099
|
+
className: "ink-signature-fonts",
|
|
1100
|
+
onKeyDown: (e) => handleRadioKeys(e.nativeEvent, e.currentTarget),
|
|
1101
|
+
children: draft.fonts.map((font) => /* @__PURE__ */ jsx("button", {
|
|
1102
|
+
type: "button",
|
|
1103
|
+
role: "radio",
|
|
1104
|
+
"aria-checked": s.font === font,
|
|
1105
|
+
"aria-label": font,
|
|
1106
|
+
tabIndex: s.font === font ? 0 : -1,
|
|
1107
|
+
className: "ink-signature-font",
|
|
1108
|
+
"data-key": font,
|
|
1109
|
+
"data-state": s.font === font ? "checked" : "unchecked",
|
|
1110
|
+
onClick: () => draft.setFont(font),
|
|
1111
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1112
|
+
className: "ink-signature-font-sample",
|
|
1113
|
+
style: { fontFamily: fontFamilyValue(font) },
|
|
1114
|
+
children: shown
|
|
1115
|
+
})
|
|
1116
|
+
}, font))
|
|
1117
|
+
})]
|
|
1118
|
+
}),
|
|
1119
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1120
|
+
...panel("image"),
|
|
1121
|
+
children: [
|
|
1122
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1123
|
+
className: "ink-field",
|
|
1124
|
+
children: [/* @__PURE__ */ jsx("span", { children: SIGNATURE_LABELS.file }), /* @__PURE__ */ jsx("input", {
|
|
1125
|
+
type: "file",
|
|
1126
|
+
accept: "image/png,image/jpeg",
|
|
1127
|
+
className: "ink-input",
|
|
1128
|
+
"data-ink-signature-file": "",
|
|
1129
|
+
onChange: (e) => void draft.chooseFile(e.target.files?.[0])
|
|
1130
|
+
})]
|
|
1131
|
+
}),
|
|
1132
|
+
s.image?.kind === "image" ? /* @__PURE__ */ jsx("img", {
|
|
1133
|
+
className: "ink-signature-image",
|
|
1134
|
+
src: s.image.imagedata,
|
|
1135
|
+
alt: ""
|
|
1136
|
+
}) : null,
|
|
1137
|
+
s.imageError ? /* @__PURE__ */ jsx("p", {
|
|
1138
|
+
className: "ink-field-error",
|
|
1139
|
+
role: "alert",
|
|
1140
|
+
children: s.imageError
|
|
1141
|
+
}) : null
|
|
1142
|
+
]
|
|
1143
|
+
}),
|
|
1144
|
+
s.saveError ? /* @__PURE__ */ jsx("p", {
|
|
1145
|
+
className: "ink-field-error",
|
|
1146
|
+
role: "alert",
|
|
1147
|
+
"data-ink-signature-save-error": "",
|
|
1148
|
+
children: s.saveError
|
|
1149
|
+
}) : null,
|
|
1150
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1151
|
+
className: "ink-dialog-actions",
|
|
1152
|
+
children: [/* @__PURE__ */ jsx(Dialog.Close, {
|
|
1153
|
+
asChild: true,
|
|
1154
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1155
|
+
type: "button",
|
|
1156
|
+
className: "ink-button",
|
|
1157
|
+
"data-ink-signature-cancel": "",
|
|
1158
|
+
children: SIGNATURE_LABELS.cancel
|
|
1159
|
+
})
|
|
1160
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1161
|
+
type: "button",
|
|
1162
|
+
className: "ink-button",
|
|
1163
|
+
"data-variant": "primary",
|
|
1164
|
+
"data-ink-signature-save": "",
|
|
1165
|
+
disabled: !s.canSave,
|
|
1166
|
+
onClick: save,
|
|
1167
|
+
children: SIGNATURE_LABELS.save
|
|
1168
|
+
})]
|
|
1169
|
+
})
|
|
1170
|
+
] });
|
|
974
1171
|
}
|
|
975
1172
|
//#endregion
|
|
976
1173
|
//#region src/components/InkIcon.tsx
|
|
@@ -991,193 +1188,864 @@ function InkIcon({ icon, size = 18 }) {
|
|
|
991
1188
|
});
|
|
992
1189
|
}
|
|
993
1190
|
//#endregion
|
|
994
|
-
//#region src/components/
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
function InkToolButton({ tool, label, asChild = false, className, children, onClick, ...rest }) {
|
|
1007
|
-
const { hostTools, pluginHost } = useInkContext("InkToolButton");
|
|
1008
|
-
const { tool: current, setTool } = useTool();
|
|
1009
|
-
const active = current === tool;
|
|
1010
|
-
/** A host or plugin tool by this id, for the name and icon core has none for. */
|
|
1011
|
-
const own = [...hostTools ?? [], ...pluginHost?.tools ?? []].find((t) => t.id === tool);
|
|
1012
|
-
const name = label ?? TOOL_LABELS[tool] ?? own?.label ?? tool;
|
|
1013
|
-
const icon = ICONS[tool] ?? own?.icon;
|
|
1014
|
-
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1015
|
-
asChild,
|
|
1016
|
-
className: cx("ink-tool-button", className),
|
|
1017
|
-
"data-ink-tool-button": "",
|
|
1018
|
-
"data-tool": tool,
|
|
1019
|
-
"data-state": active ? "on" : "off",
|
|
1020
|
-
"aria-pressed": active,
|
|
1021
|
-
"aria-label": name,
|
|
1022
|
-
title: name,
|
|
1023
|
-
onClick: (e) => {
|
|
1024
|
-
onClick?.(e);
|
|
1025
|
-
if (!e.defaultPrevented) setTool(tool);
|
|
1026
|
-
},
|
|
1027
|
-
...rest,
|
|
1028
|
-
children: typeof children === "function" ? children({
|
|
1029
|
-
active,
|
|
1030
|
-
label: name
|
|
1031
|
-
}) : children ?? (icon ? /* @__PURE__ */ jsx(InkIcon, { icon }) : null)
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
//#endregion
|
|
1035
|
-
//#region src/components/InkCommand.tsx
|
|
1036
|
-
function InkCommand({ id, asChild = false, children, disabled, onClick, ...rest }) {
|
|
1037
|
-
const { run, enabled, busy, status, label, icon } = useCommand(id);
|
|
1038
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1039
|
-
className: "ink-command",
|
|
1040
|
-
children: [/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1041
|
-
asChild,
|
|
1042
|
-
type: "button",
|
|
1043
|
-
"data-command": id,
|
|
1044
|
-
"aria-label": label || void 0,
|
|
1045
|
-
title: label || void 0,
|
|
1046
|
-
"aria-hidden": label ? void 0 : true,
|
|
1047
|
-
"aria-busy": busy,
|
|
1048
|
-
"aria-disabled": busy || void 0,
|
|
1049
|
-
...rest,
|
|
1050
|
-
disabled: disabled === true || !enabled && !busy,
|
|
1051
|
-
onClick: (e) => {
|
|
1052
|
-
onClick?.(e);
|
|
1053
|
-
if (!e.defaultPrevented) run();
|
|
1054
|
-
},
|
|
1055
|
-
children: typeof children === "function" ? children({
|
|
1056
|
-
label,
|
|
1057
|
-
busy
|
|
1058
|
-
}) : children ?? (icon ? /* @__PURE__ */ jsx(InkIcon, { icon }) : label)
|
|
1059
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
1060
|
-
className: "ink-command-status",
|
|
1061
|
-
role: "status",
|
|
1062
|
-
children: status
|
|
1063
|
-
})]
|
|
1191
|
+
//#region src/components/SignatureMenuPanel.tsx
|
|
1192
|
+
const NO_TEMPLATES$2 = [];
|
|
1193
|
+
/** A saved signature drawn small: ink as filled outlines, an image as itself. */
|
|
1194
|
+
function SignaturePreview({ template }) {
|
|
1195
|
+
if (template.kind === "image") return /* @__PURE__ */ jsx("span", {
|
|
1196
|
+
className: "ink-signature-preview",
|
|
1197
|
+
"data-kind": "image",
|
|
1198
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
1199
|
+
src: template.imagedata,
|
|
1200
|
+
alt: "",
|
|
1201
|
+
draggable: false
|
|
1202
|
+
})
|
|
1064
1203
|
});
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
"data-ink-toolbar-separator": "",
|
|
1078
|
-
...rest
|
|
1204
|
+
const { viewBox, paths } = signaturePreview(template);
|
|
1205
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1206
|
+
className: "ink-signature-preview",
|
|
1207
|
+
"data-kind": "ink",
|
|
1208
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
1209
|
+
viewBox,
|
|
1210
|
+
"aria-hidden": "true",
|
|
1211
|
+
children: paths.map((d, i) => /* @__PURE__ */ jsx("path", {
|
|
1212
|
+
d,
|
|
1213
|
+
fill: "currentColor"
|
|
1214
|
+
}, i))
|
|
1215
|
+
})
|
|
1079
1216
|
});
|
|
1080
1217
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1218
|
+
function SignatureMenuPanel({ items, menu, onChoose, onCreate }) {
|
|
1219
|
+
const { signatures } = useInkContext("SignatureMenuPanel");
|
|
1220
|
+
const templates = useReadable(signatures.templates, NO_TEMPLATES$2);
|
|
1221
|
+
const removeError = useReadable(signatures.removeError, void 0);
|
|
1222
|
+
/** The row of a removed signature, until focus lands on what took its place. */
|
|
1223
|
+
const removedRow = useRef(null);
|
|
1224
|
+
useEffect(() => {
|
|
1225
|
+
const row = removedRow.current;
|
|
1226
|
+
const panel = menu.current;
|
|
1227
|
+
if (row === null || !panel) return;
|
|
1228
|
+
removedRow.current = null;
|
|
1229
|
+
const rows = panel.querySelectorAll(".ink-signature-item");
|
|
1230
|
+
(rows[Math.min(row, rows.length - 1)] ?? panel.querySelector("[data-ink-signature-create]"))?.focus({ preventScroll: true });
|
|
1231
|
+
}, [templates, menu]);
|
|
1232
|
+
const remove = (id, row) => {
|
|
1233
|
+
removedRow.current = row;
|
|
1234
|
+
signatures.remove(id).catch(() => {
|
|
1235
|
+
removedRow.current = null;
|
|
1236
|
+
});
|
|
1237
|
+
};
|
|
1238
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1239
|
+
items.length > 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1240
|
+
role: "group",
|
|
1241
|
+
"aria-label": SIGNATURE_LABELS.saved,
|
|
1242
|
+
className: "ink-signature-list",
|
|
1243
|
+
"data-ink-menu-columns": 2,
|
|
1244
|
+
children: items.map((item, row) => [/* @__PURE__ */ jsx("button", {
|
|
1245
|
+
type: "button",
|
|
1246
|
+
role: "menuitemradio",
|
|
1247
|
+
"aria-checked": item.checked,
|
|
1248
|
+
"aria-label": item.label,
|
|
1249
|
+
tabIndex: -1,
|
|
1250
|
+
className: "ink-select-item ink-menu-item ink-signature-item",
|
|
1251
|
+
"data-ink-select-item": "",
|
|
1252
|
+
"data-key": item.id,
|
|
1253
|
+
"data-state": item.checked ? "checked" : "unchecked",
|
|
1254
|
+
onClick: () => onChoose(item.template),
|
|
1255
|
+
children: /* @__PURE__ */ jsx(SignaturePreview, { template: item.template })
|
|
1256
|
+
}, `${item.id}-choose`), /* @__PURE__ */ jsx("button", {
|
|
1257
|
+
type: "button",
|
|
1258
|
+
role: "menuitem",
|
|
1259
|
+
"aria-label": item.removeLabel,
|
|
1260
|
+
title: item.removeLabel,
|
|
1261
|
+
tabIndex: -1,
|
|
1262
|
+
className: "ink-menu-item ink-signature-remove",
|
|
1263
|
+
"data-ink-signature-remove": "",
|
|
1264
|
+
"data-key": item.id,
|
|
1265
|
+
onClick: () => remove(item.id, row),
|
|
1266
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
1267
|
+
icon: Trash2,
|
|
1268
|
+
size: 16
|
|
1269
|
+
})
|
|
1270
|
+
}, `${item.id}-remove`)])
|
|
1271
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1272
|
+
role: "separator",
|
|
1273
|
+
className: "ink-select-separator"
|
|
1274
|
+
})] }) : null,
|
|
1275
|
+
/* @__PURE__ */ jsxs("button", {
|
|
1276
|
+
type: "button",
|
|
1277
|
+
role: "menuitem",
|
|
1278
|
+
tabIndex: -1,
|
|
1279
|
+
className: "ink-select-item ink-menu-item ink-signature-create",
|
|
1280
|
+
"data-ink-select-item": "",
|
|
1281
|
+
"data-ink-signature-create": "",
|
|
1282
|
+
onClick: onCreate,
|
|
1283
|
+
children: [/* @__PURE__ */ jsx(InkIcon, {
|
|
1284
|
+
icon: Plus,
|
|
1285
|
+
size: 16
|
|
1286
|
+
}), SIGNATURE_LABELS.create]
|
|
1287
|
+
}),
|
|
1288
|
+
removeError ? /* @__PURE__ */ jsx("p", {
|
|
1289
|
+
className: "ink-field-error ink-signature-remove-error",
|
|
1290
|
+
role: "alert",
|
|
1291
|
+
"data-ink-signature-remove-error": "",
|
|
1292
|
+
children: removeError
|
|
1293
|
+
}) : null
|
|
1294
|
+
] });
|
|
1099
1295
|
}
|
|
1100
1296
|
//#endregion
|
|
1101
|
-
//#region src/components/
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1297
|
+
//#region src/components/InkSignFieldPopover.tsx
|
|
1298
|
+
const NO_TEMPLATES$1 = [];
|
|
1299
|
+
/** The field's own button, which focus returns to. */
|
|
1300
|
+
const control = (req) => req?.anchor.querySelector("button.ink-form-control") ?? null;
|
|
1301
|
+
function InkSignFieldPopover({ request, onClose }) {
|
|
1302
|
+
const ctx = useInkContext("InkSignFieldPopover");
|
|
1303
|
+
const { signatures, reportError } = ctx;
|
|
1304
|
+
const templates = useReadable(signatures.templates, NO_TEMPLATES$1);
|
|
1305
|
+
const model = useMemo(() => signatureMenuModel(templates, {}), [templates]);
|
|
1306
|
+
/** The field the dialog signs once its signature is saved. */
|
|
1307
|
+
const [creatingFor, setCreatingFor] = useState(null);
|
|
1308
|
+
const menu = useRef(null);
|
|
1309
|
+
/**
|
|
1310
|
+
* The request the menu was last open for, kept to return focus once it closes. A request
|
|
1311
|
+
* that replaces the open one takes over, so focus returns to the field asked for last.
|
|
1312
|
+
*/
|
|
1313
|
+
const last = useRef(null);
|
|
1314
|
+
/** Set while the menu closes for the dialog, which takes focus instead of the field. */
|
|
1315
|
+
const toDialog = useRef(false);
|
|
1316
|
+
const anchor = useMemo(() => ({ current: request?.anchor ?? null }), [request]);
|
|
1317
|
+
const doc = ctx.document;
|
|
1318
|
+
useEffect(() => {
|
|
1319
|
+
if (request) signatures.clearRemoveError();
|
|
1320
|
+
}, [request, signatures]);
|
|
1321
|
+
const close = useLatest(onClose);
|
|
1322
|
+
useEffect(() => {
|
|
1323
|
+
if (!request) return;
|
|
1324
|
+
last.current = request;
|
|
1325
|
+
return watchDetached(request.anchor, () => close.current());
|
|
1326
|
+
}, [request, close]);
|
|
1327
|
+
/** Focus goes back to the field's button, or to the scroll element once the field's box has gone. */
|
|
1328
|
+
const returnFocus = (req) => {
|
|
1329
|
+
const c = control(req);
|
|
1330
|
+
(c?.isConnected ? c : ctx.viewportEl.get())?.focus({ preventScroll: true });
|
|
1331
|
+
};
|
|
1332
|
+
/** Signs the request's field with `t`; a refusal (signed, read-only by now) goes to `onError`. */
|
|
1333
|
+
const sign = (req, t) => {
|
|
1334
|
+
if (!doc) return;
|
|
1335
|
+
try {
|
|
1336
|
+
signField(doc, req.field, req.widget, t);
|
|
1337
|
+
} catch (e) {
|
|
1338
|
+
if (!(e instanceof InkError)) throw e;
|
|
1339
|
+
reportError(e);
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Popover.Root, {
|
|
1343
|
+
open: request !== null,
|
|
1344
|
+
onOpenChange: (next) => !next && onClose(),
|
|
1345
|
+
children: [/* @__PURE__ */ jsx(Popover.Anchor, { virtualRef: anchor }), /* @__PURE__ */ jsx(Popover.Portal, { children: request ? /* @__PURE__ */ jsx(Popover.Content, {
|
|
1346
|
+
ref: menu,
|
|
1347
|
+
role: "menu",
|
|
1348
|
+
"aria-label": SIGNATURE_LABELS.menu,
|
|
1349
|
+
className: "ink-select-content ink-menu-content",
|
|
1350
|
+
"data-ink-select-content": "",
|
|
1351
|
+
"data-ink-menu": "signature",
|
|
1352
|
+
"data-ink-sign-field-menu": request.field,
|
|
1353
|
+
side: "bottom",
|
|
1354
|
+
align: "start",
|
|
1355
|
+
sideOffset: 4,
|
|
1356
|
+
collisionPadding: 8,
|
|
1357
|
+
onOpenAutoFocus: (e) => {
|
|
1358
|
+
e.preventDefault();
|
|
1359
|
+
focusInitialMenuItem(e.currentTarget);
|
|
1360
|
+
},
|
|
1361
|
+
onCloseAutoFocus: (e) => {
|
|
1362
|
+
e.preventDefault();
|
|
1363
|
+
if (!toDialog.current) returnFocus(last.current);
|
|
1364
|
+
toDialog.current = false;
|
|
1365
|
+
},
|
|
1366
|
+
onInteractOutside: (e) => {
|
|
1367
|
+
const target = e.detail.originalEvent.target;
|
|
1368
|
+
if (target instanceof Node && request.anchor.contains(target)) e.preventDefault();
|
|
1369
|
+
},
|
|
1370
|
+
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1371
|
+
children: /* @__PURE__ */ jsx(SignatureMenuPanel, {
|
|
1372
|
+
items: model.items,
|
|
1373
|
+
menu,
|
|
1374
|
+
onChoose: (t) => {
|
|
1375
|
+
onClose();
|
|
1376
|
+
sign(request, t);
|
|
1377
|
+
},
|
|
1378
|
+
onCreate: () => {
|
|
1379
|
+
toDialog.current = true;
|
|
1380
|
+
setCreatingFor(request);
|
|
1381
|
+
onClose();
|
|
1382
|
+
}
|
|
1383
|
+
})
|
|
1384
|
+
}) : null })]
|
|
1385
|
+
}), /* @__PURE__ */ jsx(InkSignatureDialog, {
|
|
1386
|
+
open: creatingFor !== null,
|
|
1387
|
+
onOpenChange: (next) => {
|
|
1388
|
+
if (!next) setCreatingFor(null);
|
|
1115
1389
|
},
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
//#endregion
|
|
1120
|
-
//#region src/components/InkZoomIn.tsx
|
|
1121
|
-
function InkZoomIn({ asChild = false, className, children, onClick, ...rest }) {
|
|
1122
|
-
const { zoomIn } = useZoom();
|
|
1123
|
-
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1124
|
-
asChild,
|
|
1125
|
-
className: cx("ink-zoom-in", className),
|
|
1126
|
-
"data-ink-zoom-in": "",
|
|
1127
|
-
"aria-label": "Zoom in",
|
|
1128
|
-
title: "Zoom in",
|
|
1129
|
-
...rest,
|
|
1130
|
-
onClick: (e) => {
|
|
1131
|
-
onClick?.(e);
|
|
1132
|
-
if (!e.defaultPrevented) zoomIn();
|
|
1390
|
+
select: false,
|
|
1391
|
+
onSave: (t) => {
|
|
1392
|
+
if (creatingFor) sign(creatingFor, t);
|
|
1133
1393
|
},
|
|
1134
|
-
|
|
1135
|
-
|
|
1394
|
+
onCloseAutoFocus: (e) => {
|
|
1395
|
+
e.preventDefault();
|
|
1396
|
+
returnFocus(last.current);
|
|
1397
|
+
}
|
|
1398
|
+
})] });
|
|
1136
1399
|
}
|
|
1137
1400
|
//#endregion
|
|
1138
|
-
//#region src/components/
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
if (!e.defaultPrevented) zoomOut();
|
|
1151
|
-
},
|
|
1152
|
-
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ZoomOut })
|
|
1153
|
-
});
|
|
1401
|
+
//#region src/components/InkViewport.tsx
|
|
1402
|
+
/** A key per document, so the field menu and its dialog start afresh for each one. */
|
|
1403
|
+
const documentKeys = /* @__PURE__ */ new WeakMap();
|
|
1404
|
+
let documentKeyCount = 0;
|
|
1405
|
+
function documentKey(doc) {
|
|
1406
|
+
if (!doc) return 0;
|
|
1407
|
+
let key = documentKeys.get(doc);
|
|
1408
|
+
if (key === void 0) {
|
|
1409
|
+
key = ++documentKeyCount;
|
|
1410
|
+
documentKeys.set(doc, key);
|
|
1411
|
+
}
|
|
1412
|
+
return key;
|
|
1154
1413
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
const
|
|
1158
|
-
const
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}];
|
|
1167
|
-
function Item$1({ value, label }) {
|
|
1168
|
-
return /* @__PURE__ */ jsxs(Select.Item, {
|
|
1169
|
-
value,
|
|
1170
|
-
className: "ink-select-item",
|
|
1171
|
-
"data-ink-select-item": "",
|
|
1172
|
-
children: [/* @__PURE__ */ jsx(Select.ItemText, { children: label }), /* @__PURE__ */ jsx(Select.ItemIndicator, {
|
|
1173
|
-
className: "ink-select-indicator",
|
|
1174
|
-
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
1175
|
-
icon: Check,
|
|
1176
|
-
size: 16
|
|
1177
|
-
})
|
|
1178
|
-
})]
|
|
1414
|
+
function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2, loading, empty, error, renderAnnotation, renderPageError, className, ...rest }) {
|
|
1415
|
+
const ctx = useInkContext("InkViewport");
|
|
1416
|
+
const scroll = useRef(null);
|
|
1417
|
+
const custom = !!renderAnnotation;
|
|
1418
|
+
const latest = useLatest({
|
|
1419
|
+
ctx,
|
|
1420
|
+
mode,
|
|
1421
|
+
rotation,
|
|
1422
|
+
gap,
|
|
1423
|
+
buffer,
|
|
1424
|
+
custom
|
|
1179
1425
|
});
|
|
1180
|
-
|
|
1426
|
+
const engine = useRef(null);
|
|
1427
|
+
/** Page hosts, mounts and errors the engine reported; InkPage renders portals for them. */
|
|
1428
|
+
const [pages, setPages] = useState([]);
|
|
1429
|
+
/**
|
|
1430
|
+
* Numbered before each build starts, so the pages a build mounts carry its own number. A
|
|
1431
|
+
* rebuild gives each page a new host and mount, so pages from an earlier build must remount,
|
|
1432
|
+
* not be reused.
|
|
1433
|
+
*/
|
|
1434
|
+
const buildCount = useRef(0);
|
|
1435
|
+
/** The construction options the engine was last built with, so a rebuild runs only on a change. */
|
|
1436
|
+
const builtWith = useRef(null);
|
|
1437
|
+
/** The zoom and page last reported to the host. */
|
|
1438
|
+
const lastReportedZoom = useRef(void 0);
|
|
1439
|
+
const lastReportedPage = useRef(void 0);
|
|
1440
|
+
/** The signature field whose menu is open. */
|
|
1441
|
+
const [signRequest, setSignRequest] = useState(null);
|
|
1442
|
+
/** The form script's message on show, and how many have been shown. */
|
|
1443
|
+
const [alert, setAlert] = useState(null);
|
|
1444
|
+
useEffect(() => {
|
|
1445
|
+
const { ctx: c, mode: m, rotation: r, gap: g, buffer: b, custom: cu } = latest.current;
|
|
1446
|
+
const doc = c.document;
|
|
1447
|
+
const el = scroll.current;
|
|
1448
|
+
lastReportedZoom.current = void 0;
|
|
1449
|
+
lastReportedPage.current = void 0;
|
|
1450
|
+
if (!doc || !el || c.status.state !== "ready") return;
|
|
1451
|
+
let first = true;
|
|
1452
|
+
let stops = [];
|
|
1453
|
+
buildCount.current += 1;
|
|
1454
|
+
builtWith.current = {
|
|
1455
|
+
gap: g,
|
|
1456
|
+
buffer: b,
|
|
1457
|
+
custom: cu
|
|
1458
|
+
};
|
|
1459
|
+
const e = mountViewer({
|
|
1460
|
+
container: el,
|
|
1461
|
+
document: doc,
|
|
1462
|
+
zoom: c.zoomModel ?? "fit-width",
|
|
1463
|
+
...c.pageModel === void 0 ? {} : { page: c.pageModel },
|
|
1464
|
+
mode: m,
|
|
1465
|
+
rotation: r,
|
|
1466
|
+
gap: g,
|
|
1467
|
+
buffer: b,
|
|
1468
|
+
tool: c.tool,
|
|
1469
|
+
...c.hostTools ? { tools: c.hostTools } : {},
|
|
1470
|
+
...c.pluginHost ? { plugins: c.pluginHost } : {},
|
|
1471
|
+
...c.search ? { search: c.search } : {},
|
|
1472
|
+
decorateAnnotation: (a) => latest.current.ctx.decorateAnnotation(a),
|
|
1473
|
+
...c.toolStyle ? { toolStyle: c.toolStyle } : {},
|
|
1474
|
+
toolHistory: { batch: (fn) => latest.current.ctx.history.batch(fn) },
|
|
1475
|
+
toolOptions: c.toolOptions,
|
|
1476
|
+
fonts: [...c.fonts],
|
|
1477
|
+
signatureLibrary: () => latest.current.ctx.signatures,
|
|
1478
|
+
customAnnotations: cu,
|
|
1479
|
+
...c.formHighlight === void 0 ? {} : { formHighlight: c.formHighlight },
|
|
1480
|
+
onSignField: (req) => setSignRequest((open) => open?.widget === req.widget ? null : req),
|
|
1481
|
+
onFormAction: (a) => latest.current.ctx.reportFormAction(a),
|
|
1482
|
+
onFormAlert: (message) => setAlert((prev) => ({
|
|
1483
|
+
message,
|
|
1484
|
+
shown: (prev?.shown ?? 0) + 1
|
|
1485
|
+
})),
|
|
1486
|
+
onActivate: (id) => latest.current.ctx.setActivated(id),
|
|
1487
|
+
onBuild: (vp, tc) => {
|
|
1488
|
+
latest.current.ctx.setViewport(vp, tc);
|
|
1489
|
+
if (first) {
|
|
1490
|
+
first = false;
|
|
1491
|
+
lastReportedZoom.current = vp.zoom.get();
|
|
1492
|
+
lastReportedPage.current = 0;
|
|
1493
|
+
}
|
|
1494
|
+
for (const stop of stops) stop();
|
|
1495
|
+
const reportZoom = (z) => {
|
|
1496
|
+
if (z === lastReportedZoom.current) return;
|
|
1497
|
+
lastReportedZoom.current = z;
|
|
1498
|
+
latest.current.ctx.reportZoom(z);
|
|
1499
|
+
};
|
|
1500
|
+
const reportPage = (p) => {
|
|
1501
|
+
if (p === lastReportedPage.current) return;
|
|
1502
|
+
lastReportedPage.current = p;
|
|
1503
|
+
latest.current.ctx.reportPage(p);
|
|
1504
|
+
};
|
|
1505
|
+
const followTool = (id) => {
|
|
1506
|
+
if (id !== latest.current.ctx.tool) latest.current.ctx.setTool(id);
|
|
1507
|
+
};
|
|
1508
|
+
stops = [
|
|
1509
|
+
vp.zoom.subscribe(reportZoom),
|
|
1510
|
+
vp.currentPage.subscribe(reportPage),
|
|
1511
|
+
tc.active.subscribe(followTool)
|
|
1512
|
+
];
|
|
1513
|
+
reportZoom(vp.zoom.get());
|
|
1514
|
+
reportPage(vp.currentPage.get());
|
|
1515
|
+
},
|
|
1516
|
+
onPageMount: (page, mount, host) => {
|
|
1517
|
+
const build = buildCount.current;
|
|
1518
|
+
const custom = builtWith.current?.custom ?? false;
|
|
1519
|
+
setPages((prev) => [...prev, {
|
|
1520
|
+
build,
|
|
1521
|
+
page,
|
|
1522
|
+
host,
|
|
1523
|
+
mount,
|
|
1524
|
+
error: null,
|
|
1525
|
+
custom
|
|
1526
|
+
}]);
|
|
1527
|
+
},
|
|
1528
|
+
onPageUnmount: (page) => setPages((prev) => prev.filter((p) => p.page !== page)),
|
|
1529
|
+
onPageError: (page, err) => setPages((prev) => prev.map((p) => p.page === page ? {
|
|
1530
|
+
...p,
|
|
1531
|
+
error: err
|
|
1532
|
+
} : p))
|
|
1533
|
+
});
|
|
1534
|
+
engine.current = e;
|
|
1535
|
+
return () => {
|
|
1536
|
+
for (const stop of stops) stop();
|
|
1537
|
+
e.destroy();
|
|
1538
|
+
engine.current = null;
|
|
1539
|
+
builtWith.current = null;
|
|
1540
|
+
c.setViewport(null, null);
|
|
1541
|
+
setPages((prev) => prev.length === 0 ? prev : []);
|
|
1542
|
+
setSignRequest(null);
|
|
1543
|
+
setAlert(null);
|
|
1544
|
+
};
|
|
1545
|
+
}, [
|
|
1546
|
+
ctx.document,
|
|
1547
|
+
ctx.status.state,
|
|
1548
|
+
ctx.pluginHost,
|
|
1549
|
+
ctx.search,
|
|
1550
|
+
latest
|
|
1551
|
+
]);
|
|
1552
|
+
const { viewportEl } = ctx;
|
|
1553
|
+
useLayoutEffect(() => {
|
|
1554
|
+
const el = scroll.current;
|
|
1555
|
+
viewportEl.set(el);
|
|
1556
|
+
return () => {
|
|
1557
|
+
if (viewportEl.get() === el) viewportEl.set(null);
|
|
1558
|
+
};
|
|
1559
|
+
}, [viewportEl]);
|
|
1560
|
+
const { onDecorateAnnotationChange } = ctx;
|
|
1561
|
+
useEffect(() => onDecorateAnnotationChange(() => engine.current?.redecorate()), [onDecorateAnnotationChange]);
|
|
1562
|
+
useEffect(() => {
|
|
1563
|
+
const built = builtWith.current;
|
|
1564
|
+
if (!engine.current || !built) return;
|
|
1565
|
+
if (built.gap === gap && built.buffer === buffer && built.custom === custom) return;
|
|
1566
|
+
buildCount.current += 1;
|
|
1567
|
+
builtWith.current = {
|
|
1568
|
+
gap,
|
|
1569
|
+
buffer,
|
|
1570
|
+
custom
|
|
1571
|
+
};
|
|
1572
|
+
engine.current.rebuild({
|
|
1573
|
+
gap,
|
|
1574
|
+
buffer,
|
|
1575
|
+
customAnnotations: custom
|
|
1576
|
+
});
|
|
1577
|
+
}, [
|
|
1578
|
+
gap,
|
|
1579
|
+
buffer,
|
|
1580
|
+
custom
|
|
1581
|
+
]);
|
|
1582
|
+
useEffect(() => {
|
|
1583
|
+
const vp = engine.current?.viewport;
|
|
1584
|
+
const z = ctx.zoomModel;
|
|
1585
|
+
if (vp && z !== void 0 && z !== vp.zoom.get()) vp.setZoom(z);
|
|
1586
|
+
}, [ctx.zoomModel]);
|
|
1587
|
+
useEffect(() => {
|
|
1588
|
+
const vp = engine.current?.viewport;
|
|
1589
|
+
const p = ctx.pageModel;
|
|
1590
|
+
if (!vp || p === void 0 || p === lastReportedPage.current) return;
|
|
1591
|
+
if (p !== vp.currentPage.get()) vp.scrollToPage(p);
|
|
1592
|
+
}, [ctx.pageModel]);
|
|
1593
|
+
useEffect(() => {
|
|
1594
|
+
const tc = engine.current?.tools;
|
|
1595
|
+
if (tc && tc.active.get() !== ctx.tool) tc.set(ctx.tool);
|
|
1596
|
+
}, [ctx.tool]);
|
|
1597
|
+
useEffect(() => {
|
|
1598
|
+
engine.current?.setFormHighlight(ctx.formHighlight ?? true);
|
|
1599
|
+
}, [ctx.formHighlight]);
|
|
1600
|
+
useEffect(() => {
|
|
1601
|
+
const vp = engine.current?.viewport;
|
|
1602
|
+
if (vp && vp.mode.get() !== mode) vp.setMode(mode);
|
|
1603
|
+
}, [mode]);
|
|
1604
|
+
useEffect(() => {
|
|
1605
|
+
const vp = engine.current?.viewport;
|
|
1606
|
+
if (vp && vp.rotation.get() !== rotation) vp.setRotation(rotation);
|
|
1607
|
+
}, [rotation]);
|
|
1608
|
+
const state = ctx.status.state;
|
|
1609
|
+
const err = ctx.status.state === "error" ? ctx.status.error : null;
|
|
1610
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1611
|
+
...rest,
|
|
1612
|
+
className: cx("ink-viewport-root", className),
|
|
1613
|
+
"data-ink-viewport-root": "",
|
|
1614
|
+
"data-state": state,
|
|
1615
|
+
children: [
|
|
1616
|
+
state === "loading" ? /* @__PURE__ */ jsx("div", {
|
|
1617
|
+
className: "ink-status",
|
|
1618
|
+
role: "status",
|
|
1619
|
+
"aria-live": "polite",
|
|
1620
|
+
children: loading === void 0 ? "Loading document…" : loading
|
|
1621
|
+
}) : err ? /* @__PURE__ */ jsx("div", {
|
|
1622
|
+
className: "ink-status ink-error",
|
|
1623
|
+
role: "alert",
|
|
1624
|
+
children: typeof error === "function" ? error({ error: err }) : error !== void 0 ? error : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
|
|
1625
|
+
className: "ink-error-title",
|
|
1626
|
+
children: "This document could not be opened."
|
|
1627
|
+
}), /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("code", {
|
|
1628
|
+
className: "ink-error-code",
|
|
1629
|
+
title: err.message,
|
|
1630
|
+
children: err.code
|
|
1631
|
+
}) })] })
|
|
1632
|
+
}) : state === "idle" ? /* @__PURE__ */ jsx("div", {
|
|
1633
|
+
className: "ink-status ink-empty",
|
|
1634
|
+
children: empty
|
|
1635
|
+
}) : null,
|
|
1636
|
+
/* @__PURE__ */ jsx("div", {
|
|
1637
|
+
ref: scroll,
|
|
1638
|
+
className: "ink-scroll",
|
|
1639
|
+
"data-ink-scroll": "",
|
|
1640
|
+
tabIndex: 0,
|
|
1641
|
+
role: "document",
|
|
1642
|
+
"aria-label": "Document"
|
|
1643
|
+
}),
|
|
1644
|
+
pages.map((p) => /* @__PURE__ */ jsx(InkPage, {
|
|
1645
|
+
page: p.page,
|
|
1646
|
+
host: p.host,
|
|
1647
|
+
mount: p.mount,
|
|
1648
|
+
error: p.error,
|
|
1649
|
+
customAnnotations: p.custom,
|
|
1650
|
+
renderAnnotation,
|
|
1651
|
+
renderPageError
|
|
1652
|
+
}, `${p.build}:${p.page}`)),
|
|
1653
|
+
/* @__PURE__ */ jsx(InkSignFieldPopover, {
|
|
1654
|
+
request: signRequest,
|
|
1655
|
+
onClose: () => setSignRequest(null)
|
|
1656
|
+
}, documentKey(ctx.document)),
|
|
1657
|
+
/* @__PURE__ */ jsx(InkFormAlert, {
|
|
1658
|
+
message: alert?.message ?? null,
|
|
1659
|
+
shown: alert?.shown ?? 0,
|
|
1660
|
+
fallback: () => scroll.current,
|
|
1661
|
+
onClose: () => setAlert(null)
|
|
1662
|
+
})
|
|
1663
|
+
]
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
//#endregion
|
|
1667
|
+
//#region src/components/InkToolbar.tsx
|
|
1668
|
+
function InkToolbar({ label = "Document tools", asChild = false, className, children, ...rest }) {
|
|
1669
|
+
return /* @__PURE__ */ jsx(Toolbar.Root, {
|
|
1670
|
+
asChild,
|
|
1671
|
+
orientation: "horizontal",
|
|
1672
|
+
"aria-label": label,
|
|
1673
|
+
"data-ink-toolbar": "",
|
|
1674
|
+
className: cx("ink-toolbar", className),
|
|
1675
|
+
...rest,
|
|
1676
|
+
children
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
//#endregion
|
|
1680
|
+
//#region src/components/InkToolButton.tsx
|
|
1681
|
+
const ICONS = {
|
|
1682
|
+
"select-text": TextCursor,
|
|
1683
|
+
select: SquareDashedMousePointer,
|
|
1684
|
+
highlight: Highlighter,
|
|
1685
|
+
"redact-area": Square,
|
|
1686
|
+
sketch: PenTool,
|
|
1687
|
+
draw: PenLine,
|
|
1688
|
+
note: MessageSquare,
|
|
1689
|
+
freetext: Type,
|
|
1690
|
+
signature: Signature
|
|
1691
|
+
};
|
|
1692
|
+
function InkToolButton({ tool, label, asChild = false, className, children, onClick, ...rest }) {
|
|
1693
|
+
const { hostTools, pluginHost } = useInkContext("InkToolButton");
|
|
1694
|
+
const { tool: current, setTool } = useTool();
|
|
1695
|
+
const active = current === tool;
|
|
1696
|
+
/** A host or plugin tool by this id, for the name and icon core has none for. */
|
|
1697
|
+
const own = [...hostTools ?? [], ...pluginHost?.tools ?? []].find((t) => t.id === tool);
|
|
1698
|
+
const name = label ?? TOOL_LABELS[tool] ?? own?.label ?? tool;
|
|
1699
|
+
const icon = ICONS[tool] ?? own?.icon;
|
|
1700
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1701
|
+
asChild,
|
|
1702
|
+
className: cx("ink-tool-button", className),
|
|
1703
|
+
"data-ink-tool-button": "",
|
|
1704
|
+
"data-tool": tool,
|
|
1705
|
+
"data-state": active ? "on" : "off",
|
|
1706
|
+
"aria-pressed": active,
|
|
1707
|
+
"aria-label": name,
|
|
1708
|
+
title: name,
|
|
1709
|
+
onClick: (e) => {
|
|
1710
|
+
onClick?.(e);
|
|
1711
|
+
if (!e.defaultPrevented) setTool(tool);
|
|
1712
|
+
},
|
|
1713
|
+
...rest,
|
|
1714
|
+
children: typeof children === "function" ? children({
|
|
1715
|
+
active,
|
|
1716
|
+
label: name
|
|
1717
|
+
}) : children ?? (icon ? /* @__PURE__ */ jsx(InkIcon, { icon }) : null)
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
//#endregion
|
|
1721
|
+
//#region src/components/InkCommand.tsx
|
|
1722
|
+
function InkCommand({ id, asChild = false, children, disabled, onClick, ...rest }) {
|
|
1723
|
+
const { run, enabled, busy, status, label, icon } = useCommand(id);
|
|
1724
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1725
|
+
className: "ink-command",
|
|
1726
|
+
children: [/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1727
|
+
asChild,
|
|
1728
|
+
type: "button",
|
|
1729
|
+
"data-command": id,
|
|
1730
|
+
"aria-label": label || void 0,
|
|
1731
|
+
title: label || void 0,
|
|
1732
|
+
"aria-hidden": label ? void 0 : true,
|
|
1733
|
+
"aria-busy": busy,
|
|
1734
|
+
"aria-disabled": busy || void 0,
|
|
1735
|
+
...rest,
|
|
1736
|
+
disabled: disabled === true || !enabled && !busy,
|
|
1737
|
+
onClick: (e) => {
|
|
1738
|
+
onClick?.(e);
|
|
1739
|
+
if (!e.defaultPrevented) run();
|
|
1740
|
+
},
|
|
1741
|
+
children: typeof children === "function" ? children({
|
|
1742
|
+
label,
|
|
1743
|
+
busy
|
|
1744
|
+
}) : children ?? (icon ? /* @__PURE__ */ jsx(InkIcon, { icon }) : label)
|
|
1745
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1746
|
+
className: "ink-command-status",
|
|
1747
|
+
role: "status",
|
|
1748
|
+
children: status
|
|
1749
|
+
})]
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
//#endregion
|
|
1753
|
+
//#region src/components/InkToolbarSeparator.tsx
|
|
1754
|
+
/**
|
|
1755
|
+
* A divider between groups of controls in a horizontal toolbar. Not focusable. A plain div
|
|
1756
|
+
* rather than Radix's separator, which reads as horizontal inside a horizontal toolbar.
|
|
1757
|
+
*/
|
|
1758
|
+
function InkToolbarSeparator({ className, ...rest }) {
|
|
1759
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1760
|
+
role: "separator",
|
|
1761
|
+
"aria-orientation": "vertical",
|
|
1762
|
+
className: cx("ink-toolbar-separator", className),
|
|
1763
|
+
"data-ink-toolbar-separator": "",
|
|
1764
|
+
...rest
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
//#endregion
|
|
1768
|
+
//#region src/components/InkUndo.tsx
|
|
1769
|
+
function InkUndo({ asChild = false, className, children, disabled, onClick, ...rest }) {
|
|
1770
|
+
const { canUndo, undo } = useHistory();
|
|
1771
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1772
|
+
asChild,
|
|
1773
|
+
className: cx("ink-undo", className),
|
|
1774
|
+
"data-ink-undo": "",
|
|
1775
|
+
"aria-label": "Undo",
|
|
1776
|
+
title: "Undo",
|
|
1777
|
+
...rest,
|
|
1778
|
+
disabled: disabled === true || !canUndo,
|
|
1779
|
+
onClick: (e) => {
|
|
1780
|
+
onClick?.(e);
|
|
1781
|
+
if (!e.defaultPrevented) undo();
|
|
1782
|
+
},
|
|
1783
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: Undo2 })
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
//#endregion
|
|
1787
|
+
//#region src/components/InkRedo.tsx
|
|
1788
|
+
function InkRedo({ asChild = false, className, children, disabled, onClick, ...rest }) {
|
|
1789
|
+
const { canRedo, redo } = useHistory();
|
|
1790
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1791
|
+
asChild,
|
|
1792
|
+
className: cx("ink-redo", className),
|
|
1793
|
+
"data-ink-redo": "",
|
|
1794
|
+
"aria-label": "Redo",
|
|
1795
|
+
title: "Redo",
|
|
1796
|
+
...rest,
|
|
1797
|
+
disabled: disabled === true || !canRedo,
|
|
1798
|
+
onClick: (e) => {
|
|
1799
|
+
onClick?.(e);
|
|
1800
|
+
if (!e.defaultPrevented) redo();
|
|
1801
|
+
},
|
|
1802
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: Redo2 })
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
//#endregion
|
|
1806
|
+
//#region src/components/InkSearchButton.tsx
|
|
1807
|
+
/**
|
|
1808
|
+
* Opens the find bar; pressed while it is open. Pressing it again focuses the field. Place it
|
|
1809
|
+
* in a toolbar next to `InkFindBar`, which it needs for the field to focus. Disabled until a
|
|
1810
|
+
* document is open.
|
|
1811
|
+
*/
|
|
1812
|
+
function InkSearchButton({ asChild = false, className, children, disabled, onClick, ...rest }) {
|
|
1813
|
+
const { rootEl } = useInkContext("InkSearchButton");
|
|
1814
|
+
const { state, open } = useSearch();
|
|
1815
|
+
const { isReady } = useDocument();
|
|
1816
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1817
|
+
...rest,
|
|
1818
|
+
asChild,
|
|
1819
|
+
className: cx("ink-search-button", className),
|
|
1820
|
+
"data-ink-search-button": "",
|
|
1821
|
+
"aria-label": "Find",
|
|
1822
|
+
title: "Find",
|
|
1823
|
+
"aria-pressed": state.open,
|
|
1824
|
+
"data-state": state.open ? "on" : "off",
|
|
1825
|
+
disabled: !isReady || !!disabled,
|
|
1826
|
+
onClick: (e) => {
|
|
1827
|
+
onClick?.(e);
|
|
1828
|
+
if (e.defaultPrevented) return;
|
|
1829
|
+
flushSync(() => open());
|
|
1830
|
+
const field = rootEl?.querySelector("[data-ink-find-bar] input");
|
|
1831
|
+
field?.focus();
|
|
1832
|
+
field?.select();
|
|
1833
|
+
},
|
|
1834
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: Search })
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
//#endregion
|
|
1838
|
+
//#region src/components/InkFindBar.tsx
|
|
1839
|
+
/**
|
|
1840
|
+
* The find bar: a field, the match count, previous and next, a match-case toggle and close,
|
|
1841
|
+
* a view of the root's search state. Place it anywhere inside `InkRoot`: it portals itself
|
|
1842
|
+
* into `[data-ink-viewport-root]`, the positioned ancestor the theme floats it from over the
|
|
1843
|
+
* top-right of the pages (as `InkThreadPopover` anchors to the root's overlay rather than
|
|
1844
|
+
* where it is written). It renders nothing until `InkViewport` has mounted, and is hidden
|
|
1845
|
+
* while the search is closed. While it is mounted it registers with the search controller,
|
|
1846
|
+
* which is what lets Ctrl/Cmd+F open it.
|
|
1847
|
+
*
|
|
1848
|
+
* Enter in the field steps to the next match, Shift+Enter to the previous, Escape closes.
|
|
1849
|
+
* Closing returns focus to where it was before the bar opened, else to the document.
|
|
1850
|
+
*/
|
|
1851
|
+
function InkFindBar({ className, ...rest }) {
|
|
1852
|
+
const ctx = useInkContext("InkFindBar");
|
|
1853
|
+
const { state, setQuery, setMatchCase, next, previous, close } = useSearch();
|
|
1854
|
+
const scroll = useReadable(ctx.viewportEl, null);
|
|
1855
|
+
/** The viewport root: the scroll element's parent, once InkViewport has mounted. */
|
|
1856
|
+
const target = scroll?.parentElement ?? null;
|
|
1857
|
+
const bar = useRef(null);
|
|
1858
|
+
const field = useRef(null);
|
|
1859
|
+
const live = useRef(null);
|
|
1860
|
+
/** Where focus was when the bar opened, and whether it was open on the last pass. */
|
|
1861
|
+
const before = useRef(null);
|
|
1862
|
+
const wasOpen = useRef(false);
|
|
1863
|
+
useLayoutEffect(() => {
|
|
1864
|
+
const el = bar.current;
|
|
1865
|
+
const input = field.current;
|
|
1866
|
+
if (!el || !input) return;
|
|
1867
|
+
if (state.open !== wasOpen.current) {
|
|
1868
|
+
wasOpen.current = state.open;
|
|
1869
|
+
const doc = el.ownerDocument;
|
|
1870
|
+
if (state.open) {
|
|
1871
|
+
const active = doc.activeElement;
|
|
1872
|
+
before.current = active instanceof HTMLElement && active !== doc.body && !el.contains(active) ? active : null;
|
|
1873
|
+
input.focus();
|
|
1874
|
+
input.select();
|
|
1875
|
+
} else {
|
|
1876
|
+
if (el.contains(doc.activeElement) || doc.activeElement === doc.body) (before.current?.isConnected && !el.contains(before.current) ? before.current : scroll)?.focus({ preventScroll: true });
|
|
1877
|
+
before.current = null;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (input.value !== state.query) {
|
|
1881
|
+
input.value = state.query;
|
|
1882
|
+
if (el.ownerDocument.activeElement === input) input.select();
|
|
1883
|
+
}
|
|
1884
|
+
}, [
|
|
1885
|
+
state.open,
|
|
1886
|
+
state.query,
|
|
1887
|
+
scroll
|
|
1888
|
+
]);
|
|
1889
|
+
const { search } = ctx;
|
|
1890
|
+
useEffect(() => {
|
|
1891
|
+
if (!search || !target) return;
|
|
1892
|
+
return search.attachBar();
|
|
1893
|
+
}, [search, target]);
|
|
1894
|
+
useEffect(() => {
|
|
1895
|
+
const el = live.current;
|
|
1896
|
+
if (!search || !el) return;
|
|
1897
|
+
const announce = findAnnouncer();
|
|
1898
|
+
const say = (s) => {
|
|
1899
|
+
const text = announce(s);
|
|
1900
|
+
if (el.textContent !== text) el.textContent = text;
|
|
1901
|
+
};
|
|
1902
|
+
say(search.state.get());
|
|
1903
|
+
return search.state.subscribe(say);
|
|
1904
|
+
}, [search, target]);
|
|
1905
|
+
useEffect(() => {
|
|
1906
|
+
const el = bar.current;
|
|
1907
|
+
if (!search || !el) return;
|
|
1908
|
+
const onKeyDown = (e) => {
|
|
1909
|
+
if (e.key !== "Escape" || e.defaultPrevented || e.isComposing) return;
|
|
1910
|
+
e.preventDefault();
|
|
1911
|
+
e.stopPropagation();
|
|
1912
|
+
search.close();
|
|
1913
|
+
};
|
|
1914
|
+
el.addEventListener("keydown", onKeyDown);
|
|
1915
|
+
return () => el.removeEventListener("keydown", onKeyDown);
|
|
1916
|
+
}, [search, target]);
|
|
1917
|
+
if (!target) return null;
|
|
1918
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
1919
|
+
...rest,
|
|
1920
|
+
ref: bar,
|
|
1921
|
+
className: cx("ink-find-bar", className),
|
|
1922
|
+
"data-ink-find-bar": "",
|
|
1923
|
+
role: "search",
|
|
1924
|
+
hidden: !state.open,
|
|
1925
|
+
children: [
|
|
1926
|
+
/* @__PURE__ */ jsx("input", {
|
|
1927
|
+
ref: field,
|
|
1928
|
+
type: "text",
|
|
1929
|
+
"aria-label": "Find in document",
|
|
1930
|
+
autoComplete: "off",
|
|
1931
|
+
spellCheck: false,
|
|
1932
|
+
onInput: (e) => setQuery(e.currentTarget.value),
|
|
1933
|
+
onKeyDown: (e) => {
|
|
1934
|
+
if (e.key !== "Enter" || e.nativeEvent.isComposing) return;
|
|
1935
|
+
e.preventDefault();
|
|
1936
|
+
if (e.shiftKey) previous();
|
|
1937
|
+
else next();
|
|
1938
|
+
}
|
|
1939
|
+
}),
|
|
1940
|
+
/* @__PURE__ */ jsx("span", {
|
|
1941
|
+
className: "ink-find-count",
|
|
1942
|
+
"data-ink-find-count": "",
|
|
1943
|
+
children: findCountText(state)
|
|
1944
|
+
}),
|
|
1945
|
+
/* @__PURE__ */ jsx("button", {
|
|
1946
|
+
type: "button",
|
|
1947
|
+
"data-ink-find-previous": "",
|
|
1948
|
+
"aria-label": "Previous match",
|
|
1949
|
+
title: "Previous match",
|
|
1950
|
+
onClick: () => previous(),
|
|
1951
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronUp })
|
|
1952
|
+
}),
|
|
1953
|
+
/* @__PURE__ */ jsx("button", {
|
|
1954
|
+
type: "button",
|
|
1955
|
+
"data-ink-find-next": "",
|
|
1956
|
+
"aria-label": "Next match",
|
|
1957
|
+
title: "Next match",
|
|
1958
|
+
onClick: () => next(),
|
|
1959
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronDown })
|
|
1960
|
+
}),
|
|
1961
|
+
/* @__PURE__ */ jsx("button", {
|
|
1962
|
+
type: "button",
|
|
1963
|
+
"data-ink-find-match-case": "",
|
|
1964
|
+
"aria-label": "Match case",
|
|
1965
|
+
title: "Match case",
|
|
1966
|
+
"aria-pressed": state.matchCase,
|
|
1967
|
+
onClick: () => setMatchCase(!state.matchCase),
|
|
1968
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: CaseSensitive })
|
|
1969
|
+
}),
|
|
1970
|
+
/* @__PURE__ */ jsx("button", {
|
|
1971
|
+
type: "button",
|
|
1972
|
+
"data-ink-find-close": "",
|
|
1973
|
+
"aria-label": "Close find",
|
|
1974
|
+
title: "Close find",
|
|
1975
|
+
onClick: () => close(),
|
|
1976
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: X })
|
|
1977
|
+
}),
|
|
1978
|
+
/* @__PURE__ */ jsx("div", {
|
|
1979
|
+
ref: live,
|
|
1980
|
+
className: "ink-visually-hidden",
|
|
1981
|
+
"data-ink-find-status": "",
|
|
1982
|
+
"aria-live": "polite"
|
|
1983
|
+
})
|
|
1984
|
+
]
|
|
1985
|
+
}), target);
|
|
1986
|
+
}
|
|
1987
|
+
//#endregion
|
|
1988
|
+
//#region src/components/InkZoomIn.tsx
|
|
1989
|
+
function InkZoomIn({ asChild = false, className, children, onClick, ...rest }) {
|
|
1990
|
+
const { zoomIn } = useZoom();
|
|
1991
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1992
|
+
asChild,
|
|
1993
|
+
className: cx("ink-zoom-in", className),
|
|
1994
|
+
"data-ink-zoom-in": "",
|
|
1995
|
+
"aria-label": "Zoom in",
|
|
1996
|
+
title: "Zoom in",
|
|
1997
|
+
...rest,
|
|
1998
|
+
onClick: (e) => {
|
|
1999
|
+
onClick?.(e);
|
|
2000
|
+
if (!e.defaultPrevented) zoomIn();
|
|
2001
|
+
},
|
|
2002
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ZoomIn })
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
//#endregion
|
|
2006
|
+
//#region src/components/InkZoomOut.tsx
|
|
2007
|
+
function InkZoomOut({ asChild = false, className, children, onClick, ...rest }) {
|
|
2008
|
+
const { zoomOut } = useZoom();
|
|
2009
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
2010
|
+
asChild,
|
|
2011
|
+
className: cx("ink-zoom-out", className),
|
|
2012
|
+
"data-ink-zoom-out": "",
|
|
2013
|
+
"aria-label": "Zoom out",
|
|
2014
|
+
title: "Zoom out",
|
|
2015
|
+
...rest,
|
|
2016
|
+
onClick: (e) => {
|
|
2017
|
+
onClick?.(e);
|
|
2018
|
+
if (!e.defaultPrevented) zoomOut();
|
|
2019
|
+
},
|
|
2020
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ZoomOut })
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
//#endregion
|
|
2024
|
+
//#region src/components/InkZoomSelect.tsx
|
|
2025
|
+
const CUSTOM = "custom:";
|
|
2026
|
+
const level = (z) => ZOOM_LEVELS.find((l) => Math.abs(l - z) < .001);
|
|
2027
|
+
const percent = (z) => `${Math.round(z * 100)}%`;
|
|
2028
|
+
const FITS = [{
|
|
2029
|
+
value: "fit-width",
|
|
2030
|
+
label: "Fit width"
|
|
2031
|
+
}, {
|
|
2032
|
+
value: "fit-page",
|
|
2033
|
+
label: "Fit page"
|
|
2034
|
+
}];
|
|
2035
|
+
function Item$1({ value, label }) {
|
|
2036
|
+
return /* @__PURE__ */ jsxs(Select.Item, {
|
|
2037
|
+
value,
|
|
2038
|
+
className: "ink-select-item",
|
|
2039
|
+
"data-ink-select-item": "",
|
|
2040
|
+
children: [/* @__PURE__ */ jsx(Select.ItemText, { children: label }), /* @__PURE__ */ jsx(Select.ItemIndicator, {
|
|
2041
|
+
className: "ink-select-indicator",
|
|
2042
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
2043
|
+
icon: Check,
|
|
2044
|
+
size: 16
|
|
2045
|
+
})
|
|
2046
|
+
})]
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
1181
2049
|
function InkZoomSelect({ className, ...rest }) {
|
|
1182
2050
|
const { zoom, scale, setZoom } = useZoom();
|
|
1183
2051
|
/** A numeric zoom that is not one of the levels (zoom in and out multiply). */
|
|
@@ -1299,524 +2167,291 @@ function InkPageCount({ className, ...rest }) {
|
|
|
1299
2167
|
}
|
|
1300
2168
|
//#endregion
|
|
1301
2169
|
//#region src/components/InkPrevPage.tsx
|
|
1302
|
-
function InkPrevPage({ asChild = false, className, children, disabled, onClick, ...rest }) {
|
|
1303
|
-
const { current, previous } = usePage();
|
|
1304
|
-
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1305
|
-
asChild,
|
|
1306
|
-
className: cx("ink-prev-page", className),
|
|
1307
|
-
"data-ink-prev-page": "",
|
|
1308
|
-
"aria-label": "Previous page",
|
|
1309
|
-
title: "Previous page",
|
|
1310
|
-
disabled: disabled === true || current <= 0,
|
|
1311
|
-
...rest,
|
|
1312
|
-
onClick: (e) => {
|
|
1313
|
-
onClick?.(e);
|
|
1314
|
-
if (!e.defaultPrevented) previous();
|
|
1315
|
-
},
|
|
1316
|
-
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ChevronLeft })
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
//#endregion
|
|
1320
|
-
//#region src/components/InkNextPage.tsx
|
|
1321
|
-
function InkNextPage({ asChild = false, className, children, disabled, onClick, ...rest }) {
|
|
1322
|
-
const { current, count, next } = usePage();
|
|
2170
|
+
function InkPrevPage({ asChild = false, className, children, disabled, onClick, ...rest }) {
|
|
2171
|
+
const { current, previous } = usePage();
|
|
1323
2172
|
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1324
2173
|
asChild,
|
|
1325
|
-
className: cx("ink-
|
|
1326
|
-
"data-ink-
|
|
1327
|
-
"aria-label": "
|
|
1328
|
-
title: "
|
|
1329
|
-
disabled: disabled === true || current
|
|
2174
|
+
className: cx("ink-prev-page", className),
|
|
2175
|
+
"data-ink-prev-page": "",
|
|
2176
|
+
"aria-label": "Previous page",
|
|
2177
|
+
title: "Previous page",
|
|
2178
|
+
disabled: disabled === true || current <= 0,
|
|
1330
2179
|
...rest,
|
|
1331
2180
|
onClick: (e) => {
|
|
1332
2181
|
onClick?.(e);
|
|
1333
|
-
if (!e.defaultPrevented)
|
|
1334
|
-
},
|
|
1335
|
-
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon:
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
|
-
//#endregion
|
|
1339
|
-
//#region src/components/InkColorMenu.tsx
|
|
1340
|
-
function InkColorMenu({ property, label, className, children, standalone = false, group, fallback, disabled, ...rest }) {
|
|
1341
|
-
const Wrap = standalone ? Fragment : Toolbar.Button;
|
|
1342
|
-
const { current, supported, set } = useToolStyle(group);
|
|
1343
|
-
const [open, setOpen] = useState(false);
|
|
1344
|
-
const model = colorMenuModel(property, current, supported, fallback);
|
|
1345
|
-
const name = label ?? model.label;
|
|
1346
|
-
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
1347
|
-
const focusFirst = useRef(false);
|
|
1348
|
-
const input = useRef(null);
|
|
1349
|
-
const apply = useRef((_hex) => {});
|
|
1350
|
-
apply.current = (hex) => set(model.custom(hex));
|
|
1351
|
-
const bindInput = useCallback((el) => {
|
|
1352
|
-
input.current = el;
|
|
1353
|
-
el?.addEventListener("change", () => apply.current(el.value));
|
|
1354
|
-
}, []);
|
|
1355
|
-
useEffect(() => {
|
|
1356
|
-
if (input.current) input.current.value = model.customValue.toLowerCase();
|
|
1357
|
-
}, [model.customValue]);
|
|
1358
|
-
useEffect(() => {
|
|
1359
|
-
if (!model.enabled) setOpen(false);
|
|
1360
|
-
}, [model.enabled]);
|
|
1361
|
-
const trigger = model.trigger;
|
|
1362
|
-
const swatchStyle = trigger.kind === "color" ? { "--ink-swatch": trigger.color } : void 0;
|
|
1363
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Popover.Root, {
|
|
1364
|
-
open,
|
|
1365
|
-
onOpenChange: setOpen,
|
|
1366
|
-
children: [/* @__PURE__ */ jsx(Wrap, {
|
|
1367
|
-
...standalone ? {} : { asChild: true },
|
|
1368
|
-
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
1369
|
-
asChild: true,
|
|
1370
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
1371
|
-
type: "button",
|
|
1372
|
-
className: cx("ink-menu-trigger ink-color-menu", className),
|
|
1373
|
-
"data-ink-color-menu": property,
|
|
1374
|
-
"data-state": open ? "open" : "closed",
|
|
1375
|
-
"data-mixed": model.mixed ? "" : void 0,
|
|
1376
|
-
"aria-label": name,
|
|
1377
|
-
title: name,
|
|
1378
|
-
"aria-haspopup": "menu",
|
|
1379
|
-
disabled: disabled === true || !model.enabled,
|
|
1380
|
-
...rest,
|
|
1381
|
-
onKeyDown: (e) => {
|
|
1382
|
-
rest.onKeyDown?.(e);
|
|
1383
|
-
if (e.key !== "ArrowDown" || e.defaultPrevented || open || !model.enabled) return;
|
|
1384
|
-
e.preventDefault();
|
|
1385
|
-
focusFirst.current = true;
|
|
1386
|
-
setOpen(true);
|
|
1387
|
-
},
|
|
1388
|
-
children: children ?? /* @__PURE__ */ jsx("span", {
|
|
1389
|
-
className: "ink-swatch",
|
|
1390
|
-
"data-ink-swatch": "",
|
|
1391
|
-
"data-property": property,
|
|
1392
|
-
"data-kind": trigger.kind,
|
|
1393
|
-
"data-color": trigger.kind === "color" ? trigger.color : void 0,
|
|
1394
|
-
style: swatchStyle,
|
|
1395
|
-
"aria-hidden": "true"
|
|
1396
|
-
})
|
|
1397
|
-
})
|
|
1398
|
-
})
|
|
1399
|
-
}), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
|
|
1400
|
-
role: "menu",
|
|
1401
|
-
"aria-label": name,
|
|
1402
|
-
className: "ink-select-content ink-menu-content",
|
|
1403
|
-
"data-ink-select-content": "",
|
|
1404
|
-
"data-ink-menu": `color-${property}`,
|
|
1405
|
-
side: "bottom",
|
|
1406
|
-
align: "start",
|
|
1407
|
-
sideOffset: 4,
|
|
1408
|
-
collisionPadding: 8,
|
|
1409
|
-
onOpenAutoFocus: (e) => {
|
|
1410
|
-
e.preventDefault();
|
|
1411
|
-
const menu = e.currentTarget;
|
|
1412
|
-
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
1413
|
-
else focusInitialMenuItem(menu);
|
|
1414
|
-
focusFirst.current = false;
|
|
1415
|
-
},
|
|
1416
|
-
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1417
|
-
children: [
|
|
1418
|
-
model.noFill ? /* @__PURE__ */ jsxs("button", {
|
|
1419
|
-
type: "button",
|
|
1420
|
-
role: "menuitemradio",
|
|
1421
|
-
"aria-checked": model.noFill.checked,
|
|
1422
|
-
tabIndex: -1,
|
|
1423
|
-
className: "ink-select-item ink-menu-item",
|
|
1424
|
-
"data-ink-select-item": "",
|
|
1425
|
-
"data-state": model.noFill.checked ? "checked" : "unchecked",
|
|
1426
|
-
onClick: () => set(model.noFill.patch),
|
|
1427
|
-
children: ["No fill", model.noFill.checked ? /* @__PURE__ */ jsx("span", {
|
|
1428
|
-
className: "ink-select-indicator",
|
|
1429
|
-
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
1430
|
-
icon: Check,
|
|
1431
|
-
size: 16
|
|
1432
|
-
})
|
|
1433
|
-
}) : null]
|
|
1434
|
-
}) : null,
|
|
1435
|
-
/* @__PURE__ */ jsx("div", {
|
|
1436
|
-
role: "group",
|
|
1437
|
-
"aria-label": "Colours",
|
|
1438
|
-
className: "ink-color-grid",
|
|
1439
|
-
"data-ink-menu-columns": COLOR_COLUMNS,
|
|
1440
|
-
children: model.swatches.map((sw) => /* @__PURE__ */ jsx("button", {
|
|
1441
|
-
type: "button",
|
|
1442
|
-
role: "menuitemradio",
|
|
1443
|
-
"aria-label": sw.label,
|
|
1444
|
-
"aria-checked": sw.checked,
|
|
1445
|
-
tabIndex: -1,
|
|
1446
|
-
className: "ink-swatch-item",
|
|
1447
|
-
"data-state": sw.checked ? "checked" : "unchecked",
|
|
1448
|
-
style: { "--ink-swatch": sw.color },
|
|
1449
|
-
onClick: () => set(sw.patch)
|
|
1450
|
-
}, sw.key))
|
|
1451
|
-
}),
|
|
1452
|
-
/* @__PURE__ */ jsx("button", {
|
|
1453
|
-
type: "button",
|
|
1454
|
-
role: "menuitem",
|
|
1455
|
-
tabIndex: -1,
|
|
1456
|
-
className: "ink-select-item ink-menu-item",
|
|
1457
|
-
"data-ink-select-item": "",
|
|
1458
|
-
onClick: () => input.current?.click(),
|
|
1459
|
-
children: "Custom…"
|
|
1460
|
-
})
|
|
1461
|
-
]
|
|
1462
|
-
}) })]
|
|
1463
|
-
}), /* @__PURE__ */ jsx("input", {
|
|
1464
|
-
ref: bindInput,
|
|
1465
|
-
type: "color",
|
|
1466
|
-
className: "ink-color-input",
|
|
1467
|
-
"aria-label": "Custom colour",
|
|
1468
|
-
"data-ink-color-input": property,
|
|
1469
|
-
tabIndex: -1,
|
|
1470
|
-
defaultValue: model.customValue.toLowerCase()
|
|
1471
|
-
})] });
|
|
1472
|
-
}
|
|
1473
|
-
//#endregion
|
|
1474
|
-
//#region src/components/InkShapeMenu.tsx
|
|
1475
|
-
function InkShapeMenu({ className, children, ...rest }) {
|
|
1476
|
-
const { tool, setTool } = useTool();
|
|
1477
|
-
const [options, setOptions] = useToolOptions("shape");
|
|
1478
|
-
const [open, setOpen] = useState(false);
|
|
1479
|
-
const model = shapeMenuModel(options, SHAPE_KINDS);
|
|
1480
|
-
const active = tool === "shape";
|
|
1481
|
-
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
1482
|
-
const focusFirst = useRef(false);
|
|
1483
|
-
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
1484
|
-
open,
|
|
1485
|
-
onOpenChange: setOpen,
|
|
1486
|
-
children: /* @__PURE__ */ jsx(Popover.Anchor, {
|
|
1487
|
-
asChild: true,
|
|
1488
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1489
|
-
role: "group",
|
|
1490
|
-
"aria-label": "Shape",
|
|
1491
|
-
className: cx("ink-split-button ink-shape-menu", className),
|
|
1492
|
-
"data-ink-shape-menu": "",
|
|
1493
|
-
...rest,
|
|
1494
|
-
children: [
|
|
1495
|
-
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1496
|
-
className: "ink-tool-button ink-shape-button",
|
|
1497
|
-
"data-ink-tool-button": "",
|
|
1498
|
-
"data-ink-shape-button": "",
|
|
1499
|
-
"data-tool": "shape",
|
|
1500
|
-
"data-kind": model.kind,
|
|
1501
|
-
"data-state": active ? "on" : "off",
|
|
1502
|
-
"aria-pressed": active,
|
|
1503
|
-
"aria-label": model.label,
|
|
1504
|
-
title: model.label,
|
|
1505
|
-
onClick: () => setTool("shape"),
|
|
1506
|
-
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: SHAPE_ICONS[model.kind] })
|
|
1507
|
-
}),
|
|
1508
|
-
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1509
|
-
asChild: true,
|
|
1510
|
-
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
1511
|
-
asChild: true,
|
|
1512
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
1513
|
-
type: "button",
|
|
1514
|
-
className: "ink-menu-trigger ink-shape-menu-trigger",
|
|
1515
|
-
"data-ink-shape-menu-trigger": "",
|
|
1516
|
-
"data-state": open ? "open" : "closed",
|
|
1517
|
-
"aria-label": "Choose shape",
|
|
1518
|
-
title: "Choose shape",
|
|
1519
|
-
"aria-haspopup": "menu",
|
|
1520
|
-
onKeyDown: (e) => {
|
|
1521
|
-
if (e.key !== "ArrowDown" || e.defaultPrevented || open) return;
|
|
1522
|
-
e.preventDefault();
|
|
1523
|
-
focusFirst.current = true;
|
|
1524
|
-
setOpen(true);
|
|
1525
|
-
},
|
|
1526
|
-
children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronDown })
|
|
1527
|
-
})
|
|
1528
|
-
})
|
|
1529
|
-
}),
|
|
1530
|
-
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Content, {
|
|
1531
|
-
role: "menu",
|
|
1532
|
-
"aria-label": "Shapes",
|
|
1533
|
-
className: "ink-select-content ink-menu-content",
|
|
1534
|
-
"data-ink-select-content": "",
|
|
1535
|
-
"data-ink-menu": "shape",
|
|
1536
|
-
side: "bottom",
|
|
1537
|
-
align: "start",
|
|
1538
|
-
sideOffset: 4,
|
|
1539
|
-
collisionPadding: 8,
|
|
1540
|
-
onOpenAutoFocus: (e) => {
|
|
1541
|
-
e.preventDefault();
|
|
1542
|
-
const menu = e.currentTarget;
|
|
1543
|
-
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
1544
|
-
else focusInitialMenuItem(menu);
|
|
1545
|
-
focusFirst.current = false;
|
|
1546
|
-
},
|
|
1547
|
-
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1548
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
1549
|
-
role: "group",
|
|
1550
|
-
"aria-label": "Shapes",
|
|
1551
|
-
className: "ink-shape-grid",
|
|
1552
|
-
"data-ink-menu-columns": SHAPE_COLUMNS,
|
|
1553
|
-
children: model.items.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
1554
|
-
type: "button",
|
|
1555
|
-
role: "menuitemradio",
|
|
1556
|
-
"aria-checked": item.checked,
|
|
1557
|
-
tabIndex: -1,
|
|
1558
|
-
className: "ink-select-item ink-menu-item ink-shape-item",
|
|
1559
|
-
"data-ink-select-item": "",
|
|
1560
|
-
"data-key": item.kind,
|
|
1561
|
-
"data-state": item.checked ? "checked" : "unchecked",
|
|
1562
|
-
onClick: () => {
|
|
1563
|
-
setOptions({ kind: item.kind });
|
|
1564
|
-
setTool("shape");
|
|
1565
|
-
setOpen(false);
|
|
1566
|
-
},
|
|
1567
|
-
children: [/* @__PURE__ */ jsx(InkIcon, {
|
|
1568
|
-
icon: SHAPE_ICONS[item.kind],
|
|
1569
|
-
size: 16
|
|
1570
|
-
}), item.label]
|
|
1571
|
-
}, item.kind))
|
|
1572
|
-
})
|
|
1573
|
-
}) })
|
|
1574
|
-
]
|
|
1575
|
-
})
|
|
1576
|
-
})
|
|
2182
|
+
if (!e.defaultPrevented) previous();
|
|
2183
|
+
},
|
|
2184
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ChevronLeft })
|
|
1577
2185
|
});
|
|
1578
2186
|
}
|
|
1579
2187
|
//#endregion
|
|
1580
|
-
//#region src/components/
|
|
1581
|
-
function
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
},
|
|
1597
|
-
onCloseAutoFocus,
|
|
1598
|
-
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
1599
|
-
className: "ink-dialog-title",
|
|
1600
|
-
children: SIGNATURE_LABELS.dialog
|
|
1601
|
-
}), /* @__PURE__ */ jsx(SignatureForm, {
|
|
1602
|
-
fonts,
|
|
1603
|
-
onSave,
|
|
1604
|
-
onClose: () => onOpenChange(false)
|
|
1605
|
-
})]
|
|
1606
|
-
})] })
|
|
2188
|
+
//#region src/components/InkNextPage.tsx
|
|
2189
|
+
function InkNextPage({ asChild = false, className, children, disabled, onClick, ...rest }) {
|
|
2190
|
+
const { current, count, next } = usePage();
|
|
2191
|
+
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
2192
|
+
asChild,
|
|
2193
|
+
className: cx("ink-next-page", className),
|
|
2194
|
+
"data-ink-next-page": "",
|
|
2195
|
+
"aria-label": "Next page",
|
|
2196
|
+
title: "Next page",
|
|
2197
|
+
disabled: disabled === true || current >= count - 1,
|
|
2198
|
+
...rest,
|
|
2199
|
+
onClick: (e) => {
|
|
2200
|
+
onClick?.(e);
|
|
2201
|
+
if (!e.defaultPrevented) next();
|
|
2202
|
+
},
|
|
2203
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ChevronRight })
|
|
1607
2204
|
});
|
|
1608
2205
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
const
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
const s = useReadable(draft.state, IDLE);
|
|
1627
|
-
const uid = useId();
|
|
1628
|
-
const padHost = useRef(null);
|
|
2206
|
+
//#endregion
|
|
2207
|
+
//#region src/components/InkColorMenu.tsx
|
|
2208
|
+
function InkColorMenu({ property, label, className, children, standalone = false, group, fallback, disabled, ...rest }) {
|
|
2209
|
+
const Wrap = standalone ? Fragment : Toolbar.Button;
|
|
2210
|
+
const { current, supported, set } = useToolStyle(group);
|
|
2211
|
+
const [open, setOpen] = useState(false);
|
|
2212
|
+
const model = colorMenuModel(property, current, supported, fallback);
|
|
2213
|
+
const name = label ?? model.label;
|
|
2214
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
2215
|
+
const focusFirst = useRef(false);
|
|
2216
|
+
const input = useRef(null);
|
|
2217
|
+
const apply = useRef((_hex) => {});
|
|
2218
|
+
apply.current = (hex) => set(model.custom(hex));
|
|
2219
|
+
const bindInput = useCallback((el) => {
|
|
2220
|
+
input.current = el;
|
|
2221
|
+
el?.addEventListener("change", () => apply.current(el.value));
|
|
2222
|
+
}, []);
|
|
1629
2223
|
useEffect(() => {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
}, () => {});
|
|
1645
|
-
};
|
|
1646
|
-
const tabId = (tab) => `${uid}-tab-${tab}`;
|
|
1647
|
-
const panelId = (tab) => `${uid}-panel-${tab}`;
|
|
1648
|
-
const panel = (tab) => ({
|
|
1649
|
-
role: "tabpanel",
|
|
1650
|
-
id: panelId(tab),
|
|
1651
|
-
"aria-labelledby": tabId(tab),
|
|
1652
|
-
className: "ink-tab-panel",
|
|
1653
|
-
"data-key": tab,
|
|
1654
|
-
hidden: s.tab !== tab
|
|
1655
|
-
});
|
|
1656
|
-
const shown = s.name.trim() || SIGNATURE_LABELS.namePlaceholder;
|
|
1657
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1658
|
-
/* @__PURE__ */ jsx("div", {
|
|
1659
|
-
role: "tablist",
|
|
1660
|
-
"aria-label": SIGNATURE_LABELS.tabs,
|
|
1661
|
-
className: "ink-tabs",
|
|
1662
|
-
onKeyDown: (e) => handleTabKeys(e.nativeEvent, e.currentTarget),
|
|
1663
|
-
children: SIGNATURE_TABS.map((tab) => /* @__PURE__ */ jsx("button", {
|
|
1664
|
-
type: "button",
|
|
1665
|
-
role: "tab",
|
|
1666
|
-
id: tabId(tab),
|
|
1667
|
-
"aria-controls": panelId(tab),
|
|
1668
|
-
"aria-selected": s.tab === tab,
|
|
1669
|
-
tabIndex: s.tab === tab ? 0 : -1,
|
|
1670
|
-
className: "ink-tab",
|
|
1671
|
-
"data-key": tab,
|
|
1672
|
-
"data-state": s.tab === tab ? "active" : "inactive",
|
|
1673
|
-
onClick: () => draft.setTab(tab),
|
|
1674
|
-
children: SIGNATURE_LABELS[tab]
|
|
1675
|
-
}, tab))
|
|
1676
|
-
}),
|
|
1677
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1678
|
-
...panel("draw"),
|
|
1679
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
1680
|
-
ref: padHost,
|
|
1681
|
-
className: "ink-signature-pad-host",
|
|
1682
|
-
"data-ink-signature-pad-host": "",
|
|
1683
|
-
role: "img",
|
|
1684
|
-
"aria-label": SIGNATURE_LABELS.pad
|
|
1685
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1686
|
-
className: "ink-dialog-row",
|
|
2224
|
+
if (input.current) input.current.value = model.customValue.toLowerCase();
|
|
2225
|
+
}, [model.customValue]);
|
|
2226
|
+
useEffect(() => {
|
|
2227
|
+
if (!model.enabled) setOpen(false);
|
|
2228
|
+
}, [model.enabled]);
|
|
2229
|
+
const trigger = model.trigger;
|
|
2230
|
+
const swatchStyle = trigger.kind === "color" ? { "--ink-swatch": trigger.color } : void 0;
|
|
2231
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Popover.Root, {
|
|
2232
|
+
open,
|
|
2233
|
+
onOpenChange: setOpen,
|
|
2234
|
+
children: [/* @__PURE__ */ jsx(Wrap, {
|
|
2235
|
+
...standalone ? {} : { asChild: true },
|
|
2236
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
2237
|
+
asChild: true,
|
|
1687
2238
|
children: /* @__PURE__ */ jsx("button", {
|
|
1688
2239
|
type: "button",
|
|
1689
|
-
className: "ink-
|
|
1690
|
-
"data-ink-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
className: "ink-signature-fonts",
|
|
1714
|
-
onKeyDown: (e) => handleRadioKeys(e.nativeEvent, e.currentTarget),
|
|
1715
|
-
children: draft.fonts.map((font) => /* @__PURE__ */ jsx("button", {
|
|
1716
|
-
type: "button",
|
|
1717
|
-
role: "radio",
|
|
1718
|
-
"aria-checked": s.font === font,
|
|
1719
|
-
"aria-label": font,
|
|
1720
|
-
tabIndex: s.font === font ? 0 : -1,
|
|
1721
|
-
className: "ink-signature-font",
|
|
1722
|
-
"data-key": font,
|
|
1723
|
-
"data-state": s.font === font ? "checked" : "unchecked",
|
|
1724
|
-
onClick: () => draft.setFont(font),
|
|
1725
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
1726
|
-
className: "ink-signature-font-sample",
|
|
1727
|
-
style: { fontFamily: fontFamilyValue(font) },
|
|
1728
|
-
children: shown
|
|
2240
|
+
className: cx("ink-menu-trigger ink-color-menu", className),
|
|
2241
|
+
"data-ink-color-menu": property,
|
|
2242
|
+
"data-state": open ? "open" : "closed",
|
|
2243
|
+
"data-mixed": model.mixed ? "" : void 0,
|
|
2244
|
+
"aria-label": name,
|
|
2245
|
+
title: name,
|
|
2246
|
+
"aria-haspopup": "menu",
|
|
2247
|
+
disabled: disabled === true || !model.enabled,
|
|
2248
|
+
...rest,
|
|
2249
|
+
onKeyDown: (e) => {
|
|
2250
|
+
rest.onKeyDown?.(e);
|
|
2251
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open || !model.enabled) return;
|
|
2252
|
+
e.preventDefault();
|
|
2253
|
+
focusFirst.current = true;
|
|
2254
|
+
setOpen(true);
|
|
2255
|
+
},
|
|
2256
|
+
children: children ?? /* @__PURE__ */ jsx("span", {
|
|
2257
|
+
className: "ink-swatch",
|
|
2258
|
+
"data-ink-swatch": "",
|
|
2259
|
+
"data-property": property,
|
|
2260
|
+
"data-kind": trigger.kind,
|
|
2261
|
+
"data-color": trigger.kind === "color" ? trigger.color : void 0,
|
|
2262
|
+
style: swatchStyle,
|
|
2263
|
+
"aria-hidden": "true"
|
|
1729
2264
|
})
|
|
1730
|
-
}
|
|
1731
|
-
})
|
|
1732
|
-
}),
|
|
1733
|
-
|
|
1734
|
-
|
|
2265
|
+
})
|
|
2266
|
+
})
|
|
2267
|
+
}), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
|
|
2268
|
+
role: "menu",
|
|
2269
|
+
"aria-label": name,
|
|
2270
|
+
className: "ink-select-content ink-menu-content",
|
|
2271
|
+
"data-ink-select-content": "",
|
|
2272
|
+
"data-ink-menu": `color-${property}`,
|
|
2273
|
+
side: "bottom",
|
|
2274
|
+
align: "start",
|
|
2275
|
+
sideOffset: 4,
|
|
2276
|
+
collisionPadding: 8,
|
|
2277
|
+
onOpenAutoFocus: (e) => {
|
|
2278
|
+
e.preventDefault();
|
|
2279
|
+
const menu = e.currentTarget;
|
|
2280
|
+
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
2281
|
+
else focusInitialMenuItem(menu);
|
|
2282
|
+
focusFirst.current = false;
|
|
2283
|
+
},
|
|
2284
|
+
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1735
2285
|
children: [
|
|
1736
|
-
/* @__PURE__ */ jsxs("
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2286
|
+
model.noFill ? /* @__PURE__ */ jsxs("button", {
|
|
2287
|
+
type: "button",
|
|
2288
|
+
role: "menuitemradio",
|
|
2289
|
+
"aria-checked": model.noFill.checked,
|
|
2290
|
+
tabIndex: -1,
|
|
2291
|
+
className: "ink-select-item ink-menu-item",
|
|
2292
|
+
"data-ink-select-item": "",
|
|
2293
|
+
"data-state": model.noFill.checked ? "checked" : "unchecked",
|
|
2294
|
+
onClick: () => set(model.noFill.patch),
|
|
2295
|
+
children: ["No fill", model.noFill.checked ? /* @__PURE__ */ jsx("span", {
|
|
2296
|
+
className: "ink-select-indicator",
|
|
2297
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
2298
|
+
icon: Check,
|
|
2299
|
+
size: 16
|
|
2300
|
+
})
|
|
2301
|
+
}) : null]
|
|
1750
2302
|
}) : null,
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
2303
|
+
/* @__PURE__ */ jsx("div", {
|
|
2304
|
+
role: "group",
|
|
2305
|
+
"aria-label": "Colours",
|
|
2306
|
+
className: "ink-color-grid",
|
|
2307
|
+
"data-ink-menu-columns": COLOR_COLUMNS,
|
|
2308
|
+
children: model.swatches.map((sw) => /* @__PURE__ */ jsx("button", {
|
|
2309
|
+
type: "button",
|
|
2310
|
+
role: "menuitemradio",
|
|
2311
|
+
"aria-label": sw.label,
|
|
2312
|
+
"aria-checked": sw.checked,
|
|
2313
|
+
tabIndex: -1,
|
|
2314
|
+
className: "ink-swatch-item",
|
|
2315
|
+
"data-state": sw.checked ? "checked" : "unchecked",
|
|
2316
|
+
style: { "--ink-swatch": sw.color },
|
|
2317
|
+
onClick: () => set(sw.patch)
|
|
2318
|
+
}, sw.key))
|
|
2319
|
+
}),
|
|
2320
|
+
/* @__PURE__ */ jsx("button", {
|
|
1769
2321
|
type: "button",
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2322
|
+
role: "menuitem",
|
|
2323
|
+
tabIndex: -1,
|
|
2324
|
+
className: "ink-select-item ink-menu-item",
|
|
2325
|
+
"data-ink-select-item": "",
|
|
2326
|
+
onClick: () => input.current?.click(),
|
|
2327
|
+
children: "Custom…"
|
|
1773
2328
|
})
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
] });
|
|
2329
|
+
]
|
|
2330
|
+
}) })]
|
|
2331
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
2332
|
+
ref: bindInput,
|
|
2333
|
+
type: "color",
|
|
2334
|
+
className: "ink-color-input",
|
|
2335
|
+
"aria-label": "Custom colour",
|
|
2336
|
+
"data-ink-color-input": property,
|
|
2337
|
+
tabIndex: -1,
|
|
2338
|
+
defaultValue: model.customValue.toLowerCase()
|
|
2339
|
+
})] });
|
|
1785
2340
|
}
|
|
1786
2341
|
//#endregion
|
|
1787
|
-
//#region src/components/
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2342
|
+
//#region src/components/InkShapeMenu.tsx
|
|
2343
|
+
function InkShapeMenu({ className, children, ...rest }) {
|
|
2344
|
+
const { tool, setTool } = useTool();
|
|
2345
|
+
const [options, setOptions] = useToolOptions("shape");
|
|
2346
|
+
const [open, setOpen] = useState(false);
|
|
2347
|
+
const model = shapeMenuModel(options, SHAPE_KINDS);
|
|
2348
|
+
const active = tool === "shape";
|
|
2349
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
2350
|
+
const focusFirst = useRef(false);
|
|
2351
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
2352
|
+
open,
|
|
2353
|
+
onOpenChange: setOpen,
|
|
2354
|
+
children: /* @__PURE__ */ jsx(Popover.Anchor, {
|
|
2355
|
+
asChild: true,
|
|
2356
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2357
|
+
role: "group",
|
|
2358
|
+
"aria-label": "Shape",
|
|
2359
|
+
className: cx("ink-split-button ink-shape-menu", className),
|
|
2360
|
+
"data-ink-shape-menu": "",
|
|
2361
|
+
...rest,
|
|
2362
|
+
children: [
|
|
2363
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
2364
|
+
className: "ink-tool-button ink-shape-button",
|
|
2365
|
+
"data-ink-tool-button": "",
|
|
2366
|
+
"data-ink-shape-button": "",
|
|
2367
|
+
"data-tool": "shape",
|
|
2368
|
+
"data-kind": model.kind,
|
|
2369
|
+
"data-state": active ? "on" : "off",
|
|
2370
|
+
"aria-pressed": active,
|
|
2371
|
+
"aria-label": model.label,
|
|
2372
|
+
title: model.label,
|
|
2373
|
+
onClick: () => setTool("shape"),
|
|
2374
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: SHAPE_ICONS[model.kind] })
|
|
2375
|
+
}),
|
|
2376
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
2377
|
+
asChild: true,
|
|
2378
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
2379
|
+
asChild: true,
|
|
2380
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2381
|
+
type: "button",
|
|
2382
|
+
className: "ink-menu-trigger ink-shape-menu-trigger",
|
|
2383
|
+
"data-ink-shape-menu-trigger": "",
|
|
2384
|
+
"data-state": open ? "open" : "closed",
|
|
2385
|
+
"aria-label": "Choose shape",
|
|
2386
|
+
title: "Choose shape",
|
|
2387
|
+
"aria-haspopup": "menu",
|
|
2388
|
+
onKeyDown: (e) => {
|
|
2389
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open) return;
|
|
2390
|
+
e.preventDefault();
|
|
2391
|
+
focusFirst.current = true;
|
|
2392
|
+
setOpen(true);
|
|
2393
|
+
},
|
|
2394
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronDown })
|
|
2395
|
+
})
|
|
2396
|
+
})
|
|
2397
|
+
}),
|
|
2398
|
+
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Content, {
|
|
2399
|
+
role: "menu",
|
|
2400
|
+
"aria-label": "Shapes",
|
|
2401
|
+
className: "ink-select-content ink-menu-content",
|
|
2402
|
+
"data-ink-select-content": "",
|
|
2403
|
+
"data-ink-menu": "shape",
|
|
2404
|
+
side: "bottom",
|
|
2405
|
+
align: "start",
|
|
2406
|
+
sideOffset: 4,
|
|
2407
|
+
collisionPadding: 8,
|
|
2408
|
+
onOpenAutoFocus: (e) => {
|
|
2409
|
+
e.preventDefault();
|
|
2410
|
+
const menu = e.currentTarget;
|
|
2411
|
+
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
2412
|
+
else focusInitialMenuItem(menu);
|
|
2413
|
+
focusFirst.current = false;
|
|
2414
|
+
},
|
|
2415
|
+
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
2416
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2417
|
+
role: "group",
|
|
2418
|
+
"aria-label": "Shapes",
|
|
2419
|
+
className: "ink-shape-grid",
|
|
2420
|
+
"data-ink-menu-columns": SHAPE_COLUMNS,
|
|
2421
|
+
children: model.items.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
2422
|
+
type: "button",
|
|
2423
|
+
role: "menuitemradio",
|
|
2424
|
+
"aria-checked": item.checked,
|
|
2425
|
+
tabIndex: -1,
|
|
2426
|
+
className: "ink-select-item ink-menu-item ink-shape-item",
|
|
2427
|
+
"data-ink-select-item": "",
|
|
2428
|
+
"data-key": item.kind,
|
|
2429
|
+
"data-state": item.checked ? "checked" : "unchecked",
|
|
2430
|
+
onClick: () => {
|
|
2431
|
+
setOptions({ kind: item.kind });
|
|
2432
|
+
setTool("shape");
|
|
2433
|
+
setOpen(false);
|
|
2434
|
+
},
|
|
2435
|
+
children: [/* @__PURE__ */ jsx(InkIcon, {
|
|
2436
|
+
icon: SHAPE_ICONS[item.kind],
|
|
2437
|
+
size: 16
|
|
2438
|
+
}), item.label]
|
|
2439
|
+
}, item.kind))
|
|
2440
|
+
})
|
|
2441
|
+
}) })
|
|
2442
|
+
]
|
|
2443
|
+
})
|
|
1811
2444
|
})
|
|
1812
2445
|
});
|
|
1813
2446
|
}
|
|
2447
|
+
//#endregion
|
|
2448
|
+
//#region src/components/InkSignatureMenu.tsx
|
|
2449
|
+
const NO_TEMPLATES = [];
|
|
1814
2450
|
function InkSignatureMenu({ className, children, fonts, ...rest }) {
|
|
1815
2451
|
const { tool, setTool } = useTool();
|
|
1816
2452
|
const [options, setOptions] = useToolOptions("signature");
|
|
1817
2453
|
const { signatures } = useInkContext("InkSignatureMenu");
|
|
1818
2454
|
const templates = useReadable(signatures.templates, NO_TEMPLATES);
|
|
1819
|
-
const removeError = useReadable(signatures.removeError, void 0);
|
|
1820
2455
|
const model = signatureMenuModel(templates, options);
|
|
1821
2456
|
const [open, setOpen] = useState(false);
|
|
1822
2457
|
const [creating, setCreating] = useState(false);
|
|
@@ -1827,25 +2462,9 @@ function InkSignatureMenu({ className, children, fonts, ...rest }) {
|
|
|
1827
2462
|
const focusFirst = useRef(false);
|
|
1828
2463
|
/** Set while the menu closes for the dialog, which takes focus instead of the trigger. */
|
|
1829
2464
|
const toDialog = useRef(false);
|
|
1830
|
-
/** The row of a removed signature, until focus lands on what took its place. */
|
|
1831
|
-
const removedRow = useRef(null);
|
|
1832
2465
|
useEffect(() => {
|
|
1833
2466
|
if (open) signatures.clearRemoveError();
|
|
1834
2467
|
}, [open, signatures]);
|
|
1835
|
-
useEffect(() => {
|
|
1836
|
-
const row = removedRow.current;
|
|
1837
|
-
const panel = menu.current;
|
|
1838
|
-
if (row === null || !panel) return;
|
|
1839
|
-
removedRow.current = null;
|
|
1840
|
-
const rows = panel.querySelectorAll(".ink-signature-item");
|
|
1841
|
-
(rows[Math.min(row, rows.length - 1)] ?? panel.querySelector("[data-ink-signature-create]"))?.focus({ preventScroll: true });
|
|
1842
|
-
}, [templates]);
|
|
1843
|
-
const remove = (id, row) => {
|
|
1844
|
-
removedRow.current = row;
|
|
1845
|
-
signatures.remove(id).catch(() => {
|
|
1846
|
-
removedRow.current = null;
|
|
1847
|
-
});
|
|
1848
|
-
};
|
|
1849
2468
|
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
1850
2469
|
open,
|
|
1851
2470
|
onOpenChange: setOpen,
|
|
@@ -1896,7 +2515,7 @@ function InkSignatureMenu({ className, children, fonts, ...rest }) {
|
|
|
1896
2515
|
})
|
|
1897
2516
|
})
|
|
1898
2517
|
}),
|
|
1899
|
-
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */
|
|
2518
|
+
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Content, {
|
|
1900
2519
|
ref: menu,
|
|
1901
2520
|
role: "menu",
|
|
1902
2521
|
"aria-label": SIGNATURE_LABELS.menu,
|
|
@@ -1919,71 +2538,20 @@ function InkSignatureMenu({ className, children, fonts, ...rest }) {
|
|
|
1919
2538
|
toDialog.current = false;
|
|
1920
2539
|
},
|
|
1921
2540
|
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1922
|
-
children:
|
|
1923
|
-
model.items
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
"
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
"data-key": item.id,
|
|
1937
|
-
"data-state": item.checked ? "checked" : "unchecked",
|
|
1938
|
-
onClick: () => {
|
|
1939
|
-
setOptions({ templateId: item.id });
|
|
1940
|
-
setTool("signature");
|
|
1941
|
-
setOpen(false);
|
|
1942
|
-
},
|
|
1943
|
-
children: /* @__PURE__ */ jsx(SignaturePreview, { template: item.template })
|
|
1944
|
-
}, `${item.id}-choose`), /* @__PURE__ */ jsx("button", {
|
|
1945
|
-
type: "button",
|
|
1946
|
-
role: "menuitem",
|
|
1947
|
-
"aria-label": item.removeLabel,
|
|
1948
|
-
title: item.removeLabel,
|
|
1949
|
-
tabIndex: -1,
|
|
1950
|
-
className: "ink-menu-item ink-signature-remove",
|
|
1951
|
-
"data-ink-signature-remove": "",
|
|
1952
|
-
"data-key": item.id,
|
|
1953
|
-
onClick: () => remove(item.id, row),
|
|
1954
|
-
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
1955
|
-
icon: Trash2,
|
|
1956
|
-
size: 16
|
|
1957
|
-
})
|
|
1958
|
-
}, `${item.id}-remove`)])
|
|
1959
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1960
|
-
role: "separator",
|
|
1961
|
-
className: "ink-select-separator"
|
|
1962
|
-
})] }) : null,
|
|
1963
|
-
/* @__PURE__ */ jsxs("button", {
|
|
1964
|
-
type: "button",
|
|
1965
|
-
role: "menuitem",
|
|
1966
|
-
tabIndex: -1,
|
|
1967
|
-
className: "ink-select-item ink-menu-item ink-signature-create",
|
|
1968
|
-
"data-ink-select-item": "",
|
|
1969
|
-
"data-ink-signature-create": "",
|
|
1970
|
-
onClick: () => {
|
|
1971
|
-
toDialog.current = true;
|
|
1972
|
-
setOpen(false);
|
|
1973
|
-
setCreating(true);
|
|
1974
|
-
},
|
|
1975
|
-
children: [/* @__PURE__ */ jsx(InkIcon, {
|
|
1976
|
-
icon: Plus,
|
|
1977
|
-
size: 16
|
|
1978
|
-
}), SIGNATURE_LABELS.create]
|
|
1979
|
-
}),
|
|
1980
|
-
removeError ? /* @__PURE__ */ jsx("p", {
|
|
1981
|
-
className: "ink-field-error ink-signature-remove-error",
|
|
1982
|
-
role: "alert",
|
|
1983
|
-
"data-ink-signature-remove-error": "",
|
|
1984
|
-
children: removeError
|
|
1985
|
-
}) : null
|
|
1986
|
-
]
|
|
2541
|
+
children: /* @__PURE__ */ jsx(SignatureMenuPanel, {
|
|
2542
|
+
items: model.items,
|
|
2543
|
+
menu,
|
|
2544
|
+
onChoose: (t) => {
|
|
2545
|
+
setOptions({ templateId: t.id });
|
|
2546
|
+
setTool("signature");
|
|
2547
|
+
setOpen(false);
|
|
2548
|
+
},
|
|
2549
|
+
onCreate: () => {
|
|
2550
|
+
toDialog.current = true;
|
|
2551
|
+
setOpen(false);
|
|
2552
|
+
setCreating(true);
|
|
2553
|
+
}
|
|
2554
|
+
})
|
|
1987
2555
|
}) }),
|
|
1988
2556
|
/* @__PURE__ */ jsx(InkSignatureDialog, {
|
|
1989
2557
|
open: creating,
|
|
@@ -2634,6 +3202,6 @@ function ThreadForm({ editingRoot, locked, deleteLabel, onSubmit, onRemove, onCl
|
|
|
2634
3202
|
});
|
|
2635
3203
|
}
|
|
2636
3204
|
//#endregion
|
|
2637
|
-
export { InkAnnotation, InkColorMenu, InkCommand, InkHighlightMenu, InkIcon, InkLineStyleMenu, InkNextPage, InkPage, InkPageCount, InkPageInput, InkPrevPage, InkRedo, InkRoot, InkShapeMenu, InkSignatureDialog, InkSignatureMenu, InkTextStyleMenu, InkThreadPopover, InkToolButton, InkToolbar, InkToolbarSeparator, InkUndo, InkViewport, InkZoomIn, InkZoomOut, InkZoomSelect, useAnnotations, useCommand, useDocument, useEmbedded, useHistory, useInkContext, usePage, useReadable, useSelection, useTool, useToolOptions, useToolStyle, useZoom };
|
|
3205
|
+
export { InkAnnotation, InkColorMenu, InkCommand, InkFindBar, InkHighlightMenu, InkIcon, InkLineStyleMenu, InkNextPage, InkPage, InkPageCount, InkPageInput, InkPrevPage, InkRedo, InkRoot, InkSearchButton, InkShapeMenu, InkSignatureDialog, InkSignatureMenu, InkTextStyleMenu, InkThreadPopover, InkToolButton, InkToolbar, InkToolbarSeparator, InkUndo, InkViewport, InkZoomIn, InkZoomOut, InkZoomSelect, useAnnotations, useCommand, useDocument, useEmbedded, useForm, useHistory, useInkContext, usePage, useReadable, useSearch, useSelection, useTool, useToolOptions, useToolStyle, useZoom };
|
|
2638
3206
|
|
|
2639
3207
|
//# sourceMappingURL=index.mjs.map
|