@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
@@ -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
+ const raised = tile && presentation.surface === "raised";
381
+ const raisedRecipe = bottomNavigationRecipe.raisedSurface;
373
382
  // Hidden labels keep the item's accessibilityLabel; only the visible Text row is dropped.
374
383
  const labelsVisible = presentation.labels === "visible";
375
384
  const expandedLabels = theme.environment.textScale >= 1.5 || resolved.items.length > 4;
@@ -381,17 +390,21 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
381
390
  const presentationRecipe = bottomNavigationRecipe.presentations[presentation.presentation];
382
391
  // Floating/capsule presentations own elevation; a flat bar must never gain
383
392
  // one just because a profile has a floating token. Keep router state outside.
384
- const surfaceShadow = presentationRecipe.shadow
393
+ // The raised plank's lower edge is its elevation; a floating shadow under it reads as two objects.
394
+ const surfaceShadow = presentationRecipe.shadow && !raised
385
395
  ? theme.designProfile?.tokens.shadow.floating ?? presentationRecipe.shadow
386
396
  : null;
387
397
  const centerGap = bottomNavigationRecipe.distributions[presentation.distribution].centerGap;
388
398
  const middleIndex = resolved.items.length / 2 - 1;
389
399
  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);
400
+ const selectedIconColor = resolveColorReference(tile ? tileRecipe.selectedContent : bottomNavigationRecipe.colors.selectedIcon, theme.palette);
401
+ const selectedLabelColor = resolveColorReference(tile ? tileRecipe.selectedContent : bottomNavigationRecipe.colors.selectedLabel, theme.palette);
392
402
  const pressedBackground = resolveColorReference(bottomNavigationRecipe.states.pressedBackground, theme.palette);
393
- const surfaceBackground = resolveColorReference(presentationRecipe.background, theme.palette);
394
- const surfaceBorder = resolveColorReference(presentationRecipe.border, theme.palette);
403
+ const surfaceBackground = resolveColorReference(raised ? raisedRecipe.background : tile ? tileRecipe.background : presentationRecipe.background, theme.palette);
404
+ const surfaceBorder = resolveColorReference(raised ? raisedRecipe.border : presentationRecipe.border, theme.palette);
405
+ const selectedDepthColor = raised
406
+ ? resolveColorReference(raisedRecipe.selectedDepthColor, theme.palette)
407
+ : undefined;
395
408
  const badgeMetrics = counterBadgeRecipe.sizes[bottomNavigationRecipe.badge.size];
396
409
  const badgeTone = counterBadgeRecipe.tones.danger;
397
410
  const badgeVariant = counterBadgeRecipe.variants[bottomNavigationRecipe.badge.variant];
@@ -407,7 +420,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
407
420
  paddingTop: presentationRecipe.outerPaddingTop,
408
421
  width: "100%",
409
422
  },
410
- style,
411
423
  layoutStyle,
