@inkbar/react 0.10.0 → 0.12.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
2
  import { Fragment, createContext, createElement, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
- import { InkError, createMemorySignatureStore, createSignatureLibrary, createSnapshotHistory, isAbandonedNote, openDocument, readonly, repliesTo, signField, threadOf, threadRoot, writable } from "@inkbar/core";
4
- import { COLOR_COLUMNS, FONT_FAMILIES, FORM_LABELS, MARKUP_ICONS, MARKUP_KINDS, SHAPE_COLUMNS, SHAPE_ICONS, SHAPE_KINDS, SIGNATURE_FONTS, SIGNATURE_LABELS, SIGNATURE_TABS, THUMBNAIL_WIDTH, TOOL_LABELS, ZOOM_LEVELS, attachFileKeys, attachHistoryKeys, colorMenuModel, createFocusReturn, createPluginHost, createRecordListController, createSearchController, createSidebarController, createSignatureDraft, createThumbnailPainter, createThumbnailsController, createToolOptions, cssVariableDefaults, downloadDocument, findAnnouncer, findCountText, focusInitialMenuItem, fontFamilyValue, handleFindKey, handleMenuKeys, handleRadioKeys, handleResultsFieldKey, handleTabKeys, highlightMenuModel, isBlankQuery, keepHistoryFocus, lineStyleMenuModel, menuItems, mountViewer, observeAnnotationElement, pageSelectionText, printDocument, recordsSource, searchSource, shapeMenuModel, shouldKeepOpenOnAnnotation, sidebarBadge, signatureMenuModel, signaturePreview, textStyleMenuModel, threadDeleteLabel, threadPopoverLabel, trackToolStyle, unknownCommand, unknownTool, viewerSearchOf, watchDetached, withHostTools } from "@inkbar/core/dom";
3
+ import { InkError, createMemoryStorage, createSignatureLibrary, createSnapshotHistory, createStorageSignatureStore, isAbandonedNote, openDocument, readonly, repliesTo, signField, threadOf, threadRoot, writable } from "@inkbar/core";
4
+ import { COLOR_COLUMNS, FONT_FAMILIES, FORM_LABELS, MARKUP_ICONS, MARKUP_KINDS, SHAPE_COLUMNS, SHAPE_ICONS, SHAPE_KINDS, SIGNATURE_FONTS, SIGNATURE_LABELS, SIGNATURE_TABS, THUMBNAIL_WIDTH, TOOL_LABELS, ZOOM_LEVELS, attachFileKeys, attachHistoryKeys, colorMenuModel, createFocusReturn, createOutlineController, createPluginHost, createRecordListController, createSearchController, createSidebarController, createSignatureDraft, createThumbnailPainter, createThumbnailsController, createToolOptions, cssVariableDefaults, downloadDocument, findAnnouncer, findCountText, focusInitialMenuItem, fontFamilyValue, handleFindKey, handleMenuKeys, handleRadioKeys, handleResultsFieldKey, handleTabKeys, highlightMenuModel, isBlankQuery, keepHistoryFocus, lineStyleMenuModel, menuItems, mountViewer, observeAnnotationElement, outlineTreeAttrs, pageSelectionText, printDocument, recordsSource, searchSource, shapeMenuModel, shouldKeepOpenOnAnnotation, sidebarBadge, signatureMenuModel, signaturePreview, textStyleMenuModel, threadDeleteLabel, threadPopoverLabel, trackToolStyle, unknownCommand, unknownTool, viewerSearchOf, watchDetached, withHostTools } from "@inkbar/core/dom";
5
5
  import { Dialog, Popover, Select, Slot, Toolbar } from "radix-ui";
6
6
  import { createPortal, flushSync } from "react-dom";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
- import { ALargeSmall, AlignCenter, AlignJustify, AlignLeft, AlignRight, Bold, CaseSensitive, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Download, ICON_ATTRIBUTES, Italic, Minus, Plus, Printer, Redo2, Search, Signature, SquareDashedMousePointer, TextCursor, Trash2, Underline, Undo2, X, ZoomIn, ZoomOut } from "@inkbar/icons";
8
+ import { ALargeSmall, AlignCenter, AlignJustify, AlignLeft, AlignRight, ArrowLeft, ArrowRight, Bold, CaseSensitive, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Download, ICON_ATTRIBUTES, Italic, Minus, Plus, Printer, Redo2, Search, Signature, SquareDashedMousePointer, TextCursor, Trash2, Underline, Undo2, X, ZoomIn, ZoomOut } from "@inkbar/icons";
9
9
  //#region src/context.ts
10
10
  const InkContextObject = createContext(null);
11
11
  /**
@@ -395,7 +395,7 @@ function isDevelopment$1() {
395
395
  function setTestHook(el, doc) {
396
396
  if (el && isDevelopment$1()) el.__inkDocument = doc;
397
397
  }
398
- function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsChange, onChange, fields, onFieldsChange, onFormAction, formHighlight, tool, onToolChange, tools: hostTools, plugins: pluginsProp, decorateAnnotation, fonts: fontsProp, signatures: signatureStore, signatureFonts: signatureFontsProp, page, onPageChange, zoom, onZoomChange, history, asChild = false, onReady, onError, onActivate, children, ref, ...rest }) {
398
+ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsChange, onChange, fields, onFieldsChange, onAction, onFormAction, formHighlight, tool, onToolChange, tools: hostTools, plugins: pluginsProp, decorateAnnotation, fonts: fontsProp, storage: storageProp, signatures: signatureStore, signatureFonts: signatureFontsProp, page, onPageChange, zoom, onZoomChange, history, asChild = false, onReady, onError, onActivate, children, ref, ...rest }) {
399
399
  const rootElRef = useRef(null);
400
400
  const [rootEl, setRootElState] = useState(null);
401
401
  const setRootEl = useCallback((el) => {
@@ -427,6 +427,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
427
427
  controlled: controlledProp,
428
428
  fields,
429
429
  onFieldsChange,
430
+ onAction,
430
431
  onFormAction,
431
432
  history,
432
433
  historyEnabled,
@@ -445,7 +446,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
445
446
  tools
446
447
  });
447
448
  /**
448
- * Hands `e` to `onError` one microtask later, as `onFieldsChange` and `onFormAction` are: a
449
+ * Hands `e` to `onError` one microtask later, as `onFieldsChange` and `onAction` are: a
449
450
  * script or a press reports from inside its own work. Dropped once `doc` is no longer open.
450
451
  */
451
452
  const reportErrorFor = useCallback((doc, e) => {
@@ -492,6 +493,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
492
493
  react: (fn) => active()?.react(fn) ?? noop
493
494
  };
494
495
  }, [snapshotHistory]);
