@hjmds/react-native 1.17.2 → 1.19.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 (203) 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 +8 -11
  5. package/dist/actions.js.map +1 -1
  6. package/dist/activity-chip.d.ts +29 -0
  7. package/dist/activity-chip.d.ts.map +1 -0
  8. package/dist/activity-chip.js +54 -0
  9. package/dist/activity-chip.js.map +1 -0
  10. package/dist/agreement.d.ts +1 -7
  11. package/dist/agreement.d.ts.map +1 -1
  12. package/dist/agreement.js +21 -15
  13. package/dist/agreement.js.map +1 -1
  14. package/dist/asset.d.ts +2 -13
  15. package/dist/asset.d.ts.map +1 -1
  16. package/dist/asset.js +4 -7
  17. package/dist/asset.js.map +1 -1
  18. package/dist/bottom-info.d.ts +1 -7
  19. package/dist/bottom-info.d.ts.map +1 -1
  20. package/dist/bottom-info.js +2 -4
  21. package/dist/bottom-info.js.map +1 -1
  22. package/dist/carousel-motion.d.ts +1 -0
  23. package/dist/carousel-motion.d.ts.map +1 -1
  24. package/dist/carousel-motion.js +5 -3
  25. package/dist/carousel-motion.js.map +1 -1
  26. package/dist/carousel.d.ts +1 -7
  27. package/dist/carousel.d.ts.map +1 -1
  28. package/dist/carousel.js +14 -6
  29. package/dist/carousel.js.map +1 -1
  30. package/dist/collapsible.d.ts +1 -7
  31. package/dist/collapsible.d.ts.map +1 -1
  32. package/dist/collapsible.js +4 -4
  33. package/dist/collapsible.js.map +1 -1
  34. package/dist/content-transition.d.ts +2 -1
  35. package/dist/content-transition.d.ts.map +1 -1
  36. package/dist/content-transition.js +3 -3
  37. package/dist/content-transition.js.map +1 -1
  38. package/dist/data-display.d.ts +23 -154
  39. package/dist/data-display.d.ts.map +1 -1
  40. package/dist/data-display.js +86 -92
  41. package/dist/data-display.js.map +1 -1
  42. package/dist/date-entry.d.ts +1 -0
  43. package/dist/date-entry.d.ts.map +1 -1
  44. package/dist/date-entry.js +22 -20
  45. package/dist/date-entry.js.map +1 -1
  46. package/dist/date-picker.d.ts +1 -7
  47. package/dist/date-picker.d.ts.map +1 -1
  48. package/dist/date-picker.js +19 -11
  49. package/dist/date-picker.js.map +1 -1
  50. package/dist/duration-field.d.ts +1 -1
  51. package/dist/duration-field.d.ts.map +1 -1
  52. package/dist/duration-field.js +11 -9
  53. package/dist/duration-field.js.map +1 -1
  54. package/dist/evidence.d.ts +1 -1
  55. package/dist/evidence.js +1 -1
  56. package/dist/feedback.d.ts +9 -88
  57. package/dist/feedback.d.ts.map +1 -1
  58. package/dist/feedback.js +124 -81
  59. package/dist/feedback.js.map +1 -1
  60. package/dist/field-group.d.ts +3 -2
  61. package/dist/field-group.d.ts.map +1 -1
  62. package/dist/field-group.js +7 -4
  63. package/dist/field-group.js.map +1 -1
  64. package/dist/file-picker.d.ts +1 -7
  65. package/dist/file-picker.d.ts.map +1 -1
  66. package/dist/file-picker.js +21 -11
  67. package/dist/file-picker.js.map +1 -1
  68. package/dist/forms.d.ts +6 -21
  69. package/dist/forms.d.ts.map +1 -1
  70. package/dist/forms.js +29 -35
  71. package/dist/forms.js.map +1 -1
  72. package/dist/gravity-letters.d.ts.map +1 -1
  73. package/dist/gravity-letters.js +13 -5
  74. package/dist/gravity-letters.js.map +1 -1
  75. package/dist/grid-reveal.d.ts.map +1 -1
  76. package/dist/grid-reveal.js +10 -2
  77. package/dist/grid-reveal.js.map +1 -1
  78. package/dist/heading.d.ts +1 -7
  79. package/dist/heading.d.ts.map +1 -1
  80. package/dist/heading.js +1 -4
  81. package/dist/heading.js.map +1 -1
  82. package/dist/image-viewer.d.ts.map +1 -1
  83. package/dist/image-viewer.js +2 -1
  84. package/dist/image-viewer.js.map +1 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +1 -0
  88. package/dist/index.js.map +1 -1
  89. package/dist/inline-confirm.d.ts.map +1 -1
  90. package/dist/inline-confirm.js +5 -1
  91. package/dist/inline-confirm.js.map +1 -1
  92. package/dist/inputs.d.ts +4 -71
  93. package/dist/inputs.d.ts.map +1 -1
  94. package/dist/inputs.js +52 -67
  95. package/dist/inputs.js.map +1 -1
  96. package/dist/internal/accessible-focus.d.ts +3 -0
  97. package/dist/internal/accessible-focus.d.ts.map +1 -0
  98. package/dist/internal/accessible-focus.js +15 -0
  99. package/dist/internal/accessible-focus.js.map +1 -0
  100. package/dist/internal/diagnostics.d.ts +7 -0
  101. package/dist/internal/diagnostics.d.ts.map +1 -0
  102. package/dist/internal/diagnostics.js +20 -0
  103. package/dist/internal/diagnostics.js.map +1 -0
  104. package/dist/internal/document-resource.d.ts.map +1 -1
  105. package/dist/internal/document-resource.js +2 -2
  106. package/dist/internal/document-resource.js.map +1 -1
  107. package/dist/internal/recipe-button.js +1 -1
  108. package/dist/internal/recipe-button.js.map +1 -1
  109. package/dist/internal/spinner.d.ts +1 -7
  110. package/dist/internal/spinner.d.ts.map +1 -1
  111. package/dist/internal/spinner.js +2 -4
  112. package/dist/internal/spinner.js.map +1 -1
  113. package/dist/masonry.d.ts.map +1 -1
  114. package/dist/masonry.js +2 -1
  115. package/dist/masonry.js.map +1 -1
  116. package/dist/mentions.d.ts +1 -7
  117. package/dist/mentions.d.ts.map +1 -1
  118. package/dist/mentions.js +11 -7
  119. package/dist/mentions.js.map +1 -1
  120. package/dist/navigation.d.ts +4 -74
  121. package/dist/navigation.d.ts.map +1 -1
  122. package/dist/navigation.js +86 -45
  123. package/dist/navigation.js.map +1 -1
  124. package/dist/number-field.d.ts +1 -21
  125. package/dist/number-field.d.ts.map +1 -1
  126. package/dist/number-field.js +6 -5
  127. package/dist/number-field.js.map +1 -1
  128. package/dist/overlays.d.ts +7 -16
  129. package/dist/overlays.d.ts.map +1 -1
  130. package/dist/overlays.js +220 -213
  131. package/dist/overlays.js.map +1 -1
  132. package/dist/primitives.d.ts +3 -18
  133. package/dist/primitives.d.ts.map +1 -1
  134. package/dist/primitives.js +8 -10
  135. package/dist/primitives.js.map +1 -1
  136. package/dist/provider-button.d.ts +1 -7
  137. package/dist/provider-button.d.ts.map +1 -1
  138. package/dist/provider-button.js +1 -4
  139. package/dist/provider-button.js.map +1 -1
  140. package/dist/provider.d.ts.map +1 -1
  141. package/dist/provider.js +9 -1
  142. package/dist/provider.js.map +1 -1
  143. package/dist/saved-items.d.ts.map +1 -1
  144. package/dist/saved-items.js +6 -1
  145. package/dist/saved-items.js.map +1 -1
  146. package/dist/screen-flows.d.ts +1 -2
  147. package/dist/screen-flows.d.ts.map +1 -1
  148. package/dist/screen-flows.js +60 -16
  149. package/dist/screen-flows.js.map +1 -1
  150. package/dist/screens.d.ts +1 -1
  151. package/dist/screens.d.ts.map +1 -1
  152. package/dist/screens.js +32 -15
  153. package/dist/screens.js.map +1 -1
  154. package/dist/sheet-gesture.d.ts.map +1 -1
  155. package/dist/sheet-gesture.js +6 -3
  156. package/dist/sheet-gesture.js.map +1 -1
  157. package/dist/slider.d.ts +1 -21
  158. package/dist/slider.d.ts.map +1 -1
  159. package/dist/slider.js +3 -5
  160. package/dist/slider.js.map +1 -1
  161. package/dist/sortable.d.ts.map +1 -1
  162. package/dist/sortable.js +2 -1
  163. package/dist/sortable.js.map +1 -1
  164. package/dist/status-text.d.ts +17 -0
  165. package/dist/status-text.d.ts.map +1 -0
  166. package/dist/status-text.js +18 -0
  167. package/dist/status-text.js.map +1 -0
  168. package/dist/steps.d.ts +1 -7
  169. package/dist/steps.d.ts.map +1 -1
  170. package/dist/steps.js +2 -4
  171. package/dist/steps.js.map +1 -1
  172. package/dist/swipe-actions.d.ts.map +1 -1
  173. package/dist/swipe-actions.js +13 -2
  174. package/dist/swipe-actions.js.map +1 -1
  175. package/dist/tags-input.d.ts +1 -7
  176. package/dist/tags-input.d.ts.map +1 -1
  177. package/dist/tags-input.js +2 -4
  178. package/dist/tags-input.js.map +1 -1
  179. package/dist/toggle-group.d.ts +1 -7
  180. package/dist/toggle-group.d.ts.map +1 -1
  181. package/dist/toggle-group.js +10 -5
  182. package/dist/toggle-group.js.map +1 -1
  183. package/dist/top.d.ts +1 -7
  184. package/dist/top.d.ts.map +1 -1
  185. package/dist/top.js +1 -4
  186. package/dist/top.js.map +1 -1
  187. package/dist/transfer-list.d.ts +2 -8
  188. package/dist/transfer-list.d.ts.map +1 -1
  189. package/dist/transfer-list.js +30 -11
  190. package/dist/transfer-list.js.map +1 -1
  191. package/dist/upload-item.d.ts +1 -7
  192. package/dist/upload-item.d.ts.map +1 -1
  193. package/dist/upload-item.js +2 -4
  194. package/dist/upload-item.js.map +1 -1
  195. package/dist/virtual-list.d.ts.map +1 -1
  196. package/dist/virtual-list.js +2 -1
  197. package/dist/virtual-list.js.map +1 -1
  198. package/docs/liquid-toast.md +20 -0
  199. package/package.json +17 -4
  200. package/dist/internal/deprecated-style.d.ts +0 -8
  201. package/dist/internal/deprecated-style.d.ts.map +0 -1
  202. package/dist/internal/deprecated-style.js +0 -34
  203. package/dist/internal/deprecated-style.js.map +0 -1
