@adea-ai/ui 0.135.1 → 0.137.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/index.d.ts +1 -1
- package/dist/components/composites/tree/index.d.ts.map +1 -1
- package/dist/components/composites/tree/tree.d.ts +45 -1
- package/dist/components/composites/tree/tree.d.ts.map +1 -1
- package/dist/components/composites/tree/tree.js +52 -2
- package/dist/components/composites/tree/tree.js.map +1 -1
- package/dist/components/ui/callout/callout.d.ts +72 -0
- package/dist/components/ui/callout/callout.d.ts.map +1 -0
- package/dist/components/ui/callout/callout.js +123 -0
- package/dist/components/ui/callout/callout.js.map +1 -0
- package/dist/components/ui/callout/index.d.ts +2 -0
- package/dist/components/ui/callout/index.d.ts.map +1 -0
- package/dist/components/ui/callout/index.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/r/callout.json +22 -0
- package/dist/r/registry.json +22 -0
- package/dist/r/src/components/composites/tree/index.ts +8 -1
- package/dist/r/src/components/composites/tree/tree.tsx +98 -7
- package/dist/r/src/components/ui/callout/callout.tsx +115 -0
- package/dist/r/src/components/ui/callout/index.ts +1 -0
- package/package.json +1 -1
- package/registry.json +22 -0
- package/src/components/composites/tree/index.ts +8 -1
- package/src/components/composites/tree/tree.tsx +98 -7
- package/src/components/ui/callout/callout.tsx +115 -0
- package/src/components/ui/callout/index.ts +1 -0
- package/src/index.ts +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Keyboard-accessible tree rows with virtualization-aware focus handoff. */
|
|
2
|
-
export { Tree, TreeRow, TreeRowActions, type TreeProps, type TreeRowProps } from './tree';
|
|
2
|
+
export { Tree, TreeRow, TreeRowActions, type TreeDisclosure, type TreeProps, type TreeRowProps, } from './tree';
|
|
3
3
|
export type { TreeItemDescriptor } from './tree-navigation';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/tree/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/tree/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,OAAO,EACL,IAAI,EACJ,OAAO,EACP,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,YAAY,GAClB,MAAM,QAAQ,CAAA;AACf,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { ComponentProps, JSX } from 'solid-js';
|
|
2
2
|
import { type TreeItemDescriptor } from './tree-navigation';
|
|
3
|
+
/**
|
|
4
|
+
* Who owns the pointer on a row's disclosure chevron.
|
|
5
|
+
*
|
|
6
|
+
* - `row` (default): the chevron is decoration; a click anywhere on the row,
|
|
7
|
+
* chevron included, is a row activation. This is the file-explorer
|
|
8
|
+
* convention, where the host's `onActivate` toggles a folder.
|
|
9
|
+
* - `marker`: the chevron of an expandable row is its own pointer target. It
|
|
10
|
+
* calls `onExpand(id, !expanded)` and nothing else; the rest of the row still
|
|
11
|
+
* activates. For trees where expanding and selecting are independent (a
|
|
12
|
+
* source list that selects a source and separately reveals its details).
|
|
13
|
+
*/
|
|
14
|
+
export type TreeDisclosure = 'row' | 'marker';
|
|
3
15
|
type TreeAccessibleName = {
|
|
4
16
|
'aria-label': string;
|
|
5
17
|
'aria-labelledby'?: string;
|
|
@@ -12,10 +24,19 @@ export type TreeProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'tabIn
|
|
|
12
24
|
visibleItems: readonly TreeItemDescriptor[];
|
|
13
25
|
/** The host-owned identity of the row that currently owns roving focus. */
|
|
14
26
|
activeId: string | null;
|
|
15
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Called when keyboard or pointer navigation changes the active row, and when
|
|
29
|
+
* the active row leaves `visibleItems` (collapsed away, filtered out) and the
|
|
30
|
+
* first visible row takes over as the roving stop.
|
|
31
|
+
*/
|
|
16
32
|
onActiveIdChange: (id: string) => void;
|
|
17
33
|
/** Called for Left/Right expand and collapse requests. */
|
|
18
34
|
onExpand?: (id: string, expanded: boolean) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Opt-in pointer behaviour of the disclosure chevron; see {@link TreeDisclosure}.
|
|
37
|
+
* Keyboard behaviour is identical in both modes: Left/Right expand and collapse.
|
|
38
|
+
*/
|
|
39
|
+
disclosure?: TreeDisclosure;
|
|
19
40
|
/** Called for Enter or pointer activation. Domain action stays host-owned. */
|
|
20
41
|
onActivate?: (id: string, event: Event) => void;
|
|
21
42
|
/** Called for Space selection. Selection state stays host-owned. */
|
|
@@ -39,6 +60,20 @@ export type TreeProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'tabIn
|
|
|
39
60
|
*
|
|
40
61
|
* Kobalte 0.13.14 has no Tree primitive, so this component implements the
|
|
41
62
|
* WAI-ARIA tree row navigation contract and leaves product state to callbacks.
|
|
63
|
+
*
|
|
64
|
+
* By default the disclosure chevron is decoration and a click anywhere on a
|
|
65
|
+
* row activates it, so a file explorer can toggle a folder from `onActivate`.
|
|
66
|
+
* Reach for `disclosure="marker"` when expanding and selecting are separate
|
|
67
|
+
* decisions: the chevron then calls only `onExpand`, and the rest of the row
|
|
68
|
+
* calls only `onActivate`. The chevron stays `aria-hidden` and out of the tab
|
|
69
|
+
* order in both modes; keyboard expansion is Left/Right either way.
|
|
70
|
+
*
|
|
71
|
+
* Collapsing is the one thing that can move focus without a key or a click on
|
|
72
|
+
* the target row: when the active row leaves `visibleItems`, the first visible
|
|
73
|
+
* row becomes the roving stop and `onActiveIdChange` is called with it. If the
|
|
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. This is the
|
|
76
|
+
* same in both `disclosure` modes and for every way a host collapses a row.
|
|
42
77
|
*/
|
|
43
78
|
export declare function Tree(props: TreeProps): JSX.Element;
|
|
44
79
|
export type TreeRowProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'tabIndex' | 'onClick' | 'onFocus' | 'onDoubleClick' | 'style' | 'aria-level' | 'aria-expanded' | 'aria-selected' | 'aria-posinset' | 'aria-setsize'> & {
|
|
@@ -58,6 +93,15 @@ export type TreeRowProps = Omit<ComponentProps<'div'>, 'children' | 'role' | 'ta
|
|
|
58
93
|
* Keeps the donor's disclosure marker, leading file/folder affordance, primary
|
|
59
94
|
* label and trailing-action hierarchy while replacing the donor's button and
|
|
60
95
|
* runtime callbacks with a shared treeitem and host-owned slots/actions.
|
|
96
|
+
*
|
|
97
|
+
* With `Tree`'s `disclosure="marker"` the chevron of an expandable row is a
|
|
98
|
+
* separate pointer target (`data-slot="tree-row-disclosure"`) that only calls
|
|
99
|
+
* `onExpand`. It is not focusable and has no role: the row remains the single
|
|
100
|
+
* focus stop, Enter and Space still activate and select, and Left/Right still
|
|
101
|
+
* expand and collapse, so the marker adds a pointer path and nothing else.
|
|
102
|
+
* Pressing it does not move focus or the roving stop to its row; if the toggle
|
|
103
|
+
* hides the focused row, `Tree` recovers focus as it does for any collapse (see
|
|
104
|
+
* the `Tree` comment and docs/tree.md).
|
|
61
105
|
*/
|
|
62
106
|
export declare function TreeRow(props: TreeRowProps): JSX.Element;
|
|
63
107
|
/**
|
|
@@ -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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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.js";
|
|
2
2
|
import { indexTreeItems, treeNavigationAction } from "./tree-navigation.js";
|
|
3
|
-
import { createComponent, insert, memo, mergeProps, spread, template, use } from "solid-js/web";
|
|
3
|
+
import { addEventListener, className, createComponent, delegateEvents, effect, insert, memo, mergeProps, setAttribute, spread, template, use } from "solid-js/web";
|
|
4
4
|
import { ChevronRight } from "lucide-solid";
|
|
5
5
|
import { createContext, createEffect, createMemo, createUniqueId, onCleanup, splitProps, useContext } from "solid-js";
|
|
6
6
|
//#region src/components/composites/tree/tree.tsx
|
|
7
7
|
var _tmpl$ = /*#__PURE__*/ template(`<div>`);
|
|
8
|
-
var _tmpl$2 = /*#__PURE__*/ template(`<div><span
|
|
8
|
+
var _tmpl$2 = /*#__PURE__*/ template(`<div><span></span><span class="min-w-0 flex-1 truncate">`);
|
|
9
9
|
var _tmpl$3 = /*#__PURE__*/ template(`<span class="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4">`);
|
|
10
10
|
var _tmpl$4 = /*#__PURE__*/ template(`<span class="ms-auto flex shrink-0 items-center gap-1 text-muted-foreground">`);
|
|
11
11
|
var _tmpl$5 = /*#__PURE__*/ template(`<span data-slot=tree-row-actions>`);
|
|
@@ -26,6 +26,20 @@ function useTreeContext() {
|
|
|
26
26
|
*
|
|
27
27
|
* Kobalte 0.13.14 has no Tree primitive, so this component implements the
|
|
28
28
|
* WAI-ARIA tree row navigation contract and leaves product state to callbacks.
|
|
29
|
+
*
|
|
30
|
+
* By default the disclosure chevron is decoration and a click anywhere on a
|
|
31
|
+
* row activates it, so a file explorer can toggle a folder from `onActivate`.
|
|
32
|
+
* Reach for `disclosure="marker"` when expanding and selecting are separate
|
|
33
|
+
* decisions: the chevron then calls only `onExpand`, and the rest of the row
|
|
34
|
+
* calls only `onActivate`. The chevron stays `aria-hidden` and out of the tab
|
|
35
|
+
* order in both modes; keyboard expansion is Left/Right either way.
|
|
36
|
+
*
|
|
37
|
+
* Collapsing is the one thing that can move focus without a key or a click on
|
|
38
|
+
* the target row: when the active row leaves `visibleItems`, the first visible
|
|
39
|
+
* row becomes the roving stop and `onActiveIdChange` is called with it. If the
|
|
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. This is the
|
|
42
|
+
* same in both `disclosure` modes and for every way a host collapses a row.
|
|
29
43
|
*/
|
|
30
44
|
function Tree(props) {
|
|
31
45
|
const [local, rest] = splitProps(props, [
|
|
@@ -34,6 +48,7 @@ function Tree(props) {
|
|
|
34
48
|
"activeId",
|
|
35
49
|
"onActiveIdChange",
|
|
36
50
|
"onExpand",
|
|
51
|
+
"disclosure",
|
|
37
52
|
"onActivate",
|
|
38
53
|
"onSelectItem",
|
|
39
54
|
"onRequestReveal",
|
|
@@ -117,6 +132,8 @@ function Tree(props) {
|
|
|
117
132
|
focusedId = id;
|
|
118
133
|
},
|
|
119
134
|
activate: (id, event) => local.onActivate?.(id, event),
|
|
135
|
+
expand: (id, expanded) => local.onExpand?.(id, expanded),
|
|
136
|
+
disclosure: () => local.disclosure ?? "row",
|
|
120
137
|
onActiveIdChange: local.onActiveIdChange
|
|
121
138
|
};
|
|
122
139
|
createEffect(() => {
|
|
@@ -171,6 +188,15 @@ function Tree(props) {
|
|
|
171
188
|
* Keeps the donor's disclosure marker, leading file/folder affordance, primary
|
|
172
189
|
* label and trailing-action hierarchy while replacing the donor's button and
|
|
173
190
|
* runtime callbacks with a shared treeitem and host-owned slots/actions.
|
|
191
|
+
*
|
|
192
|
+
* With `Tree`'s `disclosure="marker"` the chevron of an expandable row is a
|
|
193
|
+
* separate pointer target (`data-slot="tree-row-disclosure"`) that only calls
|
|
194
|
+
* `onExpand`. It is not focusable and has no role: the row remains the single
|
|
195
|
+
* focus stop, Enter and Space still activate and select, and Left/Right still
|
|
196
|
+
* expand and collapse, so the marker adds a pointer path and nothing else.
|
|
197
|
+
* Pressing it does not move focus or the roving stop to its row; if the toggle
|
|
198
|
+
* hides the focused row, `Tree` recovers focus as it does for any collapse (see
|
|
199
|
+
* the `Tree` comment and docs/tree.md).
|
|
174
200
|
*/
|
|
175
201
|
function TreeRow(props) {
|
|
176
202
|
const tree = useTreeContext();
|
|
@@ -214,6 +240,11 @@ function TreeRow(props) {
|
|
|
214
240
|
local.onDoubleClick?.(id(), event);
|
|
215
241
|
};
|
|
216
242
|
const position = () => tree.index().positionById.get(id());
|
|
243
|
+
const markerTarget = () => tree.disclosure() === "marker" && local.item.expandable;
|
|
244
|
+
const handleMarkerClick = (event) => {
|
|
245
|
+
event.stopPropagation();
|
|
246
|
+
tree.expand(id(), !local.item.expanded);
|
|
247
|
+
};
|
|
217
248
|
return (() => {
|
|
218
249
|
var _el$2 = _tmpl$2(), _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling;
|
|
219
250
|
var _ref$ = composeRefs(setRef, () => local.ref);
|
|
@@ -266,6 +297,9 @@ function TreeRow(props) {
|
|
|
266
297
|
"onClick": handleClick,
|
|
267
298
|
"onDblClick": handleDoubleClick
|
|
268
299
|
}), false, true);
|
|
300
|
+
addEventListener(_el$3, "dblclick", markerTarget() ? handleMarkerDoubleClick : void 0, true);
|
|
301
|
+
addEventListener(_el$3, "click", markerTarget() ? handleMarkerClick : void 0, true);
|
|
302
|
+
addEventListener(_el$3, "mousedown", markerTarget() ? handleMarkerMouseDown : void 0, true);
|
|
269
303
|
insert(_el$3, (() => {
|
|
270
304
|
var _c$ = memo(() => !!local.item.expandable);
|
|
271
305
|
return () => _c$() ? createComponent(ChevronRight, {
|
|
@@ -292,6 +326,15 @@ function TreeRow(props) {
|
|
|
292
326
|
return _el$6;
|
|
293
327
|
})() : null;
|
|
294
328
|
})(), null);
|
|
329
|
+
effect((_p$) => {
|
|
330
|
+
var _v$ = markerTarget() ? "tree-row-disclosure" : void 0, _v$2 = cn("flex", !markerTarget() && "size-3.5", "shrink-0 items-center justify-center text-muted-foreground", markerTarget() && "relative w-3.5 cursor-pointer self-stretch hover:text-foreground after:absolute after:inset-y-0 after:-inset-x-1.5");
|
|
331
|
+
_v$ !== _p$.e && setAttribute(_el$3, "data-slot", _p$.e = _v$);
|
|
332
|
+
_v$2 !== _p$.t && className(_el$3, _p$.t = _v$2);
|
|
333
|
+
return _p$;
|
|
334
|
+
}, {
|
|
335
|
+
e: void 0,
|
|
336
|
+
t: void 0
|
|
337
|
+
});
|
|
295
338
|
return _el$2;
|
|
296
339
|
})();
|
|
297
340
|
}
|
|
@@ -319,6 +362,8 @@ function TreeRowActions(props) {
|
|
|
319
362
|
return _el$7;
|
|
320
363
|
})();
|
|
321
364
|
}
|
|
365
|
+
var handleMarkerMouseDown = (event) => event.preventDefault();
|
|
366
|
+
var handleMarkerDoubleClick = (event) => event.stopPropagation();
|
|
322
367
|
function isActionTarget(target, currentTarget) {
|
|
323
368
|
if (!(target instanceof Element) || target === currentTarget) return false;
|
|
324
369
|
return Boolean(target.closest("a, button, input, textarea, select, [contenteditable=\"true\"], [role=\"button\"], [data-tree-keyboard-stop]"));
|
|
@@ -330,6 +375,11 @@ function composeRefs(internal, external) {
|
|
|
330
375
|
if (typeof ref === "function") ref(element);
|
|
331
376
|
};
|
|
332
377
|
}
|
|
378
|
+
delegateEvents([
|
|
379
|
+
"mousedown",
|
|
380
|
+
"click",
|
|
381
|
+
"dblclick"
|
|
382
|
+
]);
|
|
333
383
|
//#endregion
|
|
334
384
|
export { Tree, TreeRow, TreeRowActions };
|
|
335
385
|
|
|
@@ -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","onActiveIdChange","TreeContext","useTreeContext","tree","Error","TreeAccessibleName","TreeProps","Omit","visibleItems","onExpand","expanded","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","_el$2","_tmpl$2","_el$3","firstChild","_el$4","nextSibling","_ref$","composeRefs","ref","_$use","encodeURIComponent","aria-level","level","aria-expanded","_$memo","expandable","aria-selected","aria-posinset","aria-setsize","setSize","tabIndex","data-tree-id","data-active","data-selected","data-expanded","String","style","Math","max","_c$","_c$2","_el$5","_tmpl$3","_c$3","_el$6","_tmpl$4","TreeRowActions","pinned","_el$7","_tmpl$5","data-pinned","EventTarget","closest","internal","external","_$createComponent","_$mergeProps","_$use","_$memo"],"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 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\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 /** Called when keyboard or pointer navigation changes the active row. */\n onActiveIdChange: (id: string) => void\n /** Called for Left/Right expand and collapse requests. */\n onExpand?: (id: string, expanded: boolean) => void\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 */\nexport function Tree(props: TreeProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'visibleItems',\n 'activeId',\n 'onActiveIdChange',\n 'onExpand',\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 onActiveIdChange: local.onActiveIdChange,\n }\n\n // A filtered or removed active ID falls back to the first visible item. This\n // keeps one treeitem in the sequential tab order while notifying the host.\n createEffect(() => {\n const activeId = effectiveActiveId()\n if (activeId === null) return\n // If a host filter removes the row that had DOM focus, move focus to the\n // same projection fallback that now owns the roving tab stop.\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 */\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\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 class=\"flex size-3.5 shrink-0 items-center justify-center text-muted-foreground\">\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\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","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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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ComponentProps } from 'solid-js';
|
|
2
|
+
import { type VariantProps } from '#lib/variants';
|
|
3
|
+
/**
|
|
4
|
+
* Callout.
|
|
5
|
+
*
|
|
6
|
+
* A quiet aside that belongs to the content around it: the summary at the top of
|
|
7
|
+
* an answer, a note in documentation, a remembered fact beside the reply that
|
|
8
|
+
* used it. It is a card-coloured block with one rule on its start edge, and
|
|
9
|
+
* nothing about it interrupts the reader.
|
|
10
|
+
*
|
|
11
|
+
* That is the difference from `Alert`. An `Alert` is a message about the state of
|
|
12
|
+
* the surface it sits in — a failed sync, a validation summary — so it is always
|
|
13
|
+
* `role="alert"`, a live announcement, with a tone glyph and a tinted status
|
|
14
|
+
* surface. A callout is static page content: a plain `div` with `role="note"`, no
|
|
15
|
+
* live region, no icon, and no tint. A caller whose note really is a status
|
|
16
|
+
* message uses `Alert`; one whose note needs a different landmark role passes
|
|
17
|
+
* `role`. Reach for `Card` when the block is a self-contained object with its own
|
|
18
|
+
* header, content and footer; a callout has no parts beyond an optional
|
|
19
|
+
* `CalloutTitle`.
|
|
20
|
+
*
|
|
21
|
+
* The rule is the only thing that varies by `variant`: `default` is the accent
|
|
22
|
+
* (`--primary`), and the four status hues use the same tokens `Alert` does. The
|
|
23
|
+
* rule is a logical `border-s-*`, so it moves to the right in RTL. The corners
|
|
24
|
+
* stay square because a rounded box would curve the rule into a crescent.
|
|
25
|
+
*
|
|
26
|
+
* `size` is the padding rung and the rule's weight together, so a caller never
|
|
27
|
+
* sets `p-*` or a border width to make one denser. The rungs are Cortana's three
|
|
28
|
+
* answer-view callouts, expressed in the Tailwind spacing scale:
|
|
29
|
+
*
|
|
30
|
+
* - `lg`: 22px / 24px padding, 3px rule — Cortana's `.answer-copy`.
|
|
31
|
+
* - `md`: 18px / 20px padding, 3px rule — Cortana's `.answer-memory`.
|
|
32
|
+
* - `sm`: 12px / 14px padding, 2px rule — Cortana's `.answer-note`.
|
|
33
|
+
*
|
|
34
|
+
* Type size and colour are inherited rather than set (only the card foreground is
|
|
35
|
+
* applied), so a callout in a dense panel and one in an article both read at the
|
|
36
|
+
* size of the text around them.
|
|
37
|
+
*
|
|
38
|
+
* Text that has no break opportunity (a 200-character token, a URL, a file path)
|
|
39
|
+
* wraps with `overflow-wrap: anywhere` rather than overflowing the box. `anywhere`
|
|
40
|
+
* and not `break-word`, because only `anywhere` lowers the box's min-content width:
|
|
41
|
+
* a callout that is a flex or grid item (auto minimum size) then shrinks to its
|
|
42
|
+
* track instead of stretching it. The callout's own padding, rule and width are
|
|
43
|
+
* untouched, so none of its sizing differs from Cortana's.
|
|
44
|
+
*
|
|
45
|
+
* The block is deliberately not a flex or grid container: its children are inline
|
|
46
|
+
* text, links and code, and a flex root would turn each of those into a separate
|
|
47
|
+
* item and break the line.
|
|
48
|
+
*/
|
|
49
|
+
export declare const calloutVariants: (props?: ({
|
|
50
|
+
variant?: "default" | "destructive" | "success" | "warning" | "info" | null | undefined;
|
|
51
|
+
size?: "sm" | "md" | "lg" | null | undefined;
|
|
52
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
53
|
+
export type CalloutProps = ComponentProps<'div'> & VariantProps<typeof calloutVariants>;
|
|
54
|
+
export declare function Callout(props: CalloutProps): import("solid-js").JSX.Element;
|
|
55
|
+
/**
|
|
56
|
+
* An optional heading line for the callout. It is a styled `div`, not a heading
|
|
57
|
+
* element: a callout sits inside someone else's outline, and a fixed heading
|
|
58
|
+
* level would break it. Pass `role="heading"` with `aria-level` where the
|
|
59
|
+
* content does belong in the outline.
|
|
60
|
+
*
|
|
61
|
+
* The title always carries a 4px (`mb-1`) margin below it, whatever follows: a
|
|
62
|
+
* paragraph, a list, or bare text. CSS cannot see text nodes, so a selector such
|
|
63
|
+
* as `:last-child` or `:only-child` cannot tell `<Title/>text` (needs the gap)
|
|
64
|
+
* from `<Title/>` (needs none), and `not-last:mb-1` silently dropped the gap for
|
|
65
|
+
* bare text, the most common body. The unconditional margin is the less
|
|
66
|
+
* surprising failure: it is never missing where text follows. The price is the
|
|
67
|
+
* title-only callout, which would end 4px taller than its padding says. Pass
|
|
68
|
+
* `class="mb-0"` there (`cn` lets the caller's margin win); the `WithTitle` story
|
|
69
|
+
* shows it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function CalloutTitle(props: ComponentProps<'div'>): import("solid-js").JSX.Element;
|
|
72
|
+
//# sourceMappingURL=callout.d.ts.map
|
|
@@ -0,0 +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;AAEvF,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,kCAU1C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCASxD"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { cva } from "../../../lib/variants.js";
|
|
3
|
+
import { mergeProps, spread, template } from "solid-js/web";
|
|
4
|
+
import { splitProps } from "solid-js";
|
|
5
|
+
//#region src/components/ui/callout/callout.tsx
|
|
6
|
+
var _tmpl$ = /*#__PURE__*/ template(`<div data-slot=callout role=note>`);
|
|
7
|
+
var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=callout-title>`);
|
|
8
|
+
/**
|
|
9
|
+
* Callout.
|
|
10
|
+
*
|
|
11
|
+
* A quiet aside that belongs to the content around it: the summary at the top of
|
|
12
|
+
* an answer, a note in documentation, a remembered fact beside the reply that
|
|
13
|
+
* used it. It is a card-coloured block with one rule on its start edge, and
|
|
14
|
+
* nothing about it interrupts the reader.
|
|
15
|
+
*
|
|
16
|
+
* That is the difference from `Alert`. An `Alert` is a message about the state of
|
|
17
|
+
* the surface it sits in — a failed sync, a validation summary — so it is always
|
|
18
|
+
* `role="alert"`, a live announcement, with a tone glyph and a tinted status
|
|
19
|
+
* surface. A callout is static page content: a plain `div` with `role="note"`, no
|
|
20
|
+
* live region, no icon, and no tint. A caller whose note really is a status
|
|
21
|
+
* message uses `Alert`; one whose note needs a different landmark role passes
|
|
22
|
+
* `role`. Reach for `Card` when the block is a self-contained object with its own
|
|
23
|
+
* header, content and footer; a callout has no parts beyond an optional
|
|
24
|
+
* `CalloutTitle`.
|
|
25
|
+
*
|
|
26
|
+
* The rule is the only thing that varies by `variant`: `default` is the accent
|
|
27
|
+
* (`--primary`), and the four status hues use the same tokens `Alert` does. The
|
|
28
|
+
* rule is a logical `border-s-*`, so it moves to the right in RTL. The corners
|
|
29
|
+
* stay square because a rounded box would curve the rule into a crescent.
|
|
30
|
+
*
|
|
31
|
+
* `size` is the padding rung and the rule's weight together, so a caller never
|
|
32
|
+
* sets `p-*` or a border width to make one denser. The rungs are Cortana's three
|
|
33
|
+
* answer-view callouts, expressed in the Tailwind spacing scale:
|
|
34
|
+
*
|
|
35
|
+
* - `lg`: 22px / 24px padding, 3px rule — Cortana's `.answer-copy`.
|
|
36
|
+
* - `md`: 18px / 20px padding, 3px rule — Cortana's `.answer-memory`.
|
|
37
|
+
* - `sm`: 12px / 14px padding, 2px rule — Cortana's `.answer-note`.
|
|
38
|
+
*
|
|
39
|
+
* Type size and colour are inherited rather than set (only the card foreground is
|
|
40
|
+
* applied), so a callout in a dense panel and one in an article both read at the
|
|
41
|
+
* size of the text around them.
|
|
42
|
+
*
|
|
43
|
+
* Text that has no break opportunity (a 200-character token, a URL, a file path)
|
|
44
|
+
* wraps with `overflow-wrap: anywhere` rather than overflowing the box. `anywhere`
|
|
45
|
+
* and not `break-word`, because only `anywhere` lowers the box's min-content width:
|
|
46
|
+
* a callout that is a flex or grid item (auto minimum size) then shrinks to its
|
|
47
|
+
* track instead of stretching it. The callout's own padding, rule and width are
|
|
48
|
+
* untouched, so none of its sizing differs from Cortana's.
|
|
49
|
+
*
|
|
50
|
+
* The block is deliberately not a flex or grid container: its children are inline
|
|
51
|
+
* text, links and code, and a flex root would turn each of those into a separate
|
|
52
|
+
* item and break the line.
|
|
53
|
+
*/
|
|
54
|
+
var calloutVariants = cva("bg-card text-card-foreground block rounded-none wrap-anywhere", {
|
|
55
|
+
variants: {
|
|
56
|
+
variant: {
|
|
57
|
+
default: "border-s-primary",
|
|
58
|
+
destructive: "border-s-destructive",
|
|
59
|
+
success: "border-s-success",
|
|
60
|
+
warning: "border-s-warning",
|
|
61
|
+
info: "border-s-info"
|
|
62
|
+
},
|
|
63
|
+
size: {
|
|
64
|
+
/** 12px block / 14px inline padding, 2px rule: a short inline note. */
|
|
65
|
+
sm: "border-s-2 px-3.5 py-3",
|
|
66
|
+
/** 18px block / 20px inline padding, 3px rule: a supporting aside. */
|
|
67
|
+
md: "border-s-3 px-5 py-4.5",
|
|
68
|
+
/** 22px block / 24px inline padding, 3px rule: the lead summary of a view. */
|
|
69
|
+
lg: "border-s-3 px-6 py-5.5"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
defaultVariants: {
|
|
73
|
+
variant: "default",
|
|
74
|
+
size: "md"
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
function Callout(props) {
|
|
78
|
+
const [local, rest] = splitProps(props, [
|
|
79
|
+
"class",
|
|
80
|
+
"variant",
|
|
81
|
+
"size"
|
|
82
|
+
]);
|
|
83
|
+
return (() => {
|
|
84
|
+
var _el$ = _tmpl$();
|
|
85
|
+
spread(_el$, mergeProps({ get ["class"]() {
|
|
86
|
+
return cn(calloutVariants({
|
|
87
|
+
variant: local.variant,
|
|
88
|
+
size: local.size
|
|
89
|
+
}), local.class);
|
|
90
|
+
} }, rest), false, false);
|
|
91
|
+
return _el$;
|
|
92
|
+
})();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* An optional heading line for the callout. It is a styled `div`, not a heading
|
|
96
|
+
* element: a callout sits inside someone else's outline, and a fixed heading
|
|
97
|
+
* level would break it. Pass `role="heading"` with `aria-level` where the
|
|
98
|
+
* content does belong in the outline.
|
|
99
|
+
*
|
|
100
|
+
* The title always carries a 4px (`mb-1`) margin below it, whatever follows: a
|
|
101
|
+
* paragraph, a list, or bare text. CSS cannot see text nodes, so a selector such
|
|
102
|
+
* as `:last-child` or `:only-child` cannot tell `<Title/>text` (needs the gap)
|
|
103
|
+
* from `<Title/>` (needs none), and `not-last:mb-1` silently dropped the gap for
|
|
104
|
+
* bare text, the most common body. The unconditional margin is the less
|
|
105
|
+
* surprising failure: it is never missing where text follows. The price is the
|
|
106
|
+
* title-only callout, which would end 4px taller than its padding says. Pass
|
|
107
|
+
* `class="mb-0"` there (`cn` lets the caller's margin win); the `WithTitle` story
|
|
108
|
+
* shows it.
|
|
109
|
+
*/
|
|
110
|
+
function CalloutTitle(props) {
|
|
111
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
112
|
+
return (() => {
|
|
113
|
+
var _el$2 = _tmpl$2();
|
|
114
|
+
spread(_el$2, mergeProps({ get ["class"]() {
|
|
115
|
+
return cn("mb-1 font-medium tracking-tight", local.class);
|
|
116
|
+
} }, rest), false, false);
|
|
117
|
+
return _el$2;
|
|
118
|
+
})();
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { Callout, CalloutTitle, calloutVariants };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=callout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callout.js","names":["ComponentProps","splitProps","cva","VariantProps","cn","calloutVariants","variants","variant","default","destructive","success","warning","info","size","sm","md","lg","defaultVariants","CalloutProps","Callout","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","class","CalloutTitle","_el$2","_tmpl$2","_$mergeProps"],"sources":["../../../../src/components/ui/callout/callout.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { splitProps } from 'solid-js'\nimport { cva, type VariantProps } from '#lib/variants'\nimport { cn } from '#lib/utils'\n\n/**\n * Callout.\n *\n * A quiet aside that belongs to the content around it: the summary at the top of\n * an answer, a note in documentation, a remembered fact beside the reply that\n * used it. It is a card-coloured block with one rule on its start edge, and\n * nothing about it interrupts the reader.\n *\n * That is the difference from `Alert`. An `Alert` is a message about the state of\n * the surface it sits in — a failed sync, a validation summary — so it is always\n * `role=\"alert\"`, a live announcement, with a tone glyph and a tinted status\n * surface. A callout is static page content: a plain `div` with `role=\"note\"`, no\n * live region, no icon, and no tint. A caller whose note really is a status\n * message uses `Alert`; one whose note needs a different landmark role passes\n * `role`. Reach for `Card` when the block is a self-contained object with its own\n * header, content and footer; a callout has no parts beyond an optional\n * `CalloutTitle`.\n *\n * The rule is the only thing that varies by `variant`: `default` is the accent\n * (`--primary`), and the four status hues use the same tokens `Alert` does. The\n * rule is a logical `border-s-*`, so it moves to the right in RTL. The corners\n * stay square because a rounded box would curve the rule into a crescent.\n *\n * `size` is the padding rung and the rule's weight together, so a caller never\n * sets `p-*` or a border width to make one denser. The rungs are Cortana's three\n * answer-view callouts, expressed in the Tailwind spacing scale:\n *\n * - `lg`: 22px / 24px padding, 3px rule — Cortana's `.answer-copy`.\n * - `md`: 18px / 20px padding, 3px rule — Cortana's `.answer-memory`.\n * - `sm`: 12px / 14px padding, 2px rule — Cortana's `.answer-note`.\n *\n * Type size and colour are inherited rather than set (only the card foreground is\n * applied), so a callout in a dense panel and one in an article both read at the\n * size of the text around them.\n *\n * Text that has no break opportunity (a 200-character token, a URL, a file path)\n * wraps with `overflow-wrap: anywhere` rather than overflowing the box. `anywhere`\n * and not `break-word`, because only `anywhere` lowers the box's min-content width:\n * a callout that is a flex or grid item (auto minimum size) then shrinks to its\n * track instead of stretching it. The callout's own padding, rule and width are\n * untouched, so none of its sizing differs from Cortana's.\n *\n * The block is deliberately not a flex or grid container: its children are inline\n * text, links and code, and a flex root would turn each of those into a separate\n * item and break the line.\n */\nexport const calloutVariants = cva(\n 'bg-card text-card-foreground block rounded-none wrap-anywhere',\n {\n variants: {\n variant: {\n default: 'border-s-primary',\n destructive: 'border-s-destructive',\n success: 'border-s-success',\n warning: 'border-s-warning',\n info: 'border-s-info',\n },\n size: {\n /** 12px block / 14px inline padding, 2px rule: a short inline note. */\n sm: 'border-s-2 px-3.5 py-3',\n /** 18px block / 20px inline padding, 3px rule: a supporting aside. */\n md: 'border-s-3 px-5 py-4.5',\n /** 22px block / 24px inline padding, 3px rule: the lead summary of a view. */\n lg: 'border-s-3 px-6 py-5.5',\n },\n },\n defaultVariants: { variant: 'default', size: 'md' },\n }\n)\n\nexport type CalloutProps = ComponentProps<'div'> & VariantProps<typeof calloutVariants>\n\nexport function Callout(props: CalloutProps) {\n const [local, rest] = splitProps(props, ['class', 'variant', 'size'])\n return (\n <div\n data-slot=\"callout\"\n role=\"note\"\n class={cn(calloutVariants({ variant: local.variant, size: local.size }), local.class)}\n {...rest}\n />\n )\n}\n\n/**\n * An optional heading line for the callout. It is a styled `div`, not a heading\n * element: a callout sits inside someone else's outline, and a fixed heading\n * level would break it. Pass `role=\"heading\"` with `aria-level` where the\n * content does belong in the outline.\n *\n * The title always carries a 4px (`mb-1`) margin below it, whatever follows: a\n * paragraph, a list, or bare text. CSS cannot see text nodes, so a selector such\n * as `:last-child` or `:only-child` cannot tell `<Title/>text` (needs the gap)\n * from `<Title/>` (needs none), and `not-last:mb-1` silently dropped the gap for\n * bare text, the most common body. The unconditional margin is the less\n * surprising failure: it is never missing where text follows. The price is the\n * title-only callout, which would end 4px taller than its padding says. Pass\n * `class=\"mb-0\"` there (`cn` lets the caller's margin win); the `WithTitle` story\n * shows it.\n */\nexport function CalloutTitle(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"callout-title\"\n class={cn('mb-1 font-medium tracking-tight', local.class)}\n {...rest}\n />\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAaK,kBAAkBH,IAC7B,iEACA;CACEI,UAAU;EACRC,SAAS;GACPC,SAAS;GACTC,aAAa;GACbC,SAAS;GACTC,SAAS;GACTC,MAAM;EACR;EACAC,MAAM;;GAEJC,IAAI;;GAEJC,IAAI;;GAEJC,IAAI;EACN;CACF;CACAC,iBAAiB;EAAEV,SAAS;EAAWM,MAAM;CAAK;AACpD,CACF;AAIA,SAAgBM,QAAQC,OAAqB;CAC3C,MAAM,CAACC,OAAOC,QAAQrB,WAAWmB,OAAO;EAAC;EAAS;EAAW;CAAM,CAAC;CACpE,cAAA;EAAA,IAAAG,OAAAC,OAAA;EAAAC,OAAAF,MAAAG,WAAA,EAAA,KAAA,WAAA;GAAA,OAIWtB,GAAGC,gBAAgB;IAAEE,SAASc,MAAMd;IAASM,MAAMQ,MAAMR;GAAK,CAAC,GAAGQ,MAAMM,KAAK;EAAC,EAAA,GACjFL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAC;CAAA,EAAA,CAAA;AAGd;;;;;;;;;;;;;;;;;AAkBA,SAAgBK,aAAaR,OAA8B;CACzD,MAAM,CAACC,OAAOC,QAAQrB,WAAWmB,OAAO,CAAC,OAAO,CAAC;CACjD,cAAA;EAAA,IAAAS,QAAAC,QAAA;EAAAL,OAAAI,OAAAH,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWtB,GAAG,mCAAmCiB,MAAMM,KAAK;EAAC,EAAA,GACrDL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAO;CAAA,EAAA,CAAA;AAGd"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/callout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from './components/ui/breadcrumb';
|
|
|
25
25
|
export * from './components/ui/button';
|
|
26
26
|
export * from './components/ui/button-group';
|
|
27
27
|
export * from './components/ui/calendar';
|
|
28
|
+
export * from './components/ui/callout';
|
|
28
29
|
export * from './components/ui/card';
|
|
29
30
|
export * from './components/ui/checkbox';
|
|
30
31
|
export * from './components/ui/collapsible';
|