496
+ const [storage] = useState(() => storageProp ?? createMemoryStorage());
495
497
  const hostRef = useRef(null);
496
498
  const [pluginHost, setPluginHost] = useState(null);
497
499
  const [search, setSearch] = useState(null);
@@ -514,7 +516,8 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
514
516
  root: readonly(plugins.root),
515
517
  decorate: () => latest.current.decorateAnnotation,
516
518
  viewport: () => viewportRef.current,
517
- search: viewerSearchOf(search)
519
+ search: viewerSearchOf(search),
520
+ storage
518
521
  });
519
522
  hostRef.current = {
520
523
  host,
@@ -701,16 +704,20 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
701
704
  ]);
702
705
  const reportZoom = useCallback((z) => latest.current.onZoomChange?.(z), [latest]);
703
706
  const reportPage = useCallback((p) => latest.current.onPageChange?.(p), [latest]);
704
- const reportFormAction = useCallback((a) => {
707
+ const reportAction = useCallback((a) => {
705
708
  const doc = documentRef.current;
706
709
  queueMicrotask(() => {
707
710
  if (documentRef.current !== doc) return;
708
- flushSync(() => latest.current.onFormAction?.(a));
711
+ flushSync(() => {
712
+ const { onAction, onFormAction } = latest.current;
713
+ (onAction ?? onFormAction)?.(a);
714
+ });
709
715
  });
710
716
  }, [latest]);
711
717
  const reportError = useCallback((e) => {
712
718
  if (documentRef.current) reportErrorFor(documentRef.current, e);
713
719
  }, [reportErrorFor]);
720
+ const [navigation, setNavigation] = useState(null);
714
721
  const setViewport = useCallback((vp, tc) => {
715
722
  viewportRef.current = vp;
716
723
  setViewportState(vp);
@@ -799,7 +806,8 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
799
806
  ]);
800
807
  const fonts = useMemo(() => fontsProp ?? FONT_FAMILIES, [fontsProp]);
801
808
  const signatureFonts = useMemo(() => signatureFontsProp ?? SIGNATURE_FONTS, [signatureFontsProp]);
802
- const signatures = useMemo(() => createSignatureLibrary(signatureStore ?? createMemorySignatureStore()), [signatureStore]);
809
+ const [storageSignatures] = useState(() => createStorageSignatureStore(storage));
810
+ const signatures = useMemo(() => createSignatureLibrary(signatureStore ?? storageSignatures), [signatureStore, storageSignatures]);
803
811
  useEffect(() => {
804
812
  signatures.refresh();
805
813
  }, [signatures]);
@@ -829,6 +837,8 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
829
837
  selection,
830
838
  viewport,
831
839
  tools,
840
+ navigation,
841
+ setNavigation,
832
842
  tool: internalTool,
833
843
  setTool,
834
844
  hostTools,
@@ -838,6 +848,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
838
848
  onDecorateAnnotationChange,
839
849
  fonts,
840
850
  toolOptions,
851
+ storage,
841
852
  signatures,
842
853
  signatureFonts,
843
854
  toolStyle: styleTracker?.toolStyle ?? null,
@@ -854,7 +865,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
854
865
  rootEl,
855
866
  setViewport,
856
867
  formHighlight,
857
- reportFormAction,
868
+ reportAction,
858
869
  reportError
859
870
  }), [
860
871
  assets,
@@ -864,6 +875,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
864
875
  selection,
865
876
  viewport,
866
877
  tools,
878
+ navigation,
867
879
  internalTool,
868
880
  setTool,
869
881
  hostTools,
@@ -872,6 +884,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
872
884
  onDecorateAnnotationChange,
873
885
  fonts,
874
886
  toolOptions,
887
+ storage,
875
888
  signatures,
876
889
  signatureFonts,
877
890
  styleTracker,
@@ -888,7 +901,7 @@ function InkRoot({ src, assets, password, defaults, annotations, onAnnotationsCh
888
901
  rootEl,
889
902
  setViewport,
890
903
  formHighlight,
891
- reportFormAction,
904
+ reportAction,
892
905
  reportError
893
906
  ]);
894
907
  useEffect(() => {
@@ -1540,7 +1553,7 @@ function documentKey(doc) {
1540
1553
  }
1541
1554
  return key;
1542
1555
  }
1543
- function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2, loading, empty, error, renderAnnotation, renderPageError, className, ...rest }) {
1556
+ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2, pinchZoom = true, loading, empty, error, renderAnnotation, renderPageError, className, ...rest }) {
1544
1557
  const ctx = useInkContext("InkViewport");
1545
1558
  const scroll = useRef(null);
1546
1559
  const custom = !!renderAnnotation;
@@ -1550,6 +1563,7 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1550
1563
  rotation,
1551
1564
  gap,
1552
1565
  buffer,
1566
+ pinchZoom,
1553
1567
  custom
1554
1568
  });
