@godxjp/ui 21.0.0 → 23.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/app/app-provider.js +2 -1
- package/dist/components/data-display/badge.d.ts +28 -1
- package/dist/components/data-display/badge.js +2 -0
- package/dist/components/data-display/data-table.d.ts +18 -2
- package/dist/components/data-display/data-table.js +4 -0
- package/dist/components/data-display/index.d.ts +1 -1
- package/dist/components/data-display/popover.d.ts +19 -1
- package/dist/components/data-display/popover.js +9 -1
- package/dist/components/data-display/progress.d.ts +37 -0
- package/dist/components/data-display/progress.js +57 -5
- package/dist/components/data-display/scroll-area.d.ts +24 -4
- package/dist/components/data-display/scroll-area.js +34 -72
- package/dist/components/data-entry/cascader.js +8 -6
- package/dist/components/data-entry/checkbox.js +2 -0
- package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
- package/dist/components/data-entry/choice-hit-target.js +12 -0
- package/dist/components/data-entry/control-surface.d.ts +8 -0
- package/dist/components/data-entry/control-surface.js +10 -1
- package/dist/components/data-entry/date-picker.d.ts +10 -3
- package/dist/components/data-entry/date-picker.js +503 -238
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +7 -3
- package/dist/components/data-entry/form.js +10 -1
- package/dist/components/data-entry/index.d.ts +0 -6
- package/dist/components/data-entry/index.js +0 -6
- package/dist/components/data-entry/label.d.ts +14 -1
- package/dist/components/data-entry/label.js +6 -1
- package/dist/components/data-entry/radio.d.ts +27 -6
- package/dist/components/data-entry/radio.js +89 -43
- package/dist/components/data-entry/search-select.js +226 -89
- package/dist/components/data-entry/select.d.ts +76 -14
- package/dist/components/data-entry/select.js +497 -163
- package/dist/components/data-entry/slider.d.ts +3 -11
- package/dist/components/data-entry/slider.js +582 -93
- package/dist/components/data-entry/switch.d.ts +6 -3
- package/dist/components/data-entry/switch.js +36 -11
- package/dist/components/layout/mobile-shell.d.ts +1 -1
- package/dist/components/layout/mobile-shell.js +2 -0
- package/dist/components/navigation/dropdown-menu.d.ts +11 -3
- package/dist/components/navigation/dropdown-menu.js +190 -15
- package/dist/components/navigation/index.d.ts +0 -3
- package/dist/components/navigation/index.js +0 -78
- package/dist/components/navigation/steps.d.ts +1 -1
- package/dist/components/navigation/steps.js +3 -1
- package/dist/components/navigation/tabs-scroll.d.ts +27 -0
- package/dist/components/navigation/tabs-scroll.js +52 -1
- package/dist/components/navigation/tabs.d.ts +2 -2
- package/dist/components/navigation/tabs.js +88 -23
- package/dist/components/ui/index.d.ts +0 -4
- package/dist/components/ui/index.js +0 -4
- package/dist/components/ui/input-otp.d.ts +1 -1
- package/dist/components/ui/segmented.d.ts +15 -1
- package/dist/components/ui/segmented.js +15 -6
- package/dist/components/ui/tag-input.js +3 -5
- package/dist/i18n/messages/en.json +3 -2
- package/dist/i18n/messages/ja.json +3 -2
- package/dist/i18n/messages/vi.json +3 -2
- package/dist/lib/datetime/picker-format.d.ts +18 -0
- package/dist/lib/datetime/picker-format.js +35 -1
- package/dist/lib/field-a11y.d.ts +12 -0
- package/dist/lib/field-a11y.js +14 -1
- package/dist/lib/select-options.d.ts +21 -0
- package/dist/lib/select-options.js +46 -0
- package/dist/props/components/data-display.prop.d.ts +16 -6
- package/dist/props/components/data-entry.prop.d.ts +503 -129
- package/dist/props/components/index.d.ts +2 -2
- package/dist/props/components/layout.prop.d.ts +12 -1
- package/dist/props/components/navigation.prop.d.ts +93 -1
- package/dist/props/registry.d.ts +79 -16
- package/dist/props/registry.js +85 -15
- package/dist/props/vocabulary/data.prop.d.ts +12 -0
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/layout.prop.d.ts +11 -0
- package/dist/styles/badge-layout.css +4 -0
- package/dist/styles/card-layout.css +6 -6
- package/dist/styles/control.css +220 -29
- package/dist/styles/data-display-layout.css +94 -23
- package/dist/styles/focus-ring.css +6 -4
- package/dist/styles/fonts.css +88 -4
- package/dist/styles/navigation-layout.css +36 -236
- package/dist/styles/shell-layout.css +149 -139
- package/dist/styles/table-layout.css +41 -3
- package/dist/tokens/components/control.css +4 -0
- package/dist/tokens/components/data-display.css +9 -5
- package/dist/tokens/components/navigation.css +9 -5
- package/dist/tokens/components/scroll-area.css +7 -0
- package/dist/tokens/components/segmented.css +0 -2
- package/dist/tokens/components/shell.css +2 -0
- package/dist/tokens/components/table.css +3 -0
- package/dist/tokens/foundation.css +7 -0
- package/docs/COMPONENTS.md +14 -14
- package/docs/CONSUMER-RULES.md +24 -1
- package/docs/CUSTOMER-THEMING.md +7 -1
- package/docs/FRAME-COVERAGE-REPORT.md +5 -11
- package/docs/TOKENS.md +65 -0
- package/docs/data-display/badge.tsx +37 -0
- package/docs/data-display/data-table/index.tsx +45 -1
- package/docs/data-display/popover.tsx +43 -5
- package/docs/data-display/progress.tsx +28 -0
- package/docs/data-display/scroll-area.tsx +51 -45
- package/docs/data-entry/calendar.tsx +2 -2
- package/docs/data-entry/date-picker.tsx +144 -5
- package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
- package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
- package/docs/data-entry/form.tsx +4 -4
- package/docs/data-entry/input-otp.tsx +5 -2
- package/docs/data-entry/number-input.tsx +31 -0
- package/docs/data-entry/segmented.tsx +143 -2
- package/docs/data-entry/select-matrix.tsx +1 -2
- package/docs/data-entry/select.tsx +108 -3
- package/docs/data-entry/slider.tsx +125 -47
- package/docs/feedback/dialog.tsx +1 -2
- package/docs/feedback/sheet.tsx +1 -2
- package/docs/layout/app-shell-states.tsx +143 -0
- package/docs/layout/mobile-shell.tsx +5 -0
- package/docs/navigation/dropdown-menu.tsx +75 -4
- package/docs/navigation/tabs.tsx +149 -0
- package/docs/roadmap/antd-parity.md +35 -34
- package/docs/roadmap/parity-audit-data-entry.md +196 -172
- package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
- package/docs/roadmap/parity-backlog.md +8 -10
- package/docs/showcase/acme-portal.tsx +7 -0
- package/package.json +28 -64
- package/scripts/ui-audit.mjs +18 -5
- package/scripts/visual-audit-rules.mjs +0 -7
- package/scripts/visual-audit.mjs +6 -27
- package/dist/components/data-entry/date-range-picker.d.ts +0 -11
- package/dist/components/data-entry/date-range-picker.js +0 -349
- package/dist/components/data-entry/month-picker.d.ts +0 -10
- package/dist/components/data-entry/month-picker.js +0 -241
- package/dist/components/data-entry/month-range-picker.d.ts +0 -10
- package/dist/components/data-entry/month-range-picker.js +0 -301
- package/dist/components/navigation/context-menu.d.ts +0 -21
- package/dist/components/navigation/context-menu.js +0 -149
- package/dist/components/navigation/menubar.d.ts +0 -21
- package/dist/components/navigation/menubar.js +0 -136
- package/dist/components/navigation/navigation-menu.d.ts +0 -10
- package/dist/components/navigation/navigation-menu.js +0 -91
- package/dist/components/ui/context-menu.d.ts +0 -1
- package/dist/components/ui/context-menu.js +0 -2
- package/dist/components/ui/date-range-picker.d.ts +0 -1
- package/dist/components/ui/date-range-picker.js +0 -2
- package/dist/components/ui/menubar.d.ts +0 -1
- package/dist/components/ui/menubar.js +0 -2
- package/dist/components/ui/navigation-menu.d.ts +0 -1
- package/dist/components/ui/navigation-menu.js +0 -2
- package/docs/FRAME-A11Y-CI.md +0 -349
- package/docs/data-entry/date-range-picker.tsx +0 -175
- package/docs/data-entry/month-picker.tsx +0 -101
- package/docs/data-entry/month-range-picker.tsx +0 -115
- package/docs/navigation/context-menu.tsx +0 -128
- package/docs/navigation/menubar.tsx +0 -141
- package/docs/navigation/navigation-menu.tsx +0 -158
|
@@ -10,15 +10,24 @@ import {
|
|
|
10
10
|
applyMaxTagCount,
|
|
11
11
|
controlSurfaceAttrs,
|
|
12
12
|
resolveAllowClear,
|
|
13
|
-
resolveAriaInvalid
|
|
13
|
+
resolveAriaInvalid,
|
|
14
|
+
splitByTokenSeparators
|
|
14
15
|
} from "./control-surface.js";
|
|
16
|
+
import { normalizeSelectOptions } from "../../lib/select-options.js";
|
|
15
17
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
16
18
|
import { Command, CommandGroup } from "./command.js";
|
|
17
19
|
import { Input } from "./input.js";
|
|
18
20
|
const DEBOUNCE_MS = 250;
|
|
21
|
+
function PopupRender({
|
|
22
|
+
render,
|
|
23
|
+
children
|
|
24
|
+
}) {
|
|
25
|
+
return /* @__PURE__ */ jsx(Fragment, { children: render ? render(children) : children });
|
|
26
|
+
}
|
|
19
27
|
function SearchSelect(props) {
|
|
20
28
|
const {
|
|
21
|
-
options:
|
|
29
|
+
options: rawOptions,
|
|
30
|
+
fieldNames,
|
|
22
31
|
loadOptions,
|
|
23
32
|
renderOption,
|
|
24
33
|
labelRender,
|
|
@@ -36,6 +45,7 @@ function SearchSelect(props) {
|
|
|
36
45
|
size,
|
|
37
46
|
status,
|
|
38
47
|
variant,
|
|
48
|
+
width = "full",
|
|
39
49
|
loading: loadingProp = false,
|
|
40
50
|
open: openProp,
|
|
41
51
|
defaultOpen = false,
|
|
@@ -43,11 +53,18 @@ function SearchSelect(props) {
|
|
|
43
53
|
search: searchProp,
|
|
44
54
|
onSearchChange,
|
|
45
55
|
filterOption,
|
|
56
|
+
optionFilterProp,
|
|
46
57
|
filterSort,
|
|
47
58
|
autoClearSearchValue = true,
|
|
48
59
|
optionRender,
|
|
49
60
|
menuItemSelectedIcon,
|
|
50
61
|
notFoundContent,
|
|
62
|
+
prefix,
|
|
63
|
+
suffixIcon,
|
|
64
|
+
placement,
|
|
65
|
+
popupRender,
|
|
66
|
+
listHeight,
|
|
67
|
+
onPopupScroll,
|
|
51
68
|
popupMatchSelectWidth = true,
|
|
52
69
|
allowClear,
|
|
53
70
|
onClear,
|
|
@@ -65,7 +82,15 @@ function SearchSelect(props) {
|
|
|
65
82
|
"aria-invalid": ariaInvalid,
|
|
66
83
|
"aria-required": ariaRequired
|
|
67
84
|
} = props;
|
|
68
|
-
const
|
|
85
|
+
const staticOptions = React.useMemo(
|
|
86
|
+
() => normalizeSelectOptions(rawOptions, fieldNames),
|
|
87
|
+
[rawOptions, fieldNames]
|
|
88
|
+
);
|
|
89
|
+
const multiple = props.mode === "multiple" || props.mode === "tags";
|
|
90
|
+
const tagsMode = props.mode === "tags";
|
|
91
|
+
const maxTagTextLength = props.mode !== void 0 ? props.maxTagTextLength : void 0;
|
|
92
|
+
const tokenSeparators = props.mode !== void 0 ? props.tokenSeparators : void 0;
|
|
93
|
+
const tagRender = props.mode !== void 0 ? props.tagRender : void 0;
|
|
69
94
|
const { t } = useTranslation();
|
|
70
95
|
const nameFallback = useFieldNameFallback({
|
|
71
96
|
"aria-label": ariaLabel,
|
|
@@ -110,7 +135,7 @@ function SearchSelect(props) {
|
|
|
110
135
|
{}
|
|
111
136
|
);
|
|
112
137
|
const [internalValue, setInternalValue] = React.useState(
|
|
113
|
-
props.defaultValue ?? (props.mode === "multiple" ? [] : "")
|
|
138
|
+
props.defaultValue ?? (props.mode === "multiple" || props.mode === "tags" ? [] : "")
|
|
114
139
|
);
|
|
115
140
|
const isControlled = props.value !== void 0;
|
|
116
141
|
const rawValue = isControlled ? props.value : internalValue;
|
|
@@ -126,14 +151,20 @@ function SearchSelect(props) {
|
|
|
126
151
|
() => loadOptions ?? (async ({ query: search }) => {
|
|
127
152
|
const needle = search.trim();
|
|
128
153
|
const list = staticOptions ?? [];
|
|
129
|
-
const matches = (option) =>
|
|
154
|
+
const matches = (option) => {
|
|
155
|
+
if (filterOption) return filterOption(option, needle);
|
|
156
|
+
const haystack = optionFilterProp ? [String(option[optionFilterProp] ?? "")] : [option.label, option.value];
|
|
157
|
+
return haystack.some(
|
|
158
|
+
(field) => String(field).toLowerCase().includes(needle.toLowerCase())
|
|
159
|
+
);
|
|
160
|
+
};
|
|
130
161
|
const kept = needle ? list.filter(matches) : list;
|
|
131
162
|
return {
|
|
132
163
|
options: filterSort ? [...kept].sort((a, b) => filterSort(a, b, { searchValue: needle })) : kept,
|
|
133
164
|
hasMore: false
|
|
134
165
|
};
|
|
135
166
|
}),
|
|
136
|
-
[loadOptions, staticOptions, filterOption, filterSort]
|
|
167
|
+
[loadOptions, staticOptions, filterOption, optionFilterProp, filterSort]
|
|
137
168
|
);
|
|
138
169
|
React.useEffect(() => {
|
|
139
170
|
const handle = window.setTimeout(() => setDebouncedQuery(query.trim()), DEBOUNCE_MS);
|
|
@@ -170,10 +201,24 @@ function SearchSelect(props) {
|
|
|
170
201
|
setActiveIndex(0);
|
|
171
202
|
void fetchPage(1, debouncedQuery, false);
|
|
172
203
|
}, [open, debouncedQuery, fetchPage]);
|
|
204
|
+
const displayOptions = React.useMemo(() => {
|
|
205
|
+
if (!tagsMode) return loaded;
|
|
206
|
+
const known = new Set(loaded.map((option) => option.value));
|
|
207
|
+
const extras = [];
|
|
208
|
+
for (const entry of values) {
|
|
209
|
+
if (known.has(entry)) continue;
|
|
210
|
+
known.add(entry);
|
|
211
|
+
extras.push(pickedOptions[entry] ?? { value: entry, label: entry });
|
|
212
|
+
}
|
|
213
|
+
const needle = query.trim();
|
|
214
|
+
const exists = known.has(needle) || loaded.some((option) => option.label === needle);
|
|
215
|
+
const create = needle && !exists ? [{ value: needle, label: needle }] : [];
|
|
216
|
+
return create.length || extras.length ? [...create, ...extras, ...loaded] : loaded;
|
|
217
|
+
}, [tagsMode, loaded, values, query, pickedOptions]);
|
|
173
218
|
const grouped = React.useMemo(() => {
|
|
174
219
|
const order = [];
|
|
175
220
|
const buckets = /* @__PURE__ */ new Map();
|
|
176
|
-
for (const option of
|
|
221
|
+
for (const option of displayOptions) {
|
|
177
222
|
const key = option.group ?? "";
|
|
178
223
|
if (!buckets.has(key)) {
|
|
179
224
|
buckets.set(key, []);
|
|
@@ -186,7 +231,7 @@ function SearchSelect(props) {
|
|
|
186
231
|
heading: key || void 0,
|
|
187
232
|
items: (buckets.get(key) ?? []).map((option) => ({ option, index: flatIndex++ }))
|
|
188
233
|
}));
|
|
189
|
-
}, [
|
|
234
|
+
}, [displayOptions]);
|
|
190
235
|
const flatOrdered = React.useMemo(
|
|
191
236
|
() => grouped.flatMap((group) => group.items.map((entry) => entry.option)),
|
|
192
237
|
[grouped]
|
|
@@ -196,9 +241,10 @@ function SearchSelect(props) {
|
|
|
196
241
|
const selectedOption = value ? optionFor(value) : null;
|
|
197
242
|
const currentLabel = value ? selectedOption?.label ?? selectedLabel ?? value : resolvedPlaceholder;
|
|
198
243
|
const currentIcon = value ? selectedOption?.icon ?? selectedIcon : null;
|
|
244
|
+
const chipLabel = (label) => maxTagTextLength !== void 0 && maxTagTextLength >= 0 && label.length > maxTagTextLength ? `${label.slice(0, maxTagTextLength)}\u2026` : label;
|
|
199
245
|
const selectedChips = multiple ? values.map((entry) => ({
|
|
200
246
|
value: entry,
|
|
201
|
-
label: optionFor(entry)?.label ?? entry
|
|
247
|
+
label: chipLabel(optionFor(entry)?.label ?? entry)
|
|
202
248
|
})) : [];
|
|
203
249
|
const {
|
|
204
250
|
visible: visibleChips,
|
|
@@ -206,15 +252,15 @@ function SearchSelect(props) {
|
|
|
206
252
|
overflow: chipOverflow
|
|
207
253
|
} = applyMaxTagCount(
|
|
208
254
|
selectedChips,
|
|
209
|
-
props.mode
|
|
210
|
-
props.mode
|
|
255
|
+
props.mode !== void 0 ? props.maxTagCount : void 0,
|
|
256
|
+
props.mode !== void 0 ? props.maxTagPlaceholder : void 0
|
|
211
257
|
);
|
|
212
|
-
const maxCount = props.mode
|
|
258
|
+
const maxCount = props.mode !== void 0 ? props.maxCount : void 0;
|
|
213
259
|
const atMaxCount = maxCount !== void 0 && values.length >= maxCount;
|
|
214
260
|
const remember = (option) => setPickedOptions((prev) => ({ ...prev, [option.value]: option }));
|
|
215
261
|
const select = (option) => {
|
|
216
262
|
if (option.disabled) return;
|
|
217
|
-
if (props.mode
|
|
263
|
+
if (props.mode !== void 0) {
|
|
218
264
|
const already = values.includes(option.value);
|
|
219
265
|
if (!already && atMaxCount) return;
|
|
220
266
|
const next = already ? values.filter((entry) => entry !== option.value) : [...values, option.value];
|
|
@@ -236,8 +282,35 @@ function SearchSelect(props) {
|
|
|
236
282
|
if (autoClearSearchValue) setQuery("");
|
|
237
283
|
setOpen(false);
|
|
238
284
|
};
|
|
285
|
+
const selectTokens = (text) => {
|
|
286
|
+
if (props.mode === void 0) return;
|
|
287
|
+
const next = [...values];
|
|
288
|
+
const picked = [];
|
|
289
|
+
for (const raw of splitByTokenSeparators(text, tokenSeparators ?? [])) {
|
|
290
|
+
const token = raw.trim();
|
|
291
|
+
if (!token) continue;
|
|
292
|
+
const match = flatOrdered.find((option2) => option2.value === token || option2.label === token);
|
|
293
|
+
const option = match ?? (tagsMode ? { value: token, label: token } : void 0);
|
|
294
|
+
if (!option || option.disabled) continue;
|
|
295
|
+
if (next.includes(option.value)) continue;
|
|
296
|
+
if (maxCount !== void 0 && next.length >= maxCount) break;
|
|
297
|
+
next.push(option.value);
|
|
298
|
+
picked.push(option);
|
|
299
|
+
}
|
|
300
|
+
if (picked.length === 0) return;
|
|
301
|
+
for (const option of picked) remember(option);
|
|
302
|
+
if (!isControlled) setInternalValue(next);
|
|
303
|
+
props.onValueChange?.(
|
|
304
|
+
next,
|
|
305
|
+
next.map((entry) => picked.find((option) => option.value === entry) ?? optionFor(entry)).filter((entry) => entry != null)
|
|
306
|
+
);
|
|
307
|
+
for (const option of picked) props.onSelect?.(option.value, option);
|
|
308
|
+
};
|
|
309
|
+
const deselect = (chipValue) => {
|
|
310
|
+
select(optionFor(chipValue) ?? { value: chipValue, label: chipValue });
|
|
311
|
+
};
|
|
239
312
|
const clear = () => {
|
|
240
|
-
if (props.mode
|
|
313
|
+
if (props.mode !== void 0) {
|
|
241
314
|
if (!isControlled) setInternalValue([]);
|
|
242
315
|
props.onValueChange?.([], []);
|
|
243
316
|
} else {
|
|
@@ -301,94 +374,136 @@ function SearchSelect(props) {
|
|
|
301
374
|
);
|
|
302
375
|
const showClear = clearControl.enabled && hasSelection && !disabled && !readOnly && !loadingProp;
|
|
303
376
|
const surface = controlSurfaceAttrs({ variant, status, size });
|
|
377
|
+
const triggerProps = {
|
|
378
|
+
id,
|
|
379
|
+
role: "combobox",
|
|
380
|
+
...surface,
|
|
381
|
+
"aria-expanded": open,
|
|
382
|
+
"aria-controls": open ? listId : void 0,
|
|
383
|
+
"aria-label": triggerAriaLabel,
|
|
384
|
+
"aria-labelledby": triggerAriaLabelledby,
|
|
385
|
+
"aria-describedby": ariaDescribedby,
|
|
386
|
+
"aria-errormessage": ariaErrorMessage,
|
|
387
|
+
"aria-invalid": resolveAriaInvalid(ariaInvalid, status),
|
|
388
|
+
"aria-required": ariaRequired,
|
|
389
|
+
"aria-readonly": readOnly || void 0,
|
|
390
|
+
"aria-busy": loadingProp || void 0,
|
|
391
|
+
"data-testid": dataTestId,
|
|
392
|
+
"data-field": resolvedField,
|
|
393
|
+
// label. `""` (nothing selected) is omitted rather than rendered as an empty attribute.
|
|
394
|
+
"data-value": (multiple ? values.join(",") : value) || void 0,
|
|
395
|
+
"data-mode": multiple ? "multiple" : void 0,
|
|
396
|
+
"data-width": width,
|
|
397
|
+
className: cn(
|
|
398
|
+
controlSurfaceTriggerClass,
|
|
399
|
+
"justify-start",
|
|
400
|
+
// `bounded` deliberately emits NO width utility — its width is owned by the
|
|
401
|
+
// `[data-width="bounded"]` rule in control.css, and a utility here would win the layer
|
|
402
|
+
// order and make that token dead. Same reasoning as SelectTrigger.
|
|
403
|
+
width === "auto" && "w-auto",
|
|
404
|
+
width === "full" && "w-full",
|
|
405
|
+
// Reserve trailing room for the single clear-or-chevron overlay rendered below.
|
|
406
|
+
"ui-control-trigger-affixed"
|
|
407
|
+
)
|
|
408
|
+
};
|
|
409
|
+
const chipsRemovable = !disabled && !readOnly;
|
|
410
|
+
const triggerContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
411
|
+
prefix === void 0 || prefix === null ? null : /* @__PURE__ */ jsx("span", { "data-slot": "select-prefix", className: "ui-select-prefix", children: prefix }),
|
|
412
|
+
/* @__PURE__ */ jsx(
|
|
413
|
+
"span",
|
|
414
|
+
{
|
|
415
|
+
className: cn(
|
|
416
|
+
"ui-search-select-option-body text-start",
|
|
417
|
+
!hasSelection && "text-muted-foreground"
|
|
418
|
+
),
|
|
419
|
+
children: multiple ? hasSelection ? /* @__PURE__ */ jsxs("span", { className: "ui-search-select-values", "data-slot": "search-select-values", children: [
|
|
420
|
+
visibleChips.map((chip, index) => {
|
|
421
|
+
const close = () => deselect(chip.value);
|
|
422
|
+
return /* @__PURE__ */ jsx(
|
|
423
|
+
"span",
|
|
424
|
+
{
|
|
425
|
+
className: tagRender ? void 0 : "ui-search-select-value",
|
|
426
|
+
"data-slot": "search-select-value",
|
|
427
|
+
children: tagRender ? tagRender({
|
|
428
|
+
value: chip.value,
|
|
429
|
+
label: chip.label,
|
|
430
|
+
index,
|
|
431
|
+
disabled: Boolean(disabled) || Boolean(readOnly),
|
|
432
|
+
onClose: close
|
|
433
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
434
|
+
labelRender ? labelRender({
|
|
435
|
+
value: chip.value,
|
|
436
|
+
label: chip.label,
|
|
437
|
+
option: optionFor(chip.value) ?? void 0
|
|
438
|
+
}) : chip.label,
|
|
439
|
+
chipsRemovable ? /* @__PURE__ */ jsx(
|
|
440
|
+
"button",
|
|
441
|
+
{
|
|
442
|
+
type: "button",
|
|
443
|
+
className: "ui-search-select-value-remove",
|
|
444
|
+
"data-slot": "search-select-value-remove",
|
|
445
|
+
"aria-label": t("ui.tagInput.removeTag", { tag: chip.label }),
|
|
446
|
+
onClick: (event) => {
|
|
447
|
+
event.stopPropagation();
|
|
448
|
+
close();
|
|
449
|
+
},
|
|
450
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
|
|
451
|
+
}
|
|
452
|
+
) : null
|
|
453
|
+
] })
|
|
454
|
+
},
|
|
455
|
+
chip.value
|
|
456
|
+
);
|
|
457
|
+
}),
|
|
458
|
+
omittedChips.length > 0 ? /* @__PURE__ */ jsx("span", { className: "ui-search-select-value", "data-slot": "search-select-value-overflow", children: chipOverflow ?? t("dataEntry.selection.overflow", { count: omittedChips.length }) }) : null
|
|
459
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "truncate", children: resolvedPlaceholder }) : value && labelRender ? labelRender({ value, label: currentLabel, option: selectedOption ?? void 0 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
460
|
+
currentIcon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: currentIcon }) : null,
|
|
461
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: currentLabel })
|
|
462
|
+
] })
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
] });
|
|
304
466
|
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
305
467
|
/* @__PURE__ */ jsxs(
|
|
306
468
|
Popover,
|
|
307
469
|
{
|
|
308
470
|
open,
|
|
309
471
|
onOpenChange: (next) => {
|
|
310
|
-
if (readOnly && next) return;
|
|
472
|
+
if ((readOnly || disabled) && next) return;
|
|
311
473
|
setOpen(next);
|
|
312
474
|
if (!next && autoClearSearchValue) setQuery("");
|
|
313
475
|
},
|
|
314
476
|
children: [
|
|
315
|
-
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
316
|
-
"
|
|
477
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: multiple ? /* @__PURE__ */ jsx(
|
|
478
|
+
"div",
|
|
317
479
|
{
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
"aria-invalid": resolveAriaInvalid(ariaInvalid, status),
|
|
329
|
-
"aria-required": ariaRequired,
|
|
330
|
-
"aria-readonly": readOnly || void 0,
|
|
331
|
-
"aria-busy": loadingProp || void 0,
|
|
332
|
-
disabled,
|
|
333
|
-
"data-testid": dataTestId,
|
|
334
|
-
"data-field": resolvedField,
|
|
335
|
-
"data-value": (multiple ? values.join(",") : value) || void 0,
|
|
336
|
-
"data-mode": multiple ? "multiple" : void 0,
|
|
337
|
-
className: cn(
|
|
338
|
-
controlSurfaceTriggerClass,
|
|
339
|
-
"w-full justify-start",
|
|
340
|
-
// Reserve trailing room for the single clear-or-chevron overlay rendered below.
|
|
341
|
-
"ui-control-trigger-affixed"
|
|
342
|
-
),
|
|
343
|
-
children: /* @__PURE__ */ jsx(
|
|
344
|
-
"span",
|
|
345
|
-
{
|
|
346
|
-
className: cn(
|
|
347
|
-
"ui-search-select-option-body text-start",
|
|
348
|
-
!hasSelection && "text-muted-foreground"
|
|
349
|
-
),
|
|
350
|
-
children: multiple ? hasSelection ? (
|
|
351
|
-
// Labels, not removable chips: the trigger IS a <button>, and a per-label remove
|
|
352
|
-
// button inside it would be a button nested in a button (invalid HTML →
|
|
353
|
-
// hydration error). Removal is a second click on the row, or the clear ✕.
|
|
354
|
-
/* @__PURE__ */ jsxs("span", { className: "ui-search-select-values", "data-slot": "search-select-values", children: [
|
|
355
|
-
visibleChips.map((chip) => /* @__PURE__ */ jsx(
|
|
356
|
-
"span",
|
|
357
|
-
{
|
|
358
|
-
className: "ui-search-select-value",
|
|
359
|
-
"data-slot": "search-select-value",
|
|
360
|
-
children: labelRender ? labelRender({
|
|
361
|
-
value: chip.value,
|
|
362
|
-
label: chip.label,
|
|
363
|
-
option: optionFor(chip.value) ?? void 0
|
|
364
|
-
}) : chip.label
|
|
365
|
-
},
|
|
366
|
-
chip.value
|
|
367
|
-
)),
|
|
368
|
-
omittedChips.length > 0 ? /* @__PURE__ */ jsx(
|
|
369
|
-
"span",
|
|
370
|
-
{
|
|
371
|
-
className: "ui-search-select-value",
|
|
372
|
-
"data-slot": "search-select-value-overflow",
|
|
373
|
-
children: chipOverflow ?? t("dataEntry.selection.overflow", { count: omittedChips.length })
|
|
374
|
-
}
|
|
375
|
-
) : null
|
|
376
|
-
] })
|
|
377
|
-
) : /* @__PURE__ */ jsx("span", { className: "truncate", children: resolvedPlaceholder }) : value && labelRender ? labelRender({ value, label: currentLabel, option: selectedOption ?? void 0 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
378
|
-
currentIcon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: currentIcon }) : null,
|
|
379
|
-
/* @__PURE__ */ jsx("span", { className: "truncate", children: currentLabel })
|
|
380
|
-
] })
|
|
480
|
+
...triggerProps,
|
|
481
|
+
tabIndex: disabled ? -1 : 0,
|
|
482
|
+
"aria-disabled": disabled || void 0,
|
|
483
|
+
"data-disabled": disabled ? "" : void 0,
|
|
484
|
+
onKeyDown: (event) => {
|
|
485
|
+
if (disabled || readOnly) return;
|
|
486
|
+
if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
|
|
487
|
+
event.preventDefault();
|
|
488
|
+
setOpen(true);
|
|
489
|
+
return;
|
|
381
490
|
}
|
|
382
|
-
|
|
491
|
+
if (event.key === "Backspace" && values.length > 0) {
|
|
492
|
+
event.preventDefault();
|
|
493
|
+
deselect(values[values.length - 1]);
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
children: triggerContent
|
|
383
497
|
}
|
|
384
|
-
) }),
|
|
498
|
+
) : /* @__PURE__ */ jsx("button", { ...triggerProps, type: "button", disabled, children: triggerContent }) }),
|
|
385
499
|
resolvedName ? multiple ? values.map((entry) => /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value: entry, readOnly: true }, entry)) : /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value, readOnly: true }) : null,
|
|
386
500
|
/* @__PURE__ */ jsx(
|
|
387
501
|
PopoverContent,
|
|
388
502
|
{
|
|
389
503
|
"aria-label": triggerAriaLabelledby ? void 0 : triggerAriaLabel ?? resolvedPlaceholder,
|
|
390
504
|
"aria-labelledby": triggerAriaLabelledby,
|
|
391
|
-
|
|
505
|
+
side: placement?.startsWith("top") ? "top" : "bottom",
|
|
506
|
+
align: placement?.endsWith("End") ? "end" : "start",
|
|
392
507
|
sideOffset: 4,
|
|
393
508
|
collisionPadding: 12,
|
|
394
509
|
className: "ui-search-select-panel",
|
|
@@ -396,7 +511,7 @@ function SearchSelect(props) {
|
|
|
396
511
|
style: typeof popupMatchSelectWidth === "number" ? {
|
|
397
512
|
"--search-select-panel-inline-size": `${popupMatchSelectWidth}px`
|
|
398
513
|
} : void 0,
|
|
399
|
-
children: /* @__PURE__ */ jsxs(Command, { value, shouldFilter: false, className: "ui-search-select-command", children: [
|
|
514
|
+
children: /* @__PURE__ */ jsx(PopupRender, { render: popupRender, children: /* @__PURE__ */ jsxs(Command, { value, shouldFilter: false, className: "ui-search-select-command", children: [
|
|
400
515
|
/* @__PURE__ */ jsx("div", { className: "ui-search-select-search", children: /* @__PURE__ */ jsx(
|
|
401
516
|
Input,
|
|
402
517
|
{
|
|
@@ -406,7 +521,23 @@ function SearchSelect(props) {
|
|
|
406
521
|
"aria-activedescendant": activeOptionId,
|
|
407
522
|
"aria-label": searchPlaceholder ?? t("dataEntry.searchSelect.search"),
|
|
408
523
|
value: query,
|
|
409
|
-
onChange: (event) =>
|
|
524
|
+
onChange: (event) => {
|
|
525
|
+
const next = event.target.value;
|
|
526
|
+
if (multiple && tokenSeparators?.length && tokenSeparators.some((separator) => next.includes(separator))) {
|
|
527
|
+
selectTokens(next);
|
|
528
|
+
setQuery("");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
setQuery(next);
|
|
532
|
+
},
|
|
533
|
+
onPaste: (event) => {
|
|
534
|
+
if (!multiple || !tokenSeparators?.length) return;
|
|
535
|
+
const text = event.clipboardData.getData("text");
|
|
536
|
+
if (!tokenSeparators.some((separator) => text.includes(separator))) return;
|
|
537
|
+
event.preventDefault();
|
|
538
|
+
selectTokens(text);
|
|
539
|
+
setQuery("");
|
|
540
|
+
},
|
|
410
541
|
onKeyDown,
|
|
411
542
|
placeholder: searchPlaceholder ?? t("dataEntry.searchSelect.search"),
|
|
412
543
|
className: "ui-search-select-search-input"
|
|
@@ -420,7 +551,13 @@ function SearchSelect(props) {
|
|
|
420
551
|
"aria-multiselectable": multiple || void 0,
|
|
421
552
|
"aria-busy": loading,
|
|
422
553
|
className: "ui-search-select-list",
|
|
423
|
-
|
|
554
|
+
style: listHeight === void 0 ? void 0 : {
|
|
555
|
+
"--search-select-list-max-height": `${listHeight}px`
|
|
556
|
+
},
|
|
557
|
+
onScroll: (event) => {
|
|
558
|
+
onPopupScroll?.(event);
|
|
559
|
+
onScroll(event);
|
|
560
|
+
},
|
|
424
561
|
children: [
|
|
425
562
|
grouped.map((group) => {
|
|
426
563
|
const rows = group.items.map(({ option, index }) => /* @__PURE__ */ jsxs(
|
|
@@ -484,7 +621,7 @@ function SearchSelect(props) {
|
|
|
484
621
|
"data-tone": "destructive",
|
|
485
622
|
children: errorMessage ?? t("dataEntry.searchSelect.error")
|
|
486
623
|
}
|
|
487
|
-
) :
|
|
624
|
+
) : displayOptions.length === 0 ? /* @__PURE__ */ jsx(
|
|
488
625
|
"div",
|
|
489
626
|
{
|
|
490
627
|
role: "option",
|
|
@@ -511,7 +648,7 @@ function SearchSelect(props) {
|
|
|
511
648
|
})
|
|
512
649
|
}
|
|
513
650
|
) : null
|
|
514
|
-
] })
|
|
651
|
+
] }) })
|
|
515
652
|
}
|
|
516
653
|
)
|
|
517
654
|
]
|
|
@@ -539,7 +676,7 @@ function SearchSelect(props) {
|
|
|
539
676
|
onClick: clear,
|
|
540
677
|
children: clearControl.clearIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-control-affix-icon", "aria-hidden": "true" })
|
|
541
678
|
}
|
|
542
|
-
) : /* @__PURE__ */ jsx(
|
|
679
|
+
) : suffixIcon === null ? null : suffixIcon !== void 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "select-suffix", className: "ui-select-suffix", "aria-hidden": "true", children: suffixIcon }) : /* @__PURE__ */ jsx(
|
|
543
680
|
ChevronsUpDown,
|
|
544
681
|
{
|
|
545
682
|
className: "ui-control-affix-icon ui-control-affix-indicator",
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { ControlStatusProp, ControlVariantProp, ControlWidthProp, SizeProp } from "../../props/vocabulary/index.js";
|
|
3
|
-
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
4
3
|
import type { FieldA11yProps } from "../../lib/field-a11y.js";
|
|
5
4
|
import type { SelectDataProp } from "../../props/components/data-entry.prop.js";
|
|
6
|
-
/** Compound-API props
|
|
5
|
+
/** Compound-API props — the shape the Radix root had, plus the FormField field-a11y contract that
|
|
7
6
|
* this component re-routes to the trigger (see {@link SelectFieldA11yContext}). */
|
|
8
|
-
export type SelectCompoundProp =
|
|
7
|
+
export type SelectCompoundProp = FieldA11yProps & {
|
|
9
8
|
id?: string;
|
|
9
|
+
value?: string;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
onValueChange?: (value: string) => void;
|
|
12
|
+
open?: boolean;
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
onOpenChange?: (open: boolean) => void;
|
|
15
|
+
/**
|
|
16
|
+
* @deprecated Accepted so Radix-era call sites keep compiling. Direction now comes from the
|
|
17
|
+
* ambient locale (`AppProvider` / `I18nProvider`), which react-aria reads itself.
|
|
18
|
+
*/
|
|
19
|
+
dir?: "ltr" | "rtl";
|
|
20
|
+
/** Form field name — a hidden native `<select>` carries the value on submit. */
|
|
21
|
+
name?: string;
|
|
22
|
+
autoComplete?: string;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/** Native `required`: an empty select blocks a native form submit. */
|
|
25
|
+
required?: boolean;
|
|
26
|
+
form?: string;
|
|
27
|
+
children?: React.ReactNode;
|
|
10
28
|
};
|
|
11
29
|
export type SelectProp = SelectDataProp | SelectCompoundProp;
|
|
12
30
|
/**
|
|
@@ -17,9 +35,16 @@ export type SelectProp = SelectDataProp | SelectCompoundProp;
|
|
|
17
35
|
* optgroup grouping, and `renderOption`.
|
|
18
36
|
*/
|
|
19
37
|
export declare function Select(props: SelectProp): React.JSX.Element;
|
|
20
|
-
export declare function SelectGroup(props: React.
|
|
21
|
-
export declare
|
|
22
|
-
|
|
38
|
+
export declare function SelectGroup({ className, children, ...props }: React.HTMLAttributes<HTMLElement>): React.JSX.Element;
|
|
39
|
+
export declare const SelectValue: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLSpanElement>, "children"> & {
|
|
40
|
+
/** Shown while nothing is selected. */
|
|
41
|
+
placeholder?: React.ReactNode;
|
|
42
|
+
/** Replaces the selected option's label on the trigger while a value is selected. */
|
|
43
|
+
children?: React.ReactNode;
|
|
44
|
+
/** @deprecated Radix-only; has no effect. */
|
|
45
|
+
asChild?: boolean;
|
|
46
|
+
} & React.RefAttributes<HTMLSpanElement>>;
|
|
47
|
+
export declare const SelectTrigger: React.ForwardRefExoticComponent<React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
23
48
|
/** Control height tier — the shared `--control-height` ladder (antd `size`). */
|
|
24
49
|
size?: SizeProp;
|
|
25
50
|
/** Control surface (antd `variant`). Default `outlined`. */
|
|
@@ -43,27 +68,64 @@ export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectP
|
|
|
43
68
|
"data-field"?: string;
|
|
44
69
|
/**
|
|
45
70
|
* Normally supplied by `Select` itself — the trigger shows the option's LABEL, and this is the
|
|
46
|
-
* only place the underlying value is readable from the DOM without reaching into
|
|
47
|
-
*
|
|
71
|
+
* only place the underlying value is readable from the DOM without reaching into the aria-hidden
|
|
72
|
+
* native `<select>`.
|
|
48
73
|
*/
|
|
49
74
|
"data-value"?: string;
|
|
75
|
+
/** @deprecated Radix-only; has no effect — the trigger is always the library's own button. */
|
|
76
|
+
asChild?: boolean;
|
|
50
77
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
type SelectScrollButtonProp = React.HTMLAttributes<HTMLDivElement>;
|
|
79
|
+
/**
|
|
80
|
+
* @deprecated Radix-only. react-aria's listbox is a native scroll container, so there is nothing
|
|
81
|
+
* to render; kept (as a no-op) so existing imports keep compiling until the next major.
|
|
82
|
+
*/
|
|
83
|
+
export declare const SelectScrollUpButton: React.ForwardRefExoticComponent<SelectScrollButtonProp & React.RefAttributes<HTMLDivElement>>;
|
|
84
|
+
/** @deprecated Radix-only — see {@link SelectScrollUpButton}. */
|
|
85
|
+
export declare const SelectScrollDownButton: React.ForwardRefExoticComponent<SelectScrollButtonProp & React.RefAttributes<HTMLDivElement>>;
|
|
86
|
+
type RadixSide = "top" | "right" | "bottom" | "left";
|
|
87
|
+
export declare const SelectContent: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "dir"> & {
|
|
54
88
|
/**
|
|
55
89
|
* How the popup sizes against its trigger — the DOM half of antd `popupMatchSelectWidth`.
|
|
56
90
|
* Absent = the trigger's width is the floor (antd's `true`); `content` releases that floor;
|
|
57
91
|
* `fixed` pins both edges to `--select-content-inline-size`.
|
|
58
92
|
*/
|
|
59
93
|
"data-popup-match"?: "content" | "fixed";
|
|
94
|
+
side?: RadixSide;
|
|
95
|
+
sideOffset?: number;
|
|
96
|
+
align?: "start" | "center" | "end";
|
|
97
|
+
alignOffset?: number;
|
|
98
|
+
/** `false` keeps the popup on `side` even when it would overflow. */
|
|
99
|
+
avoidCollisions?: boolean;
|
|
100
|
+
collisionPadding?: number;
|
|
101
|
+
/** @deprecated Radix-only. The popup always positions against the trigger (`popper`). */
|
|
102
|
+
position?: "popper" | "item-aligned";
|
|
103
|
+
/** @deprecated Radix-only; has no effect. */
|
|
104
|
+
sticky?: "partial" | "always";
|
|
105
|
+
/** @deprecated Radix-only; has no effect. */
|
|
106
|
+
hideWhenDetached?: boolean;
|
|
107
|
+
/** @deprecated Radix-only; has no effect. */
|
|
108
|
+
onCloseAutoFocus?: (event: Event) => void;
|
|
109
|
+
/** @deprecated Radix-only; has no effect — Escape always closes. */
|
|
110
|
+
onEscapeKeyDown?: (event: KeyboardEvent) => void;
|
|
111
|
+
/** @deprecated Radix-only; has no effect — an outside press always closes. */
|
|
112
|
+
onPointerDownOutside?: (event: Event) => void;
|
|
113
|
+
/** @deprecated Radix-only; has no effect. */
|
|
114
|
+
forceMount?: true;
|
|
60
115
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
61
|
-
export declare const SelectLabel: React.ForwardRefExoticComponent<
|
|
62
|
-
export declare const SelectItem: React.ForwardRefExoticComponent<Omit<
|
|
116
|
+
export declare const SelectLabel: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLElement> & React.RefAttributes<HTMLElement>>;
|
|
117
|
+
export declare const SelectItem: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> & {
|
|
118
|
+
value: string;
|
|
119
|
+
disabled?: boolean;
|
|
120
|
+
/** Text for type-to-select and the hidden native `<option>`; derived from `children` if omitted. */
|
|
121
|
+
textValue?: string;
|
|
63
122
|
/**
|
|
64
123
|
* antd `menuItemSelectedIcon` — a decorative mark on the picked row. Off by default: this
|
|
65
124
|
* library marks the picked row with fill + weight, which costs no width.
|
|
66
125
|
*/
|
|
67
126
|
selectedIcon?: React.ReactNode;
|
|
127
|
+
/** @deprecated Radix-only; has no effect. */
|
|
128
|
+
asChild?: boolean;
|
|
68
129
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
69
|
-
export declare const SelectSeparator: React.ForwardRefExoticComponent<
|
|
130
|
+
export declare const SelectSeparator: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLElement> & React.RefAttributes<HTMLElement>>;
|
|
131
|
+
export {};
|