panelui-native 0.40.0 → 0.43.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 +4 -2
- package/lib/module/components/card/index.js +172 -3
- package/lib/module/components/card/index.js.map +1 -1
- package/lib/module/components/color-picker/index.js +1395 -0
- package/lib/module/components/color-picker/index.js.map +1 -0
- package/lib/module/components/combobox/index.js +87 -19
- package/lib/module/components/combobox/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +13 -4
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/kpi-chart/index.js +648 -0
- package/lib/module/components/kpi-chart/index.js.map +1 -0
- package/lib/module/components/map/index.js +72 -12
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/menu/index.js +40 -10
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/pagination/index.js +19 -9
- package/lib/module/components/pagination/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +68 -18
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/radar-chart/index.js +704 -0
- package/lib/module/components/radar-chart/index.js.map +1 -0
- package/lib/module/components/section-rail/index.js +32 -7
- package/lib/module/components/section-rail/index.js.map +1 -1
- package/lib/module/components/slider/index.js +240 -39
- package/lib/module/components/slider/index.js.map +1 -1
- package/lib/module/components/tabs/index.js +194 -10
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/text-animation/index.js +857 -0
- package/lib/module/components/text-animation/index.js.map +1 -0
- package/lib/module/icons/index.js +25 -7
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/theme/use-theme.js +1 -1
- package/lib/module/utils/chart.js +44 -0
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/module/utils/color.js +268 -0
- package/lib/module/utils/color.js.map +1 -0
- package/lib/typescript/src/components/card/index.d.ts +20 -0
- package/lib/typescript/src/components/card/index.d.ts.map +1 -1
- package/lib/typescript/src/components/color-picker/index.d.ts +258 -0
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts +13 -1
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/kpi-chart/index.d.ts +490 -0
- package/lib/typescript/src/components/kpi-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/map/index.d.ts +37 -2
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +50 -4
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radar-chart/index.d.ts +214 -0
- package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/section-rail/index.d.ts +8 -1
- package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -1
- package/lib/typescript/src/components/slider/index.d.ts +19 -0
- package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tabs/index.d.ts +11 -1
- package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
- package/lib/typescript/src/components/text-animation/index.d.ts +268 -0
- package/lib/typescript/src/components/text-animation/index.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +6 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +8 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +21 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/lib/typescript/src/utils/color.d.ts +74 -0
- package/lib/typescript/src/utils/color.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/card/index.tsx +227 -2
- package/src/components/color-picker/index.tsx +1618 -0
- package/src/components/combobox/index.tsx +89 -19
- package/src/components/drawer/index.tsx +31 -9
- package/src/components/kpi-chart/index.tsx +719 -0
- package/src/components/map/index.tsx +158 -14
- package/src/components/menu/index.tsx +51 -6
- package/src/components/pagination/index.tsx +18 -6
- package/src/components/panelside/index.tsx +134 -19
- package/src/components/radar-chart/index.tsx +910 -0
- package/src/components/section-rail/index.tsx +42 -8
- package/src/components/slider/index.tsx +283 -41
- package/src/components/tabs/index.tsx +263 -8
- package/src/components/text-animation/index.tsx +1093 -0
- package/src/icons/index.tsx +23 -4
- package/src/index.ts +73 -1
- package/src/theme/use-theme.ts +1 -1
- package/src/utils/chart.ts +52 -0
- package/src/utils/color.ts +246 -0
- package/theme.css +125 -112
|
@@ -33,18 +33,62 @@ import {
|
|
|
33
33
|
type LayoutChangeEvent,
|
|
34
34
|
type ViewProps,
|
|
35
35
|
} from 'react-native';
|
|
36
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
36
37
|
import Animated, {
|
|
37
38
|
FadeIn,
|
|
39
|
+
runOnJS,
|
|
38
40
|
useAnimatedStyle,
|
|
39
41
|
useSharedValue,
|
|
40
42
|
withSpring,
|
|
43
|
+
type SharedValue,
|
|
41
44
|
} from 'react-native-reanimated';
|
|
42
45
|
import { tv } from 'tailwind-variants';
|
|
46
|
+
import { useDirectionSign } from '../../hooks/use-direction';
|
|
43
47
|
import { Text, textChildren } from '../../primitives/text';
|
|
44
48
|
import { cn } from '../../utils/cn';
|
|
45
49
|
|
|
46
50
|
const SPRING = { damping: 24, stiffness: 300, mass: 0.7 } as const;
|
|
47
51
|
|
|
52
|
+
/**
|
|
53
|
+
* How far sideways a finger must travel before the panel takes the gesture
|
|
54
|
+
* from whatever is scrolling above it, and how far up or down before it gives
|
|
55
|
+
* up on it. The window between them is what lets a swipe start inside a
|
|
56
|
+
* vertically scrolling panel without either gesture stealing the other.
|
|
57
|
+
*/
|
|
58
|
+
const SWIPE_ACTIVATE_X = 12;
|
|
59
|
+
const SWIPE_FAIL_Y = 8;
|
|
60
|
+
|
|
61
|
+
/** A swipe past this share of the panel's width changes tab on release. */
|
|
62
|
+
const SWIPE_DISTANCE_RATIO = 0.25;
|
|
63
|
+
/** …or past this speed, however short it was. */
|
|
64
|
+
const SWIPE_VELOCITY = 500;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* How much of the finger's travel the panel actually follows.
|
|
68
|
+
*
|
|
69
|
+
* Not all of it: a panel that tracked one-to-one would look like a pager that
|
|
70
|
+
* is about to reveal the next panel, and there is nothing behind it to reveal
|
|
71
|
+
* — the panels are separate views, only one of which is mounted. A third of
|
|
72
|
+
* the distance is enough to say the gesture was received.
|
|
73
|
+
*/
|
|
74
|
+
const SWIPE_FOLLOW = 0.32;
|
|
75
|
+
/** …and a fraction of that again at the ends, where there is nowhere to go. */
|
|
76
|
+
const SWIPE_FOLLOW_AT_END = 0.1;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* How far off its resting place the arriving panel starts, as a share of the
|
|
80
|
+
* panel's width.
|
|
81
|
+
*
|
|
82
|
+
* A swipe that commits should not look like a swipe that was cancelled. Both
|
|
83
|
+
* end with a panel settling at rest, and the only thing that distinguishes
|
|
84
|
+
* them is which side it settled *from* — so the incoming panel picks up
|
|
85
|
+
* roughly where the outgoing one was let go, on the far side, and travels in.
|
|
86
|
+
*/
|
|
87
|
+
const SWIPE_ENTER = 0.3;
|
|
88
|
+
|
|
89
|
+
/** The arriving panel is springier than the one being dragged back into place. */
|
|
90
|
+
const ENTER_SPRING = { damping: 22, stiffness: 240, mass: 0.6 } as const;
|
|
91
|
+
|
|
48
92
|
export type TabsVariant = 'segmented' | 'underline' | 'pill';
|
|
49
93
|
|
|
50
94
|
const tabsVariants = tv({
|
|
@@ -109,10 +153,33 @@ interface TabsContextValue {
|
|
|
109
153
|
setValue: (value: string) => void;
|
|
110
154
|
registerLayout: (value: string, layout: TabLayout) => void;
|
|
111
155
|
layouts: Record<string, TabLayout>;
|
|
156
|
+
/**
|
|
157
|
+
* Every tab in the order it was declared, which is the order a swipe moves
|
|
158
|
+
* through them. Kept separately from `layouts` because that is a map keyed
|
|
159
|
+
* by value and its order is whatever the layout pass happened to produce —
|
|
160
|
+
* and because under RTL the leftmost trigger is the last one, so a position
|
|
161
|
+
* cannot stand in for a place in the sequence either.
|
|
162
|
+
*/
|
|
163
|
+
tabs: string[];
|
|
164
|
+
registerTab: (value: string) => void;
|
|
165
|
+
unregisterTab: (value: string) => void;
|
|
112
166
|
variant: TabsVariant;
|
|
113
167
|
scrollable: boolean;
|
|
114
168
|
setScrollable: (scrollable: boolean) => void;
|
|
115
169
|
keepMounted: boolean;
|
|
170
|
+
swipeable: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* How far the visible panel is displaced from its resting place, in points.
|
|
173
|
+
*
|
|
174
|
+
* It lives on the root rather than on a panel because the outgoing and
|
|
175
|
+
* incoming panels are different views, and a value that belonged to either
|
|
176
|
+
* of them would be destroyed at the moment of the handover. Shared, the
|
|
177
|
+
* displacement survives it: the panel being let go and the panel arriving
|
|
178
|
+
* are one continuous movement, drawn by two views in turn.
|
|
179
|
+
*/
|
|
180
|
+
swipeOffset: SharedValue<number>;
|
|
181
|
+
/** The visible panel's measured width, which the throw above is a share of. */
|
|
182
|
+
panelWidth: SharedValue<number>;
|
|
116
183
|
}
|
|
117
184
|
|
|
118
185
|
const TabsContext = createContext<TabsContextValue | null>(null);
|
|
@@ -141,6 +208,16 @@ export interface TabsProps extends ViewProps {
|
|
|
141
208
|
* Costs the render of every panel up front.
|
|
142
209
|
*/
|
|
143
210
|
keepMounted?: boolean;
|
|
211
|
+
/**
|
|
212
|
+
* Move between tabs by dragging sideways on the panel, as well as by
|
|
213
|
+
* pressing the triggers.
|
|
214
|
+
*
|
|
215
|
+
* Off by default, because a panel is allowed to contain something that
|
|
216
|
+
* already wants a horizontal drag — a carousel, a slider, a row that swipes
|
|
217
|
+
* open — and the two cannot both have it. Turn it on for panels of ordinary
|
|
218
|
+
* scrolling content, where it is the gesture people try first.
|
|
219
|
+
*/
|
|
220
|
+
swipeable?: boolean;
|
|
144
221
|
children: ReactNode;
|
|
145
222
|
}
|
|
146
223
|
|
|
@@ -151,11 +228,16 @@ function TabsRoot({
|
|
|
151
228
|
defaultValue,
|
|
152
229
|
variant = 'segmented',
|
|
153
230
|
keepMounted = false,
|
|
231
|
+
swipeable = false,
|
|
154
232
|
children,
|
|
155
233
|
...props
|
|
156
234
|
}: TabsProps) {
|
|
157
235
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
158
236
|
const [layouts, setLayouts] = useState<Record<string, TabLayout>>({});
|
|
237
|
+
const [tabs, setTabs] = useState<string[]>([]);
|
|
238
|
+
const swipeOffset = useSharedValue(0);
|
|
239
|
+
const panelWidth = useSharedValue(0);
|
|
240
|
+
const sign = useDirectionSign();
|
|
159
241
|
// Published by the List rather than the root, because it is the List that
|
|
160
242
|
// decides whether it scrolls — but the Triggers below it need to know.
|
|
161
243
|
const [scrollable, setScrollable] = useState(false);
|
|
@@ -164,10 +246,37 @@ function TabsRoot({
|
|
|
164
246
|
|
|
165
247
|
const setValue = useCallback(
|
|
166
248
|
(next: string) => {
|
|
249
|
+
/*
|
|
250
|
+
* The arriving panel starts on the side it is arriving from, and travels
|
|
251
|
+
* in. Done here rather than in the panel because only the root knows
|
|
252
|
+
* both the tab being left and the tab being gone to — a panel knows
|
|
253
|
+
* which one it is, not which one it replaced — and because a tab changed
|
|
254
|
+
* by pressing a trigger deserves the same movement as one changed by
|
|
255
|
+
* swiping to it. Without it the two read as different features.
|
|
256
|
+
*/
|
|
257
|
+
if (swipeable) {
|
|
258
|
+
const from = tabs.indexOf(resolvedValue);
|
|
259
|
+
const to = tabs.indexOf(next);
|
|
260
|
+
if (from !== -1 && to !== -1 && from !== to) {
|
|
261
|
+
swipeOffset.value =
|
|
262
|
+
(to > from ? 1 : -1) * sign * panelWidth.value * SWIPE_ENTER;
|
|
263
|
+
swipeOffset.value = withSpring(0, ENTER_SPRING);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
167
267
|
if (!isControlled) setInternalValue(next);
|
|
168
268
|
onValueChange?.(next);
|
|
169
269
|
},
|
|
170
|
-
[
|
|
270
|
+
[
|
|
271
|
+
isControlled,
|
|
272
|
+
onValueChange,
|
|
273
|
+
swipeable,
|
|
274
|
+
tabs,
|
|
275
|
+
resolvedValue,
|
|
276
|
+
sign,
|
|
277
|
+
swipeOffset,
|
|
278
|
+
panelWidth,
|
|
279
|
+
]
|
|
171
280
|
);
|
|
172
281
|
|
|
173
282
|
const registerLayout = useCallback((tab: string, layout: TabLayout) => {
|
|
@@ -180,18 +289,50 @@ function TabsRoot({
|
|
|
180
289
|
});
|
|
181
290
|
}, []);
|
|
182
291
|
|
|
292
|
+
/*
|
|
293
|
+
* Triggers add themselves on mount, so the order is React's child order —
|
|
294
|
+
* the order they are written in, which is the order they are read in.
|
|
295
|
+
*/
|
|
296
|
+
const registerTab = useCallback((tab: string) => {
|
|
297
|
+
setTabs((current) => (current.includes(tab) ? current : [...current, tab]));
|
|
298
|
+
}, []);
|
|
299
|
+
|
|
300
|
+
const unregisterTab = useCallback((tab: string) => {
|
|
301
|
+
setTabs((current) => current.filter((entry) => entry !== tab));
|
|
302
|
+
}, []);
|
|
303
|
+
|
|
183
304
|
const context = useMemo(
|
|
184
305
|
() => ({
|
|
185
306
|
value: resolvedValue,
|
|
186
307
|
setValue,
|
|
187
308
|
registerLayout,
|
|
188
309
|
layouts,
|
|
310
|
+
tabs,
|
|
311
|
+
registerTab,
|
|
312
|
+
unregisterTab,
|
|
189
313
|
variant,
|
|
190
314
|
scrollable,
|
|
191
315
|
setScrollable,
|
|
192
316
|
keepMounted,
|
|
317
|
+
swipeable,
|
|
318
|
+
swipeOffset,
|
|
319
|
+
panelWidth,
|
|
193
320
|
}),
|
|
194
|
-
[
|
|
321
|
+
[
|
|
322
|
+
resolvedValue,
|
|
323
|
+
setValue,
|
|
324
|
+
registerLayout,
|
|
325
|
+
layouts,
|
|
326
|
+
tabs,
|
|
327
|
+
registerTab,
|
|
328
|
+
unregisterTab,
|
|
329
|
+
variant,
|
|
330
|
+
scrollable,
|
|
331
|
+
keepMounted,
|
|
332
|
+
swipeable,
|
|
333
|
+
swipeOffset,
|
|
334
|
+
panelWidth,
|
|
335
|
+
]
|
|
195
336
|
);
|
|
196
337
|
|
|
197
338
|
return (
|
|
@@ -331,6 +472,15 @@ function TabsTrigger({
|
|
|
331
472
|
[context, value]
|
|
332
473
|
);
|
|
333
474
|
|
|
475
|
+
// Separate from the layout registration above, and earlier than it: a swipe
|
|
476
|
+
// needs to know the sequence, which is known at mount, not the positions,
|
|
477
|
+
// which are not known until the row has been laid out.
|
|
478
|
+
const { registerTab, unregisterTab } = context;
|
|
479
|
+
useEffect(() => {
|
|
480
|
+
registerTab(value);
|
|
481
|
+
return () => unregisterTab(value);
|
|
482
|
+
}, [value, registerTab, unregisterTab]);
|
|
483
|
+
|
|
334
484
|
return (
|
|
335
485
|
<Pressable
|
|
336
486
|
accessibilityRole="tab"
|
|
@@ -360,6 +510,86 @@ export interface TabsContentProps extends ViewProps {
|
|
|
360
510
|
function TabsContent({ className, value, children, style, ...props }: TabsContentProps) {
|
|
361
511
|
const context = useTabs('Tabs.Content');
|
|
362
512
|
const active = context.value === value;
|
|
513
|
+
const {
|
|
514
|
+
tabs,
|
|
515
|
+
setValue,
|
|
516
|
+
swipeable,
|
|
517
|
+
swipeOffset: offset,
|
|
518
|
+
panelWidth: width,
|
|
519
|
+
} = context;
|
|
520
|
+
const sign = useDirectionSign();
|
|
521
|
+
// Read on the UI thread while the finger is down, so the resistance at the
|
|
522
|
+
// ends is known without a round trip to JavaScript.
|
|
523
|
+
const index = useSharedValue(0);
|
|
524
|
+
const count = useSharedValue(0);
|
|
525
|
+
|
|
526
|
+
const position = tabs.indexOf(value);
|
|
527
|
+
useEffect(() => {
|
|
528
|
+
index.value = position;
|
|
529
|
+
count.value = tabs.length;
|
|
530
|
+
}, [position, tabs.length, index, count]);
|
|
531
|
+
|
|
532
|
+
const step = useCallback(
|
|
533
|
+
(delta: number) => {
|
|
534
|
+
const from = tabs.indexOf(value);
|
|
535
|
+
const next = tabs[from + delta];
|
|
536
|
+
if (next) setValue(next);
|
|
537
|
+
},
|
|
538
|
+
[tabs, value, setValue]
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
const pan = useMemo(
|
|
542
|
+
() =>
|
|
543
|
+
Gesture.Pan()
|
|
544
|
+
// Sideways past the threshold takes the gesture; any real vertical
|
|
545
|
+
// travel hands it back, so a panel that scrolls still scrolls.
|
|
546
|
+
.activeOffsetX([-SWIPE_ACTIVATE_X, SWIPE_ACTIVATE_X])
|
|
547
|
+
.failOffsetY([-SWIPE_FAIL_Y, SWIPE_FAIL_Y])
|
|
548
|
+
.onUpdate((event) => {
|
|
549
|
+
// Positive is "towards the previous tab" in reading order, which is
|
|
550
|
+
// rightwards under LTR and leftwards under RTL.
|
|
551
|
+
const travel = event.translationX * sign;
|
|
552
|
+
const atEnd =
|
|
553
|
+
(travel > 0 && index.value === 0) ||
|
|
554
|
+
(travel < 0 && index.value >= count.value - 1);
|
|
555
|
+
offset.value =
|
|
556
|
+
event.translationX * (atEnd ? SWIPE_FOLLOW_AT_END : SWIPE_FOLLOW);
|
|
557
|
+
})
|
|
558
|
+
.onEnd((event) => {
|
|
559
|
+
const travel = event.translationX * sign;
|
|
560
|
+
const speed = event.velocityX * sign;
|
|
561
|
+
const far = Math.abs(travel) > width.value * SWIPE_DISTANCE_RATIO;
|
|
562
|
+
const fast = Math.abs(speed) > SWIPE_VELOCITY;
|
|
563
|
+
const atEnd =
|
|
564
|
+
(travel > 0 && index.value === 0) ||
|
|
565
|
+
(travel < 0 && index.value >= count.value - 1);
|
|
566
|
+
|
|
567
|
+
if ((far || fast) && !atEnd) {
|
|
568
|
+
// Distance and speed can disagree — a flick back the way it came
|
|
569
|
+
// reads as a cancel — so the direction comes from whichever of the
|
|
570
|
+
// two crossed its threshold, speed first.
|
|
571
|
+
//
|
|
572
|
+
// No spring back to zero here: `setValue` on the root throws the
|
|
573
|
+
// displacement across to the side the next panel arrives from, and
|
|
574
|
+
// springing to rest first would undo it a frame before it happens.
|
|
575
|
+
runOnJS(step)(fast ? (speed < 0 ? 1 : -1) : travel < 0 ? 1 : -1);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Nothing changed hands, so this panel simply comes back.
|
|
580
|
+
offset.value = withSpring(0, SPRING);
|
|
581
|
+
})
|
|
582
|
+
.onFinalize((_event, success) => {
|
|
583
|
+
// A cancelled gesture never reaches `onEnd`, and would otherwise
|
|
584
|
+
// leave the panel wherever the finger abandoned it.
|
|
585
|
+
if (!success) offset.value = withSpring(0, SPRING);
|
|
586
|
+
}),
|
|
587
|
+
[sign, step, offset, width, index, count]
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const followStyle = useAnimatedStyle(() => ({
|
|
591
|
+
transform: [{ translateX: offset.value }],
|
|
592
|
+
}));
|
|
363
593
|
|
|
364
594
|
if (!active && !context.keepMounted) return null;
|
|
365
595
|
|
|
@@ -369,13 +599,31 @@ function TabsContent({ className, value, children, style, ...props }: TabsConten
|
|
|
369
599
|
* it out of the reading order too. A screen reader walking through three
|
|
370
600
|
* panels of a tab set it cannot see is worse than no tabs at all.
|
|
371
601
|
*/
|
|
372
|
-
|
|
602
|
+
const panel = (
|
|
373
603
|
<Animated.View
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
604
|
+
/*
|
|
605
|
+
* Only worth animating when the panel is genuinely arriving. A kept
|
|
606
|
+
* panel is already there; fading it in every time it is revealed would
|
|
607
|
+
* undo the point of keeping it.
|
|
608
|
+
*
|
|
609
|
+
* And never alongside the swipe: an entering layout animation owns the
|
|
610
|
+
* view's style for the length of it and will overwrite an animated style
|
|
611
|
+
* touching the same view, so the fade would eat the slide. When panels
|
|
612
|
+
* move, the movement is the entrance.
|
|
613
|
+
*/
|
|
614
|
+
entering={context.keepMounted || swipeable ? undefined : FadeIn.duration(150)}
|
|
615
|
+
onLayout={(event: LayoutChangeEvent) => {
|
|
616
|
+
// The thresholds are a share of the panel, not of the screen: a tab
|
|
617
|
+
// set inside a card is narrower than the window, and a quarter of the
|
|
618
|
+
// window would be most of the way across it.
|
|
619
|
+
//
|
|
620
|
+
// Zero is ignored. Every panel reports into the same value, and a
|
|
621
|
+
// kept-mounted one is `display: none` — it measures as nothing, and
|
|
622
|
+
// letting it say so would leave the visible panel with no width.
|
|
623
|
+
const measured = event.nativeEvent.layout.width;
|
|
624
|
+
if (measured > 0) width.value = measured;
|
|
625
|
+
}}
|
|
626
|
+
style={[!active && { display: 'none' }, swipeable && followStyle, style]}
|
|
379
627
|
pointerEvents={active ? 'auto' : 'none'}
|
|
380
628
|
accessibilityElementsHidden={!active}
|
|
381
629
|
importantForAccessibility={active ? 'auto' : 'no-hide-descendants'}
|
|
@@ -385,6 +633,13 @@ function TabsContent({ className, value, children, style, ...props }: TabsConten
|
|
|
385
633
|
{textChildren(children)}
|
|
386
634
|
</Animated.View>
|
|
387
635
|
);
|
|
636
|
+
|
|
637
|
+
// Only the visible panel carries the gesture. A kept-mounted panel is
|
|
638
|
+
// `display: none` and takes no touches anyway, but attaching a detector to
|
|
639
|
+
// each of them would put several competing recognisers in the same tree.
|
|
640
|
+
if (!swipeable || !active) return panel;
|
|
641
|
+
|
|
642
|
+
return <GestureDetector gesture={pan}>{panel}</GestureDetector>;
|
|
388
643
|
}
|
|
389
644
|
|
|
390
645
|
export const Tabs = Object.assign(TabsRoot, {
|