@deftui/react 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +2 -3
- package/dist/vendor/README.md +4 -2
- package/dist/vendor/coss/calendar.mjs +2 -0
- package/dist/vendor/coss/card.d.mts +0 -6
- package/dist/vendor/coss/card.mjs +1 -68
- package/dist/vendor/reui.io/cascader/cascader-async.d.mts +20 -0
- package/dist/vendor/reui.io/cascader/cascader-async.mjs +578 -0
- package/dist/vendor/reui.io/cascader/cascader-context.d.mts +12 -0
- package/dist/vendor/reui.io/cascader/cascader-context.mjs +56 -0
- package/dist/vendor/reui.io/cascader/cascader-footer.d.mts +60 -0
- package/dist/vendor/reui.io/cascader/cascader-footer.mjs +445 -0
- package/dist/vendor/reui.io/cascader/cascader-i18n.mjs +64 -0
- package/dist/vendor/reui.io/cascader/cascader-item.d.mts +92 -0
- package/dist/vendor/reui.io/cascader/cascader-item.mjs +533 -0
- package/dist/vendor/reui.io/cascader/cascader-lib.d.mts +3 -0
- package/dist/vendor/reui.io/cascader/cascader-lib.mjs +688 -0
- package/dist/vendor/reui.io/cascader/cascader-nav.d.mts +56 -0
- package/dist/vendor/reui.io/cascader/cascader-nav.mjs +310 -0
- package/dist/vendor/reui.io/cascader/cascader-types.d.mts +140 -0
- package/dist/vendor/reui.io/cascader/cascader-virtual.d.mts +18 -0
- package/dist/vendor/reui.io/cascader/cascader-virtual.mjs +159 -0
- package/dist/vendor/reui.io/cascader/cascader.d.mts +299 -0
- package/dist/vendor/reui.io/cascader/cascader.mjs +1470 -0
- package/dist/vendor/reui.io/frame.d.mts +22 -0
- package/dist/vendor/reui.io/frame.mjs +106 -0
- package/dist/vendor/reui.io/timeline.d.mts +21 -0
- package/dist/vendor/reui.io/timeline.mjs +127 -0
- package/dist/widgets/datetime.mjs +0 -5
- package/dist/widgets/export.d.mts +28 -0
- package/dist/widgets/index.d.mts +64 -19
- package/dist/widgets/index.mjs +54 -10
- package/package.json +2 -2
- package/dist/vendor/tailored/file-tree.d.mts +0 -21
- package/dist/vendor/tailored/file-tree.mjs +0 -146
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCascaderActions, useCascaderRender, useCascaderState } from "./cascader-context.mjs";
|
|
3
|
+
import { getCascaderCount, getCascaderMoreParent, getCascaderPath, isCascaderMoreNode } from "./cascader-lib.mjs";
|
|
4
|
+
import { Spinner } from "../../shadcn/spinner.mjs";
|
|
5
|
+
import { cn } from "cn";
|
|
6
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
7
|
+
import * as React$1 from "react";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
10
|
+
import { CheckIcon, ChevronRightIcon, LoaderCircleIcon, MinusIcon, RotateCwIcon } from "lucide-react";
|
|
11
|
+
import { Combobox } from "@base-ui/react";
|
|
12
|
+
// src/vendor/reui.io/cascader/cascader-item.tsx
|
|
13
|
+
const ROW_INSET_CLASS = "[--cascader-row-inset:8px]";
|
|
14
|
+
const ROW_FLUSH_CLASS = "ps-[var(--cascader-row-inset,8px)]! pe-[var(--cascader-row-inset,8px)]!";
|
|
15
|
+
const ROW_GUTTER_CLASS = "ps-[var(--cascader-row-inset,8px)]! pe-[calc(var(--cascader-row-inset,8px)*2_+_16px)]!";
|
|
16
|
+
const INDICATOR_CLASS = "pointer-events-none absolute flex items-center justify-center size-4 end-[var(--cascader-row-inset,8px)]! rtl:right-auto!";
|
|
17
|
+
const ROW_THEME_CLASS = "data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground rounded-sm py-1.5 [&_svg:not([class*='size-'])]:size-4";
|
|
18
|
+
/**
|
|
19
|
+
* The row's gap and type, per style, held apart from the rest of the theme.
|
|
20
|
+
*
|
|
21
|
+
* A row that pins its own gap or text size takes `ROW_SHELL_CLASS` and leaves
|
|
22
|
+
* these out, because a `style-<name>:` utility is emitted AFTER the plain one
|
|
23
|
+
* it would have to lose to. Not receiving the token beats overriding it with
|
|
24
|
+
* `!`, which a consumer then has to fight in turn.
|
|
25
|
+
*/
|
|
26
|
+
const ROW_GAP_CLASS = "gap-2";
|
|
27
|
+
const ROW_TEXT_CLASS = "text-sm";
|
|
28
|
+
const ROW_SHELL_CLASS = `${ROW_THEME_CLASS} relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 ${ROW_INSET_CLASS}`;
|
|
29
|
+
const ROW_CLASS = `${ROW_SHELL_CLASS} ${ROW_GAP_CLASS} ${ROW_TEXT_CLASS}`;
|
|
30
|
+
const CASCADER_ACTION_INSET_CLASS = `${ROW_INSET_CLASS} ${ROW_FLUSH_CLASS}`;
|
|
31
|
+
/** A footer COMMAND: the row theme, so it cannot drift from the list, plus the
|
|
32
|
+
* hover and `focus-visible` fills a plain button never gets. Fills, never a
|
|
33
|
+
* ring: a ring under rows that all paint with a fill read as a different
|
|
34
|
+
* widget bolted on. `focus-visible`, not `focus`, so a pointer press does not
|
|
35
|
+
* leave a command painted as if it were still hovered. Disabled keys off
|
|
36
|
+
* `aria-disabled`, not `:disabled`, to keep it FOCUSABLE: a footer whose only
|
|
37
|
+
* row is disabled has no tab stop. */
|
|
38
|
+
const CASCADER_ACTION_CLASS = `${ROW_SHELL_CLASS} ${ROW_TEXT_CLASS} ${ROW_FLUSH_CLASS} hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground cursor-pointer gap-2 transition-colors aria-disabled:pointer-events-none aria-disabled:opacity-50`;
|
|
39
|
+
const AFFORDANCE_BOX_CLASS = "flex size-5 shrink-0 items-center justify-center rounded-sm";
|
|
40
|
+
const AFFORDANCE_HOVER_CLASS = "transition-[background-color,box-shadow,color] hover:bg-background hover:text-foreground! hover:**:text-foreground! hover:shadow-xs";
|
|
41
|
+
const CHECKBOX_MARK_CLASS = "text-primary-foreground! **:text-primary-foreground!";
|
|
42
|
+
const INDICATOR_MARK_CLASS = "text-foreground! **:text-foreground!";
|
|
43
|
+
const CASCADER_HOVER_EXPAND_DELAY = 150;
|
|
44
|
+
/** One row. Branch presses navigate instead of selecting, vetoed here with
|
|
45
|
+
* `preventBaseUIHandler()` on `onClick` (Enter too) and on `onMouseUp`. */
|
|
46
|
+
const CascaderItem = React$1.memo(function CascaderItem({ node, index, depth, showPath = false, as = "option", expanded, indent = 16, branch: branchProp, selectable: selectableProp, selected: selectedProp, indeterminate: indeterminateProp, selectedCount: selectedCountProp, loading, error, childrenLoading = false, childrenError = false, className, children, onClick, onMouseUp, style, ...props }) {
|
|
47
|
+
const { index: treeIndex, mode, labels, isBranch, isSelectable, isDisabled, isSelected, isIndeterminate, selectedDescendantCount, multiple, branchesSelectable, indicator: indicatorEnabled, expandTrigger = "click", navigate, navigateAt, toggleExpanded, commit, getState, virtualized, loadMore, retryLevel } = useCascaderActions();
|
|
48
|
+
const { renderItem, renderLabel } = useCascaderRender();
|
|
49
|
+
const branch = branchProp ?? isBranch(node);
|
|
50
|
+
const selectable = selectableProp ?? isSelectable(node);
|
|
51
|
+
const disabled = isDisabled(node);
|
|
52
|
+
const selected = selectedProp ?? isSelected(node);
|
|
53
|
+
const indeterminate = !selected && (indeterminateProp ?? isIndeterminate(node));
|
|
54
|
+
const count = branch ? getCascaderCount(treeIndex, node) : 0;
|
|
55
|
+
const nodeDepth = depth ?? treeIndex.depthOf.get(node.value) ?? 0;
|
|
56
|
+
const selectedDescendants = branch ? selectedCountProp ?? selectedDescendantCount(node) : 0;
|
|
57
|
+
const showsSelectedCount = multiple && selectedDescendants > 0;
|
|
58
|
+
const trailingCount = showsSelectedCount ? selectedDescendants : count;
|
|
59
|
+
const showCount = trailingCount > 0;
|
|
60
|
+
const leadingCheckbox = mode === "tree" && multiple;
|
|
61
|
+
const showsIndicator = indicatorEnabled || multiple;
|
|
62
|
+
const veto = React$1.useCallback((event) => {
|
|
63
|
+
event.preventBaseUIHandler?.();
|
|
64
|
+
}, []);
|
|
65
|
+
const handleClick = React$1.useCallback((event) => {
|
|
66
|
+
onClick?.(event);
|
|
67
|
+
if (disabled) return;
|
|
68
|
+
if (branch && !selectable) {
|
|
69
|
+
veto(event);
|
|
70
|
+
navigate(node);
|
|
71
|
+
}
|
|
72
|
+
}, [
|
|
73
|
+
onClick,
|
|
74
|
+
node,
|
|
75
|
+
disabled,
|
|
76
|
+
branch,
|
|
77
|
+
selectable,
|
|
78
|
+
veto,
|
|
79
|
+
navigate
|
|
80
|
+
]);
|
|
81
|
+
const handleMouseUp = React$1.useCallback((event) => {
|
|
82
|
+
onMouseUp?.(event);
|
|
83
|
+
if (disabled) return;
|
|
84
|
+
if (branch && !selectable) veto(event);
|
|
85
|
+
}, [
|
|
86
|
+
onMouseUp,
|
|
87
|
+
disabled,
|
|
88
|
+
branch,
|
|
89
|
+
selectable,
|
|
90
|
+
veto
|
|
91
|
+
]);
|
|
92
|
+
const handleExpanderClick = React$1.useCallback((event) => {
|
|
93
|
+
event.stopPropagation();
|
|
94
|
+
veto(event);
|
|
95
|
+
if (expanded) {
|
|
96
|
+
if (!disabled || getState().expanded.has(node.value)) toggleExpanded(node.value);
|
|
97
|
+
} else if (!disabled) navigate(node);
|
|
98
|
+
}, [
|
|
99
|
+
veto,
|
|
100
|
+
node,
|
|
101
|
+
disabled,
|
|
102
|
+
expanded,
|
|
103
|
+
getState,
|
|
104
|
+
toggleExpanded,
|
|
105
|
+
navigate
|
|
106
|
+
]);
|
|
107
|
+
const handleExpanderMouseUp = React$1.useCallback((event) => {
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
veto(event);
|
|
110
|
+
}, [veto]);
|
|
111
|
+
const handleChevronClick = React$1.useCallback((event) => {
|
|
112
|
+
event.stopPropagation();
|
|
113
|
+
veto(event);
|
|
114
|
+
if (disabled) return;
|
|
115
|
+
if (as === "button") navigateAt(node, depth ?? 0);
|
|
116
|
+
else navigate(node);
|
|
117
|
+
}, [
|
|
118
|
+
veto,
|
|
119
|
+
node,
|
|
120
|
+
disabled,
|
|
121
|
+
as,
|
|
122
|
+
navigateAt,
|
|
123
|
+
depth,
|
|
124
|
+
navigate
|
|
125
|
+
]);
|
|
126
|
+
const handleChevronMouseUp = React$1.useCallback((event) => veto(event), [veto]);
|
|
127
|
+
const handleButtonMouseDown = React$1.useCallback((event) => {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
}, []);
|
|
130
|
+
const handleButtonClick = React$1.useCallback((event) => {
|
|
131
|
+
onClick?.(event);
|
|
132
|
+
if (disabled) return;
|
|
133
|
+
if (branch && !selectable) {
|
|
134
|
+
navigateAt(node, depth ?? 0);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (selectable) commit(node);
|
|
138
|
+
}, [
|
|
139
|
+
onClick,
|
|
140
|
+
node,
|
|
141
|
+
disabled,
|
|
142
|
+
branch,
|
|
143
|
+
selectable,
|
|
144
|
+
navigateAt,
|
|
145
|
+
depth,
|
|
146
|
+
commit
|
|
147
|
+
]);
|
|
148
|
+
const handleMoreClick = React$1.useCallback((event) => {
|
|
149
|
+
onClick?.(event);
|
|
150
|
+
veto(event);
|
|
151
|
+
const parent = getCascaderMoreParent(node);
|
|
152
|
+
if (parent == null) return;
|
|
153
|
+
if (error) retryLevel(parent);
|
|
154
|
+
else if (!loading) loadMore(parent);
|
|
155
|
+
}, [
|
|
156
|
+
onClick,
|
|
157
|
+
veto,
|
|
158
|
+
node,
|
|
159
|
+
error,
|
|
160
|
+
loading,
|
|
161
|
+
retryLevel,
|
|
162
|
+
loadMore
|
|
163
|
+
]);
|
|
164
|
+
const hoverNavigates = expandTrigger === "hover" && mode === "columns" && as === "option" && branch && !disabled;
|
|
165
|
+
const hoverTimerRef = React$1.useRef(null);
|
|
166
|
+
const cancelHoverNavigate = React$1.useCallback(() => {
|
|
167
|
+
if (hoverTimerRef.current == null) return;
|
|
168
|
+
window.clearTimeout(hoverTimerRef.current);
|
|
169
|
+
hoverTimerRef.current = null;
|
|
170
|
+
}, []);
|
|
171
|
+
const handleHoverPointerEnter = React$1.useCallback(() => {
|
|
172
|
+
cancelHoverNavigate();
|
|
173
|
+
hoverTimerRef.current = window.setTimeout(() => {
|
|
174
|
+
hoverTimerRef.current = null;
|
|
175
|
+
navigate(node);
|
|
176
|
+
}, CASCADER_HOVER_EXPAND_DELAY);
|
|
177
|
+
}, [
|
|
178
|
+
cancelHoverNavigate,
|
|
179
|
+
navigate,
|
|
180
|
+
node
|
|
181
|
+
]);
|
|
182
|
+
React$1.useEffect(() => {
|
|
183
|
+
if (!hoverNavigates) cancelHoverNavigate();
|
|
184
|
+
}, [hoverNavigates, cancelHoverNavigate]);
|
|
185
|
+
React$1.useEffect(() => cancelHoverNavigate, [cancelHoverNavigate]);
|
|
186
|
+
if (isCascaderMoreNode(node)) {
|
|
187
|
+
const text = error ? labels.error : loading ? node.count ? labels.loadingMore : labels.loading : labels.loadMore;
|
|
188
|
+
const moreStyle = mode === "tree" && nodeDepth > 0 ? {
|
|
189
|
+
...style,
|
|
190
|
+
"--cascader-indent": `${nodeDepth * indent}px`
|
|
191
|
+
} : style;
|
|
192
|
+
const moreShared = {
|
|
193
|
+
"data-slot": "cascader-item",
|
|
194
|
+
"data-more": "",
|
|
195
|
+
"data-state": error ? "error" : loading ? "loading" : "idle",
|
|
196
|
+
"data-depth": nodeDepth,
|
|
197
|
+
style: moreStyle,
|
|
198
|
+
className: cn(ROW_SHELL_CLASS, ROW_FLUSH_CLASS, "text-muted-foreground justify-center gap-1.5 text-xs", !error && !loading && "hover:text-foreground", mode === "tree" && "ps-[calc(var(--cascader-row-inset,8px)_+_var(--cascader-indent,0px))]!", className)
|
|
199
|
+
};
|
|
200
|
+
const moreBody = children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
201
|
+
loading ? /* @__PURE__ */ jsx(LoaderCircleIcon, {
|
|
202
|
+
className: "size-3.5 animate-spin",
|
|
203
|
+
"aria-hidden": true
|
|
204
|
+
}) : null,
|
|
205
|
+
/* @__PURE__ */ jsx("span", { children: text }),
|
|
206
|
+
error ? /* @__PURE__ */ jsx("span", {
|
|
207
|
+
className: "text-foreground font-medium",
|
|
208
|
+
children: labels.retry
|
|
209
|
+
}) : null
|
|
210
|
+
] });
|
|
211
|
+
if (as === "button") {
|
|
212
|
+
const buttonProps = { ...props };
|
|
213
|
+
delete buttonProps["aria-setsize"];
|
|
214
|
+
delete buttonProps["aria-posinset"];
|
|
215
|
+
delete buttonProps["aria-level"];
|
|
216
|
+
return /* @__PURE__ */ jsx("button", {
|
|
217
|
+
type: "button",
|
|
218
|
+
...moreShared,
|
|
219
|
+
tabIndex: -1,
|
|
220
|
+
onClick: handleMoreClick,
|
|
221
|
+
...buttonProps,
|
|
222
|
+
children: moreBody
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return /* @__PURE__ */ jsx(Combobox.Item, {
|
|
226
|
+
...moreShared,
|
|
227
|
+
value: node,
|
|
228
|
+
...virtualized && index != null ? { index } : null,
|
|
229
|
+
...mode === "tree" ? {
|
|
230
|
+
role: "treeitem",
|
|
231
|
+
"aria-level": nodeDepth + 1
|
|
232
|
+
} : null,
|
|
233
|
+
onClick: handleMoreClick,
|
|
234
|
+
onMouseUp: veto,
|
|
235
|
+
...props,
|
|
236
|
+
children: moreBody
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const itemState = {
|
|
240
|
+
branch,
|
|
241
|
+
selected,
|
|
242
|
+
disabled,
|
|
243
|
+
depth: nodeDepth,
|
|
244
|
+
count,
|
|
245
|
+
path: showPath ? getCascaderPath(treeIndex, node.value).slice(0, -1) : []
|
|
246
|
+
};
|
|
247
|
+
const custom = renderItem?.(node, itemState);
|
|
248
|
+
const customLabel = renderLabel?.(node, itemState);
|
|
249
|
+
const srDetails = [
|
|
250
|
+
branch ? labels.itemCount(count) : null,
|
|
251
|
+
showsSelectedCount ? labels.selectedCount(selectedDescendants) : null,
|
|
252
|
+
branch && mode !== "tree" ? labels.branchAffordance : null,
|
|
253
|
+
as === "button" && selected ? labels.selectedState : null,
|
|
254
|
+
as === "button" && indeterminate ? labels.partiallySelectedState : null
|
|
255
|
+
].filter(Boolean).map((detail) => `, ${detail}`).join("");
|
|
256
|
+
const checkbox = /* @__PURE__ */ jsx("span", {
|
|
257
|
+
"data-slot": "cascader-item-checkbox",
|
|
258
|
+
className: "border-input in-data-[selected]:bg-primary in-data-[selected]:border-primary in-data-[selected]:text-primary-foreground in-data-[indeterminate]:bg-primary in-data-[indeterminate]:border-primary in-data-[indeterminate]:text-primary-foreground flex size-4 shrink-0 items-center justify-center rounded-[4px] border transition-colors",
|
|
259
|
+
children: indeterminate ? /* @__PURE__ */ jsx(MinusIcon, {
|
|
260
|
+
"data-slot": "cascader-item-dash",
|
|
261
|
+
className: cn(CHECKBOX_MARK_CLASS, "size-3")
|
|
262
|
+
}) : /* @__PURE__ */ jsx(CheckIcon, {
|
|
263
|
+
"data-slot": "cascader-item-tick",
|
|
264
|
+
className: cn(CHECKBOX_MARK_CLASS, "size-3 opacity-0 in-data-[selected]:opacity-100")
|
|
265
|
+
})
|
|
266
|
+
});
|
|
267
|
+
const indicator = /* @__PURE__ */ jsx(Fragment, { children: multiple ? checkbox : /* @__PURE__ */ jsx(CheckIcon, {
|
|
268
|
+
"data-slot": "cascader-item-check",
|
|
269
|
+
className: cn("pointer-events-none", INDICATOR_MARK_CLASS)
|
|
270
|
+
}) });
|
|
271
|
+
const body = custom ?? children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
272
|
+
mode === "tree" && branch ? /* @__PURE__ */ jsx("span", {
|
|
273
|
+
"data-slot": "cascader-item-expander",
|
|
274
|
+
"aria-hidden": "true",
|
|
275
|
+
"data-state": childrenError ? "error" : childrenLoading ? "loading" : "idle",
|
|
276
|
+
onClick: handleExpanderClick,
|
|
277
|
+
onMouseUp: handleExpanderMouseUp,
|
|
278
|
+
className: cn("-ms-0.5", childrenError ? "text-destructive" : "text-muted-foreground", AFFORDANCE_BOX_CLASS, AFFORDANCE_HOVER_CLASS, disabled && expanded && "pointer-events-auto", childrenLoading && "pointer-events-none"),
|
|
279
|
+
children: childrenLoading ? /* @__PURE__ */ jsx(Spinner, {
|
|
280
|
+
"aria-hidden": "true",
|
|
281
|
+
className: "size-4"
|
|
282
|
+
}) : childrenError ? /* @__PURE__ */ jsx(RotateCwIcon, { className: "size-4" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: cn("size-4 transition-transform", expanded ? "rotate-90" : "rtl:-scale-x-100") })
|
|
283
|
+
}) : mode === "tree" ? /* @__PURE__ */ jsx("span", {
|
|
284
|
+
"data-slot": "cascader-item-expander-spacer",
|
|
285
|
+
"aria-hidden": "true",
|
|
286
|
+
className: cn("-ms-0.5", AFFORDANCE_BOX_CLASS)
|
|
287
|
+
}) : null,
|
|
288
|
+
leadingCheckbox ? selectable ? checkbox : /* @__PURE__ */ jsx("span", {
|
|
289
|
+
"data-slot": "cascader-item-checkbox-spacer",
|
|
290
|
+
"aria-hidden": "true",
|
|
291
|
+
className: "size-4 shrink-0"
|
|
292
|
+
}) : null,
|
|
293
|
+
node.icon ? /* @__PURE__ */ jsx("span", {
|
|
294
|
+
"data-slot": "cascader-item-icon",
|
|
295
|
+
className: "text-muted-foreground flex shrink-0 items-center justify-center",
|
|
296
|
+
children: node.icon
|
|
297
|
+
}) : null,
|
|
298
|
+
/* @__PURE__ */ jsxs("span", {
|
|
299
|
+
className: "flex min-w-0 flex-1 flex-col items-start gap-0.5",
|
|
300
|
+
children: [customLabel ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
301
|
+
className: "w-full truncate text-start",
|
|
302
|
+
children: node.label
|
|
303
|
+
}), node.description ? /* @__PURE__ */ jsx("span", {
|
|
304
|
+
"data-slot": "cascader-item-description",
|
|
305
|
+
className: "text-muted-foreground w-full truncate text-start text-xs",
|
|
306
|
+
children: node.description
|
|
307
|
+
}) : null] }), showPath ? /* @__PURE__ */ jsx(CascaderItemPath, { node }) : null]
|
|
308
|
+
}),
|
|
309
|
+
srDetails ? /* @__PURE__ */ jsx("span", {
|
|
310
|
+
"data-slot": "cascader-item-details",
|
|
311
|
+
className: "sr-only",
|
|
312
|
+
children: srDetails
|
|
313
|
+
}) : null,
|
|
314
|
+
branch && mode !== "tree" ? /* @__PURE__ */ jsxs("span", {
|
|
315
|
+
"data-slot": "cascader-item-trailing",
|
|
316
|
+
"aria-hidden": "true",
|
|
317
|
+
className: "text-muted-foreground ms-auto flex shrink-0 items-center gap-1 text-xs tabular-nums",
|
|
318
|
+
children: [showCount ? /* @__PURE__ */ jsx("span", {
|
|
319
|
+
"data-slot": "cascader-item-count",
|
|
320
|
+
...showsSelectedCount ? { "data-selected-count": "" } : null,
|
|
321
|
+
className: cn(showsSelectedCount && "text-primary!"),
|
|
322
|
+
children: trailingCount
|
|
323
|
+
}) : null, /* @__PURE__ */ jsx("span", {
|
|
324
|
+
"data-slot": "cascader-item-chevron",
|
|
325
|
+
"data-state": childrenError ? "error" : childrenLoading ? "loading" : "idle",
|
|
326
|
+
onClick: selectable ? handleChevronClick : void 0,
|
|
327
|
+
onMouseUp: selectable ? handleChevronMouseUp : void 0,
|
|
328
|
+
onMouseDown: as === "button" ? handleButtonMouseDown : void 0,
|
|
329
|
+
className: cn("-me-0.5", AFFORDANCE_BOX_CLASS, selectable && AFFORDANCE_HOVER_CLASS, childrenError && "text-destructive", childrenLoading && "pointer-events-none"),
|
|
330
|
+
children: childrenLoading ? /* @__PURE__ */ jsx(Spinner, {
|
|
331
|
+
"aria-hidden": "true",
|
|
332
|
+
className: "size-4"
|
|
333
|
+
}) : childrenError ? /* @__PURE__ */ jsx(RotateCwIcon, { className: "size-4" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-4 rtl:-scale-x-100" })
|
|
334
|
+
})]
|
|
335
|
+
}) : null,
|
|
336
|
+
branch && mode === "tree" && showCount ? /* @__PURE__ */ jsx("span", {
|
|
337
|
+
"data-slot": "cascader-item-count",
|
|
338
|
+
...showsSelectedCount ? { "data-selected-count": "" } : null,
|
|
339
|
+
"aria-hidden": "true",
|
|
340
|
+
className: cn("text-muted-foreground ms-auto shrink-0 text-xs tabular-nums", showsSelectedCount && "text-primary!"),
|
|
341
|
+
children: trailingCount
|
|
342
|
+
}) : null,
|
|
343
|
+
selectable && !leadingCheckbox && showsIndicator ? as === "button" ? (multiple || selected) && /* @__PURE__ */ jsx("span", {
|
|
344
|
+
"data-slot": "cascader-item-indicator",
|
|
345
|
+
className: INDICATOR_CLASS,
|
|
346
|
+
children: indicator
|
|
347
|
+
}) : /* @__PURE__ */ jsx(Combobox.ItemIndicator, {
|
|
348
|
+
keepMounted: multiple || indeterminate,
|
|
349
|
+
render: /* @__PURE__ */ jsx("span", {
|
|
350
|
+
"data-slot": "cascader-item-indicator",
|
|
351
|
+
className: INDICATOR_CLASS
|
|
352
|
+
}),
|
|
353
|
+
children: indicator
|
|
354
|
+
}) : null
|
|
355
|
+
] });
|
|
356
|
+
const rowStyle = mode === "tree" && nodeDepth > 0 ? {
|
|
357
|
+
...style,
|
|
358
|
+
"--cascader-indent": `${nodeDepth * indent}px`
|
|
359
|
+
} : style;
|
|
360
|
+
const shared = {
|
|
361
|
+
"data-slot": "cascader-item",
|
|
362
|
+
"data-branch": branch || void 0,
|
|
363
|
+
"data-selected": selected || void 0,
|
|
364
|
+
"data-indeterminate": indeterminate || void 0,
|
|
365
|
+
"data-depth": nodeDepth,
|
|
366
|
+
"data-expanded": expanded || void 0,
|
|
367
|
+
style: rowStyle,
|
|
368
|
+
className: cn(ROW_CLASS, leadingCheckbox || !showsIndicator || branch && !branchesSelectable ? ROW_FLUSH_CLASS : ROW_GUTTER_CLASS, mode === "tree" && "ps-[calc(var(--cascader-row-inset,8px)_+_var(--cascader-indent,0px))]!", className)
|
|
369
|
+
};
|
|
370
|
+
if (as === "button") {
|
|
371
|
+
const buttonProps = { ...props };
|
|
372
|
+
delete buttonProps["aria-setsize"];
|
|
373
|
+
delete buttonProps["aria-posinset"];
|
|
374
|
+
delete buttonProps["aria-level"];
|
|
375
|
+
return /* @__PURE__ */ jsx("button", {
|
|
376
|
+
type: "button",
|
|
377
|
+
...shared,
|
|
378
|
+
tabIndex: -1,
|
|
379
|
+
disabled,
|
|
380
|
+
"aria-disabled": disabled || void 0,
|
|
381
|
+
onClick: handleButtonClick,
|
|
382
|
+
onMouseDown: handleButtonMouseDown,
|
|
383
|
+
...buttonProps,
|
|
384
|
+
children: body
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
return /* @__PURE__ */ jsx(Combobox.Item, {
|
|
388
|
+
...shared,
|
|
389
|
+
value: node,
|
|
390
|
+
...virtualized && index != null ? { index } : null,
|
|
391
|
+
disabled,
|
|
392
|
+
...mode === "tree" ? {
|
|
393
|
+
role: "treeitem",
|
|
394
|
+
"aria-level": nodeDepth + 1,
|
|
395
|
+
...branch ? { "aria-expanded": !!expanded } : null
|
|
396
|
+
} : branch ? { "aria-haspopup": "listbox" } : null,
|
|
397
|
+
...indeterminate ? { "aria-checked": "mixed" } : null,
|
|
398
|
+
...hoverNavigates ? {
|
|
399
|
+
onPointerEnter: handleHoverPointerEnter,
|
|
400
|
+
onPointerLeave: cancelHoverNavigate
|
|
401
|
+
} : null,
|
|
402
|
+
onClick: handleClick,
|
|
403
|
+
onMouseUp: handleMouseUp,
|
|
404
|
+
...props,
|
|
405
|
+
children: body
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
/** Ancestor chain under a deep or global hit: "Name" alone is ambiguous. */
|
|
409
|
+
function CascaderItemPath({ node }) {
|
|
410
|
+
const { index, labels } = useCascaderActions();
|
|
411
|
+
const ancestors = getCascaderPath(index, node.value).slice(0, -1);
|
|
412
|
+
if (!ancestors.length) return null;
|
|
413
|
+
return /* @__PURE__ */ jsx("span", {
|
|
414
|
+
"data-slot": "cascader-item-path",
|
|
415
|
+
className: "text-muted-foreground flex w-full items-center gap-0.5 truncate text-start text-xs",
|
|
416
|
+
children: ancestors.map((ancestor, i) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [i > 0 ? /* @__PURE__ */ jsx("span", {
|
|
417
|
+
"aria-hidden": "true",
|
|
418
|
+
children: labels.pathSeparator
|
|
419
|
+
}) : null, /* @__PURE__ */ jsx("span", {
|
|
420
|
+
className: "truncate",
|
|
421
|
+
children: ancestor.label
|
|
422
|
+
})] }, ancestor.value))
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/** Every load-derived prop a row needs. A PAGING row reports the level it
|
|
426
|
+
* belongs to, a BRANCH row the level it OWNS; mutually exclusive, so one call
|
|
427
|
+
* answers both. Exported because a memoised row cannot read them itself. */
|
|
428
|
+
function getCascaderMoreProps(node, loadStates) {
|
|
429
|
+
const parent = getCascaderMoreParent(node);
|
|
430
|
+
if (parent == null) {
|
|
431
|
+
const own = loadStates.get(node.value);
|
|
432
|
+
return {
|
|
433
|
+
loading: false,
|
|
434
|
+
error: false,
|
|
435
|
+
childrenLoading: !!own?.loading,
|
|
436
|
+
childrenError: !!own?.error
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const state = loadStates.get(parent);
|
|
440
|
+
return {
|
|
441
|
+
loading: !!state?.loading,
|
|
442
|
+
error: !!state?.error,
|
|
443
|
+
childrenLoading: false,
|
|
444
|
+
childrenError: false
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/** Whatever the active view is: the current level, the deepest column, or the
|
|
448
|
+
* flattened tree rows. Lives here so `cascader.tsx` never imports a row
|
|
449
|
+
* component, which would cycle with the context. `index` is deliberately not
|
|
450
|
+
* passed; see `CascaderItemProps`. */
|
|
451
|
+
function CascaderItems({ children }) {
|
|
452
|
+
const { mode, isBranch, isSelectable, isSelected, isIndeterminate } = useCascaderActions();
|
|
453
|
+
const { renderedItems, deepResults, treeRows, loadStates } = useCascaderState();
|
|
454
|
+
const showPath = deepResults !== null;
|
|
455
|
+
if (mode === "tree") return /* @__PURE__ */ jsx(Fragment, { children: treeRows.map((row, i) => children ? /* @__PURE__ */ jsx(React$1.Fragment, { children: children(row.node, i) }, row.node.value) : /* @__PURE__ */ jsx(CascaderItem, {
|
|
456
|
+
node: row.node,
|
|
457
|
+
depth: row.depth,
|
|
458
|
+
expanded: row.expanded,
|
|
459
|
+
branch: row.branch,
|
|
460
|
+
selectable: isSelectable(row.node),
|
|
461
|
+
selected: isSelected(row.node),
|
|
462
|
+
indeterminate: isIndeterminate(row.node),
|
|
463
|
+
...getCascaderMoreProps(row.node, loadStates),
|
|
464
|
+
"aria-setsize": row.setSize,
|
|
465
|
+
"aria-posinset": row.posInSet
|
|
466
|
+
}, row.node.value)) });
|
|
467
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderedItems.map((node, i) => children ? /* @__PURE__ */ jsx(React$1.Fragment, { children: children(node, i) }, node.value) : /* @__PURE__ */ jsx(CascaderItem, {
|
|
468
|
+
node,
|
|
469
|
+
showPath,
|
|
470
|
+
branch: isBranch(node),
|
|
471
|
+
selectable: isSelectable(node),
|
|
472
|
+
selected: isSelected(node),
|
|
473
|
+
indeterminate: isIndeterminate(node),
|
|
474
|
+
...getCascaderMoreProps(node, loadStates),
|
|
475
|
+
"aria-setsize": renderedItems.length,
|
|
476
|
+
"aria-posinset": i + 1
|
|
477
|
+
}, node.value)) });
|
|
478
|
+
}
|
|
479
|
+
const CascaderGroupContext = React$1.createContext(false);
|
|
480
|
+
const CASCADER_LABEL_CLASS = `text-muted-foreground text-xs py-1.5 ${CASCADER_ACTION_INSET_CLASS} block truncate`;
|
|
481
|
+
const CASCADER_SEPARATOR_CLASS = "h-px bg-border my-1 mx-[calc(var(--cascader-list-pad,4px)*-1)]! shrink-0";
|
|
482
|
+
/** A run of related rows, named by the `CascaderLabel` inside it: the group is
|
|
483
|
+
* `role="group"` and Base UI points its `aria-labelledby` at the label, where a
|
|
484
|
+
* heading alone inside a listbox is dropped from the accessibility tree.
|
|
485
|
+
* Grouping is composition, not data: `CascaderItems` renders one flat run per
|
|
486
|
+
* level, so its indices line up with `listRef`. */
|
|
487
|
+
function CascaderGroup({ className, ...props }) {
|
|
488
|
+
return /* @__PURE__ */ jsx(CascaderGroupContext.Provider, {
|
|
489
|
+
value: true,
|
|
490
|
+
children: /* @__PURE__ */ jsx(Combobox.Group, {
|
|
491
|
+
"data-slot": "cascader-group",
|
|
492
|
+
className: cn("flex flex-col", className),
|
|
493
|
+
...props
|
|
494
|
+
})
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
/** Inside a group: the real thing, with the id association Base UI wires up. */
|
|
498
|
+
function CascaderGroupLabel({ className, ...props }) {
|
|
499
|
+
return /* @__PURE__ */ jsx(Combobox.GroupLabel, {
|
|
500
|
+
"data-slot": "cascader-label",
|
|
501
|
+
className: cn(CASCADER_LABEL_CLASS, className),
|
|
502
|
+
...props
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
function CascaderPlainLabel({ className, ...props }) {
|
|
506
|
+
const defaultProps = {
|
|
507
|
+
"data-slot": "cascader-label",
|
|
508
|
+
className: cn(CASCADER_LABEL_CLASS, className)
|
|
509
|
+
};
|
|
510
|
+
return useRender({
|
|
511
|
+
defaultTagName: "div",
|
|
512
|
+
render: props.render,
|
|
513
|
+
props: mergeProps(defaultProps, props)
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/** A heading. Inside `CascaderGroup` it is the group's accessible name; outside
|
|
517
|
+
* one (the footer flyout is a menu, not a listbox) it is decoration only. */
|
|
518
|
+
function CascaderLabel(props) {
|
|
519
|
+
return React$1.useContext(CascaderGroupContext) ? /* @__PURE__ */ jsx(CascaderGroupLabel, { ...props }) : /* @__PURE__ */ jsx(CascaderPlainLabel, { ...props });
|
|
520
|
+
}
|
|
521
|
+
/** Decorative on purpose: Base UI's separator is `role="separator"`, which a
|
|
522
|
+
* `listbox` may not own, and a run that needs separating for a screen reader
|
|
523
|
+
* needs a `CascaderGroup`, not a line. A consumer can pass the role back. */
|
|
524
|
+
function CascaderSeparator({ className, ...props }) {
|
|
525
|
+
return /* @__PURE__ */ jsx(Combobox.Separator, {
|
|
526
|
+
"data-slot": "cascader-separator",
|
|
527
|
+
role: "presentation",
|
|
528
|
+
"aria-hidden": "true",
|
|
529
|
+
className: cn(CASCADER_SEPARATOR_CLASS, className),
|
|
530
|
+
...props
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
export { CASCADER_ACTION_CLASS, CascaderGroup, CascaderItem, CascaderItems, CascaderLabel, CascaderSeparator, getCascaderMoreProps };
|