1555
1569
  const engine = useRef(null);
@@ -1571,7 +1585,7 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1571
1585
  /** The form script's message on show, and how many have been shown. */
1572
1586
  const [alert, setAlert] = useState(null);
1573
1587
  useEffect(() => {
1574
- const { ctx: c, mode: m, rotation: r, gap: g, buffer: b, custom: cu } = latest.current;
1588
+ const { ctx: c, mode: m, rotation: r, gap: g, buffer: b, pinchZoom: pz, custom: cu } = latest.current;
1575
1589
  const doc = c.document;
1576
1590
  const el = scroll.current;
1577
1591
  lastReportedZoom.current = void 0;
@@ -1583,6 +1597,7 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1583
1597
  builtWith.current = {
1584
1598
  gap: g,
1585
1599
  buffer: b,
1600
+ pinchZoom: pz,
1586
1601
  custom: cu
1587
1602
  };
1588
1603
  c.setOpenedToolIds(toolIdsOf(c.hostTools, c.pluginHost));
@@ -1595,6 +1610,7 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1595
1610
  rotation: r,
1596
1611
  gap: g,
1597
1612
  buffer: b,
1613
+ pinchZoom: pz,
1598
1614
  tool: c.tool,
1599
1615
  ...c.hostTools ? { tools: c.hostTools } : {},
1600
1616
  ...c.pluginHost ? { plugins: c.pluginHost } : {},
@@ -1608,7 +1624,7 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1608
1624
  customAnnotations: cu,
1609
1625
  ...c.formHighlight === void 0 ? {} : { formHighlight: c.formHighlight },
1610
1626
  onSignField: (req) => setSignRequest((open) => open?.widget === req.widget ? null : req),
1611
- onFormAction: (a) => latest.current.ctx.reportFormAction(a),
1627
+ onAction: (a) => latest.current.ctx.reportAction(a),
1612
1628
  onFormAlert: (message) => setAlert((prev) => ({
1613
1629
  message,
1614
1630
  shown: (prev?.shown ?? 0) + 1
@@ -1662,12 +1678,14 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1662
1678
  } : p))
1663
1679
  });
1664
1680
  engine.current = e;
