@puzzle-ui/ui 0.4.0 → 0.5.1

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 (163) hide show
  1. package/README.md +61 -77
  2. package/dist/components/actions/button/button.css +5 -2
  3. package/dist/components/actions/button-group/button-group.css +16 -17
  4. package/dist/components/data-display/badge/badge.css +15 -2
  5. package/dist/components/data-display/badge/index.cjs +13 -2
  6. package/dist/components/data-display/badge/index.cjs.map +1 -1
  7. package/dist/components/data-display/badge/index.d.cts +7 -2
  8. package/dist/components/data-display/badge/index.d.ts +7 -2
  9. package/dist/components/data-display/badge/index.js +13 -2
  10. package/dist/components/data-display/badge/index.js.map +1 -1
  11. package/dist/components/data-display/rating/index.cjs +84 -0
  12. package/dist/components/data-display/rating/index.cjs.map +1 -0
  13. package/dist/components/data-display/rating/index.d.cts +3 -0
  14. package/dist/components/data-display/rating/index.d.ts +3 -0
  15. package/dist/components/data-display/rating/index.js +84 -0
  16. package/dist/components/data-display/rating/index.js.map +1 -0
  17. package/dist/components/data-display/rating/rating.css +72 -0
  18. package/dist/components/data-display/rating/rating.types.d.cts +15 -0
  19. package/dist/components/data-display/rating/rating.types.d.ts +15 -0
  20. package/dist/components/data-display/rating/rating.utils.cjs +27 -0
  21. package/dist/components/data-display/rating/rating.utils.cjs.map +1 -0
  22. package/dist/components/data-display/rating/rating.utils.js +25 -0
  23. package/dist/components/data-display/rating/rating.utils.js.map +1 -0
  24. package/dist/components/forms/checkbox/checkbox.css +2 -10
  25. package/dist/components/forms/input-group/input-group.d.cts +4 -2
  26. package/dist/components/forms/input-group/input-group.d.ts +4 -2
  27. package/dist/components/forms/radio-group/index.d.cts +2 -1
  28. package/dist/components/forms/radio-group/index.d.ts +2 -1
  29. package/dist/components/forms/stepper/index.d.cts +4 -2
  30. package/dist/components/forms/stepper/index.d.ts +4 -2
  31. package/dist/components/forms/time-picker/index.cjs +108 -0
  32. package/dist/components/forms/time-picker/index.cjs.map +1 -0
  33. package/dist/components/forms/time-picker/index.d.cts +3 -0
  34. package/dist/components/forms/time-picker/index.d.ts +3 -0
  35. package/dist/components/forms/time-picker/index.js +108 -0
  36. package/dist/components/forms/time-picker/index.js.map +1 -0
  37. package/dist/components/forms/time-picker/time-picker.css +166 -0
  38. package/dist/components/forms/time-picker/time-picker.types.d.cts +34 -0
  39. package/dist/components/forms/time-picker/time-picker.types.d.ts +34 -0
  40. package/dist/components/forms/time-picker/time-picker.utils.cjs +25 -0
  41. package/dist/components/forms/time-picker/time-picker.utils.cjs.map +1 -0
  42. package/dist/components/forms/time-picker/time-picker.utils.js +24 -0
  43. package/dist/components/forms/time-picker/time-picker.utils.js.map +1 -0
  44. package/dist/components/forms/token-input/index.cjs +189 -0
  45. package/dist/components/forms/token-input/index.cjs.map +1 -0
  46. package/dist/components/forms/token-input/index.d.cts +3 -0
  47. package/dist/components/forms/token-input/index.d.ts +3 -0
  48. package/dist/components/forms/token-input/index.js +189 -0
  49. package/dist/components/forms/token-input/index.js.map +1 -0
  50. package/dist/components/forms/token-input/token-input.css +145 -0
  51. package/dist/components/forms/token-input/token-input.types.d.cts +48 -0
  52. package/dist/components/forms/token-input/token-input.types.d.ts +48 -0
  53. package/dist/components/forms/token-input/token-input.utils.cjs +30 -0
  54. package/dist/components/forms/token-input/token-input.utils.cjs.map +1 -0
  55. package/dist/components/forms/token-input/token-input.utils.js +24 -0
  56. package/dist/components/forms/token-input/token-input.utils.js.map +1 -0
  57. package/dist/components/layout/aspect-ratio/aspect-ratio.css +14 -0
  58. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs +11 -0
  59. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs.map +1 -0
  60. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js +11 -0
  61. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js.map +1 -0
  62. package/dist/components/layout/aspect-ratio/index.cjs +16 -0
  63. package/dist/components/layout/aspect-ratio/index.cjs.map +1 -0
  64. package/dist/components/layout/aspect-ratio/index.d.cts +11 -0
  65. package/dist/components/layout/aspect-ratio/index.d.ts +11 -0
  66. package/dist/components/layout/aspect-ratio/index.js +16 -0
  67. package/dist/components/layout/aspect-ratio/index.js.map +1 -0
  68. package/dist/components/layout/container/container.css +1 -0
  69. package/dist/components/layout/page-layout/page-layout.css +1 -0
  70. package/dist/components/layout/scroll-area/index.cjs +27 -0
  71. package/dist/components/layout/scroll-area/index.cjs.map +1 -0
  72. package/dist/components/layout/scroll-area/index.d.cts +15 -0
  73. package/dist/components/layout/scroll-area/index.d.ts +15 -0
  74. package/dist/components/layout/scroll-area/index.js +27 -0
  75. package/dist/components/layout/scroll-area/index.js.map +1 -0
  76. package/dist/components/layout/scroll-area/scroll-area.css +25 -0
  77. package/dist/components/layout/scroll-area/scroll-area.utils.cjs +8 -0
  78. package/dist/components/layout/scroll-area/scroll-area.utils.cjs.map +1 -0
  79. package/dist/components/layout/scroll-area/scroll-area.utils.d.cts +2 -0
  80. package/dist/components/layout/scroll-area/scroll-area.utils.d.ts +2 -0
  81. package/dist/components/layout/scroll-area/scroll-area.utils.js +8 -0
  82. package/dist/components/layout/scroll-area/scroll-area.utils.js.map +1 -0
  83. package/dist/components/layout/section/index.cjs +10 -4
  84. package/dist/components/layout/section/index.cjs.map +1 -1
  85. package/dist/components/layout/section/index.d.cts +1 -1
  86. package/dist/components/layout/section/index.d.ts +1 -1
  87. package/dist/components/layout/section/index.js +11 -5
  88. package/dist/components/layout/section/index.js.map +1 -1
  89. package/dist/components/layout/section/section.css +9 -3
  90. package/dist/components/layout/section/types.d.cts +5 -5
  91. package/dist/components/layout/section/types.d.ts +5 -5
  92. package/dist/components/layout/separator/index.cjs +18 -0
  93. package/dist/components/layout/separator/index.cjs.map +1 -0
  94. package/dist/components/layout/separator/index.d.cts +17 -0
  95. package/dist/components/layout/separator/index.d.ts +17 -0
  96. package/dist/components/layout/separator/index.js +18 -0
  97. package/dist/components/layout/separator/index.js.map +1 -0
  98. package/dist/components/layout/separator/separator.css +15 -0
  99. package/dist/components/layout/separator/separator.utils.cjs +11 -0
  100. package/dist/components/layout/separator/separator.utils.cjs.map +1 -0
  101. package/dist/components/layout/separator/separator.utils.d.cts +2 -0
  102. package/dist/components/layout/separator/separator.utils.d.ts +2 -0
  103. package/dist/components/layout/separator/separator.utils.js +11 -0
  104. package/dist/components/layout/separator/separator.utils.js.map +1 -0
  105. package/dist/components/layout/stack/index.cjs +18 -2
  106. package/dist/components/layout/stack/index.cjs.map +1 -1
  107. package/dist/components/layout/stack/index.d.cts +8 -6
  108. package/dist/components/layout/stack/index.d.ts +8 -6
  109. package/dist/components/layout/stack/index.js +18 -2
  110. package/dist/components/layout/stack/index.js.map +1 -1
  111. package/dist/components/layout/stack/stack.css +118 -138
  112. package/dist/components/navigation/carousel/index.cjs +4 -1
  113. package/dist/components/navigation/carousel/index.cjs.map +1 -1
  114. package/dist/components/navigation/carousel/index.js +4 -1
  115. package/dist/components/navigation/carousel/index.js.map +1 -1
  116. package/dist/components/navigation/tabs/index.d.cts +3 -2
  117. package/dist/components/navigation/tabs/index.d.ts +3 -2
  118. package/dist/components/navigation/tree-view/index.cjs +1 -1
  119. package/dist/components/navigation/tree-view/index.cjs.map +1 -1
  120. package/dist/components/navigation/tree-view/index.d.cts +5 -1
  121. package/dist/components/navigation/tree-view/index.d.ts +5 -1
  122. package/dist/components/navigation/tree-view/index.js +1 -1
  123. package/dist/components/navigation/tree-view/index.js.map +1 -1
  124. package/dist/components/navigation/tree-view/tree-view.css +5 -3
  125. package/dist/components/overlays/context-menu/context-menu.css +151 -0
  126. package/dist/components/overlays/context-menu/context-menu.types.d.cts +69 -0
  127. package/dist/components/overlays/context-menu/context-menu.types.d.ts +69 -0
  128. package/dist/components/overlays/context-menu/context-menu.utils.cjs +46 -0
  129. package/dist/components/overlays/context-menu/context-menu.utils.cjs.map +1 -0
  130. package/dist/components/overlays/context-menu/context-menu.utils.js +40 -0
  131. package/dist/components/overlays/context-menu/context-menu.utils.js.map +1 -0
  132. package/dist/components/overlays/context-menu/index.cjs +830 -0
  133. package/dist/components/overlays/context-menu/index.cjs.map +1 -0
  134. package/dist/components/overlays/context-menu/index.d.cts +42 -0
  135. package/dist/components/overlays/context-menu/index.d.ts +42 -0
  136. package/dist/components/overlays/context-menu/index.js +818 -0
  137. package/dist/components/overlays/context-menu/index.js.map +1 -0
  138. package/dist/components/typography/heading.cjs +6 -2
  139. package/dist/components/typography/heading.cjs.map +1 -1
  140. package/dist/components/typography/heading.d.cts +3 -2
  141. package/dist/components/typography/heading.d.ts +3 -2
  142. package/dist/components/typography/heading.js +6 -2
  143. package/dist/components/typography/heading.js.map +1 -1
  144. package/dist/components/typography/text.cjs +6 -2
  145. package/dist/components/typography/text.cjs.map +1 -1
  146. package/dist/components/typography/text.d.cts +3 -2
  147. package/dist/components/typography/text.d.ts +3 -2
  148. package/dist/components/typography/text.js +6 -2
  149. package/dist/components/typography/text.js.map +1 -1
  150. package/dist/components/utilities/preview/index.cjs +88 -20
  151. package/dist/components/utilities/preview/index.cjs.map +1 -1
  152. package/dist/components/utilities/preview/index.d.cts +6 -2
  153. package/dist/components/utilities/preview/index.d.ts +6 -2
  154. package/dist/components/utilities/preview/index.js +88 -20
  155. package/dist/components/utilities/preview/index.js.map +1 -1
  156. package/dist/components/utilities/preview/preview.css +26 -11
  157. package/dist/components/utilities/transition/index.d.cts +1 -1
  158. package/dist/components/utilities/transition/index.d.ts +1 -1
  159. package/dist/index.cjs +123 -97
  160. package/dist/index.d.cts +15 -2
  161. package/dist/index.d.ts +15 -2
  162. package/dist/index.js +8 -1
  163. package/package.json +11 -6
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["useId","useState","useRef","useMemo","useCallback","Link","cn"],"sources":["../../../../src/components/navigation/tree-view/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"@puzzle-ui/core\";\nimport { Link } from \"../link\";\nimport \"./tree-view.css\";\n\ntype TreeViewSelectionMode = \"none\" | \"single\" | \"multiple\";\ntype TreeViewSize = \"sm\" | \"md\";\n\ntype TreeViewItem<TData = unknown> = {\n /** Stable, unique identifier for this node. */\n id: string;\n\n /** Visible node label. Supply textValue when the label is not plain text. */\n label: ReactNode;\n\n /** Optional destination. When supplied, the complete row renders as a Puzzle Link. */\n href?: string;\n\n /** Plain-text value used by typeahead when label is not a string or number. */\n textValue?: string;\n\n /** Optional icon displayed before the label. */\n icon?: ReactNode;\n\n /** Optional, non-interactive content displayed after the label. */\n endAdornment?: ReactNode;\n\n /** Child nodes for a loaded branch. Omit for a leaf or a lazily loaded branch. */\n children?: TreeViewItem<TData>[];\n\n /** Marks a node as expandable before its children have been loaded. */\n hasChildren?: boolean;\n\n /** Prevents selection and activation; branches can still expand so descendants remain accessible. */\n disabled?: boolean;\n\n /** Allows a node to be excluded from selection without disabling expansion. */\n selectable?: boolean;\n\n /** Optional application data associated with the node. */\n data?: TData;\n};\n\ntype TreeViewItemState = {\n expanded: boolean;\n selected: boolean;\n focused: boolean;\n loading: boolean;\n disabled: boolean;\n hasChildren: boolean;\n};\n\ntype TreeViewProps<TData = unknown> = Omit<\n ComponentPropsWithRef<\"ul\">,\n | \"children\"\n | \"onKeyDown\"\n | \"onFocusCapture\"\n | \"onBlurCapture\"\n | \"role\"\n | \"tabIndex\"\n | \"aria-multiselectable\"\n> &\n (\n | { \"aria-label\": string; \"aria-labelledby\"?: string }\n | { \"aria-labelledby\": string; \"aria-label\"?: string }\n ) & {\n /** Hierarchical data rendered by the tree. Node IDs must be unique. */\n items: TreeViewItem<TData>[];\n\n /** Selection behavior. Defaults to single selection. */\n selectionMode?: TreeViewSelectionMode;\n\n /** Initial expanded node IDs for uncontrolled usage. Defaults to []. */\n defaultExpandedIds?: string[];\n\n /** Expanded node IDs for controlled usage. */\n expandedIds?: string[];\n\n /** Called with the next expanded node IDs. */\n onExpandedChange?: (expandedIds: string[]) => void;\n\n /** Initial selected node IDs for uncontrolled usage. Defaults to []. */\n defaultSelectedIds?: string[];\n\n /** Selected node IDs for controlled usage. */\n selectedIds?: string[];\n\n /** Called with the next selected node IDs. */\n onSelectionChange?: (selectedIds: string[]) => void;\n\n /** Initial roving-focus target. Defaults to the first selected node, then the first root node. */\n defaultFocusedId?: string;\n\n /** Called when keyboard or pointer interaction moves focus to another node. */\n onFocusedChange?: (focusedId: string) => void;\n\n /** Loads the children of a node marked with hasChildren when it is first expanded. */\n loadChildren?: (\n item: TreeViewItem<TData>,\n ) => Promise<TreeViewItem<TData>[]>;\n\n /** Called when lazy loading fails. Re-collapse and re-expand the node to retry. */\n onLoadChildrenError?: (item: TreeViewItem<TData>, error: unknown) => void;\n\n /** Called when a node is activated with Enter. */\n onItemActivate?: (item: TreeViewItem<TData>) => void;\n\n /** Determines whether an item matches the current route. */\n getItemActive?: (item: TreeViewItem<TData>) => boolean;\n\n /**\n * Custom node renderer. Replaces the icon, label and adornment of a row; the\n * expand indicator, row element and tree semantics are kept. Keep its contents non-interactive.\n */\n renderItem?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom node-icon renderer. */\n renderIcon?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom label renderer. Keep its contents non-interactive. */\n renderLabel?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Clicking the label of a branch toggles it instead of selecting it. Defaults to false. */\n expandOnItemClick?: boolean;\n\n /** Visual density. Defaults to md. */\n size?: TreeViewSize;\n\n /** Indentation between hierarchy levels, in CSS units. Defaults to the theme spacing token. */\n indentation?: string | number;\n\n /** Enables character-based typeahead navigation. Defaults to true. */\n typeahead?: boolean;\n\n /** Text announced and displayed while lazy children are loading. */\n loadingLabel?: string;\n\n /** Text displayed when lazy loading fails. */\n loadErrorLabel?: string;\n\n /** Class applied to each tree item. */\n itemClassName?: string;\n };\n\nconst normalizeIds = (ids: string[] | undefined) =>\n Array.from(\n new Set(\n (ids ?? []).filter((id) => typeof id === \"string\" && id.length > 0),\n ),\n );\n\nconst getTextValue = <TData,>(item: TreeViewItem<TData>) => {\n if (item.textValue !== undefined) return item.textValue;\n\n if (typeof item.label === \"string\" || typeof item.label === \"number\") {\n return String(item.label);\n }\n\n return \"\";\n};\n\nconst normalizeSearchText = (value: string) =>\n value\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLocaleLowerCase();\n\nconst TreeView = <TData,>({\n items,\n selectionMode = \"single\",\n defaultExpandedIds = [],\n expandedIds,\n onExpandedChange,\n defaultSelectedIds = [],\n selectedIds,\n onSelectionChange,\n defaultFocusedId,\n onFocusedChange,\n loadChildren,\n onLoadChildrenError,\n onItemActivate,\n getItemActive,\n renderItem,\n renderIcon,\n renderLabel,\n expandOnItemClick = false,\n typeahead = true,\n size = \"md\",\n indentation,\n loadingLabel = \"Loading…\",\n loadErrorLabel = \"Could not load children. Collapse and expand to retry.\",\n itemClassName,\n className,\n style,\n dir,\n ...rootProps\n}: TreeViewProps<TData>) => {\n const generatedId = useId();\n const isExpandedControlled = expandedIds !== undefined;\n const isSelectionControlled = selectedIds !== undefined;\n\n const [uncontrolledExpandedIds, setUncontrolledExpandedIds] = useState(() =>\n normalizeIds(defaultExpandedIds),\n );\n\n const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(() =>\n normalizeIds(defaultSelectedIds),\n );\n\n const [focusedId, setFocusedId] = useState<string | undefined>(\n () =>\n defaultFocusedId ??\n (selectionMode === \"none\"\n ? undefined\n : normalizeIds(selectedIds ?? defaultSelectedIds)[0]) ??\n items[0]?.id,\n );\n\n const [loadedChildren, setLoadedChildren] = useState<\n Map<string, TreeViewItem<TData>[]>\n >(() => new Map());\n\n const [loadingIds, setLoadingIds] = useState<string[]>([]);\n\n const [loadErrors, setLoadErrors] = useState<Map<string, unknown>>(\n () => new Map(),\n );\n\n const itemRefs = useRef(new Map<string, HTMLLIElement>());\n const inFlightLoads = useRef(new Set<string>());\n const previousExpandedIds = useRef<string[] | null>(null);\n const pendingFocusId = useRef<string | null>(null);\n const focusWithinTree = useRef(false);\n const typeaheadState = useRef({ value: \"\", time: 0 });\n\n const currentExpandedIds = isExpandedControlled\n ? (expandedIds ?? [])\n : uncontrolledExpandedIds;\n\n const requestedSelectedIds = isSelectionControlled\n ? (selectedIds ?? [])\n : uncontrolledSelectedIds;\n\n const currentSelectedIds =\n selectionMode === \"single\"\n ? requestedSelectedIds.slice(0, 1)\n : requestedSelectedIds;\n\n const expandedSet = useMemo(\n () => new Set(normalizeIds(currentExpandedIds)),\n [currentExpandedIds],\n );\n\n const selectedSet = useMemo(\n () => new Set(normalizeIds(currentSelectedIds)),\n [currentSelectedIds],\n );\n\n const loadingSet = useMemo(() => new Set(loadingIds), [loadingIds]);\n\n const getChildren = useCallback(\n (item: TreeViewItem<TData>) =>\n item.children ?? loadedChildren.get(item.id) ?? [],\n [loadedChildren],\n );\n\n const hasChildren = useCallback(\n (item: TreeViewItem<TData>) => {\n if (item.children !== undefined || loadedChildren.has(item.id)) {\n return getChildren(item).length > 0;\n }\n\n return Boolean(item.hasChildren);\n },\n [getChildren, loadedChildren],\n );\n\n const visibleItems = useMemo(() => {\n const result: TreeViewItem<TData>[] = [];\n\n const visit = (nodes: TreeViewItem<TData>[]) => {\n nodes.forEach((item) => {\n result.push(item);\n\n const children = getChildren(item);\n\n if (\n expandedSet.has(item.id) &&\n hasChildren(item) &&\n children.length > 0\n ) {\n visit(children);\n }\n });\n };\n\n visit(items);\n\n return result;\n }, [items, expandedSet, getChildren, hasChildren]);\n\n const parentIds = useMemo(() => {\n const result = new Map<string, string>();\n\n const visit = (nodes: TreeViewItem<TData>[], parentId?: string) => {\n nodes.forEach((item) => {\n if (parentId !== undefined) {\n result.set(item.id, parentId);\n }\n\n visit(getChildren(item), item.id);\n });\n };\n\n visit(items);\n\n return result;\n }, [items, getChildren]);\n\n const visibleIds = useMemo(\n () => new Set(visibleItems.map((item) => item.id)),\n [visibleItems],\n );\n\n let effectiveFocusedId = focusedId;\n\n if (!effectiveFocusedId || !visibleIds.has(effectiveFocusedId)) {\n let ancestor = effectiveFocusedId;\n\n while (ancestor && !visibleIds.has(ancestor)) {\n ancestor = parentIds.get(ancestor);\n }\n\n effectiveFocusedId = ancestor ?? visibleItems[0]?.id;\n }\n\n useEffect(() => {\n if (pendingFocusId.current && visibleIds.has(pendingFocusId.current)) {\n itemRefs.current.get(pendingFocusId.current)?.focus();\n pendingFocusId.current = null;\n }\n }, [visibleItems, visibleIds]);\n\n useEffect(() => {\n if (focusedId !== effectiveFocusedId && effectiveFocusedId) {\n setFocusedId(effectiveFocusedId);\n onFocusedChange?.(effectiveFocusedId);\n\n if (focusWithinTree.current) {\n itemRefs.current.get(effectiveFocusedId)?.focus();\n }\n }\n }, [effectiveFocusedId, focusedId, onFocusedChange]);\n\n const commitExpandedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isExpandedControlled) {\n setUncontrolledExpandedIds(next);\n }\n\n onExpandedChange?.(next);\n };\n\n const commitSelectedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isSelectionControlled) {\n setUncontrolledSelectedIds(next);\n }\n\n onSelectionChange?.(next);\n };\n\n const loadNodeChildren = useCallback(\n async (item: TreeViewItem<TData>) => {\n if (!loadChildren || !item.hasChildren || item.children !== undefined) {\n return;\n }\n\n if (loadedChildren.has(item.id) || inFlightLoads.current.has(item.id)) {\n return;\n }\n\n inFlightLoads.current.add(item.id);\n setLoadingIds((current) => [...current, item.id]);\n\n setLoadErrors((current) => {\n const next = new Map(current);\n next.delete(item.id);\n return next;\n });\n\n try {\n const children = await loadChildren(item);\n\n setLoadedChildren((current) => {\n const next = new Map(current);\n next.set(item.id, children);\n return next;\n });\n } catch (error) {\n setLoadErrors((current) => {\n const next = new Map(current);\n next.set(item.id, error);\n return next;\n });\n\n onLoadChildrenError?.(item, error);\n } finally {\n inFlightLoads.current.delete(item.id);\n setLoadingIds((current) => current.filter((id) => id !== item.id));\n }\n },\n [loadChildren, loadedChildren, onLoadChildrenError],\n );\n\n // Also load branches that were expanded through default or controlled props.\n // Tracking newly expanded IDs avoids retrying a failed request on unrelated renders.\n useEffect(() => {\n const previous = new Set(previousExpandedIds.current ?? []);\n\n const newlyExpandedIds = currentExpandedIds.filter(\n (id) => !previous.has(id),\n );\n\n previousExpandedIds.current = normalizeIds(currentExpandedIds);\n\n newlyExpandedIds.forEach((id) => {\n const item = findItemById(items, id, getChildren);\n if (item) void loadNodeChildren(item);\n });\n }, [currentExpandedIds, items, getChildren, loadNodeChildren]);\n\n const toggleExpanded = (item: TreeViewItem<TData>) => {\n if (!hasChildren(item)) return;\n\n const next = new Set(currentExpandedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n void loadNodeChildren(item);\n }\n\n commitExpandedIds(Array.from(next));\n };\n\n const selectItem = (item: TreeViewItem<TData>, toggleSingle = false) => {\n if (\n selectionMode === \"none\" ||\n item.disabled ||\n item.selectable === false\n ) {\n return;\n }\n\n if (selectionMode === \"single\") {\n if (\n currentSelectedIds.length === 1 &&\n currentSelectedIds[0] === item.id\n ) {\n if (toggleSingle) commitSelectedIds([]);\n return;\n }\n\n commitSelectedIds([item.id]);\n return;\n }\n\n const next = new Set(currentSelectedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n }\n\n commitSelectedIds(Array.from(next));\n };\n\n const focusItem = (id: string) => {\n const element = itemRefs.current.get(id);\n\n if (element) {\n element.focus();\n } else {\n setFocusedId(id);\n onFocusedChange?.(id);\n pendingFocusId.current = id;\n }\n };\n\n const expandSiblings = (itemId: string) => {\n const parentId = parentIds.get(itemId);\n\n const parentItem = parentId\n ? findItemById(items, parentId, getChildren)\n : undefined;\n\n const siblings = parentItem ? getChildren(parentItem) : items;\n const next = new Set(currentExpandedIds);\n let changed = false;\n\n siblings.forEach((sibling) => {\n if (hasChildren(sibling) && !next.has(sibling.id)) {\n next.add(sibling.id);\n changed = true;\n void loadNodeChildren(sibling);\n }\n });\n\n if (changed) {\n commitExpandedIds(Array.from(next));\n }\n };\n\n const handleKeyDown = (\n event: KeyboardEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n parentId?: string,\n ) => {\n const eventTarget = event.target as HTMLElement;\n\n if (eventTarget.closest('[role=\"treeitem\"]') !== event.currentTarget) {\n return;\n }\n\n if (\n eventTarget.closest(\n \"button, a, input, select, textarea, [contenteditable='true']\",\n )\n ) {\n return;\n }\n\n const index = visibleItems.findIndex(\n (visibleItem) => visibleItem.id === item.id,\n );\n\n const inheritedDirection = event.currentTarget\n .closest(\"[dir]\")\n ?.getAttribute(\"dir\");\n\n const isRtl =\n dir === \"rtl\" || (dir === undefined && inheritedDirection === \"rtl\");\n\n const openKey = isRtl ? \"ArrowLeft\" : \"ArrowRight\";\n const closeKey = isRtl ? \"ArrowRight\" : \"ArrowLeft\";\n let handled = false;\n\n switch (event.key) {\n case \"ArrowDown\":\n if (index < visibleItems.length - 1) {\n focusItem(visibleItems[index + 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"ArrowUp\":\n if (index > 0) {\n focusItem(visibleItems[index - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Home\":\n if (visibleItems[0]) {\n focusItem(visibleItems[0].id);\n }\n\n handled = true;\n break;\n\n case \"End\":\n if (visibleItems.length > 0) {\n focusItem(visibleItems[visibleItems.length - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Enter\":\n if (item.href) {\n if (!item.disabled) {\n event.currentTarget\n .querySelector<HTMLAnchorElement>(\"a.puzzle-tree__row\")\n ?.click();\n\n onItemActivate?.(item);\n }\n } else if (hasChildren(item)) {\n toggleExpanded(item);\n if (!item.disabled) onItemActivate?.(item);\n } else {\n selectItem(item);\n if (!item.disabled) onItemActivate?.(item);\n }\n\n handled = true;\n break;\n\n case \" \":\n case \"Spacebar\":\n selectItem(item, true);\n handled = true;\n break;\n\n case \"*\":\n expandSiblings(item.id);\n handled = true;\n break;\n\n default:\n if (\n event.key.length === 1 &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey\n ) {\n if (!typeahead) break;\n\n const currentTime = event.timeStamp;\n const isExpired = currentTime - typeaheadState.current.time > 500;\n const previousValue = isExpired ? \"\" : typeaheadState.current.value;\n const normalizedPrevious = normalizeSearchText(previousValue);\n const normalizedKey = normalizeSearchText(event.key);\n\n const isRepeatedCharacter =\n normalizedPrevious.length > 0 &&\n [...normalizedPrevious].every(\n (character) => character === normalizedKey,\n );\n\n const searchValue = isRepeatedCharacter\n ? event.key\n : `${previousValue}${event.key}`;\n\n typeaheadState.current = {\n value: searchValue,\n time: currentTime,\n };\n\n const normalizedSearch = normalizeSearchText(searchValue);\n\n const orderedItems = [\n ...visibleItems.slice(index + 1),\n ...visibleItems.slice(0, index + 1),\n ];\n\n const match = orderedItems.find((candidate) =>\n normalizeSearchText(getTextValue(candidate)).startsWith(\n normalizedSearch,\n ),\n );\n\n if (match) {\n focusItem(match.id);\n }\n\n handled = true;\n }\n }\n\n if (event.key === openKey) {\n if (hasChildren(item)) {\n if (!expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else {\n const firstChild = getChildren(item)[0];\n if (firstChild) focusItem(firstChild.id);\n }\n }\n\n handled = true;\n } else if (event.key === closeKey) {\n if (hasChildren(item) && expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else if (parentId) {\n focusItem(parentId);\n }\n\n handled = true;\n }\n\n if (handled) {\n event.preventDefault();\n }\n };\n\n const handleItemClick = (\n event: MouseEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as HTMLElement;\n const targetTreeItem = target.closest('[role=\"treeitem\"]');\n\n // Ignore bubbled clicks from descendants and clicks on custom interactive adornments.\n if (targetTreeItem !== event.currentTarget) return;\n\n if (\n target.closest(\n \"button, a, input, select, textarea, [data-tree-stop-selection]\",\n )\n ) {\n return;\n }\n\n focusItem(item.id);\n\n if (\n hasChildren(item) &&\n (target.closest(\"[data-tree-toggle]\") || expandOnItemClick)\n ) {\n toggleExpanded(item);\n return;\n }\n\n selectItem(item);\n };\n\n /**\n * Handles pointer activation of a linked row.\n *\n * The row link is the navigation target, but the tree item remains the\n * roving-focus element. Clicking the expansion affordance is the exception:\n * it expands the branch and deliberately does not navigate.\n */\n const handleItemLinkClick = (\n event: MouseEvent<HTMLAnchorElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as Element;\n\n if (target.closest(\"[data-tree-toggle]\")) {\n event.preventDefault();\n event.stopPropagation();\n\n focusItem(item.id);\n toggleExpanded(item);\n return;\n }\n\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n focusItem(item.id);\n selectItem(item);\n };\n\n const renderItems = (\n nodes: TreeViewItem<TData>[],\n depth = 0,\n parentId?: string,\n ): ReactNode =>\n nodes.map((item, index) => {\n const itemChildren = getChildren(item);\n const itemHasChildren = hasChildren(item);\n const expanded = itemHasChildren && expandedSet.has(item.id);\n const disabled = Boolean(item.disabled);\n const active = Boolean(getItemActive?.(item));\n\n const selected =\n selectionMode !== \"none\" &&\n selectedSet.has(item.id) &&\n item.selectable !== false &&\n !disabled;\n\n const loading = loadingSet.has(item.id);\n const focused = effectiveFocusedId === item.id;\n\n const itemState: TreeViewItemState = {\n expanded,\n selected,\n focused,\n loading,\n disabled,\n hasChildren: itemHasChildren,\n };\n\n const iconContent = renderIcon ? renderIcon(item, itemState) : item.icon;\n const itemId = `${generatedId}-item-${encodeURIComponent(item.id)}`;\n\n const selectable =\n selectionMode !== \"none\" && item.selectable !== false && !disabled;\n\n const hasLoadError = loadErrors.has(item.id);\n\n const rowContent = (\n <>\n {itemHasChildren ? (\n <span\n className=\"puzzle-tree__toggle\"\n data-tree-toggle=\"true\"\n data-expanded={expanded || undefined}\n aria-hidden=\"true\"\n />\n ) : (\n <span className=\"puzzle-tree__toggle-spacer\" aria-hidden=\"true\" />\n )}\n\n {renderItem ? (\n renderItem(item, itemState)\n ) : (\n <>\n {iconContent != null && (\n <span className=\"puzzle-tree__icon\" aria-hidden=\"true\">\n {iconContent}\n </span>\n )}\n\n <span className=\"puzzle-tree__label\">\n {renderLabel ? renderLabel(item, itemState) : item.label}\n </span>\n\n {item.endAdornment != null && (\n <span className=\"puzzle-tree__adornment\">\n {item.endAdornment}\n </span>\n )}\n </>\n )}\n </>\n );\n\n const row =\n item.href !== undefined ? (\n <Link\n href={item.href}\n className=\"puzzle-tree__row\"\n tabIndex={-1}\n aria-current={active ? \"page\" : undefined}\n aria-disabled={disabled || undefined}\n data-tree-row-link=\"true\"\n onClick={(event) => handleItemLinkClick(event, item)}\n >\n {rowContent}\n </Link>\n ) : (\n <div className=\"puzzle-tree__row\">{rowContent}</div>\n );\n\n return (\n <li\n key={item.id}\n ref={(element) => {\n if (element) {\n itemRefs.current.set(item.id, element);\n } else {\n itemRefs.current.delete(item.id);\n }\n }}\n id={itemId}\n role=\"treeitem\"\n aria-level={depth + 1}\n aria-posinset={index + 1}\n aria-setsize={nodes.length}\n aria-expanded={itemHasChildren ? expanded : undefined}\n aria-selected={selectable ? selected : undefined}\n aria-disabled={disabled || undefined}\n aria-busy={loading || undefined}\n tabIndex={focused ? 0 : -1}\n className={cn(\"puzzle-tree__item\", itemClassName)}\n data-tree-item-id={item.id}\n data-active={active || undefined}\n data-selected={selected || undefined}\n data-disabled={disabled || undefined}\n data-expanded={itemHasChildren ? expanded : undefined}\n onFocus={(event) => {\n if (event.target !== event.currentTarget) return;\n\n if (focusedId !== item.id) {\n setFocusedId(item.id);\n onFocusedChange?.(item.id);\n }\n }}\n onClick={(event) => handleItemClick(event, item)}\n onKeyDown={(event) => handleKeyDown(event, item, parentId)}\n >\n {row}\n\n {itemHasChildren && expanded && (\n <ul className=\"puzzle-tree__group\" role=\"group\">\n {loading || hasLoadError ? (\n <li\n role=\"none\"\n className={cn(\n \"puzzle-tree__status-row\",\n hasLoadError &&\n !loading &&\n \"puzzle-tree__status-row--error\",\n )}\n >\n <span role=\"status\" aria-live=\"polite\">\n {loading ? loadingLabel : loadErrorLabel}\n </span>\n </li>\n ) : null}\n\n {renderItems(itemChildren, depth + 1, item.id)}\n </ul>\n )}\n </li>\n );\n });\n\n const rootStyle = {\n ...style,\n ...(indentation !== undefined\n ? {\n \"--puzzle-tree-indentation\":\n typeof indentation === \"number\" ? `${indentation}px` : indentation,\n }\n : {}),\n } as CSSProperties;\n\n return (\n <ul\n {...rootProps}\n role=\"tree\"\n aria-multiselectable={selectionMode === \"multiple\" || undefined}\n className={cn(\"puzzle-tree\", `puzzle-tree--${size}`, className)}\n style={rootStyle}\n dir={dir}\n tabIndex={visibleItems.length === 0 ? 0 : undefined}\n onFocusCapture={() => {\n focusWithinTree.current = true;\n }}\n onBlurCapture={(event) => {\n const nextTarget = event.relatedTarget;\n const target = event.target as HTMLElement;\n\n if (\n event.currentTarget.contains(target) &&\n ((nextTarget && !event.currentTarget.contains(nextTarget as Node)) ||\n (!nextTarget && target.isConnected))\n ) {\n focusWithinTree.current = false;\n }\n }}\n >\n {renderItems(items)}\n </ul>\n );\n};\n\nconst findItemById = <TData,>(\n nodes: TreeViewItem<TData>[],\n id: string,\n getChildren: (item: TreeViewItem<TData>) => TreeViewItem<TData>[],\n): TreeViewItem<TData> | undefined => {\n for (const item of nodes) {\n if (item.id === id) return item;\n\n const found = findItemById(getChildren(item), id, getChildren);\n if (found) return found;\n }\n\n return undefined;\n};\n\nexport {\n TreeView,\n type TreeViewProps,\n type TreeViewItem,\n type TreeViewItemState,\n type TreeViewSelectionMode,\n type TreeViewSize,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["useId","useState","useRef","useMemo","useCallback","Link","cn"],"sources":["../../../../src/components/navigation/tree-view/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"@puzzle-ui/core\";\nimport { Link } from \"../link\";\nimport \"./tree-view.css\";\n\ntype TreeViewSelectionMode = \"none\" | \"single\" | \"multiple\";\ntype TreeViewSize = \"sm\" | \"md\";\n\ntype TreeViewItem<TData = unknown> = {\n /** Stable, unique identifier for this node. */\n id: string;\n\n /** Visible node label. Supply textValue when the label is not plain text. */\n label: ReactNode;\n\n /** Optional destination. When supplied, the complete row renders as a Puzzle Link. */\n href?: string;\n\n /** Plain-text value used by typeahead when label is not a string or number. */\n textValue?: string;\n\n /** Optional icon displayed before the label. */\n icon?: ReactNode;\n\n /** Optional, non-interactive content displayed after the label. */\n endAdornment?: ReactNode;\n\n /** Child nodes for a loaded branch. Omit for a leaf or a lazily loaded branch. */\n children?: TreeViewItem<TData>[];\n\n /** Marks a node as expandable before its children have been loaded. */\n hasChildren?: boolean;\n\n /** Prevents selection and activation; branches can still expand so descendants remain accessible. */\n disabled?: boolean;\n\n /** Allows a node to be excluded from selection without disabling expansion. */\n selectable?: boolean;\n\n /** Optional application data associated with the node. */\n data?: TData;\n};\n\ntype TreeViewItemState = {\n expanded: boolean;\n selected: boolean;\n focused: boolean;\n loading: boolean;\n disabled: boolean;\n hasChildren: boolean;\n};\n\ntype TreeViewProps<TData = unknown> = Omit<\n ComponentPropsWithRef<\"ul\">,\n | \"children\"\n | \"onKeyDown\"\n | \"onFocusCapture\"\n | \"onBlurCapture\"\n | \"role\"\n | \"tabIndex\"\n | \"aria-multiselectable\"\n> &\n (\n | { \"aria-label\": string; \"aria-labelledby\"?: string }\n | { \"aria-labelledby\": string; \"aria-label\"?: string }\n ) & {\n /** Hierarchical data rendered by the tree. Node IDs must be unique. */\n items: TreeViewItem<TData>[];\n\n /** Selection behavior. Defaults to single selection. */\n selectionMode?: TreeViewSelectionMode;\n\n /** Initial expanded node IDs for uncontrolled usage. Defaults to []. */\n defaultExpandedIds?: string[];\n\n /** Expanded node IDs for controlled usage. */\n expandedIds?: string[];\n\n /** Called with the next expanded node IDs. */\n onExpandedChange?: (expandedIds: string[]) => void;\n\n /** Initial selected node IDs for uncontrolled usage. Defaults to []. */\n defaultSelectedIds?: string[];\n\n /** Selected node IDs for controlled usage. */\n selectedIds?: string[];\n\n /** Called with the next selected node IDs. */\n onSelectionChange?: (selectedIds: string[]) => void;\n\n /** Initial roving-focus target. Defaults to the first selected node, then the first root node. */\n defaultFocusedId?: string;\n\n /** Called when keyboard or pointer interaction moves focus to another node. */\n onFocusedChange?: (focusedId: string) => void;\n\n /** Loads the children of a node marked with hasChildren when it is first expanded. */\n loadChildren?: (\n item: TreeViewItem<TData>,\n ) => Promise<TreeViewItem<TData>[]>;\n\n /** Called when lazy loading fails. Re-collapse and re-expand the node to retry. */\n onLoadChildrenError?: (item: TreeViewItem<TData>, error: unknown) => void;\n\n /** Called when a node is activated with Enter. */\n onItemActivate?: (item: TreeViewItem<TData>) => void;\n\n /** Determines whether an item matches the current route. */\n getItemActive?: (item: TreeViewItem<TData>) => boolean;\n\n /**\n * Custom node renderer. Replaces the icon, label and adornment of a row; the\n * expand indicator, row element and tree semantics are kept. Keep its contents non-interactive.\n */\n renderItem?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom node-icon renderer. */\n renderIcon?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom label renderer. Keep its contents non-interactive. */\n renderLabel?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /**\n * Clicking anywhere on a group row (a branch without `href`) toggles it\n * instead of selecting it. Linked branches always navigate. Set to false\n * to select groups on click and expand only via the arrow. Defaults to true.\n */\n expandOnItemClick?: boolean;\n\n /** Visual density. Defaults to md. */\n size?: TreeViewSize;\n\n /** Indentation between hierarchy levels, in CSS units. Defaults to the theme spacing token. */\n indentation?: string | number;\n\n /** Enables character-based typeahead navigation. Defaults to true. */\n typeahead?: boolean;\n\n /** Text announced and displayed while lazy children are loading. */\n loadingLabel?: string;\n\n /** Text displayed when lazy loading fails. */\n loadErrorLabel?: string;\n\n /** Class applied to each tree item. */\n itemClassName?: string;\n };\n\nconst normalizeIds = (ids: string[] | undefined) =>\n Array.from(\n new Set(\n (ids ?? []).filter((id) => typeof id === \"string\" && id.length > 0),\n ),\n );\n\nconst getTextValue = <TData,>(item: TreeViewItem<TData>) => {\n if (item.textValue !== undefined) return item.textValue;\n\n if (typeof item.label === \"string\" || typeof item.label === \"number\") {\n return String(item.label);\n }\n\n return \"\";\n};\n\nconst normalizeSearchText = (value: string) =>\n value\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLocaleLowerCase();\n\nconst TreeView = <TData,>({\n items,\n selectionMode = \"single\",\n defaultExpandedIds = [],\n expandedIds,\n onExpandedChange,\n defaultSelectedIds = [],\n selectedIds,\n onSelectionChange,\n defaultFocusedId,\n onFocusedChange,\n loadChildren,\n onLoadChildrenError,\n onItemActivate,\n getItemActive,\n renderItem,\n renderIcon,\n renderLabel,\n expandOnItemClick = true,\n typeahead = true,\n size = \"md\",\n indentation,\n loadingLabel = \"Loading…\",\n loadErrorLabel = \"Could not load children. Collapse and expand to retry.\",\n itemClassName,\n className,\n style,\n dir,\n ...rootProps\n}: TreeViewProps<TData>) => {\n const generatedId = useId();\n const isExpandedControlled = expandedIds !== undefined;\n const isSelectionControlled = selectedIds !== undefined;\n\n const [uncontrolledExpandedIds, setUncontrolledExpandedIds] = useState(() =>\n normalizeIds(defaultExpandedIds),\n );\n\n const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(() =>\n normalizeIds(defaultSelectedIds),\n );\n\n const [focusedId, setFocusedId] = useState<string | undefined>(\n () =>\n defaultFocusedId ??\n (selectionMode === \"none\"\n ? undefined\n : normalizeIds(selectedIds ?? defaultSelectedIds)[0]) ??\n items[0]?.id,\n );\n\n const [loadedChildren, setLoadedChildren] = useState<\n Map<string, TreeViewItem<TData>[]>\n >(() => new Map());\n\n const [loadingIds, setLoadingIds] = useState<string[]>([]);\n\n const [loadErrors, setLoadErrors] = useState<Map<string, unknown>>(\n () => new Map(),\n );\n\n const itemRefs = useRef(new Map<string, HTMLLIElement>());\n const inFlightLoads = useRef(new Set<string>());\n const previousExpandedIds = useRef<string[] | null>(null);\n const pendingFocusId = useRef<string | null>(null);\n const focusWithinTree = useRef(false);\n const typeaheadState = useRef({ value: \"\", time: 0 });\n\n const currentExpandedIds = isExpandedControlled\n ? (expandedIds ?? [])\n : uncontrolledExpandedIds;\n\n const requestedSelectedIds = isSelectionControlled\n ? (selectedIds ?? [])\n : uncontrolledSelectedIds;\n\n const currentSelectedIds =\n selectionMode === \"single\"\n ? requestedSelectedIds.slice(0, 1)\n : requestedSelectedIds;\n\n const expandedSet = useMemo(\n () => new Set(normalizeIds(currentExpandedIds)),\n [currentExpandedIds],\n );\n\n const selectedSet = useMemo(\n () => new Set(normalizeIds(currentSelectedIds)),\n [currentSelectedIds],\n );\n\n const loadingSet = useMemo(() => new Set(loadingIds), [loadingIds]);\n\n const getChildren = useCallback(\n (item: TreeViewItem<TData>) =>\n item.children ?? loadedChildren.get(item.id) ?? [],\n [loadedChildren],\n );\n\n const hasChildren = useCallback(\n (item: TreeViewItem<TData>) => {\n if (item.children !== undefined || loadedChildren.has(item.id)) {\n return getChildren(item).length > 0;\n }\n\n return Boolean(item.hasChildren);\n },\n [getChildren, loadedChildren],\n );\n\n const visibleItems = useMemo(() => {\n const result: TreeViewItem<TData>[] = [];\n\n const visit = (nodes: TreeViewItem<TData>[]) => {\n nodes.forEach((item) => {\n result.push(item);\n\n const children = getChildren(item);\n\n if (\n expandedSet.has(item.id) &&\n hasChildren(item) &&\n children.length > 0\n ) {\n visit(children);\n }\n });\n };\n\n visit(items);\n\n return result;\n }, [items, expandedSet, getChildren, hasChildren]);\n\n const parentIds = useMemo(() => {\n const result = new Map<string, string>();\n\n const visit = (nodes: TreeViewItem<TData>[], parentId?: string) => {\n nodes.forEach((item) => {\n if (parentId !== undefined) {\n result.set(item.id, parentId);\n }\n\n visit(getChildren(item), item.id);\n });\n };\n\n visit(items);\n\n return result;\n }, [items, getChildren]);\n\n const visibleIds = useMemo(\n () => new Set(visibleItems.map((item) => item.id)),\n [visibleItems],\n );\n\n let effectiveFocusedId = focusedId;\n\n if (!effectiveFocusedId || !visibleIds.has(effectiveFocusedId)) {\n let ancestor = effectiveFocusedId;\n\n while (ancestor && !visibleIds.has(ancestor)) {\n ancestor = parentIds.get(ancestor);\n }\n\n effectiveFocusedId = ancestor ?? visibleItems[0]?.id;\n }\n\n useEffect(() => {\n if (pendingFocusId.current && visibleIds.has(pendingFocusId.current)) {\n itemRefs.current.get(pendingFocusId.current)?.focus();\n pendingFocusId.current = null;\n }\n }, [visibleItems, visibleIds]);\n\n useEffect(() => {\n if (focusedId !== effectiveFocusedId && effectiveFocusedId) {\n setFocusedId(effectiveFocusedId);\n onFocusedChange?.(effectiveFocusedId);\n\n if (focusWithinTree.current) {\n itemRefs.current.get(effectiveFocusedId)?.focus();\n }\n }\n }, [effectiveFocusedId, focusedId, onFocusedChange]);\n\n const commitExpandedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isExpandedControlled) {\n setUncontrolledExpandedIds(next);\n }\n\n onExpandedChange?.(next);\n };\n\n const commitSelectedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isSelectionControlled) {\n setUncontrolledSelectedIds(next);\n }\n\n onSelectionChange?.(next);\n };\n\n const loadNodeChildren = useCallback(\n async (item: TreeViewItem<TData>) => {\n if (!loadChildren || !item.hasChildren || item.children !== undefined) {\n return;\n }\n\n if (loadedChildren.has(item.id) || inFlightLoads.current.has(item.id)) {\n return;\n }\n\n inFlightLoads.current.add(item.id);\n setLoadingIds((current) => [...current, item.id]);\n\n setLoadErrors((current) => {\n const next = new Map(current);\n next.delete(item.id);\n return next;\n });\n\n try {\n const children = await loadChildren(item);\n\n setLoadedChildren((current) => {\n const next = new Map(current);\n next.set(item.id, children);\n return next;\n });\n } catch (error) {\n setLoadErrors((current) => {\n const next = new Map(current);\n next.set(item.id, error);\n return next;\n });\n\n onLoadChildrenError?.(item, error);\n } finally {\n inFlightLoads.current.delete(item.id);\n setLoadingIds((current) => current.filter((id) => id !== item.id));\n }\n },\n [loadChildren, loadedChildren, onLoadChildrenError],\n );\n\n // Also load branches that were expanded through default or controlled props.\n // Tracking newly expanded IDs avoids retrying a failed request on unrelated renders.\n useEffect(() => {\n const previous = new Set(previousExpandedIds.current ?? []);\n\n const newlyExpandedIds = currentExpandedIds.filter(\n (id) => !previous.has(id),\n );\n\n previousExpandedIds.current = normalizeIds(currentExpandedIds);\n\n newlyExpandedIds.forEach((id) => {\n const item = findItemById(items, id, getChildren);\n if (item) void loadNodeChildren(item);\n });\n }, [currentExpandedIds, items, getChildren, loadNodeChildren]);\n\n const toggleExpanded = (item: TreeViewItem<TData>) => {\n if (!hasChildren(item)) return;\n\n const next = new Set(currentExpandedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n void loadNodeChildren(item);\n }\n\n commitExpandedIds(Array.from(next));\n };\n\n const selectItem = (item: TreeViewItem<TData>, toggleSingle = false) => {\n if (\n selectionMode === \"none\" ||\n item.disabled ||\n item.selectable === false\n ) {\n return;\n }\n\n if (selectionMode === \"single\") {\n if (\n currentSelectedIds.length === 1 &&\n currentSelectedIds[0] === item.id\n ) {\n if (toggleSingle) commitSelectedIds([]);\n return;\n }\n\n commitSelectedIds([item.id]);\n return;\n }\n\n const next = new Set(currentSelectedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n }\n\n commitSelectedIds(Array.from(next));\n };\n\n const focusItem = (id: string) => {\n const element = itemRefs.current.get(id);\n\n if (element) {\n element.focus();\n } else {\n setFocusedId(id);\n onFocusedChange?.(id);\n pendingFocusId.current = id;\n }\n };\n\n const expandSiblings = (itemId: string) => {\n const parentId = parentIds.get(itemId);\n\n const parentItem = parentId\n ? findItemById(items, parentId, getChildren)\n : undefined;\n\n const siblings = parentItem ? getChildren(parentItem) : items;\n const next = new Set(currentExpandedIds);\n let changed = false;\n\n siblings.forEach((sibling) => {\n if (hasChildren(sibling) && !next.has(sibling.id)) {\n next.add(sibling.id);\n changed = true;\n void loadNodeChildren(sibling);\n }\n });\n\n if (changed) {\n commitExpandedIds(Array.from(next));\n }\n };\n\n const handleKeyDown = (\n event: KeyboardEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n parentId?: string,\n ) => {\n const eventTarget = event.target as HTMLElement;\n\n if (eventTarget.closest('[role=\"treeitem\"]') !== event.currentTarget) {\n return;\n }\n\n if (\n eventTarget.closest(\n \"button, a, input, select, textarea, [contenteditable='true']\",\n )\n ) {\n return;\n }\n\n const index = visibleItems.findIndex(\n (visibleItem) => visibleItem.id === item.id,\n );\n\n const inheritedDirection = event.currentTarget\n .closest(\"[dir]\")\n ?.getAttribute(\"dir\");\n\n const isRtl =\n dir === \"rtl\" || (dir === undefined && inheritedDirection === \"rtl\");\n\n const openKey = isRtl ? \"ArrowLeft\" : \"ArrowRight\";\n const closeKey = isRtl ? \"ArrowRight\" : \"ArrowLeft\";\n let handled = false;\n\n switch (event.key) {\n case \"ArrowDown\":\n if (index < visibleItems.length - 1) {\n focusItem(visibleItems[index + 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"ArrowUp\":\n if (index > 0) {\n focusItem(visibleItems[index - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Home\":\n if (visibleItems[0]) {\n focusItem(visibleItems[0].id);\n }\n\n handled = true;\n break;\n\n case \"End\":\n if (visibleItems.length > 0) {\n focusItem(visibleItems[visibleItems.length - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Enter\":\n if (item.href) {\n if (!item.disabled) {\n event.currentTarget\n .querySelector<HTMLAnchorElement>(\"a.puzzle-tree__row\")\n ?.click();\n\n onItemActivate?.(item);\n }\n } else if (hasChildren(item)) {\n toggleExpanded(item);\n if (!item.disabled) onItemActivate?.(item);\n } else {\n selectItem(item);\n if (!item.disabled) onItemActivate?.(item);\n }\n\n handled = true;\n break;\n\n case \" \":\n case \"Spacebar\":\n selectItem(item, true);\n handled = true;\n break;\n\n case \"*\":\n expandSiblings(item.id);\n handled = true;\n break;\n\n default:\n if (\n event.key.length === 1 &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey\n ) {\n if (!typeahead) break;\n\n const currentTime = event.timeStamp;\n const isExpired = currentTime - typeaheadState.current.time > 500;\n const previousValue = isExpired ? \"\" : typeaheadState.current.value;\n const normalizedPrevious = normalizeSearchText(previousValue);\n const normalizedKey = normalizeSearchText(event.key);\n\n const isRepeatedCharacter =\n normalizedPrevious.length > 0 &&\n [...normalizedPrevious].every(\n (character) => character === normalizedKey,\n );\n\n const searchValue = isRepeatedCharacter\n ? event.key\n : `${previousValue}${event.key}`;\n\n typeaheadState.current = {\n value: searchValue,\n time: currentTime,\n };\n\n const normalizedSearch = normalizeSearchText(searchValue);\n\n const orderedItems = [\n ...visibleItems.slice(index + 1),\n ...visibleItems.slice(0, index + 1),\n ];\n\n const match = orderedItems.find((candidate) =>\n normalizeSearchText(getTextValue(candidate)).startsWith(\n normalizedSearch,\n ),\n );\n\n if (match) {\n focusItem(match.id);\n }\n\n handled = true;\n }\n }\n\n if (event.key === openKey) {\n if (hasChildren(item)) {\n if (!expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else {\n const firstChild = getChildren(item)[0];\n if (firstChild) focusItem(firstChild.id);\n }\n }\n\n handled = true;\n } else if (event.key === closeKey) {\n if (hasChildren(item) && expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else if (parentId) {\n focusItem(parentId);\n }\n\n handled = true;\n }\n\n if (handled) {\n event.preventDefault();\n }\n };\n\n const handleItemClick = (\n event: MouseEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as HTMLElement;\n const targetTreeItem = target.closest('[role=\"treeitem\"]');\n\n // Ignore bubbled clicks from descendants and clicks on custom interactive adornments.\n if (targetTreeItem !== event.currentTarget) return;\n\n if (\n target.closest(\n \"button, a, input, select, textarea, [data-tree-stop-selection]\",\n )\n ) {\n return;\n }\n\n focusItem(item.id);\n\n if (\n hasChildren(item) &&\n (target.closest(\"[data-tree-toggle]\") || expandOnItemClick)\n ) {\n toggleExpanded(item);\n return;\n }\n\n selectItem(item);\n };\n\n /**\n * Handles pointer activation of a linked row.\n *\n * The row link is the navigation target, but the tree item remains the\n * roving-focus element. Clicking the expansion affordance is the exception:\n * it expands the branch and deliberately does not navigate.\n */\n const handleItemLinkClick = (\n event: MouseEvent<HTMLAnchorElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as Element;\n\n if (target.closest(\"[data-tree-toggle]\")) {\n event.preventDefault();\n event.stopPropagation();\n\n focusItem(item.id);\n toggleExpanded(item);\n return;\n }\n\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n focusItem(item.id);\n selectItem(item);\n };\n\n const renderItems = (\n nodes: TreeViewItem<TData>[],\n depth = 0,\n parentId?: string,\n ): ReactNode =>\n nodes.map((item, index) => {\n const itemChildren = getChildren(item);\n const itemHasChildren = hasChildren(item);\n const expanded = itemHasChildren && expandedSet.has(item.id);\n const disabled = Boolean(item.disabled);\n const active = Boolean(getItemActive?.(item));\n\n const selected =\n selectionMode !== \"none\" &&\n selectedSet.has(item.id) &&\n item.selectable !== false &&\n !disabled;\n\n const loading = loadingSet.has(item.id);\n const focused = effectiveFocusedId === item.id;\n\n const itemState: TreeViewItemState = {\n expanded,\n selected,\n focused,\n loading,\n disabled,\n hasChildren: itemHasChildren,\n };\n\n const iconContent = renderIcon ? renderIcon(item, itemState) : item.icon;\n const itemId = `${generatedId}-item-${encodeURIComponent(item.id)}`;\n\n const selectable =\n selectionMode !== \"none\" && item.selectable !== false && !disabled;\n\n const hasLoadError = loadErrors.has(item.id);\n\n const rowContent = (\n <>\n {itemHasChildren ? (\n <span\n className=\"puzzle-tree__toggle\"\n data-tree-toggle=\"true\"\n data-expanded={expanded || undefined}\n aria-hidden=\"true\"\n />\n ) : (\n <span className=\"puzzle-tree__toggle-spacer\" aria-hidden=\"true\" />\n )}\n\n {renderItem ? (\n renderItem(item, itemState)\n ) : (\n <>\n {iconContent != null && (\n <span className=\"puzzle-tree__icon\" aria-hidden=\"true\">\n {iconContent}\n </span>\n )}\n\n <span className=\"puzzle-tree__label\">\n {renderLabel ? renderLabel(item, itemState) : item.label}\n </span>\n\n {item.endAdornment != null && (\n <span className=\"puzzle-tree__adornment\">\n {item.endAdornment}\n </span>\n )}\n </>\n )}\n </>\n );\n\n const row =\n item.href !== undefined ? (\n <Link\n href={item.href}\n className=\"puzzle-tree__row\"\n tabIndex={-1}\n aria-current={active ? \"page\" : undefined}\n aria-disabled={disabled || undefined}\n data-tree-row-link=\"true\"\n onClick={(event) => handleItemLinkClick(event, item)}\n >\n {rowContent}\n </Link>\n ) : (\n <div className=\"puzzle-tree__row\">{rowContent}</div>\n );\n\n return (\n <li\n key={item.id}\n ref={(element) => {\n if (element) {\n itemRefs.current.set(item.id, element);\n } else {\n itemRefs.current.delete(item.id);\n }\n }}\n id={itemId}\n role=\"treeitem\"\n aria-level={depth + 1}\n aria-posinset={index + 1}\n aria-setsize={nodes.length}\n aria-expanded={itemHasChildren ? expanded : undefined}\n aria-selected={selectable ? selected : undefined}\n aria-disabled={disabled || undefined}\n aria-busy={loading || undefined}\n tabIndex={focused ? 0 : -1}\n className={cn(\"puzzle-tree__item\", itemClassName)}\n data-tree-item-id={item.id}\n data-active={active || undefined}\n data-selected={selected || undefined}\n data-disabled={disabled || undefined}\n data-expanded={itemHasChildren ? expanded : undefined}\n onFocus={(event) => {\n if (event.target !== event.currentTarget) return;\n\n if (focusedId !== item.id) {\n setFocusedId(item.id);\n onFocusedChange?.(item.id);\n }\n }}\n onClick={(event) => handleItemClick(event, item)}\n onKeyDown={(event) => handleKeyDown(event, item, parentId)}\n >\n {row}\n\n {itemHasChildren && expanded && (\n <ul className=\"puzzle-tree__group\" role=\"group\">\n {loading || hasLoadError ? (\n <li\n role=\"none\"\n className={cn(\n \"puzzle-tree__status-row\",\n hasLoadError &&\n !loading &&\n \"puzzle-tree__status-row--error\",\n )}\n >\n <span role=\"status\" aria-live=\"polite\">\n {loading ? loadingLabel : loadErrorLabel}\n </span>\n </li>\n ) : null}\n\n {renderItems(itemChildren, depth + 1, item.id)}\n </ul>\n )}\n </li>\n );\n });\n\n const rootStyle = {\n ...style,\n ...(indentation !== undefined\n ? {\n \"--puzzle-tree-indentation\":\n typeof indentation === \"number\" ? `${indentation}px` : indentation,\n }\n : {}),\n } as CSSProperties;\n\n return (\n <ul\n {...rootProps}\n role=\"tree\"\n aria-multiselectable={selectionMode === \"multiple\" || undefined}\n className={cn(\"puzzle-tree\", `puzzle-tree--${size}`, className)}\n style={rootStyle}\n dir={dir}\n tabIndex={visibleItems.length === 0 ? 0 : undefined}\n onFocusCapture={() => {\n focusWithinTree.current = true;\n }}\n onBlurCapture={(event) => {\n const nextTarget = event.relatedTarget;\n const target = event.target as HTMLElement;\n\n if (\n event.currentTarget.contains(target) &&\n ((nextTarget && !event.currentTarget.contains(nextTarget as Node)) ||\n (!nextTarget && target.isConnected))\n ) {\n focusWithinTree.current = false;\n }\n }}\n >\n {renderItems(items)}\n </ul>\n );\n};\n\nconst findItemById = <TData,>(\n nodes: TreeViewItem<TData>[],\n id: string,\n getChildren: (item: TreeViewItem<TData>) => TreeViewItem<TData>[],\n): TreeViewItem<TData> | undefined => {\n for (const item of nodes) {\n if (item.id === id) return item;\n\n const found = findItemById(getChildren(item), id, getChildren);\n if (found) return found;\n }\n\n return undefined;\n};\n\nexport {\n TreeView,\n type TreeViewProps,\n type TreeViewItem,\n type TreeViewItemState,\n type TreeViewSelectionMode,\n type TreeViewSize,\n};\n"],"mappings":"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"}
@@ -77,7 +77,11 @@ type TreeViewProps<TData = unknown> = Omit<ComponentPropsWithRef<"ul">, "childre
77
77
  renderIcon?: (item: TreeViewItem<TData>, state: TreeViewItemState) => ReactNode;
