@hjmds/react-native 1.18.0 → 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 (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 +81 -43
  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
@@ -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,8 @@ 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;
373
380
  // Hidden labels keep the item's accessibilityLabel; only the visible Text row is dropped.
374
381
  const labelsVisible = presentation.labels === "visible";
375
382
  const expandedLabels = theme.environment.textScale >= 1.5 || resolved.items.length > 4;
@@ -387,10 +394,10 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
387
394
  const centerGap = bottomNavigationRecipe.distributions[presentation.distribution].centerGap;
388
395
  const middleIndex = resolved.items.length / 2 - 1;
389
396
  const idleColor = resolveColorReference(bottomNavigationRecipe.colors.idle, theme.palette);
390
- const selectedIconColor = resolveColorReference(bottomNavigationRecipe.colors.selectedIcon, theme.palette);
391
- 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);
392
399
  const pressedBackground = resolveColorReference(bottomNavigationRecipe.states.pressedBackground, theme.palette);
393
- const surfaceBackground = resolveColorReference(presentationRecipe.background, theme.palette);
400
+ const surfaceBackground = resolveColorReference(tile ? tileRecipe.background : presentationRecipe.background, theme.palette);
394
401
  const surfaceBorder = resolveColorReference(presentationRecipe.border, theme.palette);
395
402
  const badgeMetrics = counterBadgeRecipe.sizes[bottomNavigationRecipe.badge.size];
396
403
  const badgeTone = counterBadgeRecipe.tones.danger;
@@ -407,7 +414,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
407
414
  paddingTop: presentationRecipe.outerPaddingTop,
408
415
  width: "100%",
409
416
  },
410
- style,
411
417
  layoutStyle,
412
418
  ], children: _jsxs(View, { style: [
413
419
  {
@@ -435,7 +441,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
435
441
  shadowRadius: surfaceShadow?.radius,
436
442
  width: "100%",
437
443
  },
438
- surfaceStyle,
439
444
  ], children: [_jsx(View, { accessibilityLabel: resolved.accessibilityLabel, accessibilityRole: "tablist", style: [
440
445
  {
441
446
  alignItems: capsule ? "stretch" : "flex-start",
@@ -444,13 +449,13 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
444
449
  width: capsule ? undefined : "100%",
445
450
  flex: capsule ? 1 : undefined,
446
451
  minWidth: 0,
447
- padding: capsule ? spacing.xxs : 0,
452
+ padding: tile ? tileRecipe.padding : capsule ? spacing.xxs : 0,
453
+ gap: tile ? tileRecipe.gap : 0,
448
454
  backgroundColor: capsule ? surfaceBackground : undefined,
449
455
  borderRadius: capsule ? radius.full : undefined,
450
456
  borderWidth: capsule ? presentationRecipe.borderWidth : 0,
451
457
  borderColor: surfaceBorder,
452
458
  },
453
- listStyle,
454
459
  ], children: resolved.items.map((item, index) => {
455
460
  const selected = item.id === resolved.selectedKey;
456
461
  const focused = focusedKey === item.id;
@@ -476,12 +481,14 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
476
481
  minimumTargetStyle,
477
482
  {
478
483
  alignItems: "center",
479
- backgroundColor: pressed ? pressedBackground : capsule && selected ? theme.colors.surfaceAccent : "transparent",
480
- 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,
481
488
  // Floating edge items touch the frame. Their pressed fill must follow its
482
489
  // inner curve; the smaller generic radius painted over the border in Spint.
483
490
  // Match corners instead of clipping the whole surface (which clips shadows/focus).
484
- ...(presentation.presentation === "floating" ? {
491
+ ...(presentation.presentation === "floating" && !tile ? {
485
492
  ...(index === 0 ? {
486
493
  borderTopStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
487
494
  borderBottomStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
@@ -582,13 +589,12 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
582
589
  },
583
590
  // Keep creation outside route destinations; absolute center placement overlaps capsule labels.
584
591
  capsule ? { position: "relative", left: undefined, right: undefined, top: undefined, bottom: undefined, flexShrink: 0 } : undefined,
585
- primaryActionStyle,
586
592
  ], children: primaryAction })) : null] }) }));
587
593
  }
588
594
  /** Recipe-owned icon-over-micro-label action for Native screen chrome. */
589
595
  export function TopBarAction(props) {
590
- const { label, accessibilityLabel, accessibilityState, children, disabled = false, labelVisibility = "visible", labelStyle, layoutStyle, style, ...intentAndHostProps } = props;
591
- 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"]);
592
598
  if (!label.trim())
593
599
  throw new TypeError("TopBarAction label must not be empty");
594
600
  const resolvedAccessibilityLabel = accessibilityLabel ?? label;
@@ -630,7 +636,6 @@ export function TopBarAction(props) {
630
636
  color: resolveColorReference(topBarRecipe.actionLabel.color, theme.palette),
631
637
  fontWeight: topBarRecipe.actionLabel.fontWeight,
632
638
  },
633
- labelStyle,
634
639
  ], variant: topBarRecipe.actionLabel.textVariant, children: label })) : null] })),