@@ -9,7 +9,7 @@ import { resolveTabIndicator, resolveTabsAppearance, sampleTabIndicator, } from
9
9
  import { bottomNavigationRecipe, counterBadgeRecipe, loadMoreRecipe, menuRecipe, spinnerRecipe, tabsRecipe, topBarRecipe, } from "@hjmds/design-contracts/recipes";
10
10
  import { Fragment, forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
11
11
  import { AccessibilityInfo, ActivityIndicator, Animated, AppState, Easing, Keyboard, Modal, Platform, Pressable, ScrollView, View, findNodeHandle, } from "react-native";
12
- import { warnDeprecatedStyleProps } from "./internal/deprecated-style.js";
12
+ import { assertNoRemovedStyleProps } from "./internal/diagnostics.js";
13
13
  import { RecipeButton } from "./internal/recipe-button.js";
14
14
  import { isWebRenderer, webOnly, webTabProps } from "./internal/web-a11y.js";
15
15
  import { Button } from "./actions.js";
@@ -35,8 +35,8 @@ export function getDynamicTabPanelId(tabsId) {
35
35
  }
36
36
  /** External panel host for products that keep routing, query, or list state outside Tabs. */
37
37
  export function TabPanel(props) {
38
- const { tabsId, activeValue, label, children, layoutStyle, style } = props;
39
- warnDeprecatedStyleProps("TabPanel", { style }, "layoutStyle for placement");
38
+ const { tabsId, activeValue, label, children, layoutStyle } = props;
39
+ assertNoRemovedStyleProps("TabPanel", props, ["style"]);
40
40
  const dynamic = props.mode === "dynamic";
41
41
  const value = dynamic ? activeValue : props.value;
42
42
  const selected = value === activeValue;
@@ -52,12 +52,12 @@ export function TabPanel(props) {
52
52
  (mountPolicy === "visited" && visited);
53
53
  if (!mounted)
54
54
  return null;
55
- return (_jsx(View, { nativeID: getTabPanelId(tabsId, value, dynamic ? "dynamic" : "keyed"), accessibilityLabel: label, accessibilityLabelledBy: getTabId(tabsId, dynamic ? activeValue : value), accessibilityElementsHidden: !selected, importantForAccessibility: selected ? "auto" : "no-hide-descendants", pointerEvents: selected ? "auto" : "none", role: "tabpanel", style: [style, layoutStyle, selected ? null : { display: "none" }], children: children }));
55
+ return (_jsx(View, { nativeID: getTabPanelId(tabsId, value, dynamic ? "dynamic" : "keyed"), accessibilityLabel: label, accessibilityLabelledBy: getTabId(tabsId, dynamic ? activeValue : value), accessibilityElementsHidden: !selected, importantForAccessibility: selected ? "auto" : "no-hide-descendants", pointerEvents: selected ? "auto" : "none", role: "tabpanel", style: [layoutStyle, selected ? null : { display: "none" }], children: children }));
56
56
  }
57
57
  // UI control labels keep the ui font even when their metric variant is body; content still uses reading.
58
58
  export function Tabs(props) {
59
- const { id, label, items, value: valueProp, defaultValue, onValueChange, activationMode = tabsBehaviorDefaults.activationMode, mountPolicy = tabsBehaviorDefaults.mountPolicy, panelMode = tabsBehaviorDefaults.panelMode, appearance: suppliedAppearance, orientation = tabsBehaviorDefaults.orientation, direction: directionProp, loop = tabsBehaviorDefaults.loop, size = tabsRecipe.defaults.size, layout = tabsRecipe.defaults.layout, overflow = tabsRecipe.defaults.overflow, renderPanels = true, children, layoutStyle, style, tabListStyle, } = props;
60
- warnDeprecatedStyleProps("Tabs", { style, tabListStyle }, "layoutStyle for placement and size/layout/overflow/appearance for the tab list");
59
+ const { id, label, items, value: valueProp, defaultValue, onValueChange, activationMode = tabsBehaviorDefaults.activationMode, mountPolicy = tabsBehaviorDefaults.mountPolicy, panelMode = tabsBehaviorDefaults.panelMode, appearance: suppliedAppearance, orientation = tabsBehaviorDefaults.orientation, direction: directionProp, loop = tabsBehaviorDefaults.loop, size = tabsRecipe.defaults.size, layout = tabsRecipe.defaults.layout, overflow = tabsRecipe.defaults.overflow, renderPanels = true, children, layoutStyle, } = props;
60
+ assertNoRemovedStyleProps("Tabs", props, ["style", "tabListStyle"]);
61
61
  if (!label.trim())
62
62
  throw new TypeError("Tabs label must not be empty");
63
63
  if (panelMode === "dynamic" && mountPolicy !== "active") {
@@ -215,9 +215,8 @@ export function Tabs(props) {
215
215
  flexDirection: orientation === "vertical" ? "row" : "column",
216
216
  gap: spacing.md,
217
217
  },
218
- style,
219
218
  layoutStyle,
220
- ], children: [_jsxs(ScrollView, { ref: tabScroll, onLayout: event => setTabViewport(event.nativeEvent.layout.width), onContentSizeChange: width => setTabContentWidth(width), nativeID: id, accessibilityLabel: label, accessibilityRole: "tablist", horizontal: orientation === "horizontal", scrollEnabled: scrollable && !fitted, showsHorizontalScrollIndicator: false, showsVerticalScrollIndicator: false, style: tabListStyle, contentContainerStyle: [
219
+ ], children: [_jsxs(ScrollView, { ref: tabScroll, onLayout: event => setTabViewport(event.nativeEvent.layout.width), onContentSizeChange: width => setTabContentWidth(width), nativeID: id, accessibilityLabel: label, accessibilityRole: "tablist", horizontal: orientation === "horizontal", scrollEnabled: scrollable && !fitted, showsHorizontalScrollIndicator: false, showsVerticalScrollIndicator: false, contentContainerStyle: [
221
220
  {
222
221
  borderBottomColor: orientation === "horizontal"
223
222
  ? resolveColorReference(tabsRecipe.colors.divider, theme.palette)
@@ -273,6 +272,8 @@ export function Tabs(props) {
273
272
  },
274
273
  onMoveFocus: (intent) => moveFocus(item.id, intent),
275
274
  })), disabled: item.disabled, onAccessibilityAction: (event) => {
275
+ if (item.disabled)
276
+ return;
276
277
  const action = event.nativeEvent.actionName;
277
278
  if (action === "activate")
278
279
  setSelected(item.id);
@@ -281,10 +282,14 @@ export function Tabs(props) {
281
282
  else if (action === "decrement")
282
283
  moveFocus(item.id, "previous");
283
284
  }, onFocus: () => {
285
+ if (item.disabled)
286
+ return;
284
287
  setFocusValue(item.id);
285
288
  if (activationMode === "automatic")
286
289
  setSelected(item.id);
287
290
  }, onPress: () => {
291
+ if (item.disabled)
292
+ return;
288
293
  setFocusValue(item.id);
289
294
  setSelected(item.id);
290
295
  }, style: ({ pressed }) => [
@@ -358,8 +363,8 @@ function useBottomNavigationKeyboardVisible() {
358
363
  return visible;
359
364
  }
360
365
  /** Router-owned persistent destinations; activation emits intent without mutating selection. */
361
- export function BottomNavigation({ descriptor, onActivate, onLongActivate, renderIcon, renderBadge, getItemTestID, primaryAction, configuration = {}, safeAreaBottom = 0, layoutStyle, style, surfaceStyle, listStyle, primaryActionStyle, }) {
362
- warnDeprecatedStyleProps("BottomNavigation", { style, surfaceStyle, listStyle, primaryActionStyle }, "layoutStyle for placement and configuration (presentation/density/distribution) for appearance");
366
+ export function BottomNavigation({ descriptor, onActivate, onLongActivate, renderIcon, renderBadge, getItemTestID, primaryAction, configuration = {}, safeAreaBottom = 0, layoutStyle, ...removedProps }) {
367
+ assertNoRemovedStyleProps("BottomNavigation", removedProps, ["style", "surfaceStyle", "listStyle", "primaryActionStyle"]);
363
368
  const resolved = resolveBottomNavigationDescriptor(descriptor);
364
369
  if (!Number.isFinite(safeAreaBottom) || safeAreaBottom < 0) {
365
370
  throw new RangeError("BottomNavigation safeAreaBottom must be non-negative");
@@ -370,6 +375,10 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
370
375
  direction: configuration.direction ?? theme.environment.direction,
371
376
  }, resolved.items.length);
372
377
  const capsule = presentation.presentation === "capsule";
378
+ const tile = presentation.selection === "tile";
379
+ const tileRecipe = bottomNavigationRecipe.selectionTile;
380
+ // Hidden labels keep the item's accessibilityLabel; only the visible Text row is dropped.
381
+ const labelsVisible = presentation.labels === "visible";
373
382
  const expandedLabels = theme.environment.textScale >= 1.5 || resolved.items.length > 4;
374
383
  // Controlled providers bake scale into Text's style, so the native multiplier
375
384
  // prop cannot enforce the recipe limit (BT-QA-021). Scope labels only; AX names stay.
@@ -385,10 +394,10 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
385
394
  const centerGap = bottomNavigationRecipe.distributions[presentation.distribution].centerGap;
386
395
  const middleIndex = resolved.items.length / 2 - 1;
387
396
  const idleColor = resolveColorReference(bottomNavigationRecipe.colors.idle, theme.palette);
388
- const selectedIconColor = resolveColorReference(bottomNavigationRecipe.colors.selectedIcon, theme.palette);
389
- const selectedLabelColor = resolveColorReference(bottomNavigationRecipe.colors.selectedLabel, theme.palette);
397
+ const selectedIconColor = resolveColorReference(tile ? tileRecipe.selectedContent : bottomNavigationRecipe.colors.selectedIcon, theme.palette);
398
+ const selectedLabelColor = resolveColorReference(tile ? tileRecipe.selectedContent : bottomNavigationRecipe.colors.selectedLabel, theme.palette);
390
399
  const pressedBackground = resolveColorReference(bottomNavigationRecipe.states.pressedBackground, theme.palette);
391
- const surfaceBackground = resolveColorReference(presentationRecipe.background, theme.palette);
400
+ const surfaceBackground = resolveColorReference(tile ? tileRecipe.background : presentationRecipe.background, theme.palette);
392
401
  const surfaceBorder = resolveColorReference(presentationRecipe.border, theme.palette);
393
402
  const badgeMetrics = counterBadgeRecipe.sizes[bottomNavigationRecipe.badge.size];
394
403
  const badgeTone = counterBadgeRecipe.tones.danger;
@@ -405,7 +414,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
405
414
  paddingTop: presentationRecipe.outerPaddingTop,
406
415
  width: "100%",
407
416
  },
408
- style,
409
417
  layoutStyle,
410
418
  ], children: _jsxs(View, { style: [
411
419
  {
@@ -433,7 +441,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
433
441
  shadowRadius: surfaceShadow?.radius,
434
442
  width: "100%",
435
443
  },
436
- surfaceStyle,
437
444
  ], children: [_jsx(View, { accessibilityLabel: resolved.accessibilityLabel, accessibilityRole: "tablist", style: [
438
445
  {
439
446
  alignItems: capsule ? "stretch" : "flex-start",
@@ -442,13 +449,13 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
442
449
  width: capsule ? undefined : "100%",
443
450
  flex: capsule ? 1 : undefined,
444
451
  minWidth: 0,
445
- padding: capsule ? spacing.xxs : 0,
452
+ padding: tile ? tileRecipe.padding : capsule ? spacing.xxs : 0,
453
+ gap: tile ? tileRecipe.gap : 0,
446
454
  backgroundColor: capsule ? surfaceBackground : undefined,
447
455
  borderRadius: capsule ? radius.full : undefined,
448
456
  borderWidth: capsule ? presentationRecipe.borderWidth : 0,
449
457
  borderColor: surfaceBorder,
450
458
  },
451
- listStyle,
452
459
  ], children: resolved.items.map((item, index) => {
453
460
  const selected = item.id === resolved.selectedKey;
454
461
  const focused = focusedKey === item.id;
@@ -474,12 +481,14 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
474
481
  minimumTargetStyle,
475
482
  {
476
483
  alignItems: "center",
477
- backgroundColor: pressed ? pressedBackground : capsule && selected ? theme.colors.surfaceAccent : "transparent",
478
- borderRadius: capsule ? radius.full : theme.tokens.radius.lg,
484
+ backgroundColor: tile && selected
485
+ ? resolveColorReference(tileRecipe.selectedBackground, theme.palette)
486
+ : pressed ? pressedBackground : capsule && selected ? theme.colors.surfaceAccent : "transparent",
487
+ borderRadius: tile ? theme.tokens.radius[tileRecipe.radius] : capsule ? radius.full : theme.tokens.radius.lg,
479
488
  // Floating edge items touch the frame. Their pressed fill must follow its
480
489
  // inner curve; the smaller generic radius painted over the border in Spint.
481
490
  // Match corners instead of clipping the whole surface (which clips shadows/focus).
482
- ...(presentation.presentation === "floating" ? {
491
+ ...(presentation.presentation === "floating" && !tile ? {
483
492
  ...(index === 0 ? {
484
493
  borderTopStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
485
494
  borderBottomStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
@@ -493,7 +502,8 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
493
502
  flex: capsule && selected && !expandedLabels ? 2 : 1,
494
503
  flexShrink: 1,
495
504
  gap: density.gap,
496
- justifyContent: capsule ? "center" : "flex-start",
505
+ // Icon-only items center the indicator instead of leaving an empty label row.
506
+ justifyContent: capsule || !labelsVisible ? "center" : "flex-start",
497
507
  marginEnd: index === middleIndex ? centerGap : 0,
498
508
  minHeight: density.itemMinHeight,
499
509
  minWidth: capsule ? 44 : density.itemMinWidth,
@@ -559,7 +569,7 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
559
569
  }, children: _jsx(Text, { accessible: false, align: "center", style: {
560
570
  color: resolveColorReference(badgeTone.content, theme.palette),
561
571
  fontWeight: counterBadgeRecipe.fontWeight,
562
- }, variant: badgeMetrics.textVariant, children: item.badge.visibleLabel }) })) })) : null] }), (!capsule || expandedLabels || selected) && _jsx(HjmNativeProvider, { textScale: labelTextScale, children: _jsx(Text, { align: "center", allowFontScaling: bottomNavigationRecipe.largeText.allowFontScaling, maxFontSizeMultiplier: bottomNavigationRecipe.largeText.maxFontSizeMultiplier, style: {
572
+ }, variant: badgeMetrics.textVariant, children: item.badge.visibleLabel }) })) })) : null] }), labelsVisible && (!capsule || expandedLabels || selected) && _jsx(HjmNativeProvider, { textScale: labelTextScale, children: _jsx(Text, { align: "center", allowFontScaling: bottomNavigationRecipe.largeText.allowFontScaling, maxFontSizeMultiplier: bottomNavigationRecipe.largeText.maxFontSizeMultiplier, style: {
563
573
  color: selected ? selectedLabelColor : idleColor,
564
574
  flexShrink: 1,
565
575
  fontWeight: selected
@@ -579,13 +589,12 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
579
589
  },
580
590
  // Keep creation outside route destinations; absolute center placement overlaps capsule labels.
581
591
  capsule ? { position: "relative", left: undefined, right: undefined, top: undefined, bottom: undefined, flexShrink: 0 } : undefined,
582
- primaryActionStyle,
583
592
  ], children: primaryAction })) : null] }) }));