78
78
  /** Custom label renderer. Keep its contents non-interactive. */
79
79
  renderLabel?: (item: TreeViewItem<TData>, state: TreeViewItemState) => ReactNode;
80
- /** Clicking the label of a branch toggles it instead of selecting it. Defaults to false. */
80
+ /**
81
+ * Clicking anywhere on a group row (a branch without `href`) toggles it
82
+ * instead of selecting it. Linked branches always navigate. Set to false
83
+ * to select groups on click and expand only via the arrow. Defaults to true.
84
+ */
81
85
  expandOnItemClick?: boolean;
82
86
  /** Visual density. Defaults to md. */
83
87
  size?: TreeViewSize;
@@ -77,7 +77,11 @@ type TreeViewProps<TData = unknown> = Omit<ComponentPropsWithRef<"ul">, "childre
77
77
  renderIcon?: (item: TreeViewItem<TData>, state: TreeViewItemState) => ReactNode;
78
78
  /** Custom label renderer. Keep its contents non-interactive. */
79
79
  renderLabel?: (item: TreeViewItem<TData>, state: TreeViewItemState) => ReactNode;
80
- /** Clicking the label of a branch toggles it instead of selecting it. Defaults to false. */
80
+ /**
81
+ * Clicking anywhere on a group row (a branch without `href`) toggles it
82
+ * instead of selecting it. Linked branches always navigate. Set to false
83
+ * to select groups on click and expand only via the arrow. Defaults to true.
84
+ */
81
85
  expandOnItemClick?: boolean;
