@adea-ai/ui 0.147.0 → 0.147.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-switcher.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/workspace-switcher/workspace-switcher.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAsBnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;OAQG;IACH,IAAI,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAA;IACvC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,gGAAgG;IAChG,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,EAAE,SAAS,qBAAqB,EAAE,CAAA;IAC5C,kCAAkC;IAClC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,IAAI,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,IAAI,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAWD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,eAkJ9D"}
1
+ {"version":3,"file":"workspace-switcher.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/workspace-switcher/workspace-switcher.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAsBnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;OAQG;IACH,IAAI,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAA;IACvC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,gGAAgG;IAChG,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,EAAE,SAAS,qBAAqB,EAAE,CAAA;IAC5C,kCAAkC;IAClC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,IAAI,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,IAAI,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAWD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,eA6K9D"}
@@ -23,6 +23,8 @@ function WorkspaceSwitcher(props) {
23
23
  const [reorderDialog, setReorderDialog] = createSignal();
24
24
  const [reorderFailed, setReorderFailed] = createSignal(false);
25
25
  let cancelPending;
26
+ let openPending;
27
+ let menuClosed = true;
26
28
  let trigger;
27
29
  const current = () => props.workspaces.find((item) => item.id === props.value);
28
30
  const label = () => props.label ?? "Workspaces";
@@ -35,21 +37,28 @@ function WorkspaceSwitcher(props) {
35
37
  return false;
36
38
  });
37
39
  const warm = (next) => {
40
+ if (next) {
41
+ cancelPending?.();
42
+ menuClosed = false;
43
+ }
38
44
  setOpen(next);
39
45
  if (next && canReorder() && !reorderDialog()) loadDialog();
40
46
  };
