@godxjp/ui 17.0.0 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/components/data-display/card.d.ts +37 -1
  2. package/dist/components/data-display/card.js +7 -1
  3. package/dist/components/data-display/data-table.js +18 -3
  4. package/dist/components/data-display/empty-state.d.ts +1 -1
  5. package/dist/components/data-display/empty-state.js +5 -1
  6. package/dist/components/data-display/scroll-area.js +1 -1
  7. package/dist/components/data-display/table.js +14 -9
  8. package/dist/components/data-entry/calendar.js +4 -0
  9. package/dist/components/data-entry/cascader.d.ts +1 -1
  10. package/dist/components/data-entry/cascader.js +10 -1
  11. package/dist/components/data-entry/checkbox-group.d.ts +1 -1
  12. package/dist/components/data-entry/checkbox-group.js +13 -4
  13. package/dist/components/data-entry/color-picker.d.ts +1 -1
  14. package/dist/components/data-entry/color-picker.js +5 -2
  15. package/dist/components/data-entry/command.d.ts +2 -2
  16. package/dist/components/data-entry/date-picker.d.ts +1 -1
  17. package/dist/components/data-entry/date-picker.js +11 -1
  18. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  19. package/dist/components/data-entry/date-range-picker.js +11 -3
  20. package/dist/components/data-entry/form-field.js +7 -3
  21. package/dist/components/data-entry/month-picker.d.ts +1 -1
  22. package/dist/components/data-entry/month-picker.js +13 -1
  23. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  24. package/dist/components/data-entry/month-range-picker.js +11 -3
  25. package/dist/components/data-entry/number-input.d.ts +19 -2
  26. package/dist/components/data-entry/number-input.js +5 -7
  27. package/dist/components/data-entry/radio.d.ts +1 -1
  28. package/dist/components/data-entry/radio.js +11 -3
  29. package/dist/components/data-entry/search-input.d.ts +3 -2
  30. package/dist/components/data-entry/search-input.js +5 -2
  31. package/dist/components/data-entry/search-select.d.ts +1 -1
  32. package/dist/components/data-entry/search-select.js +51 -10
  33. package/dist/components/data-entry/select.d.ts +7 -0
  34. package/dist/components/data-entry/select.js +21 -3
  35. package/dist/components/data-entry/time-picker.d.ts +1 -1
  36. package/dist/components/data-entry/time-picker.js +11 -1
  37. package/dist/components/data-entry/transfer.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +73 -59
  39. package/dist/components/data-entry/tree-select.d.ts +1 -1
  40. package/dist/components/data-entry/tree-select.js +10 -1
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +7 -2
  43. package/dist/components/layout/app-shell.d.ts +1 -1
  44. package/dist/components/layout/app-shell.js +42 -2
  45. package/dist/components/layout/breadcrumb.d.ts +7 -1
  46. package/dist/components/layout/breadcrumb.js +6 -2
  47. package/dist/components/layout/page-container.d.ts +1 -1
  48. package/dist/components/layout/page-container.js +32 -22
  49. package/dist/components/layout/responsive-grid.js +1 -1
  50. package/dist/components/layout/sidebar.d.ts +1 -1
  51. package/dist/components/layout/sidebar.js +44 -11
  52. package/dist/components/layout/split-pane.js +2 -2
  53. package/dist/components/navigation/app-setting-picker.js +8 -1
  54. package/dist/components/navigation/pagination.d.ts +1 -1
  55. package/dist/components/navigation/pagination.js +84 -95
  56. package/dist/components/navigation/tabs.js +21 -4
  57. package/dist/i18n/messages/en.json +3 -1
  58. package/dist/i18n/messages/ja.json +3 -1
  59. package/dist/i18n/messages/vi.json +3 -1
  60. package/dist/lib/field-a11y.d.ts +66 -0
  61. package/dist/lib/field-a11y.js +44 -0
  62. package/dist/props/components/data-display.prop.d.ts +16 -1
  63. package/dist/props/components/data-entry.prop.d.ts +69 -19
  64. package/dist/props/components/layout.prop.d.ts +42 -0
  65. package/dist/props/components/navigation.prop.d.ts +16 -0
  66. package/dist/props/registry.d.ts +8 -3
  67. package/dist/props/registry.js +21 -3
  68. package/dist/props/vocabulary/data.prop.d.ts +9 -0
  69. package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
  70. package/dist/styles/base.css +1 -1
  71. package/dist/styles/layout.css +46 -9
  72. package/dist/styles/shell-layout.css +19 -0
  73. package/dist/styles/text-layout.css +1 -1
  74. package/dist/tokens/components/card.css +1 -1
  75. package/dist/tokens/foundation.css +3 -3
  76. package/package.json +8 -4
  77. package/scripts/ui-audit.mjs +2 -2
  78. package/scripts/visual-audit-rules.mjs +2 -2
