@adea-ai/ui 0.147.1 → 0.147.3

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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"}
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.147.1",
3
+ "version": "0.147.3",
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