@cdx-ui/primitives 0.0.1-alpha.29 → 0.0.1-alpha.30

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 (101) hide show
  1. package/lib/commonjs/form-control/createFormError.js +41 -0
  2. package/lib/commonjs/form-control/createFormError.js.map +1 -0
  3. package/lib/commonjs/form-control/createFormErrorIcon.js +18 -0
  4. package/lib/commonjs/form-control/createFormErrorIcon.js.map +1 -0
  5. package/lib/commonjs/form-control/createFormErrorText.js +18 -0
  6. package/lib/commonjs/form-control/createFormErrorText.js.map +1 -0
  7. package/lib/commonjs/form-control/createFormField.js +35 -0
  8. package/lib/commonjs/form-control/createFormField.js.map +1 -0
  9. package/lib/commonjs/form-control/createFormHelper.js +41 -0
  10. package/lib/commonjs/form-control/createFormHelper.js.map +1 -0
  11. package/lib/commonjs/form-control/createFormHelperText.js +18 -0
  12. package/lib/commonjs/form-control/createFormHelperText.js.map +1 -0
  13. package/lib/commonjs/form-control/createFormLabel.js +38 -0
  14. package/lib/commonjs/form-control/createFormLabel.js.map +1 -0
  15. package/lib/commonjs/form-control/createFormRoot.js +21 -0
  16. package/lib/commonjs/form-control/createFormRoot.js.map +1 -0
  17. package/lib/commonjs/form-control/index.js +53 -0
  18. package/lib/commonjs/form-control/index.js.map +1 -0
  19. package/lib/commonjs/form-control/types.js +6 -0
  20. package/lib/commonjs/form-control/types.js.map +1 -0
  21. package/lib/commonjs/index.js +12 -0
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/input/createInputField.js +3 -1
  24. package/lib/commonjs/input/createInputField.js.map +1 -1
  25. package/lib/commonjs/select/createSelectRoot.js +8 -5
  26. package/lib/commonjs/select/createSelectRoot.js.map +1 -1
  27. package/lib/commonjs/select/createSelectTrigger.js +50 -3
  28. package/lib/commonjs/select/createSelectTrigger.js.map +1 -1
  29. package/lib/module/form-control/createFormError.js +35 -0
  30. package/lib/module/form-control/createFormError.js.map +1 -0
  31. package/lib/module/form-control/createFormErrorIcon.js +13 -0
  32. package/lib/module/form-control/createFormErrorIcon.js.map +1 -0
  33. package/lib/module/form-control/createFormErrorText.js +13 -0
  34. package/lib/module/form-control/createFormErrorText.js.map +1 -0
  35. package/lib/module/form-control/createFormField.js +29 -0
  36. package/lib/module/form-control/createFormField.js.map +1 -0
  37. package/lib/module/form-control/createFormHelper.js +35 -0
  38. package/lib/module/form-control/createFormHelper.js.map +1 -0
  39. package/lib/module/form-control/createFormHelperText.js +13 -0
  40. package/lib/module/form-control/createFormHelperText.js.map +1 -0
  41. package/lib/module/form-control/createFormLabel.js +33 -0
  42. package/lib/module/form-control/createFormLabel.js.map +1 -0
  43. package/lib/module/form-control/createFormRoot.js +15 -0
  44. package/lib/module/form-control/createFormRoot.js.map +1 -0
  45. package/lib/module/form-control/index.js +49 -0
  46. package/lib/module/form-control/index.js.map +1 -0
  47. package/lib/module/form-control/types.js +4 -0
  48. package/lib/module/form-control/types.js.map +1 -0
  49. package/lib/module/index.js +1 -0
  50. package/lib/module/index.js.map +1 -1
  51. package/lib/module/input/createInputField.js +4 -2
  52. package/lib/module/input/createInputField.js.map +1 -1
  53. package/lib/module/select/createSelectRoot.js +8 -5
  54. package/lib/module/select/createSelectRoot.js.map +1 -1
  55. package/lib/module/select/createSelectTrigger.js +52 -5
  56. package/lib/module/select/createSelectTrigger.js.map +1 -1
  57. package/lib/typescript/checkbox/useCheckboxRoot.d.ts +5 -0
  58. package/lib/typescript/checkbox/useCheckboxRoot.d.ts.map +1 -1
  59. package/lib/typescript/form-control/createFormError.d.ts +5 -0
  60. package/lib/typescript/form-control/createFormError.d.ts.map +1 -0
  61. package/lib/typescript/form-control/createFormErrorIcon.d.ts +5 -0
  62. package/lib/typescript/form-control/createFormErrorIcon.d.ts.map +1 -0
  63. package/lib/typescript/form-control/createFormErrorText.d.ts +5 -0
  64. package/lib/typescript/form-control/createFormErrorText.d.ts.map +1 -0
  65. package/lib/typescript/form-control/createFormField.d.ts +6 -0
  66. package/lib/typescript/form-control/createFormField.d.ts.map +1 -0
  67. package/lib/typescript/form-control/createFormHelper.d.ts +5 -0
  68. package/lib/typescript/form-control/createFormHelper.d.ts.map +1 -0
  69. package/lib/typescript/form-control/createFormHelperText.d.ts +5 -0
  70. package/lib/typescript/form-control/createFormHelperText.d.ts.map +1 -0
  71. package/lib/typescript/form-control/createFormLabel.d.ts +8 -0
  72. package/lib/typescript/form-control/createFormLabel.d.ts.map +1 -0
  73. package/lib/typescript/form-control/createFormRoot.d.ts +6 -0
  74. package/lib/typescript/form-control/createFormRoot.d.ts.map +1 -0
  75. package/lib/typescript/form-control/index.d.ts +14 -0
  76. package/lib/typescript/form-control/index.d.ts.map +1 -0
  77. package/lib/typescript/form-control/types.d.ts +73 -0
  78. package/lib/typescript/form-control/types.d.ts.map +1 -0
  79. package/lib/typescript/index.d.ts +1 -0
  80. package/lib/typescript/index.d.ts.map +1 -1
  81. package/lib/typescript/input/createInputField.d.ts.map +1 -1
  82. package/lib/typescript/select/createSelectRoot.d.ts.map +1 -1
  83. package/lib/typescript/select/createSelectTrigger.d.ts.map +1 -1
  84. package/lib/typescript/select/types.d.ts +4 -0
  85. package/lib/typescript/select/types.d.ts.map +1 -1
  86. package/package.json +2 -2
  87. package/src/form-control/createFormError.tsx +32 -0
  88. package/src/form-control/createFormErrorIcon.tsx +9 -0
  89. package/src/form-control/createFormErrorText.tsx +9 -0
  90. package/src/form-control/createFormField.tsx +27 -0
  91. package/src/form-control/createFormHelper.tsx +28 -0
  92. package/src/form-control/createFormHelperText.tsx +9 -0
  93. package/src/form-control/createFormLabel.tsx +30 -0
  94. package/src/form-control/createFormRoot.tsx +13 -0
  95. package/src/form-control/index.tsx +71 -0
  96. package/src/form-control/types.tsx +92 -0
  97. package/src/index.ts +1 -0
  98. package/src/input/createInputField.tsx +8 -2
  99. package/src/select/createSelectRoot.tsx +7 -3
  100. package/src/select/createSelectTrigger.tsx +55 -4
  101. package/src/select/types.ts +4 -0
