@estiva-app/ui 0.44.1 → 0.45.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 (54) hide show
  1. package/dist/SelectionToolbar.d.ts +62 -0
  2. package/dist/SelectionToolbar.d.ts.map +1 -0
  3. package/dist/SuggestionMenu.d.ts +75 -0
  4. package/dist/SuggestionMenu.d.ts.map +1 -0
  5. package/dist/editor.d.ts +12 -0
  6. package/dist/editor.d.ts.map +1 -0
  7. package/dist/editor.js +357 -0
  8. package/dist/editor.js.map +7 -0
  9. package/dist/eslint/index.js +2 -2
  10. package/dist/eslint/index.js.map +2 -2
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +201 -99
  14. package/dist/index.js.map +4 -4
  15. package/dist/registry/{app-YMABQII6.js → app-LDGOH6UW.js} +3 -3
  16. package/dist/registry/{build-3EANARLQ.js → build-P6X3PTZP.js} +6 -2
  17. package/dist/registry/build.d.ts +4 -0
  18. package/dist/registry/build.d.ts.map +1 -1
  19. package/dist/registry/{chunk-SIB44CES.js → chunk-P55P2QFD.js} +9 -4
  20. package/dist/registry/chunk-P55P2QFD.js.map +7 -0
  21. package/dist/registry/{chunk-EVJW26UR.js → chunk-TQ6UQER3.js} +10 -10
  22. package/dist/registry/chunk-TQ6UQER3.js.map +7 -0
  23. package/dist/registry/cli.js +2 -2
  24. package/dist/registry/index.js +2 -2
  25. package/dist/shortcuts.d.ts +27 -0
  26. package/dist/shortcuts.d.ts.map +1 -0
  27. package/dist/suggestionPopup.d.ts +41 -0
  28. package/dist/suggestionPopup.d.ts.map +1 -0
  29. package/package.json +27 -2
  30. package/registry.json +341 -3
  31. package/src/SelectionToolbar.mdx +78 -0
  32. package/src/SelectionToolbar.stories.tsx +63 -0
  33. package/src/SelectionToolbar.test.ts +105 -0
  34. package/src/SelectionToolbar.tsx +487 -0
  35. package/src/SuggestionMenu.mdx +107 -0
  36. package/src/SuggestionMenu.stories.tsx +202 -0
  37. package/src/SuggestionMenu.test.tsx +128 -0
  38. package/src/SuggestionMenu.tsx +189 -0
  39. package/src/editor.ts +11 -0
  40. package/src/eslint/no-rebuilt-behaviour.test.ts +1 -1
  41. package/src/eslint/no-rebuilt-behaviour.ts +2 -2
  42. package/src/how.test.ts +1 -1
  43. package/src/index.ts +2 -0
  44. package/src/keptSelection.test.ts +67 -0
  45. package/src/registry/app.ts +8 -8
  46. package/src/registry/build.ts +10 -2
  47. package/src/registry/registry.test.ts +14 -9
  48. package/src/shortcuts.ts +40 -0
  49. package/src/suggestionPopup.tsx +119 -0
  50. package/stories/Choosing.mdx +2 -0
  51. package/dist/registry/chunk-EVJW26UR.js.map +0 -7
  52. package/dist/registry/chunk-SIB44CES.js.map +0 -7
  53. /package/dist/registry/{app-YMABQII6.js.map → app-LDGOH6UW.js.map} +0 -0
  54. /package/dist/registry/{build-3EANARLQ.js.map → build-P6X3PTZP.js.map} +0 -0
package/dist/index.js CHANGED
@@ -2875,11 +2875,97 @@ function Popover({ trigger, anchor, align = "left", side = "bottom", open, onOpe
2875
2875
  );
2876
2876
  }
2877
2877
 