584
593
  }
585
594
  /** Recipe-owned icon-over-micro-label action for Native screen chrome. */
586
595
  export function TopBarAction(props) {
587
- const { label, accessibilityLabel, accessibilityState, children, disabled = false, labelVisibility = "visible", labelStyle, layoutStyle, style, ...intentAndHostProps } = props;
588
- warnDeprecatedStyleProps("TopBarAction", { style, labelStyle }, "layoutStyle for placement and labelVisibility/topBarRecipe for appearance");
596
+ const { label, accessibilityLabel, accessibilityState, children, disabled = false, labelVisibility = "visible", layoutStyle, ...intentAndHostProps } = props;
597
+ assertNoRemovedStyleProps("TopBarAction", props, ["style", "labelStyle"]);
589
598
  if (!label.trim())
590
599
  throw new TypeError("TopBarAction label must not be empty");
591
600
  const resolvedAccessibilityLabel = accessibilityLabel ?? label;
@@ -627,7 +636,6 @@ export function TopBarAction(props) {
627
636
  color: resolveColorReference(topBarRecipe.actionLabel.color, theme.palette),
628
637
  fontWeight: topBarRecipe.actionLabel.fontWeight,
629
638
  },
630
- labelStyle,
631
639
  ], variant: topBarRecipe.actionLabel.textVariant, children: label })) : null] })),