412
424
  ], children: _jsxs(View, { style: [
413
425
  {
@@ -424,6 +436,8 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
424
436
  // Capsule's own list paints its border; bar/floating keep the recipe top edge here.
425
437
  borderTopWidth: capsule ? 0 : presentationRecipe.borderWidth,
426
438
  borderWidth: presentation.presentation === "floating" ? presentationRecipe.borderWidth : 0,
439
+ // Per-edge width after borderWidth: only the lower edge thickens into the plank side.
440
+ ...(raised ? { borderBottomWidth: raisedRecipe.depth } : {}),
427
441
  ...(surfaceShadow ? resolveNativeShadowElevation(surfaceShadow, theme.designProfile !== undefined, 8) : { elevation: 0 }),
428
442
  maxWidth: presentationRecipe.maxWidth ?? undefined,
429
443
  position: "relative",
@@ -435,7 +449,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
435
449
  shadowRadius: surfaceShadow?.radius,
436
450
  width: "100%",
437
451
  },
438
- surfaceStyle,
439
452
  ], children: [_jsx(View, { accessibilityLabel: resolved.accessibilityLabel, accessibilityRole: "tablist", style: [
440
453
  {
441
454
  alignItems: capsule ? "stretch" : "flex-start",
@@ -444,13 +457,13 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
444
457
  width: capsule ? undefined : "100%",
445
458
  flex: capsule ? 1 : undefined,
446
459
  minWidth: 0,
447
- padding: capsule ? spacing.xxs : 0,
460
+ padding: tile ? tileRecipe.padding : capsule ? spacing.xxs : 0,
461
+ gap: tile ? tileRecipe.gap : 0,
448
462
  backgroundColor: capsule ? surfaceBackground : undefined,
449
463
  borderRadius: capsule ? radius.full : undefined,
450
464
  borderWidth: capsule ? presentationRecipe.borderWidth : 0,
451
465
  borderColor: surfaceBorder,
452
466
  },
453
- listStyle,
454
467
  ], children: resolved.items.map((item, index) => {
455
468
  const selected = item.id === resolved.selectedKey;
456
469
  const focused = focusedKey === item.id;
@@ -476,12 +489,20 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
476
489
  minimumTargetStyle,
477
490
  {
478
491
  alignItems: "center",
479
- backgroundColor: pressed ? pressedBackground : capsule && selected ? theme.colors.surfaceAccent : "transparent",
480
- borderRadius: capsule ? radius.full : theme.tokens.radius.lg,
492
+ backgroundColor: tile && selected
493
+ ? resolveColorReference(tileRecipe.selectedBackground, theme.palette)
494
+ : pressed ? pressedBackground : capsule && selected ? theme.colors.surfaceAccent : "transparent",
495
+ borderRadius: tile ? theme.tokens.radius[tileRecipe.radius] : capsule ? radius.full : theme.tokens.radius.lg,
496
+ // Every raised tile reserves the edge so selection never shifts icon/label rows.
497
+ // The translucent edge composites over the brand fill, darkening it.
498
+ ...(raised ? {
499
+ borderBottomWidth: raisedRecipe.selectedDepth,
500
+ borderBottomColor: selected ? selectedDepthColor : "transparent",
501
+ } : {}),
481
502
  // Floating edge items touch the frame. Their pressed fill must follow its
482
503
  // inner curve; the smaller generic radius painted over the border in Spint.
483
504
  // Match corners instead of clipping the whole surface (which clips shadows/focus).
484
- ...(presentation.presentation === "floating" ? {
505
+ ...(presentation.presentation === "floating" && !tile ? {
485
506
  ...(index === 0 ? {
486
507
  borderTopStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
487
508
  borderBottomStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
@@ -582,13 +603,12 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
582
603
  },
583
604
  // Keep creation outside route destinations; absolute center placement overlaps capsule labels.
584
605
  capsule ? { position: "relative", left: undefined, right: undefined, top: undefined, bottom: undefined, flexShrink: 0 } : undefined,
585
- primaryActionStyle,
586
606
  ], children: primaryAction })) : null] }) }));
587
607
  }
588
608
  /** Recipe-owned icon-over-micro-label action for Native screen chrome. */
589
609
  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");
610
+ const { label, accessibilityLabel, accessibilityState, children, disabled = false, labelVisibility = "visible", layoutStyle, ...intentAndHostProps } = props;
611
+ assertNoRemovedStyleProps("TopBarAction", props, ["style", "labelStyle"]);
592
612
  if (!label.trim())
593
613
  throw new TypeError("TopBarAction label must not be empty");
594
614
  const resolvedAccessibilityLabel = accessibilityLabel ?? label;
@@ -630,7 +650,6 @@ export function TopBarAction(props) {
630
650
  color: resolveColorReference(topBarRecipe.actionLabel.color, theme.palette),
631
651
  fontWeight: topBarRecipe.actionLabel.fontWeight,
632
652
  },
633
- labelStyle,
634
653
  ], variant: topBarRecipe.actionLabel.textVariant, children: label })) : null] })),
635
654
  disabled,
636
655
  onPress: handlePress,
@@ -650,7 +669,6 @@ export function TopBarAction(props) {
650
669
  : 1,
651
670
  paddingHorizontal: topBarRecipe.action.paddingHorizontal,
652
671
  },
653
- style,
654
672
  layoutStyle,
655
673
  ],
656
674
  };
@@ -664,8 +682,8 @@ export function TopBarAction(props) {
664
682
  return props.renderAction?.(controlProps) ?? _jsx(Pressable, { ...controlProps });
665
683
  }