@@ -1,5 +1,4 @@
1
1
  import * as React from "react";
2
- import type { NumberInputProp } from "../../props/components/data-entry.prop.js";
3
2
  export type { NumberInputProp, NumberInputProp as NumberInputProps, } from "../../props/components/data-entry.prop.js";
4
3
  /**
5
4
  * NumberInput — WAI-ARIA spinbutton: a localized numeric `<Input>` with increment/decrement steps.
@@ -9,4 +8,22 @@ export type { NumberInputProp, NumberInputProp as NumberInputProps, } from "../.
9
8
  * (Shift = ×10) step, and the value commits clamped + rounded to `precision` on blur/Enter/step.
10
9
  * Display formats via `Intl.NumberFormat` at rest, but stays raw and typeable while focused.
11
10
  */
12
- export declare const NumberInput: React.ForwardRefExoticComponent<NumberInputProp & React.RefAttributes<HTMLInputElement>>;
11
+ export declare const NumberInput: React.ForwardRefExoticComponent<import("../../lib/field-a11y.js").FieldA11yProps & {
12
+ value?: import("../../props/index.js").ValueProp<number | null>;
13
+ defaultValue?: import("../../props/index.js").DefaultValueProp<number | null>;
14
+ onValueChange?: import("../../props/index.js").OnValueChangeProp<number | null>;
15
+ min?: number;
16
+ max?: number;
17
+ step?: number;
18
+ precision?: number;
19
+ disabled?: import("../../props/index.js").DisabledProp;
20
+ readOnly?: boolean;
21
+ size?: import("../../props/index.js").SizeProp;
22
+ placeholder?: import("../../props/index.js").PlaceholderProp;
23
+ prefix?: React.ReactNode;
24
+ suffix?: React.ReactNode;
25
+ name?: import("../../props/index.js").NameProp;
26
+ id?: import("../../props/index.js").IdProp;
27
+ className?: import("../../props/index.js").ClassNameProp;
28
+ "data-testid"?: string;
29
+ } & React.RefAttributes<HTMLInputElement>>;
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { ChevronDown, ChevronUp } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import { pickFieldA11y } from "../../lib/field-a11y.js";
7
8
  import { Button } from "../general/button.js";
8
9
  import { Input } from "./input.js";