632
640
  disabled,
633
641
  onPress: handlePress,
@@ -647,7 +655,6 @@ export function TopBarAction(props) {
647
655
  : 1,
648
656
  paddingHorizontal: topBarRecipe.action.paddingHorizontal,
649
657
  },
650
- style,
651
658
  layoutStyle,
652
659
  ],
653
660
  };
@@ -661,8 +668,8 @@ export function TopBarAction(props) {
661
668
  return props.renderAction?.(controlProps) ?? _jsx(Pressable, { ...controlProps });
662
669
  }
663
670
  /** Native screen top bar with logical action slots and large-text reflow. */
664
- export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLabel, titleAccessibilityHint, leading, trailing, actions, centered = topBarRecipe.defaults.centered, safeAreaTop = 0, layoutStyle, style, leadingStyle, titleStyle, trailingStyle, }) {
665
- warnDeprecatedStyleProps("TopBar", { style, leadingStyle, titleStyle, trailingStyle }, "layoutStyle for placement and centered/topBarRecipe for appearance");
671
+ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLabel, titleAccessibilityHint, leading, trailing, actions, centered = topBarRecipe.defaults.centered, safeAreaTop = 0, layoutStyle, ...removedProps }) {
672
+ assertNoRemovedStyleProps("TopBar", removedProps, ["style", "leadingStyle", "titleStyle", "trailingStyle"]);
666
673
  if (!Number.isFinite(safeAreaTop) || safeAreaTop < 0) {
667
674
  throw new RangeError("TopBar safeAreaTop must be non-negative");
668
675
  }
@@ -700,7 +707,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
700
707
  color: resolveColorReference(topBarRecipe.title.color, theme.palette),
701
708
  fontWeight: topBarRecipe.title.fontWeight,
702
709
  },
