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