@adea-ai/ui 0.147.1 → 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.
- package/dist/components/composites/workspace-switcher/workspace-switcher.d.ts.map +1 -1
- package/dist/components/composites/workspace-switcher/workspace-switcher.js +26 -8
- package/dist/components/composites/workspace-switcher/workspace-switcher.js.map +1 -1
- package/dist/r/src/components/composites/workspace-switcher/workspace-switcher.tsx +37 -10
- package/package.json +1 -1
- package/src/components/composites/workspace-switcher/workspace-switcher.tsx +37 -10
|
@@ -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,
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
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
|
@@ -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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
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
|