@basic-ui/core 0.0.60 → 0.0.61

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 (214) hide show
  1. package/build/cjs/index.js.map +1 -1
  2. package/build/esm/Accordion/AccordionBody.d.ts.map +1 -1
  3. package/build/esm/Accordion/AccordionBody.js +6 -26
  4. package/build/esm/Accordion/AccordionBody.js.map +1 -1
  5. package/build/esm/Accordion/AccordionHeader.d.ts.map +1 -1
  6. package/build/esm/Accordion/AccordionHeader.js +21 -69
  7. package/build/esm/Accordion/AccordionHeader.js.map +1 -1
  8. package/build/esm/Accordion/AccordionItem.d.ts.map +1 -1
  9. package/build/esm/Accordion/AccordionItem.js +31 -18
  10. package/build/esm/Accordion/AccordionItem.js.map +1 -1
  11. package/build/esm/Accordion/context.d.ts +0 -8
  12. package/build/esm/Accordion/context.d.ts.map +1 -1
  13. package/build/esm/Accordion/context.js +0 -11
  14. package/build/esm/Accordion/context.js.map +1 -1
  15. package/build/esm/Accordion/scopeQuery.d.ts +1 -0
  16. package/build/esm/Accordion/scopeQuery.d.ts.map +1 -1
  17. package/build/esm/Accordion/scopeQuery.js +3 -0
  18. package/build/esm/Accordion/scopeQuery.js.map +1 -1
  19. package/build/esm/Collapsible/Collapsible.d.ts +13 -0
  20. package/build/esm/Collapsible/Collapsible.d.ts.map +1 -0
  21. package/build/esm/Collapsible/Collapsible.js +53 -0
  22. package/build/esm/Collapsible/Collapsible.js.map +1 -0
  23. package/build/esm/Collapsible/CollapsiblePanel.d.ts +10 -0
  24. package/build/esm/Collapsible/CollapsiblePanel.d.ts.map +1 -0
  25. package/build/esm/Collapsible/CollapsiblePanel.js +85 -0
  26. package/build/esm/Collapsible/CollapsiblePanel.js.map +1 -0
  27. package/build/esm/Collapsible/CollapsibleTrigger.d.ts +11 -0
  28. package/build/esm/Collapsible/CollapsibleTrigger.d.ts.map +1 -0
  29. package/build/esm/Collapsible/CollapsibleTrigger.js +51 -0
  30. package/build/esm/Collapsible/CollapsibleTrigger.js.map +1 -0
  31. package/build/esm/Collapsible/context.d.ts +16 -0
  32. package/build/esm/Collapsible/context.d.ts.map +1 -0
  33. package/build/esm/Collapsible/context.js +11 -0
  34. package/build/esm/Collapsible/context.js.map +1 -0
  35. package/build/esm/Collapsible/index.d.ts +4 -0
  36. package/build/esm/Collapsible/index.d.ts.map +1 -0
  37. package/build/esm/Collapsible/index.js +4 -0
  38. package/build/esm/Collapsible/index.js.map +1 -0
  39. package/build/esm/Menu/Menu.d.ts +3 -2
  40. package/build/esm/Menu/Menu.d.ts.map +1 -1
  41. package/build/esm/Menu/Menu.js +64 -4
  42. package/build/esm/Menu/Menu.js.map +1 -1
  43. package/build/esm/Menu/MenuButton.d.ts.map +1 -1
  44. package/build/esm/Menu/MenuButton.js +85 -8
  45. package/build/esm/Menu/MenuButton.js.map +1 -1
  46. package/build/esm/Menu/MenuItem.d.ts.map +1 -1
  47. package/build/esm/Menu/MenuItem.js +16 -4
  48. package/build/esm/Menu/MenuItem.js.map +1 -1
  49. package/build/esm/Menu/MenuList.d.ts.map +1 -1
  50. package/build/esm/Menu/MenuList.js +47 -12
  51. package/build/esm/Menu/MenuList.js.map +1 -1
  52. package/build/esm/Menu/MenuPopover.d.ts.map +1 -1
  53. package/build/esm/Menu/MenuPopover.js +12 -1
  54. package/build/esm/Menu/MenuPopover.js.map +1 -1
  55. package/build/esm/Menu/MenuSubmenuTrigger.d.ts +8 -0
  56. package/build/esm/Menu/MenuSubmenuTrigger.d.ts.map +1 -0
  57. package/build/esm/Menu/MenuSubmenuTrigger.js +131 -0
  58. package/build/esm/Menu/MenuSubmenuTrigger.js.map +1 -0
  59. package/build/esm/Menu/context.d.ts +13 -3
  60. package/build/esm/Menu/context.d.ts.map +1 -1
  61. package/build/esm/Menu/context.js +1 -0
  62. package/build/esm/Menu/context.js.map +1 -1
  63. package/build/esm/Menu/index.d.ts +3 -0
  64. package/build/esm/Menu/index.d.ts.map +1 -1
  65. package/build/esm/Menu/index.js +2 -0
  66. package/build/esm/Menu/index.js.map +1 -1
  67. package/build/esm/Menu/scope.d.ts +1 -0
  68. package/build/esm/Menu/scope.d.ts.map +1 -1
  69. package/build/esm/Menu/scope.js +2 -1
  70. package/build/esm/Menu/scope.js.map +1 -1
  71. package/build/esm/MenuBar/MenuBar.d.ts +11 -0
  72. package/build/esm/MenuBar/MenuBar.d.ts.map +1 -0
  73. package/build/esm/MenuBar/MenuBar.js +153 -0
  74. package/build/esm/MenuBar/MenuBar.js.map +1 -0
  75. package/build/esm/MenuBar/context.d.ts +29 -0
  76. package/build/esm/MenuBar/context.d.ts.map +1 -0
  77. package/build/esm/MenuBar/context.js +7 -0
  78. package/build/esm/MenuBar/context.js.map +1 -0
  79. package/build/esm/MenuBar/index.d.ts +2 -0
  80. package/build/esm/MenuBar/index.d.ts.map +1 -0
  81. package/build/esm/MenuBar/index.js +2 -0
  82. package/build/esm/MenuBar/index.js.map +1 -0
  83. package/build/esm/Slider/Slider.d.ts +47 -1
  84. package/build/esm/Slider/Slider.d.ts.map +1 -1
  85. package/build/esm/Slider/Slider.js +91 -5
  86. package/build/esm/Slider/Slider.js.map +1 -1
  87. package/build/esm/ToggleGroup/ToggleGroup.d.ts +40 -0
  88. package/build/esm/ToggleGroup/ToggleGroup.d.ts.map +1 -0
  89. package/build/esm/ToggleGroup/ToggleGroup.js +113 -0
  90. package/build/esm/ToggleGroup/ToggleGroup.js.map +1 -0
  91. package/build/esm/ToggleGroup/ToggleGroupContext.d.ts +10 -0
  92. package/build/esm/ToggleGroup/ToggleGroupContext.d.ts.map +1 -0
  93. package/build/esm/ToggleGroup/ToggleGroupContext.js +6 -0
  94. package/build/esm/ToggleGroup/ToggleGroupContext.js.map +1 -0
  95. package/build/esm/ToggleGroup/index.d.ts +3 -0
  96. package/build/esm/ToggleGroup/index.d.ts.map +1 -0
  97. package/build/esm/ToggleGroup/index.js +3 -0
  98. package/build/esm/ToggleGroup/index.js.map +1 -0
  99. package/build/esm/Tree/Tree.d.ts +3 -0
  100. package/build/esm/Tree/Tree.d.ts.map +1 -0
  101. package/build/esm/Tree/Tree.js +730 -0
  102. package/build/esm/Tree/Tree.js.map +1 -0
  103. package/build/esm/Tree/TreeHeader.d.ts +3 -0
  104. package/build/esm/Tree/TreeHeader.d.ts.map +1 -0
  105. package/build/esm/Tree/TreeHeader.js +5 -0
  106. package/build/esm/Tree/TreeHeader.js.map +1 -0
  107. package/build/esm/Tree/TreeItem.d.ts +3 -0
  108. package/build/esm/Tree/TreeItem.d.ts.map +1 -0
  109. package/build/esm/Tree/TreeItem.js +5 -0
  110. package/build/esm/Tree/TreeItem.js.map +1 -0
  111. package/build/esm/Tree/TreeItemContent.d.ts +3 -0
  112. package/build/esm/Tree/TreeItemContent.d.ts.map +1 -0
  113. package/build/esm/Tree/TreeItemContent.js +69 -0
  114. package/build/esm/Tree/TreeItemContent.js.map +1 -0
  115. package/build/esm/Tree/TreeSection.d.ts +3 -0
  116. package/build/esm/Tree/TreeSection.d.ts.map +1 -0
  117. package/build/esm/Tree/TreeSection.js +5 -0
  118. package/build/esm/Tree/TreeSection.js.map +1 -0
  119. package/build/esm/Tree/collection.d.ts +18 -0
  120. package/build/esm/Tree/collection.d.ts.map +1 -0
  121. package/build/esm/Tree/collection.js +252 -0
  122. package/build/esm/Tree/collection.js.map +1 -0
  123. package/build/esm/Tree/context.d.ts +3 -0
  124. package/build/esm/Tree/context.d.ts.map +1 -0
  125. package/build/esm/Tree/context.js +3 -0
  126. package/build/esm/Tree/context.js.map +1 -0
  127. package/build/esm/Tree/index.d.ts +8 -0
  128. package/build/esm/Tree/index.d.ts.map +1 -0
  129. package/build/esm/Tree/index.js +7 -0
  130. package/build/esm/Tree/index.js.map +1 -0
  131. package/build/esm/Tree/types.d.ts +128 -0
  132. package/build/esm/Tree/types.d.ts.map +1 -0
  133. package/build/esm/Tree/types.js +2 -0
  134. package/build/esm/Tree/types.js.map +1 -0
  135. package/build/esm/hooks/index.d.ts +1 -0
  136. package/build/esm/hooks/index.d.ts.map +1 -1
  137. package/build/esm/hooks/index.js +1 -0
  138. package/build/esm/hooks/index.js.map +1 -1
  139. package/build/esm/hooks/useTransitionStatus.d.ts +7 -0
  140. package/build/esm/hooks/useTransitionStatus.d.ts.map +1 -0
  141. package/build/esm/hooks/useTransitionStatus.js +48 -0
  142. package/build/esm/hooks/useTransitionStatus.js.map +1 -0
  143. package/build/esm/index.d.ts +5 -0
  144. package/build/esm/index.d.ts.map +1 -1
  145. package/build/esm/index.js +5 -0
  146. package/build/esm/index.js.map +1 -1
  147. package/build/esm/toggle/Toggle.d.ts +28 -0
  148. package/build/esm/toggle/Toggle.d.ts.map +1 -0
  149. package/build/esm/toggle/Toggle.js +55 -0
  150. package/build/esm/toggle/Toggle.js.map +1 -0
  151. package/build/esm/toggle/index.d.ts +2 -0
  152. package/build/esm/toggle/index.d.ts.map +1 -0
  153. package/build/esm/toggle/index.js +2 -0
  154. package/build/esm/toggle/index.js.map +1 -0
  155. package/build/esm/utils/assign-ref.d.ts +3 -3
  156. package/build/esm/utils/assign-ref.d.ts.map +1 -1
  157. package/build/esm/utils/assign-ref.js +1 -1
  158. package/build/esm/utils/assign-ref.js.map +1 -1
  159. package/build/tsconfig-build.tsbuildinfo +1 -1
  160. package/build/tsconfig.tsbuildinfo +1 -1
  161. package/package.json +7 -4
  162. package/src/Accordion/AccordionBody.tsx +6 -35
  163. package/src/Accordion/AccordionHeader.tsx +29 -103
  164. package/src/Accordion/AccordionItem.tsx +40 -29
  165. package/src/Accordion/context.ts +0 -18
  166. package/src/Accordion/scopeQuery.ts +4 -0
  167. package/src/Collapsible/Collapsible.story.tsx +153 -0
  168. package/src/Collapsible/Collapsible.tsx +79 -0
  169. package/src/Collapsible/CollapsiblePanel.tsx +103 -0
  170. package/src/Collapsible/CollapsibleTrigger.tsx +60 -0
  171. package/src/Collapsible/context.ts +28 -0
  172. package/src/Collapsible/index.ts +3 -0
  173. package/src/Menu/Menu.story.tsx +70 -1
  174. package/src/Menu/Menu.tsx +141 -65
  175. package/src/Menu/MenuButton.tsx +115 -9
  176. package/src/Menu/MenuItem.tsx +20 -3
  177. package/src/Menu/MenuList.tsx +50 -13
  178. package/src/Menu/MenuPopover.tsx +12 -2
  179. package/src/Menu/MenuSubmenuTrigger.tsx +167 -0
  180. package/src/Menu/context.ts +20 -10
  181. package/src/Menu/index.ts +3 -0
  182. package/src/Menu/scope.ts +4 -1
  183. package/src/Menu/styles.css +57 -22
  184. package/src/MenuBar/MenuBar.story.tsx +92 -0
  185. package/src/MenuBar/MenuBar.tsx +236 -0
  186. package/src/MenuBar/context.ts +46 -0
  187. package/src/MenuBar/index.ts +1 -0
  188. package/src/MenuBar/styles.css +78 -0
  189. package/src/Slider/Slider.story.tsx +1 -1
  190. package/src/Slider/Slider.tsx +145 -8
  191. package/src/Toggle/Toggle.story.tsx +42 -0
  192. package/src/Toggle/Toggle.tsx +95 -0
  193. package/src/Toggle/index.ts +1 -0
  194. package/src/Toggle/styles.css +39 -0
  195. package/src/ToggleGroup/ToggleGroup.story.tsx +86 -0
  196. package/src/ToggleGroup/ToggleGroup.tsx +185 -0
  197. package/src/ToggleGroup/ToggleGroupContext.ts +17 -0
  198. package/src/ToggleGroup/index.ts +2 -0
  199. package/src/ToggleGroup/styles.css +66 -0
  200. package/src/Tree/Tree.story.tsx +221 -0
  201. package/src/Tree/Tree.tsx +1081 -0
  202. package/src/Tree/TreeHeader.tsx +9 -0
  203. package/src/Tree/TreeItem.tsx +9 -0
  204. package/src/Tree/TreeItemContent.tsx +91 -0
  205. package/src/Tree/TreeSection.tsx +9 -0
  206. package/src/Tree/collection.tsx +371 -0
  207. package/src/Tree/context.ts +6 -0
  208. package/src/Tree/index.ts +7 -0
  209. package/src/Tree/styles.css +135 -0
  210. package/src/Tree/types.ts +161 -0
  211. package/src/hooks/index.ts +1 -0
  212. package/src/hooks/useTransitionStatus.ts +65 -0
  213. package/src/index.ts +5 -0
  214. package/src/utils/assign-ref.ts +4 -4
