@hjmds/react-native 1.12.1 → 1.13.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 +15 -0
- package/dist/actions.d.ts +30 -2
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +28 -5
- package/dist/actions.js.map +1 -1
- package/dist/agreement.d.ts +8 -1
- package/dist/agreement.d.ts.map +1 -1
- package/dist/agreement.js +16 -3
- package/dist/agreement.js.map +1 -1
- package/dist/asset.d.ts +15 -2
- package/dist/asset.d.ts.map +1 -1
- package/dist/asset.js +7 -4
- package/dist/asset.js.map +1 -1
- package/dist/bottom-info.d.ts +8 -1
- package/dist/bottom-info.d.ts.map +1 -1
- package/dist/bottom-info.js +4 -2
- package/dist/bottom-info.js.map +1 -1
- package/dist/calendar.d.ts +3 -1
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +2 -2
- package/dist/calendar.js.map +1 -1
- package/dist/carousel.d.ts +8 -1
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +4 -2
- package/dist/carousel.js.map +1 -1
- package/dist/collapsible.d.ts +8 -1
- package/dist/collapsible.d.ts.map +1 -1
- package/dist/collapsible.js +9 -3
- package/dist/collapsible.js.map +1 -1
- package/dist/data-display.d.ts +146 -13
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +58 -29
- package/dist/data-display.js.map +1 -1
- package/dist/date-picker.d.ts +8 -1
- package/dist/date-picker.d.ts.map +1 -1
- package/dist/date-picker.js +10 -3
- package/dist/date-picker.js.map +1 -1
- package/dist/duration-field.d.ts +4 -1
- package/dist/duration-field.d.ts.map +1 -1
- package/dist/duration-field.js +2 -2
- package/dist/duration-field.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +1 -1
- package/dist/feedback.d.ts +89 -14
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +25 -16
- package/dist/feedback.js.map +1 -1
- package/dist/file-picker.d.ts +8 -1
- package/dist/file-picker.d.ts.map +1 -1
- package/dist/file-picker.js +4 -2
- package/dist/file-picker.js.map +1 -1
- package/dist/forms.d.ts +28 -4
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +19 -8
- package/dist/forms.js.map +1 -1
- package/dist/heading.d.ts +8 -1
- package/dist/heading.d.ts.map +1 -1
- package/dist/heading.js +4 -2
- package/dist/heading.js.map +1 -1
- package/dist/image-viewer.d.ts.map +1 -1
- package/dist/image-viewer.js +11 -3
- package/dist/image-viewer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inputs.d.ts +66 -3
- package/dist/inputs.d.ts.map +1 -1
- package/dist/inputs.js +135 -49
- package/dist/inputs.js.map +1 -1
- package/dist/internal/deprecated-style.d.ts +8 -0
- package/dist/internal/deprecated-style.d.ts.map +1 -0
- package/dist/internal/deprecated-style.js +34 -0
- package/dist/internal/deprecated-style.js.map +1 -0
- package/dist/internal/field-frame.d.ts +2 -1
- package/dist/internal/field-frame.d.ts.map +1 -1
- package/dist/internal/field-frame.js +4 -2
- package/dist/internal/field-frame.js.map +1 -1
- package/dist/internal/field-private.d.ts +11 -0
- package/dist/internal/field-private.d.ts.map +1 -0
- package/dist/internal/field-private.js +2 -0
- package/dist/internal/field-private.js.map +1 -0
- package/dist/internal/list-row-private.d.ts +10 -0
- package/dist/internal/list-row-private.d.ts.map +1 -0
- package/dist/internal/list-row-private.js +2 -0
- package/dist/internal/list-row-private.js.map +1 -0
- package/dist/internal/message-reactions.d.ts +21 -0
- package/dist/internal/message-reactions.d.ts.map +1 -0
- package/dist/internal/message-reactions.js +49 -0
- package/dist/internal/message-reactions.js.map +1 -0
- package/dist/internal/recipe-button.d.ts +6 -5
- package/dist/internal/recipe-button.d.ts.map +1 -1
- package/dist/internal/recipe-button.js +22 -7
- package/dist/internal/recipe-button.js.map +1 -1
- package/dist/internal/spinner.d.ts +15 -0
- package/dist/internal/spinner.d.ts.map +1 -0
- package/dist/internal/spinner.js +12 -0
- package/dist/internal/spinner.js.map +1 -0
- package/dist/masonry.d.ts +3 -1
- package/dist/masonry.d.ts.map +1 -1
- package/dist/masonry.js +3 -3
- package/dist/masonry.js.map +1 -1
- package/dist/mentions.d.ts +4 -0
- package/dist/mentions.d.ts.map +1 -1
- package/dist/mentions.js +8 -2
- package/dist/mentions.js.map +1 -1
- package/dist/navigation.d.ts +72 -4
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +39 -21
- package/dist/navigation.js.map +1 -1
- package/dist/number-field.d.ts +21 -0
- package/dist/number-field.d.ts.map +1 -1
- package/dist/number-field.js +7 -2
- package/dist/number-field.js.map +1 -1
- package/dist/overlays.d.ts +30 -2
- package/dist/overlays.d.ts.map +1 -1
- package/dist/overlays.js +100 -18
- package/dist/overlays.js.map +1 -1
- package/dist/primitives.d.ts +19 -3
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +12 -6
- package/dist/primitives.js.map +1 -1
- package/dist/provider-button.d.ts +8 -1
- package/dist/provider-button.d.ts.map +1 -1
- package/dist/provider-button.js +4 -1
- package/dist/provider-button.js.map +1 -1
- package/dist/qr-code.d.ts +4 -1
- package/dist/qr-code.d.ts.map +1 -1
- package/dist/qr-code.js +2 -2
- package/dist/qr-code.js.map +1 -1
- package/dist/reaction-picker.d.ts +4 -2
- package/dist/reaction-picker.d.ts.map +1 -1
- package/dist/reaction-picker.js +18 -6
- package/dist/reaction-picker.js.map +1 -1
- package/dist/saved-items.d.ts +21 -0
- package/dist/saved-items.d.ts.map +1 -0
- package/dist/saved-items.js +16 -0
- package/dist/saved-items.js.map +1 -0
- package/dist/screen-flows.d.ts +300 -0
- package/dist/screen-flows.d.ts.map +1 -0
- package/dist/screen-flows.js +164 -0
- package/dist/screen-flows.js.map +1 -0
- package/dist/screens.d.ts +114 -0
- package/dist/screens.d.ts.map +1 -0
- package/dist/screens.js +80 -0
- package/dist/screens.js.map +1 -0
- package/dist/slider.d.ts +21 -0
- package/dist/slider.d.ts.map +1 -1
- package/dist/slider.js +6 -2
- package/dist/slider.js.map +1 -1
- package/dist/steps.d.ts +8 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +4 -2
- package/dist/steps.js.map +1 -1
- package/dist/tags-input.d.ts +8 -1
- package/dist/tags-input.d.ts.map +1 -1
- package/dist/tags-input.js +11 -5
- package/dist/tags-input.js.map +1 -1
- package/dist/toggle-group.d.ts +8 -1
- package/dist/toggle-group.d.ts.map +1 -1
- package/dist/toggle-group.js +4 -2
- package/dist/toggle-group.js.map +1 -1
- package/dist/top.d.ts +8 -1
- package/dist/top.d.ts.map +1 -1
- package/dist/top.js +4 -1
- package/dist/top.js.map +1 -1
- package/dist/transfer-list.d.ts +8 -1
- package/dist/transfer-list.d.ts.map +1 -1
- package/dist/transfer-list.js +4 -2
- package/dist/transfer-list.js.map +1 -1
- package/dist/upload-item.d.ts +8 -1
- package/dist/upload-item.d.ts.map +1 -1
- package/dist/upload-item.js +4 -2
- package/dist/upload-item.js.map +1 -1
- package/package.json +21 -3
package/dist/inputs.js
CHANGED
|
@@ -9,13 +9,14 @@ import { visibleControlHeight } from "@hjmds/design-contracts/components/design-
|
|
|
9
9
|
import { passwordFieldRecipe, resolvePasswordFieldDescriptor, } from "@hjmds/design-contracts/components/password-field";
|
|
10
10
|
import { getOtpFieldSlotValues, otpFieldRecipe, resolveOtpFieldValue, } from "@hjmds/design-contracts/components/otp-field";
|
|
11
11
|
import { getCheckboxNextState, reconcileCheckboxSelection, resolveControlAccessibleName, resolveInitialRadioValue, resolveInitialTabValue, reconcileRadioSelection, selectionGroupBehaviorDefaults, toggleCheckboxSelection, validateCheckboxSelection, validateRadioSelection, validateSelectionItems, } from "@hjmds/design-contracts/behaviors";
|
|
12
|
-
import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState, } from "react";
|
|
12
|
+
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
|
|
13
13
|
import { ActivityIndicator, Platform, Pressable, Switch as NativeSwitch, Text as NativeText, TextInput, View, } from "react-native";
|
|
14
14
|
import { mixedCheckboxState, useControllableState } from "./internal/state.js";
|
|
15
15
|
import { webChoiceProps, webOnly } from "./internal/web-a11y.js";
|
|
16
16
|
import { logicalTextAlign, minimumTargetStyle, resolveNativeTextScaleProps, } from "./internal/styles.js";
|
|
17
17
|
import { Text } from "./primitives.js";
|
|
18
18
|
import { useHjmNativeTheme } from "./provider.js";
|
|
19
|
+
import { warnDeprecatedStyleProps } from "./internal/deprecated-style.js";
|
|
19
20
|
function resolveFieldAccessibleName(label, accessibilityLabel) {
|
|
20
21
|
const visibleLabel = label?.trim();
|
|
21
22
|
const explicitAccessibleName = accessibilityLabel?.trim();
|
|
@@ -28,10 +29,20 @@ function resolveFieldAccessibleName(label, accessibilityLabel) {
|
|
|
28
29
|
...(visibleLabel ? { visibleLabel } : {}),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
|
-
const FieldRenderer = forwardRef(function FieldRenderer({ label, value, defaultValue = "", onValueChange, description, error, required = false, disabled = false, busy = false, variant = fieldRecipe.defaults.variant, shape, accessibilityLabel, layoutStyle, allowFontScaling, multiline, maxVisibleLines, minVisibleLines, align = fieldRecipe.defaults.align, search, recipeInputStyle, searchSize = searchFieldRecipe.defaults.size, leading, trailing, onBlur, onFocus, ...props }, ref) {
|
|
32
|
+
const FieldRenderer = forwardRef(function FieldRenderer({ label, value, defaultValue = "", onValueChange, description, error, required = false, disabled = false, busy = false, variant = fieldRecipe.defaults.variant, shape, accessibilityLabel, layoutStyle, allowFontScaling, multiline, maxVisibleLines, minVisibleLines, align = fieldRecipe.defaults.align, search, recipeInputStyle, disabledOpacity, searchSize = searchFieldRecipe.defaults.size, leading, trailing, leadingAction, hjmCompactMultiline = false, onBlur, onFocus, onContentSizeChange, onSelectionChange, ...props }, ref) {
|
|
33
|
+
const resolvedDisabledOpacity = disabledOpacity ?? (search ? searchFieldRecipe.states.disabledOpacity : fieldRecipe.disabledOpacity);
|
|
32
34
|
const theme = useHjmNativeTheme();
|
|
33
35
|
const { colors, environment, textScaling } = theme;
|
|
34
36
|
const [focused, setFocused] = useState(false);
|
|
37
|
+
const [contentHeight, setContentHeight] = useState(0);
|
|
38
|
+
const inputRef = useRef(null);
|
|
39
|
+
const restoreFocusRef = useRef(false);
|
|
40
|
+
const selectionRef = useRef(null);
|
|
41
|
+
const attachInput = useCallback((node) => {
|
|
42
|
+
if (node === null)
|
|
43
|
+
restoreFocusRef.current = inputRef.current?.isFocused?.() ?? false;
|
|
44
|
+
inputRef.current = node;
|
|
45
|
+
}, []);
|
|
35
46
|
const [currentValue, setCurrentValue] = useControllableState({
|
|
36
47
|
...(value === undefined ? {} : { value }),
|
|
37
48
|
defaultValue,
|
|
@@ -65,16 +76,39 @@ const FieldRenderer = forwardRef(function FieldRenderer({ label, value, defaultV
|
|
|
65
76
|
? resolveColorReference(searchFieldRecipe.colors.placeholder, theme.palette)
|
|
66
77
|
: colors[fieldRecipe.placeholder.color];
|
|
67
78
|
const textStyle = typography[search ? searchSizing.textVariant : fieldRecipe.textVariant];
|
|
79
|
+
// Native TextInput scales its text without enlarging a fixed frame (BT-QA-020).
|
|
80
|
+
// Size the one-line frame for the same scale instead of capping accessible text.
|
|
81
|
+
const frameTextScale = textScaling.mode === "controlled"
|
|
82
|
+
? textScaling.scale
|
|
83
|
+
: allowFontScaling === false
|
|
84
|
+
? 1
|
|
85
|
+
: Math.min(textScaling.scale, props.maxFontSizeMultiplier && props.maxFontSizeMultiplier > 0 ? props.maxFontSizeMultiplier : Infinity);
|
|
86
|
+
const singleLineMinHeight = Math.ceil(textStyle.lineHeight * frameTextScale + fieldRecipe.paddingVertical * 2 + borderWidth * 2);
|
|
87
|
+
// iOS RCTUITextView updates typingAttributes/placeholder but leaves existing
|
|
88
|
+
// attributed text at its old scale (BT-QA-025). Refresh only the native editor,
|
|
89
|
+
// keeping the field's draft state and restoring focus/selection instead of capping text.
|
|
90
|
+
const editorKey = Platform.OS === "ios" && multiline
|
|
91
|
+
? `multiline-${frameTextScale}`
|
|
92
|
+
: "field";
|
|
93
|
+
// Deps: only an editorKey remount changes the handle; none re-attached callback refs every render.
|
|
94
|
+
useImperativeHandle(ref, () => inputRef.current, [editorKey]);
|
|
95
|
+
useLayoutEffect(() => {
|
|
96
|
+
if (restoreFocusRef.current) {
|
|
97
|
+
inputRef.current?.focus?.();
|
|
98
|
+
const selection = props.selection ?? selectionRef.current;
|
|
99
|
+
if (selection)
|
|
100
|
+
inputRef.current?.setNativeProps?.({ selection });
|
|
101
|
+
restoreFocusRef.current = false;
|
|
102
|
+
}
|
|
103
|
+
}, [editorKey, props.selection]);
|
|
68
104
|
// A composer that should open several lines tall asks in lines, not pixels,
|
|
69
|
-
// so the recipe keeps ownership of line height and vertical padding.
|
|
105
|
+
// so the recipe keeps ownership of line height and vertical padding. Compact: see field-private.ts.
|
|
70
106
|
const minHeight = multiline
|
|
71
107
|
? minVisibleLines === undefined
|
|
72
108
|
? fieldRecipe.multilineMinHeight
|
|
73
|
-
: Math.max(fieldRecipe.multilineMinHeight, textStyle.lineHeight * minVisibleLines +
|
|
109
|
+
: Math.max(hjmCompactMultiline ? fieldRecipe.minHeight : fieldRecipe.multilineMinHeight, textStyle.lineHeight * minVisibleLines +
|
|
74
110
|
fieldRecipe.paddingVertical * 2)
|
|
75
|
-
: search
|
|
76
|
-
? searchSizing.minHeight
|
|
77
|
-
: fieldRecipe.minHeight;
|
|
111
|
+
: Math.max(search ? searchSizing.minHeight : fieldRecipe.minHeight, singleLineMinHeight);
|
|
78
112
|
const controlRadius = radius[search
|
|
79
113
|
? searchFieldRecipe.shapes[resolvedShape]
|
|
80
114
|
: fieldRecipe.shapes[resolvedShape]];
|
|
@@ -88,6 +122,8 @@ const FieldRenderer = forwardRef(function FieldRenderer({ label, value, defaultV
|
|
|
88
122
|
fontWeight: textStyle.fontWeight,
|
|
89
123
|
lineHeight: textStyle.lineHeight,
|
|
90
124
|
minHeight: minHeight - borderWidth * 2,
|
|
125
|
+
// Explicit line bounds opt into content-driven composer sizing, without changing ordinary editors.
|
|
126
|
+
...(multiline && minVisibleLines !== undefined ? { height: Math.max(minHeight - borderWidth * 2, Math.min(currentValue ? contentHeight : 0, textStyle.lineHeight * (resolvedMaxVisibleLines ?? 1000) + fieldRecipe.paddingVertical * 2)) } : {}),
|
|
91
127
|
...(multiline &&
|
|
92
128
|
resolvedMaxVisibleLines !== null &&
|
|
93
129
|
resolvedMaxVisibleLines !== undefined
|
|
@@ -105,16 +141,8 @@ const FieldRenderer = forwardRef(function FieldRenderer({ label, value, defaultV
|
|
|
105
141
|
},
|
|
106
142
|
recipeInputStyle,
|
|
107
143
|
], allowFontScaling);
|
|
108
|
-
return (_jsx(NativeFieldFrame, { ...(visibleLabel === undefined ? {} : { label: visibleLabel }), required: required, ...(error === undefined ? {} : { error }), ...(supportText === undefined ? {} : { description: supportText }), style:
|
|
109
|
-
|
|
110
|
-
opacity: disabled
|
|
111
|
-
? search
|
|
112
|
-
? searchFieldRecipe.states.disabledOpacity
|
|
113
|
-
: fieldRecipe.disabledOpacity
|
|
114
|
-
: 1,
|
|
115
|
-
},
|
|
116
|
-
layoutStyle,
|
|
117
|
-
], children: _jsxs(View, { style: {
|
|
144
|
+
return (_jsx(NativeFieldFrame, { ...(visibleLabel === undefined ? {} : { label: visibleLabel }), required: required, ...(error === undefined ? {} : { error }), ...(supportText === undefined ? {} : { description: supportText }), ...(disabled ? { disabledOpacity: resolvedDisabledOpacity } : {}), style: layoutStyle, children: _jsxs(View, { style: {
|
|
145
|
+
opacity: disabled ? resolvedDisabledOpacity : 1,
|
|
118
146
|
alignItems: multiline ? "stretch" : "center",
|
|
119
147
|
backgroundColor,
|
|
120
148
|
borderColor,
|
|
@@ -127,13 +155,25 @@ const FieldRenderer = forwardRef(function FieldRenderer({ label, value, defaultV
|
|
|
127
155
|
paddingHorizontal: search
|
|
128
156
|
? searchSizing.paddingHorizontal
|
|
129
157
|
: fieldRecipe.paddingHorizontal,
|
|
130
|
-
}, children: [leading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", children: leading })) : null, _jsx(TextInput, { ...props, ...inputTextScaleProps, ref:
|
|
158
|
+
}, children: [leading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", children: leading })) : null, leadingAction ? _jsx(View, { style: { alignSelf: "center" }, children: leadingAction }) : null, _jsx(TextInput, { ...props, ...inputTextScaleProps, ref: attachInput, accessibilityHint: hint, accessibilityLabel: accessibleName, accessibilityRole: search ? "search" : undefined, accessibilityState: { busy, disabled },
|
|
159
|
+
// SearchField `busy` means "results are loading", not "this value is being committed":
|
|
160
|
+
// locking the editor dropped every keystroke typed while suggestions refreshed, while the
|
|
161
|
+
// Web SearchField keeps typing under `aria-busy` (2026-10-06 parity follow-up). Plain
|
|
162
|
+
// fields keep the lock because their busy means a pending save of the current value.
|
|
163
|
+
editable: !disabled && (search || !busy), multiline: multiline, onBlur: (event) => {
|
|
131
164
|
setFocused(false);
|
|
132
165
|
onBlur?.(event);
|
|
166
|
+
}, onContentSizeChange: event => {
|
|
167
|
+
if (multiline && minVisibleLines !== undefined)
|
|
168
|
+
setContentHeight(event.nativeEvent.contentSize.height);
|
|
169
|
+
onContentSizeChange?.(event);
|
|
170
|
+
}, onSelectionChange: event => {
|
|
171
|
+
selectionRef.current = event.nativeEvent.selection;
|
|
172
|
+
onSelectionChange?.(event);
|
|
133
173
|
}, onChangeText: setCurrentValue, onFocus: (event) => {
|
|
134
174
|
setFocused(true);
|
|
135
175
|
onFocus?.(event);
|
|
136
|
-
}, placeholderTextColor: placeholderColor, value: currentValue }), trailing] }) }));
|
|
176
|
+
}, placeholderTextColor: placeholderColor, value: currentValue }, editorKey), trailing] }) }));
|
|
137
177
|
});
|
|
138
178
|
export const TextField = forwardRef(function TextField(props, ref) {
|
|
139
179
|
return _jsx(FieldRenderer, { ...props, ref: ref, multiline: false, search: false });
|
|
@@ -142,6 +182,9 @@ export const TextArea = forwardRef(function TextArea(props, ref) {
|
|
|
142
182
|
return _jsx(FieldRenderer, { ...props, ref: ref, multiline: true, search: false });
|
|
143
183
|
});
|
|
144
184
|
export const SearchField = forwardRef(function SearchField({ clearLabel, busyLabel, onClear, leading: leadingNode, trailing: trailingNode, renderLeading, renderClearIcon, renderBusyIndicator, value, defaultValue, onValueChange, size = searchFieldRecipe.defaults.size, busy = false, disabled = false, ...props }, ref) {
|
|
185
|
+
const inputRef = useRef(null);
|
|
186
|
+
// Single-line child editor never remounts.
|
|
187
|
+
useImperativeHandle(ref, () => inputRef.current, []);
|
|
145
188
|
const theme = useHjmNativeTheme();
|
|
146
189
|
const searchSizing = searchFieldRecipe.sizes[size];
|
|
147
190
|
const iconProps = {
|
|
@@ -163,14 +206,16 @@ export const SearchField = forwardRef(function SearchField({ clearLabel, busyLab
|
|
|
163
206
|
}, children: renderBusyIndicator?.(iconProps) ?? (_jsx(ActivityIndicator, { color: iconProps.color, size: iconProps.size })) })) : searchValue.length > 0 ? (_jsx(Pressable, { accessibilityLabel: clearLabel, accessibilityRole: "button", disabled: disabled, hitSlop: searchSizing.clearHitSlop, onPress: () => {
|
|
164
207
|
setSearchValue("");
|
|
165
208
|
onClear?.();
|
|
209
|
+
inputRef.current?.focus();
|
|
166
210
|
}, style: {
|
|
167
211
|
alignItems: "center",
|
|
168
212
|
height: searchSizing.clearDiameter,
|
|
169
213
|
justifyContent: "center",
|
|
170
214
|
width: searchSizing.clearDiameter,
|
|
171
215
|
}, children: renderClearIcon?.(iconProps) ?? (_jsx(Text, { align: "center", style: { fontSize: iconProps.size, lineHeight: iconProps.size }, tone: "muted", children: "\u00D7" })) })) : trailingNode;
|
|
172
|
-
return (_jsx(FieldRenderer, { ...props, ref:
|
|
173
|
-
|
|
216
|
+
return (_jsx(FieldRenderer, { ...props, ref: inputRef, busy: busy, disabled: disabled, leading: leading, multiline: false, onValueChange: (next) => {
|
|
217
|
+
// Typing continues while busy (same as Web `loading`); only disabled ignores input.
|
|
218
|
+
if (!disabled)
|
|
174
219
|
setSearchValue(next);
|
|
175
220
|
}, search: true, searchSize: size, trailing: trailing, value: searchValue }));
|
|
176
221
|
});
|
|
@@ -209,7 +254,8 @@ function DefaultPasswordToggleIcon({ color, revealed, size, }) {
|
|
|
209
254
|
export const PasswordField = forwardRef(function PasswordField({ revealed: revealedProp, defaultRevealed = false, onRevealedChange, autofillHint, revealLabel, concealLabel, size = passwordFieldRecipe.defaults.size, renderToggleIcon, disabled = false, onSelectionChange, ...props }, forwardedRef) {
|
|
210
255
|
const theme = useHjmNativeTheme();
|
|
211
256
|
const inputRef = useRef(null);
|
|
212
|
-
|
|
257
|
+
// Single-line child editor never remounts.
|
|
258
|
+
useImperativeHandle(forwardedRef, () => inputRef.current, []);
|
|
213
259
|
const selectionRef = useRef({ start: 0, end: 0 });
|
|
214
260
|
const [revealed, setRevealed] = useControllableState({
|
|
215
261
|
...(revealedProp === undefined ? {} : { value: revealedProp }),
|
|
@@ -236,7 +282,9 @@ export const PasswordField = forwardRef(function PasswordField({ revealed: revea
|
|
|
236
282
|
}, 0);
|
|
237
283
|
return () => clearTimeout(timeout);
|
|
238
284
|
}, [revealed]);
|
|
239
|
-
return (_jsx(FieldRenderer, { ...props, ref: inputRef, autoComplete: autofillHint === "current" ? "current-password" : "new-password", disabled: disabled,
|
|
285
|
+
return (_jsx(FieldRenderer, { ...props, ref: inputRef, autoComplete: autofillHint === "current" ? "current-password" : "new-password", disabled: disabled,
|
|
286
|
+
// passwordFieldRecipe.states owns the amount; both renderers used the field default 0.6 until 2026-10-06.
|
|
287
|
+
disabledOpacity: passwordFieldRecipe.states.disabledOpacity, recipeInputStyle: [
|
|
240
288
|
size === "large"
|
|
241
289
|
? {
|
|
242
290
|
fontSize: typography.bodyLarge.fontSize,
|
|
@@ -247,7 +295,11 @@ export const PasswordField = forwardRef(function PasswordField({ revealed: revea
|
|
|
247
295
|
], multiline: false, onSelectionChange: (event) => {
|
|
248
296
|
selectionRef.current = event.nativeEvent.selection;
|
|
249
297
|
onSelectionChange?.(event);
|
|
250
|
-
}, search: false, secureTextEntry: resolved.nativeSecureTextEntry, textContentType: autofillHint === "current" ? "password" : "newPassword", trailing: (_jsx(Pressable, { accessibilityLabel: resolved.toggleAccessibleName, accessibilityRole: "button",
|
|
298
|
+
}, search: false, secureTextEntry: resolved.nativeSecureTextEntry, textContentType: autofillHint === "current" ? "password" : "newPassword", trailing: (_jsx(Pressable, { accessibilityLabel: resolved.toggleAccessibleName, accessibilityRole: "button",
|
|
299
|
+
// No `selected`: the label already names the next action, and VoiceOver
|
|
300
|
+
// would read "Selected, Hide password" — the Native twin of the
|
|
301
|
+
// `aria-pressed` the contract rules out (password-field.md, rationale 1).
|
|
302
|
+
accessibilityState: { disabled }, disabled: disabled, onPress: () => setRevealed(!revealed), style: ({ pressed }) => ({
|
|
251
303
|
alignItems: "center",
|
|
252
304
|
height: metrics.toggleDiameter,
|
|
253
305
|
justifyContent: "center",
|
|
@@ -256,9 +308,13 @@ export const PasswordField = forwardRef(function PasswordField({ revealed: revea
|
|
|
256
308
|
}), children: renderToggleIcon?.(appearance) ?? _jsx(DefaultPasswordToggleIcon, { ...appearance }) })) }));
|
|
257
309
|
});
|
|
258
310
|
/** One accessible numeric TextInput rendered through decorative OTP slots. */
|
|
259
|
-
export const OtpField = forwardRef(function OtpField({ label, accessibilityLabel, description, error, required = false, disabled = false, busy = false, readOnly = false, length, value: valueProp, defaultValue = "", onValueChange, onComplete, size = otpFieldRecipe.defaults.size, presentation = "boxes", slotStyle, slotTextStyle,
|
|
311
|
+
export const OtpField = forwardRef(function OtpField({ label, accessibilityLabel, description, error, required = false, disabled = false, busy = false, readOnly = false, length, value: valueProp, defaultValue = "", onValueChange, onComplete, size = otpFieldRecipe.defaults.size, presentation = "boxes", slotStyle, slotTextStyle,
|
|
312
|
+
// Inherited from BaseFieldProps; before 1.13 it fell into `...props` and was spread onto the
|
|
313
|
+
// hidden TextInput, so OtpField silently ignored its own canonical placement prop.
|
|
314
|
+
layoutStyle, allowFontScaling, onBlur, onFocus, ...props }, ref) {
|
|
260
315
|
const supportText = description;
|
|
261
316
|
const theme = useHjmNativeTheme();
|
|
317
|
+
warnDeprecatedStyleProps("OtpField", { slotStyle, slotTextStyle }, "size/presentation for slot appearance and layoutStyle for placement");
|
|
262
318
|
const { accessibleName, visibleLabel } = resolveFieldAccessibleName(label, accessibilityLabel);
|
|
263
319
|
const [focused, setFocused] = useState(false);
|
|
264
320
|
const [value, setValue] = useControllableState({
|
|
@@ -282,17 +338,20 @@ export const OtpField = forwardRef(function OtpField({ label, accessibilityLabel
|
|
|
282
338
|
const invalidBorder = resolveColorReference(otpFieldRecipe.slot.invalidBorder, theme.palette);
|
|
283
339
|
const filledBorder = resolveColorReference(otpFieldRecipe.slot.filledBorder, theme.palette);
|
|
284
340
|
const contentColor = resolveColorReference(otpFieldRecipe.slot.content, theme.palette);
|
|
341
|
+
// fieldRecipe.disabledScope: label and slots fade; the hint/error below keep full contrast.
|
|
342
|
+
// The whole column used to fade (2026-10-06 change, same as Web).
|
|
343
|
+
const disabledOpacity = disabled || busy ? otpFieldRecipe.states.disabledOpacity : 1;
|
|
285
344
|
return (_jsxs(View, { style: [
|
|
286
|
-
{
|
|
287
|
-
|
|
288
|
-
opacity: disabled || busy ? otpFieldRecipe.states.disabledOpacity : 1,
|
|
289
|
-
},
|
|
345
|
+
{ gap: fieldRecipe.label.gap },
|
|
346
|
+
layoutStyle,
|
|
290
347
|
], children: [visibleLabel ? (_jsxs(Text, { style: {
|
|
291
348
|
color: theme.colors[fieldRecipe.label.color],
|
|
292
349
|
fontWeight: fieldRecipe.label.fontWeight,
|
|
350
|
+
opacity: disabledOpacity,
|
|
293
351
|
}, tone: "body", variant: fieldRecipe.label.textVariant, children: [visibleLabel, required ? " *" : ""] })) : null, _jsxs(View, { style: { gap: otpFieldRecipe.support.gap }, children: [_jsxs(View, { style: {
|
|
294
352
|
direction: "ltr",
|
|
295
353
|
maxWidth: metrics.slotSize * length + metrics.gap * (length - 1),
|
|
354
|
+
opacity: disabledOpacity,
|
|
296
355
|
position: "relative",
|
|
297
356
|
width: "100%",
|
|
298
357
|
}, children: [_jsx(TextInput, { ...props, ref: ref, accessibilityHint: error ?? supportText, accessibilityLabel: accessibleName, accessibilityState: { busy, disabled: disabled || readOnly }, allowFontScaling: allowFontScaling, autoComplete: "one-time-code", caretHidden: true, editable: !disabled && !busy && !readOnly, keyboardType: "number-pad", maxLength: length, onBlur: (event) => {
|
|
@@ -341,7 +400,20 @@ export const OtpField = forwardRef(function OtpField({ label, accessibilityLabel
|
|
|
341
400
|
], children: _jsx(Text, { accessible: false, align: "center", allowFontScaling: allowFontScaling, style: [{ color: contentColor }, slotTextStyle], variant: metrics.textVariant, children: digit }) }, index));
|
|
342
401
|
}) })] }), _jsx(FieldMessage, { ...(error === undefined ? {} : { error }), ...(supportText === undefined ? {} : { supportText }) })] })] }));
|
|
343
402
|
});
|
|
344
|
-
|
|
403
|
+
const choiceVisualReplacement = "layoutStyle for placement and presentation/size/indicator/renderIndicator/renderLeading for appearance";
|
|
404
|
+
/** Public choice components share one deprecated slot list; ChoiceRow itself stays silent. */
|
|
405
|
+
function warnChoiceVisualStyles(component, visual) {
|
|
406
|
+
warnDeprecatedStyleProps(component, {
|
|
407
|
+
style: visual.style,
|
|
408
|
+
controlStyle: visual.controlStyle,
|
|
409
|
+
indicatorStyle: visual.indicatorStyle,
|
|
410
|
+
leadingStyle: visual.leadingStyle,
|
|
411
|
+
contentStyle: visual.contentStyle,
|
|
412
|
+
labelStyle: visual.labelStyle,
|
|
413
|
+
descriptionStyle: visual.descriptionStyle,
|
|
414
|
+
}, choiceVisualReplacement);
|
|
415
|
+
}
|
|
416
|
+
function ChoiceRow({ kind, label, description, checked, disabled, readOnly, required, invalid, readOnlyLabel, requiredLabel, invalidLabel, accessibilityHint, presentation = selectionControlRecipe.defaults.presentation, size = selectionControlRecipe.defaults.size, indicator = "default", leading, renderLeading, renderIndicator, onActivate, webTabIndex, layoutStyle, style, controlStyle, indicatorStyle, leadingStyle, contentStyle, labelStyle, descriptionStyle, }) {
|
|
345
417
|
const theme = useHjmNativeTheme();
|
|
346
418
|
const metrics = selectionControlRecipe.sizes[size];
|
|
347
419
|
const plate = selectionControlRecipe.presentations[presentation];
|
|
@@ -418,6 +490,7 @@ function ChoiceRow({ kind, label, description, checked, disabled, readOnly, requ
|
|
|
418
490
|
paddingVertical: plate.useSizePadding ? metrics.paddingVertical : 0,
|
|
419
491
|
},
|
|
420
492
|
style,
|
|
493
|
+
layoutStyle,
|
|
421
494
|
], children: [indicator === "default" ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", style: [
|
|
422
495
|
{
|
|
423
496
|
alignItems: "center",
|
|
@@ -448,6 +521,7 @@ function ChoiceRow({ kind, label, description, checked, disabled, readOnly, requ
|
|
|
448
521
|
], variant: metrics.descriptionVariant, children: description })) : null] })] }));
|
|
449
522
|
}
|
|
450
523
|
export function Checkbox({ label, checked, defaultChecked = false, onCheckedChange, disabled = false, readOnly = false, required = false, invalid = false, description, readOnlyLabel, requiredLabel, invalidLabel, leading, renderLeading, renderIndicator, accessibilityHint, ...visual }) {
|
|
524
|
+
warnChoiceVisualStyles("Checkbox", visual);
|
|
451
525
|
const [selected, setSelected] = useControllableState({
|
|
452
526
|
...(checked === undefined ? {} : { value: checked }),
|
|
453
527
|
defaultValue: defaultChecked,
|
|
@@ -459,6 +533,7 @@ export function Checkbox({ label, checked, defaultChecked = false, onCheckedChan
|
|
|
459
533
|
}
|
|
460
534
|
/** Standalone native radio item. Prefer RadioGroup when group state is owned here. */
|
|
461
535
|
export function Radio({ label, checked, defaultChecked = false, onCheckedChange, disabled = false, readOnly = false, required = false, invalid = false, description, readOnlyLabel, requiredLabel, invalidLabel, leading, renderLeading, renderIndicator, accessibilityHint, ...visual }) {
|
|
536
|
+
warnChoiceVisualStyles("Radio", visual);
|
|
462
537
|
const [selected, setSelected] = useControllableState({
|
|
463
538
|
...(checked === undefined ? {} : { value: checked }),
|
|
464
539
|
defaultValue: defaultChecked,
|
|
@@ -468,7 +543,7 @@ export function Radio({ label, checked, defaultChecked = false, onCheckedChange,
|
|
|
468
543
|
});
|
|
469
544
|
return (_jsx(ChoiceRow, { ...visual, accessibilityHint: accessibilityHint, checked: selected, description: description, disabled: disabled, indicator: visual.indicator ?? "default", invalid: invalid, invalidLabel: invalidLabel, kind: "radio", label: label, leading: leading, onActivate: () => setSelected(true), readOnly: readOnly, readOnlyLabel: readOnlyLabel, renderIndicator: renderIndicator, renderLeading: renderLeading, required: required, requiredLabel: requiredLabel }));
|
|
470
545
|
}
|
|
471
|
-
function ChoiceGroupFrame({ label, accessibilityLabel, required, requiredLabel, readOnly, readOnlyLabel, disabled, description, error, role, orientation, presentation, style, children, }) {
|
|
546
|
+
function ChoiceGroupFrame({ label, accessibilityLabel, required, requiredLabel, readOnly, readOnlyLabel, disabled, description, error, role, orientation, presentation, style, layoutStyle, children, }) {
|
|
472
547
|
const theme = useHjmNativeTheme();
|
|
473
548
|
const id = useId().replaceAll(":", "");
|
|
474
549
|
const labelId = `${id}-label`;
|
|
@@ -480,16 +555,18 @@ function ChoiceGroupFrame({ label, accessibilityLabel, required, requiredLabel,
|
|
|
480
555
|
].filter(Boolean).join(", ");
|
|
481
556
|
const gap = selectionGroupRecipe.orientations[orientation].gap[presentation];
|
|
482
557
|
return (_jsxs(View, { accessibilityHint: [error ?? description, readOnly ? readOnlyLabel : undefined]
|
|
483
|
-
.filter(Boolean).join(". ") || undefined, accessibilityLabel: announcedName, accessibilityLabelledBy: label ? labelId : undefined, accessibilityRole: role, accessibilityState: { disabled: disabled || readOnly }, accessibilityValue: error ? { text: error } : undefined, style: [{ direction: theme.environment.direction, gap: selectionGroupRecipe.supportGap }, style], children: [label ? (_jsxs(Text, { nativeID: labelId, tone: "primary", variant: selectionGroupRecipe.label.textVariant, children: [label, required ? requiredLabel ? ` (${requiredLabel})` : " *" : ""] })) : null, _jsx(View, { style: {
|
|
558
|
+
.filter(Boolean).join(". ") || undefined, accessibilityLabel: announcedName, accessibilityLabelledBy: label ? labelId : undefined, accessibilityRole: role, accessibilityState: { disabled: disabled || readOnly }, accessibilityValue: error ? { text: error } : undefined, style: [{ direction: theme.environment.direction, gap: selectionGroupRecipe.supportGap }, style, layoutStyle], children: [label ? (_jsxs(Text, { nativeID: labelId, tone: "primary", variant: selectionGroupRecipe.label.textVariant, children: [label, required ? requiredLabel ? ` (${requiredLabel})` : " *" : ""] })) : null, description && !error ? (_jsx(Text, { tone: "muted", variant: selectionGroupRecipe.description.textVariant, children: description })) : null, _jsx(View, { style: {
|
|
484
559
|
direction: theme.environment.direction,
|
|
485
560
|
flexDirection: orientation === "horizontal" && theme.environment.textScale < 1.6
|
|
486
561
|
? "row"
|
|
487
562
|
: "column",
|
|
488
563
|
gap,
|
|
489
|
-
}, children: children }), error ? (_jsx(Text, { accessibilityLiveRegion: "assertive", accessibilityRole: "alert", tone: "danger", variant: selectionGroupRecipe.error.textVariant, children: error })) :
|
|
564
|
+
}, children: children }), error ? (_jsx(Text, { accessibilityLiveRegion: "assertive", accessibilityRole: "alert", tone: "danger", variant: selectionGroupRecipe.error.textVariant, children: error })) : null] }));
|
|
490
565
|
}
|
|
491
566
|
export function RadioGroup(props) {
|
|
492
|
-
const { label, accessibilityLabel, items, value, defaultValue, onValueChange, required = false, disabled = false, readOnly = false, invalid = false, description, error, requiredLabel, readOnlyLabel, invalidLabel, orientation = selectionGroupBehaviorDefaults.orientation, presentation = selectionGroupRecipe.defaults.presentation, size = selectionControlRecipe.defaults.size, indicator = "default", renderLeading, renderIndicator, style, ...slotStyles } = props;
|
|
567
|
+
const { label, accessibilityLabel, items, value, defaultValue, onValueChange, required = false, disabled = false, readOnly = false, invalid = false, description, error, requiredLabel, readOnlyLabel, invalidLabel, orientation = selectionGroupBehaviorDefaults.orientation, presentation = selectionGroupRecipe.defaults.presentation, size = selectionControlRecipe.defaults.size, indicator = "default", renderLeading, renderIndicator, layoutStyle, style, ...slotStyles } = props;
|
|
568
|
+
// Group `style` paints the frame; the remaining slot styles reach every row.
|
|
569
|
+
warnChoiceVisualStyles("RadioGroup", { style, ...slotStyles });
|
|
493
570
|
// Removed aliases must not silently change the selected collection in JavaScript callers.
|
|
494
571
|
if ("options" in props || !Array.isArray(items))
|
|
495
572
|
throw new TypeError("RadioGroup requires items; options was removed");
|
|
@@ -521,14 +598,15 @@ export function RadioGroup(props) {
|
|
|
521
598
|
setSelected(selected);
|
|
522
599
|
}, [selected, setSelected, storedValue, value]);
|
|
523
600
|
const hasError = invalid || error !== undefined;
|
|
524
|
-
return (_jsx(ChoiceGroupFrame, { accessibilityLabel: accessibilityLabel, description: description, disabled: disabled, error: error, label: label, orientation: orientation, presentation: presentation, readOnly: readOnly, readOnlyLabel: readOnlyLabel, required: required, requiredLabel: requiredLabel, role: "radiogroup", style: style, children: resolvedItems.map((item) => {
|
|
601
|
+
return (_jsx(ChoiceGroupFrame, { accessibilityLabel: accessibilityLabel, description: description, disabled: disabled, error: error, label: label, orientation: orientation, presentation: presentation, readOnly: readOnly, readOnlyLabel: readOnlyLabel, required: required, requiredLabel: requiredLabel, role: "radiogroup", style: style, layoutStyle: layoutStyle, children: resolvedItems.map((item) => {
|
|
525
602
|
const optionDisabled = disabled || item.disabled === true;
|
|
526
603
|
const optionSelected = selected === item.value;
|
|
527
604
|
return (_createElement(ChoiceRow, { ...slotStyles, key: item.value, accessibilityHint: item.accessibilityHint, checked: optionSelected, description: item.description, disabled: optionDisabled, indicator: indicator, invalid: hasError, invalidLabel: invalidLabel ?? error, kind: "radio", label: item.label, leading: item.leading, onActivate: () => setSelected(item.value), webTabIndex: !optionDisabled && !readOnly && item.value === webTabStop ? 0 : -1, presentation: presentation, readOnly: readOnly, readOnlyLabel: readOnlyLabel, renderIndicator: renderIndicator ? (props) => renderIndicator(item, props) : undefined, renderLeading: renderLeading ? (props) => renderLeading(item, props) : undefined, required: required, requiredLabel: requiredLabel, size: size }));
|
|
528
605
|
}) }));
|
|
529
606
|
}
|
|
530
607
|
/** Validated controlled/uncontrolled checkbox collection using immutable Sets. */
|
|
531
|
-
export function CheckboxGroup({ label, accessibilityLabel, items, value, defaultValue = new Set(), onValueChange, required = false, disabled = false, readOnly = false, invalid = false, description, error, requiredLabel, readOnlyLabel, invalidLabel, orientation = selectionGroupBehaviorDefaults.orientation, presentation = selectionGroupRecipe.defaults.presentation, size = selectionControlRecipe.defaults.size, indicator = "default", renderLeading, renderIndicator, style, ...slotStyles }) {
|
|
608
|
+
export function CheckboxGroup({ label, accessibilityLabel, items, value, defaultValue = new Set(), onValueChange, required = false, disabled = false, readOnly = false, invalid = false, description, error, requiredLabel, readOnlyLabel, invalidLabel, orientation = selectionGroupBehaviorDefaults.orientation, presentation = selectionGroupRecipe.defaults.presentation, size = selectionControlRecipe.defaults.size, indicator = "default", renderLeading, renderIndicator, layoutStyle, style, ...slotStyles }) {
|
|
609
|
+
warnChoiceVisualStyles("CheckboxGroup", { style, ...slotStyles });
|
|
532
610
|
validateSelectionItems(items);
|
|
533
611
|
if (value !== undefined)
|
|
534
612
|
validateCheckboxSelection(items, value);
|
|
@@ -543,7 +621,7 @@ export function CheckboxGroup({ label, accessibilityLabel, items, value, default
|
|
|
543
621
|
setSelected(selected);
|
|
544
622
|
}, [selected, setSelected, storedValue, value]);
|
|
545
623
|
const hasError = invalid || error !== undefined;
|
|
546
|
-
return (_jsx(ChoiceGroupFrame, { accessibilityLabel: accessibilityLabel, description: description, disabled: disabled, error: error, label: label, orientation: orientation, presentation: presentation, readOnly: readOnly, readOnlyLabel: readOnlyLabel, required: required, requiredLabel: requiredLabel, style: style, children: items.map((item) => {
|
|
624
|
+
return (_jsx(ChoiceGroupFrame, { accessibilityLabel: accessibilityLabel, description: description, disabled: disabled, error: error, label: label, orientation: orientation, presentation: presentation, readOnly: readOnly, readOnlyLabel: readOnlyLabel, required: required, requiredLabel: requiredLabel, style: style, layoutStyle: layoutStyle, children: items.map((item) => {
|
|
547
625
|
const optionDisabled = disabled || item.disabled === true;
|
|
548
626
|
const optionSelected = selected.has(item.id);
|
|
549
627
|
return (_createElement(ChoiceRow, { ...slotStyles, key: item.id, checked: optionSelected, description: item.description, disabled: optionDisabled, indicator: indicator, invalid: hasError, invalidLabel: invalidLabel ?? error, kind: "checkbox", label: item.label, onActivate: () => setSelected(toggleCheckboxSelection(items, selected, item.id)), presentation: presentation, readOnly: readOnly, readOnlyLabel: readOnlyLabel, renderIndicator: renderIndicator ? (props) => renderIndicator(item, props) : undefined, renderLeading: renderLeading ? (props) => renderLeading(item, props) : undefined, required: required, requiredLabel: requiredLabel, size: size }));
|
|
@@ -554,6 +632,7 @@ export function Switch({ label, labelVisibility = "visible", presentation = swit
|
|
|
554
632
|
if (["value", "defaultValue", "onValueChange"].some(key => key in props)) {
|
|
555
633
|
throw new TypeError("Switch no longer accepts value/defaultValue/onValueChange; use checked/defaultChecked/onCheckedChange");
|
|
556
634
|
}
|
|
635
|
+
warnDeprecatedStyleProps("Switch", { style }, "layoutStyle for placement and presentation/size for appearance");
|
|
557
636
|
const { colors, environment, ...nativeTheme } = useHjmNativeTheme();
|
|
558
637
|
const dimensions = switchRecipe.sizes[size];
|
|
559
638
|
const stacked = presentation === "row" && labelVisibility === "visible"
|
|
@@ -610,10 +689,12 @@ export function Switch({ label, labelVisibility = "visible", presentation = swit
|
|
|
610
689
|
: { height: dimensions.height, width: dimensions.width }, thumbColor: thumb, trackColor: { false: trackOff, true: trackOn }, value: enabled })] }));
|
|
611
690
|
}
|
|
612
691
|
export function SegmentedControl(props) {
|
|
613
|
-
const { label, items, value, defaultValue, onValueChange, size = segmentedControlRecipe.defaults.size, disabled = false, style, } = props;
|
|
692
|
+
const { label, items, value, defaultValue, onValueChange, size = segmentedControlRecipe.defaults.size, presentation = "connected", disabled = false, layoutStyle, style, } = props;
|
|
614
693
|
// Removed aliases must not silently change the selected collection in JavaScript callers.
|
|
615
694
|
if ("options" in props || !Array.isArray(items))
|
|
616
695
|
throw new TypeError("SegmentedControl requires items; options was removed");
|
|
696
|
+
warnDeprecatedStyleProps("SegmentedControl", { style }, "layoutStyle for placement and size for appearance");
|
|
697
|
+
const pills = presentation === "pills";
|
|
617
698
|
const resolvedItems = items;
|
|
618
699
|
const theme = useHjmNativeTheme();
|
|
619
700
|
const { environment } = theme;
|
|
@@ -651,21 +732,23 @@ export function SegmentedControl(props) {
|
|
|
651
732
|
}, [collectionFallback, controlled, setSelected, storedValueValid]);
|
|
652
733
|
return (_jsx(View, { accessibilityLabel: label, accessibilityRole: "radiogroup", style: [
|
|
653
734
|
{
|
|
654
|
-
backgroundColor: resolveColorReference(segmentedControlRecipe.container.background, theme.palette),
|
|
735
|
+
backgroundColor: pills ? "transparent" : resolveColorReference(segmentedControlRecipe.container.background, theme.palette),
|
|
655
736
|
borderColor: resolveColorReference(segmentedControlRecipe.container.border, theme.palette),
|
|
656
737
|
borderRadius: radius[segmentedControlRecipe.container.radius],
|
|
657
|
-
borderWidth: segmentedControlRecipe.container.borderWidth,
|
|
738
|
+
borderWidth: pills ? 0 : segmentedControlRecipe.container.borderWidth,
|
|
658
739
|
direction: environment.direction,
|
|
659
740
|
flexDirection: stacked ? "column" : "row",
|
|
660
|
-
|
|
661
|
-
|
|
741
|
+
flexWrap: pills ? "wrap" : "nowrap",
|
|
742
|
+
gap: pills ? segmentedControlRecipe.pills.gap : segmentedControlRecipe.container.gap,
|
|
743
|
+
padding: pills ? 0 : segmentedControlRecipe.container.padding,
|
|
662
744
|
},
|
|
663
745
|
style,
|
|
746
|
+
layoutStyle,
|
|
664
747
|
], children: resolvedItems.map((item) => {
|
|
665
748
|
const isSelected = item.value === selected;
|
|
666
749
|
const optionDisabled = disabled || item.disabled === true;
|
|
667
750
|
const contentColor = resolveColorReference(isSelected
|
|
668
|
-
? segmentedControlRecipe.item.selectedContent
|
|
751
|
+
? (pills ? segmentedControlRecipe.pills.selectedContent : segmentedControlRecipe.item.selectedContent)
|
|
669
752
|
: segmentedControlRecipe.item.idleContent, theme.palette);
|
|
670
753
|
const leading = item.leading ?? item.renderLeading?.({
|
|
671
754
|
selected: isSelected,
|
|
@@ -676,29 +759,31 @@ export function SegmentedControl(props) {
|
|
|
676
759
|
return (_jsxs(Pressable, { accessibilityLabel: item.label, accessibilityRole: "radio", accessibilityState: { checked: isSelected, disabled: optionDisabled }, disabled: optionDisabled, hitSlop: sizeContract.hitSlop, onPress: () => setSelected(item.value), style: ({ pressed }) => [
|
|
677
760
|
{
|
|
678
761
|
alignItems: "center",
|
|
679
|
-
backgroundColor: isSelected
|
|
762
|
+
backgroundColor: !pills && isSelected
|
|
680
763
|
? resolveColorReference(segmentedControlRecipe.item.selectedBackground, theme.palette)
|
|
681
764
|
: "transparent",
|
|
682
765
|
borderColor: isSelected
|
|
683
766
|
? resolveColorReference(segmentedControlRecipe.item.selectedBorder, theme.palette)
|
|
684
767
|
: "transparent",
|
|
685
768
|
borderRadius: radius[segmentedControlRecipe.item.radius],
|
|
686
|
-
borderWidth: isSelected
|
|
769
|
+
borderWidth: !pills && isSelected
|
|
687
770
|
? segmentedControlRecipe.item.selectedBorderWidth
|
|
688
771
|
: 0,
|
|
689
|
-
flex: stacked ? undefined : 1,
|
|
772
|
+
flex: stacked || pills ? undefined : 1,
|
|
773
|
+
maxWidth: pills ? "100%" : undefined,
|
|
690
774
|
gap: segmentedControlRecipe.item.gap,
|
|
691
775
|
justifyContent: "center",
|
|
692
|
-
minHeight: sizeContract.minHeight,
|
|
776
|
+
minHeight: pills ? segmentedControlRecipe.pills.minHeight : sizeContract.minHeight,
|
|
693
777
|
opacity: optionDisabled
|
|
694
778
|
? segmentedControlRecipe.item.disabledOpacity
|
|
695
779
|
: pressed
|
|
696
780
|
? segmentedControlRecipe.item.pressedOpacity
|
|
697
781
|
: 1,
|
|
698
|
-
paddingHorizontal: sizeContract.paddingHorizontal,
|
|
782
|
+
paddingHorizontal: pills ? spacing.md : sizeContract.paddingHorizontal,
|
|
783
|
+
paddingVertical: pills ? segmentedControlRecipe.pills.inset : undefined,
|
|
699
784
|
width: stacked ? "100%" : undefined,
|
|
700
785
|
},
|
|
701
|
-
], children: [leading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", children: leading })) : null, _jsx(Text, { align: "center", style: {
|
|
786
|
+
], children: [pills ? _jsx(View, { pointerEvents: "none", accessible: false, style: { position: "absolute", left: 0, right: 0, top: segmentedControlRecipe.pills.inset, bottom: segmentedControlRecipe.pills.inset, borderRadius: segmentedControlRecipe.pills.radius, backgroundColor: isSelected ? resolveColorReference(segmentedControlRecipe.pills.selectedBackground, theme.palette) : theme.colors.surfaceAlt } }) : null, leading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", children: leading })) : null, _jsx(Text, { align: "center", style: {
|
|
702
787
|
color: contentColor,
|
|
703
788
|
fontWeight: isSelected
|
|
704
789
|
? segmentedControlRecipe.item.selectedFontWeight
|
|
@@ -709,6 +794,7 @@ export function SegmentedControl(props) {
|
|
|
709
794
|
/** Action/filter chip with role-specific, controlled selection semantics. */
|
|
710
795
|
export function Chip({ label, size = chipRecipe.defaults.size, disabled = false, leading, trailing, accessibilityLabel, accessibilityHint, layoutStyle, leadingStyle, indicatorStyle, labelStyle, trailingStyle, renderSelectionIndicator, selectionMode = "action", selected, onPress, }) {
|
|
711
796
|
const theme = useHjmNativeTheme();
|
|
797
|
+
warnDeprecatedStyleProps("Chip", { labelStyle }, "size/selected for label typography");
|
|
712
798
|
const selectable = selectionMode !== "action";
|
|
713
799
|
const active = selectable && selected === true;
|
|
714
800
|
const metrics = chipRecipe.sizes[size];
|