2878
+ // src/SuggestionMenu.tsx
2879
+ import { useCallback as useCallback3, useEffect as useEffect4, useId, useImperativeHandle, useMemo as useMemo4, useState as useState7 } from "react";
2880
+ import { jsx as jsx45, jsxs as jsxs31 } from "react/jsx-runtime";
2881
+ function SuggestionMenu({ rect, editorElement, ariaLabel, width, maxHeight, sections, itemKey, row, onSelect, handle }) {
2882
+ const shown = useMemo4(() => sections.filter((section) => section.items.length > 0), [sections]);
2883
+ const items = useMemo4(() => shown.flatMap((section) => section.items), [shown]);
2884
+ const keys = items.map(itemKey).join("\n");
2885
+ const [highlight, setHighlight] = useState7(0);
2886
+ const base = useId();
2887
+ const listboxId = `${base}-list`;
2888
+ useEffect4(() => setHighlight(0), [keys]);
2889
+ useImperativeHandle(handle, () => ({
2890
+ // The editor hands its arrow keys to this list: Base UI's lists need a text field of their own, and an editor is not one.
2891
+ onKeyDown: (event) => {
2892
+ if (!items.length) return false;
2893
+ if (event.key === "ArrowDown") {
2894
+ setHighlight((h) => (h + 1) % items.length);
2895
+ return true;
2896
+ }
2897
+ if (event.key === "ArrowUp") {
2898
+ setHighlight((h) => (h - 1 + items.length) % items.length);
2899
+ return true;
2900
+ }
2901
+ if (event.key === "Enter") {
2902
+ const item = items[highlight];
2903
+ if (item !== void 0) onSelect(item);
2904
+ return true;
2905
+ }
2906
+ return false;
2907
+ }
2908
+ }), [items, highlight, onSelect]);
2909
+ const open = rect !== null && items.length > 0;
2910
+ useEffect4(() => {
2911
+ if (!editorElement || !open) return;
2912
+ editorElement.setAttribute("aria-autocomplete", "list");
2913
+ editorElement.setAttribute("aria-haspopup", "listbox");
2914
+ editorElement.setAttribute("aria-controls", listboxId);
2915
+ return () => {
2916
+ for (const name of ["aria-autocomplete", "aria-haspopup", "aria-controls", "aria-activedescendant"]) editorElement.removeAttribute(name);
2917
+ };
2918
+ }, [editorElement, open, listboxId]);
2919
+ useEffect4(() => {
2920
+ if (!editorElement || !open) return;
2921
+ editorElement.setAttribute("aria-activedescendant", `${base}-${highlight}`);
2922
+ }, [editorElement, open, base, highlight]);
2923
+ const scrollRef = useCallback3((el) => {
2924
+ el?.scrollIntoView({ block: "nearest" });
2925
+ }, []);
2926
+ if (items.length === 0) return null;
2927
+ const drawRow = (item) => {
2928
+ const index = items.indexOf(item);
2929
+ const selected = index === highlight;
2930
+ const { label, description, leading, shortcut, hint, size } = row(item);
2931
+ return (
2932
+ // A row of a list the editor drives from its caret: focus stays in the text, so the row is no Tab stop.
2933
+ /* @__PURE__ */ jsx45(
2934
+ MenuItem,
2935
+ {
2936
+ role: "option",
2937
+ id: `${base}-${index}`,
2938
+ "aria-selected": selected,
2939
+ tabIndex: -1,
2940
+ ref: selected ? scrollRef : void 0,
2941
+ size,
2942
+ label,
2943
+ description,
2944
+ leading,
2945
+ shortcut,
2946
+ hint,
2947
+ selected,
2948
+ onMouseEnter: () => setHighlight(index),
2949
+ onMouseDown: (event) => {
2950
+ event.preventDefault();
2951
+ onSelect(item);
2952
+ }
2953
+ },
2954
+ itemKey(item)
2955
+ )
2956
+ );
2957
+ };
2958
+ return /* @__PURE__ */ jsx45(Popover, { anchor: rect, open: rect !== null, side: "top", align: "left", className: width, maxHeight, ariaLabel, children: /* @__PURE__ */ jsx45("div", { id: listboxId, role: "listbox", "aria-label": ariaLabel, className: "flex flex-col", children: shown.map((section, i) => /* @__PURE__ */ jsxs31("div", { className: "contents", children: [
2959
+ i > 0 && /* @__PURE__ */ jsx45(MenuSeparator, {}),
2960
+ section.label ? /* @__PURE__ */ jsx45("div", { role: "group", "aria-label": section.label, children: /* @__PURE__ */ jsx45(MenuSection, { label: section.label, className: section.className, children: section.items.map(drawRow) }) }) : section.items.map(drawRow)
2961
+ ] }, section.label ?? i)) }) });
2962
+ }
2963
+
2878
2964
  // src/Toolbar.tsx
2879
2965
  import { Toolbar as BaseToolbar } from "@base-ui/react/toolbar";
