@inkbar/react 0.11.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(() => {
@@ -1611,7 +1624,7 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1611
1624
  customAnnotations: cu,
1612
1625
  ...c.formHighlight === void 0 ? {} : { formHighlight: c.formHighlight },
1613
1626
  onSignField: (req) => setSignRequest((open) => open?.widget === req.widget ? null : req),
1614
- onFormAction: (a) => latest.current.ctx.reportFormAction(a),
1627
+ onAction: (a) => latest.current.ctx.reportAction(a),
1615
1628
  onFormAlert: (message) => setAlert((prev) => ({
1616
1629
  message,
1617
1630
  shown: (prev?.shown ?? 0) + 1
@@ -1665,12 +1678,14 @@ function InkViewport({ mode = "continuous", rotation = 0, gap = 16, buffer = 2,
1665
1678
  } : p))
1666
1679
  });
1667
1680
  engine.current = e;
1681
+ c.setNavigation(e.navigation);
1668
1682
  return () => {
1669
1683
  for (const stop of stops) stop();
1670
1684
  e.destroy();
1671
1685
  engine.current = null;
1672
1686
  builtWith.current = null;
1673
1687
  c.setViewport(null, null);
1688
+ c.setNavigation(null);
1674
1689
  c.setOpenedToolIds(null);
1675
1690
  setPages((prev) => prev.length === 0 ? prev : []);
1676
1691
  setSignRequest(null);
@@ -2415,6 +2430,182 @@ function InkNextPage({ asChild = false, className, children, disabled, onClick,
2415
2430
  });
2416
2431
  }
2417
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
2418
2609
  //#region src/components/InkColorMenu.tsx
2419
2610
  function InkColorMenu({ property, label, className, children, standalone = false, group, fallback, disabled, ...rest }) {
2420
2611
  const Wrap = standalone ? Fragment : Toolbar.Button;
@@ -3425,21 +3616,6 @@ function ThreadForm({ editingRoot, locked, deleteLabel, onSubmit, onRemove, onCl
3425
3616
  });
3426
3617
  }
3427
3618
  //#endregion
3428
- //#region src/dom-attrs.ts
3429
- /**
3430
- * Core's attributes (`rootAttrs()`, `triggerAttrs(id)`, `panelAttrs(id)` and the like) as React
3431
- * props. Core names them as the DOM does; React wants `hidden` as a boolean and `tabindex` as
3432
- * `tabIndex`. `aria-*`, `data-*`, `id`, `role`, `type` and `title` pass through as they are.
3433
- * Every component that spreads core's attributes goes through this, so the mapping lives once.
3434
- */
3435
- function domAttrs(attrs) {
3436
- const out = {};
3437
- for (const [name, value] of Object.entries(attrs)) if (name === "hidden") out.hidden = true;
3438
- else if (name === "tabindex") out.tabIndex = Number(value);
3439
- else out[name] = value;
3440
- return out;
3441
- }
3442
- //#endregion
3443
3619
  //#region src/components/sidebar-context.ts
3444
3620
  const SidebarContextObject = createContext(null);
3445
3621
  /** The sidebar context: throws a readable error outside `InkSidebar`. */
@@ -3483,6 +3659,16 @@ function SidebarTrigger({ panel, rail }) {
3483
3659
  }
3484
3660
  //#endregion
3485
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
+ }
3486
3672
  /** Sorts the panels by where their elements sit in the DOM. */
3487
3673
  function inDomOrder(entries) {
3488
3674
  return [...entries].sort((a, b) => {
@@ -3556,17 +3742,30 @@ function createRegistry(controller) {
3556
3742
  *
3557
3743
  * `open` with `onOpenChange` controls it; `defaultOpen` starts it uncontrolled; with neither,
3558
3744
  * the first panel starts open. It needs no document, so it works outside `InkRoot`. The server
3559
- * renders the root, the panels (the open one shown) and the stack headers; rail and tab
3560
- * 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.
3561
3752
  */
3562
- 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 }) {
3563
3754
  const controlled = open !== void 0;
3564
3755
  const [inner, setInner] = useState(defaultOpen);
3565
3756
  const value = controlled ? open : inner;
3566
3757
  const latest = useLatest({
3567
3758
  controlled,
3568
- onOpenChange
3759
+ onOpenChange,
3760
+ onWidthChange
3761
+ });
3762
+ const widths = widthOptions({
3763
+ defaultWidth,
3764
+ minWidth,
3765
+ maxWidth,
3766
+ width
3569
3767
  });
3768
+ const inherited = useContext(InkContextObject)?.storage;
3570
3769
  const idPrefix = useId();
3571
3770
  const [{ context, mount }] = useState(() => createRegistry(createSidebarController({
3572
3771
  layout,
@@ -3576,6 +3775,10 @@ function InkSidebar({ side = "left", layout = "rail", visible = true, open, defa
3576
3775
  ...value !== void 0 && { open: value },
3577
3776
  pending: true,
3578
3777
  idPrefix,
3778
+ ...(storageProp ?? inherited) && { storage: storageProp ?? inherited },
3779
+ ...name !== void 0 && { name },
3780
+ ...widths,
3781
+ onWidthChange: (next) => latest.current.onWidthChange?.(next),
3579
3782
  onOpenChange: (next) => {
3580
3783
  if (!latest.current.controlled) setInner(next);
3581
3784
  latest.current.onOpenChange?.(next);
@@ -3588,40 +3791,72 @@ function InkSidebar({ side = "left", layout = "rail", visible = true, open, defa
3588
3791
  layout,
3589
3792
  side,
3590
3793
  visible,
3591
- ...value !== void 0 && { open: value }
3794
+ ...value !== void 0 && { open: value },
3795
+ ...widths
3592
3796
  });
3593
3797
  }, [
3594
3798
  controller,
3595
3799
  layout,
3596
3800
  side,
3597
3801
  visible,
3598
- value
3802
+ value,
3803
+ defaultWidth,
3804
+ minWidth,
3805
+ maxWidth,
3806
+ width
3599
3807
  ]);
3600
3808
  const root = useRef(null);
3601
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;
3602
3822
  const rail = state.layout === "rail";
3603
3823
  return /* @__PURE__ */ jsxs("div", {
3604
3824
  ...rest,
3605
3825
  ref: root,
3606
3826
  className: cx("ink-sidebar", className),
3827
+ style: {
3828
+ ...style,
3829
+ ...controller.rootStyle()
3830
+ },
3831
+ suppressHydrationWarning: true,
3607
3832
  ...domAttrs(controller.rootAttrs()),
3608
- children: [state.layout !== "stack" ? /* @__PURE__ */ jsx("div", {
3609
- className: rail ? "ink-sidebar-rail" : "ink-sidebar-tabs",
3610
- ...domAttrs(controller.tablistAttrs()),
3611
- children: state.panels.map((panel) => /* @__PURE__ */ jsx("button", {
3612
- ...domAttrs(controller.triggerAttrs(panel.id)),
3613
- children: /* @__PURE__ */ jsx(SidebarTrigger, {
3614
- panel,
3615
- 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
3616
3851
  })
3617
- }, panel.id))
3618
- }) : null, /* @__PURE__ */ jsx("div", {
3619
- className: state.layout !== "stack" ? "ink-sidebar-panels" : void 0,
3620
- children: /* @__PURE__ */ jsx(SidebarContextObject.Provider, {
3621
- value: context,
3622
- children
3852
+ }),
3853
+ /* @__PURE__ */ jsx("div", {
3854
+ ref: handle,
3855
+ className: "ink-sidebar-handle",
3856
+ suppressHydrationWarning: true,
3857
+ ...domAttrs(handleAttrs)
3623
3858
  })
3624
- })]
3859
+ ]
3625
3860
  });
3626
3861
  }
3627
3862
  //#endregion
@@ -3687,21 +3922,6 @@ function InkPanel({ id, label, icon, badge, children }) {
3687
3922
  });
3688
3923
  }
3689
3924
  //#endregion
3690
- //#region src/to-readable.ts
3691
- /**
3692
- * A core `Readable` that follows a React value, for the core controllers and painters that
3693
- * components build on. The same readable for the component's life; it takes each new value in
3694
- * a layout effect, so declare it before the effects that hand it to core.
3695
- */
3696
- function useToReadable(value) {
3697
- const [store] = useState(() => writable(value));
3698
- useLayoutEffect(() => {
3699
- store.set(value);
3700
- }, [store, value]);
3701
- const [view] = useState(() => readonly(store));
3702
- return view;
3703
- }
3704
- //#endregion
3705
3925
  //#region src/components/InkThumbnail.tsx
3706
3926
  /**
3707
3927
  * One page as a static image, with its live annotations and plugin marks: core's thumbnail
@@ -4125,6 +4345,6 @@ function InkSearchResults({ field = true, className, ...rest }) {
4125
4345
  });
4126
4346
  }
4127
4347
  //#endregion
4128
- 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 };
4129
4349
 
4130
4350
  //# sourceMappingURL=index.mjs.map