@stll/ui 0.52.1 → 0.53.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.
Files changed (42) hide show
  1. package/dist/components/alert-dialog.d.ts +1 -1
  2. package/dist/components/alert-dialog.js +2 -2
  3. package/dist/components/bidi-text.d.ts +1 -1
  4. package/dist/components/button.d.ts +33 -3
  5. package/dist/components/button.js +43 -3
  6. package/dist/components/combobox.d.ts +1 -2
  7. package/dist/components/combobox.js +1 -2
  8. package/dist/components/details-grid.js +1 -1
  9. package/dist/components/dialog.d.ts +1 -1
  10. package/dist/components/dialog.js +2 -2
  11. package/dist/components/inline-rename.js +1 -0
  12. package/dist/components/input-group.d.ts +1 -1
  13. package/dist/components/menu.d.ts +2 -2
  14. package/dist/components/menu.js +1 -1
  15. package/dist/components/outline-rail.d.ts +1 -1
  16. package/dist/components/outline-rail.js +126 -85
  17. package/dist/components/outline-rail.logic.d.ts +25 -0
  18. package/dist/components/outline-rail.logic.js +40 -0
  19. package/dist/components/popover.js +1 -1
  20. package/dist/components/prose.d.ts +1 -1
  21. package/dist/components/sheet.d.ts +1 -1
  22. package/dist/components/sheet.js +2 -2
  23. package/dist/components/sidebar.d.ts +1 -1
  24. package/dist/components/sidebar.js +1 -1
  25. package/dist/components/split-button.d.ts +3 -1
  26. package/dist/components/split-button.js +3 -1
  27. package/dist/components/steps.d.ts +2 -2
  28. package/dist/components/tooltip-trigger-helper.d.ts +5 -0
  29. package/dist/components/tooltip-trigger-helper.js +5 -0
  30. package/dist/icons.d.ts +2 -2
  31. package/dist/icons.js +1 -1
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +3 -3
  34. package/dist/kanban/card-shell.d.ts +14 -1
  35. package/dist/kanban/card-shell.js +33 -4
  36. package/dist/kanban/column-header.js +1 -0
  37. package/dist/kanban/drag-interactions.d.ts +14 -1
  38. package/dist/kanban/drag-interactions.js +19 -7
  39. package/dist/kanban/index.d.ts +3 -3
  40. package/dist/lib/control-size.d.ts +1 -1
  41. package/dist/lib/matter-colors.d.ts +1 -1
  42. package/package.json +5 -3
@@ -4,7 +4,7 @@ import * as React$1 from "react";
4
4
  //#region src/components/alert-dialog.d.ts
5
5
  declare const AlertDialogCreateHandle: typeof AlertDialog$1.createHandle;
6
6
  declare const AlertDialog: typeof AlertDialog$1.Root;
7
- declare const AlertDialogPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").AlertDialogPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
7
+ declare const AlertDialogPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").DialogPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
8
8
  declare function AlertDialogTrigger({ tooltip, ...props }: AlertDialog$1.Trigger.Props & {
9
9
  tooltip?: React$1.ReactNode;
10
10
  }): React$1.JSX.Element;
@@ -11,7 +11,7 @@ const AlertDialog = AlertDialog$1.Root;
11
11
  const AlertDialogPortal = AlertDialog$1.Portal;
