@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.
- package/README.md +8 -8
- package/dist/actions.d.ts +3 -23
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +7 -10
- package/dist/actions.js.map +1 -1
- package/dist/agreement.d.ts +1 -7
- package/dist/agreement.d.ts.map +1 -1
- package/dist/agreement.js +21 -15
- package/dist/agreement.js.map +1 -1
- package/dist/asset.d.ts +2 -13
- package/dist/asset.d.ts.map +1 -1
- package/dist/asset.js +4 -7
- package/dist/asset.js.map +1 -1
- package/dist/bottom-info.d.ts +1 -7
- package/dist/bottom-info.d.ts.map +1 -1
- package/dist/bottom-info.js +2 -4
- package/dist/bottom-info.js.map +1 -1
- package/dist/carousel-motion.d.ts +1 -0
- package/dist/carousel-motion.d.ts.map +1 -1
- package/dist/carousel-motion.js +5 -3
- package/dist/carousel-motion.js.map +1 -1
- package/dist/carousel.d.ts +1 -7
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +14 -6
- package/dist/carousel.js.map +1 -1
- package/dist/collapsible.d.ts +1 -7
- package/dist/collapsible.d.ts.map +1 -1
- package/dist/collapsible.js +4 -4
- package/dist/collapsible.js.map +1 -1
- package/dist/data-display.d.ts +19 -153
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +73 -90
- package/dist/data-display.js.map +1 -1
- package/dist/date-entry.d.ts +1 -0
- package/dist/date-entry.d.ts.map +1 -1
- package/dist/date-entry.js +22 -20
- package/dist/date-entry.js.map +1 -1
- package/dist/date-picker.d.ts +1 -7
- package/dist/date-picker.d.ts.map +1 -1
- package/dist/date-picker.js +19 -11
- package/dist/date-picker.js.map +1 -1
- package/dist/duration-field.d.ts +1 -1
- package/dist/duration-field.d.ts.map +1 -1
- package/dist/duration-field.js +11 -9
- package/dist/duration-field.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +1 -1
- package/dist/feedback.d.ts +8 -88
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +72 -62
- package/dist/feedback.js.map +1 -1
- package/dist/field-group.d.ts +3 -2
- package/dist/field-group.d.ts.map +1 -1
- package/dist/field-group.js +7 -4
- package/dist/field-group.js.map +1 -1
- package/dist/file-picker.d.ts +1 -7
- package/dist/file-picker.d.ts.map +1 -1
- package/dist/file-picker.js +21 -11
- package/dist/file-picker.js.map +1 -1
- package/dist/forms.d.ts +6 -21
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +29 -35
- package/dist/forms.js.map +1 -1
- package/dist/gravity-letters.d.ts.map +1 -1
- package/dist/gravity-letters.js +13 -5
- package/dist/gravity-letters.js.map +1 -1
- package/dist/grid-reveal.d.ts.map +1 -1
- package/dist/grid-reveal.js +10 -2
- package/dist/grid-reveal.js.map +1 -1
- package/dist/heading.d.ts +1 -7
- package/dist/heading.d.ts.map +1 -1
- package/dist/heading.js +1 -4
- package/dist/heading.js.map +1 -1
- package/dist/image-viewer.d.ts.map +1 -1
- package/dist/image-viewer.js +2 -1
- package/dist/image-viewer.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/inline-confirm.d.ts.map +1 -1
- package/dist/inline-confirm.js +5 -1
- package/dist/inline-confirm.js.map +1 -1
- package/dist/inputs.d.ts +4 -71
- package/dist/inputs.d.ts.map +1 -1
- package/dist/inputs.js +25 -50
- package/dist/inputs.js.map +1 -1
- package/dist/internal/accessible-focus.d.ts +3 -0
- package/dist/internal/accessible-focus.d.ts.map +1 -0
- package/dist/internal/accessible-focus.js +15 -0
- package/dist/internal/accessible-focus.js.map +1 -0
- package/dist/internal/diagnostics.d.ts +7 -0
- package/dist/internal/diagnostics.d.ts.map +1 -0
- package/dist/internal/diagnostics.js +20 -0
- package/dist/internal/diagnostics.js.map +1 -0
- package/dist/internal/document-resource.d.ts.map +1 -1
- package/dist/internal/document-resource.js +2 -2
- package/dist/internal/document-resource.js.map +1 -1
- package/dist/internal/spinner.d.ts +1 -7
- package/dist/internal/spinner.d.ts.map +1 -1
- package/dist/internal/spinner.js +2 -4
- package/dist/internal/spinner.js.map +1 -1
- package/dist/masonry.d.ts.map +1 -1
- package/dist/masonry.js +2 -1
- package/dist/masonry.js.map +1 -1
- package/dist/mentions.d.ts +1 -7
- package/dist/mentions.d.ts.map +1 -1
- package/dist/mentions.js +11 -7
- package/dist/mentions.js.map +1 -1
- package/dist/navigation.d.ts +4 -74
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +81 -43
- package/dist/navigation.js.map +1 -1
- package/dist/number-field.d.ts +1 -21
- package/dist/number-field.d.ts.map +1 -1
- package/dist/number-field.js +6 -5
- package/dist/number-field.js.map +1 -1
- package/dist/overlays.d.ts +7 -16
- package/dist/overlays.d.ts.map +1 -1
- package/dist/overlays.js +220 -213
- package/dist/overlays.js.map +1 -1
- package/dist/primitives.d.ts +3 -18
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +8 -10
- package/dist/primitives.js.map +1 -1
- package/dist/provider-button.d.ts +1 -7
- package/dist/provider-button.d.ts.map +1 -1
- package/dist/provider-button.js +1 -4
- package/dist/provider-button.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +9 -1
- package/dist/provider.js.map +1 -1
- package/dist/saved-items.d.ts.map +1 -1
- package/dist/saved-items.js +6 -1
- package/dist/saved-items.js.map +1 -1
- package/dist/screen-flows.d.ts +1 -2
- package/dist/screen-flows.d.ts.map +1 -1
- package/dist/screen-flows.js +60 -16
- package/dist/screen-flows.js.map +1 -1
- package/dist/screens.d.ts.map +1 -1
- package/dist/screens.js +21 -10
- package/dist/screens.js.map +1 -1
- package/dist/sheet-gesture.d.ts.map +1 -1
- package/dist/sheet-gesture.js +6 -3
- package/dist/sheet-gesture.js.map +1 -1
- package/dist/slider.d.ts +1 -21
- package/dist/slider.d.ts.map +1 -1
- package/dist/slider.js +3 -5
- package/dist/slider.js.map +1 -1
- package/dist/sortable.d.ts.map +1 -1
- package/dist/sortable.js +2 -1
- package/dist/sortable.js.map +1 -1
- package/dist/status-text.d.ts +17 -0
- package/dist/status-text.d.ts.map +1 -0
- package/dist/status-text.js +18 -0
- package/dist/status-text.js.map +1 -0
- package/dist/steps.d.ts +1 -7
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +2 -4
- package/dist/steps.js.map +1 -1
- package/dist/swipe-actions.d.ts.map +1 -1
- package/dist/swipe-actions.js +13 -2
- package/dist/swipe-actions.js.map +1 -1
- package/dist/tags-input.d.ts +1 -7
- package/dist/tags-input.d.ts.map +1 -1
- package/dist/tags-input.js +2 -4
- package/dist/tags-input.js.map +1 -1
- package/dist/toggle-group.d.ts +1 -7
- package/dist/toggle-group.d.ts.map +1 -1
- package/dist/toggle-group.js +10 -5
- package/dist/toggle-group.js.map +1 -1
- package/dist/top.d.ts +1 -7
- package/dist/top.d.ts.map +1 -1
- package/dist/top.js +1 -4
- package/dist/top.js.map +1 -1
- package/dist/transfer-list.d.ts +2 -8
- package/dist/transfer-list.d.ts.map +1 -1
- package/dist/transfer-list.js +30 -11
- package/dist/transfer-list.js.map +1 -1
- package/dist/upload-item.d.ts +1 -7
- package/dist/upload-item.d.ts.map +1 -1
- package/dist/upload-item.js +2 -4
- package/dist/upload-item.js.map +1 -1
- package/dist/virtual-list.d.ts.map +1 -1
- package/dist/virtual-list.js +2 -1
- package/dist/virtual-list.js.map +1 -1
- package/docs/liquid-toast.md +9 -0
- package/package.json +9 -3
- package/dist/internal/deprecated-style.d.ts +0 -8
- package/dist/internal/deprecated-style.d.ts.map +0 -1
- package/dist/internal/deprecated-style.js +0 -34
- package/dist/internal/deprecated-style.js.map +0 -1
package/dist/navigation.js
CHANGED
|
@@ -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 {
|
|
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
|
|
39
|
-
|
|
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: [
|
|
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,
|
|
60
|
-
|
|
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,
|
|
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,
|
|
362
|
-
|
|
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:
|
|
480
|
-
|
|
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",
|
|
591
|
-
|
|
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,
|
|
668
|
-
|
|
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
|
-
|
|
837
|
+
const generation = generationRef.current;
|
|
838
|
+
taskRef.current = scheduleAfterNativeModalTeardown(() => {
|
|
839
|
+
if (generation === generationRef.current)
|
|
840
|
+
complete();
|
|
841
|
+
});
|
|
826
842
|
}, [complete, visible]);
|
|
827
|
-
useEffect(() =>
|
|
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,
|
|
840
|
-
|
|
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: [
|
|
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
|
-
|
|
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.
|