@@ -6,7 +6,7 @@ import {
6
6
  Platform,
7
7
  type TextInputKeyPressEvent,
8
8
  } from 'react-native';
9
- import { mergeRefs, useFormControl } from '@cdx-ui/utils';
9
+ import { mergeRefs, useFormControl, useFormControlContext } from '@cdx-ui/utils';
10
10
  import { dataAttributes } from '../utils/dataAttributes';
11
11
  import { useInputContext } from './context';
12
12
  import type { IInputProps } from './types';
@@ -47,12 +47,17 @@ export const createInputField = <T,>(BaseInputField: React.ComponentType<T>) =>
47
47
  id: props.id,
48
48
  });
49
49
 
50
+ const field = useFormControlContext();
51
+
50
52
  const handleFocus = (focusState: boolean, callback: any) => {
51
53
  setIsFocused(focusState);
52
54
  callback();
53
55
  };
54
56
 
55
- const mergedRef = mergeRefs(ref, inputFieldRef);
57
+ const mergedRef =
58
+ Platform.OS === 'web'
59
+ ? mergeRefs(ref, inputFieldRef)
60
+ : mergeRefs(ref, inputFieldRef, field.inputRef);
56
61
 
57
62
  const isEffectivelyDisabled = isDisabled || inputProps.disabled;
