@adea-ai/ui 0.138.0 → 0.139.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/composites/tree/tree.d.ts +8 -2
- package/dist/components/composites/tree/tree.d.ts.map +1 -1
- package/dist/components/composites/tree/tree.js +63 -8
- package/dist/components/composites/tree/tree.js.map +1 -1
- package/dist/components/layout/top-bar/top-bar.d.ts +13 -0
- package/dist/components/layout/top-bar/top-bar.d.ts.map +1 -1
- package/dist/components/layout/top-bar/top-bar.js +3 -2
- package/dist/components/layout/top-bar/top-bar.js.map +1 -1
- package/dist/components/ui/callout/callout.d.ts +7 -6
- package/dist/components/ui/callout/callout.d.ts.map +1 -1
- package/dist/components/ui/callout/callout.js +31 -16
- package/dist/components/ui/callout/callout.js.map +1 -1
- package/dist/components/ui/input-group/secret-input-group.d.ts.map +1 -1
- package/dist/components/ui/input-group/secret-input-group.js +24 -23
- package/dist/components/ui/input-group/secret-input-group.js.map +1 -1
- package/dist/r/src/components/composites/tree/tree.tsx +90 -11
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +16 -0
- package/dist/r/src/components/ui/callout/callout.tsx +31 -10
- package/dist/r/src/components/ui/input-group/secret-input-group.tsx +23 -20
- package/package.json +1 -1
- package/src/components/composites/tree/tree.tsx +90 -11
- package/src/components/layout/top-bar/top-bar.tsx +16 -0
- package/src/components/ui/callout/callout.tsx +31 -10
- package/src/components/ui/input-group/secret-input-group.tsx +23 -20
|
@@ -72,8 +72,14 @@ export type TreeProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'tabIn
|
|
|
72
72
|
* the target row: when the active row leaves `visibleItems`, the first visible
|
|
73
73
|
* row becomes the roving stop and `onActiveIdChange` is called with it. If the
|
|
74
74
|
* removed row held focus, or was the last row the tree saw focused, focus moves
|
|
75
|
-
* to that same row; if it had no focus, only the roving stop moves.
|
|
76
|
-
*
|
|
75
|
+
* to that same row; if it had no focus, only the roving stop moves. Focus that
|
|
76
|
+
* has already gone to a control outside the tree is never pulled back: the tree
|
|
77
|
+
* forgets its last focused row when focus moves to an element outside it, and
|
|
78
|
+
* the roving stop alone moves if that control still has focus when the row is
|
|
79
|
+
* hidden. Focus the tree has asked for but not yet moved (a queued recovery, or a
|
|
80
|
+
* keyboard move to a row the host has not mounted) is withdrawn when focus lands
|
|
81
|
+
* outside the tree, and is checked again when a row mounts. This is the same in
|
|
82
|
+
* both `disclosure` modes and for every way a host collapses a row.
|
|
77
83
|
*/
|
|
78
84
|
export declare function Tree(props: TreeProps): JSX.Element;
|
|
79
85
|
export type TreeRowProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'tabIndex' | 'onClick' | 'onFocus' | 'onDoubleClick' | 'style' | 'aria-level' | 'aria-expanded' | 'aria-selected' | 'aria-posinset' | 'aria-setsize'> & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/tree/tree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAYnD,OAAO,EAGL,KAAK,kBAAkB,EAExB,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/tree/tree.tsx"],"names":[],"mappings":"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"}
|
|
@@ -38,8 +38,14 @@ function useTreeContext() {
|
|
|
38
38
|
* the target row: when the active row leaves `visibleItems`, the first visible
|
|
39
39
|
* row becomes the roving stop and `onActiveIdChange` is called with it. If the
|
|
40
40
|
* removed row held focus, or was the last row the tree saw focused, focus moves
|
|
41
|
-
* to that same row; if it had no focus, only the roving stop moves.
|
|
42
|
-
*
|
|
41
|
+
* to that same row; if it had no focus, only the roving stop moves. Focus that
|
|
42
|
+
* has already gone to a control outside the tree is never pulled back: the tree
|
|
43
|
+
* forgets its last focused row when focus moves to an element outside it, and
|
|
44
|
+
* the roving stop alone moves if that control still has focus when the row is
|
|
45
|
+
* hidden. Focus the tree has asked for but not yet moved (a queued recovery, or a
|
|
46
|
+
* keyboard move to a row the host has not mounted) is withdrawn when focus lands
|
|
47
|
+
* outside the tree, and is checked again when a row mounts. This is the same in
|
|
48
|
+
* both `disclosure` modes and for every way a host collapses a row.
|
|
43
49
|
*/
|
|
44
50
|
function Tree(props) {
|
|
45
51
|
const [local, rest] = splitProps(props, [
|
|
@@ -66,9 +72,19 @@ function Tree(props) {
|
|
|
66
72
|
const rows = /* @__PURE__ */ new Map();
|
|
67
73
|
const rowIdsByElement = /* @__PURE__ */ new WeakMap();
|
|
68
74
|
let pendingFocusId = null;
|
|
75
|
+
let pendingFocusGuard;
|
|
69
76
|
let requestedRevealId = null;
|
|
70
77
|
let focusedId = null;
|
|
71
78
|
let disposed = false;
|
|
79
|
+
const isInsideTreeRow = (element) => {
|
|
80
|
+
const row = element.closest("[role=\"treeitem\"]");
|
|
81
|
+
return row instanceof HTMLDivElement && rowIdsByElement.has(row);
|
|
82
|
+
};
|
|
83
|
+
const focusIsOutsideTree = () => {
|
|
84
|
+
if (typeof document === "undefined") return false;
|
|
85
|
+
const active = document.activeElement;
|
|
86
|
+
return active instanceof Element && active !== document.body && active !== document.documentElement && !isInsideTreeRow(active);
|
|
87
|
+
};
|
|
72
88
|
const rowSizeObserver = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver((entries) => {
|
|
73
89
|
if (disposed) return;
|
|
74
90
|
for (const entry of entries) {
|
|
@@ -77,8 +93,26 @@ function Tree(props) {
|
|
|
77
93
|
if (id !== void 0 && Number.isFinite(blockSize) && blockSize > 0) local.onRowSizeChange?.(id, blockSize);
|
|
78
94
|
}
|
|
79
95
|
});
|
|
96
|
+
const setPendingFocus = (id) => {
|
|
97
|
+
pendingFocusId = id;
|
|
98
|
+
if (typeof document === "undefined") return;
|
|
99
|
+
if (id === null) {
|
|
100
|
+
if (pendingFocusGuard) document.removeEventListener("focusin", pendingFocusGuard, true);
|
|
101
|
+
pendingFocusGuard = void 0;
|
|
102
|
+
} else if (!pendingFocusGuard) {
|
|
103
|
+
pendingFocusGuard = (event) => {
|
|
104
|
+
if (event.target instanceof Element && !isInsideTreeRow(event.target)) cancelPendingFocus();
|
|
105
|
+
};
|
|
106
|
+
document.addEventListener("focusin", pendingFocusGuard, true);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const cancelPendingFocus = () => {
|
|
110
|
+
setPendingFocus(null);
|
|
111
|
+
focusedId = null;
|
|
112
|
+
};
|
|
80
113
|
onCleanup(() => {
|
|
81
114
|
disposed = true;
|
|
115
|
+
setPendingFocus(null);
|
|
82
116
|
rowSizeObserver?.disconnect();
|
|
83
117
|
});
|
|
84
118
|
const requestReveal = (id) => {
|
|
@@ -91,12 +125,16 @@ function Tree(props) {
|
|
|
91
125
|
if (pendingFocusId !== id || effectiveActiveId() !== id) return;
|
|
92
126
|
const row = rows.get(id);
|
|
93
127
|
if (!row || typeof document === "undefined") return;
|
|
128
|
+
if (focusIsOutsideTree()) {
|
|
129
|
+
cancelPendingFocus();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
94
132
|
row.focus();
|
|
95
|
-
if (document.activeElement === row)
|
|
133
|
+
if (document.activeElement === row) setPendingFocus(null);
|
|
96
134
|
});
|
|
97
135
|
};
|
|
98
136
|
const focusItem = (id) => {
|
|
99
|
-
|
|
137
|
+
setPendingFocus(id);
|
|
100
138
|
local.onActiveIdChange(id);
|
|
101
139
|
if (rows.has(id)) requestedRevealId = null;
|
|
102
140
|
else requestReveal(id);
|
|
@@ -124,13 +162,21 @@ function Tree(props) {
|
|
|
124
162
|
if (requestedRevealId === id) requestedRevealId = null;
|
|
125
163
|
if (focusWasInside) queueMicrotask(() => {
|
|
126
164
|
const fallbackId = effectiveActiveId();
|
|
127
|
-
if (
|
|
165
|
+
if (disposed || fallbackId === null || pendingFocusId === fallbackId) return;
|
|
166
|
+
if (focusIsOutsideTree()) {
|
|
167
|
+
focusedId = null;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
focusItem(fallbackId);
|
|
128
171
|
});
|
|
129
172
|
},
|
|
130
173
|
focusItem,
|
|
131
174
|
noteFocus: (id) => {
|
|
132
175
|
focusedId = id;
|
|
133
176
|
},
|
|
177
|
+
noteBlur: (next) => {
|
|
178
|
+
if (next instanceof Element && !isInsideTreeRow(next)) cancelPendingFocus();
|
|
179
|
+
},
|
|
134
180
|
activate: (id, event) => local.onActivate?.(id, event),
|
|
135
181
|
expand: (id, expanded) => local.onExpand?.(id, expanded),
|
|
136
182
|
disclosure: () => local.disclosure ?? "row",
|
|
@@ -139,8 +185,12 @@ function Tree(props) {
|
|
|
139
185
|
createEffect(() => {
|
|
140
186
|
const activeId = effectiveActiveId();
|
|
141
187
|
if (activeId === null) return;
|
|
142
|
-
if (focusedId !== null && !index().byId.has(focusedId) && pendingFocusId !== activeId)
|
|
143
|
-
|
|
188
|
+
if (focusedId !== null && !index().byId.has(focusedId) && pendingFocusId !== activeId) {
|
|
189
|
+
if (focusIsOutsideTree()) {
|
|
190
|
+
focusedId = null;
|
|
191
|
+
if (local.activeId !== activeId) local.onActiveIdChange(activeId);
|
|
192
|
+
} else focusItem(activeId);
|
|
193
|
+
} else if (local.activeId !== activeId) local.onActiveIdChange(activeId);
|
|
144
194
|
if (!rows.has(activeId)) requestReveal(activeId);
|
|
145
195
|
if (pendingFocusId === activeId) focusWhenMounted(activeId);
|
|
146
196
|
});
|
|
@@ -213,11 +263,16 @@ function TreeRow(props) {
|
|
|
213
263
|
let row;
|
|
214
264
|
let registeredId;
|
|
215
265
|
const id = () => local.item.id;
|
|
216
|
-
|
|
266
|
+
const handleFocusOut = (event) => tree.noteBlur(event.relatedTarget);
|
|
267
|
+
onCleanup(() => {
|
|
268
|
+
row?.removeEventListener("focusout", handleFocusOut);
|
|
269
|
+
tree.unregister(registeredId ?? id(), row);
|
|
270
|
+
});
|
|
217
271
|
const setRef = (element) => {
|
|
218
272
|
row = element;
|
|
219
273
|
registeredId = id();
|
|
220
274
|
tree.register(registeredId, element);
|
|
275
|
+
element.addEventListener("focusout", handleFocusOut);
|
|
221
276
|
};
|
|
222
277
|
createEffect(() => {
|
|
223
278
|
const nextId = id();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.js","names":["ComponentProps","JSX","ChevronRight","createContext","createEffect","createMemo","createUniqueId","onCleanup","splitProps","useContext","cn","indexTreeItems","treeNavigationAction","TreeItemDescriptor","TreeNavigationIndex","TreeContextValue","idPrefix","activeId","index","register","id","element","HTMLDivElement","unregister","focusItem","noteFocus","activate","event","Event","expand","expanded","disclosure","TreeDisclosure","onActiveIdChange","TreeContext","useTreeContext","tree","Error","TreeAccessibleName","TreeProps","Omit","visibleItems","onExpand","onActivate","onSelectItem","onRequestReveal","onRowSizeChange","blockSize","selectionMode","children","Element","Tree","props","local","rest","effectiveActiveId","candidate","visible","byId","has","ids","rows","Map","rowIdsByElement","WeakMap","pendingFocusId","requestedRevealId","focusedId","disposed","rowSizeObserver","ResizeObserver","undefined","entries","entry","get","target","borderBoxSize","HTMLElement","offsetHeight","Number","isFinite","disconnect","requestReveal","focusWhenMounted","queueMicrotask","row","document","focus","activeElement","context","set","observe","focusWasInside","Boolean","contains","delete","unobserve","fallbackId","handleKeyDown","EventHandler","KeyboardEvent","defaultPrevented","isActionTarget","currentTarget","action","key","preventDefault","type","_$createComponent","Provider","value","_el$","_tmpl$","_$spread","_$mergeProps","aria-multiselectable","class","_$insert","TreeRowProps","item","selected","leading","trailing","onDoubleClick","MouseEvent","TreeRow","registeredId","setRef","nextId","handleFocus","handleClick","handleDoubleClick","position","positionById","markerTarget","expandable","handleMarkerClick","HTMLSpanElement","stopPropagation","_el$2","_tmpl$2","_el$3","firstChild","_el$4","nextSibling","_ref$","composeRefs","ref","_$use","encodeURIComponent","aria-level","level","aria-expanded","_$memo","aria-selected","aria-posinset","aria-setsize","setSize","tabIndex","data-tree-id","data-active","data-selected","data-expanded","String","style","Math","max","_$addEventListener","handleMarkerDoubleClick","handleMarkerMouseDown","_c$","_c$2","_el$5","_tmpl$3","_c$3","_el$6","_tmpl$4","_$effect","_p$","_v$","_v$2","e","_$setAttribute","t","_$className","TreeRowActions","pinned","_el$7","_tmpl$5","data-pinned","EventTarget","closest","internal","external","_$delegateEvents","_$createComponent","_$mergeProps","_$use","_$memo","_$setAttribute","_$className","_$delegateEvents"],"sources":["../../../../src/components/composites/tree/tree.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { ChevronRight } from 'lucide-solid'\nimport {\n createContext,\n createEffect,\n createMemo,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { cn } from '#lib/utils'\nimport {\n indexTreeItems,\n treeNavigationAction,\n type TreeItemDescriptor,\n type TreeNavigationIndex,\n} from './tree-navigation'\n\ntype TreeContextValue = {\n idPrefix: string\n activeId: () => string | null\n index: () => TreeNavigationIndex\n register: (id: string, element: HTMLDivElement) => void\n unregister: (id: string, element: HTMLDivElement | undefined) => void\n focusItem: (id: string) => void\n noteFocus: (id: string) => void\n activate: (id: string, event: Event) => void\n expand: (id: string, expanded: boolean) => void\n disclosure: () => TreeDisclosure\n onActiveIdChange: (id: string) => void\n}\n\nconst TreeContext = createContext<TreeContextValue>()\n\nfunction useTreeContext() {\n const tree = useContext(TreeContext)\n if (!tree) throw new Error('[adea-ui]: TreeRow must be used inside Tree.')\n return tree\n}\n\n/**\n * Who owns the pointer on a row's disclosure chevron.\n *\n * - `row` (default): the chevron is decoration; a click anywhere on the row,\n * chevron included, is a row activation. This is the file-explorer\n * convention, where the host's `onActivate` toggles a folder.\n * - `marker`: the chevron of an expandable row is its own pointer target. It\n * calls `onExpand(id, !expanded)` and nothing else; the rest of the row still\n * activates. For trees where expanding and selecting are independent (a\n * source list that selects a source and separately reveals its details).\n */\nexport type TreeDisclosure = 'row' | 'marker'\n\ntype TreeAccessibleName =\n | { 'aria-label': string; 'aria-labelledby'?: string }\n | { 'aria-label'?: string; 'aria-labelledby': string }\n\nexport type TreeProps = Omit<\n ComponentProps<'div'>,\n 'children' | 'role' | 'tabIndex' | 'onKeyDown' | 'aria-label' | 'aria-labelledby'\n> &\n TreeAccessibleName & {\n /** Complete, ordered projection of currently visible (expanded) items. */\n visibleItems: readonly TreeItemDescriptor[]\n /** The host-owned identity of the row that currently owns roving focus. */\n activeId: string | null\n /**\n * Called when keyboard or pointer navigation changes the active row, and when\n * the active row leaves `visibleItems` (collapsed away, filtered out) and the\n * first visible row takes over as the roving stop.\n */\n onActiveIdChange: (id: string) => void\n /** Called for Left/Right expand and collapse requests. */\n onExpand?: (id: string, expanded: boolean) => void\n /**\n * Opt-in pointer behaviour of the disclosure chevron; see {@link TreeDisclosure}.\n * Keyboard behaviour is identical in both modes: Left/Right expand and collapse.\n */\n disclosure?: TreeDisclosure\n /** Called for Enter or pointer activation. Domain action stays host-owned. */\n onActivate?: (id: string, event: Event) => void\n /** Called for Space selection. Selection state stays host-owned. */\n onSelectItem?: (id: string, event: Event) => void\n /** Ask the host window to mount/reveal an item before focus moves to it. */\n onRequestReveal?: (id: string) => void\n /** Report a mounted row's border-box block size in CSS pixels for host range math. */\n onRowSizeChange?: (id: string, blockSize: number) => void\n /** Declares a multi-select tree to assistive technology. */\n selectionMode?: 'single' | 'multiple'\n children?: JSX.Element\n }\n\n/**\n * Tree.\n *\n * Supplies semantic and keyboard behavior for a tree whose host owns the\n * visible projection, filesystem state, row window and domain actions. The\n * mounted children can be a small virtualized window; `visibleItems` remains\n * the complete ordered projection so keyboard movement and sibling positions\n * do not depend on which rows currently exist in the DOM.\n *\n * Kobalte 0.13.14 has no Tree primitive, so this component implements the\n * WAI-ARIA tree row navigation contract and leaves product state to callbacks.\n *\n * By default the disclosure chevron is decoration and a click anywhere on a\n * row activates it, so a file explorer can toggle a folder from `onActivate`.\n * Reach for `disclosure=\"marker\"` when expanding and selecting are separate\n * decisions: the chevron then calls only `onExpand`, and the rest of the row\n * calls only `onActivate`. The chevron stays `aria-hidden` and out of the tab\n * order in both modes; keyboard expansion is Left/Right either way.\n *\n * Collapsing is the one thing that can move focus without a key or a click on\n * the target row: when the active row leaves `visibleItems`, the first visible\n * row becomes the roving stop and `onActiveIdChange` is called with it. If the\n * removed row held focus, or was the last row the tree saw focused, focus moves\n * to that same row; if it had no focus, only the roving stop moves. This is the\n * same in both `disclosure` modes and for every way a host collapses a row.\n */\nexport function Tree(props: TreeProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'visibleItems',\n 'activeId',\n 'onActiveIdChange',\n 'onExpand',\n 'disclosure',\n 'onActivate',\n 'onSelectItem',\n 'onRequestReveal',\n 'onRowSizeChange',\n 'selectionMode',\n 'children',\n ])\n\n const index = createMemo(() => indexTreeItems(local.visibleItems))\n const effectiveActiveId = createMemo(() => {\n const candidate = local.activeId\n const visible = index()\n return candidate !== null && visible.byId.has(candidate) ? candidate : (visible.ids[0] ?? null)\n })\n const idPrefix = `tree-${createUniqueId()}`\n const rows = new Map<string, HTMLDivElement>()\n const rowIdsByElement = new WeakMap<HTMLDivElement, string>()\n let pendingFocusId: string | null = null\n let requestedRevealId: string | null = null\n let focusedId: string | null = null\n let disposed = false\n const rowSizeObserver =\n typeof ResizeObserver === 'undefined'\n ? undefined\n : new ResizeObserver((entries) => {\n if (disposed) return\n for (const entry of entries) {\n const id = rowIdsByElement.get(entry.target as HTMLDivElement)\n const blockSize =\n entry.borderBoxSize[0]?.blockSize ?? (entry.target as HTMLElement).offsetHeight\n if (id !== undefined && Number.isFinite(blockSize) && blockSize > 0)\n local.onRowSizeChange?.(id, blockSize)\n }\n })\n\n onCleanup(() => {\n disposed = true\n rowSizeObserver?.disconnect()\n })\n\n const requestReveal = (id: string) => {\n if (requestedRevealId === id) return\n requestedRevealId = id\n local.onRequestReveal?.(id)\n }\n\n const focusWhenMounted = (id: string) => {\n queueMicrotask(() => {\n if (pendingFocusId !== id || effectiveActiveId() !== id) return\n const row = rows.get(id)\n if (!row || typeof document === 'undefined') return\n row.focus()\n if (document.activeElement === row) pendingFocusId = null\n })\n }\n\n const focusItem = (id: string) => {\n pendingFocusId = id\n local.onActiveIdChange(id)\n if (rows.has(id)) requestedRevealId = null\n else requestReveal(id)\n focusWhenMounted(id)\n }\n\n const context: TreeContextValue = {\n idPrefix,\n activeId: effectiveActiveId,\n index,\n register: (id, element) => {\n rows.set(id, element)\n rowIdsByElement.set(element, id)\n rowSizeObserver?.observe(element)\n if (requestedRevealId === id) requestedRevealId = null\n if (pendingFocusId === id) focusWhenMounted(id)\n },\n unregister: (id, element) => {\n if (rows.get(id) !== element) return\n const focusWasInside =\n !disposed &&\n Boolean(\n element && typeof document !== 'undefined' && element.contains(document.activeElement)\n )\n rows.delete(id)\n if (element) {\n rowIdsByElement.delete(element)\n rowSizeObserver?.unobserve(element)\n }\n if (requestedRevealId === id) requestedRevealId = null\n if (focusWasInside) {\n queueMicrotask(() => {\n const fallbackId = effectiveActiveId()\n if (!disposed && fallbackId !== null && pendingFocusId !== fallbackId)\n focusItem(fallbackId)\n })\n }\n },\n focusItem,\n noteFocus: (id) => {\n focusedId = id\n },\n activate: (id, event) => local.onActivate?.(id, event),\n expand: (id, expanded) => local.onExpand?.(id, expanded),\n disclosure: () => local.disclosure ?? 'row',\n onActiveIdChange: local.onActiveIdChange,\n }\n\n // A filtered, collapsed-away or removed active ID falls back to the first\n // visible item. This keeps one treeitem in the sequential tab order while\n // notifying the host.\n createEffect(() => {\n const activeId = effectiveActiveId()\n if (activeId === null) return\n // If the projection removes the last row that took focus (a host filter, or\n // an ancestor collapsing), move focus to the same fallback that now owns the\n // roving tab stop. `focusedId` is not cleared when focus leaves the tree, so\n // this also reclaims focus from a control outside it; that long-standing\n // behaviour is deliberately left as it was.\n if (focusedId !== null && !index().byId.has(focusedId) && pendingFocusId !== activeId) {\n focusItem(activeId)\n } else if (local.activeId !== activeId) {\n local.onActiveIdChange(activeId)\n }\n // Preserve the active row in a caller-owned virtual window, including when\n // the pane mounts with its saved active row outside the initial render range.\n if (!rows.has(activeId)) requestReveal(activeId)\n if (pendingFocusId === activeId) focusWhenMounted(activeId)\n })\n\n const handleKeyDown: JSX.EventHandler<HTMLDivElement, KeyboardEvent> = (event) => {\n if (event.defaultPrevented || isActionTarget(event.target, event.currentTarget)) return\n\n const action = treeNavigationAction(index(), effectiveActiveId(), event.key)\n if (!action) return\n event.preventDefault()\n\n switch (action.type) {\n case 'focus':\n focusItem(action.id)\n break\n case 'expand':\n local.onExpand?.(action.id, action.expanded)\n break\n case 'activate':\n local.onActivate?.(action.id, event)\n break\n case 'select':\n local.onSelectItem?.(action.id, event)\n break\n }\n }\n\n return (\n <TreeContext.Provider value={context}>\n <div\n {...rest}\n role=\"tree\"\n aria-multiselectable={local.selectionMode === 'multiple' ? 'true' : undefined}\n data-slot=\"tree\"\n class={cn(\n /* A virtualized host remounts rows as it scrolls; scroll anchoring\n would latch onto the focused row and fight the scrollbar. */\n 'flex min-w-0 flex-col [overflow-anchor:none]',\n local.class\n )}\n onKeyDown={handleKeyDown}\n >\n {local.children}\n </div>\n </TreeContext.Provider>\n )\n}\n\nexport type TreeRowProps = Omit<\n ComponentProps<'div'>,\n | 'children'\n | 'role'\n | 'tabIndex'\n | 'onClick'\n | 'onFocus'\n | 'onDoubleClick'\n | 'style'\n | 'aria-level'\n | 'aria-expanded'\n | 'aria-selected'\n | 'aria-posinset'\n | 'aria-setsize'\n> & {\n item: TreeItemDescriptor\n /** Host-owned selection state; omitted for non-selectable trees. */\n selected?: boolean\n leading?: JSX.Element\n trailing?: JSX.Element\n children: JSX.Element\n onDoubleClick?: (id: string, event: MouseEvent & { currentTarget: HTMLDivElement }) => void\n}\n\n/**\n * TreeRow.\n *\n * Keeps the donor's disclosure marker, leading file/folder affordance, primary\n * label and trailing-action hierarchy while replacing the donor's button and\n * runtime callbacks with a shared treeitem and host-owned slots/actions.\n *\n * With `Tree`'s `disclosure=\"marker\"` the chevron of an expandable row is a\n * separate pointer target (`data-slot=\"tree-row-disclosure\"`) that only calls\n * `onExpand`. It is not focusable and has no role: the row remains the single\n * focus stop, Enter and Space still activate and select, and Left/Right still\n * expand and collapse, so the marker adds a pointer path and nothing else.\n * Pressing it does not move focus or the roving stop to its row; if the toggle\n * hides the focused row, `Tree` recovers focus as it does for any collapse (see\n * the `Tree` comment and docs/tree.md).\n */\nexport function TreeRow(props: TreeRowProps) {\n const tree = useTreeContext()\n const [local, rest] = splitProps(props, [\n 'class',\n 'item',\n 'selected',\n 'leading',\n 'trailing',\n 'children',\n 'onDoubleClick',\n 'ref',\n ])\n\n let row: HTMLDivElement | undefined\n let registeredId: string | undefined\n const id = () => local.item.id\n\n onCleanup(() => tree.unregister(registeredId ?? id(), row))\n\n const setRef = (element: HTMLDivElement) => {\n row = element\n registeredId = id()\n tree.register(registeredId, element)\n }\n\n createEffect(() => {\n const nextId = id()\n if (!row || registeredId === nextId) return\n if (registeredId !== undefined) tree.unregister(registeredId, row)\n registeredId = nextId\n tree.register(nextId, row)\n })\n\n const handleFocus = () => {\n tree.noteFocus(id())\n if (tree.activeId() !== id()) tree.onActiveIdChange(id())\n }\n\n const handleClick: JSX.EventHandler<HTMLDivElement, MouseEvent> = (event) => {\n if (isActionTarget(event.target, event.currentTarget)) return\n tree.focusItem(id())\n tree.activate(id(), event)\n }\n\n const handleDoubleClick: JSX.EventHandler<HTMLDivElement, MouseEvent> = (event) => {\n if (isActionTarget(event.target, event.currentTarget)) return\n local.onDoubleClick?.(id(), event)\n }\n\n const position = () => tree.index().positionById.get(id())\n // Only an expandable row has a chevron to press; a leaf's marker slot stays\n // part of the row so a click there is an ordinary row click.\n const markerTarget = () => tree.disclosure() === 'marker' && local.item.expandable\n\n const handleMarkerClick: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) => {\n // The row's own handler would focus and activate the row.\n event.stopPropagation()\n tree.expand(id(), !local.item.expanded)\n }\n return (\n <div\n {...rest}\n ref={composeRefs(setRef, () => local.ref)}\n id={rest.id ?? `${tree.idPrefix}-item-${encodeURIComponent(id())}`}\n role=\"treeitem\"\n aria-level={local.item.level}\n aria-expanded={local.item.expandable ? (local.item.expanded ? 'true' : 'false') : undefined}\n aria-selected={local.selected === undefined ? undefined : local.selected ? 'true' : 'false'}\n aria-posinset={position()?.position}\n aria-setsize={position()?.setSize}\n tabIndex={tree.activeId() === id() ? 0 : -1}\n data-tree-id={id()}\n data-active={tree.activeId() === id() ? '' : undefined}\n data-selected={local.selected ? '' : undefined}\n data-expanded={local.item.expandable ? String(local.item.expanded) : undefined}\n class={cn(\n 'group/tree-row flex min-h-row-sm w-full min-w-0 items-center gap-1.5 rounded-md pe-1.5 text-left text-sm outline-none transition-colors',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n {\n 'bg-primary-subtle text-foreground': local.selected,\n 'hover:bg-surface-hover': !local.selected,\n },\n local.class\n )}\n style={{ 'padding-inline-start': `${6 + Math.max(0, local.item.level - 1) * 12}px` }}\n onFocus={handleFocus}\n onClick={handleClick}\n onDblClick={handleDoubleClick}\n >\n <span\n data-slot={markerTarget() ? 'tree-row-disclosure' : undefined}\n class={cn(\n 'flex',\n !markerTarget() && 'size-3.5',\n 'shrink-0 items-center justify-center text-muted-foreground',\n /* The glyph stays 14px wide but the target is not: it takes the row's full\n height (a flex child's `self-stretch`, so it follows row density and a\n tall trailing control) and the pseudo-element widens it to 26px, which\n stops at the row's own padding and gap, never over the icon or label. */\n markerTarget() &&\n 'relative w-3.5 cursor-pointer self-stretch hover:text-foreground after:absolute after:inset-y-0 after:-inset-x-1.5'\n )}\n onMouseDown={markerTarget() ? handleMarkerMouseDown : undefined}\n onClick={markerTarget() ? handleMarkerClick : undefined}\n onDblClick={markerTarget() ? handleMarkerDoubleClick : undefined}\n >\n {local.item.expandable ? (\n <ChevronRight\n aria-hidden=\"true\"\n class={cn('size-3.5 transition-transform', local.item.expanded && 'rotate-90')}\n />\n ) : null}\n </span>\n {local.leading ? (\n <span class=\"flex size-4 shrink-0 items-center justify-center [&_svg]:size-4\">\n {local.leading}\n </span>\n ) : null}\n <span class=\"min-w-0 flex-1 truncate\">{local.children}</span>\n {local.trailing ? (\n <span class=\"ms-auto flex shrink-0 items-center gap-1 text-muted-foreground\">\n {local.trailing}\n </span>\n ) : null}\n </div>\n )\n}\n\n/**\n * TreeRowActions.\n *\n * Row-level commands (rename, copy, delete) that stay out of the way until the\n * row is hovered or holds focus, so a long tree reads as names rather than a\n * wall of buttons. Put it in `TreeRow`'s `trailing` slot beside any status that\n * must stay visible. `pinned` keeps the actions shown, e.g. while a destructive\n * action waits for its confirming second press.\n */\nexport function TreeRowActions(props: ComponentProps<'span'> & { pinned?: boolean }) {\n const [local, rest] = splitProps(props, ['class', 'pinned'])\n return (\n <span\n data-slot=\"tree-row-actions\"\n data-pinned={local.pinned ? '' : undefined}\n class={cn(\n 'flex shrink-0 items-center gap-1 opacity-0 transition-opacity ease-out',\n 'group-hover/tree-row:opacity-100 group-focus-within/tree-row:opacity-100 data-pinned:opacity-100',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n// Pressing the chevron must not move focus to its row: a mousedown on a\n// non-focusable child would otherwise focus the row, making it the roving stop\n// (and notifying `onActiveIdChange`) for what is a disclosure toggle, not a\n// selection. The press itself leaves focus where it was, so Left/Right keep\n// acting on the row the keyboard user left off at. It cannot stop the toggle\n// from hiding that row; `Tree` then recovers focus onto the first visible row.\nconst handleMarkerMouseDown: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) =>\n event.preventDefault()\n// A double-click is two toggles; it must not also reach `onDoubleClick`\n// (rename, open) as though the row itself had been double-clicked.\nconst handleMarkerDoubleClick: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) =>\n event.stopPropagation()\n\nfunction isActionTarget(target: EventTarget | null, currentTarget: HTMLElement): boolean {\n if (!(target instanceof Element) || target === currentTarget) return false\n return Boolean(\n target.closest(\n 'a, button, input, textarea, select, [contenteditable=\"true\"], [role=\"button\"], [data-tree-keyboard-stop]'\n )\n )\n}\n\nfunction composeRefs(\n internal: (element: HTMLDivElement) => void,\n external: () => ComponentProps<'div'>['ref']\n): (element: HTMLDivElement) => void {\n return (element) => {\n internal(element)\n const ref = external()\n if (typeof ref === 'function') ref(element)\n }\n}\n\nexport type { TreeItemDescriptor } from './tree-navigation'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tree.js","names":["ComponentProps","JSX","ChevronRight","createContext","createEffect","createMemo","createUniqueId","onCleanup","splitProps","useContext","cn","indexTreeItems","treeNavigationAction","TreeItemDescriptor","TreeNavigationIndex","TreeContextValue","idPrefix","activeId","index","register","id","element","HTMLDivElement","unregister","focusItem","noteFocus","noteBlur","next","EventTarget","activate","event","Event","expand","expanded","disclosure","TreeDisclosure","onActiveIdChange","TreeContext","useTreeContext","tree","Error","TreeAccessibleName","TreeProps","Omit","visibleItems","onExpand","onActivate","onSelectItem","onRequestReveal","onRowSizeChange","blockSize","selectionMode","children","Element","Tree","props","local","rest","effectiveActiveId","candidate","visible","byId","has","ids","rows","Map","rowIdsByElement","WeakMap","pendingFocusId","pendingFocusGuard","FocusEvent","requestedRevealId","focusedId","disposed","isInsideTreeRow","row","closest","focusIsOutsideTree","document","active","activeElement","body","documentElement","rowSizeObserver","ResizeObserver","undefined","entries","entry","get","target","borderBoxSize","HTMLElement","offsetHeight","Number","isFinite","setPendingFocus","removeEventListener","cancelPendingFocus","addEventListener","disconnect","requestReveal","focusWhenMounted","queueMicrotask","focus","context","set","observe","focusWasInside","Boolean","contains","delete","unobserve","fallbackId","handleKeyDown","EventHandler","KeyboardEvent","defaultPrevented","isActionTarget","currentTarget","action","key","preventDefault","type","_$createComponent","Provider","value","_el$","_tmpl$","_$spread","_$mergeProps","aria-multiselectable","class","_$insert","TreeRowProps","item","selected","leading","trailing","onDoubleClick","MouseEvent","TreeRow","registeredId","handleFocusOut","relatedTarget","setRef","nextId","handleFocus","handleClick","handleDoubleClick","position","positionById","markerTarget","expandable","handleMarkerClick","HTMLSpanElement","stopPropagation","_el$2","_tmpl$2","_el$3","firstChild","_el$4","nextSibling","_ref$","composeRefs","ref","_$use","encodeURIComponent","aria-level","level","aria-expanded","_$memo","aria-selected","aria-posinset","aria-setsize","setSize","tabIndex","data-tree-id","data-active","data-selected","data-expanded","String","style","Math","max","_$addEventListener","handleMarkerDoubleClick","handleMarkerMouseDown","_c$","_c$2","_el$5","_tmpl$3","_c$3","_el$6","_tmpl$4","_$effect","_p$","_v$","_v$2","e","_$setAttribute","t","_$className","TreeRowActions","pinned","_el$7","_tmpl$5","data-pinned","internal","external","_$delegateEvents","_$createComponent","_$mergeProps","_$use","_$memo","_$setAttribute","_$className","_$delegateEvents"],"sources":["../../../../src/components/composites/tree/tree.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { ChevronRight } from 'lucide-solid'\nimport {\n createContext,\n createEffect,\n createMemo,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { cn } from '#lib/utils'\nimport {\n indexTreeItems,\n treeNavigationAction,\n type TreeItemDescriptor,\n type TreeNavigationIndex,\n} from './tree-navigation'\n\ntype TreeContextValue = {\n idPrefix: string\n activeId: () => string | null\n index: () => TreeNavigationIndex\n register: (id: string, element: HTMLDivElement) => void\n unregister: (id: string, element: HTMLDivElement | undefined) => void\n focusItem: (id: string) => void\n noteFocus: (id: string) => void\n noteBlur: (next: EventTarget | null) => void\n activate: (id: string, event: Event) => void\n expand: (id: string, expanded: boolean) => void\n disclosure: () => TreeDisclosure\n onActiveIdChange: (id: string) => void\n}\n\nconst TreeContext = createContext<TreeContextValue>()\n\nfunction useTreeContext() {\n const tree = useContext(TreeContext)\n if (!tree) throw new Error('[adea-ui]: TreeRow must be used inside Tree.')\n return tree\n}\n\n/**\n * Who owns the pointer on a row's disclosure chevron.\n *\n * - `row` (default): the chevron is decoration; a click anywhere on the row,\n * chevron included, is a row activation. This is the file-explorer\n * convention, where the host's `onActivate` toggles a folder.\n * - `marker`: the chevron of an expandable row is its own pointer target. It\n * calls `onExpand(id, !expanded)` and nothing else; the rest of the row still\n * activates. For trees where expanding and selecting are independent (a\n * source list that selects a source and separately reveals its details).\n */\nexport type TreeDisclosure = 'row' | 'marker'\n\ntype TreeAccessibleName =\n | { 'aria-label': string; 'aria-labelledby'?: string }\n | { 'aria-label'?: string; 'aria-labelledby': string }\n\nexport type TreeProps = Omit<\n ComponentProps<'div'>,\n 'children' | 'role' | 'tabIndex' | 'onKeyDown' | 'aria-label' | 'aria-labelledby'\n> &\n TreeAccessibleName & {\n /** Complete, ordered projection of currently visible (expanded) items. */\n visibleItems: readonly TreeItemDescriptor[]\n /** The host-owned identity of the row that currently owns roving focus. */\n activeId: string | null\n /**\n * Called when keyboard or pointer navigation changes the active row, and when\n * the active row leaves `visibleItems` (collapsed away, filtered out) and the\n * first visible row takes over as the roving stop.\n */\n onActiveIdChange: (id: string) => void\n /** Called for Left/Right expand and collapse requests. */\n onExpand?: (id: string, expanded: boolean) => void\n /**\n * Opt-in pointer behaviour of the disclosure chevron; see {@link TreeDisclosure}.\n * Keyboard behaviour is identical in both modes: Left/Right expand and collapse.\n */\n disclosure?: TreeDisclosure\n /** Called for Enter or pointer activation. Domain action stays host-owned. */\n onActivate?: (id: string, event: Event) => void\n /** Called for Space selection. Selection state stays host-owned. */\n onSelectItem?: (id: string, event: Event) => void\n /** Ask the host window to mount/reveal an item before focus moves to it. */\n onRequestReveal?: (id: string) => void\n /** Report a mounted row's border-box block size in CSS pixels for host range math. */\n onRowSizeChange?: (id: string, blockSize: number) => void\n /** Declares a multi-select tree to assistive technology. */\n selectionMode?: 'single' | 'multiple'\n children?: JSX.Element\n }\n\n/**\n * Tree.\n *\n * Supplies semantic and keyboard behavior for a tree whose host owns the\n * visible projection, filesystem state, row window and domain actions. The\n * mounted children can be a small virtualized window; `visibleItems` remains\n * the complete ordered projection so keyboard movement and sibling positions\n * do not depend on which rows currently exist in the DOM.\n *\n * Kobalte 0.13.14 has no Tree primitive, so this component implements the\n * WAI-ARIA tree row navigation contract and leaves product state to callbacks.\n *\n * By default the disclosure chevron is decoration and a click anywhere on a\n * row activates it, so a file explorer can toggle a folder from `onActivate`.\n * Reach for `disclosure=\"marker\"` when expanding and selecting are separate\n * decisions: the chevron then calls only `onExpand`, and the rest of the row\n * calls only `onActivate`. The chevron stays `aria-hidden` and out of the tab\n * order in both modes; keyboard expansion is Left/Right either way.\n *\n * Collapsing is the one thing that can move focus without a key or a click on\n * the target row: when the active row leaves `visibleItems`, the first visible\n * row becomes the roving stop and `onActiveIdChange` is called with it. If the\n * removed row held focus, or was the last row the tree saw focused, focus moves\n * to that same row; if it had no focus, only the roving stop moves. Focus that\n * has already gone to a control outside the tree is never pulled back: the tree\n * forgets its last focused row when focus moves to an element outside it, and\n * the roving stop alone moves if that control still has focus when the row is\n * hidden. Focus the tree has asked for but not yet moved (a queued recovery, or a\n * keyboard move to a row the host has not mounted) is withdrawn when focus lands\n * outside the tree, and is checked again when a row mounts. This is the same in\n * both `disclosure` modes and for every way a host collapses a row.\n */\nexport function Tree(props: TreeProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'visibleItems',\n 'activeId',\n 'onActiveIdChange',\n 'onExpand',\n 'disclosure',\n 'onActivate',\n 'onSelectItem',\n 'onRequestReveal',\n 'onRowSizeChange',\n 'selectionMode',\n 'children',\n ])\n\n const index = createMemo(() => indexTreeItems(local.visibleItems))\n const effectiveActiveId = createMemo(() => {\n const candidate = local.activeId\n const visible = index()\n return candidate !== null && visible.byId.has(candidate) ? candidate : (visible.ids[0] ?? null)\n })\n const idPrefix = `tree-${createUniqueId()}`\n const rows = new Map<string, HTMLDivElement>()\n const rowIdsByElement = new WeakMap<HTMLDivElement, string>()\n let pendingFocusId: string | null = null\n let pendingFocusGuard: ((event: FocusEvent) => void) | undefined\n let requestedRevealId: string | null = null\n let focusedId: string | null = null\n let disposed = false\n const isInsideTreeRow = (element: Element) => {\n const row = element.closest('[role=\"treeitem\"]')\n return row instanceof HTMLDivElement && rowIdsByElement.has(row)\n }\n // True when a focusable control outside this tree owns focus. Nothing focused\n // (`body`) is not outside: that is where focus falls when a focused row is removed.\n const focusIsOutsideTree = () => {\n if (typeof document === 'undefined') return false\n const active = document.activeElement\n return (\n active instanceof Element &&\n active !== document.body &&\n active !== document.documentElement &&\n !isInsideTreeRow(active)\n )\n }\n const rowSizeObserver =\n typeof ResizeObserver === 'undefined'\n ? undefined\n : new ResizeObserver((entries) => {\n if (disposed) return\n for (const entry of entries) {\n const id = rowIdsByElement.get(entry.target as HTMLDivElement)\n const blockSize =\n entry.borderBoxSize[0]?.blockSize ?? (entry.target as HTMLElement).offsetHeight\n if (id !== undefined && Number.isFinite(blockSize) && blockSize > 0)\n local.onRowSizeChange?.(id, blockSize)\n }\n })\n\n // Focus the tree has asked for but not yet moved (a microtask away, or waiting for a\n // virtualised row to mount) is only a request. While one is pending, focus arriving\n // on a control outside the tree withdraws it, so a row mounting later cannot take\n // focus back from a control the user, or the host, has since chosen.\n const setPendingFocus = (id: string | null) => {\n pendingFocusId = id\n if (typeof document === 'undefined') return\n if (id === null) {\n if (pendingFocusGuard) document.removeEventListener('focusin', pendingFocusGuard, true)\n pendingFocusGuard = undefined\n } else if (!pendingFocusGuard) {\n pendingFocusGuard = (event) => {\n if (event.target instanceof Element && !isInsideTreeRow(event.target)) cancelPendingFocus()\n }\n document.addEventListener('focusin', pendingFocusGuard, true)\n }\n }\n // Withdrawing a request also drops the memory of the row it was recovering for:\n // nothing is left to recover.\n const cancelPendingFocus = () => {\n setPendingFocus(null)\n focusedId = null\n }\n\n onCleanup(() => {\n disposed = true\n setPendingFocus(null)\n rowSizeObserver?.disconnect()\n })\n\n const requestReveal = (id: string) => {\n if (requestedRevealId === id) return\n requestedRevealId = id\n local.onRequestReveal?.(id)\n }\n\n const focusWhenMounted = (id: string) => {\n queueMicrotask(() => {\n if (pendingFocusId !== id || effectiveActiveId() !== id) return\n const row = rows.get(id)\n if (!row || typeof document === 'undefined') return\n // Ownership is checked when focus would move, not when it was requested: a\n // control outside the tree may have taken focus since, and keeps it.\n if (focusIsOutsideTree()) {\n cancelPendingFocus()\n return\n }\n row.focus()\n if (document.activeElement === row) setPendingFocus(null)\n })\n }\n\n const focusItem = (id: string) => {\n setPendingFocus(id)\n local.onActiveIdChange(id)\n if (rows.has(id)) requestedRevealId = null\n else requestReveal(id)\n focusWhenMounted(id)\n }\n\n const context: TreeContextValue = {\n idPrefix,\n activeId: effectiveActiveId,\n index,\n register: (id, element) => {\n rows.set(id, element)\n rowIdsByElement.set(element, id)\n rowSizeObserver?.observe(element)\n if (requestedRevealId === id) requestedRevealId = null\n if (pendingFocusId === id) focusWhenMounted(id)\n },\n unregister: (id, element) => {\n if (rows.get(id) !== element) return\n const focusWasInside =\n !disposed &&\n Boolean(\n element && typeof document !== 'undefined' && element.contains(document.activeElement)\n )\n rows.delete(id)\n if (element) {\n rowIdsByElement.delete(element)\n rowSizeObserver?.unobserve(element)\n }\n if (requestedRevealId === id) requestedRevealId = null\n if (focusWasInside) {\n queueMicrotask(() => {\n const fallbackId = effectiveActiveId()\n if (disposed || fallbackId === null || pendingFocusId === fallbackId) return\n // Focus fell out of the removed row, but it may have been given to a control\n // outside the tree before this ran. Only nothing-focused (body) recovers.\n if (focusIsOutsideTree()) {\n focusedId = null\n return\n }\n focusItem(fallbackId)\n })\n }\n },\n focusItem,\n noteFocus: (id) => {\n focusedId = id\n },\n noteBlur: (next) => {\n // Focus moving to an element outside this tree ends the memory of the last\n // focused row and withdraws any focus the tree was still waiting to give a\n // row. A null target (focus to nothing, or the window losing focus) is\n // ambiguous, so both stay for the recovery below.\n if (next instanceof Element && !isInsideTreeRow(next)) cancelPendingFocus()\n },\n activate: (id, event) => local.onActivate?.(id, event),\n expand: (id, expanded) => local.onExpand?.(id, expanded),\n disclosure: () => local.disclosure ?? 'row',\n onActiveIdChange: local.onActiveIdChange,\n }\n\n // A filtered, collapsed-away or removed active ID falls back to the first\n // visible item. This keeps one treeitem in the sequential tab order while\n // notifying the host.\n createEffect(() => {\n const activeId = effectiveActiveId()\n if (activeId === null) return\n // If the projection removes the last row that took focus (a host filter, or\n // an ancestor collapsing), move focus to the same fallback that now owns the\n // roving tab stop, unless a control outside the tree has focus: that control\n // keeps it, and only the roving stop moves.\n if (focusedId !== null && !index().byId.has(focusedId) && pendingFocusId !== activeId) {\n if (focusIsOutsideTree()) {\n focusedId = null\n if (local.activeId !== activeId) local.onActiveIdChange(activeId)\n } else {\n focusItem(activeId)\n }\n } else if (local.activeId !== activeId) {\n local.onActiveIdChange(activeId)\n }\n // Preserve the active row in a caller-owned virtual window, including when\n // the pane mounts with its saved active row outside the initial render range.\n if (!rows.has(activeId)) requestReveal(activeId)\n if (pendingFocusId === activeId) focusWhenMounted(activeId)\n })\n\n const handleKeyDown: JSX.EventHandler<HTMLDivElement, KeyboardEvent> = (event) => {\n if (event.defaultPrevented || isActionTarget(event.target, event.currentTarget)) return\n\n const action = treeNavigationAction(index(), effectiveActiveId(), event.key)\n if (!action) return\n event.preventDefault()\n\n switch (action.type) {\n case 'focus':\n focusItem(action.id)\n break\n case 'expand':\n local.onExpand?.(action.id, action.expanded)\n break\n case 'activate':\n local.onActivate?.(action.id, event)\n break\n case 'select':\n local.onSelectItem?.(action.id, event)\n break\n }\n }\n\n return (\n <TreeContext.Provider value={context}>\n <div\n {...rest}\n role=\"tree\"\n aria-multiselectable={local.selectionMode === 'multiple' ? 'true' : undefined}\n data-slot=\"tree\"\n class={cn(\n /* A virtualized host remounts rows as it scrolls; scroll anchoring\n would latch onto the focused row and fight the scrollbar. */\n 'flex min-w-0 flex-col [overflow-anchor:none]',\n local.class\n )}\n onKeyDown={handleKeyDown}\n >\n {local.children}\n </div>\n </TreeContext.Provider>\n )\n}\n\nexport type TreeRowProps = Omit<\n ComponentProps<'div'>,\n | 'children'\n | 'role'\n | 'tabIndex'\n | 'onClick'\n | 'onFocus'\n | 'onDoubleClick'\n | 'style'\n | 'aria-level'\n | 'aria-expanded'\n | 'aria-selected'\n | 'aria-posinset'\n | 'aria-setsize'\n> & {\n item: TreeItemDescriptor\n /** Host-owned selection state; omitted for non-selectable trees. */\n selected?: boolean\n leading?: JSX.Element\n trailing?: JSX.Element\n children: JSX.Element\n onDoubleClick?: (id: string, event: MouseEvent & { currentTarget: HTMLDivElement }) => void\n}\n\n/**\n * TreeRow.\n *\n * Keeps the donor's disclosure marker, leading file/folder affordance, primary\n * label and trailing-action hierarchy while replacing the donor's button and\n * runtime callbacks with a shared treeitem and host-owned slots/actions.\n *\n * With `Tree`'s `disclosure=\"marker\"` the chevron of an expandable row is a\n * separate pointer target (`data-slot=\"tree-row-disclosure\"`) that only calls\n * `onExpand`. It is not focusable and has no role: the row remains the single\n * focus stop, Enter and Space still activate and select, and Left/Right still\n * expand and collapse, so the marker adds a pointer path and nothing else.\n * Pressing it does not move focus or the roving stop to its row; if the toggle\n * hides the focused row, `Tree` recovers focus as it does for any collapse (see\n * the `Tree` comment and docs/tree.md).\n */\nexport function TreeRow(props: TreeRowProps) {\n const tree = useTreeContext()\n const [local, rest] = splitProps(props, [\n 'class',\n 'item',\n 'selected',\n 'leading',\n 'trailing',\n 'children',\n 'onDoubleClick',\n 'ref',\n ])\n\n let row: HTMLDivElement | undefined\n let registeredId: string | undefined\n const id = () => local.item.id\n\n const handleFocusOut = (event: FocusEvent) => tree.noteBlur(event.relatedTarget)\n\n onCleanup(() => {\n row?.removeEventListener('focusout', handleFocusOut)\n tree.unregister(registeredId ?? id(), row)\n })\n\n const setRef = (element: HTMLDivElement) => {\n row = element\n registeredId = id()\n tree.register(registeredId, element)\n // A listener, not an `onFocusOut` prop, so a host's own handler is untouched.\n element.addEventListener('focusout', handleFocusOut)\n }\n\n createEffect(() => {\n const nextId = id()\n if (!row || registeredId === nextId) return\n if (registeredId !== undefined) tree.unregister(registeredId, row)\n registeredId = nextId\n tree.register(nextId, row)\n })\n\n const handleFocus = () => {\n tree.noteFocus(id())\n if (tree.activeId() !== id()) tree.onActiveIdChange(id())\n }\n\n const handleClick: JSX.EventHandler<HTMLDivElement, MouseEvent> = (event) => {\n if (isActionTarget(event.target, event.currentTarget)) return\n tree.focusItem(id())\n tree.activate(id(), event)\n }\n\n const handleDoubleClick: JSX.EventHandler<HTMLDivElement, MouseEvent> = (event) => {\n if (isActionTarget(event.target, event.currentTarget)) return\n local.onDoubleClick?.(id(), event)\n }\n\n const position = () => tree.index().positionById.get(id())\n // Only an expandable row has a chevron to press; a leaf's marker slot stays\n // part of the row so a click there is an ordinary row click.\n const markerTarget = () => tree.disclosure() === 'marker' && local.item.expandable\n\n const handleMarkerClick: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) => {\n // The row's own handler would focus and activate the row.\n event.stopPropagation()\n tree.expand(id(), !local.item.expanded)\n }\n return (\n <div\n {...rest}\n ref={composeRefs(setRef, () => local.ref)}\n id={rest.id ?? `${tree.idPrefix}-item-${encodeURIComponent(id())}`}\n role=\"treeitem\"\n aria-level={local.item.level}\n aria-expanded={local.item.expandable ? (local.item.expanded ? 'true' : 'false') : undefined}\n aria-selected={local.selected === undefined ? undefined : local.selected ? 'true' : 'false'}\n aria-posinset={position()?.position}\n aria-setsize={position()?.setSize}\n tabIndex={tree.activeId() === id() ? 0 : -1}\n data-tree-id={id()}\n data-active={tree.activeId() === id() ? '' : undefined}\n data-selected={local.selected ? '' : undefined}\n data-expanded={local.item.expandable ? String(local.item.expanded) : undefined}\n class={cn(\n 'group/tree-row flex min-h-row-sm w-full min-w-0 items-center gap-1.5 rounded-md pe-1.5 text-left text-sm outline-none transition-colors',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n {\n 'bg-primary-subtle text-foreground': local.selected,\n 'hover:bg-surface-hover': !local.selected,\n },\n local.class\n )}\n style={{ 'padding-inline-start': `${6 + Math.max(0, local.item.level - 1) * 12}px` }}\n onFocus={handleFocus}\n onClick={handleClick}\n onDblClick={handleDoubleClick}\n >\n <span\n data-slot={markerTarget() ? 'tree-row-disclosure' : undefined}\n class={cn(\n 'flex',\n !markerTarget() && 'size-3.5',\n 'shrink-0 items-center justify-center text-muted-foreground',\n /* The glyph stays 14px wide but the target is not: it takes the row's full\n height (a flex child's `self-stretch`, so it follows row density and a\n tall trailing control) and the pseudo-element widens it to 26px, which\n stops at the row's own padding and gap, never over the icon or label. */\n markerTarget() &&\n 'relative w-3.5 cursor-pointer self-stretch hover:text-foreground after:absolute after:inset-y-0 after:-inset-x-1.5'\n )}\n onMouseDown={markerTarget() ? handleMarkerMouseDown : undefined}\n onClick={markerTarget() ? handleMarkerClick : undefined}\n onDblClick={markerTarget() ? handleMarkerDoubleClick : undefined}\n >\n {local.item.expandable ? (\n <ChevronRight\n aria-hidden=\"true\"\n class={cn('size-3.5 transition-transform', local.item.expanded && 'rotate-90')}\n />\n ) : null}\n </span>\n {local.leading ? (\n <span class=\"flex size-4 shrink-0 items-center justify-center [&_svg]:size-4\">\n {local.leading}\n </span>\n ) : null}\n <span class=\"min-w-0 flex-1 truncate\">{local.children}</span>\n {local.trailing ? (\n <span class=\"ms-auto flex shrink-0 items-center gap-1 text-muted-foreground\">\n {local.trailing}\n </span>\n ) : null}\n </div>\n )\n}\n\n/**\n * TreeRowActions.\n *\n * Row-level commands (rename, copy, delete) that stay out of the way until the\n * row is hovered or holds focus, so a long tree reads as names rather than a\n * wall of buttons. Put it in `TreeRow`'s `trailing` slot beside any status that\n * must stay visible. `pinned` keeps the actions shown, e.g. while a destructive\n * action waits for its confirming second press.\n */\nexport function TreeRowActions(props: ComponentProps<'span'> & { pinned?: boolean }) {\n const [local, rest] = splitProps(props, ['class', 'pinned'])\n return (\n <span\n data-slot=\"tree-row-actions\"\n data-pinned={local.pinned ? '' : undefined}\n class={cn(\n 'flex shrink-0 items-center gap-1 opacity-0 transition-opacity ease-out',\n 'group-hover/tree-row:opacity-100 group-focus-within/tree-row:opacity-100 data-pinned:opacity-100',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n// Pressing the chevron must not move focus to its row: a mousedown on a\n// non-focusable child would otherwise focus the row, making it the roving stop\n// (and notifying `onActiveIdChange`) for what is a disclosure toggle, not a\n// selection. The press itself leaves focus where it was, so Left/Right keep\n// acting on the row the keyboard user left off at. It cannot stop the toggle\n// from hiding that row; `Tree` then recovers focus onto the first visible row.\nconst handleMarkerMouseDown: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) =>\n event.preventDefault()\n// A double-click is two toggles; it must not also reach `onDoubleClick`\n// (rename, open) as though the row itself had been double-clicked.\nconst handleMarkerDoubleClick: JSX.EventHandler<HTMLSpanElement, MouseEvent> = (event) =>\n event.stopPropagation()\n\nfunction isActionTarget(target: EventTarget | null, currentTarget: HTMLElement): boolean {\n if (!(target instanceof Element) || target === currentTarget) return false\n return Boolean(\n target.closest(\n 'a, button, input, textarea, select, [contenteditable=\"true\"], [role=\"button\"], [data-tree-keyboard-stop]'\n )\n )\n}\n\nfunction composeRefs(\n internal: (element: HTMLDivElement) => void,\n external: () => ComponentProps<'div'>['ref']\n): (element: HTMLDivElement) => void {\n return (element) => {\n internal(element)\n const ref = external()\n if (typeof ref === 'function') ref(element)\n }\n}\n\nexport type { TreeItemDescriptor } from './tree-navigation'\n"],"mappings":";;;;;;;;;;;AAkCA,IAAMqC,cAAclC,cAAgC;AAEpD,SAASmC,iBAAiB;CACxB,MAAMC,OAAO9B,WAAW4B,WAAW;CACnC,IAAI,CAACE,MAAM,MAAM,IAAIC,MAAM,8CAA8C;CACzE,OAAOD;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,SAAgBe,KAAKC,OAAkB;CACrC,MAAM,CAACC,OAAOC,QAAQjD,WAAW+C,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CAED,MAAMrC,QAAQb,iBAAiBM,eAAe6C,MAAMZ,YAAY,CAAC;CACjE,MAAMc,oBAAoBrD,iBAAiB;EACzC,MAAMsD,YAAYH,MAAMvC;EACxB,MAAM2C,UAAU1C,MAAM;EACtB,OAAOyC,cAAc,QAAQC,QAAQC,KAAKC,IAAIH,SAAS,IAAIA,YAAaC,QAAQG,IAAI,MAAM;CAC5F,CAAC;CACD,MAAM/C,WAAW,QAAQV,eAAe;CACxC,MAAM0D,uBAAO,IAAIC,IAA4B;CAC7C,MAAMC,kCAAkB,IAAIC,QAAgC;CAC5D,IAAIC,iBAAgC;CACpC,IAAIC;CACJ,IAAIE,oBAAmC;CACvC,IAAIC,YAA2B;CAC/B,IAAIC,WAAW;CACf,MAAMC,mBAAmBrD,YAAqB;EAC5C,MAAMsD,MAAMtD,QAAQuD,QAAQ,qBAAmB;EAC/C,OAAOD,eAAerD,kBAAkB4C,gBAAgBJ,IAAIa,GAAG;CACjE;CAGA,MAAME,2BAA2B;EAC/B,IAAI,OAAOC,aAAa,aAAa,OAAO;EAC5C,MAAMC,SAASD,SAASE;EACxB,OACED,kBAAkB1B,WAClB0B,WAAWD,SAASG,QACpBF,WAAWD,SAASI,mBACpB,CAACR,gBAAgBK,MAAM;CAE3B;CACA,MAAMI,kBACJ,OAAOC,mBAAmB,cACtBC,KAAAA,IACA,IAAID,gBAAgBE,YAAY;EAC9B,IAAIb,UAAU;EACd,KAAK,MAAMc,SAASD,SAAS;GAC3B,MAAMlE,KAAK8C,gBAAgBsB,IAAID,MAAME,MAAwB;GAC7D,MAAMvC,YACJqC,MAAMG,cAAc,EAAE,EAAExC,aAAcqC,MAAME,OAAuBG;GACrE,IAAIxE,OAAOiE,KAAAA,KAAaQ,OAAOC,SAAS5C,SAAS,KAAKA,YAAY,GAChEM,MAAMP,kBAAkB7B,IAAI8B,SAAS;EACzC;CACF,CAAC;CAMP,MAAM6C,mBAAmB3E,OAAsB;EAC7CgD,iBAAiBhD;EACjB,IAAI,OAAO0D,aAAa,aAAa;EACrC,IAAI1D,OAAO,MAAM;GACf,IAAIiD,mBAAmBS,SAASkB,oBAAoB,WAAW3B,mBAAmB,IAAI;GACtFA,oBAAoBgB,KAAAA;EACtB,OAAO,IAAI,CAAChB,mBAAmB;GAC7BA,qBAAqBvC,UAAU;IAC7B,IAAIA,MAAM2D,kBAAkBpC,WAAW,CAACqB,gBAAgB5C,MAAM2D,MAAM,GAAGQ,mBAAmB;GAC5F;GACAnB,SAASoB,iBAAiB,WAAW7B,mBAAmB,IAAI;EAC9D;CACF;CAGA,MAAM4B,2BAA2B;EAC/BF,gBAAgB,IAAI;EACpBvB,YAAY;CACd;CAEAjE,gBAAgB;EACdkE,WAAW;EACXsB,gBAAgB,IAAI;EACpBZ,iBAAiBgB,WAAW;CAC9B,CAAC;CAED,MAAMC,iBAAiBhF,OAAe;EACpC,IAAImD,sBAAsBnD,IAAI;EAC9BmD,oBAAoBnD;EACpBoC,MAAMR,kBAAkB5B,EAAE;CAC5B;CAEA,MAAMiF,oBAAoBjF,OAAe;EACvCkF,qBAAqB;GACnB,IAAIlC,mBAAmBhD,MAAMsC,kBAAkB,MAAMtC,IAAI;GACzD,MAAMuD,MAAMX,KAAKwB,IAAIpE,EAAE;GACvB,IAAI,CAACuD,OAAO,OAAOG,aAAa,aAAa;GAG7C,IAAID,mBAAmB,GAAG;IACxBoB,mBAAmB;IACnB;GACF;GACAtB,IAAI4B,MAAM;GACV,IAAIzB,SAASE,kBAAkBL,KAAKoB,gBAAgB,IAAI;EAC1D,CAAC;CACH;CAEA,MAAMvE,aAAaJ,OAAe;EAChC2E,gBAAgB3E,EAAE;EAClBoC,MAAMpB,iBAAiBhB,EAAE;EACzB,IAAI4C,KAAKF,IAAI1C,EAAE,GAAGmD,oBAAoB;OACjC6B,cAAchF,EAAE;EACrBiF,iBAAiBjF,EAAE;CACrB;CAEA,MAAMoF,UAA4B;EAChCxF;EACAC,UAAUyC;EACVxC;EACAC,WAAWC,IAAIC,YAAY;GACzB2C,KAAKyC,IAAIrF,IAAIC,OAAO;GACpB6C,gBAAgBuC,IAAIpF,SAASD,EAAE;GAC/B+D,iBAAiBuB,QAAQrF,OAAO;GAChC,IAAIkD,sBAAsBnD,IAAImD,oBAAoB;GAClD,IAAIH,mBAAmBhD,IAAIiF,iBAAiBjF,EAAE;EAChD;EACAG,aAAaH,IAAIC,YAAY;GAC3B,IAAI2C,KAAKwB,IAAIpE,EAAE,MAAMC,SAAS;GAC9B,MAAMsF,iBACJ,CAAClC,YACDmC,QACEvF,WAAW,OAAOyD,aAAa,eAAezD,QAAQwF,SAAS/B,SAASE,aAAa,CACvF;GACFhB,KAAK8C,OAAO1F,EAAE;GACd,IAAIC,SAAS;IACX6C,gBAAgB4C,OAAOzF,OAAO;IAC9B8D,iBAAiB4B,UAAU1F,OAAO;GACpC;GACA,IAAIkD,sBAAsBnD,IAAImD,oBAAoB;GAClD,IAAIoC,gBACFL,qBAAqB;IACnB,MAAMU,aAAatD,kBAAkB;IACrC,IAAIe,YAAYuC,eAAe,QAAQ5C,mBAAmB4C,YAAY;IAGtE,IAAInC,mBAAmB,GAAG;KACxBL,YAAY;KACZ;IACF;IACAhD,UAAUwF,UAAU;GACtB,CAAC;EAEL;EACAxF;EACAC,YAAYL,OAAO;GACjBoD,YAAYpD;EACd;EACAM,WAAWC,SAAS;GAKlB,IAAIA,gBAAgB0B,WAAW,CAACqB,gBAAgB/C,IAAI,GAAGsE,mBAAmB;EAC5E;EACApE,WAAWT,IAAIU,UAAU0B,MAAMV,aAAa1B,IAAIU,KAAK;EACrDE,SAASZ,IAAIa,aAAauB,MAAMX,WAAWzB,IAAIa,QAAQ;EACvDC,kBAAkBsB,MAAMtB,cAAc;EACtCE,kBAAkBoB,MAAMpB;CAC1B;CAKAhC,mBAAmB;EACjB,MAAMa,WAAWyC,kBAAkB;EACnC,IAAIzC,aAAa,MAAM;EAKvB,IAAIuD,cAAc,QAAQ,CAACtD,MAAM,CAAC,CAAC2C,KAAKC,IAAIU,SAAS,KAAKJ,mBAAmBnD,UAAU;GACrF,IAAI4D,mBAAmB,GAAG;IACxBL,YAAY;IACZ,IAAIhB,MAAMvC,aAAaA,UAAUuC,MAAMpB,iBAAiBnB,QAAQ;GAClE,OACEO,UAAUP,QAAQ;EAEtB,OAAO,IAAIuC,MAAMvC,aAAaA,UAC5BuC,MAAMpB,iBAAiBnB,QAAQ;EAIjC,IAAI,CAAC+C,KAAKF,IAAI7C,QAAQ,GAAGmF,cAAcnF,QAAQ;EAC/C,IAAImD,mBAAmBnD,UAAUoF,iBAAiBpF,QAAQ;CAC5D,CAAC;CAED,MAAMgG,iBAAkEnF,UAAU;EAChF,IAAIA,MAAMsF,oBAAoBC,eAAevF,MAAM2D,QAAQ3D,MAAMwF,aAAa,GAAG;EAEjF,MAAMC,SAAS3G,qBAAqBM,MAAM,GAAGwC,kBAAkB,GAAG5B,MAAM0F,GAAG;EAC3E,IAAI,CAACD,QAAQ;EACbzF,MAAM2F,eAAe;EAErB,QAAQF,OAAOG,MAAf;GACE,KAAK;IACHlG,UAAU+F,OAAOnG,EAAE;IACnB;GACF,KAAK;IACHoC,MAAMX,WAAW0E,OAAOnG,IAAImG,OAAOtF,QAAQ;IAC3C;GACF,KAAK;IACHuB,MAAMV,aAAayE,OAAOnG,IAAIU,KAAK;IACnC;GACF,KAAK,UACH0B,MAAMT,eAAewE,OAAOnG,IAAIU,KAAK;EAEzC;CACF;CAEA,OAAA6F,gBACGtF,YAAYuF,UAAQ;EAACC,OAAOrB;EAAO,IAAApD,WAAA;GAAA,IAAA0E,OAAAC,OAAA;GAAAC,OAAAF,MAAAG,WAE5BxE,MAAI;IAAA,QACH;IAAM,KAAA,0BAAA;KAAA,OACWD,MAAML,kBAAkB,aAAa,SAASkC,KAAAA;IAAS;IAAA,aACnE;IAAM,KAAA,WAAA;KAAA,OACT3E,GAGL,gDACA8C,MAAM2E,KACR;IAAC;IAAA,aACUlB;GAAa,CAAA,GAAA,OAAA,IAAA;GAAAmB,OAAAN,YAEvBtE,MAAMJ,QAAQ;GAAA,OAAA0E;EAAA;CAAA,CAAA;AAIvB;;;;;;;;;;;;;;;;;AA0CA,SAAgBc,QAAQrF,OAAqB;CAC3C,MAAMhB,OAAOD,eAAe;CAC5B,MAAM,CAACkB,OAAOC,QAAQjD,WAAW+C,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAK,CACN;CAED,IAAIoB;CACJ,IAAIkE;CACJ,MAAMzH,WAAWoC,MAAM8E,KAAKlH;CAE5B,MAAM0H,kBAAkBhH,UAAsBS,KAAKb,SAASI,MAAMiH,aAAa;CAE/ExI,gBAAgB;EACdoE,KAAKqB,oBAAoB,YAAY8C,cAAc;EACnDvG,KAAKhB,WAAWsH,gBAAgBzH,GAAG,GAAGuD,GAAG;CAC3C,CAAC;CAED,MAAMqE,UAAU3H,YAA4B;EAC1CsD,MAAMtD;EACNwH,eAAezH,GAAG;EAClBmB,KAAKpB,SAAS0H,cAAcxH,OAAO;EAEnCA,QAAQ6E,iBAAiB,YAAY4C,cAAc;CACrD;CAEA1I,mBAAmB;EACjB,MAAM6I,SAAS7H,GAAG;EAClB,IAAI,CAACuD,OAAOkE,iBAAiBI,QAAQ;EACrC,IAAIJ,iBAAiBxD,KAAAA,GAAW9C,KAAKhB,WAAWsH,cAAclE,GAAG;EACjEkE,eAAeI;EACf1G,KAAKpB,SAAS8H,QAAQtE,GAAG;CAC3B,CAAC;CAED,MAAMuE,oBAAoB;EACxB3G,KAAKd,UAAUL,GAAG,CAAC;EACnB,IAAImB,KAAKtB,SAAS,MAAMG,GAAG,GAAGmB,KAAKH,iBAAiBhB,GAAG,CAAC;CAC1D;CAEA,MAAM+H,eAA6DrH,UAAU;EAC3E,IAAIuF,eAAevF,MAAM2D,QAAQ3D,MAAMwF,aAAa,GAAG;EACvD/E,KAAKf,UAAUJ,GAAG,CAAC;EACnBmB,KAAKV,SAAST,GAAG,GAAGU,KAAK;CAC3B;CAEA,MAAMsH,qBAAmEtH,UAAU;EACjF,IAAIuF,eAAevF,MAAM2D,QAAQ3D,MAAMwF,aAAa,GAAG;EACvD9D,MAAMkF,gBAAgBtH,GAAG,GAAGU,KAAK;CACnC;CAEA,MAAMuH,iBAAiB9G,KAAKrB,MAAM,CAAC,CAACoI,aAAa9D,IAAIpE,GAAG,CAAC;CAGzD,MAAMmI,qBAAqBhH,KAAKL,WAAW,MAAM,YAAYsB,MAAM8E,KAAKkB;CAExE,MAAMC,qBAAoE3H,UAAU;EAElFA,MAAM6H,gBAAgB;EACtBpH,KAAKP,OAAOZ,GAAG,GAAG,CAACoC,MAAM8E,KAAKrG,QAAQ;CACxC;CACA,cAAA;EAAA,IAAA2H,QAAAC,QAAA,GAAAC,QAAAF,MAAAG,YAAAC,QAAAF,MAAAG;EAAA,IAAAC,QAGSC,YAAYnB,cAAcxF,MAAM4G,GAAG;EAAC,OAAAF,UAAA,cAAAG,IAAAH,OAAAN,KAAA;EAAA5B,OAAA4B,OAAA3B,WADrCxE,MAAI;GAAA,IAERrC,KAAE;IAAA,OAAEqC,KAAKrC,MAAM,GAAGmB,KAAKvB,SAAQ,QAASsJ,mBAAmBlJ,GAAG,CAAC;GAAG;GAAA,QAC7D;GAAU,KAAA,gBAAA;IAAA,OACHoC,MAAM8E,KAAKkC;GAAK;GAAA,KAAA,mBAAA;IAAA,OACbE,WAAA,CAAA,CAAAlH,MAAM8E,KAAKkB,UAAU,CAAA,CAAA,IAAIhG,MAAM8E,KAAKrG,WAAW,SAAS,UAAWoD,KAAAA;GAAS;GAAA,KAAA,mBAAA;IAAA,OAC5EqF,WAAAlH,MAAM+E,aAAalD,KAAAA,CAAS,CAAA,CAAA,IAAGA,KAAAA,IAAY7B,MAAM+E,WAAW,SAAS;GAAO;GAAA,KAAA,mBAAA;IAAA,OAC5Ec,SAAS,CAAC,EAAEA;GAAQ;GAAA,KAAA,kBAAA;IAAA,OACrBA,SAAS,CAAC,EAAEyB;GAAO;GAAA,IACjCC,WAAQ;IAAA,OAAExI,KAAKtB,SAAS,MAAMG,GAAG,IAAI,IAAI;GAAE;GAAA,KAAA,kBAAA;IAAA,OAC7BA,GAAG;GAAC;GAAA,KAAA,iBAAA;IAAA,OACLmB,KAAKtB,SAAS,MAAMG,GAAG,IAAI,KAAKiE,KAAAA;GAAS;GAAA,KAAA,mBAAA;IAAA,OACvC7B,MAAM+E,WAAW,KAAKlD,KAAAA;GAAS;GAAA,KAAA,mBAAA;IAAA,OAC/BqF,WAAA,CAAA,CAAAlH,MAAM8E,KAAKkB,UAAU,CAAA,CAAA,IAAG4B,OAAO5H,MAAM8E,KAAKrG,QAAQ,IAAIoD,KAAAA;GAAS;GAAA,KAAA,WAAA;IAAA,OACvE3E,GACL,2IACA,oFACA;KACE,qCAAqC8C,MAAM+E;KAC3C,0BAA0B,CAAC/E,MAAM+E;IACnC,GACA/E,MAAM2E,KACR;GAAC;GAAA,IACDkD,QAAK;IAAA,OAAE,EAAE,wBAAwB,GAAG,IAAIC,KAAKC,IAAI,GAAG/H,MAAM8E,KAAKkC,QAAQ,CAAC,IAAI,GAAE,IAAK;GAAC;GAAA,WAC3EtB;GAAW,WACXC;GAAW,cACRC;EAAiB,CAAA,GAAA,OAAA,IAAA;EAAAoC,iBAAA1B,OAAA,YAiBfP,aAAa,IAAIkC,0BAA0BpG,KAAAA,GAAS,IAAA;EAAAmG,iBAAA1B,OAAA,SADvDP,aAAa,IAAIE,oBAAoBpE,KAAAA,GAAS,IAAA;EAAAmG,iBAAA1B,OAAA,aAD1CP,aAAa,IAAImC,wBAAwBrG,KAAAA,GAAS,IAAA;EAAA+C,OAAA0B,cAAA;GAAA,IAAA6B,MAAAjB,WAAA,CAAA,CAI9DlH,MAAM8E,KAAKkB,UAAU;GAAA,aAArBmC,IAAA,IAAAhE,gBACEzH,cAAY;IAAA,eAAA;IAAA,KAAA,WAAA;KAAA,OAEJQ,GAAG,iCAAiC8C,MAAM8E,KAAKrG,YAAY,WAAW;IAAC;GAAA,CAAA,IAE9E;EAAI,EAAA,CAAA,CAAA;EAAAmG,OAAAwB,cAAA;GAAA,IAAAgC,OAAAlB,WAAA,CAAA,CAETlH,MAAMgF,OAAO;GAAA,aAAboD,KAAA,WAAA;IAAA,IAAAC,QAAAC,QAAA;IAAA1D,OAAAyD,aAEIrI,MAAMgF,OAAO;IAAA,OAAAqD;GAAA,EAAA,CAAA,IAEd;EAAI,EAAA,CAAA,GAAA7B,KAAA;EAAA5B,OAAA4B,aAC+BxG,MAAMJ,QAAQ;EAAAgF,OAAAwB,cAAA;GAAA,IAAAmC,OAAArB,WAAA,CAAA,CACpDlH,MAAMiF,QAAQ;GAAA,aAAdsD,KAAA,WAAA;IAAA,IAAAC,QAAAC,QAAA;IAAA7D,OAAA4D,aAEIxI,MAAMiF,QAAQ;IAAA,OAAAuD;GAAA,EAAA,CAAA,IAEf;EAAI,EAAA,CAAA,GAAA,IAAA;EAAAE,QAAAC,QAAA;GAAA,IAAAC,MAjCK7C,aAAa,IAAI,wBAAwBlE,KAAAA,GAASgH,OACtD3L,GACL,QACA,CAAC6I,aAAa,KAAK,YACnB,8DAKAA,aAAa,KACX,oHACJ;GAAC6C,QAAAD,IAAAG,KAAAC,aAAAzC,OAAA,aAAAqC,IAAAG,IAAAF,GAAA;GAAAC,SAAAF,IAAAK,KAAAC,UAAA3C,OAAAqC,IAAAK,IAAAH,IAAA;GAAA,OAAAF;EAAA,GAAA;GAAAG,GAAAjH,KAAAA;GAAAmH,GAAAnH,KAAAA;EAAA,CAAA;EAAA,OAAAuE;CAAA,EAAA,CAAA;AAyBT;;;;;;;;;;AAWA,SAAgB8C,eAAenJ,OAAsD;CACnF,MAAM,CAACC,OAAOC,QAAQjD,WAAW+C,OAAO,CAAC,SAAS,QAAQ,CAAC;CAC3D,cAAA;EAAA,IAAAqJ,QAAAC,QAAA;EAAA7E,OAAA4E,OAAA3E,WAAA;GAAA,KAAA,iBAAA;IAAA,OAGiBzE,MAAMmJ,SAAS,KAAKtH,KAAAA;GAAS;GAAA,KAAA,WAAA;IAAA,OACnC3E,GACL,0EACA,oGACA8C,MAAM2E,KACR;GAAC;EAAA,GACG1E,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAmJ;CAAA,EAAA,CAAA;AAGd;AAQA,IAAMlB,yBAAwE5J,UAC5EA,MAAM2F,eAAe;AAGvB,IAAMgE,2BAA0E3J,UAC9EA,MAAM6H,gBAAgB;AAExB,SAAStC,eAAe5B,QAA4B6B,eAAqC;CACvF,IAAI,EAAE7B,kBAAkBpC,YAAYoC,WAAW6B,eAAe,OAAO;CACrE,OAAOV,QACLnB,OAAOb,QACL,8GACF,CACF;AACF;AAEA,SAASuF,YACP4C,UACAC,UACmC;CACnC,QAAQ3L,YAAY;EAClB0L,SAAS1L,OAAO;EAChB,MAAM+I,MAAM4C,SAAS;EACrB,IAAI,OAAO5C,QAAQ,YAAYA,IAAI/I,OAAO;CAC5C;AACF;AAE2D4L,eAAA;CAAA;CAAA;CAAA;AAAA,CAAA"}
|
|
@@ -8,6 +8,12 @@ import type { ComponentProps } from 'solid-js';
|
|
|
8
8
|
* narrow screens the middle track yields space to the controls, and each side
|
|
9
9
|
* group scrolls when necessary so keyboard focus can reveal every action.
|
|
10
10
|
*
|
|
11
|
+
* The centre track is sized for a lone search field. A bar whose centre holds
|
|
12
|
+
* more than that, such as a search plus an action beside it, passes
|
|
13
|
+
* `centerWidth="wide"`: the centre then stays fluid below 1280px, never
|
|
14
|
+
* narrower than 7rem so its controls are not squeezed out by a long leading
|
|
15
|
+
* group (which scrolls instead), and takes a wider fixed track above it.
|
|
16
|
+
*
|
|
11
17
|
* On a frameless desktop window this row is also the drag region, so it carries
|
|
12
18
|
* `window-drag` and the controls inside it opt back out. `macos-inset` and
|
|
13
19
|
* `windows-inset` reserve the space the native window controls occupy, which is
|
|
@@ -23,6 +29,13 @@ export type TopBarProps = ComponentProps<'header'> & {
|
|
|
23
29
|
windowsInset?: boolean;
|
|
24
30
|
/** Blur the content scrolling under the bar. */
|
|
25
31
|
glass?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* How wide the centre track is. `field` (default) suits a lone search field.
|
|
34
|
+
* `wide` suits a centre that also holds an action: fluid below 1280px and
|
|
35
|
+
* never narrower than 7rem (the side groups scroll to make room), then a
|
|
36
|
+
* clamp of 32vw up to 672px between equal side tracks.
|
|
37
|
+
*/
|
|
38
|
+
centerWidth?: 'field' | 'wide';
|
|
26
39
|
};
|
|
27
40
|
export declare function TopBar(props: TopBarProps): import("solid-js").JSX.Element;
|
|
28
41
|
/** A scrollable side group; equal wide-screen tracks centre the search. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"top-bar.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAK9C
|
|
1
|
+
{"version":3,"file":"top-bar.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAK9C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IACnD,sDAAsD;IACtD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAC/B,CAAA;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,kCA8BxC;AAYD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;CAAE,kCAqBvF;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,mEAAmE;IACnE,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC3B,kCAgBF;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAY5D;AAED,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IACzD,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kCA8BpD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,kCAqB7E"}
|
|
@@ -16,12 +16,13 @@ function TopBar(props) {
|
|
|
16
16
|
"draggable",
|
|
17
17
|
"macosInset",
|
|
18
18
|
"windowsInset",
|
|
19
|
-
"glass"
|
|
19
|
+
"glass",
|
|
20
|
+
"centerWidth"
|
|
20
21
|
]);
|
|
21
22
|
return (() => {
|
|
22
23
|
var _el$ = _tmpl$();
|
|
23
24
|
spread(_el$, mergeProps({ get ["class"]() {
|
|
24
|
-
return cn("h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]", local.glass && "glass-panel", local.draggable && "window-drag", local.macosInset ? "window-inset-macos" : "ps-3", local.windowsInset ? "window-inset-windows" : "pe-3", local.class);
|
|
25
|
+
return cn("h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]", local.centerWidth === "wide" && "grid-cols-[auto_minmax(7rem,1fr)_auto] gap-2 md:grid-cols-[auto_minmax(7rem,1fr)_auto] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)]", local.glass && "glass-panel", local.draggable && "window-drag", local.macosInset ? "window-inset-macos" : "ps-3", local.windowsInset ? "window-inset-windows" : "pe-3", local.class);
|
|
25
26
|
} }, rest), false, false);
|
|
26
27
|
return _el$;
|
|
27
28
|
})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"top-bar.js","names":["ComponentProps","Show","splitProps","Search","cn","TopBarProps","draggable","macosInset","windowsInset","glass","TopBar","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","class","invokeFocusHandler","handler","event","Event","Array","isArray","callback","data","TopBarSection","align","_el$2","_tmpl$2","$$focusin","target","HTMLElement","scrollIntoView","block","inline","onFocusIn","TopBarTitle","_el$3","_tmpl$3","TopBarBreadcrumb","_el$4","_tmpl$4","TopBarSearchProps","placeholder","shortcut","keyshortcuts","TopBarSearch","_el$5","_tmpl$6","_el$6","firstChild","aria-keyshortcuts","_$insert","_$createComponent","when","children","_el$7","_tmpl$5","TopBarPill","label","_el$8","_tmpl$7","_el$9","_$delegateEvents","_$mergeProps","_$createComponent","_$delegateEvents"],"sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { Search } from 'lucide-solid'\nimport { cn } from '#lib/utils'\n\n/**\n * TopBar.\n *\n * The window's title row and primary toolbar. Three tracks: a leading group, a\n * centred search, and a trailing group. On wide screens the search is a fixed\n * function of the window width and the side groups are equal remainders. On\n * narrow screens the middle track yields space to the controls, and each side\n * group scrolls when necessary so keyboard focus can reveal every action.\n *\n * On a frameless desktop window this row is also the drag region, so it carries\n * `window-drag` and the controls inside it opt back out. `macos-inset` and\n * `windows-inset` reserve the space the native window controls occupy, which is\n * why the row can look off-centre in a frameless window and be correct: it is\n * centred in the space the user can actually see.\n */\nexport type TopBarProps = ComponentProps<'header'> & {\n /** Make the row draggable, for a frameless window. */\n draggable?: boolean\n /** Reserve space for the macOS traffic lights. */\n macosInset?: boolean\n /** Reserve space for the Windows caption buttons. */\n windowsInset?: boolean\n /** Blur the content scrolling under the bar. */\n glass?: boolean\n}\n\nexport function TopBar(props: TopBarProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'draggable',\n 'macosInset',\n 'windowsInset',\n 'glass',\n ])\n\n return (\n <header\n data-slot=\"top-bar\"\n class={cn(\n 'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',\n local.glass && 'glass-panel',\n local.draggable && 'window-drag',\n /* Each edge gets exactly one inline-padding utility. A window inset\n and px-3 would share a layer and tie-break by build order, and a\n build that put px-3 last hid the leading controls under the\n traffic lights. */\n local.macosInset ? 'window-inset-macos' : 'ps-3',\n local.windowsInset ? 'window-inset-windows' : 'pe-3',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nfunction invokeFocusHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\n/** A scrollable side group; equal wide-screen tracks centre the search. */\nexport function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' | 'end' }) {\n const [local, rest] = splitProps(props, ['class', 'align', 'onFocusIn'])\n\n return (\n <div\n data-slot=\"top-bar-section\"\n class={cn(\n 'window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden',\n local.align === 'end' && 'md:justify-end',\n local.align !== 'end' && 'justify-start',\n local.class\n )}\n onFocusIn={(event) => {\n if (event.target instanceof HTMLElement) {\n event.target.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n }\n invokeFocusHandler(local.onFocusIn, event)\n }}\n {...rest}\n />\n )\n}\n\nexport function TopBarTitle(\n props: ComponentProps<'div'> & {\n /** Aligns text within this slot; it does not position the slot. */\n align?: 'start' | 'center'\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'align'])\n return (\n <div\n data-slot=\"top-bar-title\"\n class={cn(\n /* The title slot sits in the drag region; a link, button or field\n placed in it (a breadcrumb, a view's search) must stay pressable. */\n 'truncate text-sm font-medium [&_:is(a,button,input)]:window-no-drag',\n local.align === 'center' && 'text-center',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** A breadcrumb or path readout that yields to the actions when space runs out. */\nexport function TopBarBreadcrumb(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"top-bar-breadcrumb\"\n class={cn(\n 'flex min-w-0 items-center gap-1.5 text-sm [&_:is(a,button,input)]:window-no-drag',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport type TopBarSearchProps = ComponentProps<'button'> & {\n /** The hint shown inside the field, e.g. \"Search projects and files\". */\n placeholder?: string\n /** The shortcut chip drawn at the trailing edge, e.g. \"⌘K\". */\n shortcut?: string\n /**\n * The parseable chord behind `shortcut`, e.g. `Meta+K`. The chip is\n * `aria-hidden` — reading it out would pollute the button's accessible\n * name — so this is what assistive technology announces instead. Supply it\n * whenever `shortcut` is drawn.\n */\n keyshortcuts?: string\n}\n\n/**\n * The search affordance in the middle track.\n *\n * It is a button, not an input, and that is deliberate: clicking it opens the\n * command palette, which already owns the input, the ranking and the keyboard\n * handling. A second live input here would be a second source of truth for the\n * same query. It is styled to read as a field because that is what it behaves\n * like from the user's side.\n */\nexport function TopBarSearch(props: TopBarSearchProps) {\n const [local, rest] = splitProps(props, ['class', 'placeholder', 'shortcut', 'keyshortcuts'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-search\"\n class={cn(\n 'text-muted-foreground flex h-control-md w-full min-w-(--control-height-md) items-center justify-center gap-2 rounded-md border border-input px-3 text-sm sm:justify-start',\n 'transition-colors ease-out outline-none',\n 'hover:bg-surface-hover hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag',\n local.class\n )}\n aria-keyshortcuts={local.keyshortcuts}\n {...rest}\n >\n <Search aria-hidden=\"true\" class=\"size-4 shrink-0 sm:hidden\" />\n <span class=\"sr-only sm:not-sr-only sm:truncate\">{local.placeholder ?? 'Search'}</span>\n <Show when={local.shortcut}>\n <kbd\n aria-hidden=\"true\"\n class=\"bg-muted ms-auto hidden h-5 shrink-0 items-center rounded-sm border border-border px-1.5 font-code text-code sm:flex\"\n >\n {local.shortcut}\n </kbd>\n </Show>\n </button>\n )\n}\n\n/**\n * A labelled top-bar control — the readouts KiroCrew puts in the trailing\n * group. Container queries on the group decide which of these survive a narrow\n * window; a caller marks the cheap-to-drop ones and the group handles the rest.\n */\nexport function TopBarPill(props: ComponentProps<'button'> & { label: string }) {\n const [local, rest] = splitProps(props, ['class', 'label', 'children'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-pill\"\n class={cn(\n 'text-muted-foreground inline-flex h-control-sm shrink-0 items-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium',\n 'transition-colors ease-out outline-none',\n 'hover:border-input hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag [&_svg]:size-3.5 [&_svg]:shrink-0',\n local.class\n )}\n {...rest}\n >\n {local.children}\n <span>{local.label}</span>\n </button>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"top-bar.js","names":["ComponentProps","Show","splitProps","Search","cn","TopBarProps","draggable","macosInset","windowsInset","glass","centerWidth","TopBar","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","class","invokeFocusHandler","handler","event","Event","Array","isArray","callback","data","TopBarSection","align","_el$2","_tmpl$2","$$focusin","target","HTMLElement","scrollIntoView","block","inline","onFocusIn","TopBarTitle","_el$3","_tmpl$3","TopBarBreadcrumb","_el$4","_tmpl$4","TopBarSearchProps","placeholder","shortcut","keyshortcuts","TopBarSearch","_el$5","_tmpl$6","_el$6","firstChild","aria-keyshortcuts","_$insert","_$createComponent","when","children","_el$7","_tmpl$5","TopBarPill","label","_el$8","_tmpl$7","_el$9","_$delegateEvents","_$mergeProps","_$createComponent","_$delegateEvents"],"sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { Search } from 'lucide-solid'\nimport { cn } from '#lib/utils'\n\n/**\n * TopBar.\n *\n * The window's title row and primary toolbar. Three tracks: a leading group, a\n * centred search, and a trailing group. On wide screens the search is a fixed\n * function of the window width and the side groups are equal remainders. On\n * narrow screens the middle track yields space to the controls, and each side\n * group scrolls when necessary so keyboard focus can reveal every action.\n *\n * The centre track is sized for a lone search field. A bar whose centre holds\n * more than that, such as a search plus an action beside it, passes\n * `centerWidth=\"wide\"`: the centre then stays fluid below 1280px, never\n * narrower than 7rem so its controls are not squeezed out by a long leading\n * group (which scrolls instead), and takes a wider fixed track above it.\n *\n * On a frameless desktop window this row is also the drag region, so it carries\n * `window-drag` and the controls inside it opt back out. `macos-inset` and\n * `windows-inset` reserve the space the native window controls occupy, which is\n * why the row can look off-centre in a frameless window and be correct: it is\n * centred in the space the user can actually see.\n */\nexport type TopBarProps = ComponentProps<'header'> & {\n /** Make the row draggable, for a frameless window. */\n draggable?: boolean\n /** Reserve space for the macOS traffic lights. */\n macosInset?: boolean\n /** Reserve space for the Windows caption buttons. */\n windowsInset?: boolean\n /** Blur the content scrolling under the bar. */\n glass?: boolean\n /**\n * How wide the centre track is. `field` (default) suits a lone search field.\n * `wide` suits a centre that also holds an action: fluid below 1280px and\n * never narrower than 7rem (the side groups scroll to make room), then a\n * clamp of 32vw up to 672px between equal side tracks.\n */\n centerWidth?: 'field' | 'wide'\n}\n\nexport function TopBar(props: TopBarProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'draggable',\n 'macosInset',\n 'windowsInset',\n 'glass',\n 'centerWidth',\n ])\n\n return (\n <header\n data-slot=\"top-bar\"\n class={cn(\n 'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',\n local.centerWidth === 'wide' &&\n 'grid-cols-[auto_minmax(7rem,1fr)_auto] gap-2 md:grid-cols-[auto_minmax(7rem,1fr)_auto] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)]',\n local.glass && 'glass-panel',\n local.draggable && 'window-drag',\n /* Each edge gets exactly one inline-padding utility. A window inset\n and px-3 would share a layer and tie-break by build order, and a\n build that put px-3 last hid the leading controls under the\n traffic lights. */\n local.macosInset ? 'window-inset-macos' : 'ps-3',\n local.windowsInset ? 'window-inset-windows' : 'pe-3',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nfunction invokeFocusHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\n/** A scrollable side group; equal wide-screen tracks centre the search. */\nexport function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' | 'end' }) {\n const [local, rest] = splitProps(props, ['class', 'align', 'onFocusIn'])\n\n return (\n <div\n data-slot=\"top-bar-section\"\n class={cn(\n 'window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden',\n local.align === 'end' && 'md:justify-end',\n local.align !== 'end' && 'justify-start',\n local.class\n )}\n onFocusIn={(event) => {\n if (event.target instanceof HTMLElement) {\n event.target.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n }\n invokeFocusHandler(local.onFocusIn, event)\n }}\n {...rest}\n />\n )\n}\n\nexport function TopBarTitle(\n props: ComponentProps<'div'> & {\n /** Aligns text within this slot; it does not position the slot. */\n align?: 'start' | 'center'\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'align'])\n return (\n <div\n data-slot=\"top-bar-title\"\n class={cn(\n /* The title slot sits in the drag region; a link, button or field\n placed in it (a breadcrumb, a view's search) must stay pressable. */\n 'truncate text-sm font-medium [&_:is(a,button,input)]:window-no-drag',\n local.align === 'center' && 'text-center',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** A breadcrumb or path readout that yields to the actions when space runs out. */\nexport function TopBarBreadcrumb(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"top-bar-breadcrumb\"\n class={cn(\n 'flex min-w-0 items-center gap-1.5 text-sm [&_:is(a,button,input)]:window-no-drag',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport type TopBarSearchProps = ComponentProps<'button'> & {\n /** The hint shown inside the field, e.g. \"Search projects and files\". */\n placeholder?: string\n /** The shortcut chip drawn at the trailing edge, e.g. \"⌘K\". */\n shortcut?: string\n /**\n * The parseable chord behind `shortcut`, e.g. `Meta+K`. The chip is\n * `aria-hidden` — reading it out would pollute the button's accessible\n * name — so this is what assistive technology announces instead. Supply it\n * whenever `shortcut` is drawn.\n */\n keyshortcuts?: string\n}\n\n/**\n * The search affordance in the middle track.\n *\n * It is a button, not an input, and that is deliberate: clicking it opens the\n * command palette, which already owns the input, the ranking and the keyboard\n * handling. A second live input here would be a second source of truth for the\n * same query. It is styled to read as a field because that is what it behaves\n * like from the user's side.\n */\nexport function TopBarSearch(props: TopBarSearchProps) {\n const [local, rest] = splitProps(props, ['class', 'placeholder', 'shortcut', 'keyshortcuts'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-search\"\n class={cn(\n 'text-muted-foreground flex h-control-md w-full min-w-(--control-height-md) items-center justify-center gap-2 rounded-md border border-input px-3 text-sm sm:justify-start',\n 'transition-colors ease-out outline-none',\n 'hover:bg-surface-hover hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag',\n local.class\n )}\n aria-keyshortcuts={local.keyshortcuts}\n {...rest}\n >\n <Search aria-hidden=\"true\" class=\"size-4 shrink-0 sm:hidden\" />\n <span class=\"sr-only sm:not-sr-only sm:truncate\">{local.placeholder ?? 'Search'}</span>\n <Show when={local.shortcut}>\n <kbd\n aria-hidden=\"true\"\n class=\"bg-muted ms-auto hidden h-5 shrink-0 items-center rounded-sm border border-border px-1.5 font-code text-code sm:flex\"\n >\n {local.shortcut}\n </kbd>\n </Show>\n </button>\n )\n}\n\n/**\n * A labelled top-bar control — the readouts KiroCrew puts in the trailing\n * group. Container queries on the group decide which of these survive a narrow\n * window; a caller marks the cheap-to-drop ones and the group handles the rest.\n */\nexport function TopBarPill(props: ComponentProps<'button'> & { label: string }) {\n const [local, rest] = splitProps(props, ['class', 'label', 'children'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-pill\"\n class={cn(\n 'text-muted-foreground inline-flex h-control-sm shrink-0 items-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium',\n 'transition-colors ease-out outline-none',\n 'hover:border-input hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag [&_svg]:size-3.5 [&_svg]:shrink-0',\n local.class\n )}\n {...rest}\n >\n {local.children}\n <span>{local.label}</span>\n </button>\n )\n}\n"],"mappings":";;;;;;;;;;;;AA4CA,SAAgBW,OAAOC,OAAoB;CACzC,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;CAAa,CACd;CAED,cAAA;EAAA,IAAAG,OAAAC,OAAA;EAAAC,OAAAF,MAAAG,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWd,GACL,yOACAS,MAAMH,gBAAgB,UACpB,6JACFG,MAAMJ,SAAS,eACfI,MAAMP,aAAa,eAKnBO,MAAMN,aAAa,uBAAuB,QAC1CM,MAAML,eAAe,yBAAyB,QAC9CK,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAC;CAAA,EAAA,CAAA;AAGd;AAEA,SAASK,mBAAmBC,SAAkBC,OAAc;CAC1D,IAAIE,MAAMC,QAAQJ,OAAO,GAAG;EAC1B,MAAM,CAACK,UAAUC,QAAQN;EACzBK,SAASC,MAAML,KAAK;CACtB,OAAO,IAAI,OAAOD,YAAY,YAE5BK,QAASJ,KAAK;AAElB;;AAGA,SAAgBM,cAAchB,OAA4D;CACxF,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EAAC;EAAS;EAAS;CAAW,CAAC;CAEvE,cAAA;EAAA,IAAAkB,QAAAC,QAAA;EAAAD,MAAAE,aASgBV,UAAU;GACpB,IAAIA,MAAMW,kBAAkBC,aAC1BZ,MAAMW,OAAOE,eAAe;IAAEC,OAAO;IAAWC,QAAQ;GAAU,CAAC;GAErEjB,mBAAmBP,MAAMyB,WAAWhB,KAAK;EAC3C;EAACL,OAAAa,OAAAZ,WAAA,EAAA,KAAA,WAAA;GAAA,OAXMd,GACL,oFACAS,MAAMgB,UAAU,SAAS,kBACzBhB,MAAMgB,UAAU,SAAS,iBACzBhB,MAAMM,KACR;EAAC,EAAA,GAOGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAgB;CAAA,EAAA,CAAA;AAGd;AAEA,SAAgBS,YACd3B,OAIA;CACA,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO,CAAC,SAAS,OAAO,CAAC;CAC1D,cAAA;EAAA,IAAA4B,QAAAC,QAAA;EAAAxB,OAAAuB,OAAAtB,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWd,GAGL,uEACAS,MAAMgB,UAAU,YAAY,eAC5BhB,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAA0B;CAAA,EAAA,CAAA;AAGd;;AAGA,SAAgBE,iBAAiB9B,OAA8B;CAC7D,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO,CAAC,OAAO,CAAC;CACjD,cAAA;EAAA,IAAA+B,QAAAC,QAAA;EAAA3B,OAAA0B,OAAAzB,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWd,GACL,oFACAS,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAA6B;CAAA,EAAA,CAAA;AAGd;;;;;;;;;;AAyBA,SAAgBM,aAAarC,OAA0B;CACrD,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EAAC;EAAS;EAAe;EAAY;CAAc,CAAC;CAE5F,cAAA;EAAA,IAAAsC,QAAAC,QAAA,GAAAC,QAAAF,MAAAG;EAAApC,OAAAiC,OAAAhC,WAAA;GAAA,KAAA,WAAA;IAAA,OAIWd,GACL,6KACA,2CACA,gDACA,oFACA,kBACAS,MAAMM,KACR;GAAC;GAAA,KAAA,uBAAA;IAAA,OACkBN,MAAMmC;GAAY;EAAA,GACjClC,IAAI,GAAA,OAAA,IAAA;EAAAyC,OAAAL,OAAAM,gBAEPrD,QAAM;GAAA,eAAA;GAAA,SAAA;EAAA,CAAA,GAAAiD,KAAA;EAAAG,OAAAH,aAC2CvC,MAAMiC,eAAe,QAAQ;EAAAS,OAAAL,OAAAM,gBAC9EvD,MAAI;GAAA,IAACwD,OAAI;IAAA,OAAE5C,MAAMkC;GAAQ;GAAA,IAAAW,WAAA;IAAA,IAAAC,QAAAC,QAAA;IAAAL,OAAAI,aAKrB9C,MAAMkC,QAAQ;IAAA,OAAAY;GAAA;EAAA,CAAA,GAAA,IAAA;EAAA,OAAAT;CAAA,EAAA,CAAA;AAKzB;;;;;;AAOA,SAAgBW,WAAWjD,OAAqD;CAC9E,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EAAC;EAAS;EAAS;CAAU,CAAC;CAEtE,cAAA;EAAA,IAAAmD,QAAAC,QAAA,GAAAC,QAAAF,MAAAV;EAAApC,OAAA8C,OAAA7C,WAAA,EAAA,KAAA,WAAA;GAAA,OAIWd,GACL,2IACA,2CACA,4CACA,oFACA,oDACAS,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,IAAA;EAAAyC,OAAAQ,aAEPlD,MAAM6C,UAAQO,KAAA;EAAAV,OAAAU,aACRpD,MAAMiD,KAAK;EAAA,OAAAC;CAAA,EAAA,CAAA;AAGxB;AAACG,eAAA,CAAA,SAAA,CAAA"}
|
|
@@ -58,15 +58,16 @@ export declare function Callout(props: CalloutProps): import("solid-js").JSX.Ele
|
|
|
58
58
|
* level would break it. Pass `role="heading"` with `aria-level` where the
|
|
59
59
|
* content does belong in the outline.
|
|
60
60
|
*
|
|
61
|
-
* The title
|
|
61
|
+
* The title carries a 4px (`mb-1`) margin below it, whatever follows: a
|
|
62
62
|
* paragraph, a list, or bare text. CSS cannot see text nodes, so a selector such
|
|
63
63
|
* as `:last-child` or `:only-child` cannot tell `<Title/>text` (needs the gap)
|
|
64
64
|
* from `<Title/>` (needs none), and `not-last:mb-1` silently dropped the gap for
|
|
65
|
-
* bare text, the most common body.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
65
|
+
* bare text, the most common body. So the gap stays the default and the one case
|
|
66
|
+
* that does not want it is detected by `Callout`, which sees its rendered
|
|
67
|
+
* children: a callout whose only child is a title carries `data-title-only`, and
|
|
68
|
+
* the title drops its margin so the block ends at its own padding. A caller never
|
|
69
|
+
* passes `mb-0` for this. Anything else after the title, text included, keeps the
|
|
70
|
+
* gap.
|
|
70
71
|
*/
|
|
71
72
|
export declare function CalloutTitle(props: ComponentProps<'div'>): import("solid-js").JSX.Element;
|
|
72
73
|
//# sourceMappingURL=callout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callout.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/callout/callout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAGtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,eAAe;;;8EAsB3B,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"callout.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/callout/callout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAE9C,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAGtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,eAAO,MAAM,eAAe;;;8EAsB3B,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAA;AAYvF,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,kCAoB1C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCASxD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
2
|
import { cva } from "../../../lib/variants.js";
|
|
3
|
-
import { mergeProps, spread, template } from "solid-js/web";
|
|
4
|
-
import { splitProps } from "solid-js";
|
|
3
|
+
import { insert, mergeProps, spread, template } from "solid-js/web";
|
|
4
|
+
import { children, createMemo, splitProps } from "solid-js";
|
|
5
5
|
//#region src/components/ui/callout/callout.tsx
|
|
6
6
|
var _tmpl$ = /*#__PURE__*/ template(`<div data-slot=callout role=note>`);
|
|
7
7
|
var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=callout-title>`);
|
|
@@ -74,20 +74,34 @@ var calloutVariants = cva("bg-card text-card-foreground block rounded-none wrap-
|
|
|
74
74
|
size: "md"
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
|
+
var isRendered = (node) => node != null && typeof node !== "boolean" && (typeof node !== "string" || node.trim() !== "");
|
|
78
|
+
var isCalloutTitle = (node) => typeof node === "object" && node !== null && "getAttribute" in node && typeof node.getAttribute === "function" && node.getAttribute("data-slot") === "callout-title";
|
|
77
79
|
function Callout(props) {
|
|
78
80
|
const [local, rest] = splitProps(props, [
|
|
79
81
|
"class",
|
|
80
82
|
"variant",
|
|
81
|
-
"size"
|
|
83
|
+
"size",
|
|
84
|
+
"children"
|
|
82
85
|
]);
|
|
86
|
+
const resolved = children(() => local.children);
|
|
87
|
+
const titleOnly = createMemo(() => {
|
|
88
|
+
const nodes = resolved.toArray().filter(isRendered);
|
|
89
|
+
return nodes.length === 1 && isCalloutTitle(nodes[0]);
|
|
90
|
+
});
|
|
83
91
|
return (() => {
|
|
84
92
|
var _el$ = _tmpl$();
|
|
85
|
-
spread(_el$, mergeProps({
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
spread(_el$, mergeProps({
|
|
94
|
+
get ["data-title-only"]() {
|
|
95
|
+
return titleOnly() ? "" : void 0;
|
|
96
|
+
},
|
|
97
|
+
get ["class"]() {
|
|
98
|
+
return cn(calloutVariants({
|
|
99
|
+
variant: local.variant,
|
|
100
|
+
size: local.size
|
|
101
|
+
}), local.class);
|
|
102
|
+
}
|
|
103
|
+
}, rest), false, true);
|
|
104
|
+
insert(_el$, resolved);
|
|
91
105
|
return _el$;
|
|
92
106
|
})();
|
|
93
107
|
}
|
|
@@ -97,22 +111,23 @@ function Callout(props) {
|
|
|
97
111
|
* level would break it. Pass `role="heading"` with `aria-level` where the
|
|
98
112
|
* content does belong in the outline.
|
|
99
113
|
*
|
|
100
|
-
* The title
|
|
114
|
+
* The title carries a 4px (`mb-1`) margin below it, whatever follows: a
|
|
101
115
|
* paragraph, a list, or bare text. CSS cannot see text nodes, so a selector such
|
|
102
116
|
* as `:last-child` or `:only-child` cannot tell `<Title/>text` (needs the gap)
|
|
103
117
|
* from `<Title/>` (needs none), and `not-last:mb-1` silently dropped the gap for
|
|
104
|
-
* bare text, the most common body.
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
118
|
+
* bare text, the most common body. So the gap stays the default and the one case
|
|
119
|
+
* that does not want it is detected by `Callout`, which sees its rendered
|
|
120
|
+
* children: a callout whose only child is a title carries `data-title-only`, and
|
|
121
|
+
* the title drops its margin so the block ends at its own padding. A caller never
|
|
122
|
+
* passes `mb-0` for this. Anything else after the title, text included, keeps the
|
|
123
|
+
* gap.
|
|
109
124
|
*/
|
|
110
125
|
function CalloutTitle(props) {
|
|
111
126
|
const [local, rest] = splitProps(props, ["class"]);
|
|
112
127
|
return (() => {
|
|
113
128
|
var _el$2 = _tmpl$2();
|
|
114
129
|
spread(_el$2, mergeProps({ get ["class"]() {
|
|
115
|
-
return cn("mb-1 font-medium tracking-tight", local.class);
|
|
130
|
+
return cn("mb-1 font-medium tracking-tight in-data-title-only:mb-0", local.class);
|
|
116
131
|
} }, rest), false, false);
|
|
117
132
|
return _el$2;
|
|
118
133
|
})();
|