703
- titleStyle,
704
710
  ];
705
711
  const renderTitle = (containerStyle, numberOfLines) => {
706
712
  // Match Web: a clickable title is a UI button; a static heading keeps display.
@@ -741,7 +747,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
741
747
  paddingHorizontal: topBarRecipe.paddingHorizontal,
742
748
  paddingTop: safeAreaTop,
743
749
  },
744
- style,
745
750
  layoutStyle,
746
751
  ], children: largeText ? (
747
752
  // These branches have different host hierarchies. Reusing the full-width
@@ -760,7 +765,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
760
765
  flexDirection: "row",
761
766
  minWidth: topBarRecipe.sideMinWidth,
762
767
  },
763
- leadingStyle,
764
768
  ], children: leading })) : null, hasTitle ? (renderTitle({ flex: 1, flexShrink: 1, minWidth: 0 })) : null, hasTitle && centered && hasLeading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", pointerEvents: "none", style: { minWidth: topBarRecipe.sideMinWidth } })) : null] })) : null, hasTrailingContent ? (_jsx(View, { style: [
765
769
  {
766
770
  direction: theme.environment.direction,
@@ -771,7 +775,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
771
775
  minWidth: topBarRecipe.sideMinWidth,
772
776
  width: "100%",
773
777
  },