12
12
  function AlertDialogTrigger({ tooltip, ...props }) {
13
13
  return renderTooltipTrigger({
14
- tooltip: tooltip ?? props["aria-label"] ?? props.title,
14
+ tooltip,
15
15
  trigger: /* @__PURE__ */ jsx(AlertDialog$1.Trigger, {
16
16
  "data-slot": "alert-dialog-trigger",
17
17
  nativeButton: false,
@@ -65,7 +65,7 @@ const AlertDialogDescription = ({ className, ...props }) => /* @__PURE__ */ jsx(
65
65
  });
66
66
  function AlertDialogClose({ tooltip, ...props }) {
67
67
  return renderTooltipTrigger({
68
- tooltip: tooltip ?? props["aria-label"] ?? props.title,
68
+ tooltip,
69
69
  trigger: /* @__PURE__ */ jsx(AlertDialog$1.Close, {
70
70
  "data-slot": "alert-dialog-close",
71
71
  ...props
@@ -1,7 +1,7 @@
1
1
  import * as React$1 from "react";
2
2
  //#region src/components/bidi-text.d.ts
3
3
  export declare const BidiText: (props: BidiTextProps) => React$1.JSX.Element;
4
- export declare const UserText: (props: BidiTextProps) => React$1.JSX.Element;
4
+ export declare const UserText: typeof BidiText;
5
5
  type BidiDirection = "auto" | "ltr" | "rtl";
6
6
  type BdiElementProps = Omit<React$1.ComponentPropsWithoutRef<"bdi">, "dir"> & {
7
7
  as?: "bdi";
@@ -13,9 +13,39 @@ import { VariantProps } from "class-variance-authority";
13
13
  */
14
14
  declare const buttonAccessibleDisabledClass = "cursor-not-allowed opacity-64";
15
15
  declare const buttonVariants: (props?: ({
16
- size?: "default" | "sm" | "lg" | "icon" | "chip" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "row" | "xl" | "xs" | null | undefined;
17
- variant?: "link" | "default" | "destructive" | "destructive-outline" | "destructive-ghost" | "ghost" | "muted" | "outline" | "secondary" | null | undefined;
16
+ size?: "chip" | "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "row" | "sm" | "xl" | "xs" | null | undefined;
17
+ variant?: "default" | "destructive" | "destructive-ghost" | "destructive-outline" | "ghost" | "link" | "muted" | "outline" | "secondary" | null | undefined;
18
18
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
19
+ /** What a button's box holds: a glyph alone, or a visible label. */
20
+ declare const BUTTON_CONTENT: {
21
+ readonly icon: "icon";
22
+ readonly text: "text";
23
+ };
24
+ /**
25
+ * An icon size is a square that fits a glyph and no label, so the button shows
26
+ * no text, and its accessible name doubles as its tooltip. A text size shows
27
+ * its own label and gets a tooltip only from an explicit `tooltip`: an implicit
28
+ * one would repeat or contradict the label, and because a tooltip opens on
29
+ * focus, it would take the first Escape from a popover or dialog that focuses
30
+ * the button.
31
+ *
32
+ * Icon sizes are exactly the `icon`-prefixed ones; the type below enforces it,
33
+ * because the `icon-button-requires-tooltip` lint rule reads the same prefix.
34
+ */
35
+ declare const BUTTON_SIZE_CONTENT: {
36
+ readonly chip: "text";
37
+ readonly default: "text";
38
+ readonly icon: "icon";
39
+ readonly "icon-lg": "icon";
40
+ readonly "icon-sm": "icon";
41
+ readonly "icon-xl": "icon";
42
+ readonly "icon-xs": "icon";
43
+ readonly lg: "text";
44
+ readonly row: "text";
45
+ readonly sm: "text";
46
+ readonly xl: "text";
47
+ readonly xs: "text";
48
+ };
19
49
  type ButtonProps = {
20
50
  variant?: VariantProps<typeof buttonVariants>["variant"];
21
51
  size?: VariantProps<typeof buttonVariants>["size"];
@@ -25,4 +55,4 @@ type ButtonProps = {
25
55
  } & useRender.ComponentProps<"button">;
26
56
  declare function Button({ className, variant, size, render, loading, icon, children, disabled, tooltip, ...props }: ButtonProps): import("react").JSX.Element;
27
57
  //#endregion
28
- export { Button, buttonAccessibleDisabledClass, buttonVariants };
58
+ export { BUTTON_CONTENT, BUTTON_SIZE_CONTENT, Button, buttonAccessibleDisabledClass, buttonVariants };
@@ -7,6 +7,7 @@ import { Loader } from "./loader.js";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { mergeProps } from "@base-ui/react/merge-props";
9
9
  import { useRender } from "@base-ui/react/use-render";
10
+ import { panic } from "better-result";
10
11
  import { cva } from "class-variance-authority";
11
12
  //#region src/components/button.tsx
12
13
  /**
@@ -29,9 +30,10 @@ const buttonAccessibleDisabledClass = "cursor-not-allowed opacity-64";
29
30
  * buttons keep the treatment of their surface instead.
30
31
  */
31
32
  const buttonPressClass = "motion-safe:transition-transform motion-safe:duration-150 ease-out motion-safe:active:scale-[0.98]";
33
+ const BUTTON_DEFAULT_SIZE = "default";
32
34
  const buttonVariants = cva("focus-visible:ring-ring focus-visible:ring-offset-background relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-lg border text-base font-medium whitespace-nowrap outline-none select-none before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-64 sm:text-sm pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", {
33
35
  defaultVariants: {
34
- size: "default",
36
+ size: BUTTON_DEFAULT_SIZE,
35
37
  variant: "default"
36
38
  },
37
39
  variants: {
@@ -62,9 +64,47 @@ const buttonVariants = cva("focus-visible:ring-ring focus-visible:ring-offset-ba
62
64
  }
63
65
  }
64
66
  });
67
+ /** What a button's box holds: a glyph alone, or a visible label. */
68
+ const BUTTON_CONTENT = {
69
+ icon: "icon",
70
+ text: "text"
71
+ };
72
+ /**
73
+ * An icon size is a square that fits a glyph and no label, so the button shows
74
+ * no text, and its accessible name doubles as its tooltip. A text size shows
75
+ * its own label and gets a tooltip only from an explicit `tooltip`: an implicit
76
+ * one would repeat or contradict the label, and because a tooltip opens on
77
+ * focus, it would take the first Escape from a popover or dialog that focuses
78
+ * the button.
79
+ *
80
+ * Icon sizes are exactly the `icon`-prefixed ones; the type below enforces it,
81
+ * because the `icon-button-requires-tooltip` lint rule reads the same prefix.
82
+ */
83
+ const BUTTON_SIZE_CONTENT = {
84
+ chip: BUTTON_CONTENT.text,
85
+ default: BUTTON_CONTENT.text,
86
+ icon: BUTTON_CONTENT.icon,
87
+ "icon-lg": BUTTON_CONTENT.icon,
88
+ "icon-sm": BUTTON_CONTENT.icon,
89
+ "icon-xl": BUTTON_CONTENT.icon,
90
+ "icon-xs": BUTTON_CONTENT.icon,
91
+ lg: BUTTON_CONTENT.text,
92
+ row: BUTTON_CONTENT.text,
93
+ sm: BUTTON_CONTENT.text,
94
+ xl: BUTTON_CONTENT.text,
95
+ xs: BUTTON_CONTENT.text
96
+ };
97
+ const implicitTooltip = (size, accessibleName) => {
98
+ const content = BUTTON_SIZE_CONTENT[size ?? BUTTON_DEFAULT_SIZE];
99
+ switch (content) {
100
+ case BUTTON_CONTENT.icon: return accessibleName;
101
+ case BUTTON_CONTENT.text: return;
102
+ default: return panic(`Unhandled button content: ${String(content)}`);
103
+ }
104
+ };
65
105
  function Button({ className, variant, size, render, loading, icon, children, disabled, tooltip, ...props }) {
66
106
  const typeValue = render ? void 0 : "button";
67
- const tooltipContent = tooltip ?? props["aria-label"] ?? props.title;
107
+ const tooltipContent = tooltip ?? implicitTooltip(size, props["aria-label"] ?? props.title);
68
108
  const disposition = resolveButtonDisposition({
69
109
  disabled,
70
110
  loading,
@@ -110,4 +150,4 @@ function Button({ className, variant, size, render, loading, icon, children, dis
110
150
  });
111
151
  }
112
152
  //#endregion
113
- export { Button, buttonAccessibleDisabledClass, buttonVariants };
153
+ export { BUTTON_CONTENT, BUTTON_SIZE_CONTENT, Button, buttonAccessibleDisabledClass, buttonVariants };
@@ -36,6 +36,5 @@ declare const ComboboxChips: ({ className, children, startAddon, ...props }: Com
36
36
  startAddon?: React$1.ReactNode;
37
37
  }) => React$1.JSX.Element;
38
38
  declare const ComboboxChip: ({ children, ...props }: Combobox$1.Chip.Props) => React$1.JSX.Element;
39
- declare const useComboboxFilter: typeof Combobox$1.useFilter;
40
39
  //#endregion
41
- export { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxClear, ComboboxCollection, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxList, ComboboxPopup, ComboboxRow, ComboboxSeparator, ComboboxStatus, ComboboxTrigger, ComboboxValue, useComboboxFilter };
40
+ export { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxClear, ComboboxCollection, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxList, ComboboxPopup, ComboboxRow, ComboboxSeparator, ComboboxStatus, ComboboxTrigger, ComboboxValue };
@@ -231,6 +231,5 @@ const ComboboxChipRemove = (props) => /* @__PURE__ */ jsx(Combobox$1.ChipRemove,
231
231
  ...props,
232
232
  children: /* @__PURE__ */ jsx(XIcon, {})
233
233
  });
234
- const useComboboxFilter = Combobox$1.useFilter;
235
234
  //#endregion
236
- export { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxClear, ComboboxCollection, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxList, ComboboxPopup, ComboboxRow, ComboboxSeparator, ComboboxStatus, ComboboxTrigger, ComboboxValue, useComboboxFilter };
235
+ export { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxClear, ComboboxCollection, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxList, ComboboxPopup, ComboboxRow, ComboboxSeparator, ComboboxStatus, ComboboxTrigger, ComboboxValue };
@@ -2,7 +2,7 @@ import { cn } from "../lib/utils.js";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  //#region src/components/details-grid.tsx
4
4
  const DetailsGrid = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
5
- className: "@container min-w-0",
5
+ className: "@container min-w-0 [contain-intrinsic-inline-size:20rem]",
6
6
  "data-slot": "details-grid-container",
7
7
  children: /* @__PURE__ */ jsx("dl", {
8
8
  className: cn("grid grid-cols-[repeat(auto-fit,minmax(min(100%,20rem),1fr))] gap-x-6 gap-y-2 font-sans @min-[64rem]:grid-cols-3", className),
@@ -14,7 +14,7 @@ declare const DialogProvider: ({ labels, children }: React$1.PropsWithChildren<{
14
14
  declare const Dialog: <Payload>({ dirty, onOpenChange, onOpenChangeComplete, ...props }: Dialog$1.Root.Props<Payload> & {
15
15
  dirty?: boolean;
16
16
  }) => React$1.JSX.Element;
17
- declare const DialogPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").AlertDialogPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
17
+ declare const DialogPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").DialogPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
18
18
  declare function DialogTrigger({ tooltip, ...props }: Dialog$1.Trigger.Props & {
19
19
  tooltip?: React$1.ReactNode;
20
20
  }): React$1.JSX.Element;
@@ -65,7 +65,7 @@ const Dialog = ({ dirty = false, onOpenChange, onOpenChangeComplete, ...props })
65
65
  const DialogPortal = Dialog$1.Portal;
66
66
  function DialogTrigger({ tooltip, ...props }) {
67
67
  return renderTooltipTrigger({
68
- tooltip: tooltip ?? props["aria-label"] ?? props.title,
68
+ tooltip,
69
69
  trigger: /* @__PURE__ */ jsx(Dialog$1.Trigger, {
70
70
  "data-slot": "dialog-trigger",
71
71
  ...props
@@ -74,7 +74,7 @@ function DialogTrigger({ tooltip, ...props }) {
74
74
  }
75
75
  function DialogClose({ tooltip, ...props }) {
76
76
  return renderTooltipTrigger({
77
- tooltip: tooltip ?? props["aria-label"] ?? props.title,
77
+ tooltip,
78
78
  trigger: /* @__PURE__ */ jsx(Dialog$1.Close, {
79
79
  "data-slot": "dialog-close",
80
80
  ...props
@@ -33,6 +33,7 @@ const InlineRenameInput = ({ value, onValueChange, onCommit, onCancel, onBlur, c
33
33
  "data-slot": "inline-rename-mirror",
34
34
  children: mirrorText
35
35
  }), /* @__PURE__ */ jsx("input", {
36
+ enterKeyHint: "done",
36
37
  ...props,
37
38
  "data-slot": "inline-rename-input",
38
39
  dir: dir ?? (isStructuredInputType(props.type) ? "ltr" : contentDir(value)),
@@ -5,7 +5,7 @@ import { VariantProps } from "class-variance-authority";
5
5
  //#region src/components/input-group.d.ts
6
6
  declare const InputGroup: ({ className, ...props }: React$1.ComponentProps<"div">) => React$1.JSX.Element;
7
7
  declare const inputGroupAddonVariants: (props?: ({
8
- align?: "inline-end" | "inline-start" | "block-end" | "block-start" | null | undefined;
8
+ align?: "block-end" | "block-start" | "inline-end" | "inline-start" | null | undefined;
9
9
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
10
10
  declare const InputGroupAddon: ({ className, align, ...props }: React$1.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) => React$1.JSX.Element;
11
11
  declare const InputGroupText: ({ className, ...props }: React$1.ComponentProps<"span">) => React$1.JSX.Element;
@@ -2,8 +2,8 @@ import * as React$1 from "react";
2
2
  import { Menu as Menu$1 } from "@base-ui/react/menu";
3
3
  //#region src/components/menu.d.ts
4
4
  declare const MenuCreateHandle: typeof Menu$1.createHandle;
5
- declare const Menu: <Payload>(props: Menu$1.Root.Props<Payload>) => React$1.JSX.Element;
6
- declare const MenuPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").ContextMenuPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
5
+ declare const Menu: typeof Menu$1.Root;
6
+ declare const MenuPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").MenuPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
7
7
  declare function MenuTrigger({ tooltip, ...props }: Menu$1.Trigger.Props & {
8
8
  tooltip?: React$1.ReactNode;
9
9
  }): React$1.JSX.Element;
@@ -12,7 +12,7 @@ const Menu = Menu$1.Root;
12
12
  const MenuPortal = Menu$1.Portal;
13
13
  function MenuTrigger({ tooltip, ...props }) {
14
14
  return renderTooltipTrigger({
15
- tooltip: tooltip ?? props["aria-label"] ?? props.title,
15
+ tooltip,
16
16
  trigger: /* @__PURE__ */ jsx(Menu$1.Trigger, {
17
17
  "data-slot": "menu-trigger",
18
18
  nativeButton: true,
@@ -59,5 +59,5 @@ export type OutlineRailProps = {
59
59
  export declare const OUTLINE_CONTROL_MIN_SIZE = 32;
60
60
  /** The entry as one line of text: label, then its title when it has one. */
61
61
  export declare const outlineEntryText: (item: OutlineItem) => string;
62
- export declare const OutlineRail: ({ items, scrollContainerRef, resolvePct, onJump, activeId, header, formatMetaLabel, collapsedFromLevel, presentation, bottomInset, topOffset, panelWidth, ariaLabel }: OutlineRailProps) => import("react").JSX.Element | null;
62
+ export declare const OutlineRail: ({ items: itemsProp, scrollContainerRef, resolvePct, onJump, activeId, header, formatMetaLabel, collapsedFromLevel, presentation, bottomInset, topOffset, panelWidth, ariaLabel }: OutlineRailProps) => import("react").JSX.Element | null;
63
63
  //#endregion
@@ -5,8 +5,9 @@ import { Tooltip, TooltipContent as TooltipPopup, TooltipTrigger } from "./toolt
5
5
  import { ScrollArea } from "./scroll-area.js";
6
6
  import { DirectionalIcon } from "./directional-icon.js";
7
7
  import "../lib/panel-inset.js";
8
+ import { reconcileOutlineItems, retainOutlineIdSet } from "./outline-rail.logic.js";
8
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
- import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
10
+ import { memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
10
11
  import { Temporal } from "temporal-polyfill/full";
11
12
  //#region src/components/outline-rail.tsx
12
13
  /**
@@ -90,7 +91,101 @@ const Chevron = ({ open }) => /* @__PURE__ */ jsx(DirectionalIcon, {
90
91
  flip: !open,
91
92
  icon: ChevronRightIcon
92
93
  });
93
- const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId, header, formatMetaLabel, collapsedFromLevel, presentation = "popover", bottomInset, topOffset = 0, panelWidth = 300, ariaLabel = "Outline" }) => {
94
+ const OutlineRowView = ({ item, hasChildren, isCollapsed, isActive, isHovered, indent, metaLabel, onJump, onToggle, onHover }) => /* @__PURE__ */ jsxs("div", {
95
+ className: cn("flex items-center rounded-md pe-2.5", (isActive || isHovered) && "bg-accent"),
96
+ ...isActive ? { [ACTIVE_ROW_ATTRIBUTE]: "" } : {},
97
+ onMouseEnter: () => onHover(item.id),
98
+ onMouseLeave: () => onHover(null),
99
+ children: [
100
+ hasChildren ? /* @__PURE__ */ jsx("button", {
101
+ "aria-expanded": !isCollapsed,
102
+ "aria-label": isCollapsed ? "Expand" : "Collapse",
103
+ className: cn("text-muted-foreground hover:text-foreground flex shrink-0 items-center justify-center", OUTLINE_CONTROL_TARGET_CLASS),
104
+ style: {
105
+ minWidth: 32,
106
+ minHeight: 32,
107
+ marginInlineStart: indent - 4
108
+ },
109
+ onClick: (event) => onToggle(item.id, event.currentTarget.parentElement),
110
+ type: "button",
111
+ children: /* @__PURE__ */ jsx(Chevron, { open: !isCollapsed })
112
+ }) : /* @__PURE__ */ jsx("span", {
113
+ "aria-hidden": true,
114
+ className: "shrink-0",
115
+ style: { width: indent + 4 }
116
+ }),
117
+ item.color && /* @__PURE__ */ jsx("span", {
118
+ "aria-hidden": true,
119
+ className: "me-1.5 size-1.5 shrink-0 rounded-full",
120
+ style: { background: `var(${item.color})` }
121
+ }),
122
+ /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
123
+ render: /* @__PURE__ */ jsx("button", {
124
+ "aria-current": isActive ? "true" : void 0,
125
+ className: cn("flex min-w-0 flex-1 items-baseline gap-1.5 py-1.5 text-start text-[13px] leading-snug", rowTextClass(isActive, item.emphasis ?? OUTLINE_EMPHASIS.primary)),
126
+ onClick: () => onJump(item.id),
127
+ type: "button"
128
+ }),
129
+ children: item.title === void 0 ? /* @__PURE__ */ jsx("span", {
130
+ className: "min-w-0 truncate",
131
+ children: item.label
132
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
133
+ className: "shrink-0 font-medium",
134
+ children: item.label
135
+ }), /* @__PURE__ */ jsx("span", {
136
+ className: "min-w-0 truncate font-normal",
137
+ children: item.title
138
+ })] })
139
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: outlineEntryText(item) })] }),
140
+ item.meta !== void 0 && (metaLabel !== void 0 ? /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
141
+ render: /* @__PURE__ */ jsx("button", {
142
+ "aria-label": metaLabel,
143
+ className: cn("text-foreground-placeholder text-2xs shrink-0 tabular-nums", OUTLINE_CONTROL_TARGET_CLASS),
144
+ style: {
145
+ minWidth: 32,
146
+ minHeight: 32
147
+ },
148
+ onClick: () => onJump(item.id),
149
+ type: "button"
150
+ }),
151
+ children: item.meta
152
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: metaLabel })] }) : /* @__PURE__ */ jsx("span", {
153
+ className: "text-foreground-placeholder text-2xs shrink-0 ps-2 tabular-nums",
154
+ children: item.meta
155
+ }))
156
+ ]
157
+ });
158
+ const OutlineRow = memo(OutlineRowView);
159
+ const OutlineTreeView = ({ tree, collapsed, toggled, activeAncestorIds, activeId, hoveredId, maxLevel, formatMetaLabel, onJump, onToggle, onHover }) => {
160
+ const renderNode = (node) => {
161
+ const hasChildren = node.children.length > 0;
162
+ const isCollapsed = collapsed.has(node.item.id) && !(activeAncestorIds.has(node.item.id) && !toggled.has(node.item.id));
163
+ return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx(OutlineRow, {
164
+ hasChildren,
165
+ indent: 8 + Math.min(node.item.level, maxLevel) * 12,
166
+ isActive: activeId === node.item.id,
167
+ isCollapsed,
168
+ isHovered: hoveredId === node.item.id,
169
+ item: node.item,
170
+ metaLabel: node.item.meta === void 0 ? void 0 : formatMetaLabel?.(node.item.meta),
171
+ onHover,
172
+ onJump,
173
+ onToggle
174
+ }), hasChildren && !isCollapsed && /* @__PURE__ */ jsx("ul", {
175
+ className: "m-0 list-none p-0",
176
+ children: node.children.map(renderNode)
177
+ })] }, `${node.item.id}-${node.index}`);
178
+ };
179
+ return /* @__PURE__ */ jsx("ul", {
180
+ className: "m-0 list-none p-0 pb-2",
181
+ children: tree.map(renderNode)
182
+ });
183
+ };
184
+ const OutlineTree = memo(OutlineTreeView);
185
+ const OutlineRail = ({ items: itemsProp, scrollContainerRef, resolvePct, onJump, activeId, header, formatMetaLabel, collapsedFromLevel, presentation = "popover", bottomInset, topOffset = 0, panelWidth = 300, ariaLabel = "Outline" }) => {
186
+ const [stableItems, setStableItems] = useState(itemsProp);
187
+ const items = reconcileOutlineItems(stableItems, itemsProp);
188
+ if (items !== stableItems) setStableItems(items);
94
189
  const [pctById, setPctById] = useState(/* @__PURE__ */ new Map());
95
190
  const [derivedActive, setDerivedActive] = useState(null);
96
191
  const [hovered, setHovered] = useState(false);
@@ -109,11 +204,22 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
109
204
  const resolvePctRef = useRef(resolvePct);
110
205
  const onJumpRef = useRef(onJump);
111
206
  const tree = useMemo(() => buildTree(items), [items]);
112
- const [seededItems, setSeededItems] = useState(null);
207
+ const [seededItems, setSeededItems] = useState([]);
113
208
  if (seededItems !== items) {
209
+ const knownIds = new Set(seededItems.map((item) => item.id));
114
210
  setSeededItems(items);
115
- setToggled(/* @__PURE__ */ new Set());
116
- setCollapsed(collapsedFromLevel === void 0 ? /* @__PURE__ */ new Set() : new Set(items.filter((item) => item.level >= collapsedFromLevel).map((item) => item.id)));
211
+ setToggled((previous) => retainOutlineIdSet({
212
+ ids: previous,
213
+ knownIds,
214
+ seed: () => false,
215
+ items
216
+ }));
217
+ setCollapsed((previous) => retainOutlineIdSet({
218
+ ids: previous,
219
+ knownIds,
220
+ seed: (item) => collapsedFromLevel !== void 0 && item.level >= collapsedFromLevel,
221
+ items
222
+ }));
117
223
  }
118
224
  const maxLevel = useMemo(() => {
119
225
  let max = 0;
@@ -210,15 +316,16 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
210
316
  }
211
317
  return ancestors;
212
318
  }, [active, items]);
319
+ const activeControlled = activeId !== void 0;
213
320
  const jumpTo = useCallback((id) => {
214
321
  const container = scrollContainerRef.current;
215
322
  if (!container) return;
216
- if (activeId === void 0) {
323
+ if (!activeControlled) {
217
324
  setDerivedActive(id);
218
325
  manualLockUntil.current = Temporal.Now.instant().epochMilliseconds + 900;
219
326
  }
220
327
  onJumpRef.current(id, container);
221
- }, [activeId, scrollContainerRef]);
328
+ }, [activeControlled, scrollContainerRef]);
222
329
  const toggleCollapse = useCallback((id, rowEl) => {
223
330
  const rowTop = rowEl?.getBoundingClientRect().top;
224
331
  setToggled((prev) => new Set(prev).add(id));
@@ -283,81 +390,6 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
283
390
  if (closeTimer.current !== null) clearTimeout(closeTimer.current);
284
391
  if (openTimer.current !== null) clearTimeout(openTimer.current);
285
392
  }, []);
286
- const renderNode = (node) => {
287
- const hasChildren = node.children.length > 0;
288
- const isCollapsed = collapsed.has(node.item.id) && !(activeAncestorIds.has(node.item.id) && !toggled.has(node.item.id));
289
- const isActive = active === node.item.id;
290
- const isHovered = hoveredId === node.item.id;
291
- const highlighted = isActive || isHovered;
292
- const indent = 8 + Math.min(node.item.level, maxLevel) * 12;
293
- return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsxs("div", {
294
- className: cn("flex items-center rounded-md pe-2.5", highlighted && "bg-accent"),
295
- ...isActive ? { [ACTIVE_ROW_ATTRIBUTE]: "" } : {},
296
- onMouseEnter: () => setHoveredId(node.item.id),
297
- onMouseLeave: () => setHoveredId(null),
298
- children: [
299
- hasChildren ? /* @__PURE__ */ jsx("button", {
300
- "aria-expanded": !isCollapsed,
301
- "aria-label": isCollapsed ? "Expand" : "Collapse",
302
- className: cn("text-muted-foreground hover:text-foreground flex shrink-0 items-center justify-center", OUTLINE_CONTROL_TARGET_CLASS),
303
- style: {
304
- minWidth: 32,
305
- minHeight: 32,
306
- marginInlineStart: indent - 4
307
- },
308
- onClick: (event) => toggleCollapse(node.item.id, event.currentTarget.parentElement),
309
- type: "button",
310
- children: /* @__PURE__ */ jsx(Chevron, { open: !isCollapsed })
311
- }) : /* @__PURE__ */ jsx("span", {
312
- "aria-hidden": true,
313
- className: "shrink-0",
314
- style: { width: indent + 4 }
315
- }),
316
- node.item.color && /* @__PURE__ */ jsx("span", {
317
- "aria-hidden": true,
318
- className: "me-1.5 size-1.5 shrink-0 rounded-full",
319
- style: { background: `var(${node.item.color})` }
320
- }),
321
- /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
322
- render: /* @__PURE__ */ jsx("button", {
323
- "aria-current": isActive ? "true" : void 0,
324
- className: cn("flex min-w-0 flex-1 items-baseline gap-1.5 py-1.5 text-start text-[13px] leading-snug", rowTextClass(isActive, node.item.emphasis ?? OUTLINE_EMPHASIS.primary)),
325
- onClick: () => jumpTo(node.item.id),
326
- type: "button"
327
- }),
328
- children: node.item.title === void 0 ? /* @__PURE__ */ jsx("span", {
329
- className: "min-w-0 truncate",
330
- children: node.item.label
331
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
332
- className: "shrink-0 font-medium",
333
- children: node.item.label
334
- }), /* @__PURE__ */ jsx("span", {
335
- className: "min-w-0 truncate font-normal",
336
- children: node.item.title
337
- })] })
338
- }), /* @__PURE__ */ jsx(TooltipPopup, { children: outlineEntryText(node.item) })] }),
339
- node.item.meta !== void 0 && (formatMetaLabel ? /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
340
- render: /* @__PURE__ */ jsx("button", {
341
- "aria-label": formatMetaLabel(node.item.meta),
342
- className: cn("text-foreground-placeholder text-2xs shrink-0 tabular-nums", OUTLINE_CONTROL_TARGET_CLASS),
343
- style: {
344
- minWidth: 32,
345
- minHeight: 32
346
- },
347
- onClick: () => jumpTo(node.item.id),
348
- type: "button"
349
- }),
350
- children: node.item.meta
351
- }), /* @__PURE__ */ jsx(TooltipPopup, { children: formatMetaLabel(node.item.meta) })] }) : /* @__PURE__ */ jsx("span", {
352
- className: "text-foreground-placeholder text-2xs shrink-0 ps-2 tabular-nums",
353
- children: node.item.meta
354
- }))
355
- ]
356
- }), hasChildren && !isCollapsed && /* @__PURE__ */ jsx("ul", {
357
- className: "m-0 list-none p-0",
358
- children: node.children.map(renderNode)
359
- })] }, `${node.item.id}-${node.index}`);
360
- };
361
393
  const visibleTicks = items.flatMap((item) => {
362
394
  const pct = pctById.get(item.id);
363
395
  return pct !== void 0 && item.level <= railLevelCap ? [{
@@ -483,9 +515,18 @@ const OutlineRail = ({ items, scrollContainerRef, resolvePct, onJump, activeId,
483
515
  axis: "vertical",
484
516
  className: "min-h-0 flex-1",
485
517
  viewportRef: panelRef,
486
- children: /* @__PURE__ */ jsx("ul", {
487
- className: "m-0 list-none p-0 pb-2",
488
- children: tree.map(renderNode)
518
+ children: /* @__PURE__ */ jsx(OutlineTree, {
519
+ activeAncestorIds,
520
+ activeId: active,
521
+ collapsed,
522
+ formatMetaLabel,
523
+ hoveredId,
524
+ maxLevel,
525
+ onHover: setHoveredId,
526
+ onJump: jumpTo,
527
+ onToggle: toggleCollapse,
528
+ toggled,
529
+ tree
489
530
  })
490
531
  })]
491
532
  })]
@@ -0,0 +1,25 @@
1
+ import { OutlineItem } from "./outline-rail.js";
2
+ //#region src/components/outline-rail.logic.d.ts
3
+ /**
4
+ * `next` with every entry whose content `previous` already holds under the
5
+ * same id replaced by that earlier object, and `previous` itself when nothing
6
+ * changed. Callers rebuild their items from scratch on every change (an
7
+ * editor re-collects its headings after each edit); reconciling keeps an
8
+ * unchanged entry's identity, so only the rows that actually changed render.
9
+ */
10
+ export declare const reconcileOutlineItems: (previous: OutlineItem[], next: OutlineItem[]) => OutlineItem[];
11
+ type RetainKnownIdsOptions = {
12
+ ids: ReadonlySet<string>;
13
+ /** The ids present before `items` replaced them. */
14
+ knownIds: ReadonlySet<string>;
15
+ /** What an id seen for the first time gets. */
16
+ seed: (item: OutlineItem) => boolean;
17
+ items: OutlineItem[];
18
+ };
19
+ /**
20
+ * Carry a per-entry flag set (collapsed, toggled by hand) across an items
21
+ * change: an entry that was already there keeps the reader's state, an entry
22
+ * seen for the first time gets `seed`, and a removed entry drops out.
23
+ */
24
+ export declare const retainOutlineIdSet: ({ ids, knownIds, seed, items }: RetainKnownIdsOptions) => ReadonlySet<string>;
25
+ //#endregion
@@ -0,0 +1,40 @@
1
+ //#region src/components/outline-rail.logic.ts
2
+ const OUTLINE_ITEM_EQUALS = Object.values({
3
+ id: (a, b) => a.id === b.id,
4
+ label: (a, b) => a.label === b.label,
5
+ title: (a, b) => a.title === b.title,
6
+ level: (a, b) => a.level === b.level,
7
+ meta: (a, b) => a.meta === b.meta,
8
+ color: (a, b) => a.color === b.color,
9
+ emphasis: (a, b) => a.emphasis === b.emphasis
10
+ });
11
+ const sameOutlineItem = (a, b) => OUTLINE_ITEM_EQUALS.every((equals) => equals(a, b));
12
+ /**
13
+ * `next` with every entry whose content `previous` already holds under the
14
+ * same id replaced by that earlier object, and `previous` itself when nothing
15
+ * changed. Callers rebuild their items from scratch on every change (an
16
+ * editor re-collects its headings after each edit); reconciling keeps an
17
+ * unchanged entry's identity, so only the rows that actually changed render.
18
+ */
19
+ const reconcileOutlineItems = (previous, next) => {
20
+ if (previous === next) return previous;
21
+ const previousById = /* @__PURE__ */ new Map();
22
+ for (const item of previous) previousById.set(item.id, item);
23
+ const reconciled = next.map((item) => {
24
+ const earlier = previousById.get(item.id);
25
+ return earlier !== void 0 && sameOutlineItem(earlier, item) ? earlier : item;
26
+ });
27
+ return reconciled.length === previous.length && reconciled.every((item, index) => item === previous[index]) ? previous : reconciled;
28
+ };
29
+ /**
30
+ * Carry a per-entry flag set (collapsed, toggled by hand) across an items
31
+ * change: an entry that was already there keeps the reader's state, an entry
32
+ * seen for the first time gets `seed`, and a removed entry drops out.
33
+ */
34
+ const retainOutlineIdSet = ({ ids, knownIds, seed, items }) => {
35
+ const next = /* @__PURE__ */ new Set();
36
+ for (const item of items) if (knownIds.has(item.id) ? ids.has(item.id) : seed(item)) next.add(item.id);
37
+ return next.size === ids.size && [...next].every((id) => ids.has(id)) ? ids : next;
38
+ };
39
+ //#endregion
40
+ export { reconcileOutlineItems, retainOutlineIdSet };
@@ -10,7 +10,7 @@ const PopoverCreateHandle = Popover$1.createHandle;
10
10
  const Popover = Popover$1.Root;
11
11
  function PopoverTrigger({ tooltip, ...props }) {
12
12
  return renderTooltipTrigger({
13
- tooltip: tooltip ?? props["aria-label"] ?? props.title,
13
+ tooltip,
14
14
  trigger: /* @__PURE__ */ jsx(Popover$1.Trigger, {
15
15
  "data-slot": "popover-trigger",
16
16
  ...props
@@ -2,7 +2,7 @@ import * as React$1 from "react";
2
2
  import { VariantProps } from "class-variance-authority";
3
3
  //#region src/components/prose.d.ts
4
4
  declare const proseVariants: (props?: ({
5
- size?: "default" | "sm" | "lede" | null | undefined;
5
+ size?: "default" | "lede" | "sm" | null | undefined;
6
6
  tone?: "default" | "muted" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
8
  /** A readable measure for narrative copy; task-shaped text should not use it. */
@@ -2,7 +2,7 @@ import * as React$1 from "react";
2
2
  import { Dialog } from "@base-ui/react/dialog";
3
3
  //#region src/components/sheet.d.ts
4
4
  declare const Sheet: <Payload>(props: Dialog.Root.Props<Payload>) => React$1.JSX.Element;
5
- declare const SheetPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").AlertDialogPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
5
+ declare const SheetPortal: React$1.ForwardRefExoticComponent<Omit<import("@base-ui/react").DialogPortalProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
6
6
  type SheetSide = "inline-start" | "inline-end" | "block-start" | "block-end";
7
7
  declare function SheetTrigger({ tooltip, ...props }: Dialog.Trigger.Props & {
8
8
  tooltip?: React$1.ReactNode;