@octanejs/base-ui 0.1.1

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/package.json +47 -0
  4. package/src/avatar.ts +308 -0
  5. package/src/checkbox-group.ts +183 -0
  6. package/src/checkbox.ts +578 -0
  7. package/src/field.ts +676 -0
  8. package/src/fieldset.ts +150 -0
  9. package/src/form.ts +183 -0
  10. package/src/index.ts +23 -0
  11. package/src/input.ts +9 -0
  12. package/src/internal.ts +42 -0
  13. package/src/merge-props.ts +2 -0
  14. package/src/meter.ts +204 -0
  15. package/src/number-field.ts +978 -0
  16. package/src/progress.ts +260 -0
  17. package/src/radio-group.ts +285 -0
  18. package/src/radio.ts +401 -0
  19. package/src/separator.ts +43 -0
  20. package/src/slider.ts +1617 -0
  21. package/src/switch.ts +374 -0
  22. package/src/toggle-group.ts +136 -0
  23. package/src/toggle.ts +145 -0
  24. package/src/use-render.ts +40 -0
  25. package/src/utils/CheckboxGroupContext.ts +30 -0
  26. package/src/utils/CompositeRootContext.ts +27 -0
  27. package/src/utils/DirectionContext.ts +15 -0
  28. package/src/utils/RadioGroupContext.ts +27 -0
  29. package/src/utils/ToggleGroupContext.ts +33 -0
  30. package/src/utils/addEventListener.ts +13 -0
  31. package/src/utils/areArraysEqual.ts +11 -0
  32. package/src/utils/clamp.ts +8 -0
  33. package/src/utils/composeRefs.ts +53 -0
  34. package/src/utils/composite/CompositeItem.ts +56 -0
  35. package/src/utils/composite/CompositeList.ts +190 -0
  36. package/src/utils/composite/CompositeListContext.ts +23 -0
  37. package/src/utils/composite/CompositeRoot.ts +132 -0
  38. package/src/utils/composite/keys.ts +179 -0
  39. package/src/utils/composite/list-utils.ts +78 -0
  40. package/src/utils/composite/useCompositeItem.ts +58 -0
  41. package/src/utils/composite/useCompositeListItem.ts +108 -0
  42. package/src/utils/composite/useCompositeRoot.ts +346 -0
  43. package/src/utils/contains.ts +22 -0
  44. package/src/utils/createChangeEventDetails.ts +81 -0
  45. package/src/utils/field/FieldItemContext.ts +13 -0
  46. package/src/utils/field/FieldRootContext.ts +98 -0
  47. package/src/utils/field/FormContext.ts +37 -0
  48. package/src/utils/field/LabelableContext.ts +32 -0
  49. package/src/utils/field/LabelableProvider.ts +108 -0
  50. package/src/utils/field/constants.ts +80 -0
  51. package/src/utils/field/getCombinedFieldValidityData.ts +16 -0
  52. package/src/utils/field/useAriaLabelledBy.ts +86 -0
  53. package/src/utils/field/useFieldControlRegistration.ts +179 -0
  54. package/src/utils/field/useFieldValidation.ts +311 -0
  55. package/src/utils/field/useLabel.ts +95 -0
  56. package/src/utils/field/useLabelableId.ts +99 -0
  57. package/src/utils/field/useRegisterFieldControl.ts +41 -0
  58. package/src/utils/formatNumber.ts +44 -0
  59. package/src/utils/getDefaultFormSubmitter.ts +19 -0
  60. package/src/utils/getElementRef.ts +13 -0
  61. package/src/utils/getStateAttributesProps.ts +34 -0
  62. package/src/utils/matchesFocusVisible.ts +16 -0
  63. package/src/utils/mergeObjects.ts +18 -0
  64. package/src/utils/mergeProps.ts +206 -0
  65. package/src/utils/noop.ts +4 -0
  66. package/src/utils/number/constants.ts +4 -0
  67. package/src/utils/number/parse.ts +227 -0
  68. package/src/utils/number/types.ts +25 -0
  69. package/src/utils/number/validate.ts +129 -0
  70. package/src/utils/owner.ts +9 -0
  71. package/src/utils/platform.ts +15 -0
  72. package/src/utils/resolveAriaLabelledBy.ts +11 -0
  73. package/src/utils/resolveClassName.ts +8 -0
  74. package/src/utils/resolveRef.ts +10 -0
  75. package/src/utils/resolveStyle.ts +10 -0
  76. package/src/utils/serializeValue.ts +15 -0
  77. package/src/utils/slider/asc.ts +4 -0
  78. package/src/utils/slider/getMidpoint.ts +14 -0
  79. package/src/utils/slider/getPushedThumbValues.ts +73 -0
  80. package/src/utils/slider/getSliderValue.ts +27 -0
  81. package/src/utils/slider/replaceArrayItemAtIndex.ts +8 -0
  82. package/src/utils/slider/resolveThumbCollision.ts +178 -0
  83. package/src/utils/slider/roundValueToStep.ts +22 -0
  84. package/src/utils/slider/validateMinimumDistance.ts +22 -0
  85. package/src/utils/slider/valueArrayToPercentages.ts +11 -0
  86. package/src/utils/stringifyLocale.ts +10 -0
  87. package/src/utils/useAnimationFrame.ts +59 -0
  88. package/src/utils/useAnimationsFinished.ts +100 -0
  89. package/src/utils/useBaseUiId.ts +19 -0
  90. package/src/utils/useButton.ts +217 -0
  91. package/src/utils/useCheckboxGroupParent.ts +113 -0
  92. package/src/utils/useControlled.ts +42 -0
  93. package/src/utils/useFocusableWhenDisabled.ts +84 -0
  94. package/src/utils/useForcedRerendering.ts +11 -0
  95. package/src/utils/useIsHydrating.ts +9 -0
  96. package/src/utils/useOpenChangeComplete.ts +46 -0
  97. package/src/utils/usePressAndHold.ts +35 -0
  98. package/src/utils/useRefWithInit.ts +23 -0
  99. package/src/utils/useRegisteredLabelId.ts +32 -0
  100. package/src/utils/useRenderElement.ts +165 -0
  101. package/src/utils/useStableCallback.ts +26 -0
  102. package/src/utils/useTimeout.ts +51 -0
  103. package/src/utils/useTransitionStatus.ts +115 -0
  104. package/src/utils/useValueAsRef.ts +25 -0
  105. package/src/utils/useValueChanged.ts +37 -0
  106. package/src/utils/valueToPercent.ts +4 -0
  107. package/src/utils/visuallyHidden.ts +24 -0
