@hjmds/react-native 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +8 -8
  2. package/dist/actions.d.ts +3 -23
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +7 -10
  5. package/dist/actions.js.map +1 -1
  6. package/dist/agreement.d.ts +1 -7
  7. package/dist/agreement.d.ts.map +1 -1
  8. package/dist/agreement.js +21 -15
  9. package/dist/agreement.js.map +1 -1
  10. package/dist/asset.d.ts +2 -13
  11. package/dist/asset.d.ts.map +1 -1
  12. package/dist/asset.js +4 -7
  13. package/dist/asset.js.map +1 -1
  14. package/dist/bottom-info.d.ts +1 -7
  15. package/dist/bottom-info.d.ts.map +1 -1
  16. package/dist/bottom-info.js +2 -4
  17. package/dist/bottom-info.js.map +1 -1
  18. package/dist/carousel-motion.d.ts +1 -0
  19. package/dist/carousel-motion.d.ts.map +1 -1
  20. package/dist/carousel-motion.js +5 -3
  21. package/dist/carousel-motion.js.map +1 -1
  22. package/dist/carousel.d.ts +1 -7
  23. package/dist/carousel.d.ts.map +1 -1
  24. package/dist/carousel.js +14 -6
  25. package/dist/carousel.js.map +1 -1
  26. package/dist/collapsible.d.ts +1 -7
  27. package/dist/collapsible.d.ts.map +1 -1
  28. package/dist/collapsible.js +4 -4
  29. package/dist/collapsible.js.map +1 -1
  30. package/dist/data-display.d.ts +19 -153
  31. package/dist/data-display.d.ts.map +1 -1
  32. package/dist/data-display.js +73 -90
  33. package/dist/data-display.js.map +1 -1
  34. package/dist/date-entry.d.ts +1 -0
  35. package/dist/date-entry.d.ts.map +1 -1
  36. package/dist/date-entry.js +22 -20
  37. package/dist/date-entry.js.map +1 -1
  38. package/dist/date-picker.d.ts +1 -7
  39. package/dist/date-picker.d.ts.map +1 -1
  40. package/dist/date-picker.js +19 -11
  41. package/dist/date-picker.js.map +1 -1
  42. package/dist/duration-field.d.ts +1 -1
  43. package/dist/duration-field.d.ts.map +1 -1
  44. package/dist/duration-field.js +11 -9
  45. package/dist/duration-field.js.map +1 -1
  46. package/dist/evidence.d.ts +1 -1
  47. package/dist/evidence.js +1 -1
  48. package/dist/feedback.d.ts +8 -88
  49. package/dist/feedback.d.ts.map +1 -1
  50. package/dist/feedback.js +72 -62
  51. package/dist/feedback.js.map +1 -1
  52. package/dist/field-group.d.ts +3 -2
  53. package/dist/field-group.d.ts.map +1 -1
  54. package/dist/field-group.js +7 -4
  55. package/dist/field-group.js.map +1 -1
  56. package/dist/file-picker.d.ts +1 -7
  57. package/dist/file-picker.d.ts.map +1 -1
  58. package/dist/file-picker.js +21 -11
  59. package/dist/file-picker.js.map +1 -1
  60. package/dist/forms.d.ts +6 -21
  61. package/dist/forms.d.ts.map +1 -1
  62. package/dist/forms.js +29 -35
  63. package/dist/forms.js.map +1 -1
  64. package/dist/gravity-letters.d.ts.map +1 -1
  65. package/dist/gravity-letters.js +13 -5
  66. package/dist/gravity-letters.js.map +1 -1
  67. package/dist/grid-reveal.d.ts.map +1 -1
  68. package/dist/grid-reveal.js +10 -2
  69. package/dist/grid-reveal.js.map +1 -1
  70. package/dist/heading.d.ts +1 -7
  71. package/dist/heading.d.ts.map +1 -1
  72. package/dist/heading.js +1 -4
  73. package/dist/heading.js.map +1 -1
  74. package/dist/image-viewer.d.ts.map +1 -1
  75. package/dist/image-viewer.js +2 -1
  76. package/dist/image-viewer.js.map +1 -1
  77. package/dist/index.d.ts +1 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/inline-confirm.d.ts.map +1 -1
  82. package/dist/inline-confirm.js +5 -1
  83. package/dist/inline-confirm.js.map +1 -1
  84. package/dist/inputs.d.ts +4 -71
  85. package/dist/inputs.d.ts.map +1 -1
  86. package/dist/inputs.js +25 -50
  87. package/dist/inputs.js.map +1 -1
  88. package/dist/internal/accessible-focus.d.ts +3 -0
  89. package/dist/internal/accessible-focus.d.ts.map +1 -0
  90. package/dist/internal/accessible-focus.js +15 -0
  91. package/dist/internal/accessible-focus.js.map +1 -0
  92. package/dist/internal/diagnostics.d.ts +7 -0
  93. package/dist/internal/diagnostics.d.ts.map +1 -0
  94. package/dist/internal/diagnostics.js +20 -0
  95. package/dist/internal/diagnostics.js.map +1 -0
  96. package/dist/internal/document-resource.d.ts.map +1 -1
  97. package/dist/internal/document-resource.js +2 -2
  98. package/dist/internal/document-resource.js.map +1 -1
  99. package/dist/internal/spinner.d.ts +1 -7
  100. package/dist/internal/spinner.d.ts.map +1 -1
  101. package/dist/internal/spinner.js +2 -4
  102. package/dist/internal/spinner.js.map +1 -1
  103. package/dist/masonry.d.ts.map +1 -1
  104. package/dist/masonry.js +2 -1
  105. package/dist/masonry.js.map +1 -1
  106. package/dist/mentions.d.ts +1 -7
  107. package/dist/mentions.d.ts.map +1 -1
  108. package/dist/mentions.js +11 -7
  109. package/dist/mentions.js.map +1 -1
  110. package/dist/navigation.d.ts +4 -74
  111. package/dist/navigation.d.ts.map +1 -1
  112. package/dist/navigation.js +97 -45
  113. package/dist/navigation.js.map +1 -1
  114. package/dist/number-field.d.ts +1 -21
  115. package/dist/number-field.d.ts.map +1 -1
  116. package/dist/number-field.js +6 -5
  117. package/dist/number-field.js.map +1 -1
  118. package/dist/overlays.d.ts +7 -16
  119. package/dist/overlays.d.ts.map +1 -1
  120. package/dist/overlays.js +220 -213
  121. package/dist/overlays.js.map +1 -1
  122. package/dist/primitives.d.ts +3 -18
  123. package/dist/primitives.d.ts.map +1 -1
  124. package/dist/primitives.js +8 -10
  125. package/dist/primitives.js.map +1 -1
  126. package/dist/provider-button.d.ts +1 -7
  127. package/dist/provider-button.d.ts.map +1 -1
  128. package/dist/provider-button.js +1 -4
  129. package/dist/provider-button.js.map +1 -1
  130. package/dist/provider.d.ts.map +1 -1
  131. package/dist/provider.js +9 -1
  132. package/dist/provider.js.map +1 -1
  133. package/dist/saved-items.d.ts.map +1 -1
  134. package/dist/saved-items.js +6 -1
  135. package/dist/saved-items.js.map +1 -1
  136. package/dist/screen-flows.d.ts +1 -2
  137. package/dist/screen-flows.d.ts.map +1 -1
  138. package/dist/screen-flows.js +60 -16
  139. package/dist/screen-flows.js.map +1 -1
  140. package/dist/screens.d.ts.map +1 -1
  141. package/dist/screens.js +21 -10
  142. package/dist/screens.js.map +1 -1
  143. package/dist/sheet-gesture.d.ts.map +1 -1
  144. package/dist/sheet-gesture.js +6 -3
  145. package/dist/sheet-gesture.js.map +1 -1
  146. package/dist/slider.d.ts +1 -21
  147. package/dist/slider.d.ts.map +1 -1
  148. package/dist/slider.js +3 -5
  149. package/dist/slider.js.map +1 -1
  150. package/dist/sortable.d.ts.map +1 -1
  151. package/dist/sortable.js +2 -1
  152. package/dist/sortable.js.map +1 -1
  153. package/dist/status-text.d.ts +17 -0
  154. package/dist/status-text.d.ts.map +1 -0
  155. package/dist/status-text.js +18 -0
  156. package/dist/status-text.js.map +1 -0
  157. package/dist/steps.d.ts +1 -7
  158. package/dist/steps.d.ts.map +1 -1
  159. package/dist/steps.js +2 -4
  160. package/dist/steps.js.map +1 -1
  161. package/dist/swipe-actions.d.ts.map +1 -1
  162. package/dist/swipe-actions.js +13 -2
  163. package/dist/swipe-actions.js.map +1 -1
  164. package/dist/tags-input.d.ts +1 -7
  165. package/dist/tags-input.d.ts.map +1 -1
  166. package/dist/tags-input.js +2 -4
  167. package/dist/tags-input.js.map +1 -1
  168. package/dist/toggle-group.d.ts +1 -7
  169. package/dist/toggle-group.d.ts.map +1 -1
  170. package/dist/toggle-group.js +10 -5
  171. package/dist/toggle-group.js.map +1 -1
  172. package/dist/top.d.ts +1 -7
  173. package/dist/top.d.ts.map +1 -1
  174. package/dist/top.js +1 -4
  175. package/dist/top.js.map +1 -1
  176. package/dist/transfer-list.d.ts +2 -8
  177. package/dist/transfer-list.d.ts.map +1 -1
  178. package/dist/transfer-list.js +30 -11
  179. package/dist/transfer-list.js.map +1 -1
  180. package/dist/upload-item.d.ts +1 -7
  181. package/dist/upload-item.d.ts.map +1 -1
  182. package/dist/upload-item.js +2 -4
  183. package/dist/upload-item.js.map +1 -1
  184. package/dist/virtual-list.d.ts.map +1 -1
  185. package/dist/virtual-list.js +2 -1
  186. package/dist/virtual-list.js.map +1 -1
  187. package/docs/liquid-toast.md +9 -0
  188. package/package.json +9 -3
  189. package/dist/internal/deprecated-style.d.ts +0 -8
  190. package/dist/internal/deprecated-style.d.ts.map +0 -1
  191. package/dist/internal/deprecated-style.js +0 -34
  192. package/dist/internal/deprecated-style.js.map +0 -1
