panelui-native 0.65.0 → 0.68.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 +21 -1
- package/lib/module/components/area-chart/index.js +18 -1
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +18 -1
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/candlestick-chart/index.js +18 -1
- package/lib/module/components/candlestick-chart/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 +61 -24
- package/lib/module/components/carousel/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.map +1 -1
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/flow/flow-accessibility.js +18 -2
- package/lib/module/components/flow/flow-accessibility.js.map +1 -1
- 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 +12 -18
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/form/index.js +7 -5
- package/lib/module/components/form/index.js.map +1 -1
- package/lib/module/components/form/typed-form.js +11 -0
- package/lib/module/components/form/typed-form.js.map +1 -0
- package/lib/module/components/form/use-form.js +4 -0
- package/lib/module/components/form/use-form.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +27 -1
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +18 -1
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +187 -17
- package/lib/module/components/message-scroller/index.js.map +1 -1
- package/lib/module/components/message-scroller/message-scroller-math.js +43 -0
- package/lib/module/components/message-scroller/message-scroller-math.js.map +1 -0
- package/lib/module/components/meter/index.js +250 -0
- package/lib/module/components/meter/index.js.map +1 -0
- package/lib/module/components/meter/meter-scale.js +88 -0
- package/lib/module/components/meter/meter-scale.js.map +1 -0
- package/lib/module/components/planner/index.js +714 -0
- package/lib/module/components/planner/index.js.map +1 -0
- package/lib/module/components/planner/planner-entries.js +124 -0
- package/lib/module/components/planner/planner-entries.js.map +1 -0
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/radar-chart/index.js +18 -1
- package/lib/module/components/radar-chart/index.js.map +1 -1
- package/lib/module/components/scatter-chart/index.js +18 -1
- package/lib/module/components/scatter-chart/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/skeleton/index.js +45 -7
- package/lib/module/components/skeleton/index.js.map +1 -1
- package/lib/module/components/slider/index.js +22 -18
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/spinner/index.js +63 -14
- package/lib/module/components/spinner/index.js.map +1 -1
- package/lib/module/components/time-picker/index.js +46 -21
- package/lib/module/components/time-picker/index.js.map +1 -1
- package/lib/module/components/time-picker/ruler-window.js +25 -0
- package/lib/module/components/time-picker/ruler-window.js.map +1 -0
- package/lib/module/hooks/breakpoint-contract.js +29 -0
- package/lib/module/hooks/breakpoint-contract.js.map +1 -0
- package/lib/module/hooks/index.js +1 -1
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-breakpoint.js +46 -1
- package/lib/module/hooks/use-breakpoint.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/chart-accessibility.js +68 -0
- package/lib/module/primitives/chart-accessibility.js.map +1 -0
- package/lib/module/primitives/scrim.js +94 -3
- package/lib/module/primitives/scrim.js.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts +2 -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/candlestick-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/candlestick-chart/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/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/flow/flow-accessibility.d.ts +8 -0
- package/lib/typescript/src/components/flow/flow-accessibility.d.ts.map +1 -1
- 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 +6 -5
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
- package/lib/typescript/src/components/form/index.d.ts +18 -5
- package/lib/typescript/src/components/form/index.d.ts.map +1 -1
- package/lib/typescript/src/components/form/typed-form.d.ts +7 -0
- package/lib/typescript/src/components/form/typed-form.d.ts.map +1 -0
- package/lib/typescript/src/components/form/use-form.d.ts +4 -0
- package/lib/typescript/src/components/form/use-form.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts +30 -2
- 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 +18 -0
- package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts.map +1 -0
- package/lib/typescript/src/components/meter/index.d.ts +270 -0
- package/lib/typescript/src/components/meter/index.d.ts.map +1 -0
- package/lib/typescript/src/components/meter/meter-scale.d.ts +57 -0
- package/lib/typescript/src/components/meter/meter-scale.d.ts.map +1 -0
- package/lib/typescript/src/components/planner/index.d.ts +274 -0
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -0
- package/lib/typescript/src/components/planner/planner-entries.d.ts +75 -0
- package/lib/typescript/src/components/planner/planner-entries.d.ts.map +1 -0
- 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/radar-chart/index.d.ts +4 -0
- package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/scatter-chart/index.d.ts +2 -1
- package/lib/typescript/src/components/scatter-chart/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/skeleton/index.d.ts +25 -2
- package/lib/typescript/src/components/skeleton/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/spinner/index.d.ts +25 -2
- package/lib/typescript/src/components/spinner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/index.d.ts +5 -6
- package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/ruler-window.d.ts +12 -0
- package/lib/typescript/src/components/time-picker/ruler-window.d.ts.map +1 -0
- package/lib/typescript/src/hooks/breakpoint-contract.d.ts +6 -0
- package/lib/typescript/src/hooks/breakpoint-contract.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +1 -1
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-breakpoint.d.ts +22 -0
- package/lib/typescript/src/hooks/use-breakpoint.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/chart-accessibility.d.ts +33 -0
- package/lib/typescript/src/primitives/chart-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/primitives/scrim.d.ts +5 -3
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
- package/lib/typescript/types/form-api.type-test.d.ts +2 -0
- package/lib/typescript/types/form-api.type-test.d.ts.map +1 -0
- package/package.json +34 -5
- package/src/components/area-chart/index.tsx +27 -2
- package/src/components/bar-chart/index.tsx +27 -2
- package/src/components/bottom-sheet/index.tsx +3 -0
- package/src/components/candlestick-chart/index.tsx +30 -2
- package/src/components/carousel/carousel-lifecycle.ts +74 -0
- package/src/components/carousel/index.tsx +80 -25
- 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 +3 -0
- package/src/components/drawer/index.tsx +3 -0
- package/src/components/flow/flow-accessibility.ts +16 -2
- package/src/components/flow/flow-identifiers.ts +98 -0
- package/src/components/flow/index.tsx +29 -19
- package/src/components/form/index.tsx +32 -5
- package/src/components/form/typed-form.ts +13 -0
- package/src/components/form/use-form.ts +9 -0
- package/src/components/heatmap-chart/index.tsx +49 -3
- package/src/components/line-chart/index.tsx +27 -2
- package/src/components/message-scroller/index.tsx +273 -13
- package/src/components/message-scroller/message-scroller-math.ts +62 -0
- package/src/components/meter/index.tsx +318 -0
- package/src/components/meter/meter-scale.ts +109 -0
- package/src/components/planner/index.tsx +886 -0
- package/src/components/planner/planner-entries.ts +146 -0
- package/src/components/popover/index.tsx +3 -0
- package/src/components/radar-chart/index.tsx +24 -0
- package/src/components/scatter-chart/index.tsx +27 -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/skeleton/index.tsx +59 -5
- package/src/components/slider/index.tsx +28 -18
- package/src/components/spinner/index.tsx +80 -13
- package/src/components/time-picker/index.tsx +57 -29
- package/src/components/time-picker/ruler-window.ts +24 -0
- package/src/hooks/breakpoint-contract.ts +31 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-breakpoint.ts +61 -10
- package/src/index.ts +32 -0
- package/src/primitives/chart-accessibility.ts +102 -0
- package/src/primitives/scrim.tsx +127 -7
|
@@ -70,6 +70,7 @@ import Animated, {
|
|
|
70
70
|
import Svg, { G, Line as SvgLine, Path } from 'react-native-svg';
|
|
71
71
|
import { useCSSVariable } from 'uniwind';
|
|
72
72
|
import { Text } from '../../primitives/text';
|
|
73
|
+
import { ChartAccessibilityData, type ChartAccessibilityProps } from '../../primitives/chart-accessibility';
|
|
73
74
|
import { bandOf, compactNumber, yOf, type Plot } from '../../utils/chart';
|
|
74
75
|
import { cn } from '../../utils/cn';
|
|
75
76
|
|
|
@@ -234,7 +235,7 @@ function ohlcOf(
|
|
|
234
235
|
return { open, high, low, close };
|
|
235
236
|
}
|
|
236
237
|
|
|
237
|
-
export interface CandlestickChartProps extends ViewProps {
|
|
238
|
+
export interface CandlestickChartProps extends ViewProps, ChartAccessibilityProps<CandlestickChartDatum> {
|
|
238
239
|
className?: string;
|
|
239
240
|
/** The rows. Each one is a period along the x-axis. */
|
|
240
241
|
data: CandlestickChartDatum[];
|
|
@@ -333,6 +334,11 @@ const CandlestickChartRoot = forwardRef<CandlestickChartHandle, CandlestickChart
|
|
|
333
334
|
candleWidth,
|
|
334
335
|
fadedOpacity = 0.3,
|
|
335
336
|
onActiveIndexChange,
|
|
337
|
+
accessible,
|
|
338
|
+
accessibilityLabel,
|
|
339
|
+
accessibilityHint,
|
|
340
|
+
accessibilityLabelForDatum,
|
|
341
|
+
onAccessibilityDatumPress,
|
|
336
342
|
compact = false,
|
|
337
343
|
children,
|
|
338
344
|
...props
|
|
@@ -532,7 +538,29 @@ const CandlestickChartRoot = forwardRef<CandlestickChartHandle, CandlestickChart
|
|
|
532
538
|
<CandlestickChartContext.Provider value={context}>
|
|
533
539
|
<View {...props} style={props.style} className={cn('w-full', className)}>
|
|
534
540
|
{header}
|
|
535
|
-
<
|
|
541
|
+
<ChartAccessibilityData
|
|
542
|
+
chart="Candlestick chart"
|
|
543
|
+
data={data}
|
|
544
|
+
disabled={accessible === false || loading}
|
|
545
|
+
accessibilityLabel={accessibilityLabel}
|
|
546
|
+
accessibilityHint={accessibilityHint}
|
|
547
|
+
accessibilityLabelForDatum={accessibilityLabelForDatum}
|
|
548
|
+
onAccessibilityDatumPress={onAccessibilityDatumPress}
|
|
549
|
+
valueOf={(datum) => [
|
|
550
|
+
[xDataKey, datum[xDataKey]],
|
|
551
|
+
[openDataKey, datum[openDataKey]],
|
|
552
|
+
[highDataKey, datum[highDataKey]],
|
|
553
|
+
[lowDataKey, datum[lowDataKey]],
|
|
554
|
+
[closeDataKey, datum[closeDataKey]],
|
|
555
|
+
]}
|
|
556
|
+
/>
|
|
557
|
+
<View
|
|
558
|
+
onLayout={onLayout}
|
|
559
|
+
style={{ aspectRatio }}
|
|
560
|
+
className="w-full"
|
|
561
|
+
accessibilityElementsHidden
|
|
562
|
+
importantForAccessibility="no-hide-descendants"
|
|
563
|
+
>
|
|
536
564
|
{plot.width > 0 ? (
|
|
537
565
|
<>
|
|
538
566
|
<Svg width="100%" height="100%" style={StyleSheet.absoluteFill}>
|
|
@@ -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,8 +295,22 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
269
295
|
[isControlled, onIndexChange]
|
|
270
296
|
);
|
|
271
297
|
|
|
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);
|
|
310
|
+
|
|
272
311
|
const animateTo = useCallback(
|
|
273
312
|
(target: number) => {
|
|
313
|
+
animatedTarget.current = target;
|
|
274
314
|
if (reducedMotion) {
|
|
275
315
|
progress.value = target;
|
|
276
316
|
} else if (loop) {
|
|
@@ -288,10 +328,27 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
288
328
|
[count, loop, progress, reducedMotion]
|
|
289
329
|
);
|
|
290
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
|
+
});
|
|
347
|
+
|
|
291
348
|
const scrollTo = useCallback(
|
|
292
349
|
(target: number) => {
|
|
293
350
|
if (count <= 0) return;
|
|
294
|
-
const settled =
|
|
351
|
+
const settled = normalizeCarouselIndex(target, count, loop);
|
|
295
352
|
|
|
296
353
|
// A controlled request belongs to its owner. The finger may move the run,
|
|
297
354
|
// but after release it returns to the current prop until the owner accepts
|
|
@@ -307,23 +364,19 @@ const CarouselRoot = forwardRef<CarouselHandle, CarouselProps>(function Carousel
|
|
|
307
364
|
|
|
308
365
|
useImperativeHandle(ref, () => ({ next, previous, scrollTo }), [next, previous, scrollTo]);
|
|
309
366
|
|
|
310
|
-
// A controlled index moving underneath us animates rather than jumps.
|
|
311
|
-
useEffect(() => {
|
|
312
|
-
if (!isControlled || indexProp === undefined || count <= 0) return;
|
|
313
|
-
if (Math.abs(progress.value - indexProp) < 0.001) return;
|
|
314
|
-
animateTo(indexProp);
|
|
315
|
-
}, [animateTo, isControlled, indexProp, count, progress]);
|
|
316
|
-
|
|
317
367
|
/*
|
|
318
368
|
* Autoplay stops for good the first time a finger lands, rather than pausing.
|
|
319
369
|
* Someone who has taken hold of the run is reading it, and having it start
|
|
320
370
|
* moving again a few seconds later is the behaviour everybody hates.
|
|
321
371
|
*/
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
372
|
+
useCarouselAutoplay({
|
|
373
|
+
enabled: autoplay && !touched,
|
|
374
|
+
index,
|
|
375
|
+
count,
|
|
376
|
+
loop,
|
|
377
|
+
interval: autoplayInterval,
|
|
378
|
+
onAdvance: scrollTo,
|
|
379
|
+
});
|
|
327
380
|
|
|
328
381
|
const settle = useCallback(
|
|
329
382
|
(target: number) => {
|
|
@@ -447,13 +500,13 @@ export interface CarouselContentProps extends ViewProps {
|
|
|
447
500
|
*/
|
|
448
501
|
function CarouselContent({ className, children, ...props }: CarouselContentProps) {
|
|
449
502
|
const { setCount, variant, orientation, align } = useCarousel('Carousel.Content');
|
|
450
|
-
const slides =
|
|
503
|
+
const slides = renderableChildren(children);
|
|
451
504
|
const horizontal = orientation === 'horizontal';
|
|
452
505
|
const centred = align === 'center' || variant === 'coverflow' || variant === 'stack';
|
|
453
506
|
|
|
454
507
|
useEffect(() => {
|
|
455
|
-
setCount(slides);
|
|
456
|
-
}, [setCount, slides]);
|
|
508
|
+
setCount(slides.length);
|
|
509
|
+
}, [setCount, slides.length]);
|
|
457
510
|
|
|
458
511
|
return (
|
|
459
512
|
<View
|
|
@@ -474,8 +527,10 @@ function CarouselContent({ className, children, ...props }: CarouselContentProps
|
|
|
474
527
|
props.style,
|
|
475
528
|
]}
|
|
476
529
|
>
|
|
477
|
-
{
|
|
478
|
-
<ItemIndexContext.Provider
|
|
530
|
+
{slides.map((child, index) => (
|
|
531
|
+
<ItemIndexContext.Provider key={renderableChildKey(child, index)} value={index}>
|
|
532
|
+
{child}
|
|
533
|
+
</ItemIndexContext.Provider>
|
|
479
534
|
))}
|
|
480
535
|
</View>
|
|
481
536
|
);
|
|
@@ -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
|
+
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* A `Menu` hangs off a control that exists to be pressed: a ⋯ button, a toolbar
|
|
5
5
|
* item, something whose whole job is to open the menu. A context menu has no
|
|
6
6
|
* such control. The target is the content itself — a message, a note, a photo,
|
|
7
|
-
* a row — and the actions are reached by
|
|
7
|
+
* a row — and the actions are reached by holding it, by a named accessibility
|
|
8
|
+
* action, or from the keyboard.
|
|
8
9
|
*
|
|
9
10
|
* ```tsx
|
|
10
11
|
* <ContextMenu>
|
|
@@ -31,9 +32,9 @@
|
|
|
31
32
|
* them. The panel is `Menu`'s panel, which is `Popover`'s, so `presentation`,
|
|
32
33
|
* submenus and edge-flipping all arrive already working.
|
|
33
34
|
*
|
|
34
|
-
* What this component owns is
|
|
35
|
-
*
|
|
36
|
-
* goes.
|
|
35
|
+
* What this component owns is what a menu opened on content needs and a menu
|
|
36
|
+
* opened from a button does not: alternate invocation paths, and where the
|
|
37
|
+
* panel goes.
|
|
37
38
|
*
|
|
38
39
|
* ## Anchored to the finger, not to the target
|
|
39
40
|
*
|
|
@@ -73,7 +74,11 @@ import {
|
|
|
73
74
|
type ReactElement,
|
|
74
75
|
type ReactNode,
|
|
75
76
|
} from 'react';
|
|
76
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
View,
|
|
79
|
+
type AccessibilityActionInfo,
|
|
80
|
+
type ViewProps,
|
|
81
|
+
} from 'react-native';
|
|
77
82
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
78
83
|
import Animated, {
|
|
79
84
|
FadeOut,
|
|
@@ -95,6 +100,12 @@ import {
|
|
|
95
100
|
import { usePopoverAnchor } from '../popover';
|
|
96
101
|
import { cn } from '../../utils/cn';
|
|
97
102
|
import { selectionTick } from '../../utils/haptics';
|
|
103
|
+
import {
|
|
104
|
+
contextMenuAccessibilityInvocation,
|
|
105
|
+
contextMenuKeyInvocation,
|
|
106
|
+
type ContextMenuInvocation,
|
|
107
|
+
type ContextMenuKeyDownEvent,
|
|
108
|
+
} from './context-menu-invocation';
|
|
98
109
|
|
|
99
110
|
/**
|
|
100
111
|
* How long the target is held before the menu opens.
|
|
@@ -260,7 +271,7 @@ function ContextMenuRoot({ children, ...props }: ContextMenuProps) {
|
|
|
260
271
|
/** Which rectangle the panel is placed against. */
|
|
261
272
|
export type ContextMenuAnchor = 'point' | 'target';
|
|
262
273
|
|
|
263
|
-
export interface ContextMenuTriggerProps extends Omit<ViewProps, 'children'> {
|
|
274
|
+
export interface ContextMenuTriggerProps extends Omit<ViewProps, 'children' | 'onKeyDown'> {
|
|
264
275
|
/**
|
|
265
276
|
* Classes on the wrapper the content sits in, which lays out like any other
|
|
266
277
|
* view — it does not shrink to its child, because the things held are usually
|
|
@@ -280,7 +291,8 @@ export interface ContextMenuTriggerProps extends Omit<ViewProps, 'children'> {
|
|
|
280
291
|
* Point is the default because a context menu's target is usually large, and
|
|
281
292
|
* the middle of a whole message is not where the press was. Reach for
|
|
282
293
|
* `target` when the target is small and list-shaped and the panel should read
|
|
283
|
-
* as lining up with it.
|
|
294
|
+
* as lining up with it. Keyboard and accessibility opens always use the target
|
|
295
|
+
* bounds, because those modalities have no pointer coordinate.
|
|
284
296
|
*/
|
|
285
297
|
anchor?: ContextMenuAnchor;
|
|
286
298
|
/** How long the hold has to last, in milliseconds. 350 by default. */
|
|
@@ -310,10 +322,17 @@ export interface ContextMenuTriggerProps extends Omit<ViewProps, 'children'> {
|
|
|
310
322
|
haptics?: boolean;
|
|
311
323
|
/** Nothing opens the menu, and the short press stops firing too. */
|
|
312
324
|
disabled?: boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Called first for keyboard events. Prevent the event to keep ContextMenu
|
|
327
|
+
* from handling it. Context Menu and Shift+F10 open the menu; Enter and Space
|
|
328
|
+
* mirror the trigger's accessible activation.
|
|
329
|
+
*/
|
|
330
|
+
onKeyDown?: (event: ContextMenuKeyDownEvent) => void;
|
|
313
331
|
}
|
|
314
332
|
|
|
315
333
|
/**
|
|
316
|
-
* Wraps the content and opens the menu when
|
|
334
|
+
* Wraps the content and opens the menu when held, through accessibility
|
|
335
|
+
* actions, or from the keyboard.
|
|
317
336
|
*
|
|
318
337
|
* The wrapper is a plain view and lays out like one, stretching as a view does
|
|
319
338
|
* rather than shrinking to its child. That is the opposite of what a tooltip's
|
|
@@ -334,6 +353,14 @@ function ContextMenuTrigger({
|
|
|
334
353
|
onPress,
|
|
335
354
|
haptics = false,
|
|
336
355
|
disabled = false,
|
|
356
|
+
accessible,
|
|
357
|
+
accessibilityActions,
|
|
358
|
+
onAccessibilityAction,
|
|
359
|
+
accessibilityRole,
|
|
360
|
+
accessibilityState,
|
|
361
|
+
focusable,
|
|
362
|
+
tabIndex,
|
|
363
|
+
onKeyDown,
|
|
337
364
|
...props
|
|
338
365
|
}: ContextMenuTriggerProps) {
|
|
339
366
|
const { setOpen, anchorTo } = usePopoverAnchor('ContextMenu.Trigger');
|
|
@@ -359,8 +386,8 @@ function ContextMenuTrigger({
|
|
|
359
386
|
* The target is measured either way, because the preview draws at that rect
|
|
360
387
|
* whatever the panel is placed against.
|
|
361
388
|
*/
|
|
362
|
-
const
|
|
363
|
-
(x: number
|
|
389
|
+
const open = useCallback(
|
|
390
|
+
(point?: { x: number; y: number }) => {
|
|
364
391
|
// Ticked here rather than in the gesture callback so it fires once the
|
|
365
392
|
// hold has been accepted, which is the moment there is something to
|
|
366
393
|
// confirm — and on the same side of the bridge as the opening.
|
|
@@ -382,8 +409,8 @@ function ContextMenuTrigger({
|
|
|
382
409
|
*/
|
|
383
410
|
const grown = hasPreview ? (PREVIEW_SCALE - 1) / 2 : 0;
|
|
384
411
|
anchorTo(
|
|
385
|
-
against === 'point'
|
|
386
|
-
? {
|
|
412
|
+
against === 'point' && point
|
|
413
|
+
? { ...point, width: 0, height: 0 }
|
|
387
414
|
: {
|
|
388
415
|
x: mx - width * grown,
|
|
389
416
|
y: my - height * grown,
|
|
@@ -397,6 +424,63 @@ function ContextMenuTrigger({
|
|
|
397
424
|
[against, anchorTo, setOpen, haptics, setTarget, setContent, children, hasPreview]
|
|
398
425
|
);
|
|
399
426
|
|
|
427
|
+
const openAt = useCallback((x: number, y: number) => open({ x, y }), [open]);
|
|
428
|
+
// Keyboard and accessibility actions have no pointer coordinate to honour.
|
|
429
|
+
// The measured target is the only real location they can anchor against.
|
|
430
|
+
const openFromTarget = useCallback(() => open(), [open]);
|
|
431
|
+
|
|
432
|
+
const invoke = useCallback(
|
|
433
|
+
(invocation: ContextMenuInvocation) => {
|
|
434
|
+
if (invocation === 'menu') openFromTarget();
|
|
435
|
+
else onPress?.();
|
|
436
|
+
},
|
|
437
|
+
[onPress, openFromTarget]
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const triggerActions = useMemo<readonly AccessibilityActionInfo[] | undefined>(() => {
|
|
441
|
+
if (disabled) return undefined;
|
|
442
|
+
const reserved = new Set(['activate', 'showMenu']);
|
|
443
|
+
return [
|
|
444
|
+
{ name: 'activate' },
|
|
445
|
+
{ name: 'showMenu', label: 'Show menu' },
|
|
446
|
+
...(accessibilityActions ?? []).filter(({ name }) => !reserved.has(name)),
|
|
447
|
+
];
|
|
448
|
+
}, [accessibilityActions, disabled]);
|
|
449
|
+
|
|
450
|
+
const handleAccessibilityAction = useCallback<
|
|
451
|
+
NonNullable<ViewProps['onAccessibilityAction']>
|
|
452
|
+
>(
|
|
453
|
+
(event) => {
|
|
454
|
+
const actionName = event.nativeEvent.actionName;
|
|
455
|
+
const invocation = contextMenuAccessibilityInvocation(actionName, !!onPress, disabled);
|
|
456
|
+
if (invocation) {
|
|
457
|
+
invoke(invocation);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
// Reserved actions cannot bypass a disabled trigger through a consumer
|
|
461
|
+
// handler, while unrelated custom actions still compose normally.
|
|
462
|
+
if (actionName === 'activate' || actionName === 'showMenu') return;
|
|
463
|
+
onAccessibilityAction?.(event);
|
|
464
|
+
},
|
|
465
|
+
[disabled, invoke, onAccessibilityAction, onPress]
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const handleKeyDown = useCallback(
|
|
469
|
+
(event: ContextMenuKeyDownEvent) => {
|
|
470
|
+
onKeyDown?.(event);
|
|
471
|
+
if (event.isDefaultPrevented()) return;
|
|
472
|
+
const invocation = contextMenuKeyInvocation(event.nativeEvent, !!onPress, disabled);
|
|
473
|
+
if (!invocation) return;
|
|
474
|
+
event.preventDefault();
|
|
475
|
+
invoke(invocation);
|
|
476
|
+
},
|
|
477
|
+
[disabled, invoke, onKeyDown, onPress]
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
// React Native exposes key events at runtime (and in its generated types),
|
|
481
|
+
// while its compatibility ViewProps declaration does not yet list them.
|
|
482
|
+
const keyboardProps = { onKeyDown: handleKeyDown } as ViewProps;
|
|
483
|
+
|
|
400
484
|
const gesture = useMemo(() => {
|
|
401
485
|
/*
|
|
402
486
|
* The hold and the tap are given to the recogniser as alternatives, so it
|
|
@@ -432,7 +516,23 @@ function ContextMenuTrigger({
|
|
|
432
516
|
a view that only groups children is otherwise flattened away — and a
|
|
433
517
|
flattened view cannot be measured, which `anchor="target"` needs.
|
|
434
518
|
*/}
|
|
435
|
-
<View
|
|
519
|
+
<View
|
|
520
|
+
ref={ref}
|
|
521
|
+
collapsable={false}
|
|
522
|
+
accessible={accessible ?? true}
|
|
523
|
+
accessibilityRole={accessibilityRole ?? 'button'}
|
|
524
|
+
accessibilityState={{
|
|
525
|
+
...accessibilityState,
|
|
526
|
+
disabled: disabled || accessibilityState?.disabled,
|
|
527
|
+
}}
|
|
528
|
+
accessibilityActions={triggerActions}
|
|
529
|
+
onAccessibilityAction={handleAccessibilityAction}
|
|
530
|
+
focusable={focusable ?? !disabled}
|
|
531
|
+
tabIndex={tabIndex ?? (disabled ? -1 : 0)}
|
|
532
|
+
className={className}
|
|
533
|
+
{...keyboardProps}
|
|
534
|
+
{...props}
|
|
535
|
+
>
|
|
436
536
|
{children}
|
|
437
537
|
</View>
|
|
438
538
|
</GestureDetector>
|
|
@@ -89,6 +89,9 @@ export interface DialogContentProps extends ViewProps {
|
|
|
89
89
|
* Frost the screen behind the dialog instead of dimming it. Uses `expo-blur`
|
|
90
90
|
* when installed and falls back to the dim when it is not, so it is safe to
|
|
91
91
|
* pass either way.
|
|
92
|
+
*
|
|
93
|
+
* Someone who has Reduce Transparency switched on gets an opaque
|
|
94
|
+
* backdrop instead, which is the whole point of the setting.
|
|
92
95
|
*/
|
|
93
96
|
blur?: boolean;
|
|
94
97
|
children?: ReactNode;
|
|
@@ -285,6 +285,9 @@ export interface DrawerContentProps extends ViewProps {
|
|
|
285
285
|
/**
|
|
286
286
|
* Frost the screen behind the drawer instead of dimming it. Needs the
|
|
287
287
|
* optional `expo-blur`; without it this dims, rather than failing.
|
|
288
|
+
*
|
|
289
|
+
* Someone who has Reduce Transparency switched on gets an opaque
|
|
290
|
+
* backdrop instead, which is the whole point of the setting.
|
|
288
291
|
*/
|
|
289
292
|
blur?: boolean;
|
|
290
293
|
children?: ReactNode;
|
|
@@ -29,6 +29,14 @@ export interface FlowConnectionAction {
|
|
|
29
29
|
connection: FlowConnection;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** Opaque, collision-free name for the native accessibility action map. */
|
|
33
|
+
export function flowConnectionActionName(
|
|
34
|
+
source: { node: string; handle?: string },
|
|
35
|
+
target: { node: string; handle?: string }
|
|
36
|
+
): string {
|
|
37
|
+
return `flow-connect:${JSON.stringify([source, target])}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
/** Keep pointer and assistive-technology movement inside the same group bounds. */
|
|
33
41
|
export function clampNodePosition(
|
|
34
42
|
rect: FlowRect,
|
|
@@ -80,7 +88,10 @@ export function getFlowConnectionActions(
|
|
|
80
88
|
|
|
81
89
|
if (targets.length === 0) {
|
|
82
90
|
actions.push({
|
|
83
|
-
name:
|
|
91
|
+
name: flowConnectionActionName(
|
|
92
|
+
{ node: source.node, handle: source.id },
|
|
93
|
+
{ node: targetNode.id }
|
|
94
|
+
),
|
|
84
95
|
label: `Connect ${source.label} to ${targetNode.label}`,
|
|
85
96
|
connection: {
|
|
86
97
|
source: sourceNode,
|
|
@@ -93,7 +104,10 @@ export function getFlowConnectionActions(
|
|
|
93
104
|
|
|
94
105
|
for (const target of targets) {
|
|
95
106
|
actions.push({
|
|
96
|
-
name:
|
|
107
|
+
name: flowConnectionActionName(
|
|
108
|
+
{ node: source.node, handle: source.id },
|
|
109
|
+
{ node: target.node, handle: target.id }
|
|
110
|
+
),
|
|
97
111
|
label: `Connect ${source.label} to ${targetNode.label}, ${target.label}`,
|
|
98
112
|
connection: {
|
|
99
113
|
source: sourceNode,
|