58
63
 
@@ -67,6 +72,7 @@ export const createInputField = <T,>(BaseInputField: React.ComponentType<T>) =>
67
72
 
68
73
  return (
69
74
  <BaseInputField
75
+ {...inputProps}
70
76
  {...(props as T)}
71
77
  type={type}
72
78
  {...dataAttributes({
@@ -68,9 +68,11 @@ export const createSelectRoot = <T,>(BaseRoot: React.ComponentType<T>) =>
68
68
  const [activeValue, setActiveValue] = useState<string | undefined>(undefined);
69
69
 
70
70
  const triggerRef = useRef<any>(null);
71
- const id = useId();
72
- const triggerId = `select-trigger-${id}`;
73
- const contentId = `select-content-${id}`;
71
+ const reactId = useId();
72
+ /** Align with `useFormControl` default id (`${field.id}-input`) when inside `Form.Field`. */
73
+ const triggerId = formControlProps.id ?? `select-trigger-${reactId}`;
74
+ const contentId = `select-content-${reactId}`;
75
+ const ariaDescribedBy = formControlProps['aria-describedby'];
74
76
 
75
77
  const contextValue = useMemo(
76
78
  () => ({
@@ -90,6 +92,7 @@ export const createSelectRoot = <T,>(BaseRoot: React.ComponentType<T>) =>
90
92
  activeValue,
91
93
  setActiveValue,
92
94
  accessibilityLabel,
95
+ ariaDescribedBy,
93
96
  }),
94
97
  [
95
98
  open,
@@ -106,6 +109,7 @@ export const createSelectRoot = <T,>(BaseRoot: React.ComponentType<T>) =>
106
109
  triggerId,
107
110
  activeValue,
108
111
  accessibilityLabel,
112
+ ariaDescribedBy,
109
113
  ],
110
114
  );
111
115
 
@@ -1,7 +1,7 @@
1
1
  import type React from 'react';
2
- import { forwardRef, useCallback, useMemo } from 'react';
2
+ import { forwardRef, useCallback, useLayoutEffect, useMemo, useRef } from 'react';
3
3
  import { type GestureResponderEvent, Platform } from 'react-native';
4
- import { composeEventHandlers, mergeRefs } from '@cdx-ui/utils';
4
+ import { composeEventHandlers, mergeRefs, useFormControlContext } from '@cdx-ui/utils';
5
5
  import { useFocus, useFocusRing } from '@react-native-aria/focus';
6
6
  import { useHover, usePress } from '@react-native-aria/interactions';
7
7
  import type { InteractionState } from '../types';
@@ -19,6 +19,7 @@ export const createSelectTrigger = <T,>(BaseTrigger: React.ComponentType<T>) =>
19
19
  isFocused: isFocusedProp,
20
20
  isFocusVisible: isFocusVisibleProp,
21
21
  isDisabled: isDisabledProp,
22
+ 'aria-describedby': ariaDescribedByProp,
22
23
  ...props
23
24
  }: Omit<ISelectTriggerProps, 'children'> & {
24
25
  children?: ((state: InteractionState) => React.ReactNode) | React.ReactNode;
@@ -37,8 +38,11 @@ export const createSelectTrigger = <T,>(BaseTrigger: React.ComponentType<T>) =>
37
38
  triggerRef,
38
39
  activeValue,
39
40
  accessibilityLabel,
41
+ ariaDescribedBy: ariaDescribedByFromField,
40
42
  } = useSelectContext();
41
43
 
44
+ const field = useFormControlContext();
45
+
42
46
  const disabled = contextDisabled || !!isDisabledProp;
43
47
 
44
48
  const { isFocusVisible, focusProps: focusRingProps }: any = useFocusRing();
@@ -101,7 +105,53 @@ export const createSelectTrigger = <T,>(BaseTrigger: React.ComponentType<T>) =>
101
105
  ],
102
106
  );
103
107
 
104
- const mergedRef = mergeRefs(ref, triggerRef);
108
+ /**
109
+ * Native: `Pressable` has no usable programmatic `.focus()` (unlike `TextInput`).
110
+ * `Form.Label` calls `focusInput()` → we register a bridge on `field.inputRef` that tries
111
+ * native focus, then opens the list (expected “tap label” behavior for a select).
112
+ */
113
+ const labelFocusGateRef = useRef({ disabled: false, readOnly: false });
114
+ labelFocusGateRef.current = {
115
+ disabled: !!disabled,
116
+ readOnly: !!contextReadOnly,
117
+ };
118
+
119
+ const labelFocusBridge = useMemo(
120
+ () => ({
121
+ focus() {
122
+ const { disabled: isOff, readOnly: isRO } = labelFocusGateRef.current;
123
+ if (isOff || isRO) {
124
+ return;
125
+ }
126
+ const node = triggerRef.current as { focus?: () => void } | null;
127
+ if (node != null && typeof node.focus === 'function') {
128
+ node.focus();
129
+ }
130
+ setOpen(true);
131
+ },
132
+ }),
133
+ [setOpen],
134
+ );
135
+
136
+ useLayoutEffect(() => {
137
+ if (Platform.OS === 'web' || !field.inputRef) {
138
+ return;
139
+ }
140
+ const r = field.inputRef;
141
+ r.current = labelFocusBridge as unknown as typeof r.current;
142
+ return () => {
143
+ if (r.current === labelFocusBridge) {
144
+ r.current = null;
145
+ }
146
+ };
147
+ }, [field.inputRef, labelFocusBridge]);
148
+
149
+ const mergedRef =
150
+ Platform.OS === 'web' ? mergeRefs(ref, triggerRef) : mergeRefs(ref, triggerRef);
151
+
152
+ const ariaDescribedBy = [ariaDescribedByFromField, ariaDescribedByProp]
153
+ .filter(Boolean)
154
+ .join(' ');
105
155
 
106
156
  const webKeyboardProps =
107
157
  Platform.OS === 'web'
@@ -123,7 +173,7 @@ export const createSelectTrigger = <T,>(BaseTrigger: React.ComponentType<T>) =>
123
173
  aria-required={contextRequired || undefined}
124
174
  aria-invalid={contextInvalid || undefined}
125
175
  aria-readonly={contextReadOnly || undefined}
126
- id={triggerId}
176
+ aria-describedby={ariaDescribedBy || undefined}
127
177
  {...dataAttributes({
128
178
  hover: interactionState.hover,
129
179
  focus: interactionState.focus,
@@ -138,6 +188,7 @@ export const createSelectTrigger = <T,>(BaseTrigger: React.ComponentType<T>) =>
138
188
  })}
139
189
  disabled={disabled}
140
190
  {...(props as T)}
191
+ id={triggerId}
141
192
  onPress={composeEventHandlers(props?.onPress, handlePress)}
142
193
  onPressIn={composeEventHandlers(props?.onPressIn, pressProps.onPressIn)}
143
194
  onPressOut={composeEventHandlers(props?.onPressOut, pressProps.onPressOut)}
@@ -31,6 +31,8 @@ export interface ISelectTriggerProps extends PressableProps {
31
31
  isFocused?: boolean;
32
32
  isFocusVisible?: boolean;
33
33
  isDisabled?: boolean;
34
+ /** @platform web — merged with form field helper/error ids */
35
+ 'aria-describedby'?: string;
34
36
  }
35
37
 
36
38
  export interface ISelectValueProps {
@@ -88,6 +90,8 @@ export interface SelectContextValue {
88
90
  activeValue: string | undefined;
89
91
  setActiveValue: (value: string | undefined) => void;
90
92
  accessibilityLabel: string | undefined;
93
+ /** From `Form.Field` via `useFormControl` on the select root (helper/error `aria-describedby`). */
94
+ ariaDescribedBy?: string;
91
95
  }
92
96
 
93
97
  export type ISelectComponentType<