@@ -1,25 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { assertNoRemovedStyleProps } from "./internal/diagnostics.js";
2
3
  import { resolveAvatarInitials } from "@hjmds/design-contracts/avatar-fallback";
3
4
  import { resolveColorReference } from "@hjmds/design-contracts/color-references";
4
5
  import { fontWeight, glyph, radius, spacing } from "@hjmds/design-contracts/foundations";
5
6
  import { resolveDescriptionListColumnCount, resolveDescriptionListDescriptor, } from "@hjmds/design-contracts/components/description-list";
6
7
  import { resolveStatisticDescriptor, validateStatisticGroup, } from "@hjmds/design-contracts/components/statistic";
7
8
  import { resolveTagDescriptor, resolveTagPresentation, tagRecipe, } from "@hjmds/design-contracts/components/tag";
8
- import { warnDeprecatedStyleProps } from "./internal/deprecated-style.js";
9
9
  import { cardRecipe } from "@hjmds/design-contracts/components/card";
10
10
  import { imageRecipe, nativeResizeModes, resolveImageAspectRatio, resolveImageDescriptor, resolveImageFallbackAccessibilityLabel, } from "@hjmds/design-contracts/components/image";
11
11
  import { resolveTimelineDescriptor, timelineRecipe, } from "@hjmds/design-contracts/components/timeline";
