@inkbar/react 0.3.0 → 0.5.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 +17 -12
- package/dist/index.d.mts +1628 -718
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1254 -135
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createContext, createElement, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
-
import { InkError,
|
|
4
|
-
import { TOOL_LABELS, ZOOM_LEVELS, createFocusReturn, cssVariableDefaults, mountViewer, observeAnnotationElement, shouldKeepOpenOnAnnotation } from "@inkbar/core/dom";
|
|
5
|
-
import { Popover, Select, Slot, Toolbar } from "radix-ui";
|
|
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, repliesTo, threadOf, threadRoot } 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, createSignatureDraft, createToolOptions, cssVariableDefaults, focusInitialMenuItem, fontFamilyValue, handleMenuKeys, handleRadioKeys, handleTabKeys, highlightMenuModel, lineStyleMenuModel, menuItems, mountViewer, observeAnnotationElement, shapeMenuModel, shouldKeepOpenOnAnnotation, signatureMenuModel, signaturePreview, textStyleMenuModel, threadDeleteLabel, threadPopoverLabel, trackToolStyle } from "@inkbar/core/dom";
|
|
5
|
+
import { Dialog, Popover, Select, Slot, Toolbar } from "radix-ui";
|
|
6
6
|
import { createPortal, flushSync } from "react-dom";
|
|
7
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { Check, ChevronDown, ChevronLeft, ChevronRight,
|
|
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";
|
|
9
9
|
//#region src/context.ts
|
|
10
10
|
const InkContextObject = createContext(null);
|
|
11
11
|
/**
|
|
@@ -57,10 +57,7 @@ function useAnnotations() {
|
|
|
57
57
|
add: (input) => store?.add(input),
|
|
58
58
|
update: (id, patch) => store?.update(id, patch),
|
|
59
59
|
remove: (id) => store?.remove(id) ?? false,
|
|
60
|
-
select: (ids) => store?.select(ids)
|
|
61
|
-
/** Keeps a suggested redaction as the user's own, in the default redaction colour. */
|
|
62
|
-
accept: (id) => store ? acceptSuggestion(store, id) : void 0,
|
|
63
|
-
dismiss: (id) => store?.remove(id) ?? false
|
|
60
|
+
select: (ids) => store?.select(ids)
|
|
64
61
|
}), [store]);
|
|
65
62
|
return useMemo(() => ({
|
|
66
63
|
annotations,
|
|
@@ -87,6 +84,40 @@ function useTool() {
|
|
|
87
84
|
}), [tool, setTool]);
|
|
88
85
|
}
|
|
89
86
|
//#endregion
|
|
87
|
+
//#region src/hooks/useToolStyle.ts
|
|
88
|
+
const NO_STYLE = {};
|
|
89
|
+
const NO_PROPS = /* @__PURE__ */ new Set();
|
|
90
|
+
const EMPTY = {};
|
|
91
|
+
function useToolStyle(group) {
|
|
92
|
+
const style = useInkContext("useToolStyle()").toolStyle;
|
|
93
|
+
const scope = group && style ? style.scoped(group) : void 0;
|
|
94
|
+
const view = scope ?? style;
|
|
95
|
+
const current = useReadable(view?.current, NO_STYLE);
|
|
96
|
+
const supported = useReadable(view?.supported, NO_PROPS);
|
|
97
|
+
return useMemo(() => ({
|
|
98
|
+
current,
|
|
99
|
+
supported,
|
|
100
|
+
set: (patch, opts) => scope ? scope.set(patch) : style?.set(patch, opts),
|
|
101
|
+
get: (g) => style?.get(g) ?? EMPTY
|
|
102
|
+
}), [
|
|
103
|
+
current,
|
|
104
|
+
supported,
|
|
105
|
+
style,
|
|
106
|
+
scope
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/hooks/useToolOptions.ts
|
|
111
|
+
const NO_OPTIONS = {};
|
|
112
|
+
/**
|
|
113
|
+
* A tool's options and a setter that merges a patch into them without switching tools.
|
|
114
|
+
* Options are empty, and the setter does nothing, until the viewport has mounted.
|
|
115
|
+
*/
|
|
116
|
+
function useToolOptions(id) {
|
|
117
|
+
const tools = useInkContext("useToolOptions()").tools;
|
|
118
|
+
return [useReadable(tools?.options(id), NO_OPTIONS), useCallback((patch) => tools?.setOptions(id, patch), [tools, id])];
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
90
121
|
//#region src/hooks/useSelection.ts
|
|
91
122
|
function useSelection() {
|
|
92
123
|
const { selection, document } = useInkContext("useSelection()");
|
|
@@ -175,7 +206,7 @@ function useLatest(value) {
|
|
|
175
206
|
}
|
|
176
207
|
//#endregion
|
|
177
208
|
//#region src/components/InkRoot.tsx
|
|
178
|
-
const IDLE = { state: "idle" };
|
|
209
|
+
const IDLE$1 = { state: "idle" };
|
|
179
210
|
const NO_ANNOTATIONS = [];
|
|
180
211
|
const NO_IDS = [];
|
|
181
212
|
/** Signed URLs change; the key does not. Bytes and Blobs are their own identity. */
|
|
@@ -195,7 +226,7 @@ function isDevelopment() {
|
|
|
195
226
|
function setTestHook(el, doc) {
|
|
196
227
|
if (el && isDevelopment()) el.__inkDocument = doc;
|
|
197
228
|
}
|
|
198
|
-
function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsChange, onChange, tool, onToolChange, page, onPageChange, zoom, onZoomChange, history, asChild = false, onReady, onError, onActivate, children, ref, ...rest }) {
|
|
229
|
+
function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsChange, onChange, tool, onToolChange, tools: hostTools, decorateAnnotation, fonts: fontsProp, signatures: signatureStore, signatureFonts: signatureFontsProp, page, onPageChange, zoom, onZoomChange, history, asChild = false, onReady, onError, onActivate, children, ref, ...rest }) {
|
|
199
230
|
const rootElRef = useRef(null);
|
|
200
231
|
const [rootEl, setRootElState] = useState(null);
|
|
201
232
|
const setRootEl = useCallback((el) => {
|
|
@@ -206,11 +237,11 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
206
237
|
const [document, setDocument] = useState(null);
|
|
207
238
|
/** Overrides the document's status while opening, and when opening itself failed. */
|
|
208
239
|
const [override, setOverride] = useState(null);
|
|
209
|
-
const [internalTool, setInternalTool] = useState(tool ?? "
|
|
240
|
+
const [internalTool, setInternalTool] = useState(tool ?? "select-text");
|
|
210
241
|
const [activated, setActivatedState] = useState(null);
|
|
211
242
|
const [viewport, setViewportState] = useState(null);
|
|
212
243
|
const [tools, setToolsState] = useState(null);
|
|
213
|
-
const documentStatus = useReadable(document?.status, IDLE);
|
|
244
|
+
const documentStatus = useReadable(document?.status, IDLE$1);
|
|
214
245
|
const status = override ?? documentStatus;
|
|
215
246
|
const annotationList = useReadable(document?.annotations.list, NO_ANNOTATIONS);
|
|
216
247
|
const selection = useReadable(document?.annotations.selection, NO_IDS);
|
|
@@ -232,13 +263,17 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
232
263
|
onActivate,
|
|
233
264
|
onToolChange,
|
|
234
265
|
onPageChange,
|
|
235
|
-
onZoomChange
|
|
266
|
+
onZoomChange,
|
|
267
|
+
hostTools,
|
|
268
|
+
decorateAnnotation,
|
|
269
|
+
tool: internalTool,
|
|
270
|
+
tools
|
|
236
271
|
});
|
|
237
272
|
const snapshotHistory = useMemo(() => createSnapshotHistory({
|
|
238
273
|
apply: (list) => documentRef.current?.annotations.replaceAll(list, "history"),
|
|
239
274
|
enabled: () => latest.current.historyEnabled
|
|
240
275
|
}), [latest]);
|
|
241
|
-
|
|
276
|
+
useLayoutEffect(() => {
|
|
242
277
|
snapshotHistory.observe(annotations);
|
|
243
278
|
}, [annotations, snapshotHistory]);
|
|
244
279
|
const srcIdentity = identity(src);
|
|
@@ -320,6 +355,8 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
320
355
|
if (tool) setInternalTool(tool);
|
|
321
356
|
}, [tool]);
|
|
322
357
|
const setTool = useCallback((id) => {
|
|
358
|
+
const tc = latest.current.tools;
|
|
359
|
+
if (latest.current.tool === id && tc?.active.get() === id) tc.set(id);
|
|
323
360
|
setInternalTool(id);
|
|
324
361
|
latest.current.onToolChange?.(id);
|
|
325
362
|
}, [latest]);
|
|
@@ -357,6 +394,21 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
357
394
|
coreCanRedo,
|
|
358
395
|
snapshotHistory
|
|
359
396
|
]);
|
|
397
|
+
const latestHistory = useLatest(inkHistory);
|
|
398
|
+
/** The document's tool style; it batches through `history`, whichever mode is on. */
|
|
399
|
+
const styleTracker = useMemo(() => document ? trackToolStyle({
|
|
400
|
+
store: document.annotations,
|
|
401
|
+
history: { batch: (fn) => latestHistory.current.batch(fn) },
|
|
402
|
+
tools: latest.current.hostTools,
|
|
403
|
+
tool: latest.current.tool
|
|
404
|
+
}) : null, [
|
|
405
|
+
document,
|
|
406
|
+
latest,
|
|
407
|
+
latestHistory
|
|
408
|
+
]);
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
styleTracker?.setTool(internalTool);
|
|
411
|
+
}, [styleTracker, internalTool]);
|
|
360
412
|
useImperativeHandle(ref, () => ({
|
|
361
413
|
document,
|
|
362
414
|
status,
|
|
@@ -368,6 +420,30 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
368
420
|
rootEl,
|
|
369
421
|
inkHistory
|
|
370
422
|
]);
|
|
423
|
+
const fonts = useMemo(() => fontsProp ?? FONT_FAMILIES, [fontsProp]);
|
|
424
|
+
const signatureFonts = useMemo(() => signatureFontsProp ?? SIGNATURE_FONTS, [signatureFontsProp]);
|
|
425
|
+
const signatures = useMemo(() => createSignatureLibrary(signatureStore ?? createMemorySignatureStore()), [signatureStore]);
|
|
426
|
+
useEffect(() => {
|
|
427
|
+
signatures.refresh();
|
|
428
|
+
}, [signatures]);
|
|
429
|
+
/** Tool options last as long as the root; the tool style is per document. */
|
|
430
|
+
const [toolOptions] = useState(createToolOptions);
|
|
431
|
+
const decorate = useCallback((a) => latest.current.decorateAnnotation?.(a), [latest]);
|
|
432
|
+
const [decorateListeners] = useState(() => /* @__PURE__ */ new Set());
|
|
433
|
+
const onDecorateAnnotationChange = useCallback((listener) => {
|
|
434
|
+
decorateListeners.add(listener);
|
|
435
|
+
return () => {
|
|
436
|
+
decorateListeners.delete(listener);
|
|
437
|
+
};
|
|
438
|
+
}, [decorateListeners]);
|
|
439
|
+
const decoratedOnce = useRef(false);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
if (!decoratedOnce.current) {
|
|
442
|
+
decoratedOnce.current = true;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
for (const listener of decorateListeners) listener();
|
|
446
|
+
}, [decorateAnnotation, decorateListeners]);
|
|
371
447
|
const ctx = useMemo(() => ({
|
|
372
448
|
assets,
|
|
373
449
|
document,
|
|
@@ -378,6 +454,14 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
378
454
|
tools,
|
|
379
455
|
tool: internalTool,
|
|
380
456
|
setTool,
|
|
457
|
+
hostTools,
|
|
458
|
+
decorateAnnotation: decorate,
|
|
459
|
+
onDecorateAnnotationChange,
|
|
460
|
+
fonts,
|
|
461
|
+
toolOptions,
|
|
462
|
+
signatures,
|
|
463
|
+
signatureFonts,
|
|
464
|
+
toolStyle: styleTracker?.toolStyle ?? null,
|
|
381
465
|
zoomModel: zoom,
|
|
382
466
|
pageModel: page,
|
|
383
467
|
reportZoom,
|
|
@@ -397,6 +481,14 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
|
|
|
397
481
|
tools,
|
|
398
482
|
internalTool,
|
|
399
483
|
setTool,
|
|
484
|
+
hostTools,
|
|
485
|
+
decorate,
|
|
486
|
+
onDecorateAnnotationChange,
|
|
487
|
+
fonts,
|
|
488
|
+
toolOptions,
|
|
489
|
+
signatures,
|
|
490
|
+
signatureFonts,
|
|
491
|
+
styleTracker,
|
|
400
492
|
zoom,
|
|
401
493
|
page,
|
|
402
494
|
reportZoom,
|
|
@@ -452,7 +544,6 @@ function InkAnnotation({ annotation, element, children }) {
|
|
|
452
544
|
return () => observer.disconnect();
|
|
453
545
|
}, [element]);
|
|
454
546
|
const { id } = annotation;
|
|
455
|
-
const isSuggestion = annotation.type === "redact" && annotation.origin === "suggested";
|
|
456
547
|
const locked = annotation.locked === true;
|
|
457
548
|
const selected = ctx.selection.includes(id);
|
|
458
549
|
const store = ctx.document?.annotations;
|
|
@@ -461,23 +552,16 @@ function InkAnnotation({ annotation, element, children }) {
|
|
|
461
552
|
state: observed ?? (selected ? "selected" : "idle"),
|
|
462
553
|
locked,
|
|
463
554
|
select: () => store?.select([id]),
|
|
464
|
-
remove: () => void store?.remove(id)
|
|
465
|
-
accept: () => {
|
|
466
|
-
if (store && isSuggestion) acceptSuggestion(store, id);
|
|
467
|
-
},
|
|
468
|
-
dismiss: () => {
|
|
469
|
-
if (isSuggestion) store?.remove(id);
|
|
470
|
-
}
|
|
555
|
+
remove: () => void store?.remove(id)
|
|
471
556
|
}), [
|
|
472
557
|
annotation,
|
|
473
558
|
observed,
|
|
474
559
|
selected,
|
|
475
560
|
locked,
|
|
476
561
|
store,
|
|
477
|
-
id
|
|
478
|
-
isSuggestion
|
|
562
|
+
id
|
|
479
563
|
]);
|
|
480
|
-
return /* @__PURE__ */ jsx(Fragment, { children: children(slotProps) });
|
|
564
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: children(slotProps) });
|
|
481
565
|
}
|
|
482
566
|
//#endregion
|
|
483
567
|
//#region src/components/InkPage.tsx
|
|
@@ -511,14 +595,14 @@ function InkPage({ page, host, mount, error, customAnnotations = false, renderAn
|
|
|
511
595
|
mount,
|
|
512
596
|
customAnnotations
|
|
513
597
|
]);
|
|
514
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [error ? createPortal(/* @__PURE__ */ jsx("div", {
|
|
598
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [error ? createPortal(/* @__PURE__ */ jsx("div", {
|
|
515
599
|
className: "ink-page-error",
|
|
516
600
|
"data-ink-page-error": "",
|
|
517
601
|
role: "alert",
|
|
518
602
|
children: renderPageError ? renderPageError({
|
|
519
603
|
error,
|
|
520
604
|
page
|
|
521
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
605
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("p", { children: [
|
|
522
606
|
"Page ",
|
|
523
607
|
page + 1,
|
|
524
608
|
" could not be shown."
|
|
@@ -585,6 +669,13 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
|
|
|
585
669
|
gap: g,
|
|
586
670
|
buffer: b,
|
|
587
671
|
tool: c.tool,
|
|
672
|
+
...c.hostTools ? { tools: c.hostTools } : {},
|
|
673
|
+
decorateAnnotation: (a) => latest.current.ctx.decorateAnnotation(a),
|
|
674
|
+
...c.toolStyle ? { toolStyle: c.toolStyle } : {},
|
|
675
|
+
toolHistory: { batch: (fn) => latest.current.ctx.history.batch(fn) },
|
|
676
|
+
toolOptions: c.toolOptions,
|
|
677
|
+
fonts: [...c.fonts],
|
|
678
|
+
signatureLibrary: () => latest.current.ctx.signatures,
|
|
588
679
|
customAnnotations: cu,
|
|
589
680
|
onActivate: (id) => latest.current.ctx.setActivated(id),
|
|
590
681
|
onBuild: (vp, tc) => {
|
|
@@ -605,7 +696,14 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
|
|
|
605
696
|
lastReportedPage.current = p;
|
|
606
697
|
latest.current.ctx.reportPage(p);
|
|
607
698
|
};
|
|
608
|
-
|
|
699
|
+
const followTool = (id) => {
|
|
700
|
+
if (id !== latest.current.ctx.tool) latest.current.ctx.setTool(id);
|
|
701
|
+
};
|
|
702
|
+
stops = [
|
|
703
|
+
vp.zoom.subscribe(reportZoom),
|
|
704
|
+
vp.currentPage.subscribe(reportPage),
|
|
705
|
+
tc.active.subscribe(followTool)
|
|
706
|
+
];
|
|
609
707
|
reportZoom(vp.zoom.get());
|
|
610
708
|
reportPage(vp.currentPage.get());
|
|
611
709
|
},
|
|
@@ -641,6 +739,8 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
|
|
|
641
739
|
ctx.status.state,
|
|
642
740
|
latest
|
|
643
741
|
]);
|
|
742
|
+
const { onDecorateAnnotationChange } = ctx;
|
|
743
|
+
useEffect(() => onDecorateAnnotationChange(() => engine.current?.redecorate()), [onDecorateAnnotationChange]);
|
|
644
744
|
useEffect(() => {
|
|
645
745
|
const built = builtWith.current;
|
|
646
746
|
if (!engine.current || !built) return;
|
|
@@ -700,7 +800,7 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
|
|
|
700
800
|
}) : err ? /* @__PURE__ */ jsx("div", {
|
|
701
801
|
className: "ink-status ink-error",
|
|
702
802
|
role: "alert",
|
|
703
|
-
children: typeof error === "function" ? error({ error: err }) : error !== void 0 ? error : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
|
|
803
|
+
children: typeof error === "function" ? error({ error: err }) : error !== void 0 ? error : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
|
|
704
804
|
className: "ink-error-title",
|
|
705
805
|
children: "This document could not be opened."
|
|
706
806
|
}), /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("code", {
|
|
@@ -766,16 +866,21 @@ function InkIcon({ icon, size = 18 }) {
|
|
|
766
866
|
//#endregion
|
|
767
867
|
//#region src/components/InkToolButton.tsx
|
|
768
868
|
const ICONS = {
|
|
769
|
-
|
|
770
|
-
select:
|
|
869
|
+
"select-text": TextCursor,
|
|
870
|
+
select: SquareDashedMousePointer,
|
|
771
871
|
highlight: Highlighter,
|
|
772
872
|
"redact-area": Square,
|
|
773
|
-
|
|
873
|
+
sketch: PenTool,
|
|
874
|
+
draw: PenLine,
|
|
875
|
+
note: MessageSquare,
|
|
876
|
+
freetext: Type,
|
|
877
|
+
signature: Signature
|
|
774
878
|
};
|
|
775
879
|
function InkToolButton({ tool, label, asChild = false, className, children, onClick, ...rest }) {
|
|
776
880
|
const { tool: current, setTool } = useTool();
|
|
777
881
|
const active = current === tool;
|
|
778
|
-
const name = label ?? TOOL_LABELS[tool];
|
|
882
|
+
const name = label ?? TOOL_LABELS[tool] ?? tool;
|
|
883
|
+
const icon = ICONS[tool];
|
|
779
884
|
return /* @__PURE__ */ jsx(Toolbar.Button, {
|
|
780
885
|
asChild,
|
|
781
886
|
className: cx("ink-tool-button", className),
|
|
@@ -793,7 +898,7 @@ function InkToolButton({ tool, label, asChild = false, className, children, onCl
|
|
|
793
898
|
children: typeof children === "function" ? children({
|
|
794
899
|
active,
|
|
795
900
|
label: name
|
|
796
|
-
}) : children ?? /* @__PURE__ */ jsx(InkIcon, { icon
|
|
901
|
+
}) : children ?? (icon ? /* @__PURE__ */ jsx(InkIcon, { icon }) : null)
|
|
797
902
|
});
|
|
798
903
|
}
|
|
799
904
|
//#endregion
|
|
@@ -897,7 +1002,7 @@ const FITS = [{
|
|
|
897
1002
|
value: "fit-page",
|
|
898
1003
|
label: "Fit page"
|
|
899
1004
|
}];
|
|
900
|
-
function Item({ value, label }) {
|
|
1005
|
+
function Item$1({ value, label }) {
|
|
901
1006
|
return /* @__PURE__ */ jsxs(Select.Item, {
|
|
902
1007
|
value,
|
|
903
1008
|
className: "ink-select-item",
|
|
@@ -955,12 +1060,12 @@ function InkZoomSelect({ className, ...rest }) {
|
|
|
955
1060
|
side: "top",
|
|
956
1061
|
sideOffset: 4,
|
|
957
1062
|
children: /* @__PURE__ */ jsxs(Select.Viewport, { children: [
|
|
958
|
-
FITS.map((o) => /* @__PURE__ */ jsx(Item, {
|
|
1063
|
+
FITS.map((o) => /* @__PURE__ */ jsx(Item$1, {
|
|
959
1064
|
value: o.value,
|
|
960
1065
|
label: o.label
|
|
961
1066
|
}, o.value)),
|
|
962
1067
|
/* @__PURE__ */ jsx(Select.Separator, { className: "ink-select-separator" }),
|
|
963
|
-
levels.map((o) => /* @__PURE__ */ jsx(Item, {
|
|
1068
|
+
levels.map((o) => /* @__PURE__ */ jsx(Item$1, {
|
|
964
1069
|
value: o.value,
|
|
965
1070
|
label: o.label
|
|
966
1071
|
}, o.value))
|
|
@@ -1069,6 +1174,1094 @@ function InkNextPage({ asChild = false, className, children, disabled, onClick,
|
|
|
1069
1174
|
});
|
|
1070
1175
|
}
|
|
1071
1176
|
//#endregion
|
|
1177
|
+
//#region src/components/InkColorMenu.tsx
|
|
1178
|
+
function InkColorMenu({ property, label, className, children, standalone = false, group, fallback, disabled, ...rest }) {
|
|
1179
|
+
const Wrap = standalone ? Fragment : Toolbar.Button;
|
|
1180
|
+
const { current, supported, set } = useToolStyle(group);
|
|
1181
|
+
const [open, setOpen] = useState(false);
|
|
1182
|
+
const model = colorMenuModel(property, current, supported, fallback);
|
|
1183
|
+
const name = label ?? model.label;
|
|
1184
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
1185
|
+
const focusFirst = useRef(false);
|
|
1186
|
+
const input = useRef(null);
|
|
1187
|
+
const apply = useRef((_hex) => {});
|
|
1188
|
+
apply.current = (hex) => set(model.custom(hex));
|
|
1189
|
+
const bindInput = useCallback((el) => {
|
|
1190
|
+
input.current = el;
|
|
1191
|
+
el?.addEventListener("change", () => apply.current(el.value));
|
|
1192
|
+
}, []);
|
|
1193
|
+
useEffect(() => {
|
|
1194
|
+
if (input.current) input.current.value = model.customValue.toLowerCase();
|
|
1195
|
+
}, [model.customValue]);
|
|
1196
|
+
useEffect(() => {
|
|
1197
|
+
if (!model.enabled) setOpen(false);
|
|
1198
|
+
}, [model.enabled]);
|
|
1199
|
+
const trigger = model.trigger;
|
|
1200
|
+
const swatchStyle = trigger.kind === "color" ? { "--ink-swatch": trigger.color } : void 0;
|
|
1201
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Popover.Root, {
|
|
1202
|
+
open,
|
|
1203
|
+
onOpenChange: setOpen,
|
|
1204
|
+
children: [/* @__PURE__ */ jsx(Wrap, {
|
|
1205
|
+
...standalone ? {} : { asChild: true },
|
|
1206
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
1207
|
+
asChild: true,
|
|
1208
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1209
|
+
type: "button",
|
|
1210
|
+
className: cx("ink-menu-trigger ink-color-menu", className),
|
|
1211
|
+
"data-ink-color-menu": property,
|
|
1212
|
+
"data-state": open ? "open" : "closed",
|
|
1213
|
+
"data-mixed": model.mixed ? "" : void 0,
|
|
1214
|
+
"aria-label": name,
|
|
1215
|
+
title: name,
|
|
1216
|
+
"aria-haspopup": "menu",
|
|
1217
|
+
disabled: disabled === true || !model.enabled,
|
|
1218
|
+
...rest,
|
|
1219
|
+
onKeyDown: (e) => {
|
|
1220
|
+
rest.onKeyDown?.(e);
|
|
1221
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open || !model.enabled) return;
|
|
1222
|
+
e.preventDefault();
|
|
1223
|
+
focusFirst.current = true;
|
|
1224
|
+
setOpen(true);
|
|
1225
|
+
},
|
|
1226
|
+
children: children ?? /* @__PURE__ */ jsx("span", {
|
|
1227
|
+
className: "ink-swatch",
|
|
1228
|
+
"data-ink-swatch": "",
|
|
1229
|
+
"data-property": property,
|
|
1230
|
+
"data-kind": trigger.kind,
|
|
1231
|
+
"data-color": trigger.kind === "color" ? trigger.color : void 0,
|
|
1232
|
+
style: swatchStyle,
|
|
1233
|
+
"aria-hidden": "true"
|
|
1234
|
+
})
|
|
1235
|
+
})
|
|
1236
|
+
})
|
|
1237
|
+
}), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
|
|
1238
|
+
role: "menu",
|
|
1239
|
+
"aria-label": name,
|
|
1240
|
+
className: "ink-select-content ink-menu-content",
|
|
1241
|
+
"data-ink-select-content": "",
|
|
1242
|
+
"data-ink-menu": `color-${property}`,
|
|
1243
|
+
side: "bottom",
|
|
1244
|
+
align: "start",
|
|
1245
|
+
sideOffset: 4,
|
|
1246
|
+
collisionPadding: 8,
|
|
1247
|
+
onOpenAutoFocus: (e) => {
|
|
1248
|
+
e.preventDefault();
|
|
1249
|
+
const menu = e.currentTarget;
|
|
1250
|
+
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
1251
|
+
else focusInitialMenuItem(menu);
|
|
1252
|
+
focusFirst.current = false;
|
|
1253
|
+
},
|
|
1254
|
+
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1255
|
+
children: [
|
|
1256
|
+
model.noFill ? /* @__PURE__ */ jsxs("button", {
|
|
1257
|
+
type: "button",
|
|
1258
|
+
role: "menuitemradio",
|
|
1259
|
+
"aria-checked": model.noFill.checked,
|
|
1260
|
+
tabIndex: -1,
|
|
1261
|
+
className: "ink-select-item ink-menu-item",
|
|
1262
|
+
"data-ink-select-item": "",
|
|
1263
|
+
"data-state": model.noFill.checked ? "checked" : "unchecked",
|
|
1264
|
+
onClick: () => set(model.noFill.patch),
|
|
1265
|
+
children: ["No fill", model.noFill.checked ? /* @__PURE__ */ jsx("span", {
|
|
1266
|
+
className: "ink-select-indicator",
|
|
1267
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
1268
|
+
icon: Check,
|
|
1269
|
+
size: 16
|
|
1270
|
+
})
|
|
1271
|
+
}) : null]
|
|
1272
|
+
}) : null,
|
|
1273
|
+
/* @__PURE__ */ jsx("div", {
|
|
1274
|
+
role: "group",
|
|
1275
|
+
"aria-label": "Colours",
|
|
1276
|
+
className: "ink-color-grid",
|
|
1277
|
+
"data-ink-menu-columns": COLOR_COLUMNS,
|
|
1278
|
+
children: model.swatches.map((sw) => /* @__PURE__ */ jsx("button", {
|
|
1279
|
+
type: "button",
|
|
1280
|
+
role: "menuitemradio",
|
|
1281
|
+
"aria-label": sw.label,
|
|
1282
|
+
"aria-checked": sw.checked,
|
|
1283
|
+
tabIndex: -1,
|
|
1284
|
+
className: "ink-swatch-item",
|
|
1285
|
+
"data-state": sw.checked ? "checked" : "unchecked",
|
|
1286
|
+
style: { "--ink-swatch": sw.color },
|
|
1287
|
+
onClick: () => set(sw.patch)
|
|
1288
|
+
}, sw.key))
|
|
1289
|
+
}),
|
|
1290
|
+
/* @__PURE__ */ jsx("button", {
|
|
1291
|
+
type: "button",
|
|
1292
|
+
role: "menuitem",
|
|
1293
|
+
tabIndex: -1,
|
|
1294
|
+
className: "ink-select-item ink-menu-item",
|
|
1295
|
+
"data-ink-select-item": "",
|
|
1296
|
+
onClick: () => input.current?.click(),
|
|
1297
|
+
children: "Custom…"
|
|
1298
|
+
})
|
|
1299
|
+
]
|
|
1300
|
+
}) })]
|
|
1301
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
1302
|
+
ref: bindInput,
|
|
1303
|
+
type: "color",
|
|
1304
|
+
className: "ink-color-input",
|
|
1305
|
+
"aria-label": "Custom colour",
|
|
1306
|
+
"data-ink-color-input": property,
|
|
1307
|
+
tabIndex: -1,
|
|
1308
|
+
defaultValue: model.customValue.toLowerCase()
|
|
1309
|
+
})] });
|
|
1310
|
+
}
|
|
1311
|
+
//#endregion
|
|
1312
|
+
//#region src/components/InkShapeMenu.tsx
|
|
1313
|
+
function InkShapeMenu({ className, children, ...rest }) {
|
|
1314
|
+
const { tool, setTool } = useTool();
|
|
1315
|
+
const [options, setOptions] = useToolOptions("shape");
|
|
1316
|
+
const [open, setOpen] = useState(false);
|
|
1317
|
+
const model = shapeMenuModel(options, SHAPE_KINDS);
|
|
1318
|
+
const active = tool === "shape";
|
|
1319
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
1320
|
+
const focusFirst = useRef(false);
|
|
1321
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
1322
|
+
open,
|
|
1323
|
+
onOpenChange: setOpen,
|
|
1324
|
+
children: /* @__PURE__ */ jsx(Popover.Anchor, {
|
|
1325
|
+
asChild: true,
|
|
1326
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1327
|
+
role: "group",
|
|
1328
|
+
"aria-label": "Shape",
|
|
1329
|
+
className: cx("ink-split-button ink-shape-menu", className),
|
|
1330
|
+
"data-ink-shape-menu": "",
|
|
1331
|
+
...rest,
|
|
1332
|
+
children: [
|
|
1333
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1334
|
+
className: "ink-tool-button ink-shape-button",
|
|
1335
|
+
"data-ink-tool-button": "",
|
|
1336
|
+
"data-ink-shape-button": "",
|
|
1337
|
+
"data-tool": "shape",
|
|
1338
|
+
"data-kind": model.kind,
|
|
1339
|
+
"data-state": active ? "on" : "off",
|
|
1340
|
+
"aria-pressed": active,
|
|
1341
|
+
"aria-label": model.label,
|
|
1342
|
+
title: model.label,
|
|
1343
|
+
onClick: () => setTool("shape"),
|
|
1344
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: SHAPE_ICONS[model.kind] })
|
|
1345
|
+
}),
|
|
1346
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1347
|
+
asChild: true,
|
|
1348
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
1349
|
+
asChild: true,
|
|
1350
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1351
|
+
type: "button",
|
|
1352
|
+
className: "ink-menu-trigger ink-shape-menu-trigger",
|
|
1353
|
+
"data-ink-shape-menu-trigger": "",
|
|
1354
|
+
"data-state": open ? "open" : "closed",
|
|
1355
|
+
"aria-label": "Choose shape",
|
|
1356
|
+
title: "Choose shape",
|
|
1357
|
+
"aria-haspopup": "menu",
|
|
1358
|
+
onKeyDown: (e) => {
|
|
1359
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open) return;
|
|
1360
|
+
e.preventDefault();
|
|
1361
|
+
focusFirst.current = true;
|
|
1362
|
+
setOpen(true);
|
|
1363
|
+
},
|
|
1364
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronDown })
|
|
1365
|
+
})
|
|
1366
|
+
})
|
|
1367
|
+
}),
|
|
1368
|
+
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Content, {
|
|
1369
|
+
role: "menu",
|
|
1370
|
+
"aria-label": "Shapes",
|
|
1371
|
+
className: "ink-select-content ink-menu-content",
|
|
1372
|
+
"data-ink-select-content": "",
|
|
1373
|
+
"data-ink-menu": "shape",
|
|
1374
|
+
side: "bottom",
|
|
1375
|
+
align: "start",
|
|
1376
|
+
sideOffset: 4,
|
|
1377
|
+
collisionPadding: 8,
|
|
1378
|
+
onOpenAutoFocus: (e) => {
|
|
1379
|
+
e.preventDefault();
|
|
1380
|
+
const menu = e.currentTarget;
|
|
1381
|
+
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
1382
|
+
else focusInitialMenuItem(menu);
|
|
1383
|
+
focusFirst.current = false;
|
|
1384
|
+
},
|
|
1385
|
+
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1386
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1387
|
+
role: "group",
|
|
1388
|
+
"aria-label": "Shapes",
|
|
1389
|
+
className: "ink-shape-grid",
|
|
1390
|
+
"data-ink-menu-columns": SHAPE_COLUMNS,
|
|
1391
|
+
children: model.items.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
1392
|
+
type: "button",
|
|
1393
|
+
role: "menuitemradio",
|
|
1394
|
+
"aria-checked": item.checked,
|
|
1395
|
+
tabIndex: -1,
|
|
1396
|
+
className: "ink-select-item ink-menu-item ink-shape-item",
|
|
1397
|
+
"data-ink-select-item": "",
|
|
1398
|
+
"data-key": item.kind,
|
|
1399
|
+
"data-state": item.checked ? "checked" : "unchecked",
|
|
1400
|
+
onClick: () => {
|
|
1401
|
+
setOptions({ kind: item.kind });
|
|
1402
|
+
setTool("shape");
|
|
1403
|
+
setOpen(false);
|
|
1404
|
+
},
|
|
1405
|
+
children: [/* @__PURE__ */ jsx(InkIcon, {
|
|
1406
|
+
icon: SHAPE_ICONS[item.kind],
|
|
1407
|
+
size: 16
|
|
1408
|
+
}), item.label]
|
|
1409
|
+
}, item.kind))
|
|
1410
|
+
})
|
|
1411
|
+
}) })
|
|
1412
|
+
]
|
|
1413
|
+
})
|
|
1414
|
+
})
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
//#endregion
|
|
1418
|
+
//#region src/components/InkSignatureDialog.tsx
|
|
1419
|
+
function InkSignatureDialog({ open, onOpenChange, fonts, onSave, onCloseAutoFocus, className }) {
|
|
1420
|
+
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
1421
|
+
open,
|
|
1422
|
+
onOpenChange,
|
|
1423
|
+
children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Overlay, {
|
|
1424
|
+
className: "ink-dialog-overlay",
|
|
1425
|
+
"data-ink-dialog-overlay": ""
|
|
1426
|
+
}), /* @__PURE__ */ jsxs(Dialog.Content, {
|
|
1427
|
+
className: cx("ink-dialog ink-signature-dialog", className),
|
|
1428
|
+
"data-ink-signature-dialog": "",
|
|
1429
|
+
"aria-modal": "true",
|
|
1430
|
+
"aria-describedby": void 0,
|
|
1431
|
+
onOpenAutoFocus: (e) => {
|
|
1432
|
+
e.preventDefault();
|
|
1433
|
+
e.currentTarget.querySelector("[role=\"tab\"][aria-selected=\"true\"]")?.focus({ preventScroll: true });
|
|
1434
|
+
},
|
|
1435
|
+
onCloseAutoFocus,
|
|
1436
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
1437
|
+
className: "ink-dialog-title",
|
|
1438
|
+
children: SIGNATURE_LABELS.dialog
|
|
1439
|
+
}), /* @__PURE__ */ jsx(SignatureForm, {
|
|
1440
|
+
fonts,
|
|
1441
|
+
onSave,
|
|
1442
|
+
onClose: () => onOpenChange(false)
|
|
1443
|
+
})]
|
|
1444
|
+
})] })
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
const IDLE = {
|
|
1448
|
+
tab: "draw",
|
|
1449
|
+
padEmpty: true,
|
|
1450
|
+
name: "",
|
|
1451
|
+
font: "",
|
|
1452
|
+
image: void 0,
|
|
1453
|
+
imageError: void 0,
|
|
1454
|
+
saveError: void 0,
|
|
1455
|
+
canSave: false,
|
|
1456
|
+
saving: false
|
|
1457
|
+
};
|
|
1458
|
+
/** The dialog's body, mounted while it is open: one draft per opening. */
|
|
1459
|
+
function SignatureForm({ fonts, onSave, onClose }) {
|
|
1460
|
+
const ctx = useInkContext("InkSignatureDialog");
|
|
1461
|
+
const { setTool } = useTool();
|
|
1462
|
+
const [, setOptions] = useToolOptions("signature");
|
|
1463
|
+
const [draft] = useState(() => createSignatureDraft({ fonts: fonts ?? ctx.signatureFonts }));
|
|
1464
|
+
const s = useReadable(draft.state, IDLE);
|
|
1465
|
+
const uid = useId();
|
|
1466
|
+
const padHost = useRef(null);
|
|
1467
|
+
useEffect(() => {
|
|
1468
|
+
const host = padHost.current;
|
|
1469
|
+
const detach = host ? draft.attachPad(host) : void 0;
|
|
1470
|
+
return () => {
|
|
1471
|
+
detach?.();
|
|
1472
|
+
draft.destroy();
|
|
1473
|
+
};
|
|
1474
|
+
}, [draft]);
|
|
1475
|
+
const save = () => {
|
|
1476
|
+
draft.save(ctx.signatures).then((t) => {
|
|
1477
|
+
if (!t) return;
|
|
1478
|
+
setOptions({ templateId: t.id });
|
|
1479
|
+
setTool("signature");
|
|
1480
|
+
onSave?.(t);
|
|
1481
|
+
onClose();
|
|
1482
|
+
}, () => {});
|
|
1483
|
+
};
|
|
1484
|
+
const tabId = (tab) => `${uid}-tab-${tab}`;
|
|
1485
|
+
const panelId = (tab) => `${uid}-panel-${tab}`;
|
|
1486
|
+
const panel = (tab) => ({
|
|
1487
|
+
role: "tabpanel",
|
|
1488
|
+
id: panelId(tab),
|
|
1489
|
+
"aria-labelledby": tabId(tab),
|
|
1490
|
+
className: "ink-tab-panel",
|
|
1491
|
+
"data-key": tab,
|
|
1492
|
+
hidden: s.tab !== tab
|
|
1493
|
+
});
|
|
1494
|
+
const shown = s.name.trim() || SIGNATURE_LABELS.namePlaceholder;
|
|
1495
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1496
|
+
/* @__PURE__ */ jsx("div", {
|
|
1497
|
+
role: "tablist",
|
|
1498
|
+
"aria-label": SIGNATURE_LABELS.tabs,
|
|
1499
|
+
className: "ink-tabs",
|
|
1500
|
+
onKeyDown: (e) => handleTabKeys(e.nativeEvent, e.currentTarget),
|
|
1501
|
+
children: SIGNATURE_TABS.map((tab) => /* @__PURE__ */ jsx("button", {
|
|
1502
|
+
type: "button",
|
|
1503
|
+
role: "tab",
|
|
1504
|
+
id: tabId(tab),
|
|
1505
|
+
"aria-controls": panelId(tab),
|
|
1506
|
+
"aria-selected": s.tab === tab,
|
|
1507
|
+
tabIndex: s.tab === tab ? 0 : -1,
|
|
1508
|
+
className: "ink-tab",
|
|
1509
|
+
"data-key": tab,
|
|
1510
|
+
"data-state": s.tab === tab ? "active" : "inactive",
|
|
1511
|
+
onClick: () => draft.setTab(tab),
|
|
1512
|
+
children: SIGNATURE_LABELS[tab]
|
|
1513
|
+
}, tab))
|
|
1514
|
+
}),
|
|
1515
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1516
|
+
...panel("draw"),
|
|
1517
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1518
|
+
ref: padHost,
|
|
1519
|
+
className: "ink-signature-pad-host",
|
|
1520
|
+
"data-ink-signature-pad-host": "",
|
|
1521
|
+
role: "img",
|
|
1522
|
+
"aria-label": SIGNATURE_LABELS.pad
|
|
1523
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1524
|
+
className: "ink-dialog-row",
|
|
1525
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1526
|
+
type: "button",
|
|
1527
|
+
className: "ink-button",
|
|
1528
|
+
"data-ink-signature-clear": "",
|
|
1529
|
+
disabled: s.padEmpty,
|
|
1530
|
+
onClick: () => draft.clearPad(),
|
|
1531
|
+
children: SIGNATURE_LABELS.clear
|
|
1532
|
+
})
|
|
1533
|
+
})]
|
|
1534
|
+
}),
|
|
1535
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1536
|
+
...panel("type"),
|
|
1537
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
1538
|
+
className: "ink-field",
|
|
1539
|
+
children: [/* @__PURE__ */ jsx("span", { children: SIGNATURE_LABELS.name }), /* @__PURE__ */ jsx("input", {
|
|
1540
|
+
type: "text",
|
|
1541
|
+
className: "ink-input",
|
|
1542
|
+
"data-ink-signature-name": "",
|
|
1543
|
+
autoComplete: "name",
|
|
1544
|
+
placeholder: SIGNATURE_LABELS.namePlaceholder,
|
|
1545
|
+
value: s.name,
|
|
1546
|
+
onChange: (e) => draft.setName(e.target.value)
|
|
1547
|
+
})]
|
|
1548
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1549
|
+
role: "radiogroup",
|
|
1550
|
+
"aria-label": SIGNATURE_LABELS.fonts,
|
|
1551
|
+
className: "ink-signature-fonts",
|
|
1552
|
+
onKeyDown: (e) => handleRadioKeys(e.nativeEvent, e.currentTarget),
|
|
1553
|
+
children: draft.fonts.map((font) => /* @__PURE__ */ jsx("button", {
|
|
1554
|
+
type: "button",
|
|
1555
|
+
role: "radio",
|
|
1556
|
+
"aria-checked": s.font === font,
|
|
1557
|
+
"aria-label": font,
|
|
1558
|
+
tabIndex: s.font === font ? 0 : -1,
|
|
1559
|
+
className: "ink-signature-font",
|
|
1560
|
+
"data-key": font,
|
|
1561
|
+
"data-state": s.font === font ? "checked" : "unchecked",
|
|
1562
|
+
onClick: () => draft.setFont(font),
|
|
1563
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1564
|
+
className: "ink-signature-font-sample",
|
|
1565
|
+
style: { fontFamily: fontFamilyValue(font) },
|
|
1566
|
+
children: shown
|
|
1567
|
+
})
|
|
1568
|
+
}, font))
|
|
1569
|
+
})]
|
|
1570
|
+
}),
|
|
1571
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1572
|
+
...panel("image"),
|
|
1573
|
+
children: [
|
|
1574
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1575
|
+
className: "ink-field",
|
|
1576
|
+
children: [/* @__PURE__ */ jsx("span", { children: SIGNATURE_LABELS.file }), /* @__PURE__ */ jsx("input", {
|
|
1577
|
+
type: "file",
|
|
1578
|
+
accept: "image/png,image/jpeg",
|
|
1579
|
+
className: "ink-input",
|
|
1580
|
+
"data-ink-signature-file": "",
|
|
1581
|
+
onChange: (e) => void draft.chooseFile(e.target.files?.[0])
|
|
1582
|
+
})]
|
|
1583
|
+
}),
|
|
1584
|
+
s.image?.kind === "image" ? /* @__PURE__ */ jsx("img", {
|
|
1585
|
+
className: "ink-signature-image",
|
|
1586
|
+
src: s.image.imagedata,
|
|
1587
|
+
alt: ""
|
|
1588
|
+
}) : null,
|
|
1589
|
+
s.imageError ? /* @__PURE__ */ jsx("p", {
|
|
1590
|
+
className: "ink-field-error",
|
|
1591
|
+
role: "alert",
|
|
1592
|
+
children: s.imageError
|
|
1593
|
+
}) : null
|
|
1594
|
+
]
|
|
1595
|
+
}),
|
|
1596
|
+
s.saveError ? /* @__PURE__ */ jsx("p", {
|
|
1597
|
+
className: "ink-field-error",
|
|
1598
|
+
role: "alert",
|
|
1599
|
+
"data-ink-signature-save-error": "",
|
|
1600
|
+
children: s.saveError
|
|
1601
|
+
}) : null,
|
|
1602
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1603
|
+
className: "ink-dialog-actions",
|
|
1604
|
+
children: [/* @__PURE__ */ jsx(Dialog.Close, {
|
|
1605
|
+
asChild: true,
|
|
1606
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1607
|
+
type: "button",
|
|
1608
|
+
className: "ink-button",
|
|
1609
|
+
"data-ink-signature-cancel": "",
|
|
1610
|
+
children: SIGNATURE_LABELS.cancel
|
|
1611
|
+
})
|
|
1612
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1613
|
+
type: "button",
|
|
1614
|
+
className: "ink-button",
|
|
1615
|
+
"data-variant": "primary",
|
|
1616
|
+
"data-ink-signature-save": "",
|
|
1617
|
+
disabled: !s.canSave,
|
|
1618
|
+
onClick: save,
|
|
1619
|
+
children: SIGNATURE_LABELS.save
|
|
1620
|
+
})]
|
|
1621
|
+
})
|
|
1622
|
+
] });
|
|
1623
|
+
}
|
|
1624
|
+
//#endregion
|
|
1625
|
+
//#region src/components/InkSignatureMenu.tsx
|
|
1626
|
+
const NO_TEMPLATES = [];
|
|
1627
|
+
/** A saved signature drawn small: ink as filled outlines, an image as itself. */
|
|
1628
|
+
function SignaturePreview({ template }) {
|
|
1629
|
+
if (template.kind === "image") return /* @__PURE__ */ jsx("span", {
|
|
1630
|
+
className: "ink-signature-preview",
|
|
1631
|
+
"data-kind": "image",
|
|
1632
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
1633
|
+
src: template.imagedata,
|
|
1634
|
+
alt: "",
|
|
1635
|
+
draggable: false
|
|
1636
|
+
})
|
|
1637
|
+
});
|
|
1638
|
+
const { viewBox, paths } = signaturePreview(template);
|
|
1639
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1640
|
+
className: "ink-signature-preview",
|
|
1641
|
+
"data-kind": "ink",
|
|
1642
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
1643
|
+
viewBox,
|
|
1644
|
+
"aria-hidden": "true",
|
|
1645
|
+
children: paths.map((d, i) => /* @__PURE__ */ jsx("path", {
|
|
1646
|
+
d,
|
|
1647
|
+
fill: "currentColor"
|
|
1648
|
+
}, i))
|
|
1649
|
+
})
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function InkSignatureMenu({ className, children, fonts, ...rest }) {
|
|
1653
|
+
const { tool, setTool } = useTool();
|
|
1654
|
+
const [options, setOptions] = useToolOptions("signature");
|
|
1655
|
+
const { signatures } = useInkContext("InkSignatureMenu");
|
|
1656
|
+
const templates = useReadable(signatures.templates, NO_TEMPLATES);
|
|
1657
|
+
const removeError = useReadable(signatures.removeError, void 0);
|
|
1658
|
+
const model = signatureMenuModel(templates, options);
|
|
1659
|
+
const [open, setOpen] = useState(false);
|
|
1660
|
+
const [creating, setCreating] = useState(false);
|
|
1661
|
+
const active = tool === "signature";
|
|
1662
|
+
const main = useRef(null);
|
|
1663
|
+
const menu = useRef(null);
|
|
1664
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
1665
|
+
const focusFirst = useRef(false);
|
|
1666
|
+
/** Set while the menu closes for the dialog, which takes focus instead of the trigger. */
|
|
1667
|
+
const toDialog = useRef(false);
|
|
1668
|
+
/** The row of a removed signature, until focus lands on what took its place. */
|
|
1669
|
+
const removedRow = useRef(null);
|
|
1670
|
+
useEffect(() => {
|
|
1671
|
+
if (open) signatures.clearRemoveError();
|
|
1672
|
+
}, [open, signatures]);
|
|
1673
|
+
useEffect(() => {
|
|
1674
|
+
const row = removedRow.current;
|
|
1675
|
+
const panel = menu.current;
|
|
1676
|
+
if (row === null || !panel) return;
|
|
1677
|
+
removedRow.current = null;
|
|
1678
|
+
const rows = panel.querySelectorAll(".ink-signature-item");
|
|
1679
|
+
(rows[Math.min(row, rows.length - 1)] ?? panel.querySelector("[data-ink-signature-create]"))?.focus({ preventScroll: true });
|
|
1680
|
+
}, [templates]);
|
|
1681
|
+
const remove = (id, row) => {
|
|
1682
|
+
removedRow.current = row;
|
|
1683
|
+
signatures.remove(id).catch(() => {
|
|
1684
|
+
removedRow.current = null;
|
|
1685
|
+
});
|
|
1686
|
+
};
|
|
1687
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
1688
|
+
open,
|
|
1689
|
+
onOpenChange: setOpen,
|
|
1690
|
+
children: /* @__PURE__ */ jsx(Popover.Anchor, {
|
|
1691
|
+
asChild: true,
|
|
1692
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1693
|
+
role: "group",
|
|
1694
|
+
"aria-label": SIGNATURE_LABELS.sign,
|
|
1695
|
+
className: cx("ink-split-button ink-signature-menu", className),
|
|
1696
|
+
"data-ink-signature-menu": "",
|
|
1697
|
+
...rest,
|
|
1698
|
+
children: [
|
|
1699
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1700
|
+
ref: main,
|
|
1701
|
+
className: "ink-tool-button ink-signature-button",
|
|
1702
|
+
"data-ink-tool-button": "",
|
|
1703
|
+
"data-ink-signature-button": "",
|
|
1704
|
+
"data-tool": "signature",
|
|
1705
|
+
"data-state": active ? "on" : "off",
|
|
1706
|
+
"aria-pressed": active,
|
|
1707
|
+
"aria-label": SIGNATURE_LABELS.sign,
|
|
1708
|
+
title: SIGNATURE_LABELS.sign,
|
|
1709
|
+
onClick: () => {
|
|
1710
|
+
if (model.selected) setTool("signature");
|
|
1711
|
+
else setOpen(true);
|
|
1712
|
+
},
|
|
1713
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: Signature })
|
|
1714
|
+
}),
|
|
1715
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1716
|
+
asChild: true,
|
|
1717
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
1718
|
+
asChild: true,
|
|
1719
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1720
|
+
type: "button",
|
|
1721
|
+
className: "ink-menu-trigger ink-signature-menu-trigger",
|
|
1722
|
+
"data-ink-signature-menu-trigger": "",
|
|
1723
|
+
"data-state": open ? "open" : "closed",
|
|
1724
|
+
"aria-label": SIGNATURE_LABELS.trigger,
|
|
1725
|
+
title: SIGNATURE_LABELS.trigger,
|
|
1726
|
+
"aria-haspopup": "menu",
|
|
1727
|
+
onKeyDown: (e) => {
|
|
1728
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open) return;
|
|
1729
|
+
e.preventDefault();
|
|
1730
|
+
focusFirst.current = true;
|
|
1731
|
+
setOpen(true);
|
|
1732
|
+
},
|
|
1733
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronDown })
|
|
1734
|
+
})
|
|
1735
|
+
})
|
|
1736
|
+
}),
|
|
1737
|
+
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
|
|
1738
|
+
ref: menu,
|
|
1739
|
+
role: "menu",
|
|
1740
|
+
"aria-label": SIGNATURE_LABELS.menu,
|
|
1741
|
+
className: "ink-select-content ink-menu-content",
|
|
1742
|
+
"data-ink-select-content": "",
|
|
1743
|
+
"data-ink-menu": "signature",
|
|
1744
|
+
side: "bottom",
|
|
1745
|
+
align: "start",
|
|
1746
|
+
sideOffset: 4,
|
|
1747
|
+
collisionPadding: 8,
|
|
1748
|
+
onOpenAutoFocus: (e) => {
|
|
1749
|
+
e.preventDefault();
|
|
1750
|
+
const el = e.currentTarget;
|
|
1751
|
+
if (focusFirst.current) menuItems(el)[0]?.focus({ preventScroll: true });
|
|
1752
|
+
else focusInitialMenuItem(el);
|
|
1753
|
+
focusFirst.current = false;
|
|
1754
|
+
},
|
|
1755
|
+
onCloseAutoFocus: (e) => {
|
|
1756
|
+
if (toDialog.current) e.preventDefault();
|
|
1757
|
+
toDialog.current = false;
|
|
1758
|
+
},
|
|
1759
|
+
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
1760
|
+
children: [
|
|
1761
|
+
model.items.length > 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1762
|
+
role: "group",
|
|
1763
|
+
"aria-label": SIGNATURE_LABELS.saved,
|
|
1764
|
+
className: "ink-signature-list",
|
|
1765
|
+
"data-ink-menu-columns": 2,
|
|
1766
|
+
children: model.items.map((item, row) => [/* @__PURE__ */ jsx("button", {
|
|
1767
|
+
type: "button",
|
|
1768
|
+
role: "menuitemradio",
|
|
1769
|
+
"aria-checked": item.checked,
|
|
1770
|
+
"aria-label": item.label,
|
|
1771
|
+
tabIndex: -1,
|
|
1772
|
+
className: "ink-select-item ink-menu-item ink-signature-item",
|
|
1773
|
+
"data-ink-select-item": "",
|
|
1774
|
+
"data-key": item.id,
|
|
1775
|
+
"data-state": item.checked ? "checked" : "unchecked",
|
|
1776
|
+
onClick: () => {
|
|
1777
|
+
setOptions({ templateId: item.id });
|
|
1778
|
+
setTool("signature");
|
|
1779
|
+
setOpen(false);
|
|
1780
|
+
},
|
|
1781
|
+
children: /* @__PURE__ */ jsx(SignaturePreview, { template: item.template })
|
|
1782
|
+
}, `${item.id}-choose`), /* @__PURE__ */ jsx("button", {
|
|
1783
|
+
type: "button",
|
|
1784
|
+
role: "menuitem",
|
|
1785
|
+
"aria-label": item.removeLabel,
|
|
1786
|
+
title: item.removeLabel,
|
|
1787
|
+
tabIndex: -1,
|
|
1788
|
+
className: "ink-menu-item ink-signature-remove",
|
|
1789
|
+
"data-ink-signature-remove": "",
|
|
1790
|
+
"data-key": item.id,
|
|
1791
|
+
onClick: () => remove(item.id, row),
|
|
1792
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
1793
|
+
icon: Trash2,
|
|
1794
|
+
size: 16
|
|
1795
|
+
})
|
|
1796
|
+
}, `${item.id}-remove`)])
|
|
1797
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1798
|
+
role: "separator",
|
|
1799
|
+
className: "ink-select-separator"
|
|
1800
|
+
})] }) : null,
|
|
1801
|
+
/* @__PURE__ */ jsxs("button", {
|
|
1802
|
+
type: "button",
|
|
1803
|
+
role: "menuitem",
|
|
1804
|
+
tabIndex: -1,
|
|
1805
|
+
className: "ink-select-item ink-menu-item ink-signature-create",
|
|
1806
|
+
"data-ink-select-item": "",
|
|
1807
|
+
"data-ink-signature-create": "",
|
|
1808
|
+
onClick: () => {
|
|
1809
|
+
toDialog.current = true;
|
|
1810
|
+
setOpen(false);
|
|
1811
|
+
setCreating(true);
|
|
1812
|
+
},
|
|
1813
|
+
children: [/* @__PURE__ */ jsx(InkIcon, {
|
|
1814
|
+
icon: Plus,
|
|
1815
|
+
size: 16
|
|
1816
|
+
}), SIGNATURE_LABELS.create]
|
|
1817
|
+
}),
|
|
1818
|
+
removeError ? /* @__PURE__ */ jsx("p", {
|
|
1819
|
+
className: "ink-field-error ink-signature-remove-error",
|
|
1820
|
+
role: "alert",
|
|
1821
|
+
"data-ink-signature-remove-error": "",
|
|
1822
|
+
children: removeError
|
|
1823
|
+
}) : null
|
|
1824
|
+
]
|
|
1825
|
+
}) }),
|
|
1826
|
+
/* @__PURE__ */ jsx(InkSignatureDialog, {
|
|
1827
|
+
open: creating,
|
|
1828
|
+
onOpenChange: setCreating,
|
|
1829
|
+
fonts,
|
|
1830
|
+
onCloseAutoFocus: (e) => {
|
|
1831
|
+
e.preventDefault();
|
|
1832
|
+
main.current?.focus({ preventScroll: true });
|
|
1833
|
+
}
|
|
1834
|
+
})
|
|
1835
|
+
]
|
|
1836
|
+
})
|
|
1837
|
+
})
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
//#endregion
|
|
1841
|
+
//#region src/components/InkHighlightMenu.tsx
|
|
1842
|
+
function InkHighlightMenu({ className, children, ...rest }) {
|
|
1843
|
+
const { tool, setTool } = useTool();
|
|
1844
|
+
const [options, setOptions] = useToolOptions("highlight");
|
|
1845
|
+
const { document: doc } = useDocument();
|
|
1846
|
+
const [open, setOpen] = useState(false);
|
|
1847
|
+
const model = highlightMenuModel(options, MARKUP_KINDS);
|
|
1848
|
+
const active = tool === "highlight";
|
|
1849
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
1850
|
+
const focusFirst = useRef(false);
|
|
1851
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
1852
|
+
open,
|
|
1853
|
+
onOpenChange: setOpen,
|
|
1854
|
+
children: /* @__PURE__ */ jsx(Popover.Anchor, {
|
|
1855
|
+
asChild: true,
|
|
1856
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1857
|
+
role: "group",
|
|
1858
|
+
"aria-label": "Highlight",
|
|
1859
|
+
className: cx("ink-split-button ink-highlight-menu", className),
|
|
1860
|
+
"data-ink-highlight-menu": "",
|
|
1861
|
+
...rest,
|
|
1862
|
+
children: [
|
|
1863
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1864
|
+
className: "ink-tool-button ink-highlight-button",
|
|
1865
|
+
"data-ink-tool-button": "",
|
|
1866
|
+
"data-ink-highlight-button": "",
|
|
1867
|
+
"data-tool": "highlight",
|
|
1868
|
+
"data-kind": model.kind,
|
|
1869
|
+
"data-state": active ? "on" : "off",
|
|
1870
|
+
"aria-pressed": active,
|
|
1871
|
+
"aria-label": model.label,
|
|
1872
|
+
title: model.label,
|
|
1873
|
+
onClick: () => setTool("highlight"),
|
|
1874
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: MARKUP_ICONS[model.kind] })
|
|
1875
|
+
}),
|
|
1876
|
+
/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
1877
|
+
asChild: true,
|
|
1878
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
1879
|
+
asChild: true,
|
|
1880
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
1881
|
+
type: "button",
|
|
1882
|
+
className: "ink-menu-trigger ink-highlight-menu-trigger",
|
|
1883
|
+
"data-ink-highlight-menu-trigger": "",
|
|
1884
|
+
"data-state": open ? "open" : "closed",
|
|
1885
|
+
"aria-label": "Choose highlight",
|
|
1886
|
+
title: "Choose highlight",
|
|
1887
|
+
"aria-haspopup": "menu",
|
|
1888
|
+
onKeyDown: (e) => {
|
|
1889
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open) return;
|
|
1890
|
+
e.preventDefault();
|
|
1891
|
+
focusFirst.current = true;
|
|
1892
|
+
setOpen(true);
|
|
1893
|
+
},
|
|
1894
|
+
children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronDown })
|
|
1895
|
+
})
|
|
1896
|
+
})
|
|
1897
|
+
}),
|
|
1898
|
+
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
|
|
1899
|
+
role: "menu",
|
|
1900
|
+
"aria-label": "Highlights",
|
|
1901
|
+
className: "ink-select-content ink-menu-content",
|
|
1902
|
+
"data-ink-select-content": "",
|
|
1903
|
+
"data-ink-menu": "highlight",
|
|
1904
|
+
side: "bottom",
|
|
1905
|
+
align: "start",
|
|
1906
|
+
sideOffset: 4,
|
|
1907
|
+
collisionPadding: 8,
|
|
1908
|
+
onOpenAutoFocus: (e) => {
|
|
1909
|
+
e.preventDefault();
|
|
1910
|
+
const menu = e.currentTarget;
|
|
1911
|
+
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
1912
|
+
else focusInitialMenuItem(menu);
|
|
1913
|
+
focusFirst.current = false;
|
|
1914
|
+
},
|
|
1915
|
+
onKeyDownCapture: (e) => {
|
|
1916
|
+
if (e.currentTarget.contains(e.target)) handleMenuKeys(e.nativeEvent, e.currentTarget);
|
|
1917
|
+
},
|
|
1918
|
+
children: [
|
|
1919
|
+
/* @__PURE__ */ jsx("div", {
|
|
1920
|
+
role: "group",
|
|
1921
|
+
"aria-label": "Kinds",
|
|
1922
|
+
className: "ink-highlight-kinds",
|
|
1923
|
+
children: model.items.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
1924
|
+
type: "button",
|
|
1925
|
+
role: "menuitemradio",
|
|
1926
|
+
"aria-checked": item.checked,
|
|
1927
|
+
tabIndex: -1,
|
|
1928
|
+
className: "ink-select-item ink-menu-item ink-highlight-item",
|
|
1929
|
+
"data-ink-select-item": "",
|
|
1930
|
+
"data-key": item.kind,
|
|
1931
|
+
"data-state": item.checked ? "checked" : "unchecked",
|
|
1932
|
+
onClick: () => {
|
|
1933
|
+
setOptions({ kind: item.kind });
|
|
1934
|
+
setTool("highlight");
|
|
1935
|
+
setOpen(false);
|
|
1936
|
+
},
|
|
1937
|
+
children: [/* @__PURE__ */ jsx(InkIcon, {
|
|
1938
|
+
icon: MARKUP_ICONS[item.kind],
|
|
1939
|
+
size: 16
|
|
1940
|
+
}), item.label]
|
|
1941
|
+
}, item.kind))
|
|
1942
|
+
}),
|
|
1943
|
+
/* @__PURE__ */ jsx("div", {
|
|
1944
|
+
role: "separator",
|
|
1945
|
+
className: "ink-select-separator"
|
|
1946
|
+
}),
|
|
1947
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1948
|
+
role: "group",
|
|
1949
|
+
"aria-label": "Colour",
|
|
1950
|
+
className: "ink-highlight-colour",
|
|
1951
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1952
|
+
className: "ink-highlight-colour-label",
|
|
1953
|
+
"aria-hidden": "true",
|
|
1954
|
+
children: "Colour"
|
|
1955
|
+
}), /* @__PURE__ */ jsx(InkColorMenu, {
|
|
1956
|
+
property: "border",
|
|
1957
|
+
group: "markup",
|
|
1958
|
+
label: "Highlight colour",
|
|
1959
|
+
standalone: true,
|
|
1960
|
+
fallback: doc?.annotations.defaults.color(model.kind),
|
|
1961
|
+
role: "menuitem",
|
|
1962
|
+
tabIndex: -1
|
|
1963
|
+
})]
|
|
1964
|
+
})
|
|
1965
|
+
]
|
|
1966
|
+
}) })
|
|
1967
|
+
]
|
|
1968
|
+
})
|
|
1969
|
+
})
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
//#endregion
|
|
1973
|
+
//#region src/components/InkTextStyleMenu.tsx
|
|
1974
|
+
const TOGGLE_ICONS = {
|
|
1975
|
+
bold: Bold,
|
|
1976
|
+
italic: Italic,
|
|
1977
|
+
underline: Underline
|
|
1978
|
+
};
|
|
1979
|
+
const ALIGN_ICONS = {
|
|
1980
|
+
left: AlignLeft,
|
|
1981
|
+
center: AlignCenter,
|
|
1982
|
+
right: AlignRight,
|
|
1983
|
+
justify: AlignJustify
|
|
1984
|
+
};
|
|
1985
|
+
function InkTextStyleMenu({ className, children, disabled, ...rest }) {
|
|
1986
|
+
const { fonts } = useInkContext("InkTextStyleMenu");
|
|
1987
|
+
const { current, supported, set } = useToolStyle();
|
|
1988
|
+
const [open, setOpen] = useState(false);
|
|
1989
|
+
/** The size as typed, until it is committed with Enter or blur. */
|
|
1990
|
+
const [draft, setDraft] = useState(null);
|
|
1991
|
+
const model = textStyleMenuModel(current, supported, fonts);
|
|
1992
|
+
useEffect(() => {
|
|
1993
|
+
if (model.enabled) return;
|
|
1994
|
+
setOpen(false);
|
|
1995
|
+
setDraft(null);
|
|
1996
|
+
}, [model.enabled]);
|
|
1997
|
+
const commitSize = () => {
|
|
1998
|
+
if (draft === null) return;
|
|
1999
|
+
if (draft.trim() !== "") {
|
|
2000
|
+
const patch = model.size.commit(Number(draft));
|
|
2001
|
+
if (patch) set(patch);
|
|
2002
|
+
}
|
|
2003
|
+
setDraft(null);
|
|
2004
|
+
};
|
|
2005
|
+
const nudge = (direction) => {
|
|
2006
|
+
const patch = model.size.nudge(direction);
|
|
2007
|
+
if (patch) set(patch);
|
|
2008
|
+
setDraft(null);
|
|
2009
|
+
};
|
|
2010
|
+
const sizeShown = draft ?? (model.size.value === null ? "" : String(model.size.value));
|
|
2011
|
+
return /* @__PURE__ */ jsxs(Popover.Root, {
|
|
2012
|
+
open,
|
|
2013
|
+
onOpenChange: (next) => {
|
|
2014
|
+
if (!next) commitSize();
|
|
2015
|
+
setOpen(next);
|
|
2016
|
+
},
|
|
2017
|
+
children: [/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
2018
|
+
asChild: true,
|
|
2019
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
2020
|
+
asChild: true,
|
|
2021
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2022
|
+
type: "button",
|
|
2023
|
+
className: cx("ink-menu-trigger ink-text-style-menu", className),
|
|
2024
|
+
"data-ink-text-style-menu": "",
|
|
2025
|
+
"data-state": open ? "open" : "closed",
|
|
2026
|
+
"data-mixed": model.mixed ? "" : void 0,
|
|
2027
|
+
"aria-label": model.label,
|
|
2028
|
+
title: model.label,
|
|
2029
|
+
"aria-haspopup": "dialog",
|
|
2030
|
+
disabled: disabled === true || !model.enabled,
|
|
2031
|
+
...rest,
|
|
2032
|
+
onKeyDown: (e) => {
|
|
2033
|
+
rest.onKeyDown?.(e);
|
|
2034
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open || !model.enabled) return;
|
|
2035
|
+
e.preventDefault();
|
|
2036
|
+
setOpen(true);
|
|
2037
|
+
},
|
|
2038
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ALargeSmall })
|
|
2039
|
+
})
|
|
2040
|
+
})
|
|
2041
|
+
}), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(Popover.Content, {
|
|
2042
|
+
"aria-label": model.label,
|
|
2043
|
+
className: "ink-select-content ink-menu-content ink-text-style-content",
|
|
2044
|
+
"data-ink-select-content": "",
|
|
2045
|
+
"data-ink-menu": "text-style",
|
|
2046
|
+
side: "bottom",
|
|
2047
|
+
align: "start",
|
|
2048
|
+
sideOffset: 4,
|
|
2049
|
+
collisionPadding: 8,
|
|
2050
|
+
children: [
|
|
2051
|
+
/* @__PURE__ */ jsxs("select", {
|
|
2052
|
+
className: "ink-text-style-font",
|
|
2053
|
+
"aria-label": model.font.label,
|
|
2054
|
+
value: model.font.value,
|
|
2055
|
+
onChange: (e) => set(model.font.patch(e.target.value)),
|
|
2056
|
+
children: [model.font.value === "" ? /* @__PURE__ */ jsx("option", {
|
|
2057
|
+
value: "",
|
|
2058
|
+
disabled: true,
|
|
2059
|
+
hidden: true,
|
|
2060
|
+
children: "Mixed"
|
|
2061
|
+
}) : null, model.font.options.map((f) => /* @__PURE__ */ jsx("option", {
|
|
2062
|
+
value: f,
|
|
2063
|
+
children: f
|
|
2064
|
+
}, f))]
|
|
2065
|
+
}),
|
|
2066
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2067
|
+
className: "ink-text-style-row",
|
|
2068
|
+
children: [/* @__PURE__ */ jsx(InkColorMenu, {
|
|
2069
|
+
property: "textColor",
|
|
2070
|
+
standalone: true
|
|
2071
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2072
|
+
className: "ink-text-style-size",
|
|
2073
|
+
role: "group",
|
|
2074
|
+
"aria-label": "Size",
|
|
2075
|
+
children: [
|
|
2076
|
+
/* @__PURE__ */ jsx("button", {
|
|
2077
|
+
type: "button",
|
|
2078
|
+
className: "ink-text-style-button",
|
|
2079
|
+
"aria-label": "Decrease font size",
|
|
2080
|
+
onClick: () => nudge(-1),
|
|
2081
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
2082
|
+
icon: Minus,
|
|
2083
|
+
size: 16
|
|
2084
|
+
})
|
|
2085
|
+
}),
|
|
2086
|
+
/* @__PURE__ */ jsx("input", {
|
|
2087
|
+
type: "number",
|
|
2088
|
+
className: "ink-text-style-input",
|
|
2089
|
+
"aria-label": model.size.label,
|
|
2090
|
+
min: model.size.min,
|
|
2091
|
+
max: model.size.max,
|
|
2092
|
+
step: model.size.step,
|
|
2093
|
+
value: sizeShown,
|
|
2094
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2095
|
+
onBlur: commitSize,
|
|
2096
|
+
onKeyDown: (e) => {
|
|
2097
|
+
if (e.key === "Enter") {
|
|
2098
|
+
e.preventDefault();
|
|
2099
|
+
commitSize();
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}),
|
|
2103
|
+
/* @__PURE__ */ jsx("button", {
|
|
2104
|
+
type: "button",
|
|
2105
|
+
className: "ink-text-style-button",
|
|
2106
|
+
"aria-label": "Increase font size",
|
|
2107
|
+
onClick: () => nudge(1),
|
|
2108
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
2109
|
+
icon: Plus,
|
|
2110
|
+
size: 16
|
|
2111
|
+
})
|
|
2112
|
+
})
|
|
2113
|
+
]
|
|
2114
|
+
})]
|
|
2115
|
+
}),
|
|
2116
|
+
/* @__PURE__ */ jsx("div", {
|
|
2117
|
+
className: "ink-text-style-row",
|
|
2118
|
+
role: "group",
|
|
2119
|
+
"aria-label": "Emphasis",
|
|
2120
|
+
children: model.toggles.map((t) => /* @__PURE__ */ jsx("button", {
|
|
2121
|
+
type: "button",
|
|
2122
|
+
className: "ink-text-style-button",
|
|
2123
|
+
"aria-label": t.label,
|
|
2124
|
+
title: t.label,
|
|
2125
|
+
"aria-pressed": t.pressed,
|
|
2126
|
+
"data-state": t.pressed === true ? "on" : "off",
|
|
2127
|
+
"data-mixed": t.pressed === "mixed" ? "" : void 0,
|
|
2128
|
+
onClick: () => set(t.patch),
|
|
2129
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
2130
|
+
icon: TOGGLE_ICONS[t.key],
|
|
2131
|
+
size: 16
|
|
2132
|
+
})
|
|
2133
|
+
}, t.key))
|
|
2134
|
+
}),
|
|
2135
|
+
/* @__PURE__ */ jsx("div", {
|
|
2136
|
+
className: "ink-text-style-row",
|
|
2137
|
+
role: "radiogroup",
|
|
2138
|
+
"aria-label": model.align.label,
|
|
2139
|
+
onKeyDown: (e) => handleRadioKeys(e.nativeEvent, e.currentTarget),
|
|
2140
|
+
children: model.align.choices.map((c) => /* @__PURE__ */ jsx("button", {
|
|
2141
|
+
type: "button",
|
|
2142
|
+
role: "radio",
|
|
2143
|
+
className: "ink-text-style-button",
|
|
2144
|
+
"aria-label": c.label,
|
|
2145
|
+
title: c.label,
|
|
2146
|
+
"aria-checked": c.checked,
|
|
2147
|
+
tabIndex: c.tabStop ? 0 : -1,
|
|
2148
|
+
"data-state": c.checked ? "on" : "off",
|
|
2149
|
+
onClick: () => set(c.patch),
|
|
2150
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
2151
|
+
icon: ALIGN_ICONS[c.key],
|
|
2152
|
+
size: 16
|
|
2153
|
+
})
|
|
2154
|
+
}, c.key))
|
|
2155
|
+
})
|
|
2156
|
+
]
|
|
2157
|
+
}) })]
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
//#endregion
|
|
2161
|
+
//#region src/components/InkLineStyleMenu.tsx
|
|
2162
|
+
function Item({ label, checked, disabled, onSelect, children }) {
|
|
2163
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2164
|
+
type: "button",
|
|
2165
|
+
role: "menuitemradio",
|
|
2166
|
+
"aria-checked": checked,
|
|
2167
|
+
disabled,
|
|
2168
|
+
tabIndex: -1,
|
|
2169
|
+
className: "ink-select-item ink-menu-item",
|
|
2170
|
+
"data-ink-select-item": "",
|
|
2171
|
+
"data-state": checked ? "checked" : "unchecked",
|
|
2172
|
+
onClick: onSelect,
|
|
2173
|
+
children: [children ?? label, checked ? /* @__PURE__ */ jsx("span", {
|
|
2174
|
+
className: "ink-select-indicator",
|
|
2175
|
+
children: /* @__PURE__ */ jsx(InkIcon, {
|
|
2176
|
+
icon: Check,
|
|
2177
|
+
size: 16
|
|
2178
|
+
})
|
|
2179
|
+
}) : null]
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
function Group({ label, disabled, children }) {
|
|
2183
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2184
|
+
role: "group",
|
|
2185
|
+
"aria-label": label,
|
|
2186
|
+
"aria-disabled": disabled || void 0,
|
|
2187
|
+
"data-disabled": disabled ? "" : void 0,
|
|
2188
|
+
className: "ink-menu-group",
|
|
2189
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2190
|
+
className: "ink-menu-label",
|
|
2191
|
+
"aria-hidden": "true",
|
|
2192
|
+
children: label
|
|
2193
|
+
}), children]
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
function InkLineStyleMenu({ className, children, disabled, ...rest }) {
|
|
2197
|
+
const { current, supported, set } = useToolStyle();
|
|
2198
|
+
const [open, setOpen] = useState(false);
|
|
2199
|
+
const model = lineStyleMenuModel(current, supported);
|
|
2200
|
+
/** ArrowDown opens on the first item; a click opens on the checked one. */
|
|
2201
|
+
const focusFirst = useRef(false);
|
|
2202
|
+
useEffect(() => {
|
|
2203
|
+
if (!model.enabled) setOpen(false);
|
|
2204
|
+
}, [model.enabled]);
|
|
2205
|
+
return /* @__PURE__ */ jsxs(Popover.Root, {
|
|
2206
|
+
open,
|
|
2207
|
+
onOpenChange: setOpen,
|
|
2208
|
+
children: [/* @__PURE__ */ jsx(Toolbar.Button, {
|
|
2209
|
+
asChild: true,
|
|
2210
|
+
children: /* @__PURE__ */ jsx(Popover.Trigger, {
|
|
2211
|
+
asChild: true,
|
|
2212
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2213
|
+
type: "button",
|
|
2214
|
+
className: cx("ink-menu-trigger ink-line-style-menu", className),
|
|
2215
|
+
"data-ink-line-style-menu": "",
|
|
2216
|
+
"data-state": open ? "open" : "closed",
|
|
2217
|
+
"data-mixed": model.mixed ? "" : void 0,
|
|
2218
|
+
"aria-label": "Line style",
|
|
2219
|
+
title: "Line style",
|
|
2220
|
+
"aria-haspopup": "menu",
|
|
2221
|
+
disabled: disabled === true || !model.enabled,
|
|
2222
|
+
...rest,
|
|
2223
|
+
onKeyDown: (e) => {
|
|
2224
|
+
rest.onKeyDown?.(e);
|
|
2225
|
+
if (e.key !== "ArrowDown" || e.defaultPrevented || open || !model.enabled) return;
|
|
2226
|
+
e.preventDefault();
|
|
2227
|
+
focusFirst.current = true;
|
|
2228
|
+
setOpen(true);
|
|
2229
|
+
},
|
|
2230
|
+
children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: Minus })
|
|
2231
|
+
})
|
|
2232
|
+
})
|
|
2233
|
+
}), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Content, {
|
|
2234
|
+
role: "menu",
|
|
2235
|
+
"aria-label": "Line style",
|
|
2236
|
+
className: "ink-select-content ink-menu-content",
|
|
2237
|
+
"data-ink-select-content": "",
|
|
2238
|
+
"data-ink-menu": "line-style",
|
|
2239
|
+
side: "bottom",
|
|
2240
|
+
align: "start",
|
|
2241
|
+
sideOffset: 4,
|
|
2242
|
+
collisionPadding: 8,
|
|
2243
|
+
onOpenAutoFocus: (e) => {
|
|
2244
|
+
e.preventDefault();
|
|
2245
|
+
const menu = e.currentTarget;
|
|
2246
|
+
if (focusFirst.current) menuItems(menu)[0]?.focus({ preventScroll: true });
|
|
2247
|
+
else focusInitialMenuItem(menu);
|
|
2248
|
+
focusFirst.current = false;
|
|
2249
|
+
},
|
|
2250
|
+
onKeyDown: (e) => handleMenuKeys(e.nativeEvent, e.currentTarget),
|
|
2251
|
+
children: model.sections.map((section) => /* @__PURE__ */ jsx(Group, {
|
|
2252
|
+
label: section.label,
|
|
2253
|
+
disabled: section.disabled,
|
|
2254
|
+
children: section.items.map((item) => /* @__PURE__ */ jsx(Item, {
|
|
2255
|
+
label: item.label,
|
|
2256
|
+
checked: item.checked,
|
|
2257
|
+
disabled: section.disabled,
|
|
2258
|
+
onSelect: () => set(item.patch)
|
|
2259
|
+
}, item.key))
|
|
2260
|
+
}, section.label))
|
|
2261
|
+
}) })]
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
//#endregion
|
|
1072
2265
|
//#region src/components/annotation-element.ts
|
|
1073
2266
|
/**
|
|
1074
2267
|
* The overlay element core draws for an annotation, for anchoring a popover to it. Core
|
|
@@ -1100,13 +2293,13 @@ function useFocusReturn(ctx) {
|
|
|
1100
2293
|
}), [latest]);
|
|
1101
2294
|
}
|
|
1102
2295
|
//#endregion
|
|
1103
|
-
//#region src/components/
|
|
1104
|
-
function
|
|
1105
|
-
const ctx = useInkContext("
|
|
2296
|
+
//#region src/components/InkThreadPopover.tsx
|
|
2297
|
+
function InkThreadPopover({ children }) {
|
|
2298
|
+
const ctx = useInkContext("InkThreadPopover");
|
|
1106
2299
|
const { annotations, add, update, remove } = useAnnotations();
|
|
1107
2300
|
const note = useMemo(() => {
|
|
1108
2301
|
const a = ctx.activated ? annotations.find((x) => x.id === ctx.activated) : void 0;
|
|
1109
|
-
return a
|
|
2302
|
+
return a ? threadRoot(annotations, a) : null;
|
|
1110
2303
|
}, [annotations, ctx.activated]);
|
|
1111
2304
|
const anchor = useAnnotationElement(ctx.rootEl, note?.id ?? null);
|
|
1112
2305
|
const anchorRef = useMemo(() => ({ current: anchor }), [anchor]);
|
|
@@ -1114,10 +2307,10 @@ function InkNotePopover({ children }) {
|
|
|
1114
2307
|
const open = note !== null && anchor !== null;
|
|
1115
2308
|
/** The root, then every reply below it, depth first, each level in creation order. */
|
|
1116
2309
|
const thread = useMemo(() => note ? threadOf(annotations, note) : [], [annotations, note]);
|
|
1117
|
-
/** True while the default form writes the root
|
|
2310
|
+
/** True while the default form writes the root's own text rather than a reply. */
|
|
1118
2311
|
const editingRoot = note !== null && !note.locked && !note.contents?.trim();
|
|
1119
2312
|
/**
|
|
1120
|
-
*
|
|
2313
|
+
* Roots this popover wrote text or a reply for. In controlled mode the write is a proposal
|
|
1121
2314
|
* the host may not have applied yet, so the applied list still shows the note empty; these
|
|
1122
2315
|
* are never dropped as abandoned.
|
|
1123
2316
|
*/
|
|
@@ -1129,7 +2322,9 @@ function InkNotePopover({ children }) {
|
|
|
1129
2322
|
remove
|
|
1130
2323
|
});
|
|
1131
2324
|
function dropIfEmpty(id) {
|
|
1132
|
-
|
|
2325
|
+
const list = latest.current.annotations;
|
|
2326
|
+
const a = list.find((x) => x.id === id);
|
|
2327
|
+
if (!a || dropped.current.has(id) || written.current.has(id) || !isAbandonedNote(a, list)) return false;
|
|
1133
2328
|
dropped.current.add(id);
|
|
1134
2329
|
latest.current.remove(id);
|
|
1135
2330
|
return true;
|
|
@@ -1155,8 +2350,9 @@ function InkNotePopover({ children }) {
|
|
|
1155
2350
|
inReplyTo: root.id
|
|
1156
2351
|
});
|
|
1157
2352
|
}
|
|
2353
|
+
/** A note that closing now would remove. */
|
|
1158
2354
|
function isEmptyRoot(root) {
|
|
1159
|
-
return !written.current.has(root.id) &&
|
|
2355
|
+
return !written.current.has(root.id) && isAbandonedNote(root, annotations);
|
|
1160
2356
|
}
|
|
1161
2357
|
function close() {
|
|
1162
2358
|
const root = note;
|
|
@@ -1171,7 +2367,7 @@ function InkNotePopover({ children }) {
|
|
|
1171
2367
|
if (contents.trim()) written.current.add(note.id);
|
|
1172
2368
|
update(note.id, { contents });
|
|
1173
2369
|
}
|
|
1174
|
-
/** Locked
|
|
2370
|
+
/** Locked roots cannot be removed: removing one does nothing and keeps the popover. */
|
|
1175
2371
|
function removeThread() {
|
|
1176
2372
|
const root = note;
|
|
1177
2373
|
if (!root || root.locked) return;
|
|
@@ -1184,22 +2380,22 @@ function InkNotePopover({ children }) {
|
|
|
1184
2380
|
open,
|
|
1185
2381
|
onOpenChange: (next) => !next && close(),
|
|
1186
2382
|
children: [/* @__PURE__ */ jsx(Popover.Anchor, { virtualRef: anchorRef }), /* @__PURE__ */ jsx(Popover.Portal, { children: note && anchor ? /* @__PURE__ */ jsx(Popover.Content, {
|
|
1187
|
-
className: "ink-
|
|
1188
|
-
"data-ink-
|
|
2383
|
+
className: "ink-thread-popover",
|
|
2384
|
+
"data-ink-thread-popover": "",
|
|
1189
2385
|
side: "right",
|
|
1190
2386
|
sideOffset: 8,
|
|
1191
2387
|
collisionPadding: 8,
|
|
1192
|
-
"aria-label":
|
|
2388
|
+
"aria-label": threadPopoverLabel(note),
|
|
1193
2389
|
onInteractOutside: keepOpenOnAnnotation,
|
|
1194
2390
|
onCloseAutoFocus: (e) => focusReturn.onCloseAutoFocus(e, anchor),
|
|
1195
2391
|
children: children ? children({
|
|
1196
|
-
note,
|
|
2392
|
+
root: note,
|
|
1197
2393
|
replies: thread.slice(1),
|
|
1198
2394
|
reply,
|
|
1199
2395
|
update: updateContents,
|
|
1200
2396
|
remove: removeThread,
|
|
1201
2397
|
close
|
|
1202
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [!editingRoot || thread.length > 1 ? /* @__PURE__ */ jsx("ol", {
|
|
2398
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!editingRoot || thread.length > 1 ? /* @__PURE__ */ jsx("ol", {
|
|
1203
2399
|
className: "ink-thread",
|
|
1204
2400
|
"data-ink-thread": "",
|
|
1205
2401
|
children: thread.map((item) => /* @__PURE__ */ jsxs("li", {
|
|
@@ -1225,6 +2421,7 @@ function InkNotePopover({ children }) {
|
|
|
1225
2421
|
}) : null, /* @__PURE__ */ jsx(ThreadForm, {
|
|
1226
2422
|
editingRoot,
|
|
1227
2423
|
locked: !!note.locked,
|
|
2424
|
+
deleteLabel: threadDeleteLabel(note),
|
|
1228
2425
|
onSubmit: (text) => editingRoot ? updateContents(text) : reply(text),
|
|
1229
2426
|
onRemove: removeThread,
|
|
1230
2427
|
onClose: close
|
|
@@ -1232,8 +2429,8 @@ function InkNotePopover({ children }) {
|
|
|
1232
2429
|
}) : null })]
|
|
1233
2430
|
});
|
|
1234
2431
|
}
|
|
1235
|
-
/** The default form: the root
|
|
1236
|
-
function ThreadForm({ editingRoot, locked, onSubmit, onRemove, onClose }) {
|
|
2432
|
+
/** The default form: the root's own text while it is empty, replies after that. */
|
|
2433
|
+
function ThreadForm({ editingRoot, locked, deleteLabel, onSubmit, onRemove, onClose }) {
|
|
1237
2434
|
const [draft, setDraft] = useState("");
|
|
1238
2435
|
function submit(e) {
|
|
1239
2436
|
e.preventDefault();
|
|
@@ -1263,7 +2460,7 @@ function ThreadForm({ editingRoot, locked, onSubmit, onRemove, onClose }) {
|
|
|
1263
2460
|
!locked ? /* @__PURE__ */ jsx("button", {
|
|
1264
2461
|
type: "button",
|
|
1265
2462
|
onClick: onRemove,
|
|
1266
|
-
children:
|
|
2463
|
+
children: deleteLabel
|
|
1267
2464
|
}) : null,
|
|
1268
2465
|
/* @__PURE__ */ jsx("button", {
|
|
1269
2466
|
type: "button",
|
|
@@ -1275,84 +2472,6 @@ function ThreadForm({ editingRoot, locked, onSubmit, onRemove, onClose }) {
|
|
|
1275
2472
|
});
|
|
1276
2473
|
}
|
|
1277
2474
|
//#endregion
|
|
1278
|
-
|
|
1279
|
-
function InkSuggestionCard({ children }) {
|
|
1280
|
-
const ctx = useInkContext("InkSuggestionCard");
|
|
1281
|
-
const { annotations, selection, accept: acceptId, dismiss: dismissId, select } = useAnnotations();
|
|
1282
|
-
function suggestion(id) {
|
|
1283
|
-
const a = id ? annotations.find((x) => x.id === id) : void 0;
|
|
1284
|
-
return a?.type === "redact" && a.origin === "suggested" ? a : null;
|
|
1285
|
-
}
|
|
1286
|
-
const target = suggestion(ctx.activated) ?? (selection.length === 1 ? suggestion(selection[0]) : null);
|
|
1287
|
-
const anchor = useAnnotationElement(ctx.rootEl, target?.id ?? null);
|
|
1288
|
-
const anchorRef = useMemo(() => ({ current: anchor }), [anchor]);
|
|
1289
|
-
const focusReturn = useFocusReturn(ctx);
|
|
1290
|
-
const open = target !== null && anchor !== null;
|
|
1291
|
-
const bucket = target?.suggestion ? confidenceBucket(target.suggestion.confidence) : void 0;
|
|
1292
|
-
function close() {
|
|
1293
|
-
ctx.setActivated(null);
|
|
1294
|
-
select([]);
|
|
1295
|
-
}
|
|
1296
|
-
function accept() {
|
|
1297
|
-
if (target) acceptId(target.id);
|
|
1298
|
-
close();
|
|
1299
|
-
}
|
|
1300
|
-
/** Locked annotations cannot be removed: dismissing one does nothing and keeps the card. */
|
|
1301
|
-
function dismiss() {
|
|
1302
|
-
if (!target || target.locked) return;
|
|
1303
|
-
focusReturn.beforeRemove(anchor, "[data-origin=\"suggested\"]");
|
|
1304
|
-
dismissId(target.id);
|
|
1305
|
-
close();
|
|
1306
|
-
}
|
|
1307
|
-
return /* @__PURE__ */ jsxs(Popover.Root, {
|
|
1308
|
-
open,
|
|
1309
|
-
onOpenChange: (next) => !next && close(),
|
|
1310
|
-
children: [/* @__PURE__ */ jsx(Popover.Anchor, { virtualRef: anchorRef }), /* @__PURE__ */ jsx(Popover.Portal, { children: target && anchor ? /* @__PURE__ */ jsx(Popover.Content, {
|
|
1311
|
-
className: "ink-suggestion-card",
|
|
1312
|
-
"data-ink-suggestion-card": "",
|
|
1313
|
-
side: "bottom",
|
|
1314
|
-
sideOffset: 6,
|
|
1315
|
-
collisionPadding: 8,
|
|
1316
|
-
"aria-label": "Suggested redaction",
|
|
1317
|
-
onInteractOutside: keepOpenOnAnnotation,
|
|
1318
|
-
onCloseAutoFocus: (e) => focusReturn.onCloseAutoFocus(e, anchor),
|
|
1319
|
-
children: children ? children({
|
|
1320
|
-
annotation: target,
|
|
1321
|
-
suggestion: target.suggestion,
|
|
1322
|
-
bucket,
|
|
1323
|
-
accept,
|
|
1324
|
-
dismiss,
|
|
1325
|
-
close
|
|
1326
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1327
|
-
/* @__PURE__ */ jsxs("p", {
|
|
1328
|
-
className: "ink-suggestion-reason",
|
|
1329
|
-
children: [/* @__PURE__ */ jsx("strong", { children: target.suggestion?.reason ?? "Suggested redaction" }), bucket ? /* @__PURE__ */ jsxs("span", {
|
|
1330
|
-
className: "ink-suggestion-confidence",
|
|
1331
|
-
"data-confidence": bucket,
|
|
1332
|
-
children: [bucket, " confidence"]
|
|
1333
|
-
}) : null]
|
|
1334
|
-
}),
|
|
1335
|
-
target.suggestion?.explanation ? /* @__PURE__ */ jsx("p", {
|
|
1336
|
-
className: "ink-suggestion-explanation",
|
|
1337
|
-
children: target.suggestion.explanation
|
|
1338
|
-
}) : null,
|
|
1339
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1340
|
-
className: "ink-suggestion-actions",
|
|
1341
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
1342
|
-
type: "button",
|
|
1343
|
-
onClick: accept,
|
|
1344
|
-
children: "Accept"
|
|
1345
|
-
}), !target.locked ? /* @__PURE__ */ jsx("button", {
|
|
1346
|
-
type: "button",
|
|
1347
|
-
onClick: dismiss,
|
|
1348
|
-
children: "Dismiss"
|
|
1349
|
-
}) : null]
|
|
1350
|
-
})
|
|
1351
|
-
] })
|
|
1352
|
-
}) : null })]
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
|
-
//#endregion
|
|
1356
|
-
export { InkAnnotation, InkIcon, InkNextPage, InkNotePopover, InkPage, InkPageCount, InkPageInput, InkPrevPage, InkRedo, InkRoot, InkSuggestionCard, InkToolButton, InkToolbar, InkToolbarSeparator, InkUndo, InkViewport, InkZoomIn, InkZoomOut, InkZoomSelect, useAnnotations, useDocument, useEmbedded, useHistory, useInkContext, usePage, useReadable, useSelection, useTool, useZoom };
|
|
2475
|
+
export { InkAnnotation, InkColorMenu, InkHighlightMenu, InkIcon, InkLineStyleMenu, InkNextPage, InkPage, InkPageCount, InkPageInput, InkPrevPage, InkRedo, InkRoot, InkShapeMenu, InkSignatureDialog, InkSignatureMenu, InkTextStyleMenu, InkThreadPopover, InkToolButton, InkToolbar, InkToolbarSeparator, InkUndo, InkViewport, InkZoomIn, InkZoomOut, InkZoomSelect, useAnnotations, useDocument, useEmbedded, useHistory, useInkContext, usePage, useReadable, useSelection, useTool, useToolOptions, useToolStyle, useZoom };
|
|
1357
2476
|
|
|
1358
2477
|
//# sourceMappingURL=index.mjs.map
|