2880
- import { jsx as jsx45 } from "react/jsx-runtime";
2966
+ import { jsx as jsx46 } from "react/jsx-runtime";
2881
2967
  function Toolbar({ "aria-label": ariaLabel, orientation = "horizontal", loopFocus = true, surface = true, children, className }) {
2882
- const strip = /* @__PURE__ */ jsx45(
2968
+ const strip = /* @__PURE__ */ jsx46(
2883
2969
  BaseToolbar.Root,
2884
2970
  {
2885
2971
  "aria-label": ariaLabel,
@@ -2890,27 +2976,27 @@ function Toolbar({ "aria-label": ariaLabel, orientation = "horizontal", loopFocu
2890
2976
  }
2891
2977
  );
2892
2978
  if (!surface) return strip;
2893
- return /* @__PURE__ */ jsx45(MenuPanel, { className: "w-fit p-1", children: strip });
2979
+ return /* @__PURE__ */ jsx46(MenuPanel, { className: "w-fit p-1", children: strip });
2894
2980
  }
2895
2981
  function ToolbarButton({ ref, disabled, disabledReason, ...props }) {
2896
- return /* @__PURE__ */ jsx45(
2982
+ return /* @__PURE__ */ jsx46(
2897
2983
  BaseToolbar.Button,
2898
2984
  {
2899
2985
  ref,
2900
2986
  disabled: disabled || !!disabledReason,
2901
2987
  focusableWhenDisabled: true,
2902
- render: /* @__PURE__ */ jsx45(IconButton, { disabled, disabledReason, ...props })
2988
+ render: /* @__PURE__ */ jsx46(IconButton, { disabled, disabledReason, ...props })
2903
2989
  }
2904
2990
  );
2905
2991
  }
2906
2992
  function ToolbarLink({ ref, href, ...props }) {
2907
- return /* @__PURE__ */ jsx45(BaseToolbar.Link, { ref, render: /* @__PURE__ */ jsx45(IconButton, { href, ...props }) });
2993
+ return /* @__PURE__ */ jsx46(BaseToolbar.Link, { ref, render: /* @__PURE__ */ jsx46(IconButton, { href, ...props }) });
2908
2994
  }
2909
2995
  function ToolbarInput({ size, ...props }) {
2910
- return /* @__PURE__ */ jsx45(BaseToolbar.Input, { render: /* @__PURE__ */ jsx45(TextInput, { size }), ...props });
2996
+ return /* @__PURE__ */ jsx46(BaseToolbar.Input, { render: /* @__PURE__ */ jsx46(TextInput, { size }), ...props });
2911
2997
  }
2912
2998
  function ToolbarSeparator({ className }) {
2913
- return /* @__PURE__ */ jsx45(
2999
+ return /* @__PURE__ */ jsx46(
2914
3000
  BaseToolbar.Separator,
2915
3001
  {
2916
3002
  orientation: "vertical",
@@ -2920,9 +3006,9 @@ function ToolbarSeparator({ className }) {
2920
3006
  }
2921
3007
 
2922
3008
  // src/ReactionPicker.tsx
2923
- import { jsx as jsx46 } from "react/jsx-runtime";
3009
+ import { jsx as jsx47 } from "react/jsx-runtime";
2924
3010
  function ReactionPicker({ options, onSelect, "aria-label": ariaLabel = "Reactions", surface = true, className }) {
2925
- return /* @__PURE__ */ jsx46(Toolbar, { "aria-label": ariaLabel, surface, className: cn("gap-0.5", className), children: options.map((option) => (
3011
+ return /* @__PURE__ */ jsx47(Toolbar, { "aria-label": ariaLabel, surface, className: cn("gap-0.5", className), children: options.map((option) => (
2926
3012
  /*
2927
3013
  A `ToolbarButton`, not a button in a tooltip wrapper. The wrapper
2928
3014
  would become the toolbar's item and the button inside it would never
@@ -2932,14 +3018,14 @@ function ReactionPicker({ options, onSelect, "aria-label": ariaLabel = "Reaction
2932
3018
  The tooltip names it for a pointer, `aria-label` for everything else,
2933
3019
  and both say the meaning rather than the glyph.
2934
3020
  */
2935
- /* @__PURE__ */ jsx46(
3021
+ /* @__PURE__ */ jsx47(
2936
3022
  ToolbarButton,
2937
3023
  {
2938
3024
  "aria-label": option.label,
2939
3025
  tooltip: option.label,
2940
3026
  onClick: () => onSelect(option.emoji),
2941
3027
  className: "size-7",
2942
- children: /* @__PURE__ */ jsx46("span", { "aria-hidden": "true", className: "text-h3 leading-none", children: option.emoji })
3028
+ children: /* @__PURE__ */ jsx47("span", { "aria-hidden": "true", className: "text-h3 leading-none", children: option.emoji })
2943
3029
  },
2944
3030
  option.emoji
2945
3031
  )
@@ -2948,23 +3034,23 @@ function ReactionPicker({ options, onSelect, "aria-label": ariaLabel = "Reaction
2948
3034
 
2949
3035
  // src/PreviewCard.tsx
2950
3036
  import { PreviewCard as BasePreviewCard } from "@base-ui/react/preview-card";
2951
- import { jsx as jsx47, jsxs as jsxs31 } from "react/jsx-runtime";
3037
+ import { jsx as jsx48, jsxs as jsxs32 } from "react/jsx-runtime";
2952
3038
  var GAP6 = 12;
2953
3039
  var VIEWPORT_PAD6 = 8;
2954
3040
  var OPEN_DELAY2 = 350;
2955
3041
  var CLOSE_DELAY = 200;
2956
3042
  function PreviewCard({ content, children, side = "right", delay = OPEN_DELAY2, closeDelay = CLOSE_DELAY, className, contentClassName, wrapperClassName }) {
2957
- return /* @__PURE__ */ jsxs31(BasePreviewCard.Root, { children: [
2958
- /* @__PURE__ */ jsx47(
3043
+ return /* @__PURE__ */ jsxs32(BasePreviewCard.Root, { children: [
3044
+ /* @__PURE__ */ jsx48(
2959
3045
  BasePreviewCard.Trigger,
2960
3046
  {
2961
3047
  delay,
2962
3048
  closeDelay,
2963
- render: /* @__PURE__ */ jsx47("span", { className: cn("inline-flex", wrapperClassName) }),
3049
+ render: /* @__PURE__ */ jsx48("span", { className: cn("inline-flex", wrapperClassName) }),
2964
3050
  children
2965
3051
  }
2966
3052
  ),
2967
- /* @__PURE__ */ jsx47(BasePreviewCard.Portal, { children: /* @__PURE__ */ jsx47(
3053
+ /* @__PURE__ */ jsx48(BasePreviewCard.Portal, { children: /* @__PURE__ */ jsx48(
2968
3054
  BasePreviewCard.Positioner,
2969
3055
  {
2970
3056
  side,
@@ -2972,12 +3058,12 @@ function PreviewCard({ content, children, side = "right", delay = OPEN_DELAY2, c
2972
3058
  sideOffset: GAP6,
2973
3059
  collisionPadding: VIEWPORT_PAD6,
2974
3060
  className: "z-50 data-[anchor-hidden]:hidden",
2975
- children: /* @__PURE__ */ jsx47(
3061
+ children: /* @__PURE__ */ jsx48(
2976
3062
  BasePreviewCard.Popup,
2977
3063
  {
2978
3064
  className: cn("w-[360px] p-0 outline-none", className),
2979
- render: /* @__PURE__ */ jsx47(MenuPanel, {}),
2980
- children: /* @__PURE__ */ jsx47(ScrollArea, { viewportClassName: "max-h-[min(300px,var(--available-height))]", contentClassName: cn("flex flex-col gap-3 p-3", contentClassName), children: content })
3065
+ render: /* @__PURE__ */ jsx48(MenuPanel, {}),
3066
+ children: /* @__PURE__ */ jsx48(ScrollArea, { viewportClassName: "max-h-[min(300px,var(--available-height))]", contentClassName: cn("flex flex-col gap-3 p-3", contentClassName), children: content })
2981
3067
  }
2982
3068
  )
2983
3069
  }
@@ -2986,15 +3072,15 @@ function PreviewCard({ content, children, side = "right", delay = OPEN_DELAY2, c
2986
3072
  }
2987
3073
 
2988
3074
  // src/Rail.tsx
2989
- import { jsx as jsx48 } from "react/jsx-runtime";
3075
+ import { jsx as jsx49 } from "react/jsx-runtime";
2990
3076
  function Rail({ "aria-label": ariaLabel = "Navigation", children, className }) {
2991
- return /* @__PURE__ */ jsx48("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
3077
+ return /* @__PURE__ */ jsx49("nav", { "aria-label": ariaLabel, className: cn("flex w-16 shrink-0 flex-col items-start gap-2 px-2 py-3", className), children });
2992
3078
  }
2993
3079
 
2994
3080
  // src/RailItem.tsx
2995
- import { jsx as jsx49, jsxs as jsxs32 } from "react/jsx-runtime";
3081
+ import { jsx as jsx50, jsxs as jsxs33 } from "react/jsx-runtime";
2996
3082
  function RailItem({ href, icon, label, active = false, className, ...props }) {
2997
- return /* @__PURE__ */ jsxs32(
3083
+ return /* @__PURE__ */ jsxs33(
2998
3084
  "a",
2999
3085
  {
3000
3086
  href,
@@ -3002,14 +3088,14 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
3002
3088
  className: cn("group flex h-12 w-full shrink-0 flex-col items-center gap-0.5 px-2 py-0.5", className),
3003
3089
  ...props,
3004
3090
  children: [
3005
- /* @__PURE__ */ jsx49(
3091
+ /* @__PURE__ */ jsx50(
3006
3092
  "div",
3007
3093
  {
3008
3094
  className: cn(
3009
3095
  "flex items-center justify-center rounded-lg p-2 transition-colors",
3010
3096
  active ? "bg-bg-selected" : "group-hover:bg-bg-hover"
3011
3097
  ),
3012
- children: /* @__PURE__ */ jsx49(
3098
+ children: /* @__PURE__ */ jsx50(
3013
3099
  "span",
3014
3100
  {
3015
3101
  className: cn(
@@ -3021,14 +3107,14 @@ function RailItem({ href, icon, label, active = false, className, ...props }) {
3021
3107
  )
3022
3108
  }
3023
3109
  ),
3024
- /* @__PURE__ */ jsx49("span", { className: cn("text-center text-menu", active ? "text-text-primary" : "text-text-secondary"), children: label })
3110
+ /* @__PURE__ */ jsx50("span", { className: cn("text-center text-menu", active ? "text-text-primary" : "text-text-secondary"), children: label })
3025
3111
  ]
3026
3112
  }
3027
3113
  );
3028
3114
  }
3029
3115
 
3030
3116
  // src/Property.tsx
3031
- import { jsx as jsx50, jsxs as jsxs33 } from "react/jsx-runtime";
3117
+ import { jsx as jsx51, jsxs as jsxs34 } from "react/jsx-runtime";
3032
3118
  var VALUE_CLASSES = "contents";
3033
3119
  function Property({ label, layout = "row", children, className }) {
3034
3120
  if (layout === "stacked") {
@@ -3036,25 +3122,25 @@ function Property({ label, layout = "row", children, className }) {
3036
3122
  // gap-3 — the one label-above-content distance (Katerina, 2026-09-01):
3037
3123
  // Peek's topic-details sections already sit at 12px, Ship's rail was
3038
3124
  // 6px, and unifying means the bigger, calmer one.
3039
- /* @__PURE__ */ jsxs33("dl", { className: cn("flex flex-col gap-3", className), children: [
3040
- /* @__PURE__ */ jsx50("dt", { className: "text-menu uppercase tracking-widest text-text-secondary", children: label }),
3041
- /* @__PURE__ */ jsx50("dd", { className: VALUE_CLASSES, children })
3125
+ /* @__PURE__ */ jsxs34("dl", { className: cn("flex flex-col gap-3", className), children: [
3126
+ /* @__PURE__ */ jsx51("dt", { className: "text-menu uppercase tracking-widest text-text-secondary", children: label }),
3127
+ /* @__PURE__ */ jsx51("dd", { className: VALUE_CLASSES, children })
3042
3128
  ] })
3043
3129
  );
3044
3130
  }
3045
- return /* @__PURE__ */ jsxs33("dl", { className: cn("flex items-center gap-2", className), children: [
3046
- /* @__PURE__ */ jsx50("dt", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
3047
- /* @__PURE__ */ jsx50("dd", { className: VALUE_CLASSES, children })
3131
+ return /* @__PURE__ */ jsxs34("dl", { className: cn("flex items-center gap-2", className), children: [
3132
+ /* @__PURE__ */ jsx51("dt", { className: "w-[68px] shrink-0 text-caption text-text-secondary", children: label }),
3133
+ /* @__PURE__ */ jsx51("dd", { className: VALUE_CLASSES, children })
3048
3134
  ] });
3049
3135
  }
3050
3136
 
3051
3137
  // src/Reaction.tsx
3052
3138
  import { Toggle } from "@base-ui/react/toggle";
3053
- import { jsx as jsx51, jsxs as jsxs34 } from "react/jsx-runtime";
3139
+ import { jsx as jsx52, jsxs as jsxs35 } from "react/jsx-runtime";
3054
3140
  function Reaction({ emoji, count, pressed = false, className, ...props }) {
3055
3141
  return (
3056
3142
  // @estiva-escape(no-copied-look): W1 (Katerina, 24 September: leave alone): a reaction pill shares four words with a field's box by coincidence
3057
- /* @__PURE__ */ jsxs34(
3143
+ /* @__PURE__ */ jsxs35(
3058
3144
  Toggle,
3059
3145
  {
3060
3146
  pressed,
@@ -3076,8 +3162,8 @@ function Reaction({ emoji, count, pressed = false, className, ...props }) {
3076
3162
  ),
3077
3163
  ...props,
3078
3164
  children: [
3079
- /* @__PURE__ */ jsx51("span", { "aria-hidden": "true", className: "shrink-0 text-body-1 leading-none", children: emoji }),
3080
- /* @__PURE__ */ jsx51("span", { className: CHIP_TEXT_CLASSES, children: count })
3165
+ /* @__PURE__ */ jsx52("span", { "aria-hidden": "true", className: "shrink-0 text-body-1 leading-none", children: emoji }),
3166
+ /* @__PURE__ */ jsx52("span", { className: CHIP_TEXT_CLASSES, children: count })
3081
3167
  ]
3082
3168
  }
3083
3169
  )
@@ -3087,9 +3173,9 @@ function Reaction({ emoji, count, pressed = false, className, ...props }) {
3087
3173
  // src/SearchInput.tsx
3088
3174
  import "react";
3089
3175
  import { Input as Input3 } from "@base-ui/react/input";
3090
- import { jsx as jsx52, jsxs as jsxs35 } from "react/jsx-runtime";
3176
+ import { jsx as jsx53, jsxs as jsxs36 } from "react/jsx-runtime";
3091
3177
  function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...props }) {
3092
- return /* @__PURE__ */ jsxs35(
3178
+ return /* @__PURE__ */ jsxs36(
3093
3179
  "div",
3094
3180
  {
3095
3181
  className: cn(
@@ -3101,7 +3187,7 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
3101
3187
  className
3102
3188
  ),
3103
3189
  children: [
3104
- /* @__PURE__ */ jsx52(
3190
+ /* @__PURE__ */ jsx53(
3105
3191
  Input3,
3106
3192
  {
3107
3193
  className: cn("flex-1 min-w-0 text-input-value", BARE_INPUT_CLASSES),
@@ -3109,7 +3195,7 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
3109
3195
  ...props
3110
3196
  }
3111
3197
  ),
3112
- shortcut && /* @__PURE__ */ jsx52(Kbd, { children: shortcut })
3198
+ shortcut && /* @__PURE__ */ jsx53(Kbd, { children: shortcut })
3113
3199
  ]
3114
3200
  }
3115
3201
  );
@@ -3118,17 +3204,17 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
3118
3204
  // src/SectionHeader.tsx
3119
3205
  import { IconChevronRight as IconChevronRight2 } from "@tabler/icons-react";
3120
3206
  import { useRender } from "@base-ui/react/use-render";
3121
- import { Fragment as Fragment11, jsx as jsx53, jsxs as jsxs36 } from "react/jsx-runtime";
3207
+ import { Fragment as Fragment11, jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
3122
3208
  function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, trailing, actions, showActions = "hover", hover = "fill", render, className }) {
3123
3209
  const titleElement = useRender({
3124
- render: render ?? (chevron ? /* @__PURE__ */ jsx53("button", { type: "button", onClick: onToggle, "aria-expanded": isExpanded }) : /* @__PURE__ */ jsx53("span", {})),
3210
+ render: render ?? (chevron ? /* @__PURE__ */ jsx54("button", { type: "button", onClick: onToggle, "aria-expanded": isExpanded }) : /* @__PURE__ */ jsx54("span", {})),
3125
3211
  props: {
3126
3212
  // `text-left`: a button centres its text. `h-full` and `flex-1`: the
3127
3213
  // whole row up to the actions is the hit target, as it was when the
3128
3214
  // row itself carried the click.
3129
3215
  className: "flex h-full min-w-0 flex-1 items-center gap-1 text-left",
3130
- children: /* @__PURE__ */ jsxs36(Fragment11, { children: [
3131
- chevron && /* @__PURE__ */ jsx53(
3216
+ children: /* @__PURE__ */ jsxs37(Fragment11, { children: [
3217
+ chevron && /* @__PURE__ */ jsx54(
3132
3218
  IconChevronRight2,
3133
3219
  {
3134
3220
  size: 12,
@@ -3136,11 +3222,11 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
3136
3222
  className: cn("shrink-0 text-text-secondary transition-transform duration-150", isExpanded && "rotate-90")
3137
3223
  }
3138
3224
  ),
3139
- /* @__PURE__ */ jsx53(SectionLabel, { children: title })
3225
+ /* @__PURE__ */ jsx54(SectionLabel, { children: title })
3140
3226
  ] })
3141
3227
  }
3142
3228
  });
3143
- return /* @__PURE__ */ jsxs36(
3229
+ return /* @__PURE__ */ jsxs37(
3144
3230
  "div",
3145
3231
  {
3146
3232
  className: cn(
@@ -3153,15 +3239,15 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
3153
3239
  ),
3154
3240
  children: [
3155
3241
  titleElement,
3156
- trailing != null && /* @__PURE__ */ jsx53("div", { className: "flex shrink-0 items-center", children: trailing }),
3157
- actions && actions.length > 0 && /* @__PURE__ */ jsx53(
3242
+ trailing != null && /* @__PURE__ */ jsx54("div", { className: "flex shrink-0 items-center", children: trailing }),
3243
+ actions && actions.length > 0 && /* @__PURE__ */ jsx54(
3158
3244
  "div",
3159
3245
  {
3160
3246
  className: cn(
3161
3247
  "-mr-1 flex shrink-0 items-center gap-1",
3162
3248
  showActions === "hover" && "opacity-0 transition-opacity focus-within:opacity-100 group-hover:opacity-100"
3163
3249
  ),
3164
- children: actions.map((action) => /* @__PURE__ */ jsx53(IconButton, { tooltip: action.tooltip, "aria-label": action.tooltip, onClick: action.onClick, children: action.icon }, action.tooltip))
3250
+ children: actions.map((action) => /* @__PURE__ */ jsx54(IconButton, { tooltip: action.tooltip, "aria-label": action.tooltip, onClick: action.onClick, children: action.icon }, action.tooltip))
3165
3251
  }
3166
3252
  )
3167
3253
  ]
@@ -3170,35 +3256,35 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
3170
3256
  }
3171
3257
 
3172
3258
  // src/Panel.tsx
3173
- import { jsx as jsx54, jsxs as jsxs37 } from "react/jsx-runtime";
3259
+ import { jsx as jsx55, jsxs as jsxs38 } from "react/jsx-runtime";
3174
3260
  function Frame({ title, chevron = false, actions, children, footer, bodyClassName, bodyRef, onBodyScroll, className, broken = false }) {
3175
- return /* @__PURE__ */ jsxs37("div", { className: cn("flex h-full min-h-0 flex-1 flex-col", className), children: [
3176
- /* @__PURE__ */ jsx54(ContainerHeader, { title, chevron, actions }),
3261
+ return /* @__PURE__ */ jsxs38("div", { className: cn("flex h-full min-h-0 flex-1 flex-col", className), children: [
3262
+ /* @__PURE__ */ jsx55(ContainerHeader, { title, chevron, actions }),
3177
3263
  broken ? (
3178
3264
  // The message takes the room the body had and centres itself in it.
3179
- /* @__PURE__ */ jsx54("div", { className: "flex min-h-0 flex-1 flex-col px-4", children })
3180
- ) : /* @__PURE__ */ jsx54(ScrollArea, { className: "min-h-0 flex-1", contentClassName: bodyClassName, viewportRef: bodyRef, onScroll: onBodyScroll, children }),
3181
- !broken && footer && /* @__PURE__ */ jsx54("div", { className: "shrink-0", children: footer })
3265
+ /* @__PURE__ */ jsx55("div", { className: "flex min-h-0 flex-1 flex-col px-4", children })
3266
+ ) : /* @__PURE__ */ jsx55(ScrollArea, { className: "min-h-0 flex-1", contentClassName: bodyClassName, viewportRef: bodyRef, onScroll: onBodyScroll, children }),
3267
+ !broken && footer && /* @__PURE__ */ jsx55("div", { className: "shrink-0", children: footer })
3182
3268
  ] });
3183
3269
  }
3184
3270
  function Panel(props) {
3185
- return /* @__PURE__ */ jsx54(
3271
+ return /* @__PURE__ */ jsx55(
3186
3272
  ErrorBoundary,
3187
3273
  {
3188
3274
  label: "panel",
3189
3275
  frame: (message) => (
3190
3276
  // Only the name comes back with the message: the actions and the footer may be what threw.
3191
- /* @__PURE__ */ jsx54(Frame, { title: props.title, chevron: props.chevron, className: props.className, broken: true, children: message })
3277
+ /* @__PURE__ */ jsx55(Frame, { title: props.title, chevron: props.chevron, className: props.className, broken: true, children: message })
3192
3278
  ),
3193
- children: /* @__PURE__ */ jsx54(Frame, { ...props })
3279
+ children: /* @__PURE__ */ jsx55(Frame, { ...props })
3194
3280
  }
3195
3281
  );
3196
3282
  }
3197
3283
 
3198
3284
  // src/ListColumn.tsx
3199
- import { jsx as jsx55, jsxs as jsxs38 } from "react/jsx-runtime";
3285
+ import { jsx as jsx56, jsxs as jsxs39 } from "react/jsx-runtime";
3200
3286
  function Column({ title, chevron = false, actions, above, collapsed = false, children, className, broken = false }) {
3201
- return /* @__PURE__ */ jsxs38(
3287
+ return /* @__PURE__ */ jsxs39(
3202
3288
  "div",
3203
3289
  {
3204
3290
  className: cn(
@@ -3209,34 +3295,34 @@ function Column({ title, chevron = false, actions, above, collapsed = false, chi
3209
3295
  ),
3210
3296
  "data-collapsed": collapsed || void 0,
3211
3297
  children: [
3212
- /* @__PURE__ */ jsx55(ContainerHeader, { title, chevron, actions }),
3298
+ /* @__PURE__ */ jsx56(ContainerHeader, { title, chevron, actions }),
3213
3299
  above,
3214
3300
  broken ? (
3215
3301
  // The message takes the room the list had and centres itself in it (Katerina, 19 September).
3216
- /* @__PURE__ */ jsx55("div", { className: "flex min-h-0 flex-1 flex-col px-3", children })
3217
- ) : /* @__PURE__ */ jsx55(ScrollArea, { className: "flex-1", contentClassName: "flex flex-col gap-0.5 px-3 pt-4 pb-3", children })
3302
+ /* @__PURE__ */ jsx56("div", { className: "flex min-h-0 flex-1 flex-col px-3", children })
3303
+ ) : /* @__PURE__ */ jsx56(ScrollArea, { className: "flex-1", contentClassName: "flex flex-col gap-0.5 px-3 pt-4 pb-3", children })
3218
3304
  ]
3219
3305
  }
3220
3306
  );
3221
3307
  }
3222
3308
  function ListColumn(props) {
3223
- return /* @__PURE__ */ jsx55(
3309
+ return /* @__PURE__ */ jsx56(
3224
3310
  ErrorBoundary,
3225
3311
  {
3226
3312
  label: "list panel",
3227
3313
  frame: (message) => (
3228
3314
  // Only the name comes back with the message: the actions and the row above may be what threw.
3229
- /* @__PURE__ */ jsx55(Column, { title: props.title, chevron: props.chevron, collapsed: props.collapsed, className: props.className, broken: true, children: message })
3315
+ /* @__PURE__ */ jsx56(Column, { title: props.title, chevron: props.chevron, collapsed: props.collapsed, className: props.className, broken: true, children: message })
3230
3316
  ),
3231
- children: /* @__PURE__ */ jsx55(Column, { ...props })
3317
+ children: /* @__PURE__ */ jsx56(Column, { ...props })
3232
3318
  }
3233
3319
  );
3234
3320
  }
3235
3321
 
3236
3322
  // src/CollapsibleSection.tsx
3237
- import { useState as useState7 } from "react";
3323
+ import { useState as useState8 } from "react";
3238
3324
  import { Collapsible } from "@base-ui/react/collapsible";
3239
- import { jsx as jsx56, jsxs as jsxs39 } from "react/jsx-runtime";
3325
+ import { jsx as jsx57, jsxs as jsxs40 } from "react/jsx-runtime";
3240
3326
  var OPEN = "open";
3241
3327
  var CLOSED = "closed";
3242
3328
  function readStored(key) {
@@ -3256,21 +3342,21 @@ function writeStored(key, open) {
3256
3342
  }
3257
3343
  }
3258
3344
  function CollapsibleSection({ title, defaultOpen = true, open: openProp, onOpenChange, storageKey, trailing, actions, showActions, children, className, contentClassName }) {
3259
- const [openState, setOpenState] = useState7(() => readStored(storageKey) ?? defaultOpen);
3345
+ const [openState, setOpenState] = useState8(() => readStored(storageKey) ?? defaultOpen);
3260
3346
  const open = openProp ?? openState;
3261
3347
  const setOpen = (next) => {
3262
3348
  setOpenState(next);
3263
3349
  writeStored(storageKey, next);
3264
3350
  onOpenChange?.(next);
3265
3351
  };
3266
- return /* @__PURE__ */ jsxs39(Collapsible.Root, { open, onOpenChange: setOpen, className: cn("flex flex-col", className), children: [
3267
- /* @__PURE__ */ jsx56(SectionHeader, { title, chevron: true, isExpanded: open, trailing, actions, showActions, className: "shrink-0", render: /* @__PURE__ */ jsx56(Collapsible.Trigger, {}) }),
3268
- /* @__PURE__ */ jsx56(
3352
+ return /* @__PURE__ */ jsxs40(Collapsible.Root, { open, onOpenChange: setOpen, className: cn("flex flex-col", className), children: [
3353
+ /* @__PURE__ */ jsx57(SectionHeader, { title, chevron: true, isExpanded: open, trailing, actions, showActions, className: "shrink-0", render: /* @__PURE__ */ jsx57(Collapsible.Trigger, {}) }),
3354
+ /* @__PURE__ */ jsx57(
3269
3355
  Collapsible.Panel,
3270
3356
  {
3271
3357
  hiddenUntilFound: true,
3272
3358
  className: "h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none data-[starting-style]:h-0 data-[ending-style]:h-0",
3273
- children: /* @__PURE__ */ jsx56("div", { className: cn("flex flex-col", contentClassName), children })
3359
+ children: /* @__PURE__ */ jsx57("div", { className: cn("flex flex-col", contentClassName), children })
3274
3360
  }
3275
3361
  )
3276
3362
  ] });
@@ -3278,9 +3364,9 @@ function CollapsibleSection({ title, defaultOpen = true, open: openProp, onOpenC
3278
3364
 
3279
3365
  // src/Tabs.tsx
3280
3366
  import { Tabs as BaseTabs } from "@base-ui/react/tabs";
3281
- import { jsx as jsx57, jsxs as jsxs40 } from "react/jsx-runtime";
3367
+ import { jsx as jsx58, jsxs as jsxs41 } from "react/jsx-runtime";
3282
3368
  function Tabs({ tabs, active, onChange, size = "default", className, ...aria }) {
3283
- return /* @__PURE__ */ jsx57(
3369
+ return /* @__PURE__ */ jsx58(
3284
3370
  BaseTabs.Root,
3285
3371
  {
3286
3372
  value: active,
@@ -3288,14 +3374,14 @@ function Tabs({ tabs, active, onChange, size = "default", className, ...aria })
3288
3374
  if (details.reason === "none") onChange(value);
3289
3375
  },
3290
3376
  className,
3291
- children: /* @__PURE__ */ jsx57(
3377
+ children: /* @__PURE__ */ jsx58(
3292
3378
  BaseTabs.List,
3293
3379
  {
3294
3380
  activateOnFocus: true,
3295
3381
  "aria-label": aria["aria-label"],
3296
3382
  "aria-labelledby": aria["aria-labelledby"],
3297
3383
  className: "flex items-center gap-2",
3298
- children: tabs.map((tab) => /* @__PURE__ */ jsxs40(
3384
+ children: tabs.map((tab) => /* @__PURE__ */ jsxs41(
3299
3385
  BaseTabs.Tab,
3300
3386
  {
3301
3387
  value: tab.id,
@@ -3308,9 +3394,9 @@ function Tabs({ tabs, active, onChange, size = "default", className, ...aria })
3308
3394
  ),
3309
3395
  children: [
3310
3396
  tab.icon,
3311
- /* @__PURE__ */ jsxs40("span", { className: "flex items-baseline", children: [
3397
+ /* @__PURE__ */ jsxs41("span", { className: "flex items-baseline", children: [
3312
3398
  tab.label,
3313
- tab.count !== void 0 ? /* @__PURE__ */ jsx57("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
3399
+ tab.count !== void 0 ? /* @__PURE__ */ jsx58("span", { className: "ml-2.5 font-mono text-caption tabular-nums text-text-secondary", children: tab.count }) : null
3314
3400
  ] })
3315
3401
  ]
3316
3402
  },
@@ -3323,10 +3409,10 @@ function Tabs({ tabs, active, onChange, size = "default", className, ...aria })
3323
3409
  }
3324
3410
 
3325
3411
  // src/Toast.tsx
3326
- import { createContext as createContext6, useContext as useContext6, useMemo as useMemo4 } from "react";
3412
+ import { createContext as createContext6, useContext as useContext6, useMemo as useMemo5 } from "react";
3327
3413
  import { Toast as BaseToast } from "@base-ui/react/toast";
3328
3414
  import { IconAlertCircle as IconAlertCircle2, IconCircleCheck, IconCircleX } from "@tabler/icons-react";
3329
- import { jsx as jsx58, jsxs as jsxs41 } from "react/jsx-runtime";
3415
+ import { jsx as jsx59, jsxs as jsxs42 } from "react/jsx-runtime";
3330
3416
  var SURFACE_STYLES = {
3331
3417
  success: "bg-success-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-md leaf:bg-bg-inset leaf:border leaf:border-border-default leaf:shadow-md",
3332
3418
  brand: "bg-accent-muted signal:bg-bg-inset signal:border signal:border-border-default signal:shadow-md leaf:bg-bg-inset leaf:border leaf:border-border-default leaf:shadow-md",
@@ -3362,23 +3448,23 @@ var pillClassName = (type, hasAction, className) => cn(
3362
3448
  );
3363
3449
  function LeadingIcon({ type }) {
3364
3450
  const Icon = ICONS[type];
3365
- return /* @__PURE__ */ jsx58(Icon, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_STYLES[type]) });
3451
+ return /* @__PURE__ */ jsx59(Icon, { size: 16, stroke: 1.5, className: cn("text-text-primary shrink-0", ICON_STYLES[type]) });
3366
3452
  }
3367
3453
  function Toast({ label, type = "neutral", leadingIcon = true, actionLabel, onAction, className }) {
3368
3454
  const hasAction = !!(actionLabel && onAction);
3369
- return /* @__PURE__ */ jsxs41("div", { className: pillClassName(type, hasAction, className), children: [
3370
- /* @__PURE__ */ jsxs41("div", { className: "flex items-center gap-2 shrink-0", children: [
3371
- leadingIcon && /* @__PURE__ */ jsx58(LeadingIcon, { type }),
3372
- /* @__PURE__ */ jsx58("span", { className: LABEL_CLASSES, children: label })
3455
+ return /* @__PURE__ */ jsxs42("div", { className: pillClassName(type, hasAction, className), children: [
3456
+ /* @__PURE__ */ jsxs42("div", { className: "flex items-center gap-2 shrink-0", children: [
3457
+ leadingIcon && /* @__PURE__ */ jsx59(LeadingIcon, { type }),
3458
+ /* @__PURE__ */ jsx59("span", { className: LABEL_CLASSES, children: label })
3373
3459
  ] }),
3374
- hasAction && /* @__PURE__ */ jsx58(Button, { variant: "outlined", size: "small", onClick: onAction, children: actionLabel })
3460
+ hasAction && /* @__PURE__ */ jsx59(Button, { variant: "outlined", size: "small", onClick: onAction, children: actionLabel })
3375
3461
  ] });
3376
3462
  }
3377
3463
  var ToastContext = createContext6(null);
3378
3464
  var VISIBLE_TOASTS = 3;
3379
3465
  function ToastProvider({ children }) {
3380
- const manager = useMemo4(() => BaseToast.createToastManager(), []);
3381
- const value = useMemo4(
3466
+ const manager = useMemo5(() => BaseToast.createToastManager(), []);
3467
+ const value = useMemo5(
3382
3468
  () => ({
3383
3469
  showToast: ({ label, type = "neutral", leadingIcon = true, actionLabel, onAction, durationMs = 5e3 }) => manager.add({
3384
3470
  title: label,
@@ -3392,9 +3478,9 @@ function ToastProvider({ children }) {
3392
3478
  }),
3393
3479
  [manager]
3394
3480
  );
3395
- return /* @__PURE__ */ jsx58(ToastContext.Provider, { value, children: /* @__PURE__ */ jsxs41(BaseToast.Provider, { toastManager: manager, limit: VISIBLE_TOASTS, children: [
3481
+ return /* @__PURE__ */ jsx59(ToastContext.Provider, { value, children: /* @__PURE__ */ jsxs42(BaseToast.Provider, { toastManager: manager, limit: VISIBLE_TOASTS, children: [
3396
3482
  children,
3397
- /* @__PURE__ */ jsx58(BaseToast.Portal, { children: /* @__PURE__ */ jsx58(BaseToast.Viewport, { className: "fixed bottom-4 left-4 z-[100] flex flex-col-reverse items-start gap-2 pointer-events-none", children: /* @__PURE__ */ jsx58(ToastList, {}) }) })
3483
+ /* @__PURE__ */ jsx59(BaseToast.Portal, { children: /* @__PURE__ */ jsx59(BaseToast.Viewport, { className: "fixed bottom-4 left-4 z-[100] flex flex-col-reverse items-start gap-2 pointer-events-none", children: /* @__PURE__ */ jsx59(ToastList, {}) }) })
3398
3484
  ] }) });
3399
3485
  }
3400
3486
  function ToastList() {
@@ -3402,25 +3488,25 @@ function ToastList() {
3402
3488
  return toasts.map((toast) => {
3403
3489
  const type = toast.type ?? "neutral";
3404
3490
  const { leadingIcon = true, actionLabel, onAction } = toast.data ?? {};
3405
- return /* @__PURE__ */ jsxs41(
3491
+ return /* @__PURE__ */ jsxs42(
3406
3492
  BaseToast.Root,
3407
3493
  {
3408
3494
  toast,
3409
3495
  swipeDirection: ["left", "down"],
3410
3496
  className: pillClassName(type, !!actionLabel, "pointer-events-auto data-[limited]:hidden"),
3411
3497
  children: [
3412
- /* @__PURE__ */ jsxs41("div", { className: "flex items-center gap-2 shrink-0", children: [
3413
- leadingIcon && /* @__PURE__ */ jsx58(LeadingIcon, { type }),
3414
- /* @__PURE__ */ jsx58(BaseToast.Title, { render: /* @__PURE__ */ jsx58("span", {}), className: LABEL_CLASSES })
3498
+ /* @__PURE__ */ jsxs42("div", { className: "flex items-center gap-2 shrink-0", children: [
3499
+ leadingIcon && /* @__PURE__ */ jsx59(LeadingIcon, { type }),
3500
+ /* @__PURE__ */ jsx59(BaseToast.Title, { render: /* @__PURE__ */ jsx59("span", {}), className: LABEL_CLASSES })
3415
3501
  ] }),
3416
- actionLabel && /* @__PURE__ */ jsx58(
3502
+ actionLabel && /* @__PURE__ */ jsx59(
3417
3503
  BaseToast.Action,
3418
3504
  {
3419
3505
  onClick: () => {
3420
3506
  onAction?.();
3421
3507
  close(toast.id);
3422
3508
  },
3423
- render: /* @__PURE__ */ jsx58(Button, { variant: "outlined", size: "small", children: actionLabel })
3509
+ render: /* @__PURE__ */ jsx59(Button, { variant: "outlined", size: "small", children: actionLabel })
3424
3510
  }
3425
3511
  )
3426
3512
  ]
@@ -3434,6 +3520,19 @@ function useToast() {
3434
3520
  if (!ctx) throw new Error("useToast must be used within ToastProvider");
3435
3521
  return ctx;
3436
3522
  }
3523
+
3524
+ // src/shortcuts.ts
3525
+ function isApplePlatform() {
3526
+ if (typeof navigator === "undefined") return false;
3527
+ return /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent || "");
3528
+ }
3529
+ function shortcutLabel(keys, apple = isApplePlatform()) {
3530
+ return keys.split("-").map((part, i, all) => {
3531
+ if (i === all.length - 1) return part.length === 1 ? part.toUpperCase() : part;
3532
+ if (part === "Mod") return apple ? "Cmd" : "Ctrl";
3533
+ return part;
3534
+ }).join("+");
3535
+ }
3437
3536
  export {
3438
3537
  AppShell,
3439
3538
  AttachmentCard,
@@ -3506,6 +3605,7 @@ export {
3506
3605
  SkeletonBar,
3507
3606
  SkeletonList,
3508
3607
  SkeletonRow,
3608
+ SuggestionMenu,
3509
3609
  Tabs,
3510
3610
  TextInput,
3511
3611
  Textarea,
@@ -3524,7 +3624,9 @@ export {
3524
3624
  hueFor,
3525
3625
  initialsFor,
3526
3626
  inlineChipClassName,
3627
+ isApplePlatform,
3527
3628
  richTextClassName,
3629
+ shortcutLabel,
3528
3630
  useToast
3529
3631
  };
3530
3632
  //# sourceMappingURL=index.js.map