@oxyhq/bloom 0.79.0 → 0.80.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 +3 -3
- package/lib/commonjs/provider/index.js +11 -7
- package/lib/commonjs/provider/index.js.map +1 -1
- package/lib/commonjs/scroll/context.js +96 -0
- package/lib/commonjs/scroll/context.js.map +1 -0
- package/lib/commonjs/scroll/expo-router/index.js +133 -0
- package/lib/commonjs/scroll/expo-router/index.js.map +1 -0
- package/lib/commonjs/scroll/index.js +85 -21
- package/lib/commonjs/scroll/index.js.map +1 -1
- package/lib/commonjs/scroll/index.web.js +221 -121
- package/lib/commonjs/scroll/index.web.js.map +1 -1
- package/lib/commonjs/scroll/scrollable.native.js +58 -0
- package/lib/commonjs/scroll/scrollable.native.js.map +1 -0
- package/lib/commonjs/scroll/scrollable.web.js +9 -3
- package/lib/commonjs/scroll/scrollable.web.js.map +1 -1
- package/lib/commonjs/scroll/store.js +47 -25
- package/lib/commonjs/scroll/store.js.map +1 -1
- package/lib/commonjs/tabs/Tabs.js +263 -63
- package/lib/commonjs/tabs/Tabs.js.map +1 -1
- package/lib/commonjs/tabs/expo-router/RouterTabs.js +215 -0
- package/lib/commonjs/tabs/expo-router/RouterTabs.js.map +1 -0
- package/lib/commonjs/tabs/expo-router/index.js +13 -0
- package/lib/commonjs/tabs/expo-router/index.js.map +1 -0
- package/lib/commonjs/theme/color-policy.js +11 -10
- package/lib/commonjs/theme/color-policy.js.map +1 -1
- package/lib/module/provider/index.js +10 -6
- package/lib/module/provider/index.js.map +1 -1
- package/lib/module/scroll/context.js +90 -0
- package/lib/module/scroll/context.js.map +1 -0
- package/lib/module/scroll/expo-router/index.js +128 -0
- package/lib/module/scroll/expo-router/index.js.map +1 -0
- package/lib/module/scroll/index.js +80 -21
- package/lib/module/scroll/index.js.map +1 -1
- package/lib/module/scroll/index.web.js +218 -122
- package/lib/module/scroll/index.web.js.map +1 -1
- package/lib/module/scroll/scrollable.native.js +54 -0
- package/lib/module/scroll/scrollable.native.js.map +1 -0
- package/lib/module/scroll/scrollable.web.js +9 -3
- package/lib/module/scroll/scrollable.web.js.map +1 -1
- package/lib/module/scroll/store.js +47 -25
- package/lib/module/scroll/store.js.map +1 -1
- package/lib/module/tabs/Tabs.js +271 -65
- package/lib/module/tabs/Tabs.js.map +1 -1
- package/lib/module/tabs/expo-router/RouterTabs.js +210 -0
- package/lib/module/tabs/expo-router/RouterTabs.js.map +1 -0
- package/lib/module/tabs/expo-router/index.js +10 -0
- package/lib/module/tabs/expo-router/index.js.map +1 -0
- package/lib/module/theme/color-policy.js +11 -10
- package/lib/module/theme/color-policy.js.map +1 -1
- package/lib/typescript/commonjs/provider/index.d.ts +7 -5
- package/lib/typescript/commonjs/provider/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/scroll/context.d.ts +14 -0
- package/lib/typescript/commonjs/scroll/context.d.ts.map +1 -0
- package/lib/typescript/commonjs/scroll/expo-router/index.d.ts +7 -0
- package/lib/typescript/commonjs/scroll/expo-router/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/scroll/index.d.ts +14 -23
- package/lib/typescript/commonjs/scroll/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/scroll/index.web.d.ts +17 -19
- package/lib/typescript/commonjs/scroll/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/scroll/scrollable.native.d.ts +23 -0
- package/lib/typescript/commonjs/scroll/scrollable.native.d.ts.map +1 -0
- package/lib/typescript/commonjs/scroll/scrollable.web.d.ts +18 -5
- package/lib/typescript/commonjs/scroll/scrollable.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/scroll/store.d.ts +41 -22
- package/lib/typescript/commonjs/scroll/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/scroll/types.d.ts +82 -12
- package/lib/typescript/commonjs/scroll/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/tabs/Tabs.d.ts +41 -1
- package/lib/typescript/commonjs/tabs/Tabs.d.ts.map +1 -1
- package/lib/typescript/commonjs/tabs/expo-router/RouterTabs.d.ts +80 -0
- package/lib/typescript/commonjs/tabs/expo-router/RouterTabs.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/expo-router/index.d.ts +9 -0
- package/lib/typescript/commonjs/tabs/expo-router/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/tabs/types.d.ts +48 -4
- package/lib/typescript/commonjs/tabs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-policy.d.ts.map +1 -1
- package/lib/typescript/module/provider/index.d.ts +7 -5
- package/lib/typescript/module/provider/index.d.ts.map +1 -1
- package/lib/typescript/module/scroll/context.d.ts +14 -0
- package/lib/typescript/module/scroll/context.d.ts.map +1 -0
- package/lib/typescript/module/scroll/expo-router/index.d.ts +7 -0
- package/lib/typescript/module/scroll/expo-router/index.d.ts.map +1 -0
- package/lib/typescript/module/scroll/index.d.ts +14 -23
- package/lib/typescript/module/scroll/index.d.ts.map +1 -1
- package/lib/typescript/module/scroll/index.web.d.ts +17 -19
- package/lib/typescript/module/scroll/index.web.d.ts.map +1 -1
- package/lib/typescript/module/scroll/scrollable.native.d.ts +23 -0
- package/lib/typescript/module/scroll/scrollable.native.d.ts.map +1 -0
- package/lib/typescript/module/scroll/scrollable.web.d.ts +18 -5
- package/lib/typescript/module/scroll/scrollable.web.d.ts.map +1 -1
- package/lib/typescript/module/scroll/store.d.ts +41 -22
- package/lib/typescript/module/scroll/store.d.ts.map +1 -1
- package/lib/typescript/module/scroll/types.d.ts +82 -12
- package/lib/typescript/module/scroll/types.d.ts.map +1 -1
- package/lib/typescript/module/tabs/Tabs.d.ts +41 -1
- package/lib/typescript/module/tabs/Tabs.d.ts.map +1 -1
- package/lib/typescript/module/tabs/expo-router/RouterTabs.d.ts +80 -0
- package/lib/typescript/module/tabs/expo-router/RouterTabs.d.ts.map +1 -0
- package/lib/typescript/module/tabs/expo-router/index.d.ts +9 -0
- package/lib/typescript/module/tabs/expo-router/index.d.ts.map +1 -0
- package/lib/typescript/module/tabs/types.d.ts +48 -4
- package/lib/typescript/module/tabs/types.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-policy.d.ts.map +1 -1
- package/package.json +29 -1
- package/src/__tests__/BloomProvider.web.test.tsx +8 -15
- package/src/__tests__/Tabs.test.tsx +187 -0
- package/src/__tests__/optional-peer-imports.test.ts +7 -2
- package/src/__tests__/scroll-expo-router-adapter.test.tsx +164 -0
- package/src/__tests__/scroll-native.test.tsx +465 -0
- package/src/__tests__/scroll-store.test.ts +41 -17
- package/src/__tests__/scroll-web.test.tsx +797 -108
- package/src/provider/index.tsx +10 -7
- package/src/scroll/context.tsx +107 -0
- package/src/scroll/expo-router/index.ts +140 -0
- package/src/scroll/index.ts +92 -25
- package/src/scroll/index.web.tsx +255 -162
- package/src/scroll/scrollable.native.ts +64 -0
- package/src/scroll/scrollable.web.ts +30 -8
- package/src/scroll/store.ts +47 -25
- package/src/scroll/types.ts +82 -12
- package/src/tabs/Tabs.tsx +352 -77
- package/src/tabs/expo-router/RouterTabs.tsx +268 -0
- package/src/tabs/expo-router/index.ts +8 -0
- package/src/tabs/types.ts +48 -4
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +5 -5
- package/src/theme/__tests__/__snapshots__/visual-gallery.test.tsx.snap +11 -11
- package/src/theme/__tests__/policy-legibility.test.ts +10 -7
- package/src/theme/color-policy.ts +11 -10
- package/lib/commonjs/provider/scroll-provider.js +0 -13
- package/lib/commonjs/provider/scroll-provider.js.map +0 -1
- package/lib/commonjs/provider/scroll-provider.web.js +0 -13
- package/lib/commonjs/provider/scroll-provider.web.js.map +0 -1
- package/lib/module/provider/scroll-provider.js +0 -16
- package/lib/module/provider/scroll-provider.js.map +0 -1
- package/lib/module/provider/scroll-provider.web.js +0 -5
- package/lib/module/provider/scroll-provider.web.js.map +0 -1
- package/lib/typescript/commonjs/provider/scroll-provider.d.ts +0 -14
- package/lib/typescript/commonjs/provider/scroll-provider.d.ts.map +0 -1
- package/lib/typescript/commonjs/provider/scroll-provider.web.d.ts +0 -3
- package/lib/typescript/commonjs/provider/scroll-provider.web.d.ts.map +0 -1
- package/lib/typescript/module/provider/scroll-provider.d.ts +0 -14
- package/lib/typescript/module/provider/scroll-provider.d.ts.map +0 -1
- package/lib/typescript/module/provider/scroll-provider.web.d.ts +0 -3
- package/lib/typescript/module/provider/scroll-provider.web.d.ts.map +0 -1
- package/src/__tests__/scroll-native.test.ts +0 -25
- package/src/provider/scroll-provider.ts +0 -13
- package/src/provider/scroll-provider.web.ts +0 -2
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* expo-router bindings for the in-screen tab strip.
|
|
3
|
+
*
|
|
4
|
+
* This subpath (`@oxyhq/bloom/tabs/expo-router`) is the ONLY place in the tabs
|
|
5
|
+
* feature allowed to import `expo-router`, which is why it is a separate entry:
|
|
6
|
+
* the core `@oxyhq/bloom/tabs` stays usable in an app with a different router,
|
|
7
|
+
* or none, and the optional peer is never pulled into a bundle that does not
|
|
8
|
+
* ask for it. Same split as `tab-bar` / `tab-bar/expo-router` and
|
|
9
|
+
* `scroll` / `scroll/expo-router`.
|
|
10
|
+
*
|
|
11
|
+
* Everything routing-shaped lives here on purpose: which tab the route means,
|
|
12
|
+
* what a committed swipe navigates to, and which links to warm. The strip
|
|
13
|
+
* itself stays presentational and knows only about measured geometry.
|
|
14
|
+
*/
|
|
15
|
+
import { useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react';
|
|
16
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
17
|
+
import { runOnJS } from 'react-native-reanimated';
|
|
18
|
+
import { router, usePathname, type Href } from 'expo-router';
|
|
19
|
+
|
|
20
|
+
import { Tabs, TabsTrigger, type TabsDragController } from '../Tabs';
|
|
21
|
+
import type { TabsProps } from '../types';
|
|
22
|
+
|
|
23
|
+
export interface RouterTabItem {
|
|
24
|
+
/** Stable id for the tab. Used as a React key and a callback argument. */
|
|
25
|
+
value: string;
|
|
26
|
+
/** Tab label text. */
|
|
27
|
+
label: string;
|
|
28
|
+
/** The route this tab shows. Must be the tab's EXACT path. */
|
|
29
|
+
href: Href;
|
|
30
|
+
/** Icon rendered before the label. */
|
|
31
|
+
icon?: ReactNode;
|
|
32
|
+
/** Optional tally beside the label; `0` or omitted renders nothing. */
|
|
33
|
+
count?: number;
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RouterTabsProps = Omit<
|
|
38
|
+
TabsProps,
|
|
39
|
+
// All three describe a selection the ROUTE already answers, so accepting them
|
|
40
|
+
// here would let a caller contradict the router.
|
|
41
|
+
'value' | 'onValueChange' | 'hasSelection' | 'children'
|
|
42
|
+
> & {
|
|
43
|
+
items: RouterTabItem[];
|
|
44
|
+
/**
|
|
45
|
+
* Called instead of navigating when the ALREADY-active tab is pressed.
|
|
46
|
+
*
|
|
47
|
+
* Without it a re-tap would push a second history entry for the route the
|
|
48
|
+
* user is already on, so Back would appear to do nothing. Re-tapping the
|
|
49
|
+
* active tab conventionally means "refresh" or "scroll to top", and that is
|
|
50
|
+
* the caller's to define.
|
|
51
|
+
*/
|
|
52
|
+
onReselect?: (value: string) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Enable the horizontal swipe between neighbouring tabs. Defaults to `true`.
|
|
55
|
+
*
|
|
56
|
+
* The gesture is INDICATOR-LED: the underline tracks the finger toward the
|
|
57
|
+
* neighbour and commits on release past a threshold, while the content stays
|
|
58
|
+
* where it is. That asymmetry is deliberate — the underline has somewhere
|
|
59
|
+
* real to go and can complete its journey, whereas under a `<Slot/>` there is
|
|
60
|
+
* no neighbouring screen mounted to bring in, so translating the content
|
|
61
|
+
* would only open a blank gutter on the trailing edge.
|
|
62
|
+
*/
|
|
63
|
+
swipeEnabled?: boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve which item the current route belongs to.
|
|
68
|
+
*
|
|
69
|
+
* LONGEST matching prefix, on a segment boundary — not an exact match. All
|
|
70
|
+
* three properties matter:
|
|
71
|
+
*
|
|
72
|
+
* - A prefix is needed because a tab may own a subtree (`/feeds/1/topics` and
|
|
73
|
+
* anything under it), and an exact match would drop the underline the moment
|
|
74
|
+
* the user goes one level deeper.
|
|
75
|
+
* - LONGEST is needed because the default tab's href is a prefix of every
|
|
76
|
+
* sibling's (`/@ana` vs `/@ana/replies`), so a first-match rule would light
|
|
77
|
+
* up the default tab on every tab.
|
|
78
|
+
* - The segment boundary is what stops `/feeds/12` from matching `/feeds/1`.
|
|
79
|
+
*
|
|
80
|
+
* Returns `undefined` when nothing matches, which is a real state — a strip
|
|
81
|
+
* rendered by a LAYOUT keeps rendering while a child route outside the tab set
|
|
82
|
+
* is showing — and leaves the underline hidden rather than parked on a lie.
|
|
83
|
+
*/
|
|
84
|
+
function matchActiveValue(items: RouterTabItem[], pathname: string): string | undefined {
|
|
85
|
+
let best: { value: string; length: number } | undefined;
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
// `Href` is a string or an object; only a string form can be compared to a
|
|
88
|
+
// pathname, and an object form describes params we cannot resolve here.
|
|
89
|
+
if (typeof item.href !== 'string') continue;
|
|
90
|
+
// Compare paths only: a query string describes what the tab shows, not
|
|
91
|
+
// which tab it is, and a trailing slash is the same route either way.
|
|
92
|
+
const [path = ''] = item.href.split('?');
|
|
93
|
+
const href = path.replace(/\/$/, '');
|
|
94
|
+
const isMatch = pathname === href || pathname.startsWith(`${href}/`);
|
|
95
|
+
if (!isMatch) continue;
|
|
96
|
+
if (!best || href.length > best.length) {
|
|
97
|
+
best = { value: item.value, length: href.length };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return best?.value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A tab strip whose selection IS the route.
|
|
105
|
+
*
|
|
106
|
+
* Render it in the LAYOUT that owns the tabs, above the `<Slot/>` that renders
|
|
107
|
+
* them — not inside the tab screens themselves. That placement is the whole
|
|
108
|
+
* point, and it is load-bearing twice over: a strip inside the screens is
|
|
109
|
+
* unmounted and rebuilt by every navigation, so its underline has nothing to
|
|
110
|
+
* animate from and the chrome blanks while the incoming route's chunk loads;
|
|
111
|
+
* and a GESTURE inside the screens would be destroyed by the very navigation it
|
|
112
|
+
* commits, mid-release.
|
|
113
|
+
*
|
|
114
|
+
* ```tsx
|
|
115
|
+
* // app/(app)/[username]/_layout.tsx
|
|
116
|
+
* <RouterTabs
|
|
117
|
+
* items={[
|
|
118
|
+
* { value: 'posts', label: 'Posts', href: `/${username}` },
|
|
119
|
+
* { value: 'replies', label: 'Replies', href: `/${username}/replies` },
|
|
120
|
+
* ]}
|
|
121
|
+
* />
|
|
122
|
+
* <Slot />
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export function RouterTabs({
|
|
126
|
+
items,
|
|
127
|
+
onReselect,
|
|
128
|
+
swipeEnabled = true,
|
|
129
|
+
...tabsProps
|
|
130
|
+
}: RouterTabsProps) {
|
|
131
|
+
const pathname = usePathname();
|
|
132
|
+
const activeValue = useMemo(() => matchActiveValue(items, pathname), [items, pathname]);
|
|
133
|
+
const dragRef = useRef<TabsDragController>(null);
|
|
134
|
+
// What releasing right now would commit. A ref rather than state: it is
|
|
135
|
+
// written on every frame of the drag and read once, and re-rendering the
|
|
136
|
+
// strip mid-gesture would fight the animation the gesture is driving.
|
|
137
|
+
const pendingRef = useRef<string | null>(null);
|
|
138
|
+
|
|
139
|
+
const navigateTo = useCallback(
|
|
140
|
+
(value: string) => {
|
|
141
|
+
const target = items.find((item) => item.value === value);
|
|
142
|
+
if (!target) return;
|
|
143
|
+
// PUSH, not replace: a history entry per tab is what makes Back return to
|
|
144
|
+
// the tab the reader came from instead of leaving the screen entirely.
|
|
145
|
+
router.push(target.href);
|
|
146
|
+
},
|
|
147
|
+
[items],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const handlePress = useCallback(
|
|
151
|
+
(item: RouterTabItem) => {
|
|
152
|
+
if (item.value === activeValue) {
|
|
153
|
+
onReselect?.(item.value);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
navigateTo(item.value);
|
|
157
|
+
},
|
|
158
|
+
[activeValue, onReselect, navigateTo],
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const onDrag = useCallback((translationX: number) => {
|
|
162
|
+
pendingRef.current = dragRef.current?.drag(translationX) ?? null;
|
|
163
|
+
}, []);
|
|
164
|
+
|
|
165
|
+
// `onEnd` does not fire for every terminated gesture, and `onFinalize` fires
|
|
166
|
+
// for all of them — including after `onEnd`. Guarding on the ref being
|
|
167
|
+
// non-null is what makes the second call a no-op instead of a double commit.
|
|
168
|
+
const releasedRef = useRef(true);
|
|
169
|
+
const onRelease = useCallback(() => {
|
|
170
|
+
if (releasedRef.current) return;
|
|
171
|
+
releasedRef.current = true;
|
|
172
|
+
const committed = pendingRef.current;
|
|
173
|
+
pendingRef.current = null;
|
|
174
|
+
// The underline commits first and instantly: it lives in the layout, it
|
|
175
|
+
// survives the swap, and it is the honest signal the gesture was accepted.
|
|
176
|
+
// The content area arrives when its chunk does.
|
|
177
|
+
dragRef.current?.release(committed);
|
|
178
|
+
if (committed !== null) navigateTo(committed);
|
|
179
|
+
}, [navigateTo]);
|
|
180
|
+
|
|
181
|
+
const onBegin = useCallback(() => {
|
|
182
|
+
releasedRef.current = false;
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
const panGesture = useMemo(
|
|
186
|
+
() =>
|
|
187
|
+
Gesture.Pan()
|
|
188
|
+
// Horizontal intent only. This is a hint to the gesture system and is
|
|
189
|
+
// NOT what protects vertical scrolling on web — see `touchAction` below.
|
|
190
|
+
.activeOffsetX([-12, 12])
|
|
191
|
+
.failOffsetY([-12, 12])
|
|
192
|
+
.onBegin(() => {
|
|
193
|
+
runOnJS(onBegin)();
|
|
194
|
+
})
|
|
195
|
+
.onUpdate((event) => {
|
|
196
|
+
runOnJS(onDrag)(event.translationX);
|
|
197
|
+
})
|
|
198
|
+
.onFinalize(() => {
|
|
199
|
+
runOnJS(onRelease)();
|
|
200
|
+
}),
|
|
201
|
+
[onBegin, onDrag, onRelease],
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Warm every OTHER tab's async chunk. That fetch is the ~300ms the content
|
|
205
|
+
// area would otherwise spend empty after a commit, and a swipe offers no
|
|
206
|
+
// hover to warm on, so it is done up front rather than on interaction.
|
|
207
|
+
//
|
|
208
|
+
// `router.prefetch` — the public imperative API (declared in expo-router's
|
|
209
|
+
// `global-state/router.d.ts`) — rather than wrapping each trigger in
|
|
210
|
+
// `<Link prefetch asChild>`. Both reasons are about the WRAPPER, not about
|
|
211
|
+
// prefetching: `asChild` is Radix's `Slot`, which COMPOSES event handlers, so
|
|
212
|
+
// the trigger's own `onPress` and the link's navigation would both run — and
|
|
213
|
+
// the link's push knows nothing about `onReselect`, so re-tapping the active
|
|
214
|
+
// tab would push a duplicate entry and make Back appear to do nothing. It
|
|
215
|
+
// also injects an `href` a `Pressable` ignores. The prefetching itself is
|
|
216
|
+
// identical: `<Link prefetch>` renders `<Prefetch href/>` beside its element
|
|
217
|
+
// and does nothing else.
|
|
218
|
+
//
|
|
219
|
+
// Known trade: a real `<Link>` would render a web anchor, which is what gives
|
|
220
|
+
// middle-click and open-in-new-tab. A tab strip arguably wants that; getting
|
|
221
|
+
// both needs the trigger to render an anchor itself, which is a change to the
|
|
222
|
+
// core rather than to this file.
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
for (const item of items) {
|
|
225
|
+
if (item.value === activeValue || item.disabled) continue;
|
|
226
|
+
router.prefetch(item.href);
|
|
227
|
+
}
|
|
228
|
+
}, [items, activeValue]);
|
|
229
|
+
|
|
230
|
+
const strip = (
|
|
231
|
+
// `hasSelection` is what stops the underline asserting a tab while a
|
|
232
|
+
// NON-TAB sibling route is showing — the layout that renders this strip
|
|
233
|
+
// renders those too (a profile's `/followers` beside its tab routes).
|
|
234
|
+
<Tabs {...tabsProps} ref={dragRef} hasSelection={activeValue !== undefined}>
|
|
235
|
+
{items.map((item) => (
|
|
236
|
+
<TabsTrigger
|
|
237
|
+
key={item.value}
|
|
238
|
+
value={item.value}
|
|
239
|
+
label={item.label}
|
|
240
|
+
icon={item.icon}
|
|
241
|
+
count={item.count}
|
|
242
|
+
disabled={item.disabled}
|
|
243
|
+
// Focus, never a press, is what drives the underline — so a deep
|
|
244
|
+
// link, a browser Back and a back gesture animate exactly like a tap.
|
|
245
|
+
isFocused={item.value === activeValue}
|
|
246
|
+
onPress={() => handlePress(item)}
|
|
247
|
+
/>
|
|
248
|
+
))}
|
|
249
|
+
</Tabs>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (!swipeEnabled) return strip;
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
// `touchAction="pan-y"` is MANDATORY and is the landmine of this file.
|
|
256
|
+
// RNGH sets the detector's view to `touch-action: none` on web
|
|
257
|
+
// (`GestureHandlerWebDelegate.js`), which disables browser-native VERTICAL
|
|
258
|
+
// scrolling on that element. `failOffsetY` above does not protect against
|
|
259
|
+
// it: that is a JS decision taken after `touch-action` has already told the
|
|
260
|
+
// browser not to scroll. Removing this prop is silent on native and breaks
|
|
261
|
+
// scrolling on mobile web.
|
|
262
|
+
<GestureDetector gesture={panGesture} touchAction="pan-y">
|
|
263
|
+
{strip}
|
|
264
|
+
</GestureDetector>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
RouterTabs.displayName = 'RouterTabs';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry for `@oxyhq/bloom/tabs/expo-router`. The component lives in a sibling
|
|
3
|
+
* module because this file is the published entry path and must stay stable;
|
|
4
|
+
* the split also keeps the router binding in one place, which is what the
|
|
5
|
+
* optional-peer allowlist asserts about.
|
|
6
|
+
*/
|
|
7
|
+
export { RouterTabs } from './RouterTabs';
|
|
8
|
+
export type { RouterTabItem, RouterTabsProps } from './RouterTabs';
|
package/src/tabs/types.ts
CHANGED
|
@@ -3,10 +3,38 @@ import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
|
|
|
3
3
|
export type TabsVariant = 'underline' | 'filled' | 'outlined';
|
|
4
4
|
|
|
5
5
|
export interface TabsProps {
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The currently selected tab value — the CONTROLLED path, where the bar owns
|
|
8
|
+
* the underline and reports presses through {@link TabsProps.onValueChange}.
|
|
9
|
+
*
|
|
10
|
+
* OMIT it for the focus-driven path, where each trigger supplies its own
|
|
11
|
+
* {@link TabsTriggerProps.isFocused} and performs its own navigation. That is
|
|
12
|
+
* how `@oxyhq/bloom/tabs/expo-router` keeps the underline correct through
|
|
13
|
+
* deep links, browser Back and back gestures — none of which pass through a
|
|
14
|
+
* press handler. Providing both would put two writers on one shared value.
|
|
15
|
+
*/
|
|
16
|
+
value?: string;
|
|
17
|
+
/** Called when a tab is selected. Controlled path only. */
|
|
18
|
+
onValueChange?: (value: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Focus-driven path only: whether ANY trigger is currently focused.
|
|
21
|
+
*
|
|
22
|
+
* `false` fades the underline out where it stands. This is not the same as
|
|
23
|
+
* "the selection moved", and it is not hypothetical: a strip rendered by a
|
|
24
|
+
* LAYOUT keeps rendering while the layout's other, non-tab children are
|
|
25
|
+
* showing — a profile's `/followers` sits beside its tab routes under one
|
|
26
|
+
* layout — and without this the underline stays parked on whichever tab was
|
|
27
|
+
* last visited, asserting a selection that is not there.
|
|
28
|
+
*
|
|
29
|
+
* Fading is the whole response; the underline is deliberately NOT moved,
|
|
30
|
+
* because there is no position that means "nowhere" and travelling to a
|
|
31
|
+
* sentinel would drag it across every tab on the way out.
|
|
32
|
+
*
|
|
33
|
+
* `RouterTabs` sets it, so an app author never passes it by hand. Defaults to
|
|
34
|
+
* `true`, which is correct for the controlled path (a `value` naming no
|
|
35
|
+
* trigger simply leaves the underline where it was, as it always has).
|
|
36
|
+
*/
|
|
37
|
+
hasSelection?: boolean;
|
|
10
38
|
/** Visual variant for the tab bar. */
|
|
11
39
|
variant?: TabsVariant;
|
|
12
40
|
/**
|
|
@@ -29,8 +57,24 @@ export interface TabsTriggerProps {
|
|
|
29
57
|
label: string;
|
|
30
58
|
/** Icon rendered before the label. */
|
|
31
59
|
icon?: React.ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* Optional tally rendered as a small muted number beside the label. Omitted
|
|
62
|
+
* (or `0`) renders nothing. The underline tracks the trigger's measured
|
|
63
|
+
* width, so a count never changes where it sits.
|
|
64
|
+
*/
|
|
65
|
+
count?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Whether the ROUTER considers this tab focused — the focus-driven path.
|
|
68
|
+
* Supplying it makes this trigger, not the bar, the writer of the shared
|
|
69
|
+
* underline, and suppresses {@link TabsProps.onValueChange} on press because
|
|
70
|
+
* navigation is then the caller's job. Leave it `undefined` on the controlled
|
|
71
|
+
* path.
|
|
72
|
+
*/
|
|
73
|
+
isFocused?: boolean;
|
|
32
74
|
/** Whether this tab is disabled. */
|
|
33
75
|
disabled?: boolean;
|
|
76
|
+
/** Extra press handler. Runs on both paths, before any selection is reported. */
|
|
77
|
+
onPress?: () => void;
|
|
34
78
|
style?: StyleProp<ViewStyle>;
|
|
35
79
|
textStyle?: StyleProp<TextStyle>;
|
|
36
80
|
}
|
|
@@ -366,7 +366,7 @@
|
|
|
366
366
|
"--surface-foreground": "rgb(36 26 14)",
|
|
367
367
|
"--popover": "rgb(255 234 215)",
|
|
368
368
|
"--popover-foreground": "rgb(36 26 14)",
|
|
369
|
-
"--primary": "rgb(
|
|
369
|
+
"--primary": "rgb(150 94 0)",
|
|
370
370
|
"--primary-foreground": "rgb(255 255 255)",
|
|
371
371
|
"--secondary": "rgb(0 117 141)",
|
|
372
372
|
"--secondary-foreground": "rgb(255 255 255)",
|
|
@@ -486,8 +486,8 @@
|
|
|
486
486
|
"--surface-foreground": "rgb(33 27 13)",
|
|
487
487
|
"--popover": "rgb(250 236 213)",
|
|
488
488
|
"--popover-foreground": "rgb(33 27 13)",
|
|
489
|
-
"--primary": "rgb(
|
|
490
|
-
"--primary-foreground": "rgb(
|
|
489
|
+
"--primary": "rgb(135 102 0)",
|
|
490
|
+
"--primary-foreground": "rgb(255 255 255)",
|
|
491
491
|
"--secondary": "rgb(0 114 158)",
|
|
492
492
|
"--secondary-foreground": "rgb(255 255 255)",
|
|
493
493
|
"--tertiary": "rgb(117 66 250)",
|
|
@@ -1446,8 +1446,8 @@
|
|
|
1446
1446
|
"--surface-foreground": "rgb(24 29 18)",
|
|
1447
1447
|
"--popover": "rgb(234 240 222)",
|
|
1448
1448
|
"--popover-foreground": "rgb(24 29 18)",
|
|
1449
|
-
"--primary": "rgb(
|
|
1450
|
-
"--primary-foreground": "rgb(
|
|
1449
|
+
"--primary": "rgb(63 120 0)",
|
|
1450
|
+
"--primary-foreground": "rgb(255 255 255)",
|
|
1451
1451
|
"--secondary": "rgb(79 79 255)",
|
|
1452
1452
|
"--secondary-foreground": "rgb(255 255 255)",
|
|
1453
1453
|
"--tertiary": "rgb(190 36 152)",
|
|
@@ -47,14 +47,14 @@ exports[`theme.colors snapshot for all presets (catches unintended palette shift
|
|
|
47
47
|
"contrast50": "rgb(249 229 209)",
|
|
48
48
|
"error": "rgb(203 41 46)",
|
|
49
49
|
"icon": "rgb(84 68 50)",
|
|
50
|
-
"iconActive": "rgb(
|
|
50
|
+
"iconActive": "rgb(150 94 0)",
|
|
51
51
|
"info": "rgb(5 102 217)",
|
|
52
52
|
"negative": "rgb(186 26 26)",
|
|
53
53
|
"negativeForeground": "rgb(255 255 255)",
|
|
54
54
|
"negativeSubtle": "rgba(239, 68, 68, 0.13)",
|
|
55
55
|
"negativeSubtleForeground": "rgb(167 55 53)",
|
|
56
56
|
"overlay": "rgba(0, 0, 0, 0.5)",
|
|
57
|
-
"primary": "rgb(
|
|
57
|
+
"primary": "rgb(150 94 0)",
|
|
58
58
|
"primaryDark": "rgb(255 241 229)",
|
|
59
59
|
"primaryForeground": "rgb(255 255 255)",
|
|
60
60
|
"primaryLight": "rgb(255 241 229)",
|
|
@@ -69,7 +69,7 @@ exports[`theme.colors snapshot for all presets (catches unintended palette shift
|
|
|
69
69
|
"text": "rgb(36 26 14)",
|
|
70
70
|
"textSecondary": "rgb(84 68 50)",
|
|
71
71
|
"textTertiary": "rgb(135 116 96)",
|
|
72
|
-
"tint": "rgb(
|
|
72
|
+
"tint": "rgb(150 94 0)",
|
|
73
73
|
"warning": "rgb(150 94 0)",
|
|
74
74
|
},
|
|
75
75
|
"blue/dark": {
|
|
@@ -187,16 +187,16 @@ exports[`theme.colors snapshot for all presets (catches unintended palette shift
|
|
|
187
187
|
"contrast50": "rgb(228 235 216)",
|
|
188
188
|
"error": "rgb(203 41 46)",
|
|
189
189
|
"icon": "rgb(65 73 57)",
|
|
190
|
-
"iconActive": "rgb(
|
|
190
|
+
"iconActive": "rgb(63 120 0)",
|
|
191
191
|
"info": "rgb(5 102 217)",
|
|
192
192
|
"negative": "rgb(186 26 26)",
|
|
193
193
|
"negativeForeground": "rgb(255 255 255)",
|
|
194
194
|
"negativeSubtle": "rgba(239, 68, 68, 0.13)",
|
|
195
195
|
"negativeSubtleForeground": "rgb(167 55 53)",
|
|
196
196
|
"overlay": "rgba(0, 0, 0, 0.5)",
|
|
197
|
-
"primary": "rgb(
|
|
197
|
+
"primary": "rgb(63 120 0)",
|
|
198
198
|
"primaryDark": "rgb(240 246 227)",
|
|
199
|
-
"primaryForeground": "rgb(
|
|
199
|
+
"primaryForeground": "rgb(255 255 255)",
|
|
200
200
|
"primaryLight": "rgb(240 246 227)",
|
|
201
201
|
"primarySubtle": "rgba(87, 163, 0, 0.13)",
|
|
202
202
|
"primarySubtleForeground": "rgb(40 80 0)",
|
|
@@ -209,7 +209,7 @@ exports[`theme.colors snapshot for all presets (catches unintended palette shift
|
|
|
209
209
|
"text": "rgb(24 29 18)",
|
|
210
210
|
"textSecondary": "rgb(65 73 57)",
|
|
211
211
|
"textTertiary": "rgb(114 122 103)",
|
|
212
|
-
"tint": "rgb(
|
|
212
|
+
"tint": "rgb(63 120 0)",
|
|
213
213
|
"warning": "rgb(150 94 0)",
|
|
214
214
|
},
|
|
215
215
|
"green/dark": {
|
|
@@ -887,16 +887,16 @@ exports[`theme.colors snapshot for all presets (catches unintended palette shift
|
|
|
887
887
|
"contrast50": "rgb(244 231 208)",
|
|
888
888
|
"error": "rgb(203 41 46)",
|
|
889
889
|
"icon": "rgb(79 70 49)",
|
|
890
|
-
"iconActive": "rgb(
|
|
890
|
+
"iconActive": "rgb(135 102 0)",
|
|
891
891
|
"info": "rgb(5 102 217)",
|
|
892
892
|
"negative": "rgb(186 26 26)",
|
|
893
893
|
"negativeForeground": "rgb(255 255 255)",
|
|
894
894
|
"negativeSubtle": "rgba(239, 68, 68, 0.13)",
|
|
895
895
|
"negativeSubtleForeground": "rgb(167 55 53)",
|
|
896
896
|
"overlay": "rgba(0, 0, 0, 0.5)",
|
|
897
|
-
"primary": "rgb(
|
|
897
|
+
"primary": "rgb(135 102 0)",
|
|
898
898
|
"primaryDark": "rgb(255 242 221)",
|
|
899
|
-
"primaryForeground": "rgb(
|
|
899
|
+
"primaryForeground": "rgb(255 255 255)",
|
|
900
900
|
"primaryLight": "rgb(255 242 221)",
|
|
901
901
|
"primarySubtle": "rgba(182, 138, 0, 0.13)",
|
|
902
902
|
"primarySubtleForeground": "rgb(90 67 0)",
|
|
@@ -909,7 +909,7 @@ exports[`theme.colors snapshot for all presets (catches unintended palette shift
|
|
|
909
909
|
"text": "rgb(33 27 13)",
|
|
910
910
|
"textSecondary": "rgb(79 70 49)",
|
|
911
911
|
"textTertiary": "rgb(130 118 95)",
|
|
912
|
-
"tint": "rgb(
|
|
912
|
+
"tint": "rgb(135 102 0)",
|
|
913
913
|
"warning": "rgb(150 94 0)",
|
|
914
914
|
},
|
|
915
915
|
}
|
|
@@ -117,11 +117,12 @@ describe('colour policy legibility', () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
// The
|
|
121
|
-
// the
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
120
|
+
// The two modes want opposite things and the suite has to say which. Applying
|
|
121
|
+
// the budget in LIGHT let a light seed keep its own tone there, so faircoin
|
|
122
|
+
// rendered the same pale lime in both modes — no theme at all. Skipping it in
|
|
123
|
+
// DARK left every Follow button, avatar and chat bubble with a black label.
|
|
124
|
+
// Each half was individually legible, so nothing else could catch either.
|
|
125
|
+
it('the brand fill keeps light exemption-free and dark budgeted', () => {
|
|
125
126
|
const white = { light: 0, dark: 0 };
|
|
126
127
|
for (const preset of APP_COLOR_NAMES) {
|
|
127
128
|
for (const mode of ['light', 'dark'] as const) {
|
|
@@ -130,7 +131,9 @@ describe('colour policy legibility', () => {
|
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
// Light admits no exemption, so every preset carries white there. Dark keeps
|
|
135
|
+
// the budget, so the seeds that are already light keep their colour instead.
|
|
136
|
+
expect(white.light).toBe(APP_COLOR_NAMES.length);
|
|
137
|
+
expect(white.dark).toBeGreaterThan(APP_COLOR_NAMES.length - 4);
|
|
135
138
|
});
|
|
136
139
|
});
|
|
@@ -214,18 +214,19 @@ function vividHueNear(hue: number): number {
|
|
|
214
214
|
* why 11 of the 13 presets carry white and exactly the two light ones do not.
|
|
215
215
|
*/
|
|
216
216
|
function whiteLabelTone(palette: TonalPalette, seedTone: number, isDark: boolean): number {
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
217
|
+
// LIGHT: every fill comes down, no exemption. On a near-white page a bright
|
|
218
|
+
// colour has nothing to read against — a lime at its own tone 90 is a pale
|
|
219
|
+
// smear there, indistinguishable from the same colour in dark, which is not a
|
|
220
|
+
// theme at all. Coming down is what makes faircoin a deep green in light and a
|
|
221
|
+
// bright lime in dark.
|
|
222
|
+
if (!isDark) return LIGHT_FILL_TONE;
|
|
223
|
+
// DARK: the budget applies. Most brands come down to where white fits, but a
|
|
224
|
+
// seed that is already light cannot without ceasing to be itself, so it keeps
|
|
225
|
+
// its own tone and takes a black label. That is the eleven-and-two pattern.
|
|
226
|
+
const candidate = Math.max(DARK_FILL_TONE, seedTone - TONE_BUDGET);
|
|
226
227
|
return contrastOf(palette.tone(candidate), true) >= AA
|
|
227
228
|
? candidate
|
|
228
|
-
: Math.max(seedTone,
|
|
229
|
+
: Math.max(seedTone, DARK_SEED_FLOOR);
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
/**
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
Object.defineProperty(exports, "ScrollRestorationProvider", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _index.ScrollRestorationProvider;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
var _index = require("../scroll/index.js");
|
|
13
|
-
//# sourceMappingURL=scroll-provider.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_index","require"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.ts"],"mappings":";;;;;;;;;;;AAYA,IAAAA,MAAA,GAAAC,OAAA","ignoreList":[]}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
Object.defineProperty(exports, "ScrollRestorationProvider", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _indexWeb.ScrollRestorationProvider;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
var _indexWeb = require("../scroll/index.web.js");
|
|
13
|
-
//# sourceMappingURL=scroll-provider.web.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_indexWeb","require"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.web.ts"],"mappings":";;;;;;;;;;;AACA,IAAAA,SAAA,GAAAC,OAAA","ignoreList":[]}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Native/default binding for the scroll-restoration provider used by
|
|
5
|
-
* {@link BloomProvider}.
|
|
6
|
-
*
|
|
7
|
-
* `@oxyhq/bloom/scroll` is web-forked, so its `browser` export condition hands
|
|
8
|
-
* web consumers the real implementation while native gets the no-op. A compiled
|
|
9
|
-
* `lib/module/provider/index.js` cannot benefit from that condition (it imports
|
|
10
|
-
* a relative path, not the package subpath), so the platform choice is made
|
|
11
|
-
* here by FILENAME instead: Metro picks `scroll-provider.web.ts` on web, and
|
|
12
|
-
* every web bundler that resolves `.web.js` picks the compiled sibling. Same
|
|
13
|
-
* mechanism the toast engine uses for `ToastHost.native.tsx`.
|
|
14
|
-
*/
|
|
15
|
-
export { ScrollRestorationProvider } from "../scroll/index.js";
|
|
16
|
-
//# sourceMappingURL=scroll-provider.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["ScrollRestorationProvider"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,yBAAyB,QAAQ,oBAAW","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["ScrollRestorationProvider"],"sourceRoot":"../../../src","sources":["provider/scroll-provider.web.ts"],"mappings":";;AAAA;AACA,SAASA,yBAAyB,QAAQ,wBAAqB","ignoreList":[]}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Native/default binding for the scroll-restoration provider used by
|
|
3
|
-
* {@link BloomProvider}.
|
|
4
|
-
*
|
|
5
|
-
* `@oxyhq/bloom/scroll` is web-forked, so its `browser` export condition hands
|
|
6
|
-
* web consumers the real implementation while native gets the no-op. A compiled
|
|
7
|
-
* `lib/module/provider/index.js` cannot benefit from that condition (it imports
|
|
8
|
-
* a relative path, not the package subpath), so the platform choice is made
|
|
9
|
-
* here by FILENAME instead: Metro picks `scroll-provider.web.ts` on web, and
|
|
10
|
-
* every web bundler that resolves `.web.js` picks the compiled sibling. Same
|
|
11
|
-
* mechanism the toast engine uses for `ToastHost.native.tsx`.
|
|
12
|
-
*/
|
|
13
|
-
export { ScrollRestorationProvider } from '../scroll';
|
|
14
|
-
//# sourceMappingURL=scroll-provider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-provider.d.ts","sourceRoot":"","sources":["../../../../src/provider/scroll-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-provider.web.d.ts","sourceRoot":"","sources":["../../../../src/provider/scroll-provider.web.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Native/default binding for the scroll-restoration provider used by
|
|
3
|
-
* {@link BloomProvider}.
|
|
4
|
-
*
|
|
5
|
-
* `@oxyhq/bloom/scroll` is web-forked, so its `browser` export condition hands
|
|
6
|
-
* web consumers the real implementation while native gets the no-op. A compiled
|
|
7
|
-
* `lib/module/provider/index.js` cannot benefit from that condition (it imports
|
|
8
|
-
* a relative path, not the package subpath), so the platform choice is made
|
|
9
|
-
* here by FILENAME instead: Metro picks `scroll-provider.web.ts` on web, and
|
|
10
|
-
* every web bundler that resolves `.web.js` picks the compiled sibling. Same
|
|
11
|
-
* mechanism the toast engine uses for `ToastHost.native.tsx`.
|
|
12
|
-
*/
|
|
13
|
-
export { ScrollRestorationProvider } from '../scroll';
|
|
14
|
-
//# sourceMappingURL=scroll-provider.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-provider.d.ts","sourceRoot":"","sources":["../../../../src/provider/scroll-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-provider.web.d.ts","sourceRoot":"","sources":["../../../../src/provider/scroll-provider.web.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|