774
- trailingStyle,
775
778
  ], children: trailingContent })) : null] }, "large-text")) : (_jsxs(Fragment, { children: [renderCompactLeadingSlot ? (_jsx(View, { style: [
776
779
  {
777
780
  alignItems: "center",
@@ -781,7 +784,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
781
784
  justifyContent: "flex-start",
782
785
  minWidth: topBarRecipe.sideMinWidth,
783
786
  },
784
- leadingStyle,
785
787
  ], children: hasLeading ? leading : null })) : null, hasTitle ? (
786
788
  // Keep long route titles readable; the screen-chrome contract promises wrapping, while a one-line cap silently hid the rest.
787
789
  renderTitle({ flex: 2, flexShrink: 1, minWidth: 0 })) : null, renderCompactTrailingSlot ? (_jsx(View, { style: [
@@ -796,15 +798,20 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
796
798
  marginStart: hasTitle ? undefined : "auto",
797
799
  minWidth: topBarRecipe.sideMinWidth,
798
800
  },
799
- trailingStyle,
800
801
  ], children: hasTrailingContent ? trailingContent : null })) : null] }, "compact")) }));
801
802
  }
802
803
  function useMenuAfterDismiss(visible) {
804
+ const visibleRef = useRef(visible);
805
+ visibleRef.current = visible;
806
+ const mountedRef = useRef(true);
807
+ const generationRef = useRef(0);
803
808
  const shownRef = useRef(false);
804
809
  const previousVisibleRef = useRef(visible);
805
810
  const pendingRef = useRef(null);
806
811
  const taskRef = useRef(null);
807
812
  const complete = useCallback(() => {
813
+ if (!mountedRef.current || visibleRef.current)
814
+ return;
808
815
  taskRef.current?.cancel();
809
816
  taskRef.current = null;
810
817
  shownRef.current = false;
@@ -816,12 +823,32 @@ function useMenuAfterDismiss(visible) {
816
823
  useLayoutEffect(() => {
817
824
  const wasVisible = previousVisibleRef.current;
818
825
  previousVisibleRef.current = visible;
826
+ if (!wasVisible && visible) {
827
+ // Reopening invalidates the previous selection; its late teardown must not run under the new menu.
828
+ generationRef.current += 1;
829
+ taskRef.current?.cancel();
830
+ taskRef.current = null;
831
+ pendingRef.current = null;
832
+ shownRef.current = false;
833
+ }
819
834
  if (!wasVisible || visible || shouldAwaitNativeModalDismiss(shownRef.current))
820
835
  return;
821
836
  taskRef.current?.cancel();
822
- taskRef.current = scheduleAfterNativeModalTeardown(complete);
837
+ const generation = generationRef.current;
838
+ taskRef.current = scheduleAfterNativeModalTeardown(() => {
839
+ if (generation === generationRef.current)
840
+ complete();
841
+ });
823
842
  }, [complete, visible]);
824
- useEffect(() => () => taskRef.current?.cancel(), []);
843
+ useEffect(() => {
844
+ mountedRef.current = true;
845
+ return () => {
846
+ mountedRef.current = false;
847
+ generationRef.current += 1;
848
+ pendingRef.current = null;
849
+ taskRef.current?.cancel();
850
+ };
851
+ }, []);
825
852
  return {
826
853
  queue(callback) {
827
854
  pendingRef.current = callback;
@@ -833,8 +860,8 @@ function useMenuAfterDismiss(visible) {
833
860
  };
834
861
  }
835
862
  /** Sectioned Native action/selection menu with teardown-safe action callbacks. */
836
- export function Menu({ triggerLabel, title = triggerLabel, items, sections, source: sourceProp, selection = { mode: "none" }, onAction, onActionAfterDismiss, onSelectionAfterDismiss, open, defaultOpen = false, onOpenChange, onDismiss, disabled = false, readOnly = false, busy = false, readOnlyLabel, asyncState = { status: "idle" }, onRetry, retryLabel, density = menuRecipe.defaults.density, renderLeading, renderTrailing, dismissLabel, trigger, renderTrigger, layoutStyle, style, ...modalProps }) {
837
- warnDeprecatedStyleProps("Menu", { style }, "layoutStyle for placement and density for appearance");
863
+ export function Menu({ triggerLabel, title = triggerLabel, items, sections, source: sourceProp, selection = { mode: "none" }, onAction, onActionAfterDismiss, onSelectionAfterDismiss, open, defaultOpen = false, onOpenChange, onDismiss, disabled = false, readOnly = false, busy = false, readOnlyLabel, asyncState = { status: "idle" }, onRetry, retryLabel, density = menuRecipe.defaults.density, renderLeading, renderTrailing, dismissLabel, trigger, renderTrigger, layoutStyle, ...modalProps }) {
864
+ assertNoRemovedStyleProps("Menu", modalProps, ["style"]);
838
865
  const providedSources = [sourceProp, items, sections].filter((candidate) => candidate !== undefined).length;
839
866
  if (providedSources !== 1) {
840
867
  throw new TypeError("Menu requires exactly one of source, items, or sections");
@@ -997,7 +1024,7 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
997
1024
  requestOpen(!visible, "trigger");
998
1025
  },
999
1026
  };
1000
- return (_jsxs(View, { style: [style, layoutStyle], children: [_jsx(View, { ref: triggerRef, children: renderTrigger ? renderTrigger(triggerProps) : (_jsx(Pressable, { accessibilityHint: readOnly ? readOnlyLabel : undefined, accessibilityLabel: triggerLabel, accessibilityRole: "button", accessibilityState: triggerProps.accessibilityState, disabled: disabled || readOnly || busy, onPress: triggerProps.onPress, style: ({ pressed }) => [
1027
+ return (_jsxs(View, { style: [layoutStyle], children: [_jsx(View, { ref: triggerRef, children: renderTrigger ? renderTrigger(triggerProps) : (_jsx(Pressable, { accessibilityHint: readOnly ? readOnlyLabel : undefined, accessibilityLabel: triggerLabel, accessibilityRole: "button", accessibilityState: triggerProps.accessibilityState, disabled: disabled || readOnly || busy, onPress: triggerProps.onPress, style: ({ pressed }) => [
1001
1028
  minimumTargetStyle,
1002
1029
  {
1003
1030
  alignItems: "center",
@@ -1050,6 +1077,8 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1050
1077
  stateRef.current = descriptor.state;
1051
1078
  handlerRef.current = onLoadMore;
1052
1079
  const [controllerFacade] = useState(createNativeLoadMoreControllerFacade);
1080
+ const requestGeneration = useRef(0);
1081
+ const requestActive = useRef(false);
1053
1082
  const lastViewportRequest = useRef(null);
1054
1083
  useLayoutEffect(() => {
1055
1084
  const controller = createLoadMoreController({
@@ -1057,13 +1086,21 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1057
1086
  onLoadMore: (request) => handlerRef.current(request),
1058
1087
  });
1059
1088
  const detach = controllerFacade.attach(controller);
1089
+ requestGeneration.current += 1;
1090
+ requestActive.current = true;
1060
1091
  lastViewportRequest.current = null;
1061
1092
  return () => {
1093
+ requestGeneration.current += 1;
1094
+ requestActive.current = false;
1062
1095
  detach();
1063
1096
  controller.dispose();
1064
1097
  };
1065
1098
  }, [controllerFacade, mode]);
1066
1099
  const request = useCallback(async (reason) => {
1100
+ // FlatList may have queued onEndReached before the footer was removed.
1101
+ if (!requestActive.current)
1102
+ return "blocked-by-state";
1103
+ const generation = requestGeneration.current;
1067
1104
  const state = stateRef.current;
1068
1105
  if (reason === "viewport" && mode === "automatic" && state.status === "ready") {
1069
1106
  const previous = lastViewportRequest.current;
@@ -1073,18 +1110,22 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1073
1110
  onRequestOutcome?.(outcome, reason);
1074
1111
  return outcome;
1075
1112
  }
1076
- lastViewportRequest.current = {
1077
- mode,
1078
- requestKey: state.requestKey,
1079
- };
1080
1113
  }
1081
1114
  try {
1082
1115
  const outcome = await controllerFacade.request(state, reason);
1116
+ if (generation !== requestGeneration.current)
1117
+ return outcome;
1118
+ // Only a successfully completed viewport request retires its cursor.
1119
+ // A blocked next page or a rejection must remain eligible for retry.
1120
+ if (outcome === "started" && reason === "viewport" && state.status === "ready") {
1121
+ lastViewportRequest.current = { mode, requestKey: state.requestKey };
1122
+ }
1083
1123
  onRequestOutcome?.(outcome, reason);
1084
1124
  return outcome;
1085
1125
  }
1086
1126
  catch (error) {
1087
- onRequestError?.(error, reason);
1127
+ if (generation === requestGeneration.current)
1128
+ onRequestError?.(error, reason);
1088
1129
  // The request was accepted and started; product error state arrives
1089
1130
  // through the controlled descriptor and optional callback. Keep the
1090
1131
  // FlatList imperative bridge rejection-free like the Web observer.