1681
+ c.setNavigation(e.navigation);
1665
1682
  return () => {
1666
1683
  for (const stop of stops) stop();
1667
1684
  e.destroy();
1668
1685
  engine.current = null;
1669
1686
  builtWith.current = null;
1670
1687
  c.setViewport(null, null);
1688
+ c.setNavigation(null);
1671
1689
  c.setOpenedToolIds(null);
1672
1690
  setPages((prev) => prev.length === 0 ? prev : []);
1673
1691
  setSignRequest(null);
@@ -1693,21 +1711,24 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1693
1711
  useEffect(() => {
1694
1712
  const built = builtWith.current;
1695
1713
  if (!engine.current || !built) return;
1696
- if (built.gap === gap && built.buffer === buffer && built.custom === custom) return;
1714
+ if (built.gap === gap && built.buffer === buffer && built.pinchZoom === pinchZoom && built.custom === custom) return;
1697
1715
  buildCount.current += 1;
1698
1716
  builtWith.current = {
1699
1717
  gap,
1700
1718
  buffer,
1719
+ pinchZoom,
1701
1720
  custom
1702
1721
  };
1703
1722
  engine.current.rebuild({
1704
1723
  gap,
1705
1724
  buffer,
1725
+ pinchZoom,
1706
1726
  customAnnotations: custom
1707
1727
  });
1708
1728
  }, [
1709
1729
  gap,
1710
1730
  buffer,
1731
+ pinchZoom,
1711
1732
  custom
1712
1733
  ]);
1713
1734
  useEffect(() => {
@@ -2409,6 +2430,182 @@ function InkNextPage({ asChild = false, className, children, disabled, onClick,
2409
2430
  });
2410
2431
  }
2411
2432
  //#endregion
2433
+ //#region src/components/InkBack.tsx
2434
+ /** Returns to where the view was before the last jump; disabled while there is nowhere to go. */
2435
+ function InkBack({ asChild = false, className, children, disabled, onClick, ...rest }) {
2436
+ const ctx = useInkContext("InkBack");
2437
+ const canGoBack = useReadable(ctx.navigation?.canGoBack, false);
2438
+ return /* @__PURE__ */ jsx(Toolbar.Button, {
2439
+ asChild,
2440
+ className: cx("ink-back", className),
2441
+ "data-ink-back": "",
2442
+ "aria-label": "Back",
2443
+ title: "Back",
2444
+ disabled: disabled === true || !canGoBack,
2445
+ ...rest,
2446
+ onClick: (e) => {
2447
+ onClick?.(e);
2448
+ if (!e.defaultPrevented) ctx.navigation?.back();
2449
+ },
2450
+ children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ArrowLeft })
2451
+ });
2452
+ }
2453
+ //#endregion
2454
+ //#region src/components/InkForward.tsx
2455
+ /** Goes forward again after Back; disabled until Back is used. */
2456
+ function InkForward({ asChild = false, className, children, disabled, onClick, ...rest }) {
2457
+ const ctx = useInkContext("InkForward");
2458
+ const canGoForward = useReadable(ctx.navigation?.canGoForward, false);
2459
+ return /* @__PURE__ */ jsx(Toolbar.Button, {
2460
+ asChild,
2461
+ className: cx("ink-forward", className),
2462
+ "data-ink-forward": "",
2463
+ "aria-label": "Forward",
2464
+ title: "Forward",
2465
+ disabled: disabled === true || !canGoForward,
2466
+ ...rest,
2467
+ onClick: (e) => {
2468
+ onClick?.(e);
2469
+ if (!e.defaultPrevented) ctx.navigation?.forward();
2470
+ },
2471
+ children: children ?? /* @__PURE__ */ jsx(InkIcon, { icon: ArrowRight })
2472
+ });
2473
+ }
2474
+ //#endregion
2475
+ //#region src/dom-attrs.ts
2476
+ /**
2477
+ * Core's attributes (`rootAttrs()`, `triggerAttrs(id)`, `panelAttrs(id)` and the like) as React
2478
+ * props. Core names them as the DOM does; React wants `hidden` as a boolean and `tabindex` as
2479
+ * `tabIndex`. `aria-*`, `data-*`, `id`, `role`, `type` and `title` pass through as they are.
2480
+ * Every component that spreads core's attributes goes through this, so the mapping lives once.
2481
+ */
2482
+ function domAttrs(attrs) {
2483
+ const out = {};
2484
+ for (const [name, value] of Object.entries(attrs)) if (name === "hidden") out.hidden = true;
2485
+ else if (name === "tabindex") out.tabIndex = Number(value);
2486
+ else out[name] = value;
2487
+ return out;
2488
+ }
2489
+ //#endregion
2490
+ //#region src/to-readable.ts
2491
+ /**
2492
+ * A core `Readable` that follows a React value, for the core controllers and painters that
2493
+ * components build on. The same readable for the component's life; it takes each new value in
2494
+ * a layout effect, so declare it before the effects that hand it to core.
2495
+ */
2496
+ function useToReadable(value) {
2497
+ const [store] = useState(() => writable(value));
2498
+ useLayoutEffect(() => {
2499
+ store.set(value);
2500
+ }, [store, value]);
2501
+ const [view] = useState(() => readonly(store));
2502
+ return view;
2503
+ }
2504
+ //#endregion
2505
+ //#region src/components/InkOutline.tsx
2506
+ const NO_ITEMS = [];
2507
+ const NO_EXPANDED = /* @__PURE__ */ new Set();
2508
+ /**
2509
+ * The open document's outline as a tree: `createOutline`'s markup on core's outline
2510
+ * controller, which holds what is expanded, the Tab stop, the keys, the current item and
2511
+ * Back's history. Clicking an item, or Enter on it, follows it; its chevron expands and
2512
+ * collapses it. It fills its parent's height and scrolls, and keeps the item the reader has
2513
+ * reached in view. `renderItem` replaces an item's title. Inside `InkRoot`; the tree is empty
2514
+ * on the server.
2515
+ */
2516
+ function InkOutline({ emptyText = "This document has no outline", renderItem, className, ...rest }) {
2517
+ const ctx = useInkContext("InkOutline");
2518
+ const document = useToReadable(ctx.document);
2519
+ const viewport = useToReadable(ctx.viewport);
2520
+ const navigation = useToReadable(ctx.navigation);
2521
+ const root = useRef(null);
2522
+ const [controller, setController] = useState(null);
2523
+ useLayoutEffect(() => {
2524
+ const c = createOutlineController({
2525
+ document,
2526
+ viewport,
2527
+ navigation
2528
+ });
2529
+ setController(c);
2530
+ const unbind = c.bindScroll(root.current);
2531
+ return () => {
2532
+ unbind();
2533
+ c.destroy();
2534
+ setController(null);
2535
+ };
2536
+ }, [
2537
+ document,
2538
+ viewport,
2539
+ navigation
2540
+ ]);
2541
+ const items = useReadable(controller?.items, NO_ITEMS);
2542
+ const loading = useReadable(controller?.loading, false);
2543
+ const expanded = useReadable(controller?.expanded, NO_EXPANDED);
2544
+ const current = useReadable(controller?.current, null);
2545
+ useReadable(controller?.focused, null);
2546
+ const level = (id) => id.split(".").length - 1;
2547
+ const renderLevel = (list) => list.map((item) => {
2548
+ const c = controller;
2549
+ const open = expanded.has(item.id);
2550
+ const attrs = {};
2551
+ for (const [name, value] of Object.entries(c.itemAttrs(item.id))) if (value !== void 0) attrs[name] = value;
2552
+ return /* @__PURE__ */ jsxs("li", {
2553
+ className: "ink-outline-item",
2554
+ "data-ink-outline-item": item.id,
2555
+ "data-bold": item.bold ? "" : void 0,
2556
+ "data-italic": item.italic ? "" : void 0,
2557
+ style: { "--ink-outline-level": level(item.id) },
2558
+ ...domAttrs(attrs),
2559
+ children: [/* @__PURE__ */ jsxs("div", {
2560
+ className: "ink-outline-row",
2561
+ ...c.rowAttrs(item.id),
2562
+ onClick: () => c.activate(item.id),
2563
+ children: [item.children.length > 0 ? /* @__PURE__ */ jsx("span", {
2564
+ className: "ink-outline-toggle",
2565
+ "aria-hidden": "true",
2566
+ onClick: (e) => {
2567
+ e.stopPropagation();
2568
+ c.toggle(item.id);
2569
+ },
2570
+ children: /* @__PURE__ */ jsx(InkIcon, { icon: ChevronRight })
2571
+ }) : /* @__PURE__ */ jsx("span", {
2572
+ className: "ink-outline-spacer",
2573
+ "aria-hidden": "true"
2574
+ }), /* @__PURE__ */ jsx("span", {
2575
+ className: "ink-outline-title",
2576
+ ...c.titleAttrs(item.id),
2577
+ children: renderItem ? renderItem(item, {
2578
+ current: current === item.id,
2579
+ expanded: open
2580
+ }) : item.title
2581
+ })]
2582
+ }), item.children.length > 0 ? /* @__PURE__ */ jsx("ul", {
2583
+ className: "ink-outline-group",
2584
+ role: "group",
2585
+ hidden: !open,
2586
+ children: renderLevel(item.children)
2587
+ }) : null]
2588
+ }, item.id);
2589
+ });
2590
+ return /* @__PURE__ */ jsxs("div", {
2591
+ ...rest,
2592
+ ref: root,
2593
+ className: cx("ink-outline", className),
2594
+ "data-ink-outline": "",
2595
+ "aria-busy": loading ? "true" : void 0,
2596
+ children: [/* @__PURE__ */ jsx("ul", {
2597
+ className: "ink-outline-tree",
2598
+ ...outlineTreeAttrs(),
2599
+ hidden: items.length === 0,
2600
+ onKeyDown: (e) => controller?.onKeydown(e.nativeEvent),
2601
+ children: controller ? renderLevel(items) : null
2602
+ }), controller && !loading && items.length === 0 ? /* @__PURE__ */ jsx("p", {
2603
+ className: "ink-outline-empty",
2604
+ children: emptyText
2605
+ }) : null]
2606
+ });
2607
+ }
2608
+ //#endregion
2412
2609
  //#region src/components/InkColorMenu.tsx
