dustyui 1.0.1 → 2.0.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -1
  3. package/dist/Avatar/Avatar.d.ts +9 -0
  4. package/dist/Avatar/Avatar.js +17 -0
  5. package/dist/Avatar/Avatar.module.css +33 -0
  6. package/dist/Avatar/getInitials.d.ts +1 -0
  7. package/dist/Avatar/getInitials.js +13 -0
  8. package/dist/Badge/Badge.d.ts +7 -6
  9. package/dist/Badge/Badge.js +6 -7
  10. package/dist/Badge/Badge.module.css +25 -2
  11. package/dist/Button/Button.d.ts +9 -6
  12. package/dist/Button/Button.js +22 -7
  13. package/dist/Button/Button.module.css +106 -6
  14. package/dist/IconButton/IconButton.d.ts +7 -0
  15. package/dist/IconButton/IconButton.js +8 -0
  16. package/dist/IconButton/IconButton.module.css +17 -0
  17. package/dist/ObjectInspector/InspectorDetails.d.ts +6 -0
  18. package/dist/ObjectInspector/InspectorDetails.js +32 -0
  19. package/dist/ObjectInspector/InspectorDetails.module.css +47 -0
  20. package/dist/ObjectInspector/InspectorSearch.d.ts +9 -0
  21. package/dist/ObjectInspector/InspectorSearch.js +12 -0
  22. package/dist/ObjectInspector/InspectorSearch.module.css +39 -0
  23. package/dist/ObjectInspector/ObjectInspector.d.ts +8 -0
  24. package/dist/ObjectInspector/ObjectInspector.js +83 -0
  25. package/dist/ObjectInspector/ObjectInspector.module.css +46 -0
  26. package/dist/ObjectInspector/describe/describe.d.ts +31 -0
  27. package/dist/ObjectInspector/describe/describe.js +123 -0
  28. package/dist/ObjectInspector/describe/getChildren.d.ts +21 -0
  29. package/dist/ObjectInspector/describe/getChildren.js +77 -0
  30. package/dist/ObjectInspector/describe/getNode.d.ts +2 -0
  31. package/dist/ObjectInspector/describe/getNode.js +9 -0
  32. package/dist/ObjectInspector/describe/getPath.d.ts +26 -0
  33. package/dist/ObjectInspector/describe/getPath.js +58 -0
  34. package/dist/ObjectInspector/describe/getPreview.d.ts +10 -0
  35. package/dist/ObjectInspector/describe/getPreview.js +189 -0
  36. package/dist/ObjectInspector/describe/getValueKind.d.ts +2 -0
  37. package/dist/ObjectInspector/describe/getValueKind.js +64 -0
  38. package/dist/ObjectInspector/describe/safeRead.d.ts +12 -0
  39. package/dist/ObjectInspector/describe/safeRead.js +88 -0
  40. package/dist/ObjectInspector/getCopyText.d.ts +2 -0
  41. package/dist/ObjectInspector/getCopyText.js +103 -0
  42. package/dist/ObjectInspector/getLabelText.d.ts +2 -0
  43. package/dist/ObjectInspector/getLabelText.js +15 -0
  44. package/dist/ObjectInspector/getValueText.d.ts +2 -0
  45. package/dist/ObjectInspector/getValueText.js +5 -0
  46. package/dist/ObjectInspector/search/getHighlightParts.d.ts +5 -0
  47. package/dist/ObjectInspector/search/getHighlightParts.js +20 -0
  48. package/dist/ObjectInspector/search/getMatches.d.ts +6 -0
  49. package/dist/ObjectInspector/search/getMatches.js +46 -0
  50. package/dist/ObjectInspector/search/getSearchStatus.d.ts +2 -0
  51. package/dist/ObjectInspector/search/getSearchStatus.js +14 -0
  52. package/dist/Tree/Tree.d.ts +14 -0
  53. package/dist/Tree/Tree.js +166 -0
  54. package/dist/Tree/Tree.module.css +65 -0
  55. package/dist/Tree/getWindow.d.ts +22 -0
  56. package/dist/Tree/getWindow.js +43 -0
  57. package/dist/Tree/treeModel.d.ts +55 -0
  58. package/dist/Tree/treeModel.js +203 -0
  59. package/dist/Tree/useRowWindow.d.ts +3 -0
  60. package/dist/Tree/useRowWindow.js +78 -0
  61. package/dist/Tree/useTypeahead.d.ts +2 -0
  62. package/dist/Tree/useTypeahead.js +15 -0
  63. package/dist/announcer/announcer.d.ts +2 -0
  64. package/dist/announcer/announcer.js +41 -0
  65. package/dist/index.d.ts +11 -2
  66. package/dist/index.js +10 -6
  67. package/dist/styles/themes/dusty.css +22 -0
  68. package/dist/styles/themes/mono.css +21 -0
  69. package/dist/styles/tokens.css +21 -0
  70. package/dist/styles/visuallyHidden.module.css +8 -0
  71. package/package.json +55 -36
  72. package/dist/Badge/Badge.cjs +0 -42
  73. package/dist/Button/Button.cjs +0 -42
  74. package/dist/index.cjs +0 -31