9
10
  function decimalsOf(n) {
@@ -41,12 +42,11 @@ const NumberInput = React.forwardRef(
41
42
  name,
42
43
  id,
43
44
  className,
44
- "aria-label": ariaLabel,
45
- "aria-labelledby": ariaLabelledby,
46
- "aria-describedby": ariaDescribedby,
47
- "data-testid": dataTestId
45
+ "data-testid": dataTestId,
46
+ ...ariaProps
48
47
  }, ref) => {
49
48
  const { t, locale } = useTranslation();
49
+ const fieldA11y = pickFieldA11y(ariaProps);
50
50
  const isControlled = controlledValue !== void 0;
51
51
  const [internal, setInternal] = React.useState(defaultValue);
52
52
  const numericValue = isControlled ? controlledValue ?? null : internal;
@@ -159,9 +159,7 @@ const NumberInput = React.forwardRef(
159
159
  placeholder,
160
160
  disabled,
161
161
  readOnly,
162
- "aria-label": ariaLabel,
163
- "aria-labelledby": ariaLabelledby,
164
- "aria-describedby": ariaDescribedby,
162
+ ...fieldA11y,
165
163
  "aria-valuenow": numericValue ?? void 0,
166
164
  "aria-valuemin": min,
167
165
  "aria-valuemax": max,
@@ -4,7 +4,7 @@ import type { RadioGroupProp } from "../../props/components/data-entry.prop.js";
4
4
  export type { RadioGroupProp, RadioGroupProp as RadioGroupProps, } from "../../props/components/data-entry.prop.js";
5
5
  declare const RadioGroupRoot: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
6
6
  declare const RadioItem: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
7
- declare function RadioGroupOptions({ value, defaultValue, onValueChange, options, orientation, disabled, name, className, children, }: RadioGroupProp): import("react/jsx-runtime").JSX.Element;
7
+ declare function RadioGroupOptions({ value, defaultValue, onValueChange, options, orientation, disabled, name, id, className, children, ...ariaProps }: RadioGroupProp): import("react/jsx-runtime").JSX.Element;
8
8
  /** Single radio — use inside `Radio.Group` / `RadioGroupRoot`, or via `options` API. */
9
9
  export declare const Radio: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>> & {
10
10
  Root: React.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5
5
  import { Circle } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import { pickFieldA11y } from "../../lib/field-a11y.js";
7
8
  import { Field } from "./field.js";
8
9
  import { choiceGroupClassName } from "./choice-option.js";
9
10
  const RadioGroupRoot = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
@@ -45,10 +46,13 @@ function RadioGroupOptions({
45
46
  orientation = "vertical",
46
47
  disabled,
47
48
  name,
49
+ id,
48
50
  className,
49
- children
51
+ children,
52
+ ...ariaProps
50
53
  }) {
51
54
  const reactId = React.useId();
55
+ const groupA11y = pickFieldA11y(ariaProps);
52
56
  if (options && options.length > 0) {
53
57
  return /* @__PURE__ */ jsx(
54
58
  RadioGroupRoot,
@@ -58,11 +62,13 @@ function RadioGroupOptions({
58
62
  onValueChange,
59
63
  disabled,
60
64
  name,
65
+ id,
66
+ ...groupA11y,
61
67
  "data-orientation": orientation,
62
68
  className: choiceGroupClassName(orientation, className),
63
69
  children: options.map((opt, index) => {
64
- const id = `${reactId}-${opt.value}-${index}`;
65
- return /* @__PURE__ */ jsx(Field, { id, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(RadioItem, { id, value: opt.value, disabled: opt.disabled }) }, opt.value);
70
+ const id2 = `${reactId}-${opt.value}-${index}`;
71
+ return /* @__PURE__ */ jsx(Field, { id: id2, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(RadioItem, { id: id2, value: opt.value, disabled: opt.disabled }) }, opt.value);
66
72
  })
67
73
  }
68
74
  );
@@ -75,6 +81,8 @@ function RadioGroupOptions({
75
81
  onValueChange,
76
82
  disabled,
77
83
  name,
84
+ id,
85
+ ...groupA11y,
78
86
  "data-orientation": orientation,
79
87
  className: choiceGroupClassName(orientation, className),
80
88
  children
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
- interface SearchInputProps {
2
+ import { type FieldA11yProps } from "../../lib/field-a11y.js";
3
+ interface SearchInputProps extends FieldA11yProps {
3
4
  value?: string;
4
5
  defaultValue?: string;
5
6
  placeholder?: string;
@@ -15,5 +16,5 @@ interface SearchInputProps {
15
16
  id?: string;
16
17
  disabled?: boolean;
17
18
  }
18
- export declare function SearchInput({ value: controlledValue, defaultValue, placeholder, debounce, onValueChange, onSearch, label, ariaLabel, className, inputClassName, id, disabled, }: SearchInputProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function SearchInput({ value: controlledValue, defaultValue, placeholder, debounce, onValueChange, onSearch, label, ariaLabel, className, inputClassName, id, disabled, ...ariaProps }: SearchInputProps): import("react/jsx-runtime").JSX.Element;
19
20
  export {};
@@ -6,6 +6,7 @@ import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { Input } from "../data-entry/input.js";
7
7
  import { Label } from "../data-entry/label.js";
8
8
  import { cn } from "../../lib/utils.js";
9
+ import { resolveFieldA11y } from "../../lib/field-a11y.js";
9
10
  import { useDebouncedValue } from "../../lib/hooks.js";
10
11
  function SearchInput({
11
12
  value: controlledValue,
@@ -19,7 +20,8 @@ function SearchInput({
19
20
  className,
20
21
  inputClassName,
21
22
  id,
22
- disabled = false
23
+ disabled = false,
24
+ ...ariaProps
23
25
  }) {
24
26
  const { t } = useTranslation();
25
27
  const isControlled = controlledValue !== void 0;
@@ -30,6 +32,7 @@ function SearchInput({
30
32
  const inputId = id ?? `search-${reactId}`;
31
33
  const resolvedPlaceholder = placeholder ?? t("dataEntry.searchInput.placeholder");
32
34
  const resolvedAriaLabel = ariaLabel ?? t("common.search");
35
+ const fieldA11y = resolveFieldA11y(ariaProps, resolvedAriaLabel);
33
36
  const onSearchRef = React.useRef(onSearch);
34
37
  React.useEffect(() => {
35
38
  onSearchRef.current = onSearch;
@@ -56,7 +59,7 @@ function SearchInput({
56
59
  setValue(e.target.value);
57
60
  },
58
61
  placeholder: resolvedPlaceholder,
59
- "aria-label": resolvedAriaLabel,
62
+ ...fieldA11y,
60
63
  className: cn(
61
64
  "ui-search-input-control !pr-[var(--search-input-end-padding)] !pl-[var(--search-input-start-padding)]",
62
65
  inputClassName
@@ -8,4 +8,4 @@ export type { SearchSelectProp, SearchSelectProp as SearchSelectProps, SearchSel
8
8
  * Custom per-option rendering via `renderOption` (Ant-Design style). Form-submittable via
9
9
  * `name`; e2e-testable by the trigger's `data-testid` + each option's `${data-testid}-option-${value}`.
10
10
  */
11
- export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, name, id, className, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): import("react/jsx-runtime").JSX.Element;
11
+ export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, readOnly, size, open: openProp, onOpenChange, search: searchProp, onSearchChange, filterOption, renderError, renderLoadMore, name, id, className, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): import("react/jsx-runtime").JSX.Element;
@@ -28,6 +28,15 @@ function SearchSelect({
28
28
  clearLabel,
29
29
  clearable = true,
30
30
  disabled = false,
31
+ readOnly = false,
32
+ size,
33
+ open: openProp,
34
+ onOpenChange,
35
+ search: searchProp,
36
+ onSearchChange,
37
+ filterOption,
38
+ renderError,
39
+ renderLoadMore,
31
40
  name,
32
41
  id,
33
42
  className,
@@ -43,8 +52,26 @@ function SearchSelect({
43
52
  const reactId = React.useId();
44
53
  const listId = `${reactId}-listbox`;
45
54
  const optionDomId = (optionValue) => `${reactId}-opt-${optionValue}`;
46
- const [open, setOpen] = React.useState(false);
47
- const [query, setQuery] = React.useState("");
55
+ const [internalOpen, setInternalOpen] = React.useState(false);
56
+ const isOpenControlled = openProp !== void 0;
57
+ const open = isOpenControlled ? openProp : internalOpen;
58
+ const setOpen = React.useCallback(
59
+ (next) => {
60
+ if (!isOpenControlled) setInternalOpen(next);
61
+ onOpenChange?.(next);
62
+ },
63
+ [isOpenControlled, onOpenChange]
64
+ );
65
+ const [internalQuery, setInternalQuery] = React.useState("");
66
+ const isSearchControlled = searchProp !== void 0;
67
+ const query = isSearchControlled ? searchProp : internalQuery;
68
+ const setQuery = React.useCallback(
69
+ (next) => {
70
+ if (!isSearchControlled) setInternalQuery(next);
71
+ onSearchChange?.(next);
72
+ },
73
+ [isSearchControlled, onSearchChange]
74
+ );
48
75
  const [debouncedQuery, setDebouncedQuery] = React.useState("");
49
76
  const [loaded, setLoaded] = React.useState([]);
50
77
  const [page, setPage] = React.useState(1);
@@ -59,16 +86,15 @@ function SearchSelect({
59
86
  const reqId = React.useRef(0);
60
87
  const resolvedLoad = React.useMemo(
61
88
  () => loadOptions ?? (async ({ query: search }) => {
62
- const needle = search.trim().toLowerCase();
89
+ const needle = search.trim();
63
90
  const list = staticOptions ?? [];
91
+ const matches = (option) => filterOption ? filterOption(option, needle) : option.label.toLowerCase().includes(needle.toLowerCase()) || option.value.toLowerCase().includes(needle.toLowerCase());
64
92
  return {
65
- options: needle ? list.filter(
66
- (option) => option.label.toLowerCase().includes(needle) || option.value.toLowerCase().includes(needle)
67
- ) : list,
93
+ options: needle ? list.filter(matches) : list,
68
94
  hasMore: false
69
95
  };
70
96
  }),
71
- [loadOptions, staticOptions]
97
+ [loadOptions, staticOptions, filterOption]
72
98
  );
73
99
  React.useEffect(() => {
74
100
  const handle = window.setTimeout(() => setDebouncedQuery(query.trim()), DEBOUNCE_MS);
@@ -190,13 +216,14 @@ function SearchSelect({
190
216
  const optionTestId = (optionValue) => dataTestId ? `${dataTestId}-option-${optionValue}` : void 0;
191
217
  const activeOption = flatOrdered[activeIndex];
192
218
  const activeOptionId = activeOption ? optionDomId(activeOption.value) : void 0;
193
- const showClear = clearable && Boolean(value) && !disabled;
219
+ const showClear = clearable && Boolean(value) && !disabled && !readOnly;
194
220
  return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
195
221
  /* @__PURE__ */ jsxs(
196
222
  Popover,
197
223
  {
198
224
  open,
199
225
  onOpenChange: (next) => {
226
+ if (readOnly && next) return;
200
227
  setOpen(next);
201
228
  if (!next) setQuery("");
202
229
  },
@@ -208,6 +235,7 @@ function SearchSelect({
208
235
  type: "button",
209
236
  variant: "outline",
210
237
  role: "combobox",
238
+ size,
211
239
  "aria-expanded": open,
212
240
  "aria-controls": open ? listId : void 0,
213
241
  "aria-label": ariaLabel,
@@ -216,6 +244,7 @@ function SearchSelect({
216
244
  "aria-errormessage": ariaErrorMessage,
217
245
  "aria-invalid": ariaInvalid,
218
246
  "aria-required": ariaRequired,
247
+ "aria-readonly": readOnly || void 0,
219
248
  disabled,
220
249
  "data-testid": dataTestId,
221
250
  className: cn(
@@ -317,7 +346,12 @@ function SearchSelect({
317
346
  loadingMessage ?? t("dataEntry.searchSelect.loading")
318
347
  ]
319
348
  }
320
- ) : error ? /* @__PURE__ */ jsx(
349
+ ) : error ? renderError ? renderError({
350
+ message: errorMessage ?? t("dataEntry.searchSelect.error"),
351
+ // Retry always reloads from the first page (a defined, predictable recovery —
352
+ // NOT a resume of a failed page-N append, which would need to re-derive state).
353
+ retry: () => void fetchPage(1, debouncedQuery, false)
354
+ }) : /* @__PURE__ */ jsx(
321
355
  "div",
322
356
  {
323
357
  role: "option",
@@ -338,7 +372,14 @@ function SearchSelect({
338
372
  ) : null
339
373
  ]
340
374
  }
341
- )
375
+ ),
376
+ renderLoadMore && hasMore ? /* @__PURE__ */ jsx("div", { className: "border-border shrink-0 border-t p-1", children: renderLoadMore({
377
+ hasMore,
378
+ loading,
379
+ loadMore: () => {
380
+ if (!loading) void fetchPage(page + 1, debouncedQuery, true);
381
+ }
382
+ }) }) : null
342
383
  ] })
343
384
  }
344
385
  )
@@ -14,6 +14,13 @@ export declare function SelectGroup(props: React.ComponentProps<typeof SelectPri
14
14
  export declare function SelectValue(props: React.ComponentProps<typeof SelectPrimitive.Value>): import("react/jsx-runtime").JSX.Element;
15
15
  export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
16
16
  size?: "sm" | "md";
17
+ /**
18
+ * Show the built-in chevron disclosure indicator (default true). Set `false` for specialized
19
+ * triggers — icon-only, or one that already renders its own affordance — so it isn't
20
+ * duplicated. Omits the icon from the DOM entirely (not a CSS hide), so no consumer
21
+ * descendant CSS is needed to remove it.
22
+ */
23
+ showIndicator?: boolean;
17
24
  } & React.RefAttributes<HTMLButtonElement>>;
18
25
  export declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
19
26
  export declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -21,7 +21,7 @@ function SelectGroup(props) {
21
21
  function SelectValue(props) {
22
22
  return /* @__PURE__ */ jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
23
23
  }
24
- const SelectTrigger = React.forwardRef(({ className, children, size = "md", ...props }, ref) => /* @__PURE__ */ jsxs(
24
+ const SelectTrigger = React.forwardRef(({ className, children, size = "md", showIndicator = true, ...props }, ref) => /* @__PURE__ */ jsxs(
25
25
  SelectPrimitive.Trigger,
26
26
  {
27
27
  ref,
@@ -35,14 +35,14 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", ...p
35
35
  ...props,
36
36
  children: [
37
37
  children,
38
- /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
38
+ showIndicator ? /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
39
39
  ChevronDown,
40
40
  {
41
41
  "data-slot": "select-chevron",
42
42
  className: "size-4 shrink-0 opacity-50",
43
43
  "aria-hidden": "true"
44
44
  }
45
- ) })
45
+ ) }) : null
46
46
  ]
47
47
  }
48
48
  ));
@@ -165,6 +165,15 @@ function DataSelect({
165
165
  clearLabel,
166
166
  clearable,
167
167
  disabled,
168
+ readOnly,
169
+ size,
170
+ open,
171
+ onOpenChange,
172
+ search,
173
+ onSearchChange,
174
+ filterOption,
175
+ renderError,
176
+ renderLoadMore,
168
177
  name,
169
178
  id,
170
179
  className,
@@ -197,6 +206,15 @@ function DataSelect({
197
206
  clearLabel,
198
207
  clearable,
199
208
  disabled: disabled || !loadOptions && !hasOptions,
209
+ readOnly,
210
+ size,
211
+ open,
212
+ onOpenChange,
213
+ search,
214
+ onSearchChange,
215
+ filterOption,
216
+ renderError,
217
+ renderLoadMore,
200
218
  name,
201
219
  id,
202
220
  className,
@@ -7,4 +7,4 @@ export type { TimePickerProp, TimePickerProp as TimePickerProps, } from "../../p
7
7
  * The column display honours the active `timeFormat` (12h shows 1-12 + AM/PM) while the canonical
8
8
  * value remains 24h `HH:mm`.
9
9
  */
10
- export declare function TimePicker({ value: controlledValue, defaultValue, onValueChange, placeholder, disabled, className, id, name, minuteStep, allowClear, }: TimePickerProp): import("react/jsx-runtime").JSX.Element;
10
+ export declare function TimePicker({ value: controlledValue, defaultValue, onValueChange, placeholder, disabled, className, id, name, minuteStep, allowClear, ...ariaProps }: TimePickerProp): import("react/jsx-runtime").JSX.Element;
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { Clock } from "lucide-react";
5
5
  import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
6
6
  import { isValidHhmm, normalizeHhmm } from "../../lib/datetime/index.js";
7
+ import { pickFieldA11y } from "../../lib/field-a11y.js";
7
8
  import { cn } from "../../lib/utils.js";
8
9
  import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
9
10
  import { Input } from "./input.js";
@@ -210,12 +211,16 @@ function TimePicker({
210
211
  id,
211
212
  name,
212
213
  minuteStep = 5,
213
- allowClear = true
214
+ allowClear = true,
215
+ ...ariaProps
214
216
  }) {
215
217
  const { t } = useTranslation();
216
218
  const { timeFormat } = usePickerLocales();
217
219
  const use12h = timeFormat === "12h";
218
220
  const [open, setOpen] = React.useState(false);
221
+ const fieldA11y = pickFieldA11y(ariaProps);
222
+ const reactId = React.useId();
223
+ const dialogId = `${id ?? reactId}-dialog`;
219
224
  const [internal, setInternal] = React.useState(defaultValue ?? "");
220
225
  const isControlled = controlledValue !== void 0;
221
226
  const value = isControlled ? controlledValue : internal;
@@ -246,6 +251,8 @@ function TimePicker({
246
251
  role: "combobox",
247
252
  "aria-expanded": open,
248
253
  "aria-haspopup": "dialog",
254
+ "aria-controls": open ? dialogId : void 0,
255
+ ...fieldA11y,
249
256
  className: "tabular-nums",
250
257
  allowClear,
251
258
  onClear: clear,
@@ -285,6 +292,9 @@ function TimePicker({
285
292
  /* @__PURE__ */ jsx(
286
293
  PopoverContent,
287
294
  {
295
+ id: dialogId,
296
+ role: "dialog",
297
+ "aria-label": t("dataEntry.timePicker.openPicker") ?? "Time picker",
288
298
  className: "w-auto p-0",
289
299
  align: "end",
290
300
  onOpenAutoFocus: (event) => event.preventDefault(),
@@ -1,3 +1,3 @@
1
1
  import type { TransferProp } from "../../props/components/data-entry.prop.js";
2
2
  export type { TransferProp, TransferProp as TransferProps, TransferItemProp, } from "../../props/components/data-entry.prop.js";
3
- export declare function Transfer({ dataSource, targetKeys, onValueChange, titles, showSearch, oneWay, disabled, className, selectedKeys: selectedKeysProp, onSelectChange, }: TransferProp): import("react/jsx-runtime").JSX.Element;
3
+ export declare function Transfer({ dataSource, targetKeys, onValueChange, titles, showSearch, oneWay, disabled, id, className, selectedKeys: selectedKeysProp, onSelectChange, ...ariaProps }: TransferProp): import("react/jsx-runtime").JSX.Element;
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { ChevronLeft, ChevronRight } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
7
8
  import { Button } from "../general/button.js";
8
9
  import { ScrollArea } from "../data-display/scroll-area.js";
9
10
  import { Checkbox } from "./checkbox.js";
@@ -104,11 +105,14 @@ function Transfer({
104
105
  showSearch,
105
106
  oneWay,
106
107
  disabled,
108
+ id,
107
109
  className,
108
110
  selectedKeys: selectedKeysProp,
109
- onSelectChange
111
+ onSelectChange,
112
+ ...ariaProps
110
113
  }) {
111
114
  const { t } = useTranslation();
115
+ const groupA11y = pickGroupFieldA11y(ariaProps);
112
116
  const [internalSelected, setInternalSelected] = React.useState([[], []]);
113
117
  const selected = selectedKeysProp ?? internalSelected;
114
118
  const sourceItems = dataSource.filter((item) => !targetKeys.includes(item.key));
@@ -130,64 +134,74 @@ function Transfer({
130
134
  };
131
135
  const leftTitle = titles?.[0] ?? t("dataEntry.transfer.source");
132
136
  const rightTitle = titles?.[1] ?? t("dataEntry.transfer.target");
133
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-stretch gap-3", className), children: [
134
- /* @__PURE__ */ jsx(
135
- TransferPanel,
136
- {
137
- direction: "left",
138
- title: leftTitle,
139
- items: sourceItems,
140
- selectedKeys: selected[0],
141
- onSelectChange: (keys) => setSelected(0, keys),
142
- onSelectAll: (checked) => setSelected(0, checked ? sourceItems.filter((i) => !i.disabled).map((i) => i.key) : []),
143
- showSearch,
144
- disabled,
145
- searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
146
- emptyText: t("dataEntry.transfer.empty")
147
- }
148
- ),
149
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center gap-2", children: [
150
- /* @__PURE__ */ jsx(
151
- Button,
152
- {
153
- type: "button",
154
- size: "icon",
155
- variant: "outline",
156
- disabled: Boolean(disabled) || selected[0].length === 0,
157
- "aria-label": t("dataEntry.transfer.moveRight"),
158
- onClick: () => move("right"),
159
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
160
- }
161
- ),
162
- !oneWay && /* @__PURE__ */ jsx(
163
- Button,
164
- {
165
- type: "button",
166
- size: "icon",
167
- variant: "outline",
168
- disabled: Boolean(disabled) || selected[1].length === 0,
169
- "aria-label": t("dataEntry.transfer.moveLeft"),
170
- onClick: () => move("left"),
171
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
172
- }
173
- )
174
- ] }),
175
- /* @__PURE__ */ jsx(
176
- TransferPanel,
177
- {
178
- direction: "right",
179
- title: rightTitle,
180
- items: targetItems,
181
- selectedKeys: selected[1],
182
- onSelectChange: (keys) => setSelected(1, keys),
183
- onSelectAll: (checked) => setSelected(1, checked ? targetItems.filter((i) => !i.disabled).map((i) => i.key) : []),
184
- showSearch,
185
- disabled,
186
- searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
187
- emptyText: t("dataEntry.transfer.empty")
188
- }
189
- )
190
- ] });
137
+ return /* @__PURE__ */ jsxs(
138
+ "div",
139
+ {
140
+ role: "group",
141
+ id,
142
+ ...groupA11y,
143
+ "aria-disabled": disabled ? true : void 0,
144
+ className: cn("flex flex-wrap items-stretch gap-3", className),
145
+ children: [
146
+ /* @__PURE__ */ jsx(
147
+ TransferPanel,
148
+ {
149
+ direction: "left",
150
+ title: leftTitle,
151
+ items: sourceItems,
152
+ selectedKeys: selected[0],
153
+ onSelectChange: (keys) => setSelected(0, keys),
154
+ onSelectAll: (checked) => setSelected(0, checked ? sourceItems.filter((i) => !i.disabled).map((i) => i.key) : []),
155
+ showSearch,
156
+ disabled,
157
+ searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
158
+ emptyText: t("dataEntry.transfer.empty")
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center gap-2", children: [
162
+ /* @__PURE__ */ jsx(
163
+ Button,
164
+ {
165
+ type: "button",
166
+ size: "icon",
167
+ variant: "outline",
168
+ disabled: Boolean(disabled) || selected[0].length === 0,
169
+ "aria-label": t("dataEntry.transfer.moveRight"),
170
+ onClick: () => move("right"),
171
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
172
+ }
173
+ ),
174
+ !oneWay && /* @__PURE__ */ jsx(
175
+ Button,
176
+ {
177
+ type: "button",
178
+ size: "icon",
179
+ variant: "outline",
180
+ disabled: Boolean(disabled) || selected[1].length === 0,
181
+ "aria-label": t("dataEntry.transfer.moveLeft"),
182
+ onClick: () => move("left"),
183
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
184
+ }
185
+ )
186
+ ] }),
187
+ /* @__PURE__ */ jsx(
188
+ TransferPanel,
189
+ {
190
+ direction: "right",
191
+ title: rightTitle,
192
+ items: targetItems,
193
+ selectedKeys: selected[1],
194
+ onSelectChange: (keys) => setSelected(1, keys),
195
+ onSelectAll: (checked) => setSelected(1, checked ? targetItems.filter((i) => !i.disabled).map((i) => i.key) : []),
196
+ showSearch,
197
+ disabled,
198
+ searchPlaceholder: t("dataEntry.transfer.searchPlaceholder"),
199
+ emptyText: t("dataEntry.transfer.empty")
200
+ }
201
+ )
202
+ ]
203
+ }
204
+ );
191
205
  }
192
206
  export {
193
207
  Transfer
@@ -1,7 +1,7 @@
1
1
  import type { TreeSelectProp } from "../../props/components/data-entry.prop.js";
2
2
  export type { TreeSelectProp, TreeSelectProp as TreeSelectProps, } from "../../props/components/data-entry.prop.js";
3
3
  export { SHOW_CHILD, SHOW_PARENT, SHOW_ALL } from "./tree-select-strategy.js";
4
- declare function TreeSelectRoot({ treeData: treeDataProp, value, defaultValue, onValueChange, multiple, treeCheckable, treeCheckStrictly, showSearch, showCheckedStrategy, treeDefaultExpandAll, placeholder, disabled, allowClear, className, id, fieldNames, }: TreeSelectProp): import("react/jsx-runtime").JSX.Element;
4
+ declare function TreeSelectRoot({ treeData: treeDataProp, value, defaultValue, onValueChange, multiple, treeCheckable, treeCheckStrictly, showSearch, showCheckedStrategy, treeDefaultExpandAll, placeholder, disabled, allowClear, className, id, fieldNames, ...ariaProps }: TreeSelectProp): import("react/jsx-runtime").JSX.Element;
5
5
  export declare const TreeSelect: typeof TreeSelectRoot & {
6
6
  SHOW_CHILD: "SHOW_CHILD";
7
7
  SHOW_PARENT: "SHOW_PARENT";
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { ChevronDown, ChevronRight, ChevronsUpDown, X } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import { pickFieldA11y } from "../../lib/field-a11y.js";
7
8
  import { controlOpenRingClass } from "../../lib/control-styles.js";
8
9
  import { Button } from "../general/button.js";
9
10
  import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
@@ -57,9 +58,13 @@ function TreeSelectRoot({
57
58
  allowClear = true,
58
59
  className,
59
60
  id,
60
- fieldNames
61
+ fieldNames,
62
+ ...ariaProps
61
63
  }) {
62
64
  const { t } = useTranslation();
65
+ const fieldA11y = pickFieldA11y(ariaProps);
66
+ const reactId = React.useId();
67
+ const treeId = `${id ?? reactId}-tree`;
63
68
  const options = React.useMemo(
64
69
  () => normalizeTreeOptions(treeDataProp, fieldNames),
65
70
  [treeDataProp, fieldNames]
@@ -177,6 +182,9 @@ function TreeSelectRoot({
177
182
  variant: "outline",
178
183
  role: "combobox",
179
184
  "aria-expanded": open,
185
+ "aria-haspopup": "tree",
186
+ "aria-controls": open ? treeId : void 0,
187
+ ...fieldA11y,
180
188
  disabled,
181
189
  className: cn(
182
190
  "w-full justify-between font-normal",
@@ -211,6 +219,7 @@ function TreeSelectRoot({
211
219
  /* @__PURE__ */ jsx(ScrollArea, { className: "max-h-[min(300px,50vh)]", children: /* @__PURE__ */ jsx(
212
220
  "div",
213
221
  {
222
+ id: treeId,
214
223
  role: "tree",
215
224
  "aria-multiselectable": Boolean(checkable) || Boolean(multiple),
216
225
  className: "p-1",