12
12
  import { surfaceDefaults, surfaceGeometry } from "@hjmds/design-contracts/recipes/base";
13
13
  import { accordionRecipe, counterBadgeRecipe, badgeRecipe, listRecipe, formatCounterBadgeCount, listRowRecipe, statisticRecipe, } from "@hjmds/design-contracts/recipes";
14
- import { Children, isValidElement, useEffect, useMemo, useRef, useState, } from "react";
14
+ import { Children, isValidElement, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
15
15
  import { Image as NativeImage, LayoutAnimation, Pressable, StyleSheet, View, useWindowDimensions, } from "react-native";
16
16
  import { useControllableState } from "./internal/state.js";
17
17
  import { minimumTargetStyle } from "./internal/styles.js";
18
18
  import { webDisclosureProps, webOnly } from "./internal/web-a11y.js";
19
19
  import { Surface, Text, } from "./primitives.js";
20
20
  import { useHjmNativeTheme } from "./provider.js";
21
- export function Badge({ label, tone = badgeRecipe.defaults.tone, size = badgeRecipe.defaults.size, variant = badgeRecipe.defaults.variant, leading, accessibilityLabel, style, labelStyle, layoutStyle, ...props }) {
22
- warnDeprecatedStyleProps("Badge", { style, labelStyle }, "layoutStyle for placement and tone/size/variant for appearance");
21
+ export function Badge({ label, tone = badgeRecipe.defaults.tone, size = badgeRecipe.defaults.size, variant = badgeRecipe.defaults.variant, leading, accessibilityLabel, layoutStyle, ...props }) {
22
+ assertNoRemovedStyleProps("Badge", props, ["style", "labelStyle"]);
23
23
  const theme = useHjmNativeTheme();
24
24
  const presentation = badgeRecipe.tones[tone];
25
25
  const metrics = badgeRecipe.sizes[size];
@@ -49,17 +49,15 @@ export function Badge({ label, tone = badgeRecipe.defaults.tone, size = badgeRec
49
49
  minHeight: metrics.minHeight,
50
50
  paddingHorizontal: metrics.paddingHorizontal,
51
51
  },
52
- style,
53
52
  layoutStyle,
54
53
  ], children: [leading === undefined ? null : (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", children: leading })), _jsx(Text, { accessible: false, align: "center", emphasis: "strong", style: [
55
54
  {
56
55
  color: resolveColorReference(outlined ? presentation.outlineContent : presentation.content, theme.palette),
57
56
  },
58
- labelStyle,
59
57
  ], variant: metrics.textVariant, children: label })] }));
60
58
  }
61
- export function Tag({ children, tone, accessibilityLabel, layoutStyle, style, labelStyle, ...props }) {
62
- warnDeprecatedStyleProps("Tag", { style, labelStyle }, "layoutStyle for placement and tone for appearance");
59
+ export function Tag({ children, tone, accessibilityLabel, layoutStyle, ...props }) {
60
+ assertNoRemovedStyleProps("Tag", props, ["style", "labelStyle"]);
63
61
  const theme = useHjmNativeTheme();
64
62
  const resolvedLabel = children;
65
63
  if (resolvedLabel === undefined) {
@@ -84,9 +82,8 @@ export function Tag({ children, tone, accessibilityLabel, layoutStyle, style, la
84
82
  minHeight: tagRecipe.size.minHeight,
85
83
  paddingHorizontal: tagRecipe.size.paddingHorizontal,
86
84
  },
87
- style,
88
85
  layoutStyle,
89
- ], children: _jsx(Text, { align: "center", emphasis: "medium", style: [{ color: presentation.content }, labelStyle], variant: tagRecipe.size.textVariant, children: descriptor.label }) }));
86
+ ], children: _jsx(Text, { align: "center", emphasis: "medium", style: [{ color: presentation.content }], variant: tagRecipe.size.textVariant, children: descriptor.label }) }));
90
87
  }
91
88
  export function Card({ children, title, description, leading, media, actions, selected = cardRecipe.defaults.selected, onActivate, disabled = false, accessibilityLabel, accessibilityHint, tone = cardRecipe.defaults.tone, bordered = cardRecipe.defaults.bordered, padding = cardRecipe.defaults.padding, layoutStyle, radius: cornerRadius = surfaceDefaults.radius, ...props }) {
92
89
  const { environment, tokens } = useHjmNativeTheme();
@@ -120,9 +117,9 @@ export function Card({ children, title, description, leading, media, actions, se
120
117
  opacity: disabled ? cardRecipe.interaction.disabledOpacity : pressed ? cardRecipe.interaction.pressedOpacity : 1,
121
118
  }, layoutStyle], children: frame });
122
119
  }