635
640
  disabled,
636
641
  onPress: handlePress,
@@ -650,7 +655,6 @@ export function TopBarAction(props) {
650
655
  : 1,
651
656
  paddingHorizontal: topBarRecipe.action.paddingHorizontal,
652
657
  },
653
- style,
654
658
  layoutStyle,
655
659
  ],
656
660
  };
@@ -664,8 +668,8 @@ export function TopBarAction(props) {
664
668
  return props.renderAction?.(controlProps) ?? _jsx(Pressable, { ...controlProps });
665
669
  }
666
670
  /** Native screen top bar with logical action slots and large-text reflow. */
667
- export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLabel, titleAccessibilityHint, leading, trailing, actions, centered = topBarRecipe.defaults.centered, safeAreaTop = 0, layoutStyle, style, leadingStyle, titleStyle, trailingStyle, }) {
668
- 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"]);
669
673
  if (!Number.isFinite(safeAreaTop) || safeAreaTop < 0) {
670
674
  throw new RangeError("TopBar safeAreaTop must be non-negative");
671
675
  }
@@ -703,7 +707,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
703
707
  color: resolveColorReference(topBarRecipe.title.color, theme.palette),
704
708
  fontWeight: topBarRecipe.title.fontWeight,
705
709
  },
706
- titleStyle,
707
710
  ];
708
711
  const renderTitle = (containerStyle, numberOfLines) => {
709
712
  // Match Web: a clickable title is a UI button; a static heading keeps display.
@@ -744,7 +747,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
744
747
  paddingHorizontal: topBarRecipe.paddingHorizontal,
745
748
  paddingTop: safeAreaTop,
746
749
  },
747
- style,
748
750
  layoutStyle,
749
751
  ], children: largeText ? (
750
752
  // These branches have different host hierarchies. Reusing the full-width
@@ -763,7 +765,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
763
765
  flexDirection: "row",
764
766
  minWidth: topBarRecipe.sideMinWidth,
765
767
  },
766
- leadingStyle,
767
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: [
768
769
  {
769
770
  direction: theme.environment.direction,
@@ -774,7 +775,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
774
775
  minWidth: topBarRecipe.sideMinWidth,
775
776
  width: "100%",
776
777
  },
777
- trailingStyle,
778
778
  ], children: trailingContent })) : null] }, "large-text")) : (_jsxs(Fragment, { children: [renderCompactLeadingSlot ? (_jsx(View, { style: [
779
779
  {
780
780
  alignItems: "center",
@@ -784,7 +784,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
784
784
  justifyContent: "flex-start",
785
785
  minWidth: topBarRecipe.sideMinWidth,
786
786
  },
787
- leadingStyle,
788
787
  ], children: hasLeading ? leading : null })) : null, hasTitle ? (
789
788
  // Keep long route titles readable; the screen-chrome contract promises wrapping, while a one-line cap silently hid the rest.
790
789
  renderTitle({ flex: 2, flexShrink: 1, minWidth: 0 })) : null, renderCompactTrailingSlot ? (_jsx(View, { style: [
@@ -799,15 +798,20 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
799
798
  marginStart: hasTitle ? undefined : "auto",
800
799
  minWidth: topBarRecipe.sideMinWidth,
801
800
  },
802
- trailingStyle,
803
801
  ], children: hasTrailingContent ? trailingContent : null })) : null] }, "compact")) }));
804
802
  }
