@hjmds/react-native 1.12.1 → 1.13.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 (174) hide show
  1. package/README.md +15 -0
  2. package/dist/actions.d.ts +30 -2
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +28 -5
  5. package/dist/actions.js.map +1 -1
  6. package/dist/agreement.d.ts +8 -1
  7. package/dist/agreement.d.ts.map +1 -1
  8. package/dist/agreement.js +16 -3
  9. package/dist/agreement.js.map +1 -1
  10. package/dist/asset.d.ts +15 -2
  11. package/dist/asset.d.ts.map +1 -1
  12. package/dist/asset.js +7 -4
  13. package/dist/asset.js.map +1 -1
  14. package/dist/bottom-info.d.ts +8 -1
  15. package/dist/bottom-info.d.ts.map +1 -1
  16. package/dist/bottom-info.js +4 -2
  17. package/dist/bottom-info.js.map +1 -1
  18. package/dist/calendar.d.ts +3 -1
  19. package/dist/calendar.d.ts.map +1 -1
  20. package/dist/calendar.js +2 -2
  21. package/dist/calendar.js.map +1 -1
  22. package/dist/carousel.d.ts +8 -1
  23. package/dist/carousel.d.ts.map +1 -1
  24. package/dist/carousel.js +4 -2
  25. package/dist/carousel.js.map +1 -1
  26. package/dist/collapsible.d.ts +8 -1
  27. package/dist/collapsible.d.ts.map +1 -1
  28. package/dist/collapsible.js +9 -3
  29. package/dist/collapsible.js.map +1 -1
  30. package/dist/data-display.d.ts +146 -13
  31. package/dist/data-display.d.ts.map +1 -1
  32. package/dist/data-display.js +58 -29
  33. package/dist/data-display.js.map +1 -1
  34. package/dist/date-picker.d.ts +8 -1
  35. package/dist/date-picker.d.ts.map +1 -1
  36. package/dist/date-picker.js +10 -3
  37. package/dist/date-picker.js.map +1 -1
  38. package/dist/duration-field.d.ts +4 -1
  39. package/dist/duration-field.d.ts.map +1 -1
  40. package/dist/duration-field.js +2 -2
  41. package/dist/duration-field.js.map +1 -1
  42. package/dist/evidence.d.ts +1 -1
  43. package/dist/evidence.js +1 -1
  44. package/dist/feedback.d.ts +89 -14
  45. package/dist/feedback.d.ts.map +1 -1
  46. package/dist/feedback.js +25 -16
  47. package/dist/feedback.js.map +1 -1
  48. package/dist/file-picker.d.ts +8 -1
  49. package/dist/file-picker.d.ts.map +1 -1
  50. package/dist/file-picker.js +4 -2
  51. package/dist/file-picker.js.map +1 -1
  52. package/dist/forms.d.ts +28 -4
  53. package/dist/forms.d.ts.map +1 -1
  54. package/dist/forms.js +19 -8
  55. package/dist/forms.js.map +1 -1
  56. package/dist/heading.d.ts +8 -1
  57. package/dist/heading.d.ts.map +1 -1
  58. package/dist/heading.js +4 -2
  59. package/dist/heading.js.map +1 -1
  60. package/dist/image-viewer.d.ts.map +1 -1
  61. package/dist/image-viewer.js +11 -3
  62. package/dist/image-viewer.js.map +1 -1
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/inputs.d.ts +66 -3
  67. package/dist/inputs.d.ts.map +1 -1
  68. package/dist/inputs.js +142 -51
  69. package/dist/inputs.js.map +1 -1
  70. package/dist/internal/deprecated-style.d.ts +8 -0
  71. package/dist/internal/deprecated-style.d.ts.map +1 -0
  72. package/dist/internal/deprecated-style.js +34 -0
  73. package/dist/internal/deprecated-style.js.map +1 -0
  74. package/dist/internal/field-frame.d.ts +2 -1
  75. package/dist/internal/field-frame.d.ts.map +1 -1
  76. package/dist/internal/field-frame.js +4 -2
  77. package/dist/internal/field-frame.js.map +1 -1
  78. package/dist/internal/field-private.d.ts +11 -0
  79. package/dist/internal/field-private.d.ts.map +1 -0
  80. package/dist/internal/field-private.js +2 -0
  81. package/dist/internal/field-private.js.map +1 -0
  82. package/dist/internal/list-row-private.d.ts +10 -0
  83. package/dist/internal/list-row-private.d.ts.map +1 -0
  84. package/dist/internal/list-row-private.js +2 -0
  85. package/dist/internal/list-row-private.js.map +1 -0
  86. package/dist/internal/message-reactions.d.ts +21 -0
  87. package/dist/internal/message-reactions.d.ts.map +1 -0
  88. package/dist/internal/message-reactions.js +49 -0
  89. package/dist/internal/message-reactions.js.map +1 -0
  90. package/dist/internal/recipe-button.d.ts +6 -5
  91. package/dist/internal/recipe-button.d.ts.map +1 -1
  92. package/dist/internal/recipe-button.js +22 -7
  93. package/dist/internal/recipe-button.js.map +1 -1
  94. package/dist/internal/spinner.d.ts +15 -0
  95. package/dist/internal/spinner.d.ts.map +1 -0
  96. package/dist/internal/spinner.js +12 -0
  97. package/dist/internal/spinner.js.map +1 -0
  98. package/dist/masonry.d.ts +3 -1
  99. package/dist/masonry.d.ts.map +1 -1
  100. package/dist/masonry.js +3 -3
  101. package/dist/masonry.js.map +1 -1
  102. package/dist/mentions.d.ts +4 -0
  103. package/dist/mentions.d.ts.map +1 -1
  104. package/dist/mentions.js +8 -2
  105. package/dist/mentions.js.map +1 -1
  106. package/dist/navigation.d.ts +72 -4
  107. package/dist/navigation.d.ts.map +1 -1
  108. package/dist/navigation.js +39 -21
  109. package/dist/navigation.js.map +1 -1
  110. package/dist/number-field.d.ts +21 -0
  111. package/dist/number-field.d.ts.map +1 -1
  112. package/dist/number-field.js +7 -2
  113. package/dist/number-field.js.map +1 -1
  114. package/dist/overlays.d.ts +30 -2
  115. package/dist/overlays.d.ts.map +1 -1
  116. package/dist/overlays.js +108 -19
  117. package/dist/overlays.js.map +1 -1
  118. package/dist/primitives.d.ts +19 -3
  119. package/dist/primitives.d.ts.map +1 -1
  120. package/dist/primitives.js +12 -6
  121. package/dist/primitives.js.map +1 -1
  122. package/dist/provider-button.d.ts +8 -1
  123. package/dist/provider-button.d.ts.map +1 -1
  124. package/dist/provider-button.js +4 -1
  125. package/dist/provider-button.js.map +1 -1
  126. package/dist/qr-code.d.ts +4 -1
  127. package/dist/qr-code.d.ts.map +1 -1
  128. package/dist/qr-code.js +2 -2
  129. package/dist/qr-code.js.map +1 -1
  130. package/dist/reaction-picker.d.ts +4 -2
  131. package/dist/reaction-picker.d.ts.map +1 -1
  132. package/dist/reaction-picker.js +18 -6
  133. package/dist/reaction-picker.js.map +1 -1
  134. package/dist/saved-items.d.ts +21 -0
  135. package/dist/saved-items.d.ts.map +1 -0
  136. package/dist/saved-items.js +16 -0
  137. package/dist/saved-items.js.map +1 -0
  138. package/dist/screen-flows.d.ts +303 -0
  139. package/dist/screen-flows.d.ts.map +1 -0
  140. package/dist/screen-flows.js +172 -0
  141. package/dist/screen-flows.js.map +1 -0
  142. package/dist/screens.d.ts +114 -0
  143. package/dist/screens.d.ts.map +1 -0
  144. package/dist/screens.js +80 -0
  145. package/dist/screens.js.map +1 -0
  146. package/dist/slider.d.ts +21 -0
  147. package/dist/slider.d.ts.map +1 -1
  148. package/dist/slider.js +6 -2
  149. package/dist/slider.js.map +1 -1
  150. package/dist/steps.d.ts +8 -1
  151. package/dist/steps.d.ts.map +1 -1
  152. package/dist/steps.js +4 -2
  153. package/dist/steps.js.map +1 -1
  154. package/dist/tags-input.d.ts +8 -1
  155. package/dist/tags-input.d.ts.map +1 -1
  156. package/dist/tags-input.js +11 -5
  157. package/dist/tags-input.js.map +1 -1
  158. package/dist/toggle-group.d.ts +8 -1
  159. package/dist/toggle-group.d.ts.map +1 -1
  160. package/dist/toggle-group.js +4 -2
  161. package/dist/toggle-group.js.map +1 -1
  162. package/dist/top.d.ts +8 -1
  163. package/dist/top.d.ts.map +1 -1
  164. package/dist/top.js +4 -1
  165. package/dist/top.js.map +1 -1
  166. package/dist/transfer-list.d.ts +8 -1
  167. package/dist/transfer-list.d.ts.map +1 -1
  168. package/dist/transfer-list.js +4 -2
  169. package/dist/transfer-list.js.map +1 -1
  170. package/dist/upload-item.d.ts +8 -1
  171. package/dist/upload-item.d.ts.map +1 -1
  172. package/dist/upload-item.js +4 -2
  173. package/dist/upload-item.js.map +1 -1
  174. 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: ref, accessibilityHint: hint, accessibilityLabel: accessibleName, accessibilityRole: search ? "search" : undefined, accessibilityState: { busy, disabled }, editable: !disabled && !busy, multiline: multiline, onBlur: (event) => {
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: ref, busy: busy, disabled: disabled, leading: leading, multiline: false, onValueChange: (next) => {
173
- if (!busy && !disabled)
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
- useImperativeHandle(forwardedRef, () => inputRef.current);
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, recipeInputStyle: [
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", accessibilityState: { disabled, selected: revealed }, disabled: disabled, onPress: () => setRevealed(!revealed), style: ({ pressed }) => ({
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, allowFontScaling, onBlur, onFocus, ...props }, ref) {
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
- gap: fieldRecipe.label.gap,
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
- 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, style, controlStyle, indicatorStyle, leadingStyle, contentStyle, labelStyle, descriptionStyle, }) {
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 })) : description ? (_jsx(Text, { tone: "muted", variant: selectionGroupRecipe.description.textVariant, children: description })) : null] }));
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,15 +689,19 @@ 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;
620
701
  const sizeContract = segmentedControlRecipe.sizes[size];