123
- export function ListRow({ title, description, leading, trailing, titleMetadata, trailingAction, trailingText, metadataLabel, trailingLabel, onPress, accessibilityLabel, accessibilityHint, disabled = false, density = listRowRecipe.defaults.density, selected: selectedProp, leadingShape = "square", layoutStyle, leadingStyle, contentStyle, titleStyle, titleRowStyle, descriptionStyle, trailingStyle, trailingActionStyle, containerProps, accessibilityState, ...forwarded }) {
120
+ export function ListRow({ title, tone = listRowRecipe.defaults.tone, description, leading, trailing, titleMetadata, trailingAction, trailingText, metadataLabel, trailingLabel, onPress, accessibilityLabel, accessibilityHint, disabled = false, density = listRowRecipe.defaults.density, loading = false, loadingLabel, selected: selectedProp, leadingShape = "square", layoutStyle, leadingStyle, contentStyle, titleRowStyle, trailingStyle, trailingActionStyle, containerProps, accessibilityState, ...forwarded }) {
121
+ assertNoRemovedStyleProps("ListRow", forwarded, ["titleStyle", "descriptionStyle"]);
124
122
  const { hjmTitleEmphasis, ...props } = forwarded;
125
- warnDeprecatedStyleProps("ListRow", { titleStyle, descriptionStyle }, "density/selected and the listRowRecipe typography for appearance");
126
123
  const theme = useHjmNativeTheme();
127
124
  const metrics = listRowRecipe.density[density];
128
125
  const interactive = onPress !== undefined;
@@ -155,6 +152,15 @@ export function ListRow({ title, description, leading, trailing, titleMetadata,
155
152
  width: listRowRecipe.leadingSize,
156
153
  ...(leadingRadius === null ? {} : { borderRadius: theme.tokens.radius[leadingRadius] }),
157
154
  };
155
+ if (loading) {
156
+ // Search and other collections share ListRow geometry instead of approximating it with
157
+ // loose Skeletons. Never mount placeholder actions or forward touch handlers while loading.
158
+ const placeholder = { backgroundColor: theme.colors.surfaceAlt, borderRadius: theme.tokens.radius.sm };
159
+ return _jsxs(View, { testID: props.testID, onLayout: props.onLayout, accessible: Boolean(loadingLabel), accessibilityRole: "progressbar", accessibilityLabel: loadingLabel, accessibilityState: { busy: true }, importantForAccessibility: loadingLabel ? "yes" : "no-hide-descendants", style: [{ alignItems: "center", flexDirection: "row", direction: theme.environment.direction,
160
+ gap: listRowRecipe.gap, minHeight: visualState.minHeight,
161
+ paddingHorizontal: metrics.paddingHorizontal, paddingVertical: metrics.paddingVertical,
162
+ borderWidth: 1, borderColor: "transparent" }, layoutStyle], children: [leading ? _jsx(View, { style: [placeholder, leadingFrameStyle] }) : null, _jsxs(View, { style: { flex: 1, minWidth: 0, gap: spacing.xxs }, children: [_jsx(View, { style: [placeholder, { width: `${listRowRecipe.loading.titleWidthPercent}%`, height: theme.tokens.typography[listRowRecipe.title.textVariant].lineHeight }] }), description ? _jsx(View, { style: [placeholder, { width: `${listRowRecipe.loading.descriptionWidthPercent}%`, height: theme.tokens.typography[listRowRecipe.description.textVariant].lineHeight }] }) : null] }), trailing || trailingText ? _jsx(View, { style: [placeholder, { width: listRowRecipe.loading.trailingWidth, height: theme.tokens.typography[listRowRecipe.trailing.textVariant].lineHeight }] }) : null] });
163
+ }
158
164
  const rowContent = (_jsxs(_Fragment, { children: [leading ? (_jsx(View, { accessible: interactive ? false : undefined, importantForAccessibility: interactive ? "no-hide-descendants" : "auto", style: [leadingFrameStyle, leadingStyle], children: leading })) : null, _jsxs(View, { style: [{ flex: 1, gap: spacing.xxs, minWidth: 0 }, contentStyle], children: [_jsxs(View, { style: [
159
165
  {
160
166
  alignItems: "center",
@@ -166,14 +172,12 @@ export function ListRow({ title, description, leading, trailing, titleMetadata,
166
172
  titleRowStyle,
167
173
  ], children: [_jsx(Text, { style: [
168
174
  {
169
- color: resolveColorReference(listRowRecipe.title.color, theme.palette),
175
+ color: resolveColorReference(listRowRecipe.tones[tone].titleColor, theme.palette),
170
176
  flexShrink: 1,
171
177
  fontWeight: hjmTitleEmphasis === "regular" ? fontWeight.regular : listRowRecipe.title.fontWeight,
172
178
  },
173
- titleStyle,
174
179
  ], variant: listRowRecipe.title.textVariant, children: title }), resolvedMetadata === undefined ? null : (_jsx(View, { accessible: interactive ? false : undefined, importantForAccessibility: interactive ? "no-hide-descendants" : "auto", children: resolvedMetadata }))] }), description ? (_jsx(Text, { style: [
175
180
  { color: resolveColorReference(listRowRecipe.description.color, theme.palette) },
176
- descriptionStyle,
177
181
  ], variant: listRowRecipe.description.textVariant, children: description })) : null] }), trailingText || trailing ? (_jsx(View, { accessible: interactive ? false : undefined, importantForAccessibility: interactive ? "no-hide-descendants" : "auto", style: [{ flexShrink: 0 }, trailingStyle], children: trailingText ? (_jsx(Text, { style: { color: resolveColorReference(listRowRecipe.trailing.textColor, theme.palette) }, variant: listRowRecipe.trailing.textVariant, children: trailingText })) : trailing })) : null] }));
178
182
  const contentStyleFor = (pressed) => [
179
183
  minimumTargetStyle,
@@ -223,10 +227,9 @@ export function ListRow({ title, description, leading, trailing, titleMetadata,
223
227
  trailingActionStyle,
224
228
  ], children: trailingAction })] }));
225
229
  }
226
- export function Avatar({ source, renderImage, name, initials, renderFallback, size = 44, decorative = false, accessibilityLabel, style, imageStyle, layoutStyle, }) {
230
+ export function Avatar({ source, renderImage, name, initials, renderFallback, size = 44, decorative = false, accessibilityLabel, layoutStyle, }) {
227
231
  if (!Number.isFinite(size) || size < 24)
228
232
  throw new RangeError("Avatar size must be at least 24");
229
- warnDeprecatedStyleProps("Avatar", { style, imageStyle }, "layoutStyle for placement and size/renderFallback for appearance");
230
233
  const { colors } = useHjmNativeTheme();
231
234
  const sourceKey = source === undefined ? "none" : resolveImageSourceKey(source);
232
235
  const [failedSource, setFailedSource] = useState(null);
@@ -259,55 +262,55 @@ export function Avatar({ source, renderImage, name, initials, renderFallback, si
259
262
  overflow: "hidden",
260
263
  width: size,
261
264
  },
262
- style,
263
265
  layoutStyle,
264
266
  ], children: source !== undefined && !failed ? (renderImage ? (_jsx(View, { accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: { height: size, width: size }, children: renderImage({ source, size, onError: failImage,
265
- fallback: renderFallback?.({ size, decorative: true }) ?? _jsx(Text, { align: "center", style: { color: colors.contentBrand }, variant: "label", children: fallback }) }) })) : (_jsx(NativeImage, { accessible: false, onError: failImage, source: source, style: [{ height: size, width: size }, imageStyle] }))) : (_jsx(View, { accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: renderFallback?.({ size, decorative: true }) ?? _jsx(Text, { align: "center", style: { color: colors.contentBrand }, variant: "label", children: fallback }) })) }));
267
+ fallback: renderFallback?.({ size, decorative: true }) ?? _jsx(Text, { align: "center", style: { color: colors.contentBrand }, variant: "label", children: fallback }) }) })) : (_jsx(NativeImage, { accessible: false, onError: failImage, source: source, style: [{ height: size, width: size }] }))) : (_jsx(View, { accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: renderFallback?.({ size, decorative: true }) ?? _jsx(Text, { align: "center", style: { color: colors.contentBrand }, variant: "label", children: fallback }) })) }));
266
268
  }