@@ -0,0 +1,260 @@
1
+ // Ported from .base-ui/packages/react/src/progress/ (v1.6.0): root/ProgressRoot,
2
+ // root/ProgressRootContext, root/stateAttributesMapping, track/ProgressTrack,
3
+ // indicator/ProgressIndicator, value/ProgressValue, label/ProgressLabel — plus its
4
+ // `index.parts` (the `Progress` namespace).
5
+ //
6
+ // A progress bar. `Progress.Root` (`role="progressbar"`) owns the value/status and aria;
7
+ // the `status` state ('indeterminate' | 'progressing' | 'complete') is exposed on every
8
+ // part as `data-progressing`/`data-complete`/`data-indeterminate`. Base UI uses a PLAIN
9
+ // React context — ported as a plain octane createContext.
10
+ import { createContext, createElement, useContext, useMemo, useState } from 'octane';
11
+
12
+ import { S, subSlot } from './internal';
13
+ import { useRenderElement, type RenderProp } from './utils/useRenderElement';
14
+ import type { StateAttributesMapping } from './utils/getStateAttributesProps';
15
+ import { useRegisteredLabelId } from './utils/useRegisteredLabelId';
16
+ import { useValueAsRef } from './utils/useValueAsRef';
17
+ import { valueToPercent } from './utils/valueToPercent';
18
+ import { formatNumberValue } from './utils/formatNumber';
19
+ import { visuallyHidden } from './utils/visuallyHidden';
20
+
21
+ export type ProgressStatus = 'indeterminate' | 'progressing' | 'complete';
22
+
23
+ export interface ProgressRootState {
24
+ status: ProgressStatus;
25
+ }
26
+
27
+ // Custom state→data-* mapping: `status` maps to a presence attribute per value, not the
28
+ // default `data-status="<value>"`.
29
+ const progressStateAttributesMapping: StateAttributesMapping<ProgressRootState> = {
30
+ status(value: ProgressStatus): Record<string, string> | null {
31
+ if (value === 'progressing') {
32
+ return { 'data-progressing': '' };
33
+ }
34
+ if (value === 'complete') {
35
+ return { 'data-complete': '' };
36
+ }
37
+ if (value === 'indeterminate') {
38
+ return { 'data-indeterminate': '' };
39
+ }
40
+ return null;
41
+ },
42
+ };
43
+
44
+ // --- Context -----------------------------------------------------------------
45
+
46
+ export interface ProgressRootContextValue {
47
+ formattedValue: string;
48
+ max: number;
49
+ min: number;
50
+ value: number | null;
51
+ setLabelId: (id: string | undefined) => void;
52
+ state: ProgressRootState;
53
+ status: ProgressStatus;
54
+ }
55
+
56
+ const ProgressRootContext = createContext<ProgressRootContextValue | undefined>(undefined);
57
+
58
+ function useProgressRootContext(): ProgressRootContextValue {
59
+ const context = useContext(ProgressRootContext);
60
+ if (context === undefined) {
61
+ throw new Error(
62
+ 'Base UI: ProgressRootContext is missing. Progress parts must be placed within <Progress.Root>.',
63
+ );
64
+ }
65
+ return context;
66
+ }
67
+
68
+ function getDefaultAriaValueText(formattedValue: string | null, value: number | null): string {
69
+ if (value == null) {
70
+ return 'indeterminate progress';
71
+ }
72
+ return formattedValue || `${value}%`;
73
+ }
74
+
75
+ // --- Root --------------------------------------------------------------------
76
+
77
+ export interface ProgressRootProps {
78
+ 'aria-valuetext'?: string;
79
+ format?: Intl.NumberFormatOptions;
80
+ getAriaValueText?: (formattedValue: string | null, value: number | null) => string;
81
+ locale?: Intl.LocalesArgument;
82
+ max?: number;
83
+ min?: number;
84
+ value: number | null;
85
+ render?: RenderProp<ProgressRootState>;
86
+ className?: string | ((state: ProgressRootState) => string | undefined);
87
+ style?: Record<string, any> | ((state: ProgressRootState) => Record<string, any> | undefined);
88
+ ref?: any;
89
+ [key: string]: any;
90
+ }
91
+
92
+ function ProgressRoot(props: ProgressRootProps): any {
93
+ const slot = S('ProgressRoot');
94
+ const {
95
+ format,
96
+ getAriaValueText = getDefaultAriaValueText,
97
+ locale,
98
+ max = 100,
99
+ min = 0,
100
+ value,
101
+ render,
102
+ className,
103
+ children,
104
+ style,
105
+ ref,
106
+ ...elementProps
107
+ } = props;
108
+
109
+ const [labelId, setLabelId] = useState<string | undefined>(undefined, subSlot(slot, 'labelId'));
110
+
111
+ const formatOptionsRef = useValueAsRef<Intl.NumberFormatOptions | undefined>(
112
+ format,
113
+ subSlot(slot, 'fmt'),
114
+ );
115
+
116
+ let status: ProgressStatus = 'indeterminate';
117
+ if (Number.isFinite(value)) {
118
+ status = value === max ? 'complete' : 'progressing';
119
+ }
120
+
121
+ const formattedValue = formatNumberValue(value, locale, formatOptionsRef.current);
122
+
123
+ const state: ProgressRootState = useMemo(() => ({ status }), [status], subSlot(slot, 'state'));
124
+
125
+ const defaultProps = {
126
+ 'aria-labelledby': labelId,
127
+ 'aria-valuemax': max,
128
+ 'aria-valuemin': min,
129
+ 'aria-valuenow': value ?? undefined,
130
+ 'aria-valuetext': getAriaValueText(formattedValue, value),
131
+ role: 'progressbar',
132
+ children: [
133
+ children,
134
+ createElement('span', { role: 'presentation', style: visuallyHidden, children: 'x' }),
135
+ ],
136
+ };
137
+
138
+ const contextValue: ProgressRootContextValue = useMemo(
139
+ () => ({ formattedValue, max, min, setLabelId, state, status, value }),
140
+ [formattedValue, max, min, setLabelId, state, status, value],
141
+ subSlot(slot, 'ctx'),
142
+ );
143
+
144
+ const element = useRenderElement(
145
+ 'div',
146
+ { render, className, style },
147
+ {
148
+ state,
149
+ ref,
150
+ props: [defaultProps, elementProps],
151
+ stateAttributesMapping: progressStateAttributesMapping,
152
+ },
153
+ subSlot(slot, 're'),
154
+ );
155
+
156
+ return createElement(ProgressRootContext.Provider, { value: contextValue, children: element });
157
+ }
158
+
159
+ // --- Track -------------------------------------------------------------------
160
+
161
+ function ProgressTrack(props: any): any {
162
+ const slot = S('ProgressTrack');
163
+ const { render, className, style, ref, ...elementProps } = props;
164
+ const { state } = useProgressRootContext();
165
+ return useRenderElement(
166
+ 'div',
167
+ { render, className, style },
168
+ { state, ref, props: elementProps, stateAttributesMapping: progressStateAttributesMapping },
169
+ subSlot(slot, 're'),
170
+ );
171
+ }
172
+
173
+ // --- Indicator ---------------------------------------------------------------
174
+
175
+ function ProgressIndicator(props: any): any {
176
+ const slot = S('ProgressIndicator');
177
+ const { render, className, style, ref, ...elementProps } = props;
178
+ const { max, min, value, state } = useProgressRootContext();
179
+
180
+ const percentageValue =
181
+ Number.isFinite(value) && value !== null ? valueToPercent(value, min, max) : null;
182
+ const indicatorStyle =
183
+ percentageValue == null
184
+ ? {}
185
+ : { insetInlineStart: 0, height: 'inherit', width: `${percentageValue}%` };
186
+
187
+ return useRenderElement(
188
+ 'div',
189
+ { render, className, style },
190
+ {
191
+ state,
192
+ ref,
193
+ props: [{ style: indicatorStyle }, elementProps],
194
+ stateAttributesMapping: progressStateAttributesMapping,
195
+ },
196
+ subSlot(slot, 're'),
197
+ );
198
+ }
199
+
200
+ // --- Value -------------------------------------------------------------------
201
+
202
+ function ProgressValue(props: any): any {
203
+ const slot = S('ProgressValue');
204
+ const { className, render, children, style, ref, ...elementProps } = props;
205
+ const { value, formattedValue, state } = useProgressRootContext();
206
+
207
+ const formattedValueArg = value == null ? 'indeterminate' : formattedValue;
208
+ const formattedValueDisplay = value == null ? null : formattedValue;
209
+
210
+ return useRenderElement(
211
+ 'span',
212
+ { render, className, style },
213
+ {
214
+ state,
215
+ ref,
216
+ props: [
217
+ {
218
+ 'aria-hidden': true,
219
+ children:
220
+ typeof children === 'function'
221
+ ? children(formattedValueArg, value)
222
+ : formattedValueDisplay,
223
+ },
224
+ elementProps,
225
+ ],
226
+ stateAttributesMapping: progressStateAttributesMapping,
227
+ },
228
+ subSlot(slot, 're'),
229
+ );
230
+ }
231
+
232
+ // --- Label -------------------------------------------------------------------
233
+
234
+ function ProgressLabel(props: any): any {
235
+ const slot = S('ProgressLabel');
236
+ const { render, className, style, id: idProp, ref, ...elementProps } = props;
237
+ const { setLabelId, state } = useProgressRootContext();
238
+ const id = useRegisteredLabelId(idProp, setLabelId, subSlot(slot, 'labelId'));
239
+ return useRenderElement(
240
+ 'span',
241
+ { render, className, style },
242
+ {
243
+ state,
244
+ ref,
245
+ props: [{ id, role: 'presentation' }, elementProps],
246
+ stateAttributesMapping: progressStateAttributesMapping,
247
+ },
248
+ subSlot(slot, 're'),
249
+ );
250
+ }
251
+
252
+ // --- Namespace (mirrors `export * as Progress`) ------------------------------
253
+
254
+ export const Progress = {
255
+ Root: ProgressRoot,
256
+ Track: ProgressTrack,
257
+ Indicator: ProgressIndicator,
258
+ Value: ProgressValue,
259
+ Label: ProgressLabel,
260
+ };
@@ -0,0 +1,285 @@
1
+ // Ported from .base-ui/packages/react/src/radio-group/RadioGroup.tsx (v1.6.0). Provides a
2
+ // shared value + roving focus (via CompositeRoot) to a series of <Radio.Root>s, rendering a
3
+ // `<div role="radiogroup">`. octane: forwardRef → ref-as-prop; native events.
4
+ import { createElement, useMemo, useRef, useState } from 'octane';
5
+
6
+ import { S, subSlot } from './internal';
7
+ import { CompositeRoot } from './utils/composite/CompositeRoot';
8
+ import { SHIFT } from './utils/composite/keys';
9
+ import { contains } from './utils/contains';
10
+ import { useBaseUiId } from './utils/useBaseUiId';
11
+ import { useControlled } from './utils/useControlled';
12
+ import { useStableCallback } from './utils/useStableCallback';
13
+ import { useValueChanged } from './utils/useValueChanged';
14
+ import { fieldValidityMapping, type FieldRootState } from './utils/field/constants';
15
+ import { useFieldRootContext } from './utils/field/FieldRootContext';
16
+ import { useFormContext } from './utils/field/FormContext';
17
+ import { useLabelableContext } from './utils/field/LabelableContext';
18
+ import { useRegisterFieldControl } from './utils/field/useRegisterFieldControl';
19
+ import { RadioGroupContext, type RadioGroupContextValue } from './utils/RadioGroupContext';
20
+ import { useFieldsetRootContext } from './fieldset';
21
+
22
+ const MODIFIER_KEYS = [SHIFT];
23
+
24
+ export interface RadioGroupState extends FieldRootState {
25
+ readOnly: boolean;
26
+ required: boolean;
27
+ }
28
+
29
+ export interface RadioGroupProps<Value = any> {
30
+ disabled?: boolean;
31
+ readOnly?: boolean;
32
+ required?: boolean;
33
+ value?: Value;
34
+ defaultValue?: Value;
35
+ onValueChange?: (value: Value, eventDetails: any) => void;
36
+ form?: string;
37
+ name?: string;
38
+ inputRef?: any;
39
+ id?: string;
40
+ render?: any;
41
+ className?: any;
42
+ style?: any;
43
+ ref?: any;
44
+ [key: string]: any;
45
+ }
46
+
47
+ function RadioGroup<Value = any>(props: RadioGroupProps<Value>): any {
48
+ const slot = S('RadioGroup');
49
+ const {
50
+ render,
51
+ className,
52
+ disabled: disabledProp,
53
+ readOnly,
54
+ required,
55
+ onValueChange: onValueChangeProp,
56
+ value: externalValue,
57
+ defaultValue,
58
+ form,
59
+ name: nameProp,
60
+ inputRef: inputRefProp,
61
+ id: idProp,
62
+ style,
63
+ ref,
64
+ ...elementProps
65
+ } = props;
66
+
67
+ const {
68
+ setTouched: setFieldTouched,
69
+ setFocused,
70
+ validationMode,
71
+ name: fieldName,
72
+ disabled: fieldDisabled,
73
+ state: fieldState,
74
+ validation,
75
+ setDirty,
76
+ setFilled,
77
+ validityData,
78
+ } = useFieldRootContext();
79
+ const { labelId } = useLabelableContext();
80
+ const { clearErrors } = useFormContext();
81
+ const fieldsetContext = useFieldsetRootContext(true);
82
+
83
+ const disabled = fieldDisabled || disabledProp;
84
+ const name = fieldName ?? nameProp;
85
+ const id = useBaseUiId(idProp, subSlot(slot, 'id'));
86
+
87
+ const [checkedValue, setCheckedValueUnwrapped] = useControlled<Value>(
88
+ { controlled: externalValue, default: defaultValue, name: 'RadioGroup', state: 'value' },
89
+ subSlot(slot, 'value'),
90
+ );
91
+ const [touched, setTouched] = useState(false, subSlot(slot, 'touched'));
92
+
93
+ const setCheckedValue = useStableCallback(
94
+ (value: Value, eventDetails: any) => {
95
+ onValueChangeProp?.(value, eventDetails);
96
+ if (eventDetails.isCanceled) {
97
+ return;
98
+ }
99
+ setCheckedValueUnwrapped(value);
100
+ },
101
+ subSlot(slot, 'setValue'),
102
+ );
103
+
104
+ const controlRef = useRef<HTMLElement | null>(null, subSlot(slot, 'controlRef'));
105
+ const groupInputRef = useRef<HTMLInputElement | null>(null, subSlot(slot, 'groupInput'));
106
+ const firstEnabledInputRef = useRef<HTMLInputElement | null>(null, subSlot(slot, 'firstInput'));
107
+
108
+ function setInputRef(hiddenInput: HTMLInputElement | null): void | (() => void) {
109
+ let cleanup: void | (() => void) | undefined;
110
+ if (inputRefProp) {
111
+ if (typeof inputRefProp === 'function') {
112
+ cleanup = inputRefProp(hiddenInput);
113
+ } else {
114
+ inputRefProp.current = hiddenInput;
115
+ }
116
+ }
117
+ groupInputRef.current = hiddenInput;
118
+ validation.inputRef.current = hiddenInput;
119
+ return cleanup;
120
+ }
121
+
122
+ const registerControlRef = useStableCallback(
123
+ (element: HTMLElement | null, isDisabled = false) => {
124
+ if (!element) {
125
+ return;
126
+ }
127
+ if (isDisabled) {
128
+ if (controlRef.current === element) {
129
+ controlRef.current = null;
130
+ }
131
+ return;
132
+ }
133
+ if (controlRef.current == null) {
134
+ controlRef.current = element;
135
+ }
136
+ },
137
+ subSlot(slot, 'registerControl'),
138
+ );
139
+
140
+ const registerInputRef = useStableCallback(
141
+ (input: HTMLInputElement | null) => {
142
+ if (!input || input.disabled) {
143
+ return undefined;
144
+ }
145
+ if (!firstEnabledInputRef.current) {
146
+ firstEnabledInputRef.current = input;
147
+ }
148
+ const currentInput = groupInputRef.current;
149
+ if (input.checked || currentInput == null || currentInput.disabled) {
150
+ return setInputRef(input);
151
+ }
152
+ return undefined;
153
+ },
154
+ subSlot(slot, 'registerInput'),
155
+ );
156
+
157
+ const getFormValue = useStableCallback(
158
+ () => {
159
+ const input = groupInputRef.current;
160
+ if (!input || input.disabled || !input.checked) {
161
+ return null;
162
+ }
163
+ return checkedValue ?? null;
164
+ },
165
+ subSlot(slot, 'getFormValue'),
166
+ );
167
+
168
+ useRegisterFieldControl(
169
+ controlRef,
170
+ id,
171
+ checkedValue ?? null,
172
+ getFormValue,
173
+ !disabled,
174
+ nameProp,
175
+ subSlot(slot, 'register'),
176
+ );
177
+
178
+ useValueChanged(
179
+ checkedValue,
180
+ () => {
181
+ clearErrors(name);
182
+ setDirty(checkedValue !== validityData.initialValue);
183
+ setFilled(checkedValue != null);
184
+ validation.change(checkedValue);
185
+ const fallbackInput = firstEnabledInputRef.current;
186
+ if (checkedValue == null && fallbackInput && !fallbackInput.disabled) {
187
+ setInputRef(fallbackInput);
188
+ }
189
+ },
190
+ subSlot(slot, 'valueChanged'),
191
+ );
192
+
193
+ const ariaLabelledby = elementProps['aria-labelledby'] ?? labelId ?? fieldsetContext?.legendId;
194
+
195
+ const state: RadioGroupState = {
196
+ ...fieldState,
197
+ disabled: disabled ?? false,
198
+ required: required ?? false,
199
+ readOnly: readOnly ?? false,
200
+ };
201
+
202
+ const contextValue: RadioGroupContextValue<Value> = useMemo(
203
+ () => ({
204
+ ...fieldState,
205
+ checkedValue,
206
+ disabled,
207
+ form,
208
+ validation,
209
+ name,
210
+ readOnly,
211
+ registerControlRef,
212
+ registerInputRef,
213
+ required,
214
+ setCheckedValue,
215
+ setTouched,
216
+ touched,
217
+ }),
218
+ [
219
+ checkedValue,
220
+ disabled,
221
+ form,
222
+ validation,
223
+ fieldState,
224
+ name,
225
+ readOnly,
226
+ registerControlRef,
227
+ registerInputRef,
228
+ required,
229
+ setCheckedValue,
230
+ setTouched,
231
+ touched,
232
+ ],
233
+ subSlot(slot, 'ctx'),
234
+ );
235
+
236
+ const defaultProps: Record<string, any> = {
237
+ id: idProp,
238
+ role: 'radiogroup',
239
+ 'aria-required': required || undefined,
240
+ 'aria-disabled': disabled || undefined,
241
+ 'aria-readonly': readOnly || undefined,
242
+ 'aria-labelledby': ariaLabelledby,
243
+ onFocus() {
244
+ setFocused(true);
245
+ },
246
+ onBlur(event: any) {
247
+ if (!contains(event.currentTarget, event.relatedTarget)) {
248
+ setFieldTouched(true);
249
+ setFocused(false);
250
+ if (validationMode === 'onBlur') {
251
+ validation.commit(checkedValue);
252
+ }
253
+ }
254
+ },
255
+ onKeyDownCapture(event: any) {
256
+ if (event.key.startsWith('Arrow')) {
257
+ setTouched(true);
258
+ setFocused(true);
259
+ }
260
+ },
261
+ };
262
+
263
+ const compositeRoot = createElement(CompositeRoot, {
264
+ render,
265
+ className,
266
+ style,
267
+ state,
268
+ props: [
269
+ defaultProps,
270
+ elementProps,
271
+ (p: any) => validation.getValidationProps(disabled ?? false, p),
272
+ ],
273
+ refs: [ref],
274
+ stateAttributesMapping: fieldValidityMapping,
275
+ enableHomeAndEndKeys: false,
276
+ modifierKeys: MODIFIER_KEYS,
277
+ });
278
+
279
+ return createElement(RadioGroupContext.Provider, {
280
+ value: contextValue,
281
+ children: compositeRoot,
282
+ });
283
+ }
284
+
285
+ export { RadioGroup };