805
803
  function useMenuAfterDismiss(visible) {
804
+ const visibleRef = useRef(visible);
805
+ visibleRef.current = visible;
806
+ const mountedRef = useRef(true);
807
+ const generationRef = useRef(0);
806
808
  const shownRef = useRef(false);
807
809
  const previousVisibleRef = useRef(visible);
808
810
  const pendingRef = useRef(null);
809
811
  const taskRef = useRef(null);
810
812
  const complete = useCallback(() => {
813
+ if (!mountedRef.current || visibleRef.current)
814
+ return;
811
815
  taskRef.current?.cancel();
812
816
  taskRef.current = null;
813
817
  shownRef.current = false;
@@ -819,12 +823,32 @@ function useMenuAfterDismiss(visible) {
819
823
  useLayoutEffect(() => {
820
824
  const wasVisible = previousVisibleRef.current;
821
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
+ }
822
834
  if (!wasVisible || visible || shouldAwaitNativeModalDismiss(shownRef.current))
823
835
  return;
824
836
  taskRef.current?.cancel();
825
- taskRef.current = scheduleAfterNativeModalTeardown(complete);
837
+ const generation = generationRef.current;
838
+ taskRef.current = scheduleAfterNativeModalTeardown(() => {
839
+ if (generation === generationRef.current)
840
+ complete();
841
+ });
826
842
  }, [complete, visible]);
827
- 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
+ }, []);
828
852
  return {
829
853
  queue(callback) {
830
854
  pendingRef.current = callback;
@@ -836,8 +860,8 @@ function useMenuAfterDismiss(visible) {
836
860
  };
837
861
  }
838
862
  /** Sectioned Native action/selection menu with teardown-safe action callbacks. */
839
- 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 }) {
840
- 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"]);
841
865
  const providedSources = [sourceProp, items, sections].filter((candidate) => candidate !== undefined).length;
842
866
  if (providedSources !== 1) {
843
867
  throw new TypeError("Menu requires exactly one of source, items, or sections");
@@ -1000,7 +1024,7 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
1000
1024
  requestOpen(!visible, "trigger");
1001
1025
  },
1002
1026
  };
1003
- 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 }) => [
1004
1028
  minimumTargetStyle,
1005
1029
  {
1006
1030
  alignItems: "center",
@@ -1053,6 +1077,8 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1053
1077
  stateRef.current = descriptor.state;
1054
1078
  handlerRef.current = onLoadMore;
1055
1079
  const [controllerFacade] = useState(createNativeLoadMoreControllerFacade);
1080
+ const requestGeneration = useRef(0);
1081
+ const requestActive = useRef(false);
1056
1082
  const lastViewportRequest = useRef(null);
1057
1083
  useLayoutEffect(() => {
1058
1084
  const controller = createLoadMoreController({
@@ -1060,13 +1086,21 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1060
1086
  onLoadMore: (request) => handlerRef.current(request),
1061
1087
  });
1062
1088
  const detach = controllerFacade.attach(controller);
1089
+ requestGeneration.current += 1;
1090
+ requestActive.current = true;
1063
1091
  lastViewportRequest.current = null;
1064
1092
  return () => {
1093
+ requestGeneration.current += 1;
1094
+ requestActive.current = false;
1065
1095
  detach();
1066
1096
  controller.dispose();
1067
1097
  };
1068
1098
  }, [controllerFacade, mode]);
1069
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;
1070
1104
  const state = stateRef.current;
1071
1105
  if (reason === "viewport" && mode === "automatic" && state.status === "ready") {
1072
1106
  const previous = lastViewportRequest.current;
@@ -1076,18 +1110,22 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1076
1110
  onRequestOutcome?.(outcome, reason);
1077
1111
  return outcome;
1078
1112
  }
1079
- lastViewportRequest.current = {
1080
- mode,
1081
- requestKey: state.requestKey,
1082
- };
1083
1113
  }
1084
1114
  try {
1085
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
+ }
1086
1123
  onRequestOutcome?.(outcome, reason);
1087
1124
  return outcome;
1088
1125
  }
1089
1126
  catch (error) {
1090
- onRequestError?.(error, reason);
1127
+ if (generation === requestGeneration.current)
1128
+ onRequestError?.(error, reason);
1091
1129
  // The request was accepted and started; product error state arrives
1092
1130
  // through the controlled descriptor and optional callback. Keep the
1093
1131
  // FlatList imperative bridge rejection-free like the Web observer.