@@ -0,0 +1,2 @@
1
+ export type ValueKind = "string" | "number" | "bigint" | "boolean" | "null" | "undefined" | "symbol" | "function" | "array" | "object" | "map" | "set" | "date" | "regexp" | "error" | "entry" | "getter" | "circular" | "uninspectable";
2
+ export declare function getValueKind(value: unknown): ValueKind;
@@ -0,0 +1,64 @@
1
+ import { getDateTime, getMapSize, getRegExpSource, getSetSize, getTypedArrayLength, } from "./safeRead.js";
2
+ function succeeds(read) {
3
+ try {
4
+ read();
5
+ return true;
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ }
11
+ function isRegExp(value) {
12
+ return value !== RegExp.prototype && succeeds(() => getRegExpSource(value));
13
+ }
14
+ function isError(value) {
15
+ const { isError: nativeIsError } = Error;
16
+ return nativeIsError ? nativeIsError(value) : value instanceof Error;
17
+ }
18
+ function isTypedArray(value) {
19
+ return (ArrayBuffer.isView(value) && succeeds(() => getTypedArrayLength(value)));
20
+ }
21
+ function hasPlainPrototype(value) {
22
+ try {
23
+ const prototype = Reflect.getPrototypeOf(value);
24
+ return prototype === Object.prototype || prototype === null;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ function getObjectKind(value) {
31
+ if (Array.isArray(value) || isTypedArray(value)) {
32
+ return "array";
33
+ }
34
+ if (hasPlainPrototype(value)) {
35
+ return "object";
36
+ }
37
+ if (succeeds(() => getMapSize(value))) {
38
+ return "map";
39
+ }
40
+ if (succeeds(() => getSetSize(value))) {
41
+ return "set";
42
+ }
43
+ if (succeeds(() => getDateTime(value))) {
44
+ return "date";
45
+ }
46
+ if (isRegExp(value)) {
47
+ return "regexp";
48
+ }
49
+ if (isError(value)) {
50
+ return "error";
51
+ }
52
+ return "object";
53
+ }
54
+ export function getValueKind(value) {
55
+ if (value === null) {
56
+ return "null";
57
+ }
58
+ switch (typeof value) {
59
+ case "object":
60
+ return getObjectKind(value);
61
+ default:
62
+ return typeof value;
63
+ }
64
+ }
@@ -0,0 +1,12 @@
1
+ export declare const getMapSize: (target: unknown) => number;
2
+ export declare const getSetSize: (target: unknown) => number;
3
+ export declare const getTypedArrayLength: (target: unknown) => number;
4
+ export declare const getRegExpSource: (target: unknown) => string;
5
+ export declare function getDateTime(value: object): number;
6
+ export declare function getRegExpText(value: object): string;
7
+ export declare function getSymbolText(value: symbol): string;
8
+ export declare function isClass(value: object): boolean;
9
+ export declare function getDataProperty(target: object, key: PropertyKey): unknown;
10
+ export declare function getArrayLength(value: unknown): number | undefined;
11
+ export declare function getConstructorName(value: object): string | undefined;
12
+ export declare function getFailureReason(error: unknown): string;
@@ -0,0 +1,88 @@
1
+ function nativeGetter(prototype, name) {
2
+ const get = Object.getOwnPropertyDescriptor(prototype, name)?.get;
3
+ if (!get) {
4
+ throw new TypeError(`Missing native getter: ${name}`);
5
+ }
6
+ return (target) => get.call(target);
7
+ }
8
+ const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
9
+ const getTime = Date.prototype.getTime;
10
+ const functionToString = Function.prototype.toString;
11
+ const symbolToString = Symbol.prototype.toString;
12
+ export const getMapSize = nativeGetter(Map.prototype, "size");
13
+ export const getSetSize = nativeGetter(Set.prototype, "size");
14
+ export const getTypedArrayLength = nativeGetter(typedArrayPrototype, "length");
15
+ export const getRegExpSource = nativeGetter(RegExp.prototype, "source");
16
+ const regExpFlags = [
17
+ ["d", "hasIndices"],
18
+ ["g", "global"],
19
+ ["i", "ignoreCase"],
20
+ ["m", "multiline"],
21
+ ["s", "dotAll"],
22
+ ["u", "unicode"],
23
+ ["v", "unicodeSets"],
24
+ ["y", "sticky"],
25
+ ].flatMap(([flag, name]) => Object.getOwnPropertyDescriptor(RegExp.prototype, name)?.get
26
+ ? [[flag, nativeGetter(RegExp.prototype, name)]]
27
+ : []);
28
+ export function getDateTime(value) {
29
+ return getTime.call(value);
30
+ }
31
+ export function getRegExpText(value) {
32
+ const flags = regExpFlags
33
+ .filter(([, isSet]) => isSet(value))
34
+ .map(([flag]) => flag)
35
+ .join("");
36
+ return `/${getRegExpSource(value)}/${flags}`;
37
+ }
38
+ export function getSymbolText(value) {
39
+ return symbolToString.call(value);
40
+ }
41
+ export function isClass(value) {
42
+ return functionToString.call(value).startsWith("class");
43
+ }
44
+ export function getDataProperty(target, key) {
45
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
46
+ return descriptor && "value" in descriptor ? descriptor.value : undefined;
47
+ }
48
+ export function getArrayLength(value) {
49
+ try {
50
+ if (!Array.isArray(value)) {
51
+ return undefined;
52
+ }
53
+ const length = getDataProperty(value, "length");
54
+ return typeof length === "number" ? length : undefined;
55
+ }
56
+ catch {
57
+ return undefined;
58
+ }
59
+ }
60
+ export function getConstructorName(value) {
61
+ const prototype = Reflect.getPrototypeOf(value);
62
+ if (prototype === null) {
63
+ return undefined;
64
+ }
65
+ const constructor = getDataProperty(prototype, "constructor");
66
+ if (typeof constructor !== "function") {
67
+ return undefined;
68
+ }
69
+ const name = getDataProperty(constructor, "name");
70
+ return typeof name === "string" && name !== "" ? name : undefined;
71
+ }
72
+ export function getFailureReason(error) {
73
+ try {
74
+ if (typeof error === "string") {
75
+ return error;
76
+ }
77
+ if (typeof error === "object" && error !== null) {
78
+ const message = getDataProperty(error, "message");
79
+ if (typeof message === "string" && message !== "") {
80
+ return message;
81
+ }
82
+ }
83
+ }
84
+ catch {
85
+ return "unknown error";
86
+ }
87
+ return "unknown error";
88
+ }
@@ -0,0 +1,2 @@
1
+ import type { InspectorNode } from "./describe/describe.js";
2
+ export declare function getCopyText(root: InspectorNode, limit?: number): string;
@@ -0,0 +1,103 @@
1
+ import { getArrayLength } from "./describe/safeRead.js";
2
+ import { getLabelText } from "./getLabelText.js";
3
+ import { getValueText } from "./getValueText.js";
4
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/u;
5
+ const INDENT = " ";
6
+ const PLAIN_TYPES = new Set(["Object", "Array"]);
7
+ function getPrefix(node, isRoot) {
8
+ if (isRoot || !node.label || node.label.type === "index") {
9
+ return "";
10
+ }
11
+ const text = getLabelText(node.label);
12
+ const isPlain = node.label.type !== "property" || IDENTIFIER.test(text);
13
+ return `${isPlain ? text : JSON.stringify(text)}: `;
14
+ }
15
+ function getLeafText(node) {
16
+ if (node.kind === "string" && typeof node.value === "string") {
17
+ return JSON.stringify(node.value);
18
+ }
19
+ const length = getListLength(node);
20
+ if (length !== undefined && length > 0) {
21
+ return `[${getHoleText(length).slice(0, -1)}]`;
22
+ }
23
+ return node.typeName && PLAIN_TYPES.has(node.typeName)
24
+ ? getOpening(node) + getClosing(node)
25
+ : node.preview;
26
+ }
27
+ function getOpening(node) {
28
+ const bracket = node.kind === "array" ? "[" : "{";
29
+ return node.typeName && !PLAIN_TYPES.has(node.typeName)
30
+ ? `${node.typeName} ${bracket}`
31
+ : bracket;
32
+ }
33
+ function getListLength(node) {
34
+ return node.kind === "array" ? getArrayLength(node.value) : undefined;
35
+ }
36
+ function getHoleText(count) {
37
+ return `<${count} empty ${count === 1 ? "item" : "items"}>,`;
38
+ }
39
+ function getClosing(node) {
40
+ return node.kind === "array" ? "]" : "}";
41
+ }
42
+ export function getCopyText(root, limit = 5000) {
43
+ if (root.childCount === 0) {
44
+ return root.kind === "string" ? getValueText(root) : getLeafText(root);
45
+ }
46
+ const lines = [];
47
+ const pending = [root];
48
+ const depths = new Map([[root, 0]]);
49
+ let remaining = limit;
50
+ let isCut = false;
51
+ for (let item = pending.pop(); item !== undefined; item = pending.pop()) {
52
+ if (typeof item === "string") {
53
+ lines.push(item);
54
+ continue;
55
+ }
56
+ const depth = depths.get(item) ?? 0;
57
+ depths.delete(item);
58
+ const indent = INDENT.repeat(depth);
59
+ const end = item === root ? "" : ",";
60
+ if (remaining <= 0) {
61
+ if (!isCut) {
62
+ isCut = true;
63
+ lines.push(`${indent}…`);
64
+ }
65
+ continue;
66
+ }
67
+ remaining -= 1;
68
+ const prefix = indent + getPrefix(item, item === root);
69
+ if (item.childCount === 0) {
70
+ lines.push(prefix + getLeafText(item) + end);
71
+ continue;
72
+ }
73
+ if (item.childCount > limit) {
74
+ lines.push(prefix + item.preview + end);
75
+ continue;
76
+ }
77
+ lines.push(prefix + getOpening(item));
78
+ pending.push(indent + getClosing(item) + end);
79
+ const children = item.children();
80
+ const childIndent = INDENT.repeat(depth + 1);
81
+ const length = getListLength(item);
82
+ const isList = length !== undefined;
83
+ let next = length ?? 0;
84
+ for (let index = children.length - 1; index >= 0; index--) {
85
+ const child = children[index];
86
+ if (!child) {
87
+ continue;
88
+ }
89
+ if (isList && child.label?.type === "index") {
90
+ if (child.label.index + 1 < next) {
91
+ pending.push(childIndent + getHoleText(next - child.label.index - 1));
92
+ }
93
+ next = child.label.index;
94
+ }
95
+ depths.set(child, depth + 1);
96
+ pending.push(child);
97
+ }
98
+ if (isList && next > 0) {
99
+ pending.push(childIndent + getHoleText(next));
100
+ }
101
+ }
102
+ return lines.join("\n");
103
+ }
@@ -0,0 +1,2 @@
1
+ import type { NodeLabel } from "./describe/describe.js";
2
+ export declare function getLabelText(label: NodeLabel): string;
@@ -0,0 +1,15 @@
1
+ export function getLabelText(label) {
2
+ switch (label.type) {
3
+ case "property":
4
+ return label.key === "" ? '""' : label.key;
5
+ case "symbol":
6
+ return Symbol.prototype.toString.call(label.key);
7
+ case "index":
8
+ case "entry":
9
+ return String(label.index);
10
+ case "entryKey":
11
+ return "key";
12
+ case "entryValue":
13
+ return "value";
14
+ }
15
+ }
@@ -0,0 +1,2 @@
1
+ import type { InspectorNode } from "./describe/describe.js";
2
+ export declare function getValueText(node: InspectorNode): string;
@@ -0,0 +1,5 @@
1
+ export function getValueText(node) {
2
+ return node.kind === "string" && typeof node.value === "string"
3
+ ? node.value
4
+ : node.preview;
5
+ }
@@ -0,0 +1,5 @@
1
+ export interface HighlightPart {
2
+ readonly text: string;
3
+ readonly isMatch: boolean;
4
+ }
5
+ export declare function getHighlightParts(text: string, search: string): HighlightPart[];
@@ -0,0 +1,20 @@
1
+ export function getHighlightParts(text, search) {
2
+ const query = search.trim().toLocaleLowerCase();
3
+ const lowered = text.toLocaleLowerCase();
4
+ if (query === "" || lowered.length !== text.length) {
5
+ return [{ text, isMatch: false }];
6
+ }
7
+ const parts = [];
8
+ let position = 0;
9
+ for (let found = lowered.indexOf(query); found >= 0; found = lowered.indexOf(query, position)) {
10
+ if (found > position) {
11
+ parts.push({ text: text.slice(position, found), isMatch: false });
12
+ }
13
+ position = found + query.length;
14
+ parts.push({ text: text.slice(found, position), isMatch: true });
15
+ }
16
+ if (position < text.length || parts.length === 0) {
17
+ parts.push({ text: text.slice(position), isMatch: false });
18
+ }
19
+ return parts;
20
+ }
@@ -0,0 +1,6 @@
1
+ import type { InspectorNode } from "../describe/describe.js";
2
+ export interface Matches {
3
+ readonly ids: readonly string[];
4
+ readonly isComplete: boolean;
5
+ }
6
+ export declare function getMatches(root: InspectorNode, text: string, limit: number): Matches;
@@ -0,0 +1,46 @@
1
+ import { getLabelText } from "../getLabelText.js";
2
+ import { getValueText } from "../getValueText.js";
3
+ const NO_MATCHES = { ids: [], isComplete: true };
4
+ function isMatch(node, query) {
5
+ if (node.label &&
6
+ getLabelText(node.label).toLocaleLowerCase().includes(query)) {
7
+ return true;
8
+ }
9
+ return (node.childCount === 0 &&
10
+ getValueText(node).toLocaleLowerCase().includes(query));
11
+ }
12
+ export function getMatches(root, text, limit) {
13
+ const query = text.trim().toLocaleLowerCase();
14
+ if (query === "") {
15
+ return NO_MATCHES;
16
+ }
17
+ const ids = [];
18
+ const pending = [root];
19
+ let remaining = limit;
20
+ let isComplete = true;
21
+ for (let node = pending.pop(); node; node = pending.pop()) {
22
+ if (remaining <= 0) {
23
+ isComplete = false;
24
+ break;
25
+ }
26
+ remaining -= 1;
27
+ if (isMatch(node, query)) {
28
+ ids.push(node.id);
29
+ }
30
+ if (node.childCount === 0) {
31
+ continue;
32
+ }
33
+ if (node.childCount > limit) {
34
+ isComplete = false;
35
+ continue;
36
+ }
37
+ const children = node.children();
38
+ for (let index = children.length - 1; index >= 0; index--) {
39
+ const child = children[index];
40
+ if (child) {
41
+ pending.push(child);
42
+ }
43
+ }
44
+ }
45
+ return { ids, isComplete };
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { Matches } from "./getMatches.js";
2
+ export declare function getSearchStatus(query: string, matches: Matches, current: number): string;
@@ -0,0 +1,14 @@
1
+ export function getSearchStatus(query, matches, current) {
2
+ if (query.trim() === "") {
3
+ return "";
4
+ }
5
+ const count = matches.ids.length;
6
+ const scope = matches.isComplete ? "" : " in the part that was searched";
7
+ if (count === 0) {
8
+ return `No matches${scope}`;
9
+ }
10
+ if (current >= 0) {
11
+ return `${current + 1} of ${count}${scope}`;
12
+ }
13
+ return `${count} ${count === 1 ? "match" : "matches"}${scope}`;
14
+ }
@@ -0,0 +1,14 @@
1
+ import { type ComponentProps, type ReactNode } from "react";
2
+ import { type TreeNode, type TreeRow } from "./treeModel.js";
3
+ export interface TreeProps<Node extends TreeNode> extends Omit<ComponentProps<"div">, "children" | "dangerouslySetInnerHTML" | "role" | "aria-label" | "aria-labelledby"> {
4
+ root: Node;
5
+ label: string;
6
+ renderRow: (row: TreeRow<Node>) => ReactNode;
7
+ getRowText?: (row: TreeRow<Node>) => string;
8
+ expanded?: ReadonlySet<string>;
9
+ defaultExpanded?: Iterable<string>;
10
+ onExpandedChange?: (expanded: ReadonlySet<string>) => void;
11
+ selected?: string;
12
+ onSelectedChange?: (selected: string) => void;
13
+ }
14
+ export declare function Tree<Node extends TreeNode>({ root, label, renderRow, getRowText, expanded: controlledExpanded, defaultExpanded, onExpandedChange, selected: controlledSelected, onSelectedChange, className, onKeyDown, onFocus, onBlur, ref, ...props }: TreeProps<Node>): import("react").JSX.Element;
@@ -0,0 +1,166 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useEffectEvent, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from "react";
4
+ import clsx from "clsx";
5
+ import { getWindowItems } from "./getWindow.js";
6
+ import { findVisibleAncestor, keyToAction, reduceTree, visibleRows, } from "./treeModel.js";
7
+ import { useRowWindow } from "./useRowWindow.js";
8
+ import { useTypeahead } from "./useTypeahead.js";
9
+ import styles from "./Tree.module.css";
10
+ const TAB_STOP = ':scope > [tabindex="0"]';
11
+ const INTERACTIVE = [
12
+ "a[href]",
13
+ "button",
14
+ "input",
15
+ "select",
16
+ "textarea",
17
+ "summary",
18
+ "label",
19
+ '[role="button"]',
20
+ '[role="link"]',
21
+ '[role="checkbox"]',
22
+ '[role="switch"]',
23
+ '[contenteditable=""]',
24
+ '[contenteditable="true"]',
25
+ '[tabindex]:not([tabindex="-1"])',
26
+ ].join(",");
27
+ function hasRow(rows, id) {
28
+ return id !== undefined && rows.some((row) => row.id === id);
29
+ }
30
+ function getSelected(rows, controlled, own) {
31
+ return hasRow(rows, controlled) ? controlled : own;
32
+ }
33
+ function isInsideControl(target, row) {
34
+ if (!(target instanceof Element)) {
35
+ return false;
36
+ }
37
+ const control = target.closest(INTERACTIVE);
38
+ return control !== null && control !== row && row.contains(control);
39
+ }
40
+ export function Tree({ root, label, renderRow, getRowText, expanded: controlledExpanded, defaultExpanded, onExpandedChange, selected: controlledSelected, onSelectedChange, className, onKeyDown, onFocus, onBlur, ref, ...props }) {
41
+ const [ownExpanded, setOwnExpanded] = useState(() => new Set(defaultExpanded));
42
+ const [ownSelected, setOwnSelected] = useState();
43
+ const element = useRef(null);
44
+ const focusAfterRender = useRef(false);
45
+ const hasFocusWithin = useRef(false);
46
+ const reported = useRef(controlledSelected);
47
+ const expanded = controlledExpanded ?? ownExpanded;
48
+ const rows = useMemo(() => visibleRows(root, expanded), [root, expanded]);
49
+ const [previousRows, setPreviousRows] = useState(rows);
50
+ if (previousRows !== rows) {
51
+ setPreviousRows(rows);
52
+ const before = getSelected(previousRows, controlledSelected, ownSelected);
53
+ if (before !== undefined && !hasRow(rows, before)) {
54
+ setOwnSelected(findVisibleAncestor(previousRows, before, rows));
55
+ }
56
+ }
57
+ const focused = getSelected(rows, controlledSelected, ownSelected);
58
+ const tabStop = useMemo(() => Math.max(0, rows.findIndex((row) => row.id === focused)), [rows, focused]);
59
+ const rowWindow = useRowWindow(element, rows.length);
60
+ const withTypedText = useTypeahead();
61
+ const scrolledTo = useRef(focused);
62
+ const notifySelected = useEffectEvent((id) => {
63
+ onSelectedChange?.(id);
64
+ });
65
+ useEffect(() => {
66
+ if (focused !== undefined &&
67
+ focused !== controlledSelected &&
68
+ focused !== reported.current) {
69
+ reported.current = focused;
70
+ notifySelected(focused);
71
+ }
72
+ }, [focused, controlledSelected]);
73
+ useImperativeHandle(ref, () => element.current);
74
+ useLayoutEffect(() => {
75
+ const container = element.current;
76
+ if (!container) {
77
+ return;
78
+ }
79
+ for (const child of container.children) {
80
+ if (!(child instanceof HTMLElement)) {
81
+ continue;
82
+ }
83
+ const gapRows = child.dataset.gapRows;
84
+ if (gapRows === undefined) {
85
+ child.style.setProperty("--tree-level", child.getAttribute("aria-level") ?? "1");
86
+ }
87
+ else {
88
+ child.style.setProperty("--tree-gap-rows", gapRows);
89
+ }
90
+ }
91
+ const lostFocus = hasFocusWithin.current && !container.contains(document.activeElement);
92
+ if (focusAfterRender.current || lostFocus) {
93
+ focusAfterRender.current = false;
94
+ const target = container.querySelector(TAB_STOP);
95
+ if (target instanceof HTMLElement) {
96
+ target.focus();
97
+ }
98
+ }
99
+ if (scrolledTo.current !== focused) {
100
+ scrolledTo.current = focused;
101
+ container
102
+ .querySelector(TAB_STOP)
103
+ ?.scrollIntoView({ block: "nearest", inline: "nearest" });
104
+ }
105
+ });
106
+ function select(id) {
107
+ if (id === focused) {
108
+ return;
109
+ }
110
+ reported.current = id;
111
+ if (controlledSelected === undefined) {
112
+ setOwnSelected(id);
113
+ }
114
+ onSelectedChange?.(id);
115
+ }
116
+ function dispatch(action) {
117
+ const current = { expanded, focused: rows[tabStop]?.id };
118
+ const next = reduceTree(rows, current, action, getRowText);
119
+ if (next.expanded !== expanded) {
120
+ if (controlledExpanded === undefined) {
121
+ setOwnExpanded(next.expanded);
122
+ }
123
+ onExpandedChange?.(next.expanded);
124
+ }
125
+ if (next.focused !== undefined && next.focused !== focused) {
126
+ focusAfterRender.current = true;
127
+ select(next.focused);
128
+ }
129
+ }
130
+ function handleKeyDown(event) {
131
+ onKeyDown?.(event);
132
+ if (event.defaultPrevented ||
133
+ event.ctrlKey ||
134
+ event.metaKey ||
135
+ event.altKey ||
136
+ event.target !== event.currentTarget) {
137
+ return;
138
+ }
139
+ const action = keyToAction(event.key);
140
+ if (action) {
141
+ event.preventDefault();
142
+ dispatch(withTypedText(action, event.timeStamp));
143
+ }
144
+ }
145
+ function renderTreeRow(row, index) {
146
+ if (!row) {
147
+ return null;
148
+ }
149
+ return (_jsxs("div", { role: "treeitem", "aria-level": row.level, "aria-setsize": row.setSize, "aria-posinset": row.positionInSet, "aria-expanded": row.expandable ? row.expanded : undefined, "aria-selected": row.id === focused, tabIndex: index === tabStop ? 0 : -1, className: styles.row, style: { "--tree-level": row.level }, onClick: (event) => {
150
+ if (!isInsideControl(event.target, event.currentTarget)) {
151
+ dispatch({ type: "toggle", id: row.id });
152
+ }
153
+ }, onFocus: () => select(row.id), onKeyDown: handleKeyDown, children: [_jsx("span", { className: styles.toggle, "aria-hidden": "true" }), _jsx("span", { children: renderRow(row) })] }, row.id));
154
+ }
155
+ return (_jsx("div", { ...props, ref: element, role: "tree", "aria-label": label, tabIndex: -1, className: clsx(styles.tree, className), onFocus: (event) => {
156
+ hasFocusWithin.current = true;
157
+ onFocus?.(event);
158
+ }, onBlur: (event) => {
159
+ if (!event.currentTarget.contains(event.relatedTarget)) {
160
+ hasFocusWithin.current = false;
161
+ }
162
+ onBlur?.(event);
163
+ }, children: rowWindow
164
+ ? getWindowItems(rowWindow, rows.length, tabStop).map((item) => item.type === "gap" ? (_jsx("div", { role: "none", className: styles.gap, "data-gap-rows": item.rowCount, style: { "--tree-gap-rows": item.rowCount } }, `gap-${item.id}`)) : (renderTreeRow(rows[item.index], item.index)))
165
+ : rows.map(renderTreeRow) }));
166
+ }
@@ -0,0 +1,65 @@
1
+ .tree {
2
+ overflow-x: auto;
3
+ color: var(--dusty-text);
4
+ font-family: var(--dusty-font-mono);
5
+ font-size: 0.8125rem;
6
+ line-height: 1.6;
7
+ }
8
+
9
+ .row {
10
+ display: flex;
11
+ align-items: baseline;
12
+ inline-size: max-content;
13
+ min-inline-size: 100%;
14
+ padding-inline: calc((var(--tree-level, 1) - 1) * var(--tree-indent, 1rem))
15
+ 0.5rem;
16
+ block-size: var(--tree-row-height, 1.3rem);
17
+ border-radius: var(--dusty-radius-sm);
18
+ white-space: nowrap;
19
+ cursor: default;
20
+ }
21
+
22
+ .gap {
23
+ block-size: calc(var(--tree-gap-rows, 0) * var(--tree-row-height, 1.3rem));
24
+ }
25
+
26
+ .row[aria-expanded] {
27
+ cursor: pointer;
28
+ }
29
+
30
+ .row[aria-selected="true"] {
31
+ background-color: var(--dusty-surface-selected);
32
+ }
33
+
34
+ .row:focus-visible {
35
+ outline: var(--dusty-focus-ring-width) solid var(--dusty-focus-ring);
36
+ outline-offset: calc(-1 * var(--dusty-focus-ring-width));
37
+ }
38
+
39
+ .toggle {
40
+ flex: none;
41
+ inline-size: 1rem;
42
+ color: var(--dusty-text-muted);
43
+ text-align: center;
44
+ }
45
+
46
+ .row[aria-expanded] > .toggle::before {
47
+ display: inline-block;
48
+ content: "▸";
49
+ }
50
+
51
+ .row[aria-expanded="true"] > .toggle::before {
52
+ rotate: 90deg;
53
+ }
54
+
55
+ @media (hover: hover) {
56
+ .row:hover:not([aria-selected="true"]) {
57
+ background-color: var(--dusty-surface-hover);
58
+ }
59
+ }
60
+
61
+ @media (prefers-reduced-motion: no-preference) {
62
+ .row[aria-expanded] > .toggle::before {
63
+ transition: rotate 120ms ease-out;
64
+ }
65
+ }