82
86
  /** Visual density. Defaults to md. */
83
87
  size?: TreeViewSize;
@@ -12,7 +12,7 @@ const getTextValue = (item) => {
12
12
  return "";
13
13
  };
14
14
  const normalizeSearchText = (value) => value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLocaleLowerCase();
15
- const TreeView = ({ items, selectionMode = "single", defaultExpandedIds = [], expandedIds, onExpandedChange, defaultSelectedIds = [], selectedIds, onSelectionChange, defaultFocusedId, onFocusedChange, loadChildren, onLoadChildrenError, onItemActivate, getItemActive, renderItem, renderIcon, renderLabel, expandOnItemClick = false, typeahead = true, size = "md", indentation, loadingLabel = "Loading…", loadErrorLabel = "Could not load children. Collapse and expand to retry.", itemClassName, className, style, dir, ...rootProps }) => {
15
+ const TreeView = ({ items, selectionMode = "single", defaultExpandedIds = [], expandedIds, onExpandedChange, defaultSelectedIds = [], selectedIds, onSelectionChange, defaultFocusedId, onFocusedChange, loadChildren, onLoadChildrenError, onItemActivate, getItemActive, renderItem, renderIcon, renderLabel, expandOnItemClick = true, typeahead = true, size = "md", indentation, loadingLabel = "Loading…", loadErrorLabel = "Could not load children. Collapse and expand to retry.", itemClassName, className, style, dir, ...rootProps }) => {
16
16
  const generatedId = useId();
17
17
  const isExpandedControlled = expandedIds !== void 0;
18
18
  const isSelectionControlled = selectedIds !== void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/navigation/tree-view/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"@puzzle-ui/core\";\nimport { Link } from \"../link\";\nimport \"./tree-view.css\";\n\ntype TreeViewSelectionMode = \"none\" | \"single\" | \"multiple\";\ntype TreeViewSize = \"sm\" | \"md\";\n\ntype TreeViewItem<TData = unknown> = {\n /** Stable, unique identifier for this node. */\n id: string;\n\n /** Visible node label. Supply textValue when the label is not plain text. */\n label: ReactNode;\n\n /** Optional destination. When supplied, the complete row renders as a Puzzle Link. */\n href?: string;\n\n /** Plain-text value used by typeahead when label is not a string or number. */\n textValue?: string;\n\n /** Optional icon displayed before the label. */\n icon?: ReactNode;\n\n /** Optional, non-interactive content displayed after the label. */\n endAdornment?: ReactNode;\n\n /** Child nodes for a loaded branch. Omit for a leaf or a lazily loaded branch. */\n children?: TreeViewItem<TData>[];\n\n /** Marks a node as expandable before its children have been loaded. */\n hasChildren?: boolean;\n\n /** Prevents selection and activation; branches can still expand so descendants remain accessible. */\n disabled?: boolean;\n\n /** Allows a node to be excluded from selection without disabling expansion. */\n selectable?: boolean;\n\n /** Optional application data associated with the node. */\n data?: TData;\n};\n\ntype TreeViewItemState = {\n expanded: boolean;\n selected: boolean;\n focused: boolean;\n loading: boolean;\n disabled: boolean;\n hasChildren: boolean;\n};\n\ntype TreeViewProps<TData = unknown> = Omit<\n ComponentPropsWithRef<\"ul\">,\n | \"children\"\n | \"onKeyDown\"\n | \"onFocusCapture\"\n | \"onBlurCapture\"\n | \"role\"\n | \"tabIndex\"\n | \"aria-multiselectable\"\n> &\n (\n | { \"aria-label\": string; \"aria-labelledby\"?: string }\n | { \"aria-labelledby\": string; \"aria-label\"?: string }\n ) & {\n /** Hierarchical data rendered by the tree. Node IDs must be unique. */\n items: TreeViewItem<TData>[];\n\n /** Selection behavior. Defaults to single selection. */\n selectionMode?: TreeViewSelectionMode;\n\n /** Initial expanded node IDs for uncontrolled usage. Defaults to []. */\n defaultExpandedIds?: string[];\n\n /** Expanded node IDs for controlled usage. */\n expandedIds?: string[];\n\n /** Called with the next expanded node IDs. */\n onExpandedChange?: (expandedIds: string[]) => void;\n\n /** Initial selected node IDs for uncontrolled usage. Defaults to []. */\n defaultSelectedIds?: string[];\n\n /** Selected node IDs for controlled usage. */\n selectedIds?: string[];\n\n /** Called with the next selected node IDs. */\n onSelectionChange?: (selectedIds: string[]) => void;\n\n /** Initial roving-focus target. Defaults to the first selected node, then the first root node. */\n defaultFocusedId?: string;\n\n /** Called when keyboard or pointer interaction moves focus to another node. */\n onFocusedChange?: (focusedId: string) => void;\n\n /** Loads the children of a node marked with hasChildren when it is first expanded. */\n loadChildren?: (\n item: TreeViewItem<TData>,\n ) => Promise<TreeViewItem<TData>[]>;\n\n /** Called when lazy loading fails. Re-collapse and re-expand the node to retry. */\n onLoadChildrenError?: (item: TreeViewItem<TData>, error: unknown) => void;\n\n /** Called when a node is activated with Enter. */\n onItemActivate?: (item: TreeViewItem<TData>) => void;\n\n /** Determines whether an item matches the current route. */\n getItemActive?: (item: TreeViewItem<TData>) => boolean;\n\n /**\n * Custom node renderer. Replaces the icon, label and adornment of a row; the\n * expand indicator, row element and tree semantics are kept. Keep its contents non-interactive.\n */\n renderItem?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom node-icon renderer. */\n renderIcon?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom label renderer. Keep its contents non-interactive. */\n renderLabel?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Clicking the label of a branch toggles it instead of selecting it. Defaults to false. */\n expandOnItemClick?: boolean;\n\n /** Visual density. Defaults to md. */\n size?: TreeViewSize;\n\n /** Indentation between hierarchy levels, in CSS units. Defaults to the theme spacing token. */\n indentation?: string | number;\n\n /** Enables character-based typeahead navigation. Defaults to true. */\n typeahead?: boolean;\n\n /** Text announced and displayed while lazy children are loading. */\n loadingLabel?: string;\n\n /** Text displayed when lazy loading fails. */\n loadErrorLabel?: string;\n\n /** Class applied to each tree item. */\n itemClassName?: string;\n };\n\nconst normalizeIds = (ids: string[] | undefined) =>\n Array.from(\n new Set(\n (ids ?? []).filter((id) => typeof id === \"string\" && id.length > 0),\n ),\n );\n\nconst getTextValue = <TData,>(item: TreeViewItem<TData>) => {\n if (item.textValue !== undefined) return item.textValue;\n\n if (typeof item.label === \"string\" || typeof item.label === \"number\") {\n return String(item.label);\n }\n\n return \"\";\n};\n\nconst normalizeSearchText = (value: string) =>\n value\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLocaleLowerCase();\n\nconst TreeView = <TData,>({\n items,\n selectionMode = \"single\",\n defaultExpandedIds = [],\n expandedIds,\n onExpandedChange,\n defaultSelectedIds = [],\n selectedIds,\n onSelectionChange,\n defaultFocusedId,\n onFocusedChange,\n loadChildren,\n onLoadChildrenError,\n onItemActivate,\n getItemActive,\n renderItem,\n renderIcon,\n renderLabel,\n expandOnItemClick = false,\n typeahead = true,\n size = \"md\",\n indentation,\n loadingLabel = \"Loading…\",\n loadErrorLabel = \"Could not load children. Collapse and expand to retry.\",\n itemClassName,\n className,\n style,\n dir,\n ...rootProps\n}: TreeViewProps<TData>) => {\n const generatedId = useId();\n const isExpandedControlled = expandedIds !== undefined;\n const isSelectionControlled = selectedIds !== undefined;\n\n const [uncontrolledExpandedIds, setUncontrolledExpandedIds] = useState(() =>\n normalizeIds(defaultExpandedIds),\n );\n\n const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(() =>\n normalizeIds(defaultSelectedIds),\n );\n\n const [focusedId, setFocusedId] = useState<string | undefined>(\n () =>\n defaultFocusedId ??\n (selectionMode === \"none\"\n ? undefined\n : normalizeIds(selectedIds ?? defaultSelectedIds)[0]) ??\n items[0]?.id,\n );\n\n const [loadedChildren, setLoadedChildren] = useState<\n Map<string, TreeViewItem<TData>[]>\n >(() => new Map());\n\n const [loadingIds, setLoadingIds] = useState<string[]>([]);\n\n const [loadErrors, setLoadErrors] = useState<Map<string, unknown>>(\n () => new Map(),\n );\n\n const itemRefs = useRef(new Map<string, HTMLLIElement>());\n const inFlightLoads = useRef(new Set<string>());\n const previousExpandedIds = useRef<string[] | null>(null);\n const pendingFocusId = useRef<string | null>(null);\n const focusWithinTree = useRef(false);\n const typeaheadState = useRef({ value: \"\", time: 0 });\n\n const currentExpandedIds = isExpandedControlled\n ? (expandedIds ?? [])\n : uncontrolledExpandedIds;\n\n const requestedSelectedIds = isSelectionControlled\n ? (selectedIds ?? [])\n : uncontrolledSelectedIds;\n\n const currentSelectedIds =\n selectionMode === \"single\"\n ? requestedSelectedIds.slice(0, 1)\n : requestedSelectedIds;\n\n const expandedSet = useMemo(\n () => new Set(normalizeIds(currentExpandedIds)),\n [currentExpandedIds],\n );\n\n const selectedSet = useMemo(\n () => new Set(normalizeIds(currentSelectedIds)),\n [currentSelectedIds],\n );\n\n const loadingSet = useMemo(() => new Set(loadingIds), [loadingIds]);\n\n const getChildren = useCallback(\n (item: TreeViewItem<TData>) =>\n item.children ?? loadedChildren.get(item.id) ?? [],\n [loadedChildren],\n );\n\n const hasChildren = useCallback(\n (item: TreeViewItem<TData>) => {\n if (item.children !== undefined || loadedChildren.has(item.id)) {\n return getChildren(item).length > 0;\n }\n\n return Boolean(item.hasChildren);\n },\n [getChildren, loadedChildren],\n );\n\n const visibleItems = useMemo(() => {\n const result: TreeViewItem<TData>[] = [];\n\n const visit = (nodes: TreeViewItem<TData>[]) => {\n nodes.forEach((item) => {\n result.push(item);\n\n const children = getChildren(item);\n\n if (\n expandedSet.has(item.id) &&\n hasChildren(item) &&\n children.length > 0\n ) {\n visit(children);\n }\n });\n };\n\n visit(items);\n\n return result;\n }, [items, expandedSet, getChildren, hasChildren]);\n\n const parentIds = useMemo(() => {\n const result = new Map<string, string>();\n\n const visit = (nodes: TreeViewItem<TData>[], parentId?: string) => {\n nodes.forEach((item) => {\n if (parentId !== undefined) {\n result.set(item.id, parentId);\n }\n\n visit(getChildren(item), item.id);\n });\n };\n\n visit(items);\n\n return result;\n }, [items, getChildren]);\n\n const visibleIds = useMemo(\n () => new Set(visibleItems.map((item) => item.id)),\n [visibleItems],\n );\n\n let effectiveFocusedId = focusedId;\n\n if (!effectiveFocusedId || !visibleIds.has(effectiveFocusedId)) {\n let ancestor = effectiveFocusedId;\n\n while (ancestor && !visibleIds.has(ancestor)) {\n ancestor = parentIds.get(ancestor);\n }\n\n effectiveFocusedId = ancestor ?? visibleItems[0]?.id;\n }\n\n useEffect(() => {\n if (pendingFocusId.current && visibleIds.has(pendingFocusId.current)) {\n itemRefs.current.get(pendingFocusId.current)?.focus();\n pendingFocusId.current = null;\n }\n }, [visibleItems, visibleIds]);\n\n useEffect(() => {\n if (focusedId !== effectiveFocusedId && effectiveFocusedId) {\n setFocusedId(effectiveFocusedId);\n onFocusedChange?.(effectiveFocusedId);\n\n if (focusWithinTree.current) {\n itemRefs.current.get(effectiveFocusedId)?.focus();\n }\n }\n }, [effectiveFocusedId, focusedId, onFocusedChange]);\n\n const commitExpandedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isExpandedControlled) {\n setUncontrolledExpandedIds(next);\n }\n\n onExpandedChange?.(next);\n };\n\n const commitSelectedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isSelectionControlled) {\n setUncontrolledSelectedIds(next);\n }\n\n onSelectionChange?.(next);\n };\n\n const loadNodeChildren = useCallback(\n async (item: TreeViewItem<TData>) => {\n if (!loadChildren || !item.hasChildren || item.children !== undefined) {\n return;\n }\n\n if (loadedChildren.has(item.id) || inFlightLoads.current.has(item.id)) {\n return;\n }\n\n inFlightLoads.current.add(item.id);\n setLoadingIds((current) => [...current, item.id]);\n\n setLoadErrors((current) => {\n const next = new Map(current);\n next.delete(item.id);\n return next;\n });\n\n try {\n const children = await loadChildren(item);\n\n setLoadedChildren((current) => {\n const next = new Map(current);\n next.set(item.id, children);\n return next;\n });\n } catch (error) {\n setLoadErrors((current) => {\n const next = new Map(current);\n next.set(item.id, error);\n return next;\n });\n\n onLoadChildrenError?.(item, error);\n } finally {\n inFlightLoads.current.delete(item.id);\n setLoadingIds((current) => current.filter((id) => id !== item.id));\n }\n },\n [loadChildren, loadedChildren, onLoadChildrenError],\n );\n\n // Also load branches that were expanded through default or controlled props.\n // Tracking newly expanded IDs avoids retrying a failed request on unrelated renders.\n useEffect(() => {\n const previous = new Set(previousExpandedIds.current ?? []);\n\n const newlyExpandedIds = currentExpandedIds.filter(\n (id) => !previous.has(id),\n );\n\n previousExpandedIds.current = normalizeIds(currentExpandedIds);\n\n newlyExpandedIds.forEach((id) => {\n const item = findItemById(items, id, getChildren);\n if (item) void loadNodeChildren(item);\n });\n }, [currentExpandedIds, items, getChildren, loadNodeChildren]);\n\n const toggleExpanded = (item: TreeViewItem<TData>) => {\n if (!hasChildren(item)) return;\n\n const next = new Set(currentExpandedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n void loadNodeChildren(item);\n }\n\n commitExpandedIds(Array.from(next));\n };\n\n const selectItem = (item: TreeViewItem<TData>, toggleSingle = false) => {\n if (\n selectionMode === \"none\" ||\n item.disabled ||\n item.selectable === false\n ) {\n return;\n }\n\n if (selectionMode === \"single\") {\n if (\n currentSelectedIds.length === 1 &&\n currentSelectedIds[0] === item.id\n ) {\n if (toggleSingle) commitSelectedIds([]);\n return;\n }\n\n commitSelectedIds([item.id]);\n return;\n }\n\n const next = new Set(currentSelectedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n }\n\n commitSelectedIds(Array.from(next));\n };\n\n const focusItem = (id: string) => {\n const element = itemRefs.current.get(id);\n\n if (element) {\n element.focus();\n } else {\n setFocusedId(id);\n onFocusedChange?.(id);\n pendingFocusId.current = id;\n }\n };\n\n const expandSiblings = (itemId: string) => {\n const parentId = parentIds.get(itemId);\n\n const parentItem = parentId\n ? findItemById(items, parentId, getChildren)\n : undefined;\n\n const siblings = parentItem ? getChildren(parentItem) : items;\n const next = new Set(currentExpandedIds);\n let changed = false;\n\n siblings.forEach((sibling) => {\n if (hasChildren(sibling) && !next.has(sibling.id)) {\n next.add(sibling.id);\n changed = true;\n void loadNodeChildren(sibling);\n }\n });\n\n if (changed) {\n commitExpandedIds(Array.from(next));\n }\n };\n\n const handleKeyDown = (\n event: KeyboardEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n parentId?: string,\n ) => {\n const eventTarget = event.target as HTMLElement;\n\n if (eventTarget.closest('[role=\"treeitem\"]') !== event.currentTarget) {\n return;\n }\n\n if (\n eventTarget.closest(\n \"button, a, input, select, textarea, [contenteditable='true']\",\n )\n ) {\n return;\n }\n\n const index = visibleItems.findIndex(\n (visibleItem) => visibleItem.id === item.id,\n );\n\n const inheritedDirection = event.currentTarget\n .closest(\"[dir]\")\n ?.getAttribute(\"dir\");\n\n const isRtl =\n dir === \"rtl\" || (dir === undefined && inheritedDirection === \"rtl\");\n\n const openKey = isRtl ? \"ArrowLeft\" : \"ArrowRight\";\n const closeKey = isRtl ? \"ArrowRight\" : \"ArrowLeft\";\n let handled = false;\n\n switch (event.key) {\n case \"ArrowDown\":\n if (index < visibleItems.length - 1) {\n focusItem(visibleItems[index + 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"ArrowUp\":\n if (index > 0) {\n focusItem(visibleItems[index - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Home\":\n if (visibleItems[0]) {\n focusItem(visibleItems[0].id);\n }\n\n handled = true;\n break;\n\n case \"End\":\n if (visibleItems.length > 0) {\n focusItem(visibleItems[visibleItems.length - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Enter\":\n if (item.href) {\n if (!item.disabled) {\n event.currentTarget\n .querySelector<HTMLAnchorElement>(\"a.puzzle-tree__row\")\n ?.click();\n\n onItemActivate?.(item);\n }\n } else if (hasChildren(item)) {\n toggleExpanded(item);\n if (!item.disabled) onItemActivate?.(item);\n } else {\n selectItem(item);\n if (!item.disabled) onItemActivate?.(item);\n }\n\n handled = true;\n break;\n\n case \" \":\n case \"Spacebar\":\n selectItem(item, true);\n handled = true;\n break;\n\n case \"*\":\n expandSiblings(item.id);\n handled = true;\n break;\n\n default:\n if (\n event.key.length === 1 &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey\n ) {\n if (!typeahead) break;\n\n const currentTime = event.timeStamp;\n const isExpired = currentTime - typeaheadState.current.time > 500;\n const previousValue = isExpired ? \"\" : typeaheadState.current.value;\n const normalizedPrevious = normalizeSearchText(previousValue);\n const normalizedKey = normalizeSearchText(event.key);\n\n const isRepeatedCharacter =\n normalizedPrevious.length > 0 &&\n [...normalizedPrevious].every(\n (character) => character === normalizedKey,\n );\n\n const searchValue = isRepeatedCharacter\n ? event.key\n : `${previousValue}${event.key}`;\n\n typeaheadState.current = {\n value: searchValue,\n time: currentTime,\n };\n\n const normalizedSearch = normalizeSearchText(searchValue);\n\n const orderedItems = [\n ...visibleItems.slice(index + 1),\n ...visibleItems.slice(0, index + 1),\n ];\n\n const match = orderedItems.find((candidate) =>\n normalizeSearchText(getTextValue(candidate)).startsWith(\n normalizedSearch,\n ),\n );\n\n if (match) {\n focusItem(match.id);\n }\n\n handled = true;\n }\n }\n\n if (event.key === openKey) {\n if (hasChildren(item)) {\n if (!expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else {\n const firstChild = getChildren(item)[0];\n if (firstChild) focusItem(firstChild.id);\n }\n }\n\n handled = true;\n } else if (event.key === closeKey) {\n if (hasChildren(item) && expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else if (parentId) {\n focusItem(parentId);\n }\n\n handled = true;\n }\n\n if (handled) {\n event.preventDefault();\n }\n };\n\n const handleItemClick = (\n event: MouseEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as HTMLElement;\n const targetTreeItem = target.closest('[role=\"treeitem\"]');\n\n // Ignore bubbled clicks from descendants and clicks on custom interactive adornments.\n if (targetTreeItem !== event.currentTarget) return;\n\n if (\n target.closest(\n \"button, a, input, select, textarea, [data-tree-stop-selection]\",\n )\n ) {\n return;\n }\n\n focusItem(item.id);\n\n if (\n hasChildren(item) &&\n (target.closest(\"[data-tree-toggle]\") || expandOnItemClick)\n ) {\n toggleExpanded(item);\n return;\n }\n\n selectItem(item);\n };\n\n /**\n * Handles pointer activation of a linked row.\n *\n * The row link is the navigation target, but the tree item remains the\n * roving-focus element. Clicking the expansion affordance is the exception:\n * it expands the branch and deliberately does not navigate.\n */\n const handleItemLinkClick = (\n event: MouseEvent<HTMLAnchorElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as Element;\n\n if (target.closest(\"[data-tree-toggle]\")) {\n event.preventDefault();\n event.stopPropagation();\n\n focusItem(item.id);\n toggleExpanded(item);\n return;\n }\n\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n focusItem(item.id);\n selectItem(item);\n };\n\n const renderItems = (\n nodes: TreeViewItem<TData>[],\n depth = 0,\n parentId?: string,\n ): ReactNode =>\n nodes.map((item, index) => {\n const itemChildren = getChildren(item);\n const itemHasChildren = hasChildren(item);\n const expanded = itemHasChildren && expandedSet.has(item.id);\n const disabled = Boolean(item.disabled);\n const active = Boolean(getItemActive?.(item));\n\n const selected =\n selectionMode !== \"none\" &&\n selectedSet.has(item.id) &&\n item.selectable !== false &&\n !disabled;\n\n const loading = loadingSet.has(item.id);\n const focused = effectiveFocusedId === item.id;\n\n const itemState: TreeViewItemState = {\n expanded,\n selected,\n focused,\n loading,\n disabled,\n hasChildren: itemHasChildren,\n };\n\n const iconContent = renderIcon ? renderIcon(item, itemState) : item.icon;\n const itemId = `${generatedId}-item-${encodeURIComponent(item.id)}`;\n\n const selectable =\n selectionMode !== \"none\" && item.selectable !== false && !disabled;\n\n const hasLoadError = loadErrors.has(item.id);\n\n const rowContent = (\n <>\n {itemHasChildren ? (\n <span\n className=\"puzzle-tree__toggle\"\n data-tree-toggle=\"true\"\n data-expanded={expanded || undefined}\n aria-hidden=\"true\"\n />\n ) : (\n <span className=\"puzzle-tree__toggle-spacer\" aria-hidden=\"true\" />\n )}\n\n {renderItem ? (\n renderItem(item, itemState)\n ) : (\n <>\n {iconContent != null && (\n <span className=\"puzzle-tree__icon\" aria-hidden=\"true\">\n {iconContent}\n </span>\n )}\n\n <span className=\"puzzle-tree__label\">\n {renderLabel ? renderLabel(item, itemState) : item.label}\n </span>\n\n {item.endAdornment != null && (\n <span className=\"puzzle-tree__adornment\">\n {item.endAdornment}\n </span>\n )}\n </>\n )}\n </>\n );\n\n const row =\n item.href !== undefined ? (\n <Link\n href={item.href}\n className=\"puzzle-tree__row\"\n tabIndex={-1}\n aria-current={active ? \"page\" : undefined}\n aria-disabled={disabled || undefined}\n data-tree-row-link=\"true\"\n onClick={(event) => handleItemLinkClick(event, item)}\n >\n {rowContent}\n </Link>\n ) : (\n <div className=\"puzzle-tree__row\">{rowContent}</div>\n );\n\n return (\n <li\n key={item.id}\n ref={(element) => {\n if (element) {\n itemRefs.current.set(item.id, element);\n } else {\n itemRefs.current.delete(item.id);\n }\n }}\n id={itemId}\n role=\"treeitem\"\n aria-level={depth + 1}\n aria-posinset={index + 1}\n aria-setsize={nodes.length}\n aria-expanded={itemHasChildren ? expanded : undefined}\n aria-selected={selectable ? selected : undefined}\n aria-disabled={disabled || undefined}\n aria-busy={loading || undefined}\n tabIndex={focused ? 0 : -1}\n className={cn(\"puzzle-tree__item\", itemClassName)}\n data-tree-item-id={item.id}\n data-active={active || undefined}\n data-selected={selected || undefined}\n data-disabled={disabled || undefined}\n data-expanded={itemHasChildren ? expanded : undefined}\n onFocus={(event) => {\n if (event.target !== event.currentTarget) return;\n\n if (focusedId !== item.id) {\n setFocusedId(item.id);\n onFocusedChange?.(item.id);\n }\n }}\n onClick={(event) => handleItemClick(event, item)}\n onKeyDown={(event) => handleKeyDown(event, item, parentId)}\n >\n {row}\n\n {itemHasChildren && expanded && (\n <ul className=\"puzzle-tree__group\" role=\"group\">\n {loading || hasLoadError ? (\n <li\n role=\"none\"\n className={cn(\n \"puzzle-tree__status-row\",\n hasLoadError &&\n !loading &&\n \"puzzle-tree__status-row--error\",\n )}\n >\n <span role=\"status\" aria-live=\"polite\">\n {loading ? loadingLabel : loadErrorLabel}\n </span>\n </li>\n ) : null}\n\n {renderItems(itemChildren, depth + 1, item.id)}\n </ul>\n )}\n </li>\n );\n });\n\n const rootStyle = {\n ...style,\n ...(indentation !== undefined\n ? {\n \"--puzzle-tree-indentation\":\n typeof indentation === \"number\" ? `${indentation}px` : indentation,\n }\n : {}),\n } as CSSProperties;\n\n return (\n <ul\n {...rootProps}\n role=\"tree\"\n aria-multiselectable={selectionMode === \"multiple\" || undefined}\n className={cn(\"puzzle-tree\", `puzzle-tree--${size}`, className)}\n style={rootStyle}\n dir={dir}\n tabIndex={visibleItems.length === 0 ? 0 : undefined}\n onFocusCapture={() => {\n focusWithinTree.current = true;\n }}\n onBlurCapture={(event) => {\n const nextTarget = event.relatedTarget;\n const target = event.target as HTMLElement;\n\n if (\n event.currentTarget.contains(target) &&\n ((nextTarget && !event.currentTarget.contains(nextTarget as Node)) ||\n (!nextTarget && target.isConnected))\n ) {\n focusWithinTree.current = false;\n }\n }}\n >\n {renderItems(items)}\n </ul>\n );\n};\n\nconst findItemById = <TData,>(\n nodes: TreeViewItem<TData>[],\n id: string,\n getChildren: (item: TreeViewItem<TData>) => TreeViewItem<TData>[],\n): TreeViewItem<TData> | undefined => {\n for (const item of nodes) {\n if (item.id === id) return item;\n\n const found = findItemById(getChildren(item), id, getChildren);\n if (found) return found;\n }\n\n return undefined;\n};\n\nexport {\n TreeView,\n type TreeViewProps,\n type TreeViewItem,\n type TreeViewItemState,\n type TreeViewSelectionMode,\n type TreeViewSize,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/navigation/tree-view/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type CSSProperties,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport { cn } from \"@puzzle-ui/core\";\nimport { Link } from \"../link\";\nimport \"./tree-view.css\";\n\ntype TreeViewSelectionMode = \"none\" | \"single\" | \"multiple\";\ntype TreeViewSize = \"sm\" | \"md\";\n\ntype TreeViewItem<TData = unknown> = {\n /** Stable, unique identifier for this node. */\n id: string;\n\n /** Visible node label. Supply textValue when the label is not plain text. */\n label: ReactNode;\n\n /** Optional destination. When supplied, the complete row renders as a Puzzle Link. */\n href?: string;\n\n /** Plain-text value used by typeahead when label is not a string or number. */\n textValue?: string;\n\n /** Optional icon displayed before the label. */\n icon?: ReactNode;\n\n /** Optional, non-interactive content displayed after the label. */\n endAdornment?: ReactNode;\n\n /** Child nodes for a loaded branch. Omit for a leaf or a lazily loaded branch. */\n children?: TreeViewItem<TData>[];\n\n /** Marks a node as expandable before its children have been loaded. */\n hasChildren?: boolean;\n\n /** Prevents selection and activation; branches can still expand so descendants remain accessible. */\n disabled?: boolean;\n\n /** Allows a node to be excluded from selection without disabling expansion. */\n selectable?: boolean;\n\n /** Optional application data associated with the node. */\n data?: TData;\n};\n\ntype TreeViewItemState = {\n expanded: boolean;\n selected: boolean;\n focused: boolean;\n loading: boolean;\n disabled: boolean;\n hasChildren: boolean;\n};\n\ntype TreeViewProps<TData = unknown> = Omit<\n ComponentPropsWithRef<\"ul\">,\n | \"children\"\n | \"onKeyDown\"\n | \"onFocusCapture\"\n | \"onBlurCapture\"\n | \"role\"\n | \"tabIndex\"\n | \"aria-multiselectable\"\n> &\n (\n | { \"aria-label\": string; \"aria-labelledby\"?: string }\n | { \"aria-labelledby\": string; \"aria-label\"?: string }\n ) & {\n /** Hierarchical data rendered by the tree. Node IDs must be unique. */\n items: TreeViewItem<TData>[];\n\n /** Selection behavior. Defaults to single selection. */\n selectionMode?: TreeViewSelectionMode;\n\n /** Initial expanded node IDs for uncontrolled usage. Defaults to []. */\n defaultExpandedIds?: string[];\n\n /** Expanded node IDs for controlled usage. */\n expandedIds?: string[];\n\n /** Called with the next expanded node IDs. */\n onExpandedChange?: (expandedIds: string[]) => void;\n\n /** Initial selected node IDs for uncontrolled usage. Defaults to []. */\n defaultSelectedIds?: string[];\n\n /** Selected node IDs for controlled usage. */\n selectedIds?: string[];\n\n /** Called with the next selected node IDs. */\n onSelectionChange?: (selectedIds: string[]) => void;\n\n /** Initial roving-focus target. Defaults to the first selected node, then the first root node. */\n defaultFocusedId?: string;\n\n /** Called when keyboard or pointer interaction moves focus to another node. */\n onFocusedChange?: (focusedId: string) => void;\n\n /** Loads the children of a node marked with hasChildren when it is first expanded. */\n loadChildren?: (\n item: TreeViewItem<TData>,\n ) => Promise<TreeViewItem<TData>[]>;\n\n /** Called when lazy loading fails. Re-collapse and re-expand the node to retry. */\n onLoadChildrenError?: (item: TreeViewItem<TData>, error: unknown) => void;\n\n /** Called when a node is activated with Enter. */\n onItemActivate?: (item: TreeViewItem<TData>) => void;\n\n /** Determines whether an item matches the current route. */\n getItemActive?: (item: TreeViewItem<TData>) => boolean;\n\n /**\n * Custom node renderer. Replaces the icon, label and adornment of a row; the\n * expand indicator, row element and tree semantics are kept. Keep its contents non-interactive.\n */\n renderItem?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom node-icon renderer. */\n renderIcon?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /** Custom label renderer. Keep its contents non-interactive. */\n renderLabel?: (\n item: TreeViewItem<TData>,\n state: TreeViewItemState,\n ) => ReactNode;\n\n /**\n * Clicking anywhere on a group row (a branch without `href`) toggles it\n * instead of selecting it. Linked branches always navigate. Set to false\n * to select groups on click and expand only via the arrow. Defaults to true.\n */\n expandOnItemClick?: boolean;\n\n /** Visual density. Defaults to md. */\n size?: TreeViewSize;\n\n /** Indentation between hierarchy levels, in CSS units. Defaults to the theme spacing token. */\n indentation?: string | number;\n\n /** Enables character-based typeahead navigation. Defaults to true. */\n typeahead?: boolean;\n\n /** Text announced and displayed while lazy children are loading. */\n loadingLabel?: string;\n\n /** Text displayed when lazy loading fails. */\n loadErrorLabel?: string;\n\n /** Class applied to each tree item. */\n itemClassName?: string;\n };\n\nconst normalizeIds = (ids: string[] | undefined) =>\n Array.from(\n new Set(\n (ids ?? []).filter((id) => typeof id === \"string\" && id.length > 0),\n ),\n );\n\nconst getTextValue = <TData,>(item: TreeViewItem<TData>) => {\n if (item.textValue !== undefined) return item.textValue;\n\n if (typeof item.label === \"string\" || typeof item.label === \"number\") {\n return String(item.label);\n }\n\n return \"\";\n};\n\nconst normalizeSearchText = (value: string) =>\n value\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLocaleLowerCase();\n\nconst TreeView = <TData,>({\n items,\n selectionMode = \"single\",\n defaultExpandedIds = [],\n expandedIds,\n onExpandedChange,\n defaultSelectedIds = [],\n selectedIds,\n onSelectionChange,\n defaultFocusedId,\n onFocusedChange,\n loadChildren,\n onLoadChildrenError,\n onItemActivate,\n getItemActive,\n renderItem,\n renderIcon,\n renderLabel,\n expandOnItemClick = true,\n typeahead = true,\n size = \"md\",\n indentation,\n loadingLabel = \"Loading…\",\n loadErrorLabel = \"Could not load children. Collapse and expand to retry.\",\n itemClassName,\n className,\n style,\n dir,\n ...rootProps\n}: TreeViewProps<TData>) => {\n const generatedId = useId();\n const isExpandedControlled = expandedIds !== undefined;\n const isSelectionControlled = selectedIds !== undefined;\n\n const [uncontrolledExpandedIds, setUncontrolledExpandedIds] = useState(() =>\n normalizeIds(defaultExpandedIds),\n );\n\n const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(() =>\n normalizeIds(defaultSelectedIds),\n );\n\n const [focusedId, setFocusedId] = useState<string | undefined>(\n () =>\n defaultFocusedId ??\n (selectionMode === \"none\"\n ? undefined\n : normalizeIds(selectedIds ?? defaultSelectedIds)[0]) ??\n items[0]?.id,\n );\n\n const [loadedChildren, setLoadedChildren] = useState<\n Map<string, TreeViewItem<TData>[]>\n >(() => new Map());\n\n const [loadingIds, setLoadingIds] = useState<string[]>([]);\n\n const [loadErrors, setLoadErrors] = useState<Map<string, unknown>>(\n () => new Map(),\n );\n\n const itemRefs = useRef(new Map<string, HTMLLIElement>());\n const inFlightLoads = useRef(new Set<string>());\n const previousExpandedIds = useRef<string[] | null>(null);\n const pendingFocusId = useRef<string | null>(null);\n const focusWithinTree = useRef(false);\n const typeaheadState = useRef({ value: \"\", time: 0 });\n\n const currentExpandedIds = isExpandedControlled\n ? (expandedIds ?? [])\n : uncontrolledExpandedIds;\n\n const requestedSelectedIds = isSelectionControlled\n ? (selectedIds ?? [])\n : uncontrolledSelectedIds;\n\n const currentSelectedIds =\n selectionMode === \"single\"\n ? requestedSelectedIds.slice(0, 1)\n : requestedSelectedIds;\n\n const expandedSet = useMemo(\n () => new Set(normalizeIds(currentExpandedIds)),\n [currentExpandedIds],\n );\n\n const selectedSet = useMemo(\n () => new Set(normalizeIds(currentSelectedIds)),\n [currentSelectedIds],\n );\n\n const loadingSet = useMemo(() => new Set(loadingIds), [loadingIds]);\n\n const getChildren = useCallback(\n (item: TreeViewItem<TData>) =>\n item.children ?? loadedChildren.get(item.id) ?? [],\n [loadedChildren],\n );\n\n const hasChildren = useCallback(\n (item: TreeViewItem<TData>) => {\n if (item.children !== undefined || loadedChildren.has(item.id)) {\n return getChildren(item).length > 0;\n }\n\n return Boolean(item.hasChildren);\n },\n [getChildren, loadedChildren],\n );\n\n const visibleItems = useMemo(() => {\n const result: TreeViewItem<TData>[] = [];\n\n const visit = (nodes: TreeViewItem<TData>[]) => {\n nodes.forEach((item) => {\n result.push(item);\n\n const children = getChildren(item);\n\n if (\n expandedSet.has(item.id) &&\n hasChildren(item) &&\n children.length > 0\n ) {\n visit(children);\n }\n });\n };\n\n visit(items);\n\n return result;\n }, [items, expandedSet, getChildren, hasChildren]);\n\n const parentIds = useMemo(() => {\n const result = new Map<string, string>();\n\n const visit = (nodes: TreeViewItem<TData>[], parentId?: string) => {\n nodes.forEach((item) => {\n if (parentId !== undefined) {\n result.set(item.id, parentId);\n }\n\n visit(getChildren(item), item.id);\n });\n };\n\n visit(items);\n\n return result;\n }, [items, getChildren]);\n\n const visibleIds = useMemo(\n () => new Set(visibleItems.map((item) => item.id)),\n [visibleItems],\n );\n\n let effectiveFocusedId = focusedId;\n\n if (!effectiveFocusedId || !visibleIds.has(effectiveFocusedId)) {\n let ancestor = effectiveFocusedId;\n\n while (ancestor && !visibleIds.has(ancestor)) {\n ancestor = parentIds.get(ancestor);\n }\n\n effectiveFocusedId = ancestor ?? visibleItems[0]?.id;\n }\n\n useEffect(() => {\n if (pendingFocusId.current && visibleIds.has(pendingFocusId.current)) {\n itemRefs.current.get(pendingFocusId.current)?.focus();\n pendingFocusId.current = null;\n }\n }, [visibleItems, visibleIds]);\n\n useEffect(() => {\n if (focusedId !== effectiveFocusedId && effectiveFocusedId) {\n setFocusedId(effectiveFocusedId);\n onFocusedChange?.(effectiveFocusedId);\n\n if (focusWithinTree.current) {\n itemRefs.current.get(effectiveFocusedId)?.focus();\n }\n }\n }, [effectiveFocusedId, focusedId, onFocusedChange]);\n\n const commitExpandedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isExpandedControlled) {\n setUncontrolledExpandedIds(next);\n }\n\n onExpandedChange?.(next);\n };\n\n const commitSelectedIds = (nextIds: string[]) => {\n const next = normalizeIds(nextIds);\n\n if (!isSelectionControlled) {\n setUncontrolledSelectedIds(next);\n }\n\n onSelectionChange?.(next);\n };\n\n const loadNodeChildren = useCallback(\n async (item: TreeViewItem<TData>) => {\n if (!loadChildren || !item.hasChildren || item.children !== undefined) {\n return;\n }\n\n if (loadedChildren.has(item.id) || inFlightLoads.current.has(item.id)) {\n return;\n }\n\n inFlightLoads.current.add(item.id);\n setLoadingIds((current) => [...current, item.id]);\n\n setLoadErrors((current) => {\n const next = new Map(current);\n next.delete(item.id);\n return next;\n });\n\n try {\n const children = await loadChildren(item);\n\n setLoadedChildren((current) => {\n const next = new Map(current);\n next.set(item.id, children);\n return next;\n });\n } catch (error) {\n setLoadErrors((current) => {\n const next = new Map(current);\n next.set(item.id, error);\n return next;\n });\n\n onLoadChildrenError?.(item, error);\n } finally {\n inFlightLoads.current.delete(item.id);\n setLoadingIds((current) => current.filter((id) => id !== item.id));\n }\n },\n [loadChildren, loadedChildren, onLoadChildrenError],\n );\n\n // Also load branches that were expanded through default or controlled props.\n // Tracking newly expanded IDs avoids retrying a failed request on unrelated renders.\n useEffect(() => {\n const previous = new Set(previousExpandedIds.current ?? []);\n\n const newlyExpandedIds = currentExpandedIds.filter(\n (id) => !previous.has(id),\n );\n\n previousExpandedIds.current = normalizeIds(currentExpandedIds);\n\n newlyExpandedIds.forEach((id) => {\n const item = findItemById(items, id, getChildren);\n if (item) void loadNodeChildren(item);\n });\n }, [currentExpandedIds, items, getChildren, loadNodeChildren]);\n\n const toggleExpanded = (item: TreeViewItem<TData>) => {\n if (!hasChildren(item)) return;\n\n const next = new Set(currentExpandedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n void loadNodeChildren(item);\n }\n\n commitExpandedIds(Array.from(next));\n };\n\n const selectItem = (item: TreeViewItem<TData>, toggleSingle = false) => {\n if (\n selectionMode === \"none\" ||\n item.disabled ||\n item.selectable === false\n ) {\n return;\n }\n\n if (selectionMode === \"single\") {\n if (\n currentSelectedIds.length === 1 &&\n currentSelectedIds[0] === item.id\n ) {\n if (toggleSingle) commitSelectedIds([]);\n return;\n }\n\n commitSelectedIds([item.id]);\n return;\n }\n\n const next = new Set(currentSelectedIds);\n\n if (next.has(item.id)) {\n next.delete(item.id);\n } else {\n next.add(item.id);\n }\n\n commitSelectedIds(Array.from(next));\n };\n\n const focusItem = (id: string) => {\n const element = itemRefs.current.get(id);\n\n if (element) {\n element.focus();\n } else {\n setFocusedId(id);\n onFocusedChange?.(id);\n pendingFocusId.current = id;\n }\n };\n\n const expandSiblings = (itemId: string) => {\n const parentId = parentIds.get(itemId);\n\n const parentItem = parentId\n ? findItemById(items, parentId, getChildren)\n : undefined;\n\n const siblings = parentItem ? getChildren(parentItem) : items;\n const next = new Set(currentExpandedIds);\n let changed = false;\n\n siblings.forEach((sibling) => {\n if (hasChildren(sibling) && !next.has(sibling.id)) {\n next.add(sibling.id);\n changed = true;\n void loadNodeChildren(sibling);\n }\n });\n\n if (changed) {\n commitExpandedIds(Array.from(next));\n }\n };\n\n const handleKeyDown = (\n event: KeyboardEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n parentId?: string,\n ) => {\n const eventTarget = event.target as HTMLElement;\n\n if (eventTarget.closest('[role=\"treeitem\"]') !== event.currentTarget) {\n return;\n }\n\n if (\n eventTarget.closest(\n \"button, a, input, select, textarea, [contenteditable='true']\",\n )\n ) {\n return;\n }\n\n const index = visibleItems.findIndex(\n (visibleItem) => visibleItem.id === item.id,\n );\n\n const inheritedDirection = event.currentTarget\n .closest(\"[dir]\")\n ?.getAttribute(\"dir\");\n\n const isRtl =\n dir === \"rtl\" || (dir === undefined && inheritedDirection === \"rtl\");\n\n const openKey = isRtl ? \"ArrowLeft\" : \"ArrowRight\";\n const closeKey = isRtl ? \"ArrowRight\" : \"ArrowLeft\";\n let handled = false;\n\n switch (event.key) {\n case \"ArrowDown\":\n if (index < visibleItems.length - 1) {\n focusItem(visibleItems[index + 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"ArrowUp\":\n if (index > 0) {\n focusItem(visibleItems[index - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Home\":\n if (visibleItems[0]) {\n focusItem(visibleItems[0].id);\n }\n\n handled = true;\n break;\n\n case \"End\":\n if (visibleItems.length > 0) {\n focusItem(visibleItems[visibleItems.length - 1]!.id);\n }\n\n handled = true;\n break;\n\n case \"Enter\":\n if (item.href) {\n if (!item.disabled) {\n event.currentTarget\n .querySelector<HTMLAnchorElement>(\"a.puzzle-tree__row\")\n ?.click();\n\n onItemActivate?.(item);\n }\n } else if (hasChildren(item)) {\n toggleExpanded(item);\n if (!item.disabled) onItemActivate?.(item);\n } else {\n selectItem(item);\n if (!item.disabled) onItemActivate?.(item);\n }\n\n handled = true;\n break;\n\n case \" \":\n case \"Spacebar\":\n selectItem(item, true);\n handled = true;\n break;\n\n case \"*\":\n expandSiblings(item.id);\n handled = true;\n break;\n\n default:\n if (\n event.key.length === 1 &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey\n ) {\n if (!typeahead) break;\n\n const currentTime = event.timeStamp;\n const isExpired = currentTime - typeaheadState.current.time > 500;\n const previousValue = isExpired ? \"\" : typeaheadState.current.value;\n const normalizedPrevious = normalizeSearchText(previousValue);\n const normalizedKey = normalizeSearchText(event.key);\n\n const isRepeatedCharacter =\n normalizedPrevious.length > 0 &&\n [...normalizedPrevious].every(\n (character) => character === normalizedKey,\n );\n\n const searchValue = isRepeatedCharacter\n ? event.key\n : `${previousValue}${event.key}`;\n\n typeaheadState.current = {\n value: searchValue,\n time: currentTime,\n };\n\n const normalizedSearch = normalizeSearchText(searchValue);\n\n const orderedItems = [\n ...visibleItems.slice(index + 1),\n ...visibleItems.slice(0, index + 1),\n ];\n\n const match = orderedItems.find((candidate) =>\n normalizeSearchText(getTextValue(candidate)).startsWith(\n normalizedSearch,\n ),\n );\n\n if (match) {\n focusItem(match.id);\n }\n\n handled = true;\n }\n }\n\n if (event.key === openKey) {\n if (hasChildren(item)) {\n if (!expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else {\n const firstChild = getChildren(item)[0];\n if (firstChild) focusItem(firstChild.id);\n }\n }\n\n handled = true;\n } else if (event.key === closeKey) {\n if (hasChildren(item) && expandedSet.has(item.id)) {\n toggleExpanded(item);\n } else if (parentId) {\n focusItem(parentId);\n }\n\n handled = true;\n }\n\n if (handled) {\n event.preventDefault();\n }\n };\n\n const handleItemClick = (\n event: MouseEvent<HTMLLIElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as HTMLElement;\n const targetTreeItem = target.closest('[role=\"treeitem\"]');\n\n // Ignore bubbled clicks from descendants and clicks on custom interactive adornments.\n if (targetTreeItem !== event.currentTarget) return;\n\n if (\n target.closest(\n \"button, a, input, select, textarea, [data-tree-stop-selection]\",\n )\n ) {\n return;\n }\n\n focusItem(item.id);\n\n if (\n hasChildren(item) &&\n (target.closest(\"[data-tree-toggle]\") || expandOnItemClick)\n ) {\n toggleExpanded(item);\n return;\n }\n\n selectItem(item);\n };\n\n /**\n * Handles pointer activation of a linked row.\n *\n * The row link is the navigation target, but the tree item remains the\n * roving-focus element. Clicking the expansion affordance is the exception:\n * it expands the branch and deliberately does not navigate.\n */\n const handleItemLinkClick = (\n event: MouseEvent<HTMLAnchorElement>,\n item: TreeViewItem<TData>,\n ) => {\n const target = event.target as Element;\n\n if (target.closest(\"[data-tree-toggle]\")) {\n event.preventDefault();\n event.stopPropagation();\n\n focusItem(item.id);\n toggleExpanded(item);\n return;\n }\n\n if (item.disabled) {\n event.preventDefault();\n event.stopPropagation();\n return;\n }\n\n focusItem(item.id);\n selectItem(item);\n };\n\n const renderItems = (\n nodes: TreeViewItem<TData>[],\n depth = 0,\n parentId?: string,\n ): ReactNode =>\n nodes.map((item, index) => {\n const itemChildren = getChildren(item);\n const itemHasChildren = hasChildren(item);\n const expanded = itemHasChildren && expandedSet.has(item.id);\n const disabled = Boolean(item.disabled);\n const active = Boolean(getItemActive?.(item));\n\n const selected =\n selectionMode !== \"none\" &&\n selectedSet.has(item.id) &&\n item.selectable !== false &&\n !disabled;\n\n const loading = loadingSet.has(item.id);\n const focused = effectiveFocusedId === item.id;\n\n const itemState: TreeViewItemState = {\n expanded,\n selected,\n focused,\n loading,\n disabled,\n hasChildren: itemHasChildren,\n };\n\n const iconContent = renderIcon ? renderIcon(item, itemState) : item.icon;\n const itemId = `${generatedId}-item-${encodeURIComponent(item.id)}`;\n\n const selectable =\n selectionMode !== \"none\" && item.selectable !== false && !disabled;\n\n const hasLoadError = loadErrors.has(item.id);\n\n const rowContent = (\n <>\n {itemHasChildren ? (\n <span\n className=\"puzzle-tree__toggle\"\n data-tree-toggle=\"true\"\n data-expanded={expanded || undefined}\n aria-hidden=\"true\"\n />\n ) : (\n <span className=\"puzzle-tree__toggle-spacer\" aria-hidden=\"true\" />\n )}\n\n {renderItem ? (\n renderItem(item, itemState)\n ) : (\n <>\n {iconContent != null && (\n <span className=\"puzzle-tree__icon\" aria-hidden=\"true\">\n {iconContent}\n </span>\n )}\n\n <span className=\"puzzle-tree__label\">\n {renderLabel ? renderLabel(item, itemState) : item.label}\n </span>\n\n {item.endAdornment != null && (\n <span className=\"puzzle-tree__adornment\">\n {item.endAdornment}\n </span>\n )}\n </>\n )}\n </>\n );\n\n const row =\n item.href !== undefined ? (\n <Link\n href={item.href}\n className=\"puzzle-tree__row\"\n tabIndex={-1}\n aria-current={active ? \"page\" : undefined}\n aria-disabled={disabled || undefined}\n data-tree-row-link=\"true\"\n onClick={(event) => handleItemLinkClick(event, item)}\n >\n {rowContent}\n </Link>\n ) : (\n <div className=\"puzzle-tree__row\">{rowContent}</div>\n );\n\n return (\n <li\n key={item.id}\n ref={(element) => {\n if (element) {\n itemRefs.current.set(item.id, element);\n } else {\n itemRefs.current.delete(item.id);\n }\n }}\n id={itemId}\n role=\"treeitem\"\n aria-level={depth + 1}\n aria-posinset={index + 1}\n aria-setsize={nodes.length}\n aria-expanded={itemHasChildren ? expanded : undefined}\n aria-selected={selectable ? selected : undefined}\n aria-disabled={disabled || undefined}\n aria-busy={loading || undefined}\n tabIndex={focused ? 0 : -1}\n className={cn(\"puzzle-tree__item\", itemClassName)}\n data-tree-item-id={item.id}\n data-active={active || undefined}\n data-selected={selected || undefined}\n data-disabled={disabled || undefined}\n data-expanded={itemHasChildren ? expanded : undefined}\n onFocus={(event) => {\n if (event.target !== event.currentTarget) return;\n\n if (focusedId !== item.id) {\n setFocusedId(item.id);\n onFocusedChange?.(item.id);\n }\n }}\n onClick={(event) => handleItemClick(event, item)}\n onKeyDown={(event) => handleKeyDown(event, item, parentId)}\n >\n {row}\n\n {itemHasChildren && expanded && (\n <ul className=\"puzzle-tree__group\" role=\"group\">\n {loading || hasLoadError ? (\n <li\n role=\"none\"\n className={cn(\n \"puzzle-tree__status-row\",\n hasLoadError &&\n !loading &&\n \"puzzle-tree__status-row--error\",\n )}\n >\n <span role=\"status\" aria-live=\"polite\">\n {loading ? loadingLabel : loadErrorLabel}\n </span>\n </li>\n ) : null}\n\n {renderItems(itemChildren, depth + 1, item.id)}\n </ul>\n )}\n </li>\n );\n });\n\n const rootStyle = {\n ...style,\n ...(indentation !== undefined\n ? {\n \"--puzzle-tree-indentation\":\n typeof indentation === \"number\" ? `${indentation}px` : indentation,\n }\n : {}),\n } as CSSProperties;\n\n return (\n <ul\n {...rootProps}\n role=\"tree\"\n aria-multiselectable={selectionMode === \"multiple\" || undefined}\n className={cn(\"puzzle-tree\", `puzzle-tree--${size}`, className)}\n style={rootStyle}\n dir={dir}\n tabIndex={visibleItems.length === 0 ? 0 : undefined}\n onFocusCapture={() => {\n focusWithinTree.current = true;\n }}\n onBlurCapture={(event) => {\n const nextTarget = event.relatedTarget;\n const target = event.target as HTMLElement;\n\n if (\n event.currentTarget.contains(target) &&\n ((nextTarget && !event.currentTarget.contains(nextTarget as Node)) ||\n (!nextTarget && target.isConnected))\n ) {\n focusWithinTree.current = false;\n }\n }}\n >\n {renderItems(items)}\n </ul>\n );\n};\n\nconst findItemById = <TData,>(\n nodes: TreeViewItem<TData>[],\n id: string,\n getChildren: (item: TreeViewItem<TData>) => TreeViewItem<TData>[],\n): TreeViewItem<TData> | undefined => {\n for (const item of nodes) {\n if (item.id === id) return item;\n\n const found = findItemById(getChildren(item), id, getChildren);\n if (found) return found;\n }\n\n return undefined;\n};\n\nexport {\n TreeView,\n type TreeViewProps,\n type TreeViewItem,\n type TreeViewItemState,\n type TreeViewSelectionMode,\n type TreeViewSize,\n};\n"],"mappings":"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"}