621
- const stacked = segmentedControlRecipe.adaptive.largeTextLayout === "stacked"
702
+ // Pills keep their row at large text (segmentedControlRecipe.pills.largeTextLayout, 1.13.1): they wrap in a
703
+ // block and stay one line in a rail. Stacking them made a filter rail a ~440pt column (utilverse, 2026-10-06).
704
+ const stacked = !pills && segmentedControlRecipe.adaptive.largeTextLayout === "stacked"
622
705
  && environment.textScale >= segmentedControlRecipe.adaptive.stackAtFontScale;
623
706
  const descriptors = resolvedItems.map((item) => ({
624
707
  id: item.value,
@@ -651,21 +734,23 @@ export function SegmentedControl(props) {
651
734
  }, [collectionFallback, controlled, setSelected, storedValueValid]);
652
735
  return (_jsx(View, { accessibilityLabel: label, accessibilityRole: "radiogroup", style: [
653
736
  {
654
- backgroundColor: resolveColorReference(segmentedControlRecipe.container.background, theme.palette),
737
+ backgroundColor: pills ? "transparent" : resolveColorReference(segmentedControlRecipe.container.background, theme.palette),
655
738
  borderColor: resolveColorReference(segmentedControlRecipe.container.border, theme.palette),
656
739
  borderRadius: radius[segmentedControlRecipe.container.radius],
657
- borderWidth: segmentedControlRecipe.container.borderWidth,
740
+ borderWidth: pills ? 0 : segmentedControlRecipe.container.borderWidth,
658
741
  direction: environment.direction,
659
742
  flexDirection: stacked ? "column" : "row",
660
- gap: segmentedControlRecipe.container.gap,
661
- padding: segmentedControlRecipe.container.padding,
743
+ flexWrap: pills ? "wrap" : "nowrap",
744
+ gap: pills ? segmentedControlRecipe.pills.gap : segmentedControlRecipe.container.gap,
745
+ padding: pills ? 0 : segmentedControlRecipe.container.padding,
662
746
  },
663
747
  style,
748
+ layoutStyle,
664
749
  ], children: resolvedItems.map((item) => {
665
750
  const isSelected = item.value === selected;
666
751
  const optionDisabled = disabled || item.disabled === true;
667
752
  const contentColor = resolveColorReference(isSelected
668
- ? segmentedControlRecipe.item.selectedContent
753
+ ? (pills ? segmentedControlRecipe.pills.selectedContent : segmentedControlRecipe.item.selectedContent)
669
754
  : segmentedControlRecipe.item.idleContent, theme.palette);
670
755
  const leading = item.leading ?? item.renderLeading?.({
671
756
  selected: isSelected,
@@ -676,29 +761,31 @@ export function SegmentedControl(props) {
676
761
  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
762
  {
678
763
  alignItems: "center",
679
- backgroundColor: isSelected
764
+ backgroundColor: !pills && isSelected
680
765
  ? resolveColorReference(segmentedControlRecipe.item.selectedBackground, theme.palette)
681
766
  : "transparent",
682
767
  borderColor: isSelected
683
768
  ? resolveColorReference(segmentedControlRecipe.item.selectedBorder, theme.palette)
684
769
  : "transparent",
685
770
  borderRadius: radius[segmentedControlRecipe.item.radius],
686
- borderWidth: isSelected
771
+ borderWidth: !pills && isSelected
687
772
  ? segmentedControlRecipe.item.selectedBorderWidth
688
773
  : 0,
689
- flex: stacked ? undefined : 1,
774
+ flex: stacked || pills ? undefined : 1,
775
+ maxWidth: pills ? "100%" : undefined,
690
776
  gap: segmentedControlRecipe.item.gap,
691
777
  justifyContent: "center",
692
- minHeight: sizeContract.minHeight,
778
+ minHeight: pills ? segmentedControlRecipe.pills.minHeight : sizeContract.minHeight,
693
779
  opacity: optionDisabled
694
780
  ? segmentedControlRecipe.item.disabledOpacity
695
781
  : pressed
696
782
  ? segmentedControlRecipe.item.pressedOpacity
697
783
  : 1,
698
- paddingHorizontal: sizeContract.paddingHorizontal,
784
+ paddingHorizontal: pills ? spacing.md : sizeContract.paddingHorizontal,
785
+ paddingVertical: pills ? segmentedControlRecipe.pills.inset : undefined,
699
786
  width: stacked ? "100%" : undefined,
700
787
  },
701
- ], children: [leading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", children: leading })) : null, _jsx(Text, { align: "center", style: {
788
+ ], 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
789
  color: contentColor,
703
790
  fontWeight: isSelected
704
791
  ? segmentedControlRecipe.item.selectedFontWeight
@@ -709,6 +796,7 @@ export function SegmentedControl(props) {
709
796
  /** Action/filter chip with role-specific, controlled selection semantics. */
710
797
  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
798
  const theme = useHjmNativeTheme();
799
+ warnDeprecatedStyleProps("Chip", { labelStyle }, "size/selected for label typography");
712
800
  const selectable = selectionMode !== "action";
713
801
  const active = selectable && selected === true;
714
802
  const metrics = chipRecipe.sizes[size];
@@ -746,7 +834,10 @@ export function Chip({ label, size = chipRecipe.defaults.size, disabled = false,
746
834
  direction: theme.environment.direction,
747
835
  flexDirection: "row",
748
836
  gap: metrics.gap,
749
- height: visibleControlHeight(metrics.height, theme.environment.minimumVisualTarget),
837
+ // A floor, not a fixed height, as Button does for text content and as Web `.hjm-chip` already does
838
+ // (min-block-size): the fixed 36 clipped the filter-trigger and suggested-query labels at accessibility-large
839
+ // (utilverse 1.13.0 adoption, 2026-10-06). At 1x the label fits, so the chip is still 36.
840
+ minHeight: visibleControlHeight(metrics.height, theme.environment.minimumVisualTarget),
750
841
  opacity: disabled
751
842
  ? chipRecipe.states.disabledOpacity
752
843
  : pressed