@hjmds/react-native 1.17.2 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/actions.d.ts +3 -23
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +8 -11
- package/dist/actions.js.map +1 -1
- package/dist/activity-chip.d.ts +29 -0
- package/dist/activity-chip.d.ts.map +1 -0
- package/dist/activity-chip.js +54 -0
- package/dist/activity-chip.js.map +1 -0
- 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/content-transition.d.ts +2 -1
- package/dist/content-transition.d.ts.map +1 -1
- package/dist/content-transition.js +3 -3
- package/dist/content-transition.js.map +1 -1
- package/dist/data-display.d.ts +23 -154
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +86 -92
- 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 +9 -88
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +124 -81
- 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 +52 -67
- 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/recipe-button.js +1 -1
- package/dist/internal/recipe-button.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 +86 -45
- 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 +1 -1
- package/dist/screens.d.ts.map +1 -1
- package/dist/screens.js +32 -15
- 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 +20 -0
- package/package.json +17 -4
- 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,10 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
370
375
|
direction: configuration.direction ?? theme.environment.direction,
|
|
371
376
|
}, resolved.items.length);
|
|
372
377
|
const capsule = presentation.presentation === "capsule";
|
|
378
|
+
const tile = presentation.selection === "tile";
|
|
379
|
+
const tileRecipe = bottomNavigationRecipe.selectionTile;
|
|
380
|
+
// Hidden labels keep the item's accessibilityLabel; only the visible Text row is dropped.
|
|
381
|
+
const labelsVisible = presentation.labels === "visible";
|
|
373
382
|
const expandedLabels = theme.environment.textScale >= 1.5 || resolved.items.length > 4;
|
|
374
383
|
// Controlled providers bake scale into Text's style, so the native multiplier
|
|
375
384
|
// prop cannot enforce the recipe limit (BT-QA-021). Scope labels only; AX names stay.
|
|
@@ -385,10 +394,10 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
385
394
|
const centerGap = bottomNavigationRecipe.distributions[presentation.distribution].centerGap;
|
|
386
395
|
const middleIndex = resolved.items.length / 2 - 1;
|
|
387
396
|
const idleColor = resolveColorReference(bottomNavigationRecipe.colors.idle, theme.palette);
|
|
388
|
-
const selectedIconColor = resolveColorReference(bottomNavigationRecipe.colors.selectedIcon, theme.palette);
|
|
389
|
-
const selectedLabelColor = resolveColorReference(bottomNavigationRecipe.colors.selectedLabel, theme.palette);
|
|
397
|
+
const selectedIconColor = resolveColorReference(tile ? tileRecipe.selectedContent : bottomNavigationRecipe.colors.selectedIcon, theme.palette);
|
|
398
|
+
const selectedLabelColor = resolveColorReference(tile ? tileRecipe.selectedContent : bottomNavigationRecipe.colors.selectedLabel, theme.palette);
|
|
390
399
|
const pressedBackground = resolveColorReference(bottomNavigationRecipe.states.pressedBackground, theme.palette);
|
|
391
|
-
const surfaceBackground = resolveColorReference(presentationRecipe.background, theme.palette);
|
|
400
|
+
const surfaceBackground = resolveColorReference(tile ? tileRecipe.background : presentationRecipe.background, theme.palette);
|
|
392
401
|
const surfaceBorder = resolveColorReference(presentationRecipe.border, theme.palette);
|
|
393
402
|
const badgeMetrics = counterBadgeRecipe.sizes[bottomNavigationRecipe.badge.size];
|
|
394
403
|
const badgeTone = counterBadgeRecipe.tones.danger;
|
|
@@ -405,7 +414,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
405
414
|
paddingTop: presentationRecipe.outerPaddingTop,
|
|
406
415
|
width: "100%",
|
|
407
416
|
},
|
|
408
|
-
style,
|
|
409
417
|
layoutStyle,
|
|
410
418
|
], children: _jsxs(View, { style: [
|
|
411
419
|
{
|
|
@@ -433,7 +441,6 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
433
441
|
shadowRadius: surfaceShadow?.radius,
|
|
434
442
|
width: "100%",
|
|
435
443
|
},
|
|
436
|
-
surfaceStyle,
|
|
437
444
|
], children: [_jsx(View, { accessibilityLabel: resolved.accessibilityLabel, accessibilityRole: "tablist", style: [
|
|
438
445
|
{
|
|
439
446
|
alignItems: capsule ? "stretch" : "flex-start",
|
|
@@ -442,13 +449,13 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
442
449
|
width: capsule ? undefined : "100%",
|
|
443
450
|
flex: capsule ? 1 : undefined,
|
|
444
451
|
minWidth: 0,
|
|
445
|
-
padding: capsule ? spacing.xxs : 0,
|
|
452
|
+
padding: tile ? tileRecipe.padding : capsule ? spacing.xxs : 0,
|
|
453
|
+
gap: tile ? tileRecipe.gap : 0,
|
|
446
454
|
backgroundColor: capsule ? surfaceBackground : undefined,
|
|
447
455
|
borderRadius: capsule ? radius.full : undefined,
|
|
448
456
|
borderWidth: capsule ? presentationRecipe.borderWidth : 0,
|
|
449
457
|
borderColor: surfaceBorder,
|
|
450
458
|
},
|
|
451
|
-
listStyle,
|
|
452
459
|
], children: resolved.items.map((item, index) => {
|
|
453
460
|
const selected = item.id === resolved.selectedKey;
|
|
454
461
|
const focused = focusedKey === item.id;
|
|
@@ -474,12 +481,14 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
474
481
|
minimumTargetStyle,
|
|
475
482
|
{
|
|
476
483
|
alignItems: "center",
|
|
477
|
-
backgroundColor:
|
|
478
|
-
|
|
484
|
+
backgroundColor: tile && selected
|
|
485
|
+
? resolveColorReference(tileRecipe.selectedBackground, theme.palette)
|
|
486
|
+
: pressed ? pressedBackground : capsule && selected ? theme.colors.surfaceAccent : "transparent",
|
|
487
|
+
borderRadius: tile ? theme.tokens.radius[tileRecipe.radius] : capsule ? radius.full : theme.tokens.radius.lg,
|
|
479
488
|
// Floating edge items touch the frame. Their pressed fill must follow its
|
|
480
489
|
// inner curve; the smaller generic radius painted over the border in Spint.
|
|
481
490
|
// Match corners instead of clipping the whole surface (which clips shadows/focus).
|
|
482
|
-
...(presentation.presentation === "floating" ? {
|
|
491
|
+
...(presentation.presentation === "floating" && !tile ? {
|
|
483
492
|
...(index === 0 ? {
|
|
484
493
|
borderTopStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
|
|
485
494
|
borderBottomStartRadius: Math.max(0, theme.tokens.radius[presentationRecipe.radius] - presentationRecipe.borderWidth),
|
|
@@ -493,7 +502,8 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
493
502
|
flex: capsule && selected && !expandedLabels ? 2 : 1,
|
|
494
503
|
flexShrink: 1,
|
|
495
504
|
gap: density.gap,
|
|
496
|
-
|
|
505
|
+
// Icon-only items center the indicator instead of leaving an empty label row.
|
|
506
|
+
justifyContent: capsule || !labelsVisible ? "center" : "flex-start",
|
|
497
507
|
marginEnd: index === middleIndex ? centerGap : 0,
|
|
498
508
|
minHeight: density.itemMinHeight,
|
|
499
509
|
minWidth: capsule ? 44 : density.itemMinWidth,
|
|
@@ -559,7 +569,7 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
559
569
|
}, children: _jsx(Text, { accessible: false, align: "center", style: {
|
|
560
570
|
color: resolveColorReference(badgeTone.content, theme.palette),
|
|
561
571
|
fontWeight: counterBadgeRecipe.fontWeight,
|
|
562
|
-
}, variant: badgeMetrics.textVariant, children: item.badge.visibleLabel }) })) })) : null] }), (!capsule || expandedLabels || selected) && _jsx(HjmNativeProvider, { textScale: labelTextScale, children: _jsx(Text, { align: "center", allowFontScaling: bottomNavigationRecipe.largeText.allowFontScaling, maxFontSizeMultiplier: bottomNavigationRecipe.largeText.maxFontSizeMultiplier, style: {
|
|
572
|
+
}, variant: badgeMetrics.textVariant, children: item.badge.visibleLabel }) })) })) : null] }), labelsVisible && (!capsule || expandedLabels || selected) && _jsx(HjmNativeProvider, { textScale: labelTextScale, children: _jsx(Text, { align: "center", allowFontScaling: bottomNavigationRecipe.largeText.allowFontScaling, maxFontSizeMultiplier: bottomNavigationRecipe.largeText.maxFontSizeMultiplier, style: {
|
|
563
573
|
color: selected ? selectedLabelColor : idleColor,
|
|
564
574
|
flexShrink: 1,
|
|
565
575
|
fontWeight: selected
|
|
@@ -579,13 +589,12 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
579
589
|
},
|
|
580
590
|
// Keep creation outside route destinations; absolute center placement overlaps capsule labels.
|
|
581
591
|
capsule ? { position: "relative", left: undefined, right: undefined, top: undefined, bottom: undefined, flexShrink: 0 } : undefined,
|
|
582
|
-
primaryActionStyle,
|
|
583
592
|
], children: primaryAction })) : null] }) }));
|
|
584
593
|
}
|
|
585
594
|
/** Recipe-owned icon-over-micro-label action for Native screen chrome. */
|
|
586
595
|
export function TopBarAction(props) {
|
|
587
|
-
const { label, accessibilityLabel, accessibilityState, children, disabled = false, labelVisibility = "visible",
|
|
588
|
-
|
|
596
|
+
const { label, accessibilityLabel, accessibilityState, children, disabled = false, labelVisibility = "visible", layoutStyle, ...intentAndHostProps } = props;
|
|
597
|
+
assertNoRemovedStyleProps("TopBarAction", props, ["style", "labelStyle"]);
|
|
589
598
|
if (!label.trim())
|
|
590
599
|
throw new TypeError("TopBarAction label must not be empty");
|
|
591
600
|
const resolvedAccessibilityLabel = accessibilityLabel ?? label;
|
|
@@ -627,7 +636,6 @@ export function TopBarAction(props) {
|
|
|
627
636
|
color: resolveColorReference(topBarRecipe.actionLabel.color, theme.palette),
|
|
628
637
|
fontWeight: topBarRecipe.actionLabel.fontWeight,
|
|
629
638
|
},
|
|
630
|
-
labelStyle,
|
|
631
639
|
], variant: topBarRecipe.actionLabel.textVariant, children: label })) : null] })),
|
|
632
640
|
disabled,
|
|
633
641
|
onPress: handlePress,
|
|
@@ -647,7 +655,6 @@ export function TopBarAction(props) {
|
|
|
647
655
|
: 1,
|
|
648
656
|
paddingHorizontal: topBarRecipe.action.paddingHorizontal,
|
|
649
657
|
},
|
|
650
|
-
style,
|
|
651
658
|
layoutStyle,
|
|
652
659
|
],
|
|
653
660
|
};
|
|
@@ -661,8 +668,8 @@ export function TopBarAction(props) {
|
|
|
661
668
|
return props.renderAction?.(controlProps) ?? _jsx(Pressable, { ...controlProps });
|
|
662
669
|
}
|
|
663
670
|
/** Native screen top bar with logical action slots and large-text reflow. */
|
|
664
|
-
export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLabel, titleAccessibilityHint, leading, trailing, actions, centered = topBarRecipe.defaults.centered, safeAreaTop = 0, layoutStyle,
|
|
665
|
-
|
|
671
|
+
export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLabel, titleAccessibilityHint, leading, trailing, actions, centered = topBarRecipe.defaults.centered, safeAreaTop = 0, layoutStyle, ...removedProps }) {
|
|
672
|
+
assertNoRemovedStyleProps("TopBar", removedProps, ["style", "leadingStyle", "titleStyle", "trailingStyle"]);
|
|
666
673
|
if (!Number.isFinite(safeAreaTop) || safeAreaTop < 0) {
|
|
667
674
|
throw new RangeError("TopBar safeAreaTop must be non-negative");
|
|
668
675
|
}
|
|
@@ -700,7 +707,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
|
|
|
700
707
|
color: resolveColorReference(topBarRecipe.title.color, theme.palette),
|
|
701
708
|
fontWeight: topBarRecipe.title.fontWeight,
|
|
702
709
|
},
|
|
703
|
-
titleStyle,
|
|
704
710
|
];
|
|
705
711
|
const renderTitle = (containerStyle, numberOfLines) => {
|
|
706
712
|
// Match Web: a clickable title is a UI button; a static heading keeps display.
|
|
@@ -741,7 +747,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
|
|
|
741
747
|
paddingHorizontal: topBarRecipe.paddingHorizontal,
|
|
742
748
|
paddingTop: safeAreaTop,
|
|
743
749
|
},
|
|
744
|
-
style,
|
|
745
750
|
layoutStyle,
|
|
746
751
|
], children: largeText ? (
|
|
747
752
|
// These branches have different host hierarchies. Reusing the full-width
|
|
@@ -760,7 +765,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
|
|
|
760
765
|
flexDirection: "row",
|
|
761
766
|
minWidth: topBarRecipe.sideMinWidth,
|
|
762
767
|
},
|
|
763
|
-
leadingStyle,
|
|
764
768
|
], children: leading })) : null, hasTitle ? (renderTitle({ flex: 1, flexShrink: 1, minWidth: 0 })) : null, hasTitle && centered && hasLeading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", pointerEvents: "none", style: { minWidth: topBarRecipe.sideMinWidth } })) : null] })) : null, hasTrailingContent ? (_jsx(View, { style: [
|
|
765
769
|
{
|
|
766
770
|
direction: theme.environment.direction,
|
|
@@ -771,7 +775,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
|
|
|
771
775
|
minWidth: topBarRecipe.sideMinWidth,
|
|
772
776
|
width: "100%",
|
|
773
777
|
},
|
|
774
|
-
trailingStyle,
|
|
775
778
|
], children: trailingContent })) : null] }, "large-text")) : (_jsxs(Fragment, { children: [renderCompactLeadingSlot ? (_jsx(View, { style: [
|
|
776
779
|
{
|
|
777
780
|
alignItems: "center",
|
|
@@ -781,7 +784,6 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
|
|
|
781
784
|
justifyContent: "flex-start",
|
|
782
785
|
minWidth: topBarRecipe.sideMinWidth,
|
|
783
786
|
},
|
|
784
|
-
leadingStyle,
|
|
785
787
|
], children: hasLeading ? leading : null })) : null, hasTitle ? (
|
|
786
788
|
// Keep long route titles readable; the screen-chrome contract promises wrapping, while a one-line cap silently hid the rest.
|
|
787
789
|
renderTitle({ flex: 2, flexShrink: 1, minWidth: 0 })) : null, renderCompactTrailingSlot ? (_jsx(View, { style: [
|
|
@@ -796,15 +798,20 @@ export function TopBar({ title, titleLeading, onTitlePress, titleAccessibilityLa
|
|
|
796
798
|
marginStart: hasTitle ? undefined : "auto",
|
|
797
799
|
minWidth: topBarRecipe.sideMinWidth,
|
|
798
800
|
},
|
|
799
|
-
trailingStyle,
|
|
800
801
|
], children: hasTrailingContent ? trailingContent : null })) : null] }, "compact")) }));
|
|
801
802
|
}
|
|
802
803
|
function useMenuAfterDismiss(visible) {
|
|
804
|
+
const visibleRef = useRef(visible);
|
|
805
|
+
visibleRef.current = visible;
|
|
806
|
+
const mountedRef = useRef(true);
|
|
807
|
+
const generationRef = useRef(0);
|
|
803
808
|
const shownRef = useRef(false);
|
|
804
809
|
const previousVisibleRef = useRef(visible);
|
|
805
810
|
const pendingRef = useRef(null);
|
|
806
811
|
const taskRef = useRef(null);
|
|
807
812
|
const complete = useCallback(() => {
|
|
813
|
+
if (!mountedRef.current || visibleRef.current)
|
|
814
|
+
return;
|
|
808
815
|
taskRef.current?.cancel();
|
|
809
816
|
taskRef.current = null;
|
|
810
817
|
shownRef.current = false;
|
|
@@ -816,12 +823,32 @@ function useMenuAfterDismiss(visible) {
|
|
|
816
823
|
useLayoutEffect(() => {
|
|
817
824
|
const wasVisible = previousVisibleRef.current;
|
|
818
825
|
previousVisibleRef.current = visible;
|
|
826
|
+
if (!wasVisible && visible) {
|
|
827
|
+
// Reopening invalidates the previous selection; its late teardown must not run under the new menu.
|
|
828
|
+
generationRef.current += 1;
|
|
829
|
+
taskRef.current?.cancel();
|
|
830
|
+
taskRef.current = null;
|
|
831
|
+
pendingRef.current = null;
|
|
832
|
+
shownRef.current = false;
|
|
833
|
+
}
|
|
819
834
|
if (!wasVisible || visible || shouldAwaitNativeModalDismiss(shownRef.current))
|
|
820
835
|
return;
|
|
821
836
|
taskRef.current?.cancel();
|
|
822
|
-
|
|
837
|
+
const generation = generationRef.current;
|
|
838
|
+
taskRef.current = scheduleAfterNativeModalTeardown(() => {
|
|
839
|
+
if (generation === generationRef.current)
|
|
840
|
+
complete();
|
|
841
|
+
});
|
|
823
842
|
}, [complete, visible]);
|
|
824
|
-
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
|
+
}, []);
|
|
825
852
|
return {
|
|
826
853
|
queue(callback) {
|
|
827
854
|
pendingRef.current = callback;
|
|
@@ -833,8 +860,8 @@ function useMenuAfterDismiss(visible) {
|
|
|
833
860
|
};
|
|
834
861
|
}
|
|
835
862
|
/** Sectioned Native action/selection menu with teardown-safe action callbacks. */
|
|
836
|
-
export function Menu({ triggerLabel, title = triggerLabel, items, sections, source: sourceProp, selection = { mode: "none" }, onAction, onActionAfterDismiss, onSelectionAfterDismiss, open, defaultOpen = false, onOpenChange, onDismiss, disabled = false, readOnly = false, busy = false, readOnlyLabel, asyncState = { status: "idle" }, onRetry, retryLabel, density = menuRecipe.defaults.density, renderLeading, renderTrailing, dismissLabel, trigger, renderTrigger, layoutStyle,
|
|
837
|
-
|
|
863
|
+
export function Menu({ triggerLabel, title = triggerLabel, items, sections, source: sourceProp, selection = { mode: "none" }, onAction, onActionAfterDismiss, onSelectionAfterDismiss, open, defaultOpen = false, onOpenChange, onDismiss, disabled = false, readOnly = false, busy = false, readOnlyLabel, asyncState = { status: "idle" }, onRetry, retryLabel, density = menuRecipe.defaults.density, renderLeading, renderTrailing, dismissLabel, trigger, renderTrigger, layoutStyle, ...modalProps }) {
|
|
864
|
+
assertNoRemovedStyleProps("Menu", modalProps, ["style"]);
|
|
838
865
|
const providedSources = [sourceProp, items, sections].filter((candidate) => candidate !== undefined).length;
|
|
839
866
|
if (providedSources !== 1) {
|
|
840
867
|
throw new TypeError("Menu requires exactly one of source, items, or sections");
|
|
@@ -997,7 +1024,7 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
|
|
|
997
1024
|
requestOpen(!visible, "trigger");
|
|
998
1025
|
},
|
|
999
1026
|
};
|
|
1000
|
-
return (_jsxs(View, { style: [
|
|
1027
|
+
return (_jsxs(View, { style: [layoutStyle], children: [_jsx(View, { ref: triggerRef, children: renderTrigger ? renderTrigger(triggerProps) : (_jsx(Pressable, { accessibilityHint: readOnly ? readOnlyLabel : undefined, accessibilityLabel: triggerLabel, accessibilityRole: "button", accessibilityState: triggerProps.accessibilityState, disabled: disabled || readOnly || busy, onPress: triggerProps.onPress, style: ({ pressed }) => [
|
|
1001
1028
|
minimumTargetStyle,
|
|
1002
1029
|
{
|
|
1003
1030
|
alignItems: "center",
|
|
@@ -1050,6 +1077,8 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
|
|
|
1050
1077
|
stateRef.current = descriptor.state;
|
|
1051
1078
|
handlerRef.current = onLoadMore;
|
|
1052
1079
|
const [controllerFacade] = useState(createNativeLoadMoreControllerFacade);
|
|
1080
|
+
const requestGeneration = useRef(0);
|
|
1081
|
+
const requestActive = useRef(false);
|
|
1053
1082
|
const lastViewportRequest = useRef(null);
|
|
1054
1083
|
useLayoutEffect(() => {
|
|
1055
1084
|
const controller = createLoadMoreController({
|
|
@@ -1057,13 +1086,21 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
|
|
|
1057
1086
|
onLoadMore: (request) => handlerRef.current(request),
|
|
1058
1087
|
});
|
|
1059
1088
|
const detach = controllerFacade.attach(controller);
|
|
1089
|
+
requestGeneration.current += 1;
|
|
1090
|
+
requestActive.current = true;
|
|
1060
1091
|
lastViewportRequest.current = null;
|
|
1061
1092
|
return () => {
|
|
1093
|
+
requestGeneration.current += 1;
|
|
1094
|
+
requestActive.current = false;
|
|
1062
1095
|
detach();
|
|
1063
1096
|
controller.dispose();
|
|
1064
1097
|
};
|
|
1065
1098
|
}, [controllerFacade, mode]);
|
|
1066
1099
|
const request = useCallback(async (reason) => {
|
|
1100
|
+
// FlatList may have queued onEndReached before the footer was removed.
|
|
1101
|
+
if (!requestActive.current)
|
|
1102
|
+
return "blocked-by-state";
|
|
1103
|
+
const generation = requestGeneration.current;
|
|
1067
1104
|
const state = stateRef.current;
|
|
1068
1105
|
if (reason === "viewport" && mode === "automatic" && state.status === "ready") {
|
|
1069
1106
|
const previous = lastViewportRequest.current;
|
|
@@ -1073,18 +1110,22 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
|
|
|
1073
1110
|
onRequestOutcome?.(outcome, reason);
|
|
1074
1111
|
return outcome;
|
|
1075
1112
|
}
|
|
1076
|
-
lastViewportRequest.current = {
|
|
1077
|
-
mode,
|
|
1078
|
-
requestKey: state.requestKey,
|
|
1079
|
-
};
|
|
1080
1113
|
}
|
|
1081
1114
|
try {
|
|
1082
1115
|
const outcome = await controllerFacade.request(state, reason);
|
|
1116
|
+
if (generation !== requestGeneration.current)
|
|
1117
|
+
return outcome;
|
|
1118
|
+
// Only a successfully completed viewport request retires its cursor.
|
|
1119
|
+
// A blocked next page or a rejection must remain eligible for retry.
|
|
1120
|
+
if (outcome === "started" && reason === "viewport" && state.status === "ready") {
|
|
1121
|
+
lastViewportRequest.current = { mode, requestKey: state.requestKey };
|
|
1122
|
+
}
|
|
1083
1123
|
onRequestOutcome?.(outcome, reason);
|
|
1084
1124
|
return outcome;
|
|
1085
1125
|
}
|
|
1086
1126
|
catch (error) {
|
|
1087
|
-
|
|
1127
|
+
if (generation === requestGeneration.current)
|
|
1128
|
+
onRequestError?.(error, reason);
|
|
1088
1129
|
// The request was accepted and started; product error state arrives
|
|
1089
1130
|
// through the controlled descriptor and optional callback. Keep the
|
|
1090
1131
|
// FlatList imperative bridge rejection-free like the Web observer.
|