@@ -0,0 +1,9 @@
1
+ import { forwardRef } from 'react';
2
+
3
+ import type { TreeHeaderProps } from './types';
4
+
5
+ export const TreeHeader = forwardRef<HTMLDivElement, TreeHeaderProps>(
6
+ function TreeHeader() {
7
+ return null;
8
+ }
9
+ );
@@ -0,0 +1,9 @@
1
+ import { forwardRef } from 'react';
2
+
3
+ import type { TreeItemProps } from './types';
4
+
5
+ export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
6
+ function TreeItem() {
7
+ return null;
8
+ }
9
+ );
@@ -0,0 +1,91 @@
1
+ import type { KeyboardEvent, MouseEvent, ReactElement, ReactNode } from 'react';
2
+ import {
3
+ Children,
4
+ cloneElement,
5
+ forwardRef,
6
+ isValidElement,
7
+ useContext,
8
+ } from 'react';
9
+
10
+ import { TreeItemRenderContext } from './context';
11
+ import type {
12
+ TreeItemContentProps,
13
+ TreeItemRenderContextValue,
14
+ } from './types';
15
+ import { wrapEvent } from '../utils';
16
+
17
+ export const TreeItemContent = forwardRef<
18
+ HTMLDivElement,
19
+ TreeItemContentProps
20
+ >(function TreeItemContent(props, forwardedRef) {
21
+ const { as: Comp = 'div', innerAs, children, ...otherProps } = props;
22
+ const context = useContext(TreeItemRenderContext);
23
+
24
+ if (!context) {
25
+ return (
26
+ <Comp ref={forwardedRef} as={innerAs} {...otherProps}>
27
+ {children}
28
+ </Comp>
29
+ );
30
+ }
31
+
32
+ return (
33
+ <Comp ref={forwardedRef} as={innerAs} {...otherProps}>
34
+ {Children.map(children, (child) => enhanceSlottedChild(child, context))}
35
+ </Comp>
36
+ );
37
+ });
38
+
39
+ function enhanceSlottedChild(
40
+ child: ReactNode,
41
+ context: TreeItemRenderContextValue
42
+ ) {
43
+ if (!isValidElement(child)) {
44
+ return child;
45
+ }
46
+
47
+ const element = child as ReactElement<any>;
48
+ const slot = (element.props as { slot?: string }).slot;
49
+ if (slot === 'chevron') {
50
+ return cloneElement(element, {
51
+ 'aria-expanded': context.item.hasChildItems
52
+ ? context.isExpanded
53
+ : undefined,
54
+ 'aria-label': context.isExpanded ? 'Collapse' : 'Expand',
55
+ 'aria-labelledby': context.rowId,
56
+ disabled: context.isDisabled || !context.item.hasChildItems,
57
+ tabIndex: -1,
58
+ onClick: wrapEvent(element.props.onClick, context.toggleExpanded),
59
+ onKeyDown: wrapEvent(
60
+ element.props.onKeyDown,
61
+ (event: KeyboardEvent<any>) => {
62
+ if (event.key === 'Enter' || event.key === ' ') {
63
+ context.toggleExpanded(event);
64
+ }
65
+ }
66
+ ),
67
+ });
68
+ }
69
+
70
+ if (slot === 'selection') {
71
+ return cloneElement(element, {
72
+ checked: context.isSelected,
73
+ disabled: !context.canSelect,
74
+ 'aria-label': 'Select',
75
+ 'aria-labelledby': context.rowId,
76
+ tabIndex: -1,
77
+ onClick: wrapEvent(element.props.onClick, (event: MouseEvent<any>) => {
78
+ event.stopPropagation();
79
+ }),
80
+ onChange: wrapEvent(element.props.onChange, context.toggleSelection),
81
+ });
82
+ }
83
+
84
+ if (slot === 'drag') {
85
+ return cloneElement(element, {
86
+ 'data-tree-drag-handle': '',
87
+ });
88
+ }
89
+
90
+ return child;
91
+ }
@@ -0,0 +1,9 @@
1
+ import { forwardRef } from 'react';
2
+
3
+ import type { TreeSectionProps } from './types';
4
+
5
+ export const TreeSection = forwardRef<HTMLDivElement, TreeSectionProps>(
6
+ function TreeSection() {
7
+ return null;
8
+ }
9
+ );
@@ -0,0 +1,371 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import { Children, Fragment, isValidElement, useMemo } from 'react';
3
+
4
+ import { TreeHeader } from './TreeHeader';
5
+ import { TreeItem } from './TreeItem';
6
+ import { TreeSection } from './TreeSection';
7
+ import type {
8
+ ParsedTreeItem,
9
+ ParsedTreeNode,
10
+ ParsedTreeSection,
11
+ TreeHeaderProps,
12
+ TreeItemProps,
13
+ TreeKey,
14
+ TreeProps,
15
+ TreeSectionProps,
16
+ TreeVisibleRow,
17
+ } from './types';
18
+
19
+ export interface TreeCollectionState {
20
+ parsedTree: ParsedTreeNode[];
21
+ allItems: ParsedTreeItem[];
22
+ visibleItems: ParsedTreeItem[];
23
+ visibleRows: TreeVisibleRow[];
24
+ }
25
+
26
+ export function useTreeCollection(
27
+ children: ReactNode,
28
+ options: {
29
+ expandedKeys?: Iterable<TreeKey>;
30
+ renderLoadingState?: TreeProps['renderLoadingState'];
31
+ } = {}
32
+ ): TreeCollectionState {
33
+ const expandedKeys = useMemo(
34
+ () => new Set(options.expandedKeys ?? []),
35
+ [options.expandedKeys]
36
+ );
37
+ const parsedTree = useMemo(() => parseTreeChildren(children), [children]);
38
+ const allItems = useMemo(() => flattenAllItems(parsedTree), [parsedTree]);
39
+ const visibleItems = useMemo(
40
+ () => flattenVisibleItems(parsedTree, expandedKeys),
41
+ [expandedKeys, parsedTree]
42
+ );
43
+ const visibleRows = useMemo(
44
+ () => flattenVisibleRows(parsedTree, expandedKeys, options.renderLoadingState),
45
+ [expandedKeys, options.renderLoadingState, parsedTree]
46
+ );
47
+
48
+ return {
49
+ parsedTree,
50
+ allItems,
51
+ visibleItems,
52
+ visibleRows,
53
+ };
54
+ }
55
+
56
+ export function parseTreeChildren(children: ReactNode): ParsedTreeNode[] {
57
+ const nodes: ParsedTreeNode[] = [];
58
+ let itemIndex = 0;
59
+
60
+ const visitChild = (child: ReactNode, index: number) => {
61
+ if (!isValidElement(child)) {
62
+ return;
63
+ }
64
+
65
+ if (isFragmentElement(child)) {
66
+ Children.forEach(child.props.children, visitChild);
67
+ return;
68
+ }
69
+
70
+ if (isTreeSectionElement(child)) {
71
+ const section = parseTreeSection(child, index);
72
+ nodes.push(section);
73
+ return;
74
+ }
75
+
76
+ if (isTreeItemElement(child)) {
77
+ const item = parseTreeItem(child, {
78
+ level: 1,
79
+ parentKey: null,
80
+ index: itemIndex,
81
+ posInSet: itemIndex + 1,
82
+ setSize: countTreeItems(children),
83
+ path: [itemIndex],
84
+ });
85
+ itemIndex += 1;
86
+ nodes.push(item);
87
+ }
88
+ };
89
+
90
+ Children.forEach(children, visitChild);
91
+
92
+ return nodes;
93
+ }
94
+
95
+ export function flattenAllItems(nodes: ParsedTreeNode[]): ParsedTreeItem[] {
96
+ const items: ParsedTreeItem[] = [];
97
+ const append = (item: ParsedTreeItem) => {
98
+ items.push(item);
99
+ item.childItems.forEach(append);
100
+ };
101
+
102
+ nodes.forEach((node) => {
103
+ if (node.type === 'item') {
104
+ append(node);
105
+ } else {
106
+ node.items.forEach(append);
107
+ }
108
+ });
109
+
110
+ return items;
111
+ }
112
+
113
+ export function flattenVisibleItems(
114
+ nodes: ParsedTreeNode[],
115
+ expandedKeys: Set<TreeKey>
116
+ ): ParsedTreeItem[] {
117
+ const items: ParsedTreeItem[] = [];
118
+ const append = (item: ParsedTreeItem) => {
119
+ items.push(item);
120
+ if (expandedKeys.has(item.key)) {
121
+ item.childItems.forEach(append);
122
+ }
123
+ };
124
+
125
+ nodes.forEach((node) => {
126
+ if (node.type === 'item') {
127
+ append(node);
128
+ } else {
129
+ node.items.forEach(append);
130
+ }
131
+ });
132
+
133
+ return items;
134
+ }
135
+
136
+ export function flattenVisibleRows(
137
+ nodes: ParsedTreeNode[],
138
+ expandedKeys: Set<TreeKey>,
139
+ renderLoadingState: TreeProps['renderLoadingState']
140
+ ): TreeVisibleRow[] {
141
+ const rows: TreeVisibleRow[] = [];
142
+ const append = (item: ParsedTreeItem) => {
143
+ rows.push({
144
+ type: 'item',
145
+ key: item.key,
146
+ item,
147
+ });
148
+ if (expandedKeys.has(item.key)) {
149
+ item.childItems.forEach(append);
150
+ if (shouldRenderLoadingRow(item, renderLoadingState)) {
151
+ rows.push({
152
+ type: 'loading',
153
+ key: `${item.keyString}-loading`,
154
+ item,
155
+ level: item.level + 1,
156
+ });
157
+ }
158
+ }
159
+ };
160
+
161
+ nodes.forEach((node) => {
162
+ if (node.type === 'section') {
163
+ if (node.header) {
164
+ rows.push({
165
+ type: 'section',
166
+ key: node.key,
167
+ section: node,
168
+ });
169
+ }
170
+ node.items.forEach(append);
171
+ } else {
172
+ append(node);
173
+ }
174
+ });
175
+
176
+ return rows;
177
+ }
178
+
179
+ export function shouldRenderLoadingRow(
180
+ item: ParsedTreeItem,
181
+ renderLoadingState: TreeProps['renderLoadingState']
182
+ ) {
183
+ return Boolean(
184
+ renderLoadingState &&
185
+ item.hasChildItems &&
186
+ item.childItems.length === 0 &&
187
+ item.props.isLoading
188
+ );
189
+ }
190
+
191
+ function parseTreeSection(
192
+ element: ReactElement<TreeSectionProps>,
193
+ sectionIndex: number
194
+ ): ParsedTreeSection {
195
+ const childrenArray = Children.toArray(element.props.children);
196
+ let header: ReactNode = null;
197
+ let headerProps: TreeHeaderProps = {};
198
+ const itemElements: ReactElement<TreeItemProps>[] = [];
199
+
200
+ const visitChild = (child: ReactNode) => {
201
+ if (!isValidElement(child)) {
202
+ return;
203
+ }
204
+
205
+ if (isFragmentElement(child)) {
206
+ Children.forEach(child.props.children, visitChild);
207
+ } else if (isTreeHeaderElement(child)) {
208
+ header = child.props.children;
209
+ headerProps = child.props;
210
+ } else if (isTreeItemElement(child)) {
211
+ itemElements.push(child);
212
+ }
213
+ };
214
+
215
+ childrenArray.forEach(visitChild);
216
+
217
+ const items = itemElements.map((itemElement, index) =>
218
+ parseTreeItem(itemElement, {
219
+ level: 1,
220
+ parentKey: null,
221
+ index,
222
+ posInSet: index + 1,
223
+ setSize: itemElements.length,
224
+ path: [sectionIndex, index],
225
+ })
226
+ );
227
+
228
+ return {
229
+ type: 'section',
230
+ key: `section-${sectionIndex}`,
231
+ header,
232
+ headerProps,
233
+ sectionProps: element.props,
234
+ items,
235
+ };
236
+ }
237
+
238
+ function parseTreeItem(
239
+ element: ReactElement<TreeItemProps>,
240
+ options: {
241
+ level: number;
242
+ parentKey: TreeKey | null;
243
+ index: number;
244
+ posInSet: number;
245
+ setSize: number;
246
+ path: number[];
247
+ }
248
+ ): ParsedTreeItem {
249
+ const { content, childElements } = splitTreeItemChildren(element.props);
250
+ const key = element.props.id ?? options.path.join('.');
251
+ const childItems = childElements.map((childElement, index) =>
252
+ parseTreeItem(childElement, {
253
+ level: options.level + 1,
254
+ parentKey: key,
255
+ index,
256
+ posInSet: index + 1,
257
+ setSize: childElements.length,
258
+ path: [...options.path, index],
259
+ })
260
+ );
261
+ const textValue =
262
+ element.props.textValue ??
263
+ getTextValue(element.props.title) ??
264
+ getTextValue(content) ??
265
+ '';
266
+
267
+ if (process.env.NODE_ENV !== 'production' && !textValue) {
268
+ console.warn(
269
+ 'A textValue, title, or text content is required for <TreeItem> elements to support typeahead.'
270
+ );
271
+ }
272
+
273
+ return {
274
+ type: 'item',
275
+ key,
276
+ keyString: String(key),
277
+ level: options.level,
278
+ index: options.index,
279
+ posInSet: options.posInSet,
280
+ setSize: options.setSize,
281
+ parentKey: options.parentKey,
282
+ content,
283
+ textValue,
284
+ disabled: element.props.disabled || element.props.isDisabled || false,
285
+ hasChildItems: element.props.hasChildItems || childItems.length > 0,
286
+ childItems,
287
+ props: element.props,
288
+ };
289
+ }
290
+
291
+ function splitTreeItemChildren(props: TreeItemProps) {
292
+ const childElements: ReactElement<TreeItemProps>[] = [];
293
+ const contentChildren: ReactNode[] = [];
294
+
295
+ const visitChild = (child: ReactNode) => {
296
+ if (isValidElement(child) && isTreeItemElement(child)) {
297
+ childElements.push(child);
298
+ } else if (isValidElement(child) && isTreeSectionElement(child)) {
299
+ Children.forEach(child.props.children, (sectionChild) => {
300
+ visitChild(sectionChild);
301
+ });
302
+ } else if (isValidElement(child) && isFragmentElement(child)) {
303
+ Children.forEach(child.props.children, visitChild);
304
+ } else if (child !== null && child !== undefined) {
305
+ contentChildren.push(child);
306
+ }
307
+ };
308
+
309
+ Children.forEach(props.children, visitChild);
310
+
311
+ return {
312
+ content: props.title ?? contentChildren,
313
+ childElements,
314
+ };
315
+ }
316
+
317
+ function countTreeItems(children: ReactNode) {
318
+ let count = 0;
319
+ const visitChild = (child: ReactNode) => {
320
+ if (isValidElement(child) && isTreeItemElement(child)) {
321
+ count += 1;
322
+ } else if (isValidElement(child) && isFragmentElement(child)) {
323
+ Children.forEach(child.props.children, visitChild);
324
+ }
325
+ };
326
+
327
+ Children.forEach(children, visitChild);
328
+ return count;
329
+ }
330
+
331
+ function getTextValue(node: ReactNode): string | undefined {
332
+ if (typeof node === 'string' || typeof node === 'number') {
333
+ return String(node);
334
+ }
335
+
336
+ if (Array.isArray(node)) {
337
+ const text = node
338
+ .map((child) => getTextValue(child))
339
+ .filter(Boolean)
340
+ .join('');
341
+ return text || undefined;
342
+ }
343
+
344
+ if (isValidElement(node)) {
345
+ return getTextValue((node as ReactElement<any>).props.children);
346
+ }
347
+
348
+ return undefined;
349
+ }
350
+
351
+ function isTreeItemElement(
352
+ child: ReactNode
353
+ ): child is ReactElement<TreeItemProps> {
354
+ return isValidElement(child) && child.type === TreeItem;
355
+ }
356
+
357
+ function isTreeSectionElement(
358
+ child: ReactNode
359
+ ): child is ReactElement<TreeSectionProps> {
360
+ return isValidElement(child) && child.type === TreeSection;
361
+ }
362
+
363
+ function isTreeHeaderElement(
364
+ child: ReactNode
365
+ ): child is ReactElement<TreeHeaderProps> {
366
+ return isValidElement(child) && child.type === TreeHeader;
367
+ }
368
+
369
+ function isFragmentElement(child: ReactNode): child is ReactElement<{ children?: ReactNode }> {
370
+ return isValidElement(child) && child.type === Fragment;
371
+ }
@@ -0,0 +1,6 @@
1
+ import { createContext } from 'react';
2
+
3
+ import type { TreeItemRenderContextValue } from './types';
4
+
5
+ export const TreeItemRenderContext =
6
+ createContext<TreeItemRenderContextValue | null>(null);
@@ -0,0 +1,7 @@
1
+ export * from './collection';
2
+ export * from './Tree';
3
+ export * from './TreeHeader';
4
+ export * from './TreeItem';
5
+ export * from './TreeItemContent';
6
+ export * from './TreeSection';
7
+ export type * from './types';
@@ -0,0 +1,135 @@
1
+ .TreeStory-frame {
2
+ min-height: 360px;
3
+ padding: 48px;
4
+ background: white;
5
+ color: #1f1f1f;
6
+ font-family: sans-serif;
7
+ font-size: 14px;
8
+ }
9
+
10
+ .TreeStory-frame [data-tree] {
11
+ box-sizing: border-box;
12
+ width: 280px;
13
+ margin: 0;
14
+ padding: 4px;
15
+ border: 1px solid #d8d8d8;
16
+ background: white;
17
+ color: #1f1f1f;
18
+ outline: 0;
19
+ }
20
+
21
+ .TreeStory-frame [data-tree][data-focus-visible] {
22
+ outline: 2px solid #1f1f1f;
23
+ outline-offset: 2px;
24
+ }
25
+
26
+ .TreeStory-frame [role='rowgroup'] {
27
+ display: block;
28
+ }
29
+
30
+ .TreeStory-frame [role='rowheader'] {
31
+ padding: 0.5rem 0.75rem;
32
+ color: #777;
33
+ font-size: 0.75rem;
34
+ font-weight: 700;
35
+ text-transform: uppercase;
36
+ }
37
+
38
+ .TreeStory-frame [data-tree-item] {
39
+ display: flex;
40
+ align-items: center;
41
+ min-height: 2rem;
42
+ outline: 0;
43
+ cursor: default;
44
+ user-select: none;
45
+ }
46
+
47
+ .TreeStory-frame [data-tree-item] > [role='gridcell'],
48
+ .TreeStory-frame [data-tree-loading-row] > [role='gridcell'] {
49
+ box-sizing: border-box;
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 0.5rem;
53
+ width: 100%;
54
+ min-width: 0;
55
+ padding: 0.375rem 0.75rem;
56
+ padding-left: calc(0.75rem + (var(--tree-item-level, 1) - 1) * 1rem);
57
+ }
58
+
59
+ .TreeStory-frame [data-tree-loading-row] {
60
+ color: #777;
61
+ }
62
+
63
+ .TreeStory-frame [data-tree-item][data-has-child-items] > [role='gridcell']::before {
64
+ content: '>';
65
+ display: inline-flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ width: 1rem;
69
+ height: 1rem;
70
+ color: #777;
71
+ transition: transform 120ms ease;
72
+ }
73
+
74
+ .TreeStory-frame [data-tree-item][data-expanded] > [role='gridcell']::before {
75
+ transform: rotate(90deg);
76
+ }
77
+
78
+ .TreeStory-frame [data-tree-item] > [role='gridcell']:has([slot='chevron'])::before {
79
+ content: none;
80
+ }
81
+
82
+ .TreeStory-frame [data-tree-item][data-focused],
83
+ .TreeStory-frame [data-tree-item]:hover:not([data-disabled]) {
84
+ background: #f0f0f0;
85
+ }
86
+
87
+ .TreeStory-frame [data-tree-item][data-selected] {
88
+ background: #1f1f1f;
89
+ color: white;
90
+ }
91
+
92
+ .TreeStory-frame [data-tree-item][data-selected][data-focused] {
93
+ background: #1f1f1f;
94
+ color: white;
95
+ }
96
+
97
+ .TreeStory-frame [data-tree-item][data-disabled] {
98
+ color: #777;
99
+ cursor: not-allowed;
100
+ }
101
+
102
+ .TreeStory-frame [slot='chevron'],
103
+ .TreeStory-frame [slot='drag'] {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ width: 1.5rem;
108
+ height: 1.5rem;
109
+ margin: 0;
110
+ padding: 0;
111
+ border: 0;
112
+ background: transparent;
113
+ color: inherit;
114
+ font: inherit;
115
+ }
116
+
117
+ .TreeStory-frame [slot='chevron'] {
118
+ transition: transform 120ms ease;
119
+ }
120
+
121
+ .TreeStory-frame [data-tree-item][data-expanded] [slot='chevron'] {
122
+ transform: rotate(90deg);
123
+ }
124
+
125
+ .TreeStory-frame [slot='selection'] {
126
+ width: 1rem;
127
+ height: 1rem;
128
+ margin: 0;
129
+ }
130
+
131
+ .TreeStory-frame [slot='drag'],
132
+ .TreeStory-frame [data-tree-drag-handle] {
133
+ margin-left: auto;
134
+ color: #777;
135
+ }