666
684
  /** 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");
685
+ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLabel, titleAccessibilityHint, leading, trailing, actions, centered = topBarRecipe.defaults.centered, safeAreaTop = 0, layoutStyle, ...removedProps }) {
686
+ assertNoRemovedStyleProps("TopBar", removedProps, ["style", "leadingStyle", "titleStyle", "trailingStyle"]);
669
687
  if (!Number.isFinite(safeAreaTop) || safeAreaTop < 0) {
670
688
  throw new RangeError("TopBar safeAreaTop must be non-negative");
671
689
  }
@@ -703,7 +721,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
703
721
  color: resolveColorReference(topBarRecipe.title.color, theme.palette),
704
722
  fontWeight: topBarRecipe.title.fontWeight,
705
723
  },
706
- titleStyle,
707
724
  ];
708
725
  const renderTitle = (containerStyle, numberOfLines) => {
709
726
  // Match Web: a clickable title is a UI button; a static heading keeps display.
@@ -744,7 +761,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
744
761
  paddingHorizontal: topBarRecipe.paddingHorizontal,
745
762
  paddingTop: safeAreaTop,
746
763
  },
747
- style,
748
764
  layoutStyle,
749
765
  ], children: largeText ? (
750
766
  // These branches have different host hierarchies. Reusing the full-width
@@ -763,7 +779,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
763
779
  flexDirection: "row",
764
780
  minWidth: topBarRecipe.sideMinWidth,
765
781
  },
766
- leadingStyle,
767
782
  ], 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
783
  {
769
784
  direction: theme.environment.direction,
@@ -774,7 +789,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
774
789
  minWidth: topBarRecipe.sideMinWidth,
775
790
  width: "100%",
776
791
  },
777
- trailingStyle,
778
792
  ], children: trailingContent })) : null] }, "large-text")) : (_jsxs(Fragment, { children: [renderCompactLeadingSlot ? (_jsx(View, { style: [
779
793
  {
780
794
  alignItems: "center",
@@ -784,7 +798,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
784
798
  justifyContent: "flex-start",
785
799
  minWidth: topBarRecipe.sideMinWidth,
786
800
  },
787
- leadingStyle,
788
801
  ], children: hasLeading ? leading : null })) : null, hasTitle ? (
789
802
  // Keep long route titles readable; the screen-chrome contract promises wrapping, while a one-line cap silently hid the rest.
790
803
  renderTitle({ flex: 2, flexShrink: 1, minWidth: 0 })) : null, renderCompactTrailingSlot ? (_jsx(View, { style: [
@@ -799,15 +812,20 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
799
812
  marginStart: hasTitle ? undefined : "auto",
800
813
  minWidth: topBarRecipe.sideMinWidth,
801
814
  },
802
- trailingStyle,
803
815
  ], children: hasTrailingContent ? trailingContent : null })) : null] }, "compact")) }));
804
816
  }
805
817
  function useMenuAfterDismiss(visible) {
818
+ const visibleRef = useRef(visible);
819
+ visibleRef.current = visible;
820
+ const mountedRef = useRef(true);
821
+ const generationRef = useRef(0);
806
822
  const shownRef = useRef(false);
807
823
  const previousVisibleRef = useRef(visible);
808
824
  const pendingRef = useRef(null);
809
825
  const taskRef = useRef(null);
810
826
  const complete = useCallback(() => {
827
+ if (!mountedRef.current || visibleRef.current)
828
+ return;
811
829
  taskRef.current?.cancel();
812
830
  taskRef.current = null;
813
831
  shownRef.current = false;
@@ -819,12 +837,32 @@ function useMenuAfterDismiss(visible) {
819
837
  useLayoutEffect(() => {
820
838
  const wasVisible = previousVisibleRef.current;
821
839
  previousVisibleRef.current = visible;
840
+ if (!wasVisible && visible) {
841
+ // Reopening invalidates the previous selection; its late teardown must not run under the new menu.
842
+ generationRef.current += 1;
843
+ taskRef.current?.cancel();
844
+ taskRef.current = null;
845
+ pendingRef.current = null;
846
+ shownRef.current = false;
847
+ }
822
848
  if (!wasVisible || visible || shouldAwaitNativeModalDismiss(shownRef.current))
823
849
  return;
824
850
  taskRef.current?.cancel();
825
- taskRef.current = scheduleAfterNativeModalTeardown(complete);
851
+ const generation = generationRef.current;
852
+ taskRef.current = scheduleAfterNativeModalTeardown(() => {
853
+ if (generation === generationRef.current)
854
+ complete();
855
+ });
826
856
  }, [complete, visible]);
827
- useEffect(() => () => taskRef.current?.cancel(), []);
857
+ useEffect(() => {
858
+ mountedRef.current = true;
859
+ return () => {
860
+ mountedRef.current = false;
861
+ generationRef.current += 1;
862
+ pendingRef.current = null;
863
+ taskRef.current?.cancel();
864
+ };
865
+ }, []);
828
866
  return {
829
867
  queue(callback) {
830
868
  pendingRef.current = callback;
@@ -836,8 +874,8 @@ function useMenuAfterDismiss(visible) {
836
874
  };
837
875
  }
838
876
  /** 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");
877
+ 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 }) {
878
+ assertNoRemovedStyleProps("Menu", modalProps, ["style"]);
841
879
  const providedSources = [sourceProp, items, sections].filter((candidate) => candidate !== undefined).length;
842
880
  if (providedSources !== 1) {
843
881
  throw new TypeError("Menu requires exactly one of source, items, or sections");
@@ -1000,7 +1038,7 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
1000
1038
  requestOpen(!visible, "trigger");
1001
1039
  },
1002
1040
  };
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 }) => [
1041
+ 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
1042
  minimumTargetStyle,
1005
1043
  {
1006
1044
  alignItems: "center",
@@ -1053,6 +1091,8 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1053
1091
  stateRef.current = descriptor.state;
1054
1092
  handlerRef.current = onLoadMore;
1055
1093
  const [controllerFacade] = useState(createNativeLoadMoreControllerFacade);
1094
+ const requestGeneration = useRef(0);
1095
+ const requestActive = useRef(false);
1056
1096
  const lastViewportRequest = useRef(null);
1057
1097
  useLayoutEffect(() => {
1058
1098
  const controller = createLoadMoreController({
@@ -1060,13 +1100,21 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1060
1100
  onLoadMore: (request) => handlerRef.current(request),
1061
1101
  });
1062
1102
  const detach = controllerFacade.attach(controller);
1103
+ requestGeneration.current += 1;
1104
+ requestActive.current = true;
1063
1105
  lastViewportRequest.current = null;
1064
1106
  return () => {
1107
+ requestGeneration.current += 1;
1108
+ requestActive.current = false;
1065
1109
  detach();
1066
1110
  controller.dispose();
1067
1111
  };
1068
1112
  }, [controllerFacade, mode]);
1069
1113
  const request = useCallback(async (reason) => {
1114
+ // FlatList may have queued onEndReached before the footer was removed.
1115
+ if (!requestActive.current)
1116
+ return "blocked-by-state";
1117
+ const generation = requestGeneration.current;
1070
1118
  const state = stateRef.current;
1071
1119
  if (reason === "viewport" && mode === "automatic" && state.status === "ready") {
1072
1120
  const previous = lastViewportRequest.current;
@@ -1076,18 +1124,22 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
1076
1124
  onRequestOutcome?.(outcome, reason);
1077
1125
  return outcome;
1078
1126
  }
1079
- lastViewportRequest.current = {
1080
- mode,
1081
- requestKey: state.requestKey,
1082
- };
1083
1127
  }
1084
1128
  try {
1085
1129
  const outcome = await controllerFacade.request(state, reason);
1130
+ if (generation !== requestGeneration.current)
1131
+ return outcome;
1132
+ // Only a successfully completed viewport request retires its cursor.
1133
+ // A blocked next page or a rejection must remain eligible for retry.
1134
+ if (outcome === "started" && reason === "viewport" && state.status === "ready") {
1135
+ lastViewportRequest.current = { mode, requestKey: state.requestKey };
1136
+ }
1086
1137
  onRequestOutcome?.(outcome, reason);
1087
1138
  return outcome;
1088
1139
  }
1089
1140
  catch (error) {
1090
- onRequestError?.(error, reason);
1141
+ if (generation === requestGeneration.current)
1142
+ onRequestError?.(error, reason);
1091
1143
  // The request was accepted and started; product error state arrives
1092
1144
  // through the controlled descriptor and optional callback. Keep the
1093
1145
  // FlatList imperative bridge rejection-free like the Web observer.