2413
2610
  function InkColorMenu({ property, label, className, children, standalone = false, group, fallback, disabled, ...rest }) {
2414
2611
  const Wrap = standalone ? Fragment : Toolbar.Button;
@@ -3419,21 +3616,6 @@ function ThreadForm({ editingRoot, locked, deleteLabel, onSubmit, onRemove, onCl
3419
3616
  });
3420
3617
  }
3421
3618
  //#endregion
3422
- //#region src/dom-attrs.ts
3423
- /**
3424
- * Core's attributes (`rootAttrs()`, `triggerAttrs(id)`, `panelAttrs(id)` and the like) as React
3425
- * props. Core names them as the DOM does; React wants `hidden` as a boolean and `tabindex` as
3426
- * `tabIndex`. `aria-*`, `data-*`, `id`, `role`, `type` and `title` pass through as they are.
3427
- * Every component that spreads core's attributes goes through this, so the mapping lives once.
3428
- */
3429
- function domAttrs(attrs) {
3430
- const out = {};
3431
- for (const [name, value] of Object.entries(attrs)) if (name === "hidden") out.hidden = true;
3432
- else if (name === "tabindex") out.tabIndex = Number(value);
3433
- else out[name] = value;
3434
- return out;
3435
- }
3436
- //#endregion
3437
3619
  //#region src/components/sidebar-context.ts
