@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/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, 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, Hand, Highlighter, ICON_ATTRIBUTES, MessageSquare, MousePointer2, Redo2, Square, Undo2, ZoomIn, ZoomOut } from "@inkbar/icons";
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 ?? "view");
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
- useEffect(() => {
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
- stops = [vp.zoom.subscribe(reportZoom), vp.currentPage.subscribe(reportPage)];
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
- view: Hand,
770
- select: MousePointer2,
869
+ "select-text": TextCursor,
870
+ select: SquareDashedMousePointer,
771
871
  highlight: Highlighter,
772
872
  "redact-area": Square,
773
- note: MessageSquare
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: ICONS[tool] })
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/InkNotePopover.tsx
1104
- function InkNotePopover({ children }) {
1105
- const ctx = useInkContext("InkNotePopover");
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?.type === "text" ? threadRoot(annotations, a) : null;
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 note's own text rather than a reply. */
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
- * Notes this popover wrote text or a reply for. In controlled mode the write is a proposal
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
- if (dropped.current.has(id) || written.current.has(id) || !isAbandonedNote(latest.current.annotations, id)) return false;
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) && !root.locked && !root.contents?.trim() && thread.length === 1;
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 notes cannot be removed: removing one does nothing and keeps the popover. */
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-note-popover",
1188
- "data-ink-note-popover": "",
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": "Note thread",
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 note's own text while it is empty, replies after that. */
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: "Delete"
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
- //#region src/components/InkSuggestionCard.tsx
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