panelui-native 0.63.0 → 0.66.1
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 +5 -0
- package/lib/module/components/badge/index.js +1 -1
- package/lib/module/components/badge/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +39 -2
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js +2 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +19 -9
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/carousel/carousel-lifecycle.js +51 -0
- package/lib/module/components/carousel/carousel-lifecycle.js.map +1 -0
- package/lib/module/components/carousel/index.js +86 -35
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/checkbox/index.js +2 -3
- package/lib/module/components/checkbox/index.js.map +1 -1
- package/lib/module/components/context-menu/context-menu-invocation.js +23 -0
- package/lib/module/components/context-menu/context-menu-invocation.js.map +1 -0
- package/lib/module/components/context-menu/index.js +78 -9
- package/lib/module/components/context-menu/index.js.map +1 -1
- package/lib/module/components/dialog/index.js +2 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +2 -2
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/fab/index.js +4 -3
- package/lib/module/components/fab/index.js.map +1 -1
- package/lib/module/components/field/index.js +41 -6
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/flow/flow-accessibility.js +100 -0
- package/lib/module/components/flow/flow-accessibility.js.map +1 -0
- package/lib/module/components/flow/flow-identifiers.js +81 -0
- package/lib/module/components/flow/flow-identifiers.js.map +1 -0
- package/lib/module/components/flow/index.js +112 -57
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/form/use-form.js +93 -66
- package/lib/module/components/form/use-form.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +2 -2
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/hex-chart/index.js +2 -2
- package/lib/module/components/hex-chart/index.js.map +1 -1
- package/lib/module/components/kpi/index.js +3 -1
- package/lib/module/components/kpi/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +3 -3
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +71 -8
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/map/map-accessibility.js +20 -0
- package/lib/module/components/map/map-accessibility.js.map +1 -0
- package/lib/module/components/map/maplibre.js.map +1 -1
- package/lib/module/components/menu/index.js +4 -1
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +17 -9
- package/lib/module/components/message-scroller/index.js.map +1 -1
- package/lib/module/components/message-scroller/message-scroller-math.js +20 -0
- package/lib/module/components/message-scroller/message-scroller-math.js.map +1 -0
- package/lib/module/components/number-input/index.js +1 -23
- package/lib/module/components/number-input/index.js.map +1 -1
- package/lib/module/components/number-input/number-input-math.js +44 -0
- package/lib/module/components/number-input/number-input-math.js.map +1 -0
- package/lib/module/components/plot/index.js +2 -2
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +1 -2
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/selection-mode/index.js +26 -31
- package/lib/module/components/selection-mode/index.js.map +1 -1
- package/lib/module/components/selection-mode/selection-mode-contracts.js +48 -0
- package/lib/module/components/selection-mode/selection-mode-contracts.js.map +1 -0
- package/lib/module/components/signature/index.js +62 -20
- package/lib/module/components/signature/index.js.map +1 -1
- package/lib/module/components/signature/signature-accessibility.js +58 -0
- package/lib/module/components/signature/signature-accessibility.js.map +1 -0
- package/lib/module/components/slider/index.js +22 -18
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/sortable/index.js +5 -55
- package/lib/module/components/sortable/index.js.map +1 -1
- package/lib/module/components/sortable/reorder.js +63 -0
- package/lib/module/components/sortable/reorder.js.map +1 -0
- package/lib/module/components/swipe/index.js +4 -4
- package/lib/module/components/swipe/index.js.map +1 -1
- package/lib/module/components/switch/index.js +36 -3
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/time-picker/accessibility.js +21 -0
- package/lib/module/components/time-picker/accessibility.js.map +1 -0
- package/lib/module/components/time-picker/index.js +34 -8
- package/lib/module/components/time-picker/index.js.map +1 -1
- package/lib/module/components/waterfall-chart/index.js +1325 -0
- package/lib/module/components/waterfall-chart/index.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/modal-isolation-store.js +29 -0
- package/lib/module/primitives/modal-isolation-store.js.map +1 -0
- package/lib/module/primitives/portal.js +36 -4
- package/lib/module/primitives/portal.js.map +1 -1
- package/lib/module/primitives/scrim.js +94 -3
- package/lib/module/primitives/scrim.js.map +1 -1
- package/lib/module/providers/panel-ui-provider.js +27 -6
- package/lib/module/providers/panel-ui-provider.js.map +1 -1
- package/lib/typescript/src/components/badge/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +3 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts +23 -0
- package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/components/carousel/index.d.ts +5 -2
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
- package/lib/typescript/src/components/checkbox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts +20 -0
- package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts.map +1 -0
- package/lib/typescript/src/components/context-menu/index.d.ts +18 -8
- package/lib/typescript/src/components/context-menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/dialog/index.d.ts +3 -0
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts +3 -0
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
- package/lib/typescript/src/components/field/index.d.ts +8 -1
- package/lib/typescript/src/components/field/index.d.ts.map +1 -1
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts +57 -0
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/flow/flow-identifiers.d.ts +24 -0
- package/lib/typescript/src/components/flow/flow-identifiers.d.ts.map +1 -0
- package/lib/typescript/src/components/flow/index.d.ts +20 -7
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
- package/lib/typescript/src/components/form/use-form.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/kpi/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts +13 -5
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/map-accessibility.d.ts +19 -0
- package/lib/typescript/src/components/map/map-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/map/maplibre.d.ts +2 -0
- package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts +8 -0
- package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts.map +1 -0
- package/lib/typescript/src/components/number-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/number-input/number-input-math.d.ts +9 -0
- package/lib/typescript/src/components/number-input/number-input-math.d.ts.map +1 -0
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/popover/index.d.ts +3 -0
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/selection-mode/index.d.ts +6 -4
- package/lib/typescript/src/components/selection-mode/index.d.ts.map +1 -1
- package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts +18 -0
- package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts.map +1 -0
- package/lib/typescript/src/components/signature/index.d.ts +6 -0
- package/lib/typescript/src/components/signature/index.d.ts.map +1 -1
- package/lib/typescript/src/components/signature/signature-accessibility.d.ts +27 -0
- package/lib/typescript/src/components/signature/signature-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sortable/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sortable/reorder.d.ts +14 -0
- package/lib/typescript/src/components/sortable/reorder.d.ts.map +1 -0
- package/lib/typescript/src/components/swipe/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts +3 -3
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/accessibility.d.ts +10 -0
- package/lib/typescript/src/components/time-picker/accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/waterfall-chart/index.d.ts +379 -0
- package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/modal-isolation-store.d.ts +10 -0
- package/lib/typescript/src/primitives/modal-isolation-store.d.ts.map +1 -0
- package/lib/typescript/src/primitives/portal.d.ts +6 -0
- package/lib/typescript/src/primitives/portal.d.ts.map +1 -1
- package/lib/typescript/src/primitives/scrim.d.ts +5 -3
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
- package/lib/typescript/src/providers/panel-ui-provider.d.ts.map +1 -1
- package/package.json +11 -5
- package/src/components/badge/index.tsx +4 -1
- package/src/components/bar-chart/index.tsx +40 -3
- package/src/components/bottom-sheet/index.tsx +6 -3
- package/src/components/button/index.tsx +15 -21
- package/src/components/carousel/carousel-lifecycle.ts +74 -0
- package/src/components/carousel/index.tsx +118 -41
- package/src/components/checkbox/index.tsx +1 -2
- package/src/components/context-menu/context-menu-invocation.ts +44 -0
- package/src/components/context-menu/index.tsx +113 -13
- package/src/components/dialog/index.tsx +6 -3
- package/src/components/drawer/index.tsx +6 -3
- package/src/components/fab/index.tsx +8 -12
- package/src/components/field/index.tsx +52 -10
- package/src/components/flow/flow-accessibility.ts +123 -0
- package/src/components/flow/flow-identifiers.ts +98 -0
- package/src/components/flow/index.tsx +191 -69
- package/src/components/form/use-form.ts +101 -65
- package/src/components/heatmap-chart/index.tsx +2 -3
- package/src/components/hex-chart/index.tsx +2 -3
- package/src/components/kpi/index.tsx +3 -1
- package/src/components/line-chart/index.tsx +3 -2
- package/src/components/map/index.tsx +110 -7
- package/src/components/map/map-accessibility.ts +41 -0
- package/src/components/map/maplibre.ts +2 -0
- package/src/components/menu/index.tsx +4 -1
- package/src/components/message-scroller/index.tsx +39 -7
- package/src/components/message-scroller/message-scroller-math.ts +24 -0
- package/src/components/number-input/index.tsx +1 -26
- package/src/components/number-input/number-input-math.ts +50 -0
- package/src/components/plot/index.tsx +2 -1
- package/src/components/popover/index.tsx +3 -0
- package/src/components/radio-group/index.tsx +1 -2
- package/src/components/selection-mode/index.tsx +37 -30
- package/src/components/selection-mode/selection-mode-contracts.ts +53 -0
- package/src/components/signature/index.tsx +107 -20
- package/src/components/signature/signature-accessibility.ts +67 -0
- package/src/components/slider/index.tsx +28 -18
- package/src/components/sortable/index.tsx +12 -64
- package/src/components/sortable/reorder.ts +88 -0
- package/src/components/swipe/index.tsx +7 -4
- package/src/components/switch/index.tsx +42 -6
- package/src/components/time-picker/accessibility.ts +24 -0
- package/src/components/time-picker/index.tsx +48 -2
- package/src/components/waterfall-chart/index.tsx +1589 -0
- package/src/index.ts +25 -0
- package/src/primitives/modal-isolation-store.ts +31 -0
- package/src/primitives/portal.tsx +34 -4
- package/src/primitives/scrim.tsx +127 -7
- package/src/providers/panel-ui-provider.tsx +41 -9
- package/theme.css +30 -3
|
@@ -20,7 +20,10 @@ const badgeVariants = tv({
|
|
|
20
20
|
default: { root: 'bg-primary', label: 'text-primary-foreground' },
|
|
21
21
|
secondary: { root: 'bg-secondary', label: 'text-secondary-foreground' },
|
|
22
22
|
outline: { root: 'border-border bg-transparent', label: 'text-foreground' },
|
|
23
|
-
destructive: {
|
|
23
|
+
destructive: {
|
|
24
|
+
root: 'bg-destructive',
|
|
25
|
+
label: 'text-destructive-solid-foreground',
|
|
26
|
+
},
|
|
24
27
|
success: { root: 'bg-success-subtle', label: 'text-success-foreground' },
|
|
25
28
|
warning: { root: 'bg-warning-subtle', label: 'text-warning-foreground' },
|
|
26
29
|
info: { root: 'bg-info-subtle', label: 'text-info-foreground' },
|
|
@@ -1083,6 +1083,15 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1083
1083
|
const width = plot.width;
|
|
1084
1084
|
const height = plot.height;
|
|
1085
1085
|
|
|
1086
|
+
/*
|
|
1087
|
+
* The readout's own height, measured rather than assumed. Sideways it has to
|
|
1088
|
+
* be clamped inside the plot vertically, and how tall it is depends on how
|
|
1089
|
+
* many series are listed in it — a constant here would either let a
|
|
1090
|
+
* three-series readout hang off the bottom or reserve room a one-series
|
|
1091
|
+
* readout never uses.
|
|
1092
|
+
*/
|
|
1093
|
+
const labelHeight = useSharedValue(0);
|
|
1094
|
+
|
|
1086
1095
|
/*
|
|
1087
1096
|
* Declared inside the memo, next to its callers: a worklet may only call
|
|
1088
1097
|
* another worklet, and the rule is enforced by crashing rather than warning.
|
|
@@ -1118,8 +1127,17 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1118
1127
|
});
|
|
1119
1128
|
}, [total, left, top, width, height, horizontal, activeIndex, setActiveIndexJS]);
|
|
1120
1129
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1130
|
+
/*
|
|
1131
|
+
* The readout centres over its band and is clamped inside the plot, so it
|
|
1132
|
+
* never runs off the edge at the first or last one.
|
|
1133
|
+
*
|
|
1134
|
+
* Which axis the band runs along is the whole difference between the two
|
|
1135
|
+
* orientations. Upright, the bands are side by side, so the readout slides
|
|
1136
|
+
* across and sits above them all. Sideways, the bands are stacked down the
|
|
1137
|
+
* plot, so it slides *down* to the row it is describing and stays centred
|
|
1138
|
+
* across — held at the top instead, it would name the row under the finger
|
|
1139
|
+
* while covering the first one, which is the row a reader checks it against.
|
|
1140
|
+
*/
|
|
1123
1141
|
const labelStyle = useAnimatedStyle(() => {
|
|
1124
1142
|
const index = activeIndex.value;
|
|
1125
1143
|
if (index < 0 || !total) return { opacity: 0 };
|
|
@@ -1130,7 +1148,23 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1130
1148
|
plot.left + plot.width - half,
|
|
1131
1149
|
Math.max(plot.left + half, horizontal ? plot.left + plot.width / 2 : centre)
|
|
1132
1150
|
);
|
|
1133
|
-
|
|
1151
|
+
|
|
1152
|
+
if (!horizontal) {
|
|
1153
|
+
return { opacity: 1, transform: [{ translateX: clamped - half }] };
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Until the first measurement lands the height is zero, which clamps to
|
|
1157
|
+
// the top of the plot — the same place it used to sit, rather than a jump
|
|
1158
|
+
// from somewhere it never was.
|
|
1159
|
+
const tall = labelHeight.value;
|
|
1160
|
+
const y = Math.min(
|
|
1161
|
+
plot.top + Math.max(plot.height - tall, 0),
|
|
1162
|
+
Math.max(plot.top, centre - tall / 2)
|
|
1163
|
+
);
|
|
1164
|
+
return {
|
|
1165
|
+
opacity: 1,
|
|
1166
|
+
transform: [{ translateX: clamped - half }, { translateY: y }],
|
|
1167
|
+
};
|
|
1134
1168
|
});
|
|
1135
1169
|
|
|
1136
1170
|
const active = activeIndexJS >= 0 ? data[activeIndexJS] : null;
|
|
@@ -1151,6 +1185,9 @@ function BarChartTooltip({ formatValue, formatX, className }: BarChartTooltipPro
|
|
|
1151
1185
|
>
|
|
1152
1186
|
{active ? (
|
|
1153
1187
|
<View
|
|
1188
|
+
onLayout={(event) => {
|
|
1189
|
+
labelHeight.value = event.nativeEvent.layout.height;
|
|
1190
|
+
}}
|
|
1154
1191
|
className={cn(
|
|
1155
1192
|
'rounded-xl border border-border bg-popover px-2.5 py-1.5 shadow-lg',
|
|
1156
1193
|
className
|
|
@@ -31,7 +31,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
31
31
|
import { tv } from 'tailwind-variants';
|
|
32
32
|
import { getNativeUI } from '../../native';
|
|
33
33
|
import { XIcon } from '../../icons';
|
|
34
|
-
import {
|
|
34
|
+
import { ModalPortal } from '../../primitives/portal';
|
|
35
35
|
import { Scrim } from '../../primitives/scrim';
|
|
36
36
|
import { Text, textChildren } from '../../primitives/text';
|
|
37
37
|
import { useBackHandler } from '../../hooks/use-back-handler';
|
|
@@ -236,6 +236,9 @@ export interface BottomSheetContentProps extends ViewProps {
|
|
|
236
236
|
* Frost the screen behind the sheet instead of dimming it, so what is behind
|
|
237
237
|
* stays legible as shape and colour while losing its detail. Needs the
|
|
238
238
|
* optional `expo-blur`; without it this dims, rather than failing.
|
|
239
|
+
*
|
|
240
|
+
* Someone who has Reduce Transparency switched on gets an opaque
|
|
241
|
+
* backdrop instead, which is the whole point of the setting.
|
|
239
242
|
*/
|
|
240
243
|
blur?: boolean;
|
|
241
244
|
/**
|
|
@@ -435,7 +438,7 @@ function BottomSheetContent({
|
|
|
435
438
|
if (!open) return null;
|
|
436
439
|
|
|
437
440
|
return (
|
|
438
|
-
<
|
|
441
|
+
<ModalPortal>
|
|
439
442
|
{/* Portal content mounts under PortalHost, outside this provider's
|
|
440
443
|
subtree — re-provide the context so nested consumers keep working. */}
|
|
441
444
|
<BottomSheetContext.Provider value={context}>
|
|
@@ -507,7 +510,7 @@ function BottomSheetContent({
|
|
|
507
510
|
</GestureDetector>
|
|
508
511
|
</View>
|
|
509
512
|
</BottomSheetContext.Provider>
|
|
510
|
-
</
|
|
513
|
+
</ModalPortal>
|
|
511
514
|
);
|
|
512
515
|
}
|
|
513
516
|
|
|
@@ -42,8 +42,9 @@ const buttonVariants = tv({
|
|
|
42
42
|
},
|
|
43
43
|
destructive: {
|
|
44
44
|
root: 'border-destructive bg-destructive shadow-sm',
|
|
45
|
-
label: 'text-
|
|
46
|
-
spinner:
|
|
45
|
+
label: 'text-destructive-solid-foreground',
|
|
46
|
+
spinner:
|
|
47
|
+
'border-destructive-solid-foreground/32 border-t-destructive-solid-foreground',
|
|
47
48
|
},
|
|
48
49
|
/** Neutral surface for third-party sign-in, sized for a full-width stack. */
|
|
49
50
|
social: {
|
|
@@ -53,8 +54,8 @@ const buttonVariants = tv({
|
|
|
53
54
|
},
|
|
54
55
|
},
|
|
55
56
|
size: {
|
|
56
|
-
sm: { root: 'h-9 gap-1.5 px-2.5', label: 'text-sm' },
|
|
57
|
-
md: { root: 'h-11 px-4', label: 'text-base' },
|
|
57
|
+
sm: { root: 'h-9 min-w-9 gap-1.5 px-2.5', label: 'text-sm' },
|
|
58
|
+
md: { root: 'h-11 min-w-11 px-4', label: 'text-base' },
|
|
58
59
|
lg: { root: 'h-12 px-6', label: 'text-lg' },
|
|
59
60
|
icon: { root: 'h-11 w-11 px-0' },
|
|
60
61
|
},
|
|
@@ -134,18 +135,16 @@ const ATTACHED = 'rounded-none border-transparent shadow-none active:bg-accent';
|
|
|
134
135
|
|
|
135
136
|
const SPINNER_SIZE = { sm: 'sm', md: 'sm', lg: 'md', icon: 'sm' } as const;
|
|
136
137
|
|
|
138
|
+
/** Extra room around the styled sizes so even the compact button reaches 48dp. */
|
|
139
|
+
const BUTTON_HIT_SLOP = { sm: 6, md: 2, lg: 0, icon: 2 } as const;
|
|
140
|
+
|
|
137
141
|
/**
|
|
138
142
|
* The theme token each variant's content reads against. Icons in the content
|
|
139
143
|
* slots inherit the resolved value, so they follow the theme automatically.
|
|
140
|
-
*
|
|
141
|
-
* `destructive` is absent on purpose: its background is a saturated red in
|
|
142
|
-
* every theme, so its content is always white rather than a themed token.
|
|
143
144
|
*/
|
|
144
|
-
const CONTENT_COLOR_VAR: Record<
|
|
145
|
-
Exclude<NonNullable<ButtonVariantProps['variant']>, 'destructive'>,
|
|
146
|
-
string
|
|
147
|
-
> = {
|
|
145
|
+
const CONTENT_COLOR_VAR: Record<NonNullable<ButtonVariantProps['variant']>, string> = {
|
|
148
146
|
primary: '--color-primary-foreground',
|
|
147
|
+
destructive: '--color-destructive-solid-foreground',
|
|
149
148
|
secondary: '--color-secondary-foreground',
|
|
150
149
|
outline: '--color-foreground',
|
|
151
150
|
ghost: '--color-foreground',
|
|
@@ -328,17 +327,9 @@ export const Button = forwardRef<View, ButtonProps>(
|
|
|
328
327
|
// Icons in the content slots inherit this, so they stay legible when the
|
|
329
328
|
// theme inverts the button's background. Without it every caller has to
|
|
330
329
|
// hardcode a hex that is wrong in one theme or the other.
|
|
331
|
-
const themedColor = useCSSVariable(
|
|
332
|
-
CONTENT_COLOR_VAR[
|
|
333
|
-
resolvedVariant === 'destructive' ? 'primary' : (resolvedVariant ?? 'primary')
|
|
334
|
-
]
|
|
335
|
-
);
|
|
330
|
+
const themedColor = useCSSVariable(CONTENT_COLOR_VAR[resolvedVariant ?? 'primary']);
|
|
336
331
|
const contentColor =
|
|
337
|
-
|
|
338
|
-
? '#ffffff'
|
|
339
|
-
: typeof themedColor === 'string'
|
|
340
|
-
? themedColor
|
|
341
|
-
: undefined;
|
|
332
|
+
typeof themedColor === 'string' ? themedColor : undefined;
|
|
342
333
|
|
|
343
334
|
if (nativeUI) {
|
|
344
335
|
const { Host, Button: NativeButton, RNHostView } = nativeUI;
|
|
@@ -446,6 +437,9 @@ export const Button = forwardRef<View, ButtonProps>(
|
|
|
446
437
|
accessibilityRole="button"
|
|
447
438
|
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
448
439
|
disabled={isDisabled}
|
|
440
|
+
// Joined segments cannot borrow room from each other without making
|
|
441
|
+
// two actions claim the same pixels. ButtonGroup owns that layout.
|
|
442
|
+
hitSlop={attached ? undefined : BUTTON_HIT_SLOP[resolvedSize ?? 'md']}
|
|
449
443
|
// Before the spread, so a caller can still ask for the scale back.
|
|
450
444
|
pressScale={attached ? 1 : undefined}
|
|
451
445
|
className={root({ className: cn(attached && ATTACHED, className) })}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export function normalizeCarouselIndex(index: number, count: number, loop: boolean): number {
|
|
4
|
+
'worklet';
|
|
5
|
+
if (count <= 0 || !Number.isFinite(index)) return 0;
|
|
6
|
+
if (loop) return ((index % count) + count) % count;
|
|
7
|
+
return Math.max(0, Math.min(index, count - 1));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface CarouselIndexLifecycleOptions {
|
|
11
|
+
requestedIndex: number;
|
|
12
|
+
count: number;
|
|
13
|
+
countKnown: boolean;
|
|
14
|
+
loop: boolean;
|
|
15
|
+
onCorrection: (index: number) => void;
|
|
16
|
+
onSettledIndex: (index: number) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Keeps the rendered position inside the current run and reports corrections once. */
|
|
20
|
+
export function useCarouselIndexLifecycle({
|
|
21
|
+
requestedIndex,
|
|
22
|
+
count,
|
|
23
|
+
countKnown,
|
|
24
|
+
loop,
|
|
25
|
+
onCorrection,
|
|
26
|
+
onSettledIndex,
|
|
27
|
+
}: CarouselIndexLifecycleOptions): number {
|
|
28
|
+
const index = countKnown
|
|
29
|
+
? normalizeCarouselIndex(requestedIndex, count, loop)
|
|
30
|
+
: requestedIndex;
|
|
31
|
+
const reportedCorrection = useRef<string | null>(null);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!countKnown) return;
|
|
35
|
+
onSettledIndex(index);
|
|
36
|
+
|
|
37
|
+
if (index === requestedIndex) {
|
|
38
|
+
reportedCorrection.current = null;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const correction = `${requestedIndex}:${count}:${loop}`;
|
|
43
|
+
if (reportedCorrection.current === correction) return;
|
|
44
|
+
reportedCorrection.current = correction;
|
|
45
|
+
onCorrection(index);
|
|
46
|
+
}, [count, countKnown, index, loop, onCorrection, onSettledIndex, requestedIndex]);
|
|
47
|
+
|
|
48
|
+
return index;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CarouselAutoplayOptions {
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
index: number;
|
|
54
|
+
count: number;
|
|
55
|
+
loop: boolean;
|
|
56
|
+
interval: number;
|
|
57
|
+
onAdvance: (index: number) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** One autoplay request per accepted index; configuration changes re-arm it. */
|
|
61
|
+
export function useCarouselAutoplay({
|
|
62
|
+
enabled,
|
|
63
|
+
index,
|
|
64
|
+
count,
|
|
65
|
+
loop,
|
|
66
|
+
interval,
|
|
67
|
+
onAdvance,
|
|
68
|
+
}: CarouselAutoplayOptions): void {
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!enabled || count <= 1 || (!loop && index >= count - 1)) return;
|
|
71
|
+
const timer = setTimeout(() => onAdvance(index + 1), interval);
|
|
72
|
+
return () => clearTimeout(timer);
|
|
73
|
+
}, [count, enabled, index, interval, loop, onAdvance]);
|
|
74
|
+
}
|
|
@@ -50,11 +50,13 @@ import {
|
|
|
50
50
|
Children,
|
|
51
51
|
createContext,
|
|
52
52
|
forwardRef,
|
|
53
|
+
isValidElement,
|
|
53
54
|
useCallback,
|
|
54
55
|
useContext,
|
|
55
56
|
useEffect,
|
|
56
57
|
useImperativeHandle,
|
|
57
58
|
useMemo,
|
|
59
|
+
useRef,
|
|
58
60
|
useState,
|
|
59
61
|
type ReactNode,
|
|
60
62
|
} from 'react';
|
|
@@ -74,6 +76,11 @@ import Animated, {
|
|
|
74
76
|
import { ChevronLeftIcon, ChevronRightIcon } from '../../icons';
|
|
75
77
|
import { Text, textChildren } from '../../primitives/text';
|
|
76
78
|
import { cn } from '../../utils/cn';
|
|
79
|
+
import {
|
|
80
|
+
normalizeCarouselIndex,
|
|
81
|
+
useCarouselAutoplay,
|
|
82
|
+
useCarouselIndexLifecycle,
|
|
83
|
+
} from './carousel-lifecycle';
|
|
77
84
|
|
|
78
85
|
/** Settles the run onto a whole index. Tuned to stop rather than to bounce. */
|
|
79
86
|
const SPRING = { damping: 22, stiffness: 190, mass: 0.55 } as const;
|
|
@@ -161,8 +168,7 @@ export function useCarouselState() {
|
|
|
161
168
|
*/
|
|
162
169
|
function wrap(value: number, count: number) {
|
|
163
170
|
'worklet';
|
|
164
|
-
|
|
165
|
-
return ((value % count) + count) % count;
|
|
171
|
+
return normalizeCarouselIndex(value, count, true);
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
/**
|
|
@@ -183,6 +189,17 @@ function distance(index: number, progress: number, count: number, loop: boolean)
|
|
|
183
189
|
return raw;
|
|
184
190
|
}
|
|
185
191
|
|
|
192
|
+
/** Drop the empty nodes React omits before assigning slide positions. */
|
|
193
|
+
function renderableChildren(children: ReactNode) {
|
|
194
|
+
// React treats a Fragment as one child here. Keep that keyed boundary rather
|
|
195
|
+
// than recursively flattening it and changing React's reconciliation model.
|
|
196
|
+
return Children.toArray(children);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function renderableChildKey(child: ReactNode, index: number) {
|
|
200
|
+
return isValidElement(child) && child.key !== null ? child.key : index;
|
|
201
|
+
}
|
|
202
|
+
|
|
186
203
|
export interface CarouselProps extends ViewProps {
|
|
187
204
|
className?: string;
|
|
188
205
|
/** How the slides are arranged, and how they move. */
|
|
@@ -203,11 +220,14 @@ export interface CarouselProps extends ViewProps {
|
|
|
203
220
|
* wants; set it for a run that shows more than one at a time.
|
|
204
221
|
*/
|
|
205
222
|
itemSize?: number;
|
|
206
|
-
/** Advance on a timer. Stops
|
|
223
|
+
/** Advance on a timer. Stops at the non-looping end or after the first touch. */
|
|
207
224
|
autoplay?: boolean;
|
|
208
225
|
/** Milliseconds each slide is held when `autoplay` is set. */
|
|
209
226
|
autoplayInterval?: number;
|
|
210
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* Controlled active slide. Requests move visually only after this value changes;
|
|
229
|
+
* an index invalidated by a child-count change is normalized and reported.
|
|
230
|
+
*/
|
|
211
231
|
index?: number;
|
|
212
232
|
/** Starting slide when uncontrolled. */
|
|
213
233
|
defaultIndex?: number;
|
|
@@ -245,14 +265,15 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
245
265
|
) {
|
|
246
266
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
247
267
|
const [internalIndex, setInternalIndex] = useState(defaultIndex);
|
|
248
|
-
const [count,
|
|
268
|
+
const [count, setCountValue] = useState(0);
|
|
269
|
+
const [countKnown, setCountKnown] = useState(false);
|
|
249
270
|
const [touched, setTouched] = useState(false);
|
|
250
271
|
const reducedMotion = useReducedMotion();
|
|
251
272
|
|
|
252
273
|
const isControlled = indexProp !== undefined;
|
|
253
|
-
const
|
|
274
|
+
const requestedIndex = isControlled ? indexProp : internalIndex;
|
|
254
275
|
|
|
255
|
-
const progress = useSharedValue(defaultIndex);
|
|
276
|
+
const progress = useSharedValue(indexProp ?? defaultIndex);
|
|
256
277
|
const engaged = useSharedValue(0);
|
|
257
278
|
|
|
258
279
|
// A deck is dealt from the top of a pile, so it is dragged sideways whatever
|
|
@@ -261,6 +282,11 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
261
282
|
const along = axis === 'horizontal' ? size.width : size.height;
|
|
262
283
|
const itemSize = itemSizeProp ?? along ?? 0;
|
|
263
284
|
|
|
285
|
+
const setCount = useCallback((next: number) => {
|
|
286
|
+
setCountValue(next);
|
|
287
|
+
setCountKnown(true);
|
|
288
|
+
}, []);
|
|
289
|
+
|
|
264
290
|
const setIndex = useCallback(
|
|
265
291
|
(next: number) => {
|
|
266
292
|
if (!isControlled) setInternalIndex(next);
|
|
@@ -269,28 +295,68 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
269
295
|
[isControlled, onIndexChange]
|
|
270
296
|
);
|
|
271
297
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
298
|
+
/*
|
|
299
|
+
* The slide the run is currently travelling to.
|
|
300
|
+
*
|
|
301
|
+
* Moving the run and recording where it went are two steps: `scrollTo` starts
|
|
302
|
+
* the spring, then the new index arrives back through state and the lifecycle
|
|
303
|
+
* effect asks for it again. Without this the second ask restarts the spring a
|
|
304
|
+
* frame into the first — from a standstill, so a flick loses the momentum it
|
|
305
|
+
* was carrying. Remembering the target lets the echo be recognised and
|
|
306
|
+
* ignored, while a genuine request for the same slide still animates, because
|
|
307
|
+
* that one comes through `scrollTo` and sets this first.
|
|
308
|
+
*/
|
|
309
|
+
const animatedTarget = useRef<number | null>(null);
|
|
276
310
|
|
|
311
|
+
const animateTo = useCallback(
|
|
312
|
+
(target: number) => {
|
|
313
|
+
animatedTarget.current = target;
|
|
277
314
|
if (reducedMotion) {
|
|
278
|
-
progress.value =
|
|
315
|
+
progress.value = target;
|
|
279
316
|
} else if (loop) {
|
|
280
317
|
// Spring to the nearest representation of the target rather than to the
|
|
281
318
|
// target itself, so a wrap from the last slide to the first travels one
|
|
282
319
|
// step forward instead of winding all the way back through the run.
|
|
283
|
-
const shortest = progress.value + distance(
|
|
320
|
+
const shortest = progress.value + distance(target, progress.value, count, true);
|
|
284
321
|
progress.value = withSpring(shortest, SPRING, (finished) => {
|
|
285
322
|
if (finished) progress.value = wrap(progress.value, count);
|
|
286
323
|
});
|
|
287
324
|
} else {
|
|
288
|
-
progress.value = withSpring(
|
|
325
|
+
progress.value = withSpring(target, SPRING);
|
|
289
326
|
}
|
|
327
|
+
},
|
|
328
|
+
[count, loop, progress, reducedMotion]
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const settleIndex = useCallback(
|
|
332
|
+
(next: number) => {
|
|
333
|
+
if (animatedTarget.current === next) return;
|
|
334
|
+
if (Math.abs(progress.value - next) >= 0.001) animateTo(next);
|
|
335
|
+
},
|
|
336
|
+
[animateTo, progress]
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const index = useCarouselIndexLifecycle({
|
|
340
|
+
requestedIndex,
|
|
341
|
+
count,
|
|
342
|
+
countKnown,
|
|
343
|
+
loop,
|
|
344
|
+
onCorrection: setIndex,
|
|
345
|
+
onSettledIndex: settleIndex,
|
|
346
|
+
});
|
|
290
347
|
|
|
348
|
+
const scrollTo = useCallback(
|
|
349
|
+
(target: number) => {
|
|
350
|
+
if (count <= 0) return;
|
|
351
|
+
const settled = normalizeCarouselIndex(target, count, loop);
|
|
352
|
+
|
|
353
|
+
// A controlled request belongs to its owner. The finger may move the run,
|
|
354
|
+
// but after release it returns to the current prop until the owner accepts
|
|
355
|
+
// the request by changing that prop.
|
|
356
|
+
animateTo(isControlled ? index : settled);
|
|
291
357
|
setIndex(settled);
|
|
292
358
|
},
|
|
293
|
-
[count,
|
|
359
|
+
[animateTo, count, index, isControlled, loop, setIndex]
|
|
294
360
|
);
|
|
295
361
|
|
|
296
362
|
const next = useCallback(() => scrollTo(index + 1), [index, scrollTo]);
|
|
@@ -298,23 +364,19 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
298
364
|
|
|
299
365
|
useImperativeHandle(ref, () => ({ next, previous, scrollTo }), [next, previous, scrollTo]);
|
|
300
366
|
|
|
301
|
-
// A controlled index moving underneath us animates rather than jumps.
|
|
302
|
-
useEffect(() => {
|
|
303
|
-
if (!isControlled || indexProp === undefined || count <= 0) return;
|
|
304
|
-
if (Math.abs(progress.value - indexProp) < 0.001) return;
|
|
305
|
-
progress.value = reducedMotion ? indexProp : withSpring(indexProp, SPRING);
|
|
306
|
-
}, [isControlled, indexProp, count, progress, reducedMotion]);
|
|
307
|
-
|
|
308
367
|
/*
|
|
309
368
|
* Autoplay stops for good the first time a finger lands, rather than pausing.
|
|
310
369
|
* Someone who has taken hold of the run is reading it, and having it start
|
|
311
370
|
* moving again a few seconds later is the behaviour everybody hates.
|
|
312
371
|
*/
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
372
|
+
useCarouselAutoplay({
|
|
373
|
+
enabled: autoplay && !touched,
|
|
374
|
+
index,
|
|
375
|
+
count,
|
|
376
|
+
loop,
|
|
377
|
+
interval: autoplayInterval,
|
|
378
|
+
onAdvance: scrollTo,
|
|
379
|
+
});
|
|
318
380
|
|
|
319
381
|
const settle = useCallback(
|
|
320
382
|
(target: number) => {
|
|
@@ -438,13 +500,13 @@ export interface CarouselContentProps extends ViewProps {
|
|
|
438
500
|
*/
|
|
439
501
|
function CarouselContent({ className, children, ...props }: CarouselContentProps) {
|
|
440
502
|
const { setCount, variant, orientation, align } = useCarousel('Carousel.Content');
|
|
441
|
-
const slides =
|
|
503
|
+
const slides = renderableChildren(children);
|
|
442
504
|
const horizontal = orientation === 'horizontal';
|
|
443
505
|
const centred = align === 'center' || variant === 'coverflow' || variant === 'stack';
|
|
444
506
|
|
|
445
507
|
useEffect(() => {
|
|
446
|
-
setCount(slides);
|
|
447
|
-
}, [setCount, slides]);
|
|
508
|
+
setCount(slides.length);
|
|
509
|
+
}, [setCount, slides.length]);
|
|
448
510
|
|
|
449
511
|
return (
|
|
450
512
|
<View
|
|
@@ -465,8 +527,10 @@ function CarouselContent({ className, children, ...props }: CarouselContentProps
|
|
|
465
527
|
props.style,
|
|
466
528
|
]}
|
|
467
529
|
>
|
|
468
|
-
{
|
|
469
|
-
<ItemIndexContext.Provider
|
|
530
|
+
{slides.map((child, index) => (
|
|
531
|
+
<ItemIndexContext.Provider key={renderableChildKey(child, index)} value={index}>
|
|
532
|
+
{child}
|
|
533
|
+
</ItemIndexContext.Provider>
|
|
470
534
|
))}
|
|
471
535
|
</View>
|
|
472
536
|
);
|
|
@@ -688,16 +752,30 @@ function CarouselDots({
|
|
|
688
752
|
key={dot}
|
|
689
753
|
disabled={!interactive}
|
|
690
754
|
onPress={() => scrollTo(dot)}
|
|
691
|
-
hitSlop={6}
|
|
692
755
|
accessibilityRole="tab"
|
|
693
756
|
accessibilityLabel={`Slide ${dot + 1} of ${count}`}
|
|
694
757
|
accessibilityState={{ selected: active }}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
758
|
+
// 24, not the 48 the buttons get. Dots sit a few points apart, so
|
|
759
|
+
// a 48 box would either overlap its neighbours — making a tap near
|
|
760
|
+
// the join land on the wrong slide — or push a five-slide run out
|
|
761
|
+
// to the width of the screen. 24 clears the minimum with the pitch
|
|
762
|
+
// still wider than the target, so no two dots contend for a touch.
|
|
763
|
+
className={cn('items-center justify-center', interactive && 'h-6 w-6')}
|
|
764
|
+
>
|
|
765
|
+
<View
|
|
766
|
+
className={cn(
|
|
767
|
+
'rounded-full',
|
|
768
|
+
horizontal
|
|
769
|
+
? active
|
|
770
|
+
? 'h-1 w-4'
|
|
771
|
+
: 'h-1 w-1'
|
|
772
|
+
: active
|
|
773
|
+
? 'h-4 w-1'
|
|
774
|
+
: 'h-1 w-1',
|
|
775
|
+
active ? 'bg-foreground' : 'bg-foreground/30'
|
|
776
|
+
)}
|
|
777
|
+
/>
|
|
778
|
+
</Pressable>
|
|
701
779
|
);
|
|
702
780
|
})}
|
|
703
781
|
</View>
|
|
@@ -723,12 +801,11 @@ function makeArrow(direction: 'previous' | 'next') {
|
|
|
723
801
|
<Pressable
|
|
724
802
|
disabled={disabled}
|
|
725
803
|
onPress={direction === 'next' ? next : previous}
|
|
726
|
-
hitSlop={8}
|
|
727
804
|
accessibilityRole="button"
|
|
728
805
|
accessibilityLabel={direction === 'next' ? 'Next slide' : 'Previous slide'}
|
|
729
806
|
accessibilityState={{ disabled }}
|
|
730
807
|
className={cn(
|
|
731
|
-
'items-center justify-center rounded-full
|
|
808
|
+
'h-12 w-12 items-center justify-center rounded-full',
|
|
732
809
|
disabled ? 'opacity-30' : 'active:bg-foreground/10',
|
|
733
810
|
className
|
|
734
811
|
)}
|
|
@@ -13,7 +13,7 @@ import { Text } from '../../primitives/text';
|
|
|
13
13
|
|
|
14
14
|
const checkboxVariants = tv({
|
|
15
15
|
slots: {
|
|
16
|
-
row: 'flex-row items-center gap-2.5',
|
|
16
|
+
row: 'min-h-12 min-w-12 flex-row items-center gap-2.5',
|
|
17
17
|
box: 'h-5 w-5 items-center justify-center rounded-md border border-input bg-background',
|
|
18
18
|
fill: 'absolute inset-0 items-center justify-center rounded-md bg-primary',
|
|
19
19
|
content: 'flex-1 gap-0.5',
|
|
@@ -130,7 +130,6 @@ export const Checkbox = forwardRef<View, CheckboxProps>(
|
|
|
130
130
|
disabled={disabled}
|
|
131
131
|
// An indeterminate box resolves upward: the press turns the group on.
|
|
132
132
|
onPress={() => onCheckedChange?.(indeterminate ? true : !checked)}
|
|
133
|
-
hitSlop={8}
|
|
134
133
|
// A description makes the row two lines tall — align the box to the
|
|
135
134
|
// label rather than centring it against the whole block.
|
|
136
135
|
className={slots.row({
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type ContextMenuInvocation = 'menu' | 'press';
|
|
2
|
+
|
|
3
|
+
export interface ContextMenuKey {
|
|
4
|
+
key: string;
|
|
5
|
+
shiftKey?: boolean;
|
|
6
|
+
altKey?: boolean;
|
|
7
|
+
ctrlKey?: boolean;
|
|
8
|
+
metaKey?: boolean;
|
|
9
|
+
repeat?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The cross-platform subset ContextMenu consumes from a key-down event. */
|
|
13
|
+
export interface ContextMenuKeyDownEvent {
|
|
14
|
+
nativeEvent: ContextMenuKey;
|
|
15
|
+
preventDefault: () => void;
|
|
16
|
+
isDefaultPrevented: () => boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Resolve a screen-reader/switch action to the same paths touch users have. */
|
|
20
|
+
export function contextMenuAccessibilityInvocation(
|
|
21
|
+
actionName: string,
|
|
22
|
+
hasPrimaryAction: boolean,
|
|
23
|
+
disabled: boolean
|
|
24
|
+
): ContextMenuInvocation | undefined {
|
|
25
|
+
if (disabled) return undefined;
|
|
26
|
+
if (actionName === 'showMenu') return 'menu';
|
|
27
|
+
if (actionName === 'activate') return hasPrimaryAction ? 'press' : 'menu';
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Resolve web/hardware keys without stealing modified shortcuts or repeats. */
|
|
32
|
+
export function contextMenuKeyInvocation(
|
|
33
|
+
event: ContextMenuKey,
|
|
34
|
+
hasPrimaryAction: boolean,
|
|
35
|
+
disabled: boolean
|
|
36
|
+
): ContextMenuInvocation | undefined {
|
|
37
|
+
if (disabled || event.repeat) return undefined;
|
|
38
|
+
if (event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey)) return 'menu';
|
|
39
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return undefined;
|
|
40
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
41
|
+
return hasPrimaryAction ? 'press' : 'menu';
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|