3438
3620
  const SidebarContextObject = createContext(null);
3439
3621
  /** The sidebar context: throws a readable error outside `InkSidebar`. */
@@ -3477,6 +3659,16 @@ function SidebarTrigger({ panel, rail }) {
3477
3659
  }
3478
3660
  //#endregion
3479
3661
  //#region src/components/InkSidebar.tsx
3662
+ /** The width props the host gave; an undefined one keeps the controller's. */
3663
+ function widthOptions(props) {
3664
+ const { defaultWidth, minWidth, maxWidth, width } = props;
3665
+ return {
3666
+ ...defaultWidth !== void 0 && { defaultWidth },
3667
+ ...minWidth !== void 0 && { minWidth },
3668
+ ...maxWidth !== void 0 && { maxWidth },
3669
+ ...width !== void 0 && { width }
3670
+ };
3671
+ }
3480
3672
  /** Sorts the panels by where their elements sit in the DOM. */
3481
3673
  function inDomOrder(entries) {
3482
3674
  return [...entries].sort((a, b) => {
@@ -3550,17 +3742,30 @@ function createRegistry(controller) {
3550
3742
  *
3551
3743
  * `open` with `onOpenChange` controls it; `defaultOpen` starts it uncontrolled; with neither,
3552
3744
  * the first panel starts open. It needs no document, so it works outside `InkRoot`. The server
3553
- * renders the root, the panels (the open one shown) and the stack headers; rail and tab
3554
- * buttons follow once mounted.
3745
+ * renders the root, the panels (the open one shown), the stack headers and the resize handle;
3746
+ * rail and tab buttons follow once mounted.
3747
+ *
3748
+ * The handle on the inner edge resizes the sidebar. Its width is saved in `storage`, under
3749
+ * `sidebar.<name>.width`; inside `InkRoot` the root's storage is the default. `width` with
3750
+ * `onWidthChange` makes the width the host's: drags and keys then only report, and storage is
3751
+ * not read or written.
3555
3752
  */
3556
- function InkSidebar({ side = "left", layout = "rail", visible = true, open, defaultOpen, onOpenChange, className, children, ...rest }) {
3753
+ function InkSidebar({ side = "left", layout = "rail", visible = true, open, defaultOpen, onOpenChange, storage: storageProp, name, defaultWidth, minWidth, maxWidth, width, onWidthChange, className, style, children, ...rest }) {
3557
3754
  const controlled = open !== void 0;
3558
3755
  const [inner, setInner] = useState(defaultOpen);
3559
3756
  const value = controlled ? open : inner;
3560
3757
  const latest = useLatest({
3561
3758
  controlled,
3562
- onOpenChange
3759
+ onOpenChange,
3760
+ onWidthChange
3761
+ });
3762
+ const widths = widthOptions({
3763
+ defaultWidth,
3764
+ minWidth,
3765
+ maxWidth,
3766
+ width
3563
3767
  });
3768
+ const inherited = useContext(InkContextObject)?.storage;
3564
3769
  const idPrefix = useId();
3565
3770
  const [{ context, mount }] = useState(() => createRegistry(createSidebarController({
3566
3771
  layout,
@@ -3570,6 +3775,10 @@ function InkSidebar({ side = "left", layout = "rail", visible = true, open, defa
3570
3775
  ...value !== void 0 && { open: value },
3571
3776
  pending: true,
3572
3777
  idPrefix,
3778
+ ...(storageProp ?? inherited) && { storage: storageProp ?? inherited },
3779
+ ...name !== void 0 && { name },
3780
+ ...widths,
3781
+ onWidthChange: (next) => latest.current.onWidthChange?.(next),
3573
3782
  onOpenChange: (next) => {
3574
3783
  if (!latest.current.controlled) setInner(next);
3575
3784
  latest.current.onOpenChange?.(next);
@@ -3582,40 +3791,72 @@ function InkSidebar({ side = "left", layout = "rail", visible = true, open, defa
3582
3791
  layout,
3583
3792
  side,
3584
3793
  visible,
3585
- ...value !== void 0 && { open: value }
3794
+ ...value !== void 0 && { open: value },
3795
+ ...widths
3586
3796
  });
3587
3797
  }, [
3588
3798
  controller,
3589
3799
  layout,
3590
3800
  side,
3591
3801
  visible,
3592
- value
3802
+ value,
3803
+ defaultWidth,
3804
+ minWidth,
3805
+ maxWidth,
3806
+ width
3593
3807
  ]);
3594
3808
  const root = useRef(null);
3595
3809
  useLayoutEffect(() => mount(root.current), [mount]);
3810
+ const handle = useRef(null);
3811
+ const [mounted, setMounted] = useState(false);
3812
+ useLayoutEffect(() => {
3813
+ setMounted(true);
3814
+ const el = handle.current;
3815
+ const unbind = controller.bindHandle(el);
3816
+ for (const [prop, value] of Object.entries(controller.rootStyle())) root.current.style.setProperty(prop, value);
3817
+ for (const [attr, value] of Object.entries(controller.handleAttrs())) if (attr.startsWith("aria-value")) el.setAttribute(attr, value);
3818
+ return unbind;
3819
+ }, [controller]);
3820
+ const handleAttrs = controller.handleAttrs();
3821
+ if (!mounted) delete handleAttrs.hidden;
3596
3822
  const rail = state.layout === "rail";
3597
3823
  return /* @__PURE__ */ jsxs("div", {
3598
3824
  ...rest,
3599
3825
  ref: root,
3600
3826
  className: cx("ink-sidebar", className),
3827
+ style: {
3828
+ ...style,
3829
+ ...controller.rootStyle()
3830
+ },
3831
+ suppressHydrationWarning: true,
3601
3832
  ...domAttrs(controller.rootAttrs()),
3602
- children: [state.layout !== "stack" ? /* @__PURE__ */ jsx("div", {
3603
- className: rail ? "ink-sidebar-rail" : "ink-sidebar-tabs",
3604
- ...domAttrs(controller.tablistAttrs()),
3605
- children: state.panels.map((panel) => /* @__PURE__ */ jsx("button", {
3606
- ...domAttrs(controller.triggerAttrs(panel.id)),
3607
- children: /* @__PURE__ */ jsx(SidebarTrigger, {
3608
- panel,
3609
- rail
3833
+ children: [
3834
+ state.layout !== "stack" ? /* @__PURE__ */ jsx("div", {
3835
+ className: rail ? "ink-sidebar-rail" : "ink-sidebar-tabs",
3836
+ ...domAttrs(controller.tablistAttrs()),
3837
+ children: state.panels.map((panel) => /* @__PURE__ */ jsx("button", {
3838
+ ...domAttrs(controller.triggerAttrs(panel.id)),
3839
+ children: /* @__PURE__ */ jsx(SidebarTrigger, {
3840
+ panel,
3841
+ rail
3842
+ })
3843
+ }, panel.id))
3844
+ }) : null,
3845
+ /* @__PURE__ */ jsx("div", {
3846
+ className: state.layout !== "stack" ? "ink-sidebar-panels" : void 0,
3847
+ ...state.layout !== "stack" && domAttrs(controller.panelsAttrs()),
3848
+ children: /* @__PURE__ */ jsx(SidebarContextObject.Provider, {
3849
+ value: context,
3850
+ children
3610
3851
  })
3611
- }, panel.id))
3612
- }) : null, /* @__PURE__ */ jsx("div", {
3613
- className: state.layout !== "stack" ? "ink-sidebar-panels" : void 0,
3614
- children: /* @__PURE__ */ jsx(SidebarContextObject.Provider, {
3615
- value: context,
3616
- children
3852
+ }),
3853
+ /* @__PURE__ */ jsx("div", {
3854
+ ref: handle,
3855
+ className: "ink-sidebar-handle",
3856
+ suppressHydrationWarning: true,
3857
+ ...domAttrs(handleAttrs)
3617
3858
  })
3618
- })]
3859
+ ]
3619
3860
  });
3620
3861
  }
3621
3862
  //#endregion
@@ -3681,21 +3922,6 @@ function InkPanel({ id, label, icon, badge, children }) {
3681
3922
  });
3682
3923
  }
3683
3924
  //#endregion
3684
- //#region src/to-readable.ts
3685
- /**
3686
- * A core `Readable` that follows a React value, for the core controllers and painters that
3687
- * components build on. The same readable for the component's life; it takes each new value in
3688
- * a layout effect, so declare it before the effects that hand it to core.
3689
- */
3690
- function useToReadable(value) {
3691
- const [store] = useState(() => writable(value));
3692
- useLayoutEffect(() => {
3693
- store.set(value);
3694
- }, [store, value]);
3695
- const [view] = useState(() => readonly(store));
3696
- return view;
3697
- }
3698
- //#endregion
3699
3925
  //#region src/components/InkThumbnail.tsx
3700
3926
  /**
3701
3927
  * One page as a static image, with its live annotations and plugin marks: core's thumbnail
@@ -4119,6 +4345,6 @@ function InkSearchResults({ field = true, className, ...rest }) {
4119
4345
  });
4120
4346
  }
4121
4347
  //#endregion
4122
- export { InkAnnotation, InkColorMenu, InkCommand, InkDownloadButton, InkFindBar, InkHighlightMenu, InkIcon, InkLineStyleMenu, InkNextPage, InkPage, InkPageCount, InkPageInput, InkPanel, InkPrevPage, InkPrintButton, InkRecordList, InkRedo, InkRoot, InkSearchButton, InkSearchResults, InkShapeMenu, InkSidebar, InkSignatureDialog, InkSignatureMenu, InkTextStyleMenu, InkThreadPopover, InkThumbnail, InkThumbnails, InkToolButton, InkToolbar, InkToolbarButton, InkToolbarSeparator, InkUndo, InkViewport, InkZoomIn, InkZoomOut, InkZoomSelect, useAnnotations, useCommand, useDocument, useEmbedded, useForm, useHistory, useInkContext, usePage, useReadable, useRecordSelection, useRecords, useSearch, useSelection, useTool, useToolOptions, useToolStyle, useZoom };
4348
+ export { InkAnnotation, InkBack, InkColorMenu, InkCommand, InkDownloadButton, InkFindBar, InkForward, InkHighlightMenu, InkIcon, InkLineStyleMenu, InkNextPage, InkOutline, InkPage, InkPageCount, InkPageInput, InkPanel, InkPrevPage, InkPrintButton, InkRecordList, InkRedo, InkRoot, InkSearchButton, InkSearchResults, InkShapeMenu, InkSidebar, InkSignatureDialog, InkSignatureMenu, InkTextStyleMenu, InkThreadPopover, InkThumbnail, InkThumbnails, InkToolButton, InkToolbar, InkToolbarButton, InkToolbarSeparator, InkUndo, InkViewport, InkZoomIn, InkZoomOut, InkZoomSelect, useAnnotations, useCommand, useDocument, useEmbedded, useForm, useHistory, useInkContext, usePage, useReadable, useRecordSelection, useRecords, useSearch, useSelection, useTool, useToolOptions, useToolStyle, useZoom };
4123
4349
 
4124
4350
  //# sourceMappingURL=index.mjs.map