267
- export function Divider({ orientation = "horizontal", inset = 0, style, layoutStyle }) {
269
+ export function Divider({ orientation = "horizontal", inset = 0, layoutStyle }) {
268
270
  if (!Number.isFinite(inset) || inset < 0)
269
271
  throw new RangeError("Divider inset must be non-negative");
270
- warnDeprecatedStyleProps("Divider", { style }, "layoutStyle for placement and orientation/inset for appearance");
271
272
  const { colors } = useHjmNativeTheme();
272
273
  return (_jsx(View, { accessible: false, style: [
273
274
  orientation === "horizontal"
274
275
  ? { backgroundColor: colors.border, height: 1, marginHorizontal: inset, width: "auto" }
275
276
  : { alignSelf: "stretch", backgroundColor: colors.border, marginVertical: inset, width: 1 },
276
- style,
277
277
  layoutStyle,
278
278
  ] }));
279
279
  }
280
280
  // UI control labels keep the ui font even when their metric variant is body; content still uses reading.
281
- export function Accordion({ label, items, expandedValues, defaultExpandedValues = [], onExpandedValuesChange, multiple = accordionRecipe.defaults.allowsMultipleExpanded, density = accordionRecipe.defaults.density, renderIndicator, style, itemStyle, triggerStyle, titleStyle, indicatorStyle, panelStyle, layoutStyle, }) {
282
- warnDeprecatedStyleProps("Accordion", { style, itemStyle, triggerStyle, titleStyle, indicatorStyle, panelStyle }, "layoutStyle for placement and density/renderIndicator for appearance");
281
+ export function Accordion({ label, items, expandedValues, defaultExpandedValues = [], onExpandedValuesChange, multiple = accordionRecipe.defaults.allowsMultipleExpanded, density = accordionRecipe.defaults.density, renderIndicator, layoutStyle, }) {
283
282
  if (items.length === 0)
284
283
  throw new Error("Accordion requires at least one item");
285
284
  const itemValues = new Set(items.map((item) => item.value));
286
285
  if (itemValues.size !== items.length)
287
286
  throw new TypeError("Accordion values must be unique");
288
- const initial = expandedValues ?? defaultExpandedValues;
289
- if (initial.some((value) => !itemValues.has(value))) {
290
- throw new RangeError("Accordion expanded values must match items");
291
- }
292
- if (!multiple && initial.length > 1) {
293
- throw new RangeError("Accordion only accepts one expanded value unless multiple is true");
294
- }
295
287
  const theme = useHjmNativeTheme();
296
288
  const metrics = accordionRecipe.density[density];
297
289
  const indicatorColor = resolveColorReference(accordionRecipe.indicator.color, theme.palette);
298
- const [expanded, setExpanded] = useControllableState({
290
+ const [storedExpanded, setExpanded] = useControllableState({
299
291
  ...(expandedValues === undefined ? {} : { value: expandedValues }),
300
292
  defaultValue: defaultExpandedValues,
301
293
  ...(onExpandedValuesChange === undefined ? {} : { onChange: onExpandedValuesChange }),
302
294
  });
303
- return (_jsx(View, { accessibilityLabel: label, accessibilityRole: "list", style: [style, layoutStyle], children: items.map((item) => {
295
+ const committed = useRef(false);
296
+ const expanded = expandedValues === undefined && committed.current ? storedExpanded.filter(value => itemValues.has(value)) : storedExpanded;
297
+ if (expanded.some(value => !itemValues.has(value)))
298
+ throw new RangeError("Accordion expanded values must match items");
299
+ if (!multiple && expanded.length > 1)
300
+ throw new RangeError("Accordion only accepts one expanded value unless multiple is true");
301
+ useLayoutEffect(() => {
302
+ committed.current = true;
303
+ // Retire expansion with the deleted item instead of reopening it when its key returns.
304
+ if (expanded.length !== storedExpanded.length)
305
+ setExpanded(expanded);
306
+ }, [expanded, storedExpanded, setExpanded]);
307
+ return (_jsx(View, { accessibilityLabel: label, accessibilityRole: "list", style: [layoutStyle], children: items.map((item) => {
304
308
  const isExpanded = expanded.includes(item.value);
305
309
  return (_jsxs(View, { style: [
306
310
  {
307
311
  borderBottomColor: resolveColorReference(accordionRecipe.divider, theme.palette),
308
312
  borderBottomWidth: 1,
309
313
  },
310
- itemStyle,
311
314
  ], children: [_jsxs(Pressable, { accessibilityHint: item.accessibilityHint, accessibilityLabel: item.accessibilityLabel ?? item.title, accessibilityRole: "button", accessibilityState: { disabled: item.disabled === true, expanded: isExpanded }, ...webOnly(webDisclosureProps(isExpanded, item.disabled === true)), disabled: item.disabled, onPress: () => {
312
315
  if (!theme.environment.reducedMotion) {
313
316
  LayoutAnimation.configureNext({
@@ -338,14 +341,12 @@ export function Accordion({ label, items, expandedValues, defaultExpandedValues
338
341
  paddingHorizontal: accordionRecipe.paddingHorizontal,
339
342
  paddingVertical: metrics.paddingVertical,
340
343
  },
341
- triggerStyle,
342
344
  ], children: [_jsxs(View, { style: { flex: 1, gap: spacing.xxs }, children: [_jsx(Text, { fontRole: "ui", style: [
343
345
  {
344
346
  color: resolveColorReference(accordionRecipe.title.color, theme.palette),
345
347
  fontWeight: accordionRecipe.title.fontWeight,
346
348
  },
347
- titleStyle,
348
- ], variant: accordionRecipe.title.textVariant, children: item.title }), item.description ? _jsx(Text, { tone: "muted", variant: "caption", children: item.description }) : null] }), _jsx(View, { accessible: false, style: indicatorStyle, children: renderIndicator ? (renderIndicator({
349
+ ], variant: accordionRecipe.title.textVariant, children: item.title }), item.description ? _jsx(Text, { tone: "muted", variant: "caption", children: item.description }) : null] }), _jsx(View, { accessible: false, children: renderIndicator ? (renderIndicator({
349
350
  value: item.value,
350
351
  expanded: isExpanded,
351
352
  disabled: item.disabled === true,
@@ -356,12 +357,11 @@ export function Accordion({ label, items, expandedValues, defaultExpandedValues
356
357
  paddingBottom: accordionRecipe.panel.paddingBottom,
357
358
  paddingStart: accordionRecipe.panel.paddingInlineStart,
358
359
  },
359
- panelStyle,
360
360
  ], children: item.content })) : null] }, item.value));
361
361
  }) }));
362
362
  }
363
- export function DescriptionList({ label, descriptor, availableWidth, style, itemStyle, layoutStyle, onLayout, ...props }) {
364
- warnDeprecatedStyleProps("DescriptionList", { style, itemStyle }, "layoutStyle for placement and descriptor.columns for arrangement");
363
+ export function DescriptionList({ label, descriptor, availableWidth, layoutStyle, onLayout, ...props }) {
364
+ assertNoRemovedStyleProps("DescriptionList", props, ["style", "itemStyle"]);
365
365
  const resolved = resolveDescriptionListDescriptor(descriptor);
366
366
  const { width: windowWidth } = useWindowDimensions();
367
367
  const { environment } = useHjmNativeTheme();
@@ -388,9 +388,8 @@ export function DescriptionList({ label, descriptor, availableWidth, style, item
388
388
  flexWrap: "wrap",
389
389
  gap: spacing.sm,
390
390
  },
391
- style,
392
391
  layoutStyle,
393
- ], children: resolved.items.map((item) => (_jsxs(View, { accessibilityLabel: `${item.label}, ${item.value}`, accessible: true, style: [{ gap: spacing.xxs, width: itemWidth }, itemStyle], children: [_jsx(Text, { accessible: false, tone: "muted", variant: "label", children: item.label }), _jsx(Text, { accessible: false, tone: "primary", children: item.value })] }, item.id))) }));
392
+ ], children: resolved.items.map((item) => (_jsxs(View, { accessibilityLabel: `${item.label}, ${item.value}`, accessible: true, style: [{ gap: spacing.xxs, width: itemWidth }], children: [_jsx(Text, { accessible: false, tone: "muted", variant: "label", children: item.label }), _jsx(Text, { accessible: false, tone: "primary", children: item.value })] }, item.id))) }));
394
393
  }
395
394
  function resolveImageSourceKey(source) {
396
395
  if (typeof source === "number")
@@ -415,27 +414,32 @@ export function Image(imageProps) {
415
414
  const resolvedDecorative = descriptor.decorative;
416
415
  const resolvedAccessibilityLabel = descriptor.decorative ? undefined : descriptor.accessibilityLabel;
417
416
  const resolvedResizeMode = nativeResizeModes[descriptor.fit];
418
- const sourceKey = `src:${descriptor.src}`;
419
417
  const source = useMemo(() => sourceAdapter?.(descriptor) ?? { uri: descriptor.src }, [descriptor.accessibilityLabel, descriptor.decorative, descriptor.fit,
420
418
  descriptor.height, descriptor.src, descriptor.width, sourceAdapter]);
421
- const [state, setState] = useState({ sourceKey, status: "loading" });
422
- const status = state.sourceKey === sourceKey ? state.status : "loading";
423
- useEffect(() => {
424
- setState((current) => current.sourceKey === sourceKey
425
- ? current
426
- : { sourceKey, status: "loading" });
427
- }, [sourceKey]);
419
+ // Auth/cache changes can create a new request at the same URL. Compare the
420
+ // resolved source contents, not the inline adapter function's identity.
421
+ const sourceKey = JSON.stringify([descriptor.src, resolveImageSourceKey(source)]);
422
+ // Match the Web image lifecycle: a replaced/unmounted host cannot publish
423
+ // results, including an old A request after the product changes A→B→A.
424
+ const generation = useMemo(() => ({}), [sourceKey]);
425
+ const activeGeneration = useRef(generation);
426
+ useLayoutEffect(() => {
427
+ activeGeneration.current = generation;
428
+ return () => { activeGeneration.current = null; };
429
+ }, [generation]);
430
+ const [state, setState] = useState({ generation, status: "loading" });
431
+ const status = state.generation === generation ? state.status : "loading";
428
432
  const reportLoad = (event) => {
429
- setState((current) => current.sourceKey === sourceKey
430
- ? { sourceKey, status: "loaded" }
431
- : current);
433
+ if (activeGeneration.current !== generation)
434
+ return;
435
+ setState({ generation, status: "loaded" });
432
436
  onLoadStatusChange?.("loaded");
433
437
  onLoad?.(event);
434
438
  };
435
439
  const reportError = (event) => {
436
- setState((current) => current.sourceKey === sourceKey
437
- ? { sourceKey, status: "error" }
438
- : current);
440
+ if (activeGeneration.current !== generation)
441
+ return;
442
+ setState({ generation, status: "error" });
439
443
  onLoadStatusChange?.("error");
440
444
  onError?.(event);
441
445
  };
@@ -529,15 +533,15 @@ const styles = StyleSheet.create({
529
533
  width: glyph.lg,
530
534
  },
531
535
  });
532
- export function CounterBadge({ count, accessibilityLabel, max = counterBadgeRecipe.defaults.max, tone = counterBadgeRecipe.defaults.tone, size = counterBadgeRecipe.defaults.size, variant = counterBadgeRecipe.defaults.variant, style, layoutStyle, }) {
533
- warnDeprecatedStyleProps("CounterBadge", { style }, "layoutStyle for placement and tone/size/variant for appearance");
536
+ export function CounterBadge({ count, accessibilityLabel, max = counterBadgeRecipe.defaults.max, tone = counterBadgeRecipe.defaults.tone, size = counterBadgeRecipe.defaults.size, variant = counterBadgeRecipe.defaults.variant, layoutStyle, }) {
534
537
  if (accessibilityLabel !== undefined && !accessibilityLabel.trim()) {
535
538
  throw new TypeError("CounterBadge accessibilityLabel must not be empty");
536
539
  }
540
+ // A zero count hides the badge, but must not change its Hook call order.
541
+ const { colors } = useHjmNativeTheme();
537
542
  const visibleLabel = formatCounterBadgeCount(count, max);
538
543
  if (visibleLabel === null)
539
544
  return null;
540
- const { colors } = useHjmNativeTheme();
541
545
  const presentation = {
542
546
  danger: { background: colors.dangerFill, content: colors.onDanger },
543
547
  brand: { background: colors.primary, content: colors.onPrimary },
@@ -557,13 +561,12 @@ export function CounterBadge({ count, accessibilityLabel, max = counterBadgeReci
557
561
  minWidth: metrics.minWidth,
558
562
  paddingHorizontal: metrics.paddingHorizontal,
559
563
  },
560
- style,
561
564
  layoutStyle,
562
565
  ], children: _jsx(Text, { accessible: false, align: "center", style: { color: presentation[tone].content, fontWeight: "700" }, variant: "caption", children: visibleLabel }) }));
563
566
  }
564
567
  /** Semantic list container that owns separator rhythm around composed rows. */
565
- export function List({ label, children, separator = listRecipe.defaults.separator, appearance = "plain", style, layoutStyle, ...props }) {
566
- warnDeprecatedStyleProps("List", { style }, "layoutStyle for placement and appearance/separator for appearance");
568
+ export function List({ label, children, separator = listRecipe.defaults.separator, appearance = "plain", layoutStyle, ...props }) {
569
+ assertNoRemovedStyleProps("List", props, ["style"]);
567
570
  const { colors, environment, tokens } = useHjmNativeTheme();
568
571
  const items = Children.toArray(children);
569
572
  const separatorContract = listRecipe.separators[separator];
@@ -580,7 +583,6 @@ export function List({ label, children, separator = listRecipe.defaults.separato
580
583
  }
581
584
  : {}),
582
585
  },
583
- style,
584
586
  layoutStyle,
585
587
  ], children: items.map((item, index) => (_jsxs(View, { children: [item, separatorContract && index < items.length - 1 ? (_jsx(View, { accessible: false, style: {
586
588
  backgroundColor: colors.border,
@@ -590,21 +592,10 @@ export function List({ label, children, separator = listRecipe.defaults.separato
590
592
  } })) : null] }, isValidElement(item) && item.key !== null ? item.key : `hjm-list-${index}`))) }));
591
593
  }
592
594
  export function Statistic(props) {
593
- warnDeprecatedStyleProps("Statistic", {
594
- style: props.style,
595
- labelStyle: props.labelStyle,
596
- valueStyle: props.valueStyle,
597
- affixStyle: props.affixStyle,
598
- trendStyle: props.trendStyle,
599
- hintStyle: props.hintStyle,
600
- }, "layoutStyle for placement and density/presentation for appearance");
601
595
  return renderStatistic(props);
602
596
  }
603
- // StatisticGroup composes Statistic with its own item width and the group's deprecated `itemStyle`.
604
- // Routing through this plain function (called in the same component, so hook order is unchanged)
605
- // keeps those internal values from being reported as caller misuse of Statistic.style. A private
606
- // prop on the public component was rejected because it would leak into the exported type.
607
- function renderStatistic({ descriptor, density = "comfortable", presentation = "plain", contextLabel, accessibilityLabel, composeAccessibilityLabel, renderTrendMark, style, labelStyle, valueStyle, affixStyle, trendStyle, hintStyle, layoutStyle, }) {
597
+ // StatisticGroup reuses this renderer with its calculated layout width.
598
+ function renderStatistic({ descriptor, density = "comfortable", presentation = "plain", contextLabel, accessibilityLabel, composeAccessibilityLabel, renderTrendMark, layoutStyle, }) {
608
599
  const resolved = resolveStatisticDescriptor(descriptor);
609
600
  const theme = useHjmNativeTheme();
610
601
  const densityContract = statisticRecipe.density[density];
@@ -643,14 +634,12 @@ function renderStatistic({ descriptor, density = "comfortable", presentation = "
643
634
  minWidth: 0,
644
635
  padding: densityContract.padding,
645
636
  },
646
- style,
647
637
  layoutStyle,
648
638
  ], children: [_jsx(Text, { accessible: false, style: [
649
639
  {
650
640
  color: resolveColorReference(statisticRecipe.label.color, theme.palette),
651
641
  fontWeight: statisticRecipe.label.fontWeight,
652
642
  },
653
- labelStyle,
654
643
  ], variant: densityContract.labelVariant, children: resolved.label }), _jsxs(View, { accessible: false, style: {
655
644
  alignItems: "baseline",
656
645
  direction: theme.environment.direction,
@@ -663,7 +652,6 @@ function renderStatistic({ descriptor, density = "comfortable", presentation = "
663
652
  color: resolveColorReference(statisticRecipe.affix.color, theme.palette),
664
653
  fontWeight: statisticRecipe.affix.fontWeight,
665
654
  },
666
- affixStyle,
667
655
  ], variant: statisticRecipe.affix.textVariant, children: resolved.prefix })) : null, _jsx(Text, { accessible: false, style: [
668
656
  {
669
657
  color: resolveColorReference(statisticRecipe.value.color, theme.palette),
@@ -671,13 +659,11 @@ function renderStatistic({ descriptor, density = "comfortable", presentation = "
671
659
  fontVariant: ["tabular-nums"],
672
660
  fontWeight: statisticRecipe.value.fontWeight,
673
661
  },
674
- valueStyle,
675
662
  ], variant: densityContract.valueVariant, children: resolved.value }), resolved.suffix ? (_jsx(Text, { accessible: false, style: [
676
663
  {
677
664
  color: resolveColorReference(statisticRecipe.affix.color, theme.palette),
678
665
  fontWeight: statisticRecipe.affix.fontWeight,
679
666
  },
680
- affixStyle,
681
667
  ], variant: statisticRecipe.affix.textVariant, children: resolved.suffix })) : null] }), resolved.trend ? (_jsxs(View, { accessible: false, style: {
682
668
  alignItems: "center",
683
669
  flexDirection: "row",
@@ -690,14 +676,12 @@ function renderStatistic({ descriptor, density = "comfortable", presentation = "
690
676
  flexShrink: 1,
691
677
  fontWeight: statisticRecipe.trend.fontWeight,
692
678
  },
693
- trendStyle,
694
679
  ], variant: statisticRecipe.trend.textVariant, children: resolved.trend.label })] })) : null, resolved.hint ? (_jsx(Text, { accessible: false, style: [
695
680
  { color: resolveColorReference(statisticRecipe.hint.color, theme.palette) },
696
- hintStyle,
697
681
  ], variant: statisticRecipe.hint.textVariant, children: resolved.hint })) : null] }));
698
682
  }
699
- export function StatisticGroup({ label, descriptor, availableWidth, density, presentation, composeAccessibilityLabel, renderTrendMark, style, itemStyle, layoutStyle, onLayout, ...props }) {
700
- warnDeprecatedStyleProps("StatisticGroup", { style, itemStyle }, "layoutStyle for placement and density/presentation for appearance");
683
+ export function StatisticGroup({ label, descriptor, availableWidth, density, presentation, composeAccessibilityLabel, renderTrendMark, layoutStyle, onLayout, ...props }) {
684
+ assertNoRemovedStyleProps("StatisticGroup", props, ["style", "itemStyle"]);
701
685
  validateStatisticGroup(descriptor);
702
686
  const { width: windowWidth } = useWindowDimensions();
703
687
  const { environment } = useHjmNativeTheme();
@@ -736,20 +720,19 @@ export function StatisticGroup({ label, descriptor, availableWidth, density, pre
736
720
  flexWrap: "wrap",
737
721
  gap: statisticRecipe.group.gap,
738
722
  },
739
- style,
740
723
  layoutStyle,
741
- ], children: descriptor.items.map((item, index) => (_jsx(StatisticGroupItem, { contextLabel: label, descriptor: item, ...(composeAccessibilityLabel === undefined ? {} : { composeAccessibilityLabel }), ...(density === undefined ? {} : { density }), ...(presentation === undefined ? {} : { presentation }), ...(renderTrendMark === undefined ? {} : { renderTrendMark }), layoutStyle: { width: index >= finalRowStart ? finalRowItemWidth : itemWidth }, ...(itemStyle === undefined ? {} : { style: itemStyle }) }, item.id))) }));
724
+ ], children: descriptor.items.map((item, index) => (_jsx(StatisticGroupItem, { contextLabel: label, descriptor: item, ...(composeAccessibilityLabel === undefined ? {} : { composeAccessibilityLabel }), ...(density === undefined ? {} : { density }), ...(presentation === undefined ? {} : { presentation }), ...(renderTrendMark === undefined ? {} : { renderTrendMark }), layoutStyle: { width: index >= finalRowStart ? finalRowItemWidth : itemWidth } }, item.id))) }));
742
725
  }
743
726
  /** Group cell: same render as Statistic without attributing the group's styles to the caller. */
744
727
  function StatisticGroupItem(props) {
745
728
  return renderStatistic(props);
746
729
  }
747
730
  /** Ordered record of completed events; unlike Steps it has no current cursor. */
748
- export function Timeline({ items, composeAccessibleName, style, layoutStyle, ...props }) {
749
- warnDeprecatedStyleProps("Timeline", { style }, "layoutStyle for placement and item tone for appearance");
731
+ export function Timeline({ items, composeAccessibleName, layoutStyle, ...props }) {
732
+ assertNoRemovedStyleProps("Timeline", props, ["style"]);
750
733
  const theme = useHjmNativeTheme();
751
734
  const resolved = resolveTimelineDescriptor({ items }, { composeAccessibleName });
752
- return (_jsx(View, { ...props, style: [{ gap: timelineRecipe.gap }, style, layoutStyle], children: resolved.map((item, index) => {
735
+ return (_jsx(View, { ...props, style: [{ gap: timelineRecipe.gap }, layoutStyle], children: resolved.map((item, index) => {
753
736
  const tone = timelineRecipe.dot.tones[item.tone];
754
737
  const accessibilityLabel = [
755
738
  item.accessibleName,