@@ -39,7 +39,7 @@
39
39
  user-select: none;
40
40
  touch-action: manipulation;
41
41
  transition: background-color var(--puzzle-tree-duration) var(--puzzle-tree-easing),
42
- color var(--puzzle-tree-duration) var(--puzzle-tree-easing);
42
+ color var(--puzzle-tree-duration) var(--puzzle-tree-easing);
43
43
  display: flex;
44
44
  }
45
45
 
@@ -150,7 +150,8 @@
150
150
  .puzzle-tree__group {
151
151
  gap: var(--puzzle-tree-row-gap);
152
152
  min-width: 0;
153
- border-inline-start: var(--puzzle-border-width, 1px) solid var(--puzzle-color-border);
153
+ border-inline-start: var(--puzzle-border-width, 1px) solid
154
+ var(--puzzle-color-border);
154
155
  flex-direction: column;
155
156
  margin-block-start: 0;
156
157
  margin-inline-start: var(--puzzle-tree-indentation);
@@ -174,7 +175,8 @@
174
175
  }
175
176
 
176
177
  .puzzle-tree__status-row:not(.puzzle-tree__status-row--error) [role="status"]:before {
177
- border: var(--puzzle-border-width-strong, 2px) solid var(--puzzle-color-border-strong);
178
+ border: var(--puzzle-border-width-strong, 2px) solid
179
+ var(--puzzle-color-border-strong);
178
180
  border-block-start-color: var(--puzzle-color-primary);
179
181
  border-radius: var(--puzzle-radius-full, 9999px);
180
182
  content: "";
@@ -0,0 +1,151 @@
1
+ .puzzle-context-menu__anchor {
2
+ z-index: -1;
3
+ pointer-events: none;
4
+ width: 1px;
5
+ height: 1px;
6
+ position: fixed;
7
+ }
8
+
9
+ .puzzle-context-menu__trigger {
10
+ touch-action: manipulation;
11
+ }
12
+
13
+ .puzzle-context-menu__trigger:focus-visible {
14
+ outline: 2px solid var(--puzzle-color-ring);
15
+ outline-offset: 2px;
16
+ }
17
+
18
+ .puzzle-popout__content.puzzle-context-menu__popover {
19
+ min-width: 11rem;
20
+ padding: var(--puzzle-space-1, .25rem);
21
+ scrollbar-width: thin;
22
+ overflow: hidden auto;
23
+ }
24
+
25
+ .puzzle-context-menu__content {
26
+ gap: var(--puzzle-space-1, .25rem);
27
+ overscroll-behavior: contain;
28
+ scrollbar-width: thin;
29
+ outline: none;
30
+ flex-direction: column;
31
+ min-width: 0;
32
+ max-width: min(20rem, 100vw - 1rem);
33
+ display: flex;
34
+ overflow: hidden auto;
35
+ }
36
+
37
+ .puzzle-context-menu__group {
38
+ gap: var(--puzzle-space-1, .25rem);
39
+ flex-direction: column;
40
+ min-width: 0;
41
+ display: flex;
42
+ }
43
+
44
+ .puzzle-context-menu__label {
45
+ padding: var(--puzzle-space-2, .5rem) var(--puzzle-space-3, .75rem);
46
+ color: var(--puzzle-color-text-muted);
47
+ font-size: var(--puzzle-font-size-xs, .75rem);
48
+ font-weight: var(--puzzle-font-weight-semibold, 600);
49
+ line-height: var(--puzzle-font-line-height-body2, 1.4);
50
+ }
51
+
52
+ .puzzle-context-menu__item {
53
+ box-sizing: border-box;
54
+ align-items: center;
55
+ gap: var(--puzzle-space-2, .5rem);
56
+ width: 100%;
57
+ min-width: 0;
58
+ min-height: 2.25rem;
59
+ padding: var(--puzzle-space-2, .5rem) var(--puzzle-space-3, .75rem);
60
+ border-radius: var(--puzzle-radius-sm, .25rem);
61
+ color: var(--puzzle-color-text);
62
+ font: inherit;
63
+ font-size: var(--puzzle-font-size-sm, .875rem);
64
+ line-height: var(--puzzle-font-line-height-body2, 1.4);
65
+ text-align: start;
66
+ cursor: pointer;
67
+ user-select: none;
68
+ touch-action: manipulation;
69
+ transition: color var(--puzzle-duration-fast, .15s) ease,
70
+ background-color var(--puzzle-duration-fast, .15s) ease,
71
+ border-color var(--puzzle-duration-fast, .15s) ease;
72
+ background: none;
73
+ border: 1px solid #0000;
74
+ text-decoration: none;
75
+ display: flex;
76
+ }
77
+
78
+ .puzzle-context-menu__item:hover:not(:disabled):not([aria-disabled="true"]), .puzzle-context-menu__item:focus-visible:not(:disabled):not([aria-disabled="true"]) {
79
+ background: var(--puzzle-color-surface-sunken);
80
+ outline: none;
81
+ }
82
+
83
+ .puzzle-context-menu__item:focus-visible:not(:disabled):not([aria-disabled="true"]) {
84
+ border-color: var(--puzzle-color-ring);
85
+ }
86
+
87
+ .puzzle-context-menu__item:disabled, .puzzle-context-menu__item[aria-disabled="true"] {
88
+ opacity: .5;
89
+ cursor: not-allowed;
90
+ }
91
+
92
+ .puzzle-context-menu__item--danger {
93
+ color: var(--puzzle-color-danger);
94
+ }
95
+
96
+ .puzzle-context-menu__item--danger:hover:not(:disabled):not([aria-disabled="true"]), .puzzle-context-menu__item--danger:focus-visible:not(:disabled):not([aria-disabled="true"]) {
97
+ background: color-mix(in srgb, var(--puzzle-color-danger), transparent 92%);
98
+ }
99
+
100
+ .puzzle-context-menu__item-label {
101
+ flex: auto;
102
+ min-width: 0;
103
+ }
104
+
105
+ .puzzle-context-menu__indicator {
106
+ width: 1rem;
107
+ min-height: 1rem;
108
+ color: var(--puzzle-color-primary);
109
+ font-size: .875rem;
110
+ font-weight: var(--puzzle-font-weight-semibold, 600);
111
+ flex: 0 0 1rem;
112
+ justify-content: center;
113
+ align-items: center;
114
+ display: inline-flex;
115
+ }
116
+
117
+ .puzzle-context-menu__submenu-indicator {
118
+ color: var(--puzzle-color-text-muted);
119
+ flex: none;
120
+ margin-inline-start: var(--puzzle-space-2, .5rem);
121
+ font-size: 1.125rem;
122
+ line-height: 1;
123
+ }
124
+
125
+ .puzzle-context-menu__separator {
126
+ height: 1px;
127
+ margin-block: var(--puzzle-space-1, .25rem);
128
+ background: var(--puzzle-color-border);
129
+ flex: none;
130
+ }
131
+
132
+ .puzzle-context-menu__submenu-popover {
133
+ z-index: 1;
134
+ }
135
+
136
+ @media (prefers-reduced-motion: reduce) {
137
+ .puzzle-context-menu__item, .puzzle-context-menu__trigger {
138
+ transition: none;
139
+ }
140
+ }
141
+
142
+ @media (forced-colors: active) {
143
+ .puzzle-context-menu__item:focus-visible:not(:disabled), .puzzle-context-menu__trigger:focus-visible {
144
+ outline-offset: 2px;
145
+ outline: 2px solid highlight;
146
+ }
147
+
148
+ .puzzle-context-menu__separator {
149
+ background: canvastext;
150
+ }
151
+ }