@inkbar/react 0.2.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/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, 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
  /**
@@ -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 ?? "view");
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
- useEffect(() => {
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
- view: Hand,
770
- select: MousePointer2,
846
+ "select-text": TextCursor,
847
+ select: SquareDashedMousePointer,
771
848
  highlight: Highlighter,
772
849
  "redact-area": Square,
773
- note: MessageSquare
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: ICONS[tool] })
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/InkNotePopover.tsx
1104
- function InkNotePopover({ children }) {
1105
- const ctx = useInkContext("InkNotePopover");
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?.type === "note" ? threadRoot(annotations, a) : null;
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 note's own text rather than a reply. */
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
- * Notes this popover wrote text or a reply for. In controlled mode the write is a proposal
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
- if (dropped.current.has(id) || written.current.has(id) || !isAbandonedNote(latest.current.annotations, id)) return false;
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;
@@ -1148,15 +2300,16 @@ function InkNotePopover({ children }) {
1148
2300
  if (!root || !contents) return;
1149
2301
  written.current.add(root.id);
1150
2302
  add({
1151
- type: "note",
2303
+ type: "text",
1152
2304
  page: root.page,
1153
2305
  rect: root.rect,
1154
2306
  contents,
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) && !root.locked && !root.contents?.trim() && thread.length === 1;
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 notes cannot be removed: removing one does nothing and keeps the popover. */
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-note-popover",
1188
- "data-ink-note-popover": "",
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": "Note thread",
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 note's own text while it is empty, replies after that. */
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: "Delete"
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, 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 };
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