41
47
  const requestReorder = () => {
42
48
  cancelPending?.();
43
- if (reorderDialog()) {
44
- setReordering(true);
45
- return;
46
- }
47
49
  let waiting = true;
50
+ let loaded = !!reorderDialog();
48
51
  const stop = () => {
49
52
  waiting = false;
50
53
  document.removeEventListener("keydown", onKey, true);
51
54
  document.removeEventListener("pointerdown", stop, true);
52
55
  if (cancelPending === stop) cancelPending = void 0;
56
+ if (openPending === openWhenReady) openPending = void 0;
57
+ };
58
+ const openWhenReady = () => {
59
+ if (!waiting || !loaded || !menuClosed) return;
60
+ stop();
61
+ setReordering(true);
53
62
  };
54
63
  const onKey = (event) => {
55
64
  if (event.key === "Escape") stop();
@@ -57,10 +66,14 @@ function WorkspaceSwitcher(props) {
57
66
  document.addEventListener("keydown", onKey, true);
58
67
  document.addEventListener("pointerdown", stop, true);
59
68
  cancelPending = stop;
60
- loadDialog().then((loaded) => {
61
- const stillWanted = waiting;
62
- stop();
63
- if (loaded && stillWanted) setReordering(true);
69
+ openPending = openWhenReady;
70
+ if (loaded) openWhenReady();
71
+ else loadDialog().then((success) => {
72
+ if (!success) stop();
73
+ else {
74
+ loaded = true;
75
+ openWhenReady();
76
+ }
64
77
  });
65
78
  };
66
79
  onCleanup(() => cancelPending?.());
@@ -114,6 +127,11 @@ function WorkspaceSwitcher(props) {
114
127
  align: "start",
115
128
  sideOffset: 6,
116
129
  "class": "min-w-56",
130
+ onCloseAutoFocus: () => {
131
+ if (open()) return;
132
+ menuClosed = true;
133
+ queueMicrotask(() => openPending?.());
134
+ },
117
135
  get children() {
118
136
  return [createComponent(DropdownMenuGroup, { get children() {
119
137
  return [createComponent(DropdownMenuLabel, { get children() {
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-switcher.js","names":["ArrowDownUp","ChevronDown","Plus","Settings","JSX","Dynamic","For","Show","createSignal","onCleanup","cn","DropdownMenu","DropdownMenuContent","DropdownMenuGroup","DropdownMenuItem","DropdownMenuLabel","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuSeparator","DropdownMenuTrigger","ActionButton","loadReorderDialog","ReorderDialog","Awaited","ReturnType","WorkspaceSwitcherItem","id","name","mark","Element","detail","fixed","WorkspaceSwitcherProps","workspaces","value","onValueChange","onCreate","createLabel","onManage","manageLabel","onReorder","ids","Promise","reorderLabel","reorderErrorLabel","label","placeholder","disabled","class","drawMark","copy","Node","cloneNode","WorkspaceSwitcher","props","open","setOpen","reordering","setReordering","reorderDialog","setReorderDialog","reorderFailed","setReorderFailed","cancelPending","trigger","HTMLElement","current","find","item","canReorder","undefined","filter","length","loadDialog","then","module","WorkspaceReorderDialog","warm","next","requestReorder","waiting","stop","document","removeEventListener","onKey","event","KeyboardEvent","key","addEventListener","loaded","stillWanted","_$createComponent","modal","onOpenChange","children","as","variant","size","ref","el","tooltip","aria-label","_el$","_tmpl$","_$insert","_c$","_$memo","_el$2","_tmpl$2","align","sideOffset","onChange","String","each","closeOnSelect","data-workspace-id","_el$3","_el$4","_tmpl$3","when","onSelect","component","restoreFocusRef","_$createComponent","_$memo"],"sources":["../../../../src/components/composites/workspace-switcher/workspace-switcher.tsx"],"sourcesContent":["import { ArrowDownUp, ChevronDown, Plus, Settings } from 'lucide-solid'\nimport type { JSX } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { For, Show, createSignal, onCleanup } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../../ui/dropdown-menu'\nimport { ActionButton } from '../action-button'\n\n// The dialog is only reachable from a menu item, so it stays out of the code\n// that ships with the switcher and loads on demand.\nconst loadReorderDialog = () => import('./workspace-reorder-dialog')\ntype ReorderDialog = Awaited<ReturnType<typeof loadReorderDialog>>['WorkspaceReorderDialog']\n\n/**\n * WorkspaceSwitcher.\n *\n * The workspace picker that sits in the top bar beside the sidebar toggle. One\n * ghost button shows the current workspace's mark and name; pressing it opens a\n * radio list of the others. Choosing one only changes what the window is\n * looking at: the switcher reports the id and the host decides what that means,\n * which is why it carries no status of its own and never closes, restarts or\n * moves anything.\n *\n * It is deliberately the simple interaction (a single menu, not a nested\n * accordion): the sidebar below it already shows what is inside the chosen\n * workspace, so the switcher's only job is to name the workspace and let the\n * user change it.\n */\nexport type WorkspaceSwitcherItem = {\n id: string\n name: string\n /**\n * The workspace's mark, drawn in the trigger and in its row. Decorative.\n *\n * A DOM node can sit in one place, so the current workspace's mark would have\n * to be in the trigger and its menu row at once. Pass a function to draw a\n * fresh mark in each place (the preferred form). A node built once is also\n * safe: the trigger keeps it and the current row gets a copy, which carries\n * no event handlers or reactive bindings, so keep marks static.\n */\n mark: JSX.Element | (() => JSX.Element)\n /** A quiet trailing readout in the row, e.g. an unread count. Optional. */\n detail?: JSX.Element\n /** Pinned to the top of the order, e.g. Home: it cannot be moved and nothing moves above it. */\n fixed?: boolean\n}\n\nexport type WorkspaceSwitcherProps = {\n workspaces: readonly WorkspaceSwitcherItem[]\n /** The current workspace's id. */\n value: string | undefined\n onValueChange(id: string): void\n /** Offered at the foot of the menu when present. */\n onCreate?(): void\n createLabel?: string\n /** Offered beside the create action when present: open the current workspace's settings. */\n onManage?(): void\n manageLabel?: string\n /**\n * Offered when present and at least two workspaces can move: opens a dialog\n * with Move up and Move down on each row. Receives the full id order after\n * each move; the host saves it and passes the workspaces back in that order.\n */\n onReorder?(ids: readonly string[]): void | Promise<void>\n reorderLabel?: string\n /** Replaces the \"Reorder workspaces…\" item when the dialog could not be loaded. */\n reorderErrorLabel?: string\n /** The menu's heading; also the trigger's accessible-name prefix. */\n label?: string\n /** Shown while no workspace is current. */\n placeholder?: string\n disabled?: boolean\n class?: string\n}\n\n/**\n * Draws one placement of a mark. A function draws anew each time; a node that is\n * already placed elsewhere is copied so the original stays where it is.\n */\nfunction drawMark(mark: WorkspaceSwitcherItem['mark'], copy: boolean): JSX.Element {\n if (typeof mark === 'function') return mark()\n return copy && mark instanceof Node ? mark.cloneNode(true) : mark\n}\n\nexport function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {\n const [open, setOpen] = createSignal(false)\n const [reordering, setReordering] = createSignal(false)\n const [reorderDialog, setReorderDialog] = createSignal<ReorderDialog>()\n const [reorderFailed, setReorderFailed] = createSignal(false)\n let cancelPending: (() => void) | undefined\n let trigger: HTMLElement | undefined\n const current = () => props.workspaces.find((item) => item.id === props.value)\n const label = () => props.label ?? 'Workspaces'\n const canReorder = () =>\n props.onReorder !== undefined && props.workspaces.filter((item) => !item.fixed).length > 1\n\n // Fetch the dialog's code. Safe to call early and often: the browser keeps one\n // copy, and a failure is remembered here instead of surfacing as an unhandled\n // rejection, so the switcher keeps working without reordering.\n const loadDialog = () =>\n loadReorderDialog().then(\n (module) => {\n setReorderDialog(() => module.WorkspaceReorderDialog)\n return true\n },\n () => {\n setReorderFailed(true)\n return false\n }\n )\n // Warm the chunk while the menu is open, so choosing the item rarely waits.\n const warm = (next: boolean) => {\n setOpen(next)\n if (next && canReorder() && !reorderDialog()) void loadDialog()\n }\n // Choosing the item while the code is still arriving must not open the dialog\n // late: nothing on screen can take Escape or an outside click yet, so either\n // of them withdraws the request, and the dialog opens only if the user waits.\n const requestReorder = () => {\n cancelPending?.()\n if (reorderDialog()) {\n setReordering(true)\n return\n }\n let waiting = true\n const stop = () => {\n waiting = false\n document.removeEventListener('keydown', onKey, true)\n document.removeEventListener('pointerdown', stop, true)\n if (cancelPending === stop) cancelPending = undefined\n }\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') stop()\n }\n document.addEventListener('keydown', onKey, true)\n document.addEventListener('pointerdown', stop, true)\n cancelPending = stop\n void loadDialog().then((loaded) => {\n const stillWanted = waiting\n stop()\n if (loaded && stillWanted) setReordering(true)\n })\n }\n onCleanup(() => cancelPending?.())\n\n return (\n <>\n <DropdownMenu modal={false} open={open()} onOpenChange={warm}>\n <DropdownMenuTrigger\n as={ActionButton}\n variant=\"ghost\"\n size=\"sm\"\n disabled={props.disabled}\n ref={(el: HTMLElement) => (trigger = el)}\n data-slot=\"workspace-switcher\"\n tooltip={`Workspace: ${current()?.name ?? props.placeholder ?? 'Choose workspace'}`}\n aria-label={`Switch workspace, current: ${current()?.name ?? 'none'}`}\n class={cn('max-w-full min-w-0 shrink', props.class)}\n >\n <span aria-hidden=\"true\" class=\"flex shrink-0\">\n {current() && drawMark(current()!.mark, false)}\n </span>\n <span class=\"min-w-0 max-w-40 truncate\">\n {current()?.name ?? props.placeholder ?? 'Choose workspace'}\n </span>\n <ChevronDown aria-hidden=\"true\" class=\"size-3.5 shrink-0 text-muted-foreground\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" sideOffset={6} class=\"min-w-56\">\n <DropdownMenuGroup>\n <DropdownMenuLabel>{label()}</DropdownMenuLabel>\n <DropdownMenuRadioGroup\n value={props.value}\n onChange={(value) => {\n setOpen(false)\n const id = String(value)\n if (id !== props.value) props.onValueChange(id)\n }}\n >\n <For each={props.workspaces}>\n {(item) => (\n <DropdownMenuRadioItem value={item.id} closeOnSelect data-workspace-id={item.id}>\n <span aria-hidden=\"true\" class=\"flex shrink-0\">\n {drawMark(item.mark, item.id === props.value)}\n </span>\n <span class=\"min-w-0 flex-1 truncate\">{item.name}</span>\n <Show when={item.detail}>{item.detail}</Show>\n </DropdownMenuRadioItem>\n )}\n </For>\n </DropdownMenuRadioGroup>\n </DropdownMenuGroup>\n <Show when={props.onCreate || props.onManage || canReorder()}>\n <DropdownMenuSeparator />\n <Show when={props.onCreate}>\n <DropdownMenuItem onSelect={() => props.onCreate?.()}>\n <Plus aria-hidden=\"true\" />\n {props.createLabel ?? 'New workspace'}\n </DropdownMenuItem>\n </Show>\n <Show when={canReorder()}>\n <DropdownMenuItem disabled={reorderFailed()} onSelect={requestReorder}>\n <ArrowDownUp aria-hidden=\"true\" />\n {reorderFailed()\n ? (props.reorderErrorLabel ?? 'Couldn’t load reordering. Reload to try again.')\n : (props.reorderLabel ?? 'Reorder workspaces…')}\n </DropdownMenuItem>\n </Show>\n <Show when={props.onManage}>\n <DropdownMenuItem onSelect={() => props.onManage?.()}>\n <Settings aria-hidden=\"true\" />\n {props.manageLabel ?? 'Workspace settings…'}\n </DropdownMenuItem>\n </Show>\n </Show>\n </DropdownMenuContent>\n </DropdownMenu>\n <Show when={reorderDialog() && props.onReorder}>\n {(onReorder) => (\n <Dynamic\n component={reorderDialog()}\n open={reordering()}\n onOpenChange={setReordering}\n workspaces={props.workspaces}\n onReorder={onReorder()}\n restoreFocusRef={() => trigger}\n />\n )}\n </Show>\n </>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workspace-switcher.js","names":["ArrowDownUp","ChevronDown","Plus","Settings","JSX","Dynamic","For","Show","createSignal","onCleanup","cn","DropdownMenu","DropdownMenuContent","DropdownMenuGroup","DropdownMenuItem","DropdownMenuLabel","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuSeparator","DropdownMenuTrigger","ActionButton","loadReorderDialog","ReorderDialog","Awaited","ReturnType","WorkspaceSwitcherItem","id","name","mark","Element","detail","fixed","WorkspaceSwitcherProps","workspaces","value","onValueChange","onCreate","createLabel","onManage","manageLabel","onReorder","ids","Promise","reorderLabel","reorderErrorLabel","label","placeholder","disabled","class","drawMark","copy","Node","cloneNode","WorkspaceSwitcher","props","open","setOpen","reordering","setReordering","reorderDialog","setReorderDialog","reorderFailed","setReorderFailed","cancelPending","openPending","menuClosed","trigger","HTMLElement","current","find","item","canReorder","undefined","filter","length","loadDialog","then","module","WorkspaceReorderDialog","warm","next","requestReorder","waiting","loaded","stop","document","removeEventListener","onKey","openWhenReady","event","KeyboardEvent","key","addEventListener","success","_$createComponent","modal","onOpenChange","children","as","variant","size","ref","el","tooltip","aria-label","_el$","_tmpl$","_$insert","_c$","_$memo","_el$2","_tmpl$2","align","sideOffset","onCloseAutoFocus","queueMicrotask","onChange","String","each","closeOnSelect","data-workspace-id","_el$3","_el$4","_tmpl$3","when","onSelect","component","restoreFocusRef","_$createComponent","_$memo"],"sources":["../../../../src/components/composites/workspace-switcher/workspace-switcher.tsx"],"sourcesContent":["import { ArrowDownUp, ChevronDown, Plus, Settings } from 'lucide-solid'\nimport type { JSX } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { For, Show, createSignal, onCleanup } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../../ui/dropdown-menu'\nimport { ActionButton } from '../action-button'\n\n// The dialog is only reachable from a menu item, so it stays out of the code\n// that ships with the switcher and loads on demand.\nconst loadReorderDialog = () => import('./workspace-reorder-dialog')\ntype ReorderDialog = Awaited<ReturnType<typeof loadReorderDialog>>['WorkspaceReorderDialog']\n\n/**\n * WorkspaceSwitcher.\n *\n * The workspace picker that sits in the top bar beside the sidebar toggle. One\n * ghost button shows the current workspace's mark and name; pressing it opens a\n * radio list of the others. Choosing one only changes what the window is\n * looking at: the switcher reports the id and the host decides what that means,\n * which is why it carries no status of its own and never closes, restarts or\n * moves anything.\n *\n * It is deliberately the simple interaction (a single menu, not a nested\n * accordion): the sidebar below it already shows what is inside the chosen\n * workspace, so the switcher's only job is to name the workspace and let the\n * user change it.\n */\nexport type WorkspaceSwitcherItem = {\n id: string\n name: string\n /**\n * The workspace's mark, drawn in the trigger and in its row. Decorative.\n *\n * A DOM node can sit in one place, so the current workspace's mark would have\n * to be in the trigger and its menu row at once. Pass a function to draw a\n * fresh mark in each place (the preferred form). A node built once is also\n * safe: the trigger keeps it and the current row gets a copy, which carries\n * no event handlers or reactive bindings, so keep marks static.\n */\n mark: JSX.Element | (() => JSX.Element)\n /** A quiet trailing readout in the row, e.g. an unread count. Optional. */\n detail?: JSX.Element\n /** Pinned to the top of the order, e.g. Home: it cannot be moved and nothing moves above it. */\n fixed?: boolean\n}\n\nexport type WorkspaceSwitcherProps = {\n workspaces: readonly WorkspaceSwitcherItem[]\n /** The current workspace's id. */\n value: string | undefined\n onValueChange(id: string): void\n /** Offered at the foot of the menu when present. */\n onCreate?(): void\n createLabel?: string\n /** Offered beside the create action when present: open the current workspace's settings. */\n onManage?(): void\n manageLabel?: string\n /**\n * Offered when present and at least two workspaces can move: opens a dialog\n * with Move up and Move down on each row. Receives the full id order after\n * each move; the host saves it and passes the workspaces back in that order.\n */\n onReorder?(ids: readonly string[]): void | Promise<void>\n reorderLabel?: string\n /** Replaces the \"Reorder workspaces…\" item when the dialog could not be loaded. */\n reorderErrorLabel?: string\n /** The menu's heading; also the trigger's accessible-name prefix. */\n label?: string\n /** Shown while no workspace is current. */\n placeholder?: string\n disabled?: boolean\n class?: string\n}\n\n/**\n * Draws one placement of a mark. A function draws anew each time; a node that is\n * already placed elsewhere is copied so the original stays where it is.\n */\nfunction drawMark(mark: WorkspaceSwitcherItem['mark'], copy: boolean): JSX.Element {\n if (typeof mark === 'function') return mark()\n return copy && mark instanceof Node ? mark.cloneNode(true) : mark\n}\n\nexport function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {\n const [open, setOpen] = createSignal(false)\n const [reordering, setReordering] = createSignal(false)\n const [reorderDialog, setReorderDialog] = createSignal<ReorderDialog>()\n const [reorderFailed, setReorderFailed] = createSignal(false)\n let cancelPending: (() => void) | undefined\n let openPending: (() => void) | undefined\n let menuClosed = true\n let trigger: HTMLElement | undefined\n const current = () => props.workspaces.find((item) => item.id === props.value)\n const label = () => props.label ?? 'Workspaces'\n const canReorder = () =>\n props.onReorder !== undefined && props.workspaces.filter((item) => !item.fixed).length > 1\n\n // Fetch the dialog's code. Safe to call early and often: the browser keeps one\n // copy, and a failure is remembered here instead of surfacing as an unhandled\n // rejection, so the switcher keeps working without reordering.\n const loadDialog = () =>\n loadReorderDialog().then(\n (module) => {\n setReorderDialog(() => module.WorkspaceReorderDialog)\n return true\n },\n () => {\n setReorderFailed(true)\n return false\n }\n )\n // Warm the chunk while the menu is open, so choosing the item rarely waits.\n const warm = (next: boolean) => {\n if (next) {\n // Reopening is new intent, including keyboard activation: an older\n // delayed reorder must not open after another workspace or action wins.\n cancelPending?.()\n menuClosed = false\n }\n setOpen(next)\n if (next && canReorder() && !reorderDialog()) void loadDialog()\n }\n // Choosing the item while the code is still arriving must not open the dialog\n // late: nothing on screen can take Escape or an outside click yet, so either\n // of them withdraws the request, and the dialog opens only if the user waits.\n const requestReorder = () => {\n cancelPending?.()\n let waiting = true\n let loaded = !!reorderDialog()\n const stop = () => {\n waiting = false\n document.removeEventListener('keydown', onKey, true)\n document.removeEventListener('pointerdown', stop, true)\n if (cancelPending === stop) cancelPending = undefined\n if (openPending === openWhenReady) openPending = undefined\n }\n const openWhenReady = () => {\n if (!waiting || !loaded || !menuClosed) return\n stop()\n setReordering(true)\n }\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') stop()\n }\n document.addEventListener('keydown', onKey, true)\n document.addEventListener('pointerdown', stop, true)\n cancelPending = stop\n openPending = openWhenReady\n if (loaded) openWhenReady()\n else\n void loadDialog().then((success) => {\n if (!success) stop()\n else {\n loaded = true\n openWhenReady()\n }\n })\n }\n onCleanup(() => cancelPending?.())\n\n return (\n <>\n <DropdownMenu modal={false} open={open()} onOpenChange={warm}>\n <DropdownMenuTrigger\n as={ActionButton}\n variant=\"ghost\"\n size=\"sm\"\n disabled={props.disabled}\n ref={(el: HTMLElement) => (trigger = el)}\n data-slot=\"workspace-switcher\"\n tooltip={`Workspace: ${current()?.name ?? props.placeholder ?? 'Choose workspace'}`}\n aria-label={`Switch workspace, current: ${current()?.name ?? 'none'}`}\n class={cn('max-w-full min-w-0 shrink', props.class)}\n >\n <span aria-hidden=\"true\" class=\"flex shrink-0\">\n {current() && drawMark(current()!.mark, false)}\n </span>\n <span class=\"min-w-0 max-w-40 truncate\">\n {current()?.name ?? props.placeholder ?? 'Choose workspace'}\n </span>\n <ChevronDown aria-hidden=\"true\" class=\"size-3.5 shrink-0 text-muted-foreground\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"start\"\n sideOffset={6}\n class=\"min-w-56\"\n onCloseAutoFocus={() => {\n if (open()) return\n menuClosed = true\n // Kobalte restores the menu trigger and removes its focus scope\n // after this callback. Open the dialog only once that work ends.\n queueMicrotask(() => openPending?.())\n }}\n >\n <DropdownMenuGroup>\n <DropdownMenuLabel>{label()}</DropdownMenuLabel>\n <DropdownMenuRadioGroup\n value={props.value}\n onChange={(value) => {\n setOpen(false)\n const id = String(value)\n if (id !== props.value) props.onValueChange(id)\n }}\n >\n <For each={props.workspaces}>\n {(item) => (\n <DropdownMenuRadioItem value={item.id} closeOnSelect data-workspace-id={item.id}>\n <span aria-hidden=\"true\" class=\"flex shrink-0\">\n {drawMark(item.mark, item.id === props.value)}\n </span>\n <span class=\"min-w-0 flex-1 truncate\">{item.name}</span>\n <Show when={item.detail}>{item.detail}</Show>\n </DropdownMenuRadioItem>\n )}\n </For>\n </DropdownMenuRadioGroup>\n </DropdownMenuGroup>\n <Show when={props.onCreate || props.onManage || canReorder()}>\n <DropdownMenuSeparator />\n <Show when={props.onCreate}>\n <DropdownMenuItem onSelect={() => props.onCreate?.()}>\n <Plus aria-hidden=\"true\" />\n {props.createLabel ?? 'New workspace'}\n </DropdownMenuItem>\n </Show>\n <Show when={canReorder()}>\n <DropdownMenuItem disabled={reorderFailed()} onSelect={requestReorder}>\n <ArrowDownUp aria-hidden=\"true\" />\n {reorderFailed()\n ? (props.reorderErrorLabel ?? 'Couldn’t load reordering. Reload to try again.')\n : (props.reorderLabel ?? 'Reorder workspaces…')}\n </DropdownMenuItem>\n </Show>\n <Show when={props.onManage}>\n <DropdownMenuItem onSelect={() => props.onManage?.()}>\n <Settings aria-hidden=\"true\" />\n {props.manageLabel ?? 'Workspace settings…'}\n </DropdownMenuItem>\n </Show>\n </Show>\n </DropdownMenuContent>\n </DropdownMenu>\n <Show when={reorderDialog() && props.onReorder}>\n {(onReorder) => (\n <Dynamic\n component={reorderDialog()}\n open={reordering()}\n onOpenChange={setReordering}\n workspaces={props.workspaces}\n onReorder={onReorder()}\n restoreFocusRef={() => trigger}\n />\n )}\n </Show>\n </>\n )\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"side-rail.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/side-rail/side-rail.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAOnE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,IAAI,gBAAgB,CAC5E,CAAC,EACD;IACE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CACF,CAAA;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,eAmCjF;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAY1D;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAY3D;AAED,kDAAkD;AAClD,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAY1D;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,eAahF;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAiBnB,CAAA;AAUX,eAAO,MAAM,sBAAsB;IACjC,MAAM;IACN,IAAI;CACL,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,cAAc,GAAG,GAAG,IAAI,gBAAgB,CAC9E,CAAC,EACD;IACE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACnB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CACF,CAAA;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,cAAc,GAAG,GAAG,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,eA4HvF;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG;IAC9C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,eA6EF"}
1
+ {"version":3,"file":"side-rail.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/side-rail/side-rail.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAOnE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,IAAI,gBAAgB,CAC5E,CAAC,EACD;IACE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CACF,CAAA;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,eAmCjF;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAY1D;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAY3D;AAED,kDAAkD;AAClD,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAY1D;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,eAahF;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAiBnB,CAAA;AAUX,eAAO,MAAM,sBAAsB;IACjC,MAAM;IACN,IAAI;CACL,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,cAAc,GAAG,GAAG,IAAI,gBAAgB,CAC9E,CAAC,EACD;IACE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACnB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CACF,CAAA;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,cAAc,GAAG,GAAG,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,eA6HvF;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG;IAC9C,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,eA8EF"}
@@ -220,6 +220,7 @@ function SideRailItem(props) {
220
220
  get when() {
221
221
  return tip();
222
222
  },
223
+ keyed: true,
223
224
  children: (position) => createComponent(Portal, { get children() {
224
225
  var _el$1 = _tmpl$10(), _el$10 = _el$1.firstChild;
225
226
  insert(_el$10, () => local.children);
@@ -235,7 +236,7 @@ function SideRailItem(props) {
235
236
  }
236
237
  }), null);
237
238
  effect((_p$) => {
238
- var _v$ = cn(tooltipTip, "pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap"), _v$2 = `${position().top}px`, _v$3 = `${position().left}px`, _v$4 = `${position().height}px`, _v$5 = cn("flex size-5 shrink-0 items-center justify-center [&_svg]:size-5", { "text-primary": local.active });
239
+ var _v$ = cn(tooltipTip, "pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap"), _v$2 = `${position.top}px`, _v$3 = `${position.left}px`, _v$4 = `${position.height}px`, _v$5 = cn("flex size-5 shrink-0 items-center justify-center [&_svg]:size-5", { "text-primary": local.active });
239
240
  _v$ !== _p$.e && className(_el$1, _p$.e = _v$);
240
241
  _v$2 !== _p$.t && setStyleProperty(_el$1, "top", _p$.t = _v$2);
241
242
  _v$3 !== _p$.a && setStyleProperty(_el$1, "left", _p$.a = _v$3);
@@ -310,12 +311,13 @@ function SideRailButton(props) {
310
311
  get when() {
311
312
  return tip();
312
313
  },
314
+ keyed: true,
313
315
  children: (position) => createComponent(Portal, { get children() {
314
316
  var _el$16 = _tmpl$12(), _el$17 = _el$16.firstChild;
315
317
  insert(_el$17, () => local.children);
316
318
  insert(_el$16, () => local.label, null);
317
319
  effect((_p$) => {
318
- var _v$6 = cn(tooltipTip, "pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap"), _v$7 = `${position().top}px`, _v$8 = `${position().left}px`, _v$9 = `${position().height}px`;
320
+ var _v$6 = cn(tooltipTip, "pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap"), _v$7 = `${position.top}px`, _v$8 = `${position.left}px`, _v$9 = `${position.height}px`;
319
321
  _v$6 !== _p$.e && className(_el$16, _p$.e = _v$6);
320
322
  _v$7 !== _p$.t && setStyleProperty(_el$16, "top", _p$.t = _v$7);
321
323
  _v$8 !== _p$.a && setStyleProperty(_el$16, "left", _p$.a = _v$8);
@@ -1 +1 @@
1
- {"version":3,"file":"side-rail.js","names":["Polymorphic","PolymorphicProps","ComponentProps","JSX","ValidComponent","Show","createMemo","createSignal","splitProps","Portal","tooltipTip","cn","SideRailContext","createSideRailValue","SideRailProps","T","class","collapsed","SideRail","props","local","rest","value","_$createComponent","Provider","children","_$mergeProps","as","aria-label","data-collapsed","SideRailHeader","_el$","_tmpl$","_$spread","SideRailContent","_el$2","_tmpl$2","SideRailFooter","_el$3","_tmpl$3","SideRailSection","label","_el$4","_tmpl$5","_$insert","when","_el$5","_tmpl$4","sideRailItemClass","join","sideRailItemStateClass","active","idle","SideRailItemProps","badge","Element","trailing","shortcut","keyshortcuts","SideRailItem","row","setRow","HTMLElement","tip","setTip","top","left","height","closest","getAttribute","showTip","element","rect","getBoundingClientRect","right","hideTip","ref","aria-current","undefined","aria-keyshortcuts","data-active","onPointerEnter","onPointerLeave","onFocus","onBlur","Record","_el$6","_tmpl$6","_el$7","_tmpl$7","_el$8","_tmpl$8","_$memo","_el$9","_tmpl$9","_el$0","_tmpl$0","position","_el$1","_tmpl$10","_el$10","firstChild","_el$11","_tmpl$1","_$effect","_p$","_v$","_v$2","_v$3","_v$4","_v$5","e","_$className","t","_$setStyleProperty","a","o","i","SideRailButton","Omit","_el$12","_tmpl$11","_el$13","_el$14","nextSibling","addEventListener","_$use","_el$15","_el$16","_tmpl$12","_el$17","_v$6","_v$7","_v$8","_v$9","_$createComponent","_$mergeProps","_$memo","_$className","_$setStyleProperty"],"sources":["../../../../src/components/layout/side-rail/side-rail.tsx"],"sourcesContent":["import { Polymorphic, type PolymorphicProps } from '@kobalte/core/polymorphic'\nimport type { ComponentProps, JSX, ValidComponent } from 'solid-js'\nimport { Show, createMemo, createSignal, splitProps } from 'solid-js'\nimport { Portal } from 'solid-js/web'\nimport { tooltipTip } from '#lib/overlay'\nimport { cn } from '#lib/utils'\nimport { SideRailContext, createSideRailValue } from './side-rail-context'\n\n/**\n * SideRail.\n *\n * The narrow, persistent column of top-level destinations at the window's leading\n * edge: the app mark, the primary sections, and a footer for account and settings.\n * It is the one piece of chrome that is always on screen, which is why it is a\n * component rather than something each view arranges.\n *\n * Two forms, one component. Collapsed it is an icon column; expanded it carries\n * labels. Both widths come from `--rail-width` (56px) and `--rail-width-expanded`\n * (236px), so an app cannot pick its own and end up a few pixels out of step with\n * the app beside it.\n *\n * The rail does not own its collapsed state; the app does. That state has to\n * survive a reload and usually lives with the rest of the window preferences, and\n * a rail that kept it privately could not be restored.\n */\nexport type SideRailProps<T extends ValidComponent = 'nav'> = PolymorphicProps<\n T,\n {\n class?: string\n collapsed?: boolean\n 'aria-label'?: string\n }\n>\n\nexport function SideRail<T extends ValidComponent = 'nav'>(props: SideRailProps<T>) {\n const [local, rest] = splitProps(props as SideRailProps, [\n 'class',\n 'collapsed',\n 'aria-label',\n 'children',\n ])\n\n const collapsed = () => local.collapsed ?? false\n const value = createSideRailValue(\n collapsed,\n createMemo(() => (collapsed() ? 'var(--rail-width)' : 'var(--rail-width-expanded)'))\n )\n\n return (\n <SideRailContext.Provider value={value}>\n <Polymorphic\n as=\"nav\"\n aria-label={local['aria-label'] ?? 'Primary'}\n /* Rendered in both states, with a real value, so the descendant selectors\n below can key on true *and* false — an absent attribute would leave the\n expanded form matching neither. */\n data-collapsed={collapsed() ? 'true' : 'false'}\n class={cn(\n 'group/rail bg-sidebar text-sidebar-foreground flex h-full shrink-0 flex-col border-e border-sidebar-border',\n 'transition-[width] duration-150 ease-out',\n { 'w-rail': collapsed(), 'w-rail-expanded': !collapsed() },\n local.class\n )}\n {...(rest as ComponentProps<'nav'>)}\n >\n {local.children}\n </Polymorphic>\n </SideRailContext.Provider>\n )\n}\n\n/** The header slot: the app mark, and the product name when there is room. */\nexport function SideRailHeader(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"side-rail-header\"\n class={cn(\n 'h-topbar flex shrink-0 items-center gap-2.5 px-3 group-data-[collapsed=true]/rail:justify-center group-data-[collapsed=true]/rail:px-0',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** The scrolling middle. Keeps the header and the footer pinned. */\nexport function SideRailContent(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"side-rail-content\"\n class={cn(\n 'flex min-h-0 flex-1 flex-col gap-0.5 overflow-x-hidden overflow-y-auto px-2 py-2',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** The pinned footer: account, settings, help. */\nexport function SideRailFooter(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"side-rail-footer\"\n class={cn(\n 'flex shrink-0 flex-col gap-0.5 border-t border-sidebar-border px-2 py-2',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** A titled group of rail destinations. The title hides when collapsed. */\nexport function SideRailSection(props: ComponentProps<'div'> & { label?: string }) {\n const [local, rest] = splitProps(props, ['class', 'label', 'children'])\n\n return (\n <div data-slot=\"side-rail-section\" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>\n <Show when={local.label}>\n <div class=\"text-sidebar-muted-foreground px-2 pt-2 pb-1 text-2xs font-medium tracking-wide uppercase group-data-[collapsed=true]/rail:hidden\">\n {local.label}\n </div>\n </Show>\n {local.children}\n </div>\n )\n}\n\n/**\n * The classes a rail row uses. Exported so a caller's own element — a router link,\n * a custom button — can match the rail exactly instead of approximating it.\n */\nexport const sideRailItemClass = [\n 'group/rail-item flex h-rail-item min-w-0 w-full items-center gap-2.5 rounded-md px-3 text-sm font-medium',\n 'group-data-[collapsed=true]/rail:justify-center group-data-[collapsed=true]/rail:px-0',\n 'transition-[color,background-color,transform] ease-out outline-none select-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n /*\n * The donor's row scales up slightly under the pointer and down under a press.\n * It is a CSS transform rather than a motion-library gesture because the two are\n * the same thing here and the transform costs nothing at runtime: no wrapper\n * element, no listener, and it works on whatever element a caller renders the row\n * as — an anchor, a button, a router's own link.\n *\n * The collapsed rail opts out: a 1.02 scale on a 56px column pushes the row past\n * the rail's own edge, which reads as a glitch rather than as feedback.\n */\n 'hover:scale-[1.02] active:scale-[0.97] group-data-[collapsed=true]/rail:hover:scale-100 group-data-[collapsed=true]/rail:active:scale-[0.98]',\n '[&_svg]:size-5 [&_svg]:shrink-0',\n].join(' ')\n\n/*\n * The rail's states are accent-driven: the icon carries `--primary` on hover and\n * on the active destination, and both states draw the accent's own tint rather\n * than a neutral fill, so the rail reads as the app's accent chroma instead of a\n * grey column. The active label stays on `sidebar-foreground` because the accent\n * presets guarantee their 3:1 interaction minimum against the surface, not the\n * 4.5:1 small text owes; the icon is a graphic and rides the accent.\n */\nexport const sideRailItemStateClass = {\n active: 'bg-primary-subtle text-sidebar-foreground [&_svg]:text-primary',\n idle: 'text-sidebar-muted-foreground hover:bg-primary-subtle/60 hover:text-primary',\n}\n\n/**\n * A rail destination.\n *\n * ## The tooltip is the rail's hover affordance, not a generic bubble\n *\n * Collapsed, the row has no text, so the label has to come from somewhere. This\n * ports the donor's treatment rather than using a standard tooltip, because the\n * details are what make it read as part of the rail:\n *\n * - It is **flush with the row**: positioned at the row's own top and given the\n * row's own height, so it continues the row outward instead of floating near\n * it. A centred bubble with an arrow reads as a separate object.\n * - It **repeats the icon**, which anchors it to the row it belongs to.\n * - It carries the **keyboard chord**, which is the one piece of information a\n * collapsed rail otherwise cannot show.\n * - It opens on **hover and on focus**, so a keyboard user gets the same label a\n * pointer user does.\n * - It is `pointer-events-none` and `aria-hidden`: the row already carries the\n * name (as a visually hidden span when collapsed), so this is decoration and\n * must never be the accessible name.\n *\n * ## The chord on the row\n *\n * Expanded, the row shows its chord on hover and on focus — and the chord is\n * declared once to assistive technology through `aria-keyshortcuts` rather than\n * being read out as glyphs. The visible badge is `aria-hidden` for that reason.\n */\nexport type SideRailItemProps<T extends ValidComponent = 'a'> = PolymorphicProps<\n T,\n {\n class?: string\n /** Marks the item as the current destination. */\n active?: boolean\n /**\n * The item's accessible name. Required, and always in the accessibility tree:\n * collapsed it moves into the tooltip and a visually hidden span rather than\n * being deleted.\n */\n label: string\n /**\n * A count or a dot. Expanded, it trails the label at the row's end; collapsed,\n * it moves onto the icon's corner, so the rail never loses it.\n */\n badge?: JSX.Element\n /** An indicator pinned to the trailing edge in the expanded form. */\n trailing?: JSX.Element\n /** The chord as it should be drawn, e.g. `⌘1`. Shown on hover and focus. */\n shortcut?: string\n /**\n * The same chord in the ARIA grammar, e.g. `Meta+1`. Declared to assistive\n * technology instead of the display glyphs, which are not a keyboard shortcut\n * a screen reader can parse. Supply it whenever `shortcut` is drawn.\n */\n keyshortcuts?: string\n }\n>\n\nexport function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItemProps<T>) {\n const [local, rest] = splitProps(props as SideRailItemProps, [\n 'as',\n 'class',\n 'active',\n 'label',\n 'badge',\n 'trailing',\n 'shortcut',\n 'keyshortcuts',\n 'children',\n ])\n\n const [row, setRow] = createSignal<HTMLElement>()\n const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)\n\n const collapsed = () =>\n row()?.closest('[data-collapsed]')?.getAttribute('data-collapsed') === 'true'\n\n const showTip = () => {\n const element = row()\n if (!element || !collapsed()) return\n const rect = element.getBoundingClientRect()\n setTip({ top: rect.top, left: rect.right + 8, height: rect.height })\n }\n\n const hideTip = () => setTip(null)\n\n return (\n <>\n <Polymorphic\n as={local.as ?? 'a'}\n ref={setRow}\n aria-current={local.active ? 'page' : undefined}\n aria-keyshortcuts={local.keyshortcuts}\n data-active={local.active ? '' : undefined}\n class={cn(\n sideRailItemClass,\n 'relative',\n local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,\n local.class\n )}\n onPointerEnter={showTip}\n onPointerLeave={hideTip}\n onFocus={showTip}\n onBlur={hideTip}\n {...(rest as Record<string, unknown>)}\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center [&_svg]:size-5\">\n {local.children}\n </span>\n\n <span class=\"min-w-0 flex-1 truncate text-start group-data-[collapsed=true]/rail:sr-only\">\n {local.label}\n </span>\n\n {/*\n * One element, two placements, because the badge is caller content and a DOM\n * node cannot be in two places. Expanded, it is a trailing count in the row's\n * flow, after the label — over the icon it would cover the glyph the row is\n * recognised by. Collapsed, there is no label to trail, so it leaves the flow\n * and pins to the icon's top-end corner: anchored to the row's centre (where\n * the centred icon sits) and pushed out by 14px, the icon's half-width plus\n * the 4px overhang, so a wider count grows back over the icon rather than past\n * the rail's edge.\n */}\n <Show when={local.badge}>\n <span\n data-slot=\"side-rail-badge\"\n class=\"flex shrink-0 items-center justify-center group-data-[collapsed=true]/rail:absolute group-data-[collapsed=true]/rail:end-1/2 group-data-[collapsed=true]/rail:top-1/2 group-data-[collapsed=true]/rail:-me-3.5 group-data-[collapsed=true]/rail:-mt-3.5\"\n >\n {local.badge}\n </span>\n </Show>\n\n <Show when={local.shortcut && local.trailing === undefined}>\n <span\n aria-hidden=\"true\"\n class=\"text-sidebar-muted-foreground shrink-0 font-code text-code leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden\"\n >\n {local.shortcut}\n </span>\n </Show>\n\n <Show when={local.trailing}>\n <span class=\"ms-auto shrink-0\">{local.trailing}</span>\n </Show>\n </Polymorphic>\n\n <Show when={tip()}>\n {(position) => (\n <Portal>\n <div\n aria-hidden=\"true\"\n data-slot=\"side-rail-tip\"\n class={cn(\n tooltipTip,\n 'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'\n )}\n style={{\n top: `${position().top}px`,\n left: `${position().left}px`,\n height: `${position().height}px`,\n }}\n >\n <span\n class={cn('flex size-5 shrink-0 items-center justify-center [&_svg]:size-5', {\n 'text-primary': local.active,\n })}\n >\n {local.children}\n </span>\n {local.label}\n <Show when={local.shortcut}>\n <span class=\"text-muted-foreground shrink-0 font-code text-code leading-none\">\n {local.shortcut}\n </span>\n </Show>\n </div>\n </Portal>\n )}\n </Show>\n </>\n )\n}\n\n/** A non-navigating rail control: a collapse toggle, a \"new\" action. */\nexport function SideRailButton(\n props: Omit<ComponentProps<'button'>, 'type'> & {\n label: string\n shortcut?: string\n active?: boolean\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'label', 'shortcut', 'children', 'active'])\n const [row, setRow] = createSignal<HTMLElement>()\n const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)\n\n const collapsed = () =>\n row()?.closest('[data-collapsed]')?.getAttribute('data-collapsed') === 'true'\n\n const showTip = () => {\n const element = row()\n if (!element || !collapsed()) return\n const rect = element.getBoundingClientRect()\n setTip({ top: rect.top, left: rect.right + 8, height: rect.height })\n }\n\n return (\n <>\n <button\n type=\"button\"\n ref={setRow}\n aria-label={local.label}\n data-active={local.active ? 'true' : undefined}\n onPointerEnter={showTip}\n onPointerLeave={() => setTip(null)}\n onFocus={showTip}\n onBlur={() => setTip(null)}\n class={cn(\n sideRailItemClass,\n local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,\n 'disabled:pointer-events-none disabled:opacity-50',\n local.class\n )}\n {...rest}\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center [&_svg]:size-5\">\n {local.children}\n </span>\n <span class=\"min-w-0 flex-1 truncate text-start group-data-[collapsed=true]/rail:sr-only\">\n {local.label}\n </span>\n <Show when={local.shortcut}>\n <span\n aria-hidden=\"true\"\n class=\"text-sidebar-muted-foreground shrink-0 font-code text-code leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden\"\n >\n {local.shortcut}\n </span>\n </Show>\n </button>\n\n <Show when={tip()}>\n {(position) => (\n <Portal>\n <div\n aria-hidden=\"true\"\n data-slot=\"side-rail-tip\"\n class={cn(\n tooltipTip,\n 'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'\n )}\n style={{\n top: `${position().top}px`,\n left: `${position().left}px`,\n height: `${position().height}px`,\n }}\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center [&_svg]:size-5\">\n {local.children}\n </span>\n {local.label}\n </div>\n </Portal>\n )}\n </Show>\n </>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"side-rail.js","names":["Polymorphic","PolymorphicProps","ComponentProps","JSX","ValidComponent","Show","createMemo","createSignal","splitProps","Portal","tooltipTip","cn","SideRailContext","createSideRailValue","SideRailProps","T","class","collapsed","SideRail","props","local","rest","value","_$createComponent","Provider","children","_$mergeProps","as","aria-label","data-collapsed","SideRailHeader","_el$","_tmpl$","_$spread","SideRailContent","_el$2","_tmpl$2","SideRailFooter","_el$3","_tmpl$3","SideRailSection","label","_el$4","_tmpl$5","_$insert","when","_el$5","_tmpl$4","sideRailItemClass","join","sideRailItemStateClass","active","idle","SideRailItemProps","badge","Element","trailing","shortcut","keyshortcuts","SideRailItem","row","setRow","HTMLElement","tip","setTip","top","left","height","closest","getAttribute","showTip","element","rect","getBoundingClientRect","right","hideTip","ref","aria-current","undefined","aria-keyshortcuts","data-active","onPointerEnter","onPointerLeave","onFocus","onBlur","Record","_el$6","_tmpl$6","_el$7","_tmpl$7","_el$8","_tmpl$8","_$memo","_el$9","_tmpl$9","_el$0","_tmpl$0","keyed","position","_el$1","_tmpl$10","_el$10","firstChild","_el$11","_tmpl$1","_$effect","_p$","_v$","_v$2","_v$3","_v$4","_v$5","e","_$className","t","_$setStyleProperty","a","o","i","SideRailButton","Omit","_el$12","_tmpl$11","_el$13","_el$14","nextSibling","addEventListener","_$use","_el$15","_el$16","_tmpl$12","_el$17","_v$6","_v$7","_v$8","_v$9","_$createComponent","_$mergeProps","_$memo","_$className","_$setStyleProperty"],"sources":["../../../../src/components/layout/side-rail/side-rail.tsx"],"sourcesContent":["import { Polymorphic, type PolymorphicProps } from '@kobalte/core/polymorphic'\nimport type { ComponentProps, JSX, ValidComponent } from 'solid-js'\nimport { Show, createMemo, createSignal, splitProps } from 'solid-js'\nimport { Portal } from 'solid-js/web'\nimport { tooltipTip } from '#lib/overlay'\nimport { cn } from '#lib/utils'\nimport { SideRailContext, createSideRailValue } from './side-rail-context'\n\n/**\n * SideRail.\n *\n * The narrow, persistent column of top-level destinations at the window's leading\n * edge: the app mark, the primary sections, and a footer for account and settings.\n * It is the one piece of chrome that is always on screen, which is why it is a\n * component rather than something each view arranges.\n *\n * Two forms, one component. Collapsed it is an icon column; expanded it carries\n * labels. Both widths come from `--rail-width` (56px) and `--rail-width-expanded`\n * (236px), so an app cannot pick its own and end up a few pixels out of step with\n * the app beside it.\n *\n * The rail does not own its collapsed state; the app does. That state has to\n * survive a reload and usually lives with the rest of the window preferences, and\n * a rail that kept it privately could not be restored.\n */\nexport type SideRailProps<T extends ValidComponent = 'nav'> = PolymorphicProps<\n T,\n {\n class?: string\n collapsed?: boolean\n 'aria-label'?: string\n }\n>\n\nexport function SideRail<T extends ValidComponent = 'nav'>(props: SideRailProps<T>) {\n const [local, rest] = splitProps(props as SideRailProps, [\n 'class',\n 'collapsed',\n 'aria-label',\n 'children',\n ])\n\n const collapsed = () => local.collapsed ?? false\n const value = createSideRailValue(\n collapsed,\n createMemo(() => (collapsed() ? 'var(--rail-width)' : 'var(--rail-width-expanded)'))\n )\n\n return (\n <SideRailContext.Provider value={value}>\n <Polymorphic\n as=\"nav\"\n aria-label={local['aria-label'] ?? 'Primary'}\n /* Rendered in both states, with a real value, so the descendant selectors\n below can key on true *and* false — an absent attribute would leave the\n expanded form matching neither. */\n data-collapsed={collapsed() ? 'true' : 'false'}\n class={cn(\n 'group/rail bg-sidebar text-sidebar-foreground flex h-full shrink-0 flex-col border-e border-sidebar-border',\n 'transition-[width] duration-150 ease-out',\n { 'w-rail': collapsed(), 'w-rail-expanded': !collapsed() },\n local.class\n )}\n {...(rest as ComponentProps<'nav'>)}\n >\n {local.children}\n </Polymorphic>\n </SideRailContext.Provider>\n )\n}\n\n/** The header slot: the app mark, and the product name when there is room. */\nexport function SideRailHeader(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"side-rail-header\"\n class={cn(\n 'h-topbar flex shrink-0 items-center gap-2.5 px-3 group-data-[collapsed=true]/rail:justify-center group-data-[collapsed=true]/rail:px-0',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** The scrolling middle. Keeps the header and the footer pinned. */\nexport function SideRailContent(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"side-rail-content\"\n class={cn(\n 'flex min-h-0 flex-1 flex-col gap-0.5 overflow-x-hidden overflow-y-auto px-2 py-2',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** The pinned footer: account, settings, help. */\nexport function SideRailFooter(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"side-rail-footer\"\n class={cn(\n 'flex shrink-0 flex-col gap-0.5 border-t border-sidebar-border px-2 py-2',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** A titled group of rail destinations. The title hides when collapsed. */\nexport function SideRailSection(props: ComponentProps<'div'> & { label?: string }) {\n const [local, rest] = splitProps(props, ['class', 'label', 'children'])\n\n return (\n <div data-slot=\"side-rail-section\" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>\n <Show when={local.label}>\n <div class=\"text-sidebar-muted-foreground px-2 pt-2 pb-1 text-2xs font-medium tracking-wide uppercase group-data-[collapsed=true]/rail:hidden\">\n {local.label}\n </div>\n </Show>\n {local.children}\n </div>\n )\n}\n\n/**\n * The classes a rail row uses. Exported so a caller's own element — a router link,\n * a custom button — can match the rail exactly instead of approximating it.\n */\nexport const sideRailItemClass = [\n 'group/rail-item flex h-rail-item min-w-0 w-full items-center gap-2.5 rounded-md px-3 text-sm font-medium',\n 'group-data-[collapsed=true]/rail:justify-center group-data-[collapsed=true]/rail:px-0',\n 'transition-[color,background-color,transform] ease-out outline-none select-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n /*\n * The donor's row scales up slightly under the pointer and down under a press.\n * It is a CSS transform rather than a motion-library gesture because the two are\n * the same thing here and the transform costs nothing at runtime: no wrapper\n * element, no listener, and it works on whatever element a caller renders the row\n * as — an anchor, a button, a router's own link.\n *\n * The collapsed rail opts out: a 1.02 scale on a 56px column pushes the row past\n * the rail's own edge, which reads as a glitch rather than as feedback.\n */\n 'hover:scale-[1.02] active:scale-[0.97] group-data-[collapsed=true]/rail:hover:scale-100 group-data-[collapsed=true]/rail:active:scale-[0.98]',\n '[&_svg]:size-5 [&_svg]:shrink-0',\n].join(' ')\n\n/*\n * The rail's states are accent-driven: the icon carries `--primary` on hover and\n * on the active destination, and both states draw the accent's own tint rather\n * than a neutral fill, so the rail reads as the app's accent chroma instead of a\n * grey column. The active label stays on `sidebar-foreground` because the accent\n * presets guarantee their 3:1 interaction minimum against the surface, not the\n * 4.5:1 small text owes; the icon is a graphic and rides the accent.\n */\nexport const sideRailItemStateClass = {\n active: 'bg-primary-subtle text-sidebar-foreground [&_svg]:text-primary',\n idle: 'text-sidebar-muted-foreground hover:bg-primary-subtle/60 hover:text-primary',\n}\n\n/**\n * A rail destination.\n *\n * ## The tooltip is the rail's hover affordance, not a generic bubble\n *\n * Collapsed, the row has no text, so the label has to come from somewhere. This\n * ports the donor's treatment rather than using a standard tooltip, because the\n * details are what make it read as part of the rail:\n *\n * - It is **flush with the row**: positioned at the row's own top and given the\n * row's own height, so it continues the row outward instead of floating near\n * it. A centred bubble with an arrow reads as a separate object.\n * - It **repeats the icon**, which anchors it to the row it belongs to.\n * - It carries the **keyboard chord**, which is the one piece of information a\n * collapsed rail otherwise cannot show.\n * - It opens on **hover and on focus**, so a keyboard user gets the same label a\n * pointer user does.\n * - It is `pointer-events-none` and `aria-hidden`: the row already carries the\n * name (as a visually hidden span when collapsed), so this is decoration and\n * must never be the accessible name.\n *\n * ## The chord on the row\n *\n * Expanded, the row shows its chord on hover and on focus — and the chord is\n * declared once to assistive technology through `aria-keyshortcuts` rather than\n * being read out as glyphs. The visible badge is `aria-hidden` for that reason.\n */\nexport type SideRailItemProps<T extends ValidComponent = 'a'> = PolymorphicProps<\n T,\n {\n class?: string\n /** Marks the item as the current destination. */\n active?: boolean\n /**\n * The item's accessible name. Required, and always in the accessibility tree:\n * collapsed it moves into the tooltip and a visually hidden span rather than\n * being deleted.\n */\n label: string\n /**\n * A count or a dot. Expanded, it trails the label at the row's end; collapsed,\n * it moves onto the icon's corner, so the rail never loses it.\n */\n badge?: JSX.Element\n /** An indicator pinned to the trailing edge in the expanded form. */\n trailing?: JSX.Element\n /** The chord as it should be drawn, e.g. `⌘1`. Shown on hover and focus. */\n shortcut?: string\n /**\n * The same chord in the ARIA grammar, e.g. `Meta+1`. Declared to assistive\n * technology instead of the display glyphs, which are not a keyboard shortcut\n * a screen reader can parse. Supply it whenever `shortcut` is drawn.\n */\n keyshortcuts?: string\n }\n>\n\nexport function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItemProps<T>) {\n const [local, rest] = splitProps(props as SideRailItemProps, [\n 'as',\n 'class',\n 'active',\n 'label',\n 'badge',\n 'trailing',\n 'shortcut',\n 'keyshortcuts',\n 'children',\n ])\n\n const [row, setRow] = createSignal<HTMLElement>()\n const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)\n\n const collapsed = () =>\n row()?.closest('[data-collapsed]')?.getAttribute('data-collapsed') === 'true'\n\n const showTip = () => {\n const element = row()\n if (!element || !collapsed()) return\n const rect = element.getBoundingClientRect()\n setTip({ top: rect.top, left: rect.right + 8, height: rect.height })\n }\n\n const hideTip = () => setTip(null)\n\n return (\n <>\n <Polymorphic\n as={local.as ?? 'a'}\n ref={setRow}\n aria-current={local.active ? 'page' : undefined}\n aria-keyshortcuts={local.keyshortcuts}\n data-active={local.active ? '' : undefined}\n class={cn(\n sideRailItemClass,\n 'relative',\n local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,\n local.class\n )}\n onPointerEnter={showTip}\n onPointerLeave={hideTip}\n onFocus={showTip}\n onBlur={hideTip}\n {...(rest as Record<string, unknown>)}\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center [&_svg]:size-5\">\n {local.children}\n </span>\n\n <span class=\"min-w-0 flex-1 truncate text-start group-data-[collapsed=true]/rail:sr-only\">\n {local.label}\n </span>\n\n {/*\n * One element, two placements, because the badge is caller content and a DOM\n * node cannot be in two places. Expanded, it is a trailing count in the row's\n * flow, after the label — over the icon it would cover the glyph the row is\n * recognised by. Collapsed, there is no label to trail, so it leaves the flow\n * and pins to the icon's top-end corner: anchored to the row's centre (where\n * the centred icon sits) and pushed out by 14px, the icon's half-width plus\n * the 4px overhang, so a wider count grows back over the icon rather than past\n * the rail's edge.\n */}\n <Show when={local.badge}>\n <span\n data-slot=\"side-rail-badge\"\n class=\"flex shrink-0 items-center justify-center group-data-[collapsed=true]/rail:absolute group-data-[collapsed=true]/rail:end-1/2 group-data-[collapsed=true]/rail:top-1/2 group-data-[collapsed=true]/rail:-me-3.5 group-data-[collapsed=true]/rail:-mt-3.5\"\n >\n {local.badge}\n </span>\n </Show>\n\n <Show when={local.shortcut && local.trailing === undefined}>\n <span\n aria-hidden=\"true\"\n class=\"text-sidebar-muted-foreground shrink-0 font-code text-code leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden\"\n >\n {local.shortcut}\n </span>\n </Show>\n\n <Show when={local.trailing}>\n <span class=\"ms-auto shrink-0\">{local.trailing}</span>\n </Show>\n </Polymorphic>\n\n {/* The portal can finish an effect after the tooltip closes; capture its position. */}\n <Show when={tip()} keyed>\n {(position) => (\n <Portal>\n <div\n aria-hidden=\"true\"\n data-slot=\"side-rail-tip\"\n class={cn(\n tooltipTip,\n 'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'\n )}\n style={{\n top: `${position.top}px`,\n left: `${position.left}px`,\n height: `${position.height}px`,\n }}\n >\n <span\n class={cn('flex size-5 shrink-0 items-center justify-center [&_svg]:size-5', {\n 'text-primary': local.active,\n })}\n >\n {local.children}\n </span>\n {local.label}\n <Show when={local.shortcut}>\n <span class=\"text-muted-foreground shrink-0 font-code text-code leading-none\">\n {local.shortcut}\n </span>\n </Show>\n </div>\n </Portal>\n )}\n </Show>\n </>\n )\n}\n\n/** A non-navigating rail control: a collapse toggle, a \"new\" action. */\nexport function SideRailButton(\n props: Omit<ComponentProps<'button'>, 'type'> & {\n label: string\n shortcut?: string\n active?: boolean\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'label', 'shortcut', 'children', 'active'])\n const [row, setRow] = createSignal<HTMLElement>()\n const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)\n\n const collapsed = () =>\n row()?.closest('[data-collapsed]')?.getAttribute('data-collapsed') === 'true'\n\n const showTip = () => {\n const element = row()\n if (!element || !collapsed()) return\n const rect = element.getBoundingClientRect()\n setTip({ top: rect.top, left: rect.right + 8, height: rect.height })\n }\n\n return (\n <>\n <button\n type=\"button\"\n ref={setRow}\n aria-label={local.label}\n data-active={local.active ? 'true' : undefined}\n onPointerEnter={showTip}\n onPointerLeave={() => setTip(null)}\n onFocus={showTip}\n onBlur={() => setTip(null)}\n class={cn(\n sideRailItemClass,\n local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,\n 'disabled:pointer-events-none disabled:opacity-50',\n local.class\n )}\n {...rest}\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center [&_svg]:size-5\">\n {local.children}\n </span>\n <span class=\"min-w-0 flex-1 truncate text-start group-data-[collapsed=true]/rail:sr-only\">\n {local.label}\n </span>\n <Show when={local.shortcut}>\n <span\n aria-hidden=\"true\"\n class=\"text-sidebar-muted-foreground shrink-0 font-code text-code leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden\"\n >\n {local.shortcut}\n </span>\n </Show>\n </button>\n\n {/* The portal can finish an effect after the tooltip closes; capture its position. */}\n <Show when={tip()} keyed>\n {(position) => (\n <Portal>\n <div\n aria-hidden=\"true\"\n data-slot=\"side-rail-tip\"\n class={cn(\n tooltipTip,\n 'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'\n )}\n style={{\n top: `${position.top}px`,\n left: `${position.left}px`,\n height: `${position.height}px`,\n }}\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center [&_svg]:size-5\">\n {local.children}\n </span>\n {local.label}\n </div>\n </Portal>\n )}\n </Show>\n </>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgBkB,SAA2CC,OAAyB;CAClF,MAAM,CAACC,OAAOC,QAAQb,WAAWW,OAAwB;EACvD;EACA;EACA;EACA;CAAU,CACX;CAED,MAAMF,kBAAkBG,MAAMH,aAAa;CAC3C,MAAMK,QAAQT,oBACZI,WACAX,iBAAkBW,UAAU,IAAI,sBAAsB,4BAA6B,CACrF;CAEA,OAAAM,gBACGX,gBAAgBY,UAAQ;EAAQF;EAAK,IAAAG,WAAA;GAAA,OAAAF,gBACnCvB,aAAW0B,WAAA;IACVC,IAAE;IAAA,KAAA,gBAAA;KAAA,OACUP,MAAM,iBAAiB;IAAS;IAAA,KAAA,oBAAA;KAAA,OAI5BH,UAAU,IAAI,SAAS;IAAO;IAAA,KAAA,WAAA;KAAA,OACvCN,GACL,8GACA,4CACA;MAAE,UAAUM,UAAU;MAAG,mBAAmB,CAACA,UAAU;KAAE,GACzDG,MAAMJ,KACR;IAAC;GAAA,GACIK,MAA6B,EAAA,IAAAI,WAAA;IAAA,OAEjCL,MAAMK;GAAQ,EAAA,CAAA,CAAA;EAAA;CAAA,CAAA;AAIvB;;AAGA,SAAgBK,eAAeX,OAA8B;CAC3D,MAAM,CAACC,OAAOC,QAAQb,WAAWW,OAAO,CAAC,OAAO,CAAC;CACjD,cAAA;EAAA,IAAAY,OAAAC,OAAA;EAAAC,OAAAF,MAAAL,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWf,GACL,0IACAS,MAAMJ,KACR;EAAC,EAAA,GACGK,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAU;CAAA,EAAA,CAAA;AAGd;;AAGA,SAAgBG,gBAAgBf,OAA8B;CAC5D,MAAM,CAACC,OAAOC,QAAQb,WAAWW,OAAO,CAAC,OAAO,CAAC;CACjD,cAAA;EAAA,IAAAgB,QAAAC,QAAA;EAAAH,OAAAE,OAAAT,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWf,GACL,oFACAS,MAAMJ,KACR;EAAC,EAAA,GACGK,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAc;CAAA,EAAA,CAAA;AAGd;;AAGA,SAAgBE,eAAelB,OAA8B;CAC3D,MAAM,CAACC,OAAOC,QAAQb,WAAWW,OAAO,CAAC,OAAO,CAAC;CACjD,cAAA;EAAA,IAAAmB,QAAAC,QAAA;EAAAN,OAAAK,OAAAZ,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWf,GACL,2EACAS,MAAMJ,KACR;EAAC,EAAA,GACGK,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAiB;CAAA,EAAA,CAAA;AAGd;;AAGA,SAAgBE,gBAAgBrB,OAAmD;CACjF,MAAM,CAACC,OAAOC,QAAQb,WAAWW,OAAO;EAAC;EAAS;EAAS;CAAU,CAAC;CAEtE,cAAA;EAAA,IAAAuB,QAAAC,QAAA;EAAAV,OAAAS,OAAAhB,WAAA,EAAA,KAAA,WAAA;GAAA,OAC4Cf,GAAG,yBAAyBS,MAAMJ,KAAK;EAAC,EAAA,GAAMK,IAAI,GAAA,OAAA,IAAA;EAAAuB,OAAAF,OAAAnB,gBACzFlB,MAAI;GAAA,IAACwC,OAAI;IAAA,OAAEzB,MAAMqB;GAAK;GAAA,IAAAhB,WAAA;IAAA,IAAAqB,QAAAC,QAAA;IAAAH,OAAAE,aAElB1B,MAAMqB,KAAK;IAAA,OAAAK;GAAA;EAAA,CAAA,GAAA,IAAA;EAAAF,OAAAF,aAGftB,MAAMK,UAAQ,IAAA;EAAA,OAAAiB;CAAA,EAAA,CAAA;AAGrB;;;;;AAMA,IAAaM,oBAAoB;CAC/B;CACA;CACA;CACA;CAWA;CACA;AAAiC,CAClC,CAACC,KAAK,GAAG;AAUV,IAAaC,yBAAyB;CACpCC,QAAQ;CACRC,MAAM;AACR;AA2DA,SAAgBO,aAA6CxC,OAA6B;CACxF,MAAM,CAACC,OAAOC,QAAQb,WAAWW,OAA4B;EAC3D;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CAED,MAAM,CAACyC,KAAKC,UAAUtD,aAA0B;CAChD,MAAM,CAACwD,KAAKC,UAAUzD,aAAmE,IAAI;CAE7F,MAAMU,kBACJ2C,IAAI,CAAC,EAAEQ,QAAQ,kBAAkB,CAAC,EAAEC,aAAa,gBAAgB,MAAM;CAEzE,MAAMC,gBAAgB;EACpB,MAAMC,UAAUX,IAAI;EACpB,IAAI,CAACW,WAAW,CAACtD,UAAU,GAAG;EAC9B,MAAMuD,OAAOD,QAAQE,sBAAsB;EAC3CT,OAAO;GAAEC,KAAKO,KAAKP;GAAKC,MAAMM,KAAKE,QAAQ;GAAGP,QAAQK,KAAKL;EAAO,CAAC;CACrE;CAEA,MAAMQ,gBAAgBX,OAAO,IAAI;CAEjC,OAAA,CAAAzC,gBAEKvB,aAAW0B,WAAA;EAAA,IACVC,KAAE;GAAA,OAAEP,MAAMO,MAAM;EAAG;EAAAiD,KACdf;EAAM,KAAA,kBAAA;GAAA,OACGzC,MAAM+B,SAAS,SAAS2B,KAAAA;EAAS;EAAA,KAAA,uBAAA;GAAA,OAC5B1D,MAAMsC;EAAY;EAAA,KAAA,iBAAA;GAAA,OACxBtC,MAAM+B,SAAS,KAAK2B,KAAAA;EAAS;EAAA,KAAA,WAAA;GAAA,OACnCnE,GACLqC,mBACA,YACA5B,MAAM+B,SAASD,uBAAuBC,SAASD,uBAAuBE,MACtEhC,MAAMJ,KACR;EAAC;EACDiE,gBAAgBX;EAChBY,gBAAgBP;EAChBQ,SAASb;EACTc,QAAQT;CAAO,GACVtD,MAA+B,EAAA,IAAAI,WAAA;EAAA,OAAA;UAAA;IAAA,IAAA6D,QAAAC,QAAA;IAAA3C,OAAA0C,aAGjClE,MAAMK,QAAQ;IAAA,OAAA6D;GAAA,EAAA,CAAA;UAAA;IAAA,IAAAE,QAAAC,QAAA;IAAA7C,OAAA4C,aAIdpE,MAAMqB,KAAK;IAAA,OAAA+C;GAAA,EAAA,CAAA;GAAAjE,gBAablB,MAAI;IAAA,IAACwC,OAAI;KAAA,OAAEzB,MAAMkC;IAAK;IAAA,IAAA7B,WAAA;KAAA,IAAAiE,QAAAC,QAAA;KAAA/C,OAAA8C,aAKlBtE,MAAMkC,KAAK;KAAA,OAAAoC;IAAA;GAAA,CAAA;GAAAnE,gBAIflB,MAAI;IAAA,IAACwC,OAAI;KAAA,OAAE+C,WAAA,CAAA,CAAAxE,MAAMqC,QAAQ,CAAA,CAAA,KAAIrC,MAAMoC,aAAasB,KAAAA;IAAS;IAAA,IAAArD,WAAA;KAAA,IAAAoE,QAAAC,QAAA;KAAAlD,OAAAiD,aAKrDzE,MAAMqC,QAAQ;KAAA,OAAAoC;IAAA;GAAA,CAAA;GAAAtE,gBAIlBlB,MAAI;IAAA,IAACwC,OAAI;KAAA,OAAEzB,MAAMoC;IAAQ;IAAA,IAAA/B,WAAA;KAAA,IAAAsE,QAAAC,QAAA;KAAApD,OAAAmD,aACQ3E,MAAMoC,QAAQ;KAAA,OAAAuC;IAAA;GAAA,CAAA;EAAA;CAAA,EAAA,CAAA,CAAA,GAAAxE,gBAKjDlB,MAAI;EAAA,IAACwC,OAAI;GAAA,OAAEkB,IAAI;EAAC;EAAEkC,OAAK;EAAAxE,WACpByE,aAAQ3E,gBACPd,QAAM,EAAA,IAAAgB,WAAA;GAAA,IAAA0E,QAAAC,SAAA,GAAAC,SAAAF,MAAAG;GAAA1D,OAAAyD,cAmBAjF,MAAMK,QAAQ;GAAAmB,OAAAuD,aAEhB/E,MAAMqB,OAAK,IAAA;GAAAG,OAAAuD,OAAA5E,gBACXlB,MAAI;IAAA,IAACwC,OAAI;KAAA,OAAEzB,MAAMqC;IAAQ;IAAA,IAAAhC,WAAA;KAAA,IAAA8E,SAAAC,QAAA;KAAA5D,OAAA2D,cAErBnF,MAAMqC,QAAQ;KAAA,OAAA8C;IAAA;GAAA,CAAA,GAAA,IAAA;GAAAE,QAAAC,QAAA;IAAA,IAAAC,MApBZhG,GACLD,YACA,iGACF,GAACkG,OAEM,GAAGV,SAASjC,IAAG,KAAI4C,OAClB,GAAGX,SAAShC,KAAI,KAAI4C,OAClB,GAAGZ,SAAS/B,OAAM,KAAI4C,OAIvBpG,GAAG,mEAAmE,EAC3E,gBAAgBS,MAAM+B,OACxB,CAAC;IAACwD,QAAAD,IAAAM,KAAAC,UAAAd,OAAAO,IAAAM,IAAAL,GAAA;IAAAC,SAAAF,IAAAQ,KAAAC,iBAAAhB,OAAA,OAAAO,IAAAQ,IAAAN,IAAA;IAAAC,SAAAH,IAAAU,KAAAD,iBAAAhB,OAAA,QAAAO,IAAAU,IAAAP,IAAA;IAAAC,SAAAJ,IAAAW,KAAAF,iBAAAhB,OAAA,UAAAO,IAAAW,IAAAP,IAAA;IAAAC,SAAAL,IAAAY,KAAAL,UAAAZ,QAAAK,IAAAY,IAAAP,IAAA;IAAA,OAAAL;GAAA,GAAA;IAAAM,GAAAlC,KAAAA;IAAAoC,GAAApC,KAAAA;IAAAsC,GAAAtC,KAAAA;IAAAuC,GAAAvC,KAAAA;IAAAwC,GAAAxC,KAAAA;GAAA,CAAA;GAAA,OAAAqB;EAAA,EAAA,CAAA;CAYT,CAAA,CAAA;AAIT;;AAGA,SAAgBoB,eACdpG,OAKA;CACA,MAAM,CAACC,OAAOC,QAAQb,WAAWW,OAAO;EAAC;EAAS;EAAS;EAAY;EAAY;CAAQ,CAAC;CAC5F,MAAM,CAACyC,KAAKC,UAAUtD,aAA0B;CAChD,MAAM,CAACwD,KAAKC,UAAUzD,aAAmE,IAAI;CAE7F,MAAMU,kBACJ2C,IAAI,CAAC,EAAEQ,QAAQ,kBAAkB,CAAC,EAAEC,aAAa,gBAAgB,MAAM;CAEzE,MAAMC,gBAAgB;EACpB,MAAMC,UAAUX,IAAI;EACpB,IAAI,CAACW,WAAW,CAACtD,UAAU,GAAG;EAC9B,MAAMuD,OAAOD,QAAQE,sBAAsB;EAC3CT,OAAO;GAAEC,KAAKO,KAAKP;GAAKC,MAAMM,KAAKE,QAAQ;GAAGP,QAAQK,KAAKL;EAAO,CAAC;CACrE;CAEA,OAAA,QAAA;EAAA,IAAAsD,SAAAC,SAAA,GAAAC,SAAAF,OAAAnB,YAAAsB,SAAAD,OAAAE;EAAAJ,OAAAK,iBAAA,cAUoB9D,OAAO,IAAI,CAAC;EAAAyD,OAAAK,iBAAA,SADjBxD,OAAO;EAAAmD,OAAAK,iBAAA,sBADM9D,OAAO,IAAI,CAAC;EAAAyD,OAAAK,iBAAA,gBADlBxD,OAAO;EAAAyD,IAHlBlE,QAAM4D,MAAA;EAAAxF,OAAAwF,QAAA/F,WAAA;GAAA,KAAA,gBAAA;IAAA,OACCN,MAAMqB;GAAK;GAAA,KAAA,iBAAA;IAAA,OACVrB,MAAM+B,SAAS,SAAS2B,KAAAA;GAAS;GAAA,KAAA,WAAA;IAAA,OAKvCnE,GACLqC,mBACA5B,MAAM+B,SAASD,uBAAuBC,SAASD,uBAAuBE,MACtE,oDACAhC,MAAMJ,KACR;GAAC;EAAA,GACGK,IAAI,GAAA,OAAA,IAAA;EAAAuB,OAAA+E,cAGLvG,MAAMK,QAAQ;EAAAmB,OAAAgF,cAGdxG,MAAMqB,KAAK;EAAAG,OAAA6E,QAAAlG,gBAEblB,MAAI;GAAA,IAACwC,OAAI;IAAA,OAAEzB,MAAMqC;GAAQ;GAAA,IAAAhC,WAAA;IAAA,IAAAuG,SAAAlC,QAAA;IAAAlD,OAAAoF,cAKrB5G,MAAMqC,QAAQ;IAAA,OAAAuE;GAAA;EAAA,CAAA,GAAA,IAAA;EAAA,OAAAP;CAAA,EAAA,CAAA,GAAAlG,gBAMpBlB,MAAI;EAAA,IAACwC,OAAI;GAAA,OAAEkB,IAAI;EAAC;EAAEkC,OAAK;EAAAxE,WACpByE,aAAQ3E,gBACPd,QAAM,EAAA,IAAAgB,WAAA;GAAA,IAAAwG,SAAAC,SAAA,GAAAC,SAAAF,OAAA3B;GAAA1D,OAAAuF,cAeA/G,MAAMK,QAAQ;GAAAmB,OAAAqF,cAEhB7G,MAAMqB,OAAK,IAAA;GAAAgE,QAAAC,QAAA;IAAA,IAAA0B,OAbLzH,GACLD,YACA,iGACF,GAAC2H,OAEM,GAAGnC,SAASjC,IAAG,KAAIqE,OAClB,GAAGpC,SAAShC,KAAI,KAAIqE,OAClB,GAAGrC,SAAS/B,OAAM;IAAIiE,SAAA1B,IAAAM,KAAAC,UAAAgB,QAAAvB,IAAAM,IAAAoB,IAAA;IAAAC,SAAA3B,IAAAQ,KAAAC,iBAAAc,QAAA,OAAAvB,IAAAQ,IAAAmB,IAAA;IAAAC,SAAA5B,IAAAU,KAAAD,iBAAAc,QAAA,QAAAvB,IAAAU,IAAAkB,IAAA;IAAAC,SAAA7B,IAAAW,KAAAF,iBAAAc,QAAA,UAAAvB,IAAAW,IAAAkB,IAAA;IAAA,OAAA7B;GAAA,GAAA;IAAAM,GAAAlC,KAAAA;IAAAoC,GAAApC,KAAAA;IAAAsC,GAAAtC,KAAAA;IAAAuC,GAAAvC,KAAAA;GAAA,CAAA;GAAA,OAAAmD;EAAA,EAAA,CAAA;CASrC,CAAA,CAAA;AAIT"}
@@ -98,6 +98,8 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
98
98
  const [reorderDialog, setReorderDialog] = createSignal<ReorderDialog>()
99
99
  const [reorderFailed, setReorderFailed] = createSignal(false)
100
100
  let cancelPending: (() => void) | undefined
101
+ let openPending: (() => void) | undefined
102
+ let menuClosed = true
101
103
  let trigger: HTMLElement | undefined
102
104
  const current = () => props.workspaces.find((item) => item.id === props.value)
103
105
  const label = () => props.label ?? 'Workspaces'
@@ -120,6 +122,12 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
120
122
  )
121
123
  // Warm the chunk while the menu is open, so choosing the item rarely waits.
122
124
  const warm = (next: boolean) => {
125
+ if (next) {
126
+ // Reopening is new intent, including keyboard activation: an older
127
+ // delayed reorder must not open after another workspace or action wins.
128
+ cancelPending?.()
129
+ menuClosed = false
130
+ }
123
131
  setOpen(next)
124
132
  if (next && canReorder() && !reorderDialog()) void loadDialog()
125
133
  }
@@ -128,16 +136,19 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
128
136
  // of them withdraws the request, and the dialog opens only if the user waits.
129
137
  const requestReorder = () => {
130
138
  cancelPending?.()
131
- if (reorderDialog()) {
132
- setReordering(true)
133
- return
134
- }
135
139
  let waiting = true
140
+ let loaded = !!reorderDialog()
136
141
  const stop = () => {
137
142
  waiting = false
138
143
  document.removeEventListener('keydown', onKey, true)
139
144
  document.removeEventListener('pointerdown', stop, true)
140
145
  if (cancelPending === stop) cancelPending = undefined
146
+ if (openPending === openWhenReady) openPending = undefined
147
+ }
148
+ const openWhenReady = () => {
149
+ if (!waiting || !loaded || !menuClosed) return
150
+ stop()
151
+ setReordering(true)
141
152
  }
142
153
  const onKey = (event: KeyboardEvent) => {
143
154
  if (event.key === 'Escape') stop()
@@ -145,11 +156,16 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
145
156
  document.addEventListener('keydown', onKey, true)
146
157
  document.addEventListener('pointerdown', stop, true)
147
158
  cancelPending = stop
148
- void loadDialog().then((loaded) => {
149
- const stillWanted = waiting
150
- stop()
151
- if (loaded && stillWanted) setReordering(true)
152
- })
159
+ openPending = openWhenReady
160
+ if (loaded) openWhenReady()
161
+ else
162
+ void loadDialog().then((success) => {
163
+ if (!success) stop()
164
+ else {
165
+ loaded = true
166
+ openWhenReady()
167
+ }
168
+ })
153
169
  }
154
170
  onCleanup(() => cancelPending?.())
155
171
 
@@ -175,7 +191,18 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
175
191
  </span>
176
192
  <ChevronDown aria-hidden="true" class="size-3.5 shrink-0 text-muted-foreground" />
177
193
  </DropdownMenuTrigger>
178
- <DropdownMenuContent align="start" sideOffset={6} class="min-w-56">
194
+ <DropdownMenuContent
195
+ align="start"
196
+ sideOffset={6}
197
+ class="min-w-56"
198
+ onCloseAutoFocus={() => {
199
+ if (open()) return
200
+ menuClosed = true
201
+ // Kobalte restores the menu trigger and removes its focus scope
202
+ // after this callback. Open the dialog only once that work ends.
203
+ queueMicrotask(() => openPending?.())
204
+ }}
205
+ >
179
206
  <DropdownMenuGroup>
180
207
  <DropdownMenuLabel>{label()}</DropdownMenuLabel>
181
208
  <DropdownMenuRadioGroup
@@ -312,7 +312,8 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
312
312
  </Show>
313
313
  </Polymorphic>
314
314
 
315
- <Show when={tip()}>
315
+ {/* The portal can finish an effect after the tooltip closes; capture its position. */}
316
+ <Show when={tip()} keyed>
316
317
  {(position) => (
317
318
  <Portal>
318
319
  <div
@@ -323,9 +324,9 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
323
324
  'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'
324
325
  )}
325
326
  style={{
326
- top: `${position().top}px`,
327
- left: `${position().left}px`,
328
- height: `${position().height}px`,
327
+ top: `${position.top}px`,
328
+ left: `${position.left}px`,
329
+ height: `${position.height}px`,
329
330
  }}
330
331
  >
331
332
  <span
@@ -406,7 +407,8 @@ export function SideRailButton(
406
407
  </Show>
407
408
  </button>
408
409
 
409
- <Show when={tip()}>
410
+ {/* The portal can finish an effect after the tooltip closes; capture its position. */}
411
+ <Show when={tip()} keyed>
410
412
  {(position) => (
411
413
  <Portal>
412
414
  <div
@@ -417,9 +419,9 @@ export function SideRailButton(
417
419
  'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'
418
420
  )}
419
421
  style={{
420
- top: `${position().top}px`,
421
- left: `${position().left}px`,
422
- height: `${position().height}px`,
422
+ top: `${position.top}px`,
423
+ left: `${position.left}px`,
424
+ height: `${position.height}px`,
423
425
  }}
424
426
  >
425
427
  <span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.147.0",
3
+ "version": "0.147.2",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -98,6 +98,8 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
98
98
  const [reorderDialog, setReorderDialog] = createSignal<ReorderDialog>()
99
99
  const [reorderFailed, setReorderFailed] = createSignal(false)
100
100
  let cancelPending: (() => void) | undefined
101
+ let openPending: (() => void) | undefined
102
+ let menuClosed = true
101
103
  let trigger: HTMLElement | undefined
102
104
  const current = () => props.workspaces.find((item) => item.id === props.value)
103
105
  const label = () => props.label ?? 'Workspaces'
@@ -120,6 +122,12 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
120
122
  )
121
123
  // Warm the chunk while the menu is open, so choosing the item rarely waits.
122
124
  const warm = (next: boolean) => {
125
+ if (next) {
126
+ // Reopening is new intent, including keyboard activation: an older
127
+ // delayed reorder must not open after another workspace or action wins.
128
+ cancelPending?.()
129
+ menuClosed = false
130
+ }
123
131
  setOpen(next)
124
132
  if (next && canReorder() && !reorderDialog()) void loadDialog()
125
133
  }
@@ -128,16 +136,19 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
128
136
  // of them withdraws the request, and the dialog opens only if the user waits.
129
137
  const requestReorder = () => {
130
138
  cancelPending?.()
131
- if (reorderDialog()) {
132
- setReordering(true)
133
- return
134
- }
135
139
  let waiting = true
140
+ let loaded = !!reorderDialog()
136
141
  const stop = () => {
137
142
  waiting = false
138
143
  document.removeEventListener('keydown', onKey, true)
139
144
  document.removeEventListener('pointerdown', stop, true)
140
145
  if (cancelPending === stop) cancelPending = undefined
146
+ if (openPending === openWhenReady) openPending = undefined
147
+ }
148
+ const openWhenReady = () => {
149
+ if (!waiting || !loaded || !menuClosed) return
150
+ stop()
151
+ setReordering(true)
141
152
  }
142
153
  const onKey = (event: KeyboardEvent) => {
143
154
  if (event.key === 'Escape') stop()
@@ -145,11 +156,16 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
145
156
  document.addEventListener('keydown', onKey, true)
146
157
  document.addEventListener('pointerdown', stop, true)
147
158
  cancelPending = stop
148
- void loadDialog().then((loaded) => {
149
- const stillWanted = waiting
150
- stop()
151
- if (loaded && stillWanted) setReordering(true)
152
- })
159
+ openPending = openWhenReady
160
+ if (loaded) openWhenReady()
161
+ else
162
+ void loadDialog().then((success) => {
163
+ if (!success) stop()
164
+ else {
165
+ loaded = true
166
+ openWhenReady()
167
+ }
168
+ })
153
169
  }
154
170
  onCleanup(() => cancelPending?.())
155
171
 
@@ -175,7 +191,18 @@ export function WorkspaceSwitcher(props: WorkspaceSwitcherProps) {
175
191
  </span>
176
192
  <ChevronDown aria-hidden="true" class="size-3.5 shrink-0 text-muted-foreground" />
177
193
  </DropdownMenuTrigger>
178
- <DropdownMenuContent align="start" sideOffset={6} class="min-w-56">
194
+ <DropdownMenuContent
195
+ align="start"
196
+ sideOffset={6}
197
+ class="min-w-56"
198
+ onCloseAutoFocus={() => {
199
+ if (open()) return
200
+ menuClosed = true
201
+ // Kobalte restores the menu trigger and removes its focus scope
202
+ // after this callback. Open the dialog only once that work ends.
203
+ queueMicrotask(() => openPending?.())
204
+ }}
205
+ >
179
206
  <DropdownMenuGroup>
180
207
  <DropdownMenuLabel>{label()}</DropdownMenuLabel>
181
208
  <DropdownMenuRadioGroup
@@ -312,7 +312,8 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
312
312
  </Show>
313
313
  </Polymorphic>
314
314
 
315
- <Show when={tip()}>
315
+ {/* The portal can finish an effect after the tooltip closes; capture its position. */}
316
+ <Show when={tip()} keyed>
316
317
  {(position) => (
317
318
  <Portal>
318
319
  <div
@@ -323,9 +324,9 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
323
324
  'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'
324
325
  )}
325
326
  style={{
326
- top: `${position().top}px`,
327
- left: `${position().left}px`,
328
- height: `${position().height}px`,
327
+ top: `${position.top}px`,
328
+ left: `${position.left}px`,
329
+ height: `${position.height}px`,
329
330
  }}
330
331
  >
331
332
  <span
@@ -406,7 +407,8 @@ export function SideRailButton(
406
407
  </Show>
407
408
  </button>
408
409
 
409
- <Show when={tip()}>
410
+ {/* The portal can finish an effect after the tooltip closes; capture its position. */}
411
+ <Show when={tip()} keyed>
410
412
  {(position) => (
411
413
  <Portal>
412
414
  <div
@@ -417,9 +419,9 @@ export function SideRailButton(
417
419
  'pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 ps-3 pe-3 whitespace-nowrap'
418
420
  )}
419
421
  style={{
420
- top: `${position().top}px`,
421
- left: `${position().left}px`,
422
- height: `${position().height}px`,
422
+ top: `${position.top}px`,
423
+ left: `${position.left}px`,
424
+ height: `${position.height}px`,
423
425
  }}
424
426
  >
425
427
  <span class="flex size-5 shrink-0 items-center justify-center [&_svg]:size-5">