@oxyhq/bloom 1.10.0 → 1.11.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/docs/layout.mdx +33 -1
- package/lib/commonjs/fab/Fab.js +42 -0
- package/lib/commonjs/fab/Fab.js.map +1 -1
- package/lib/commonjs/fab/Fab.web.js +18 -3
- package/lib/commonjs/fab/Fab.web.js.map +1 -1
- package/lib/commonjs/layout/bottom-edge.js +72 -24
- package/lib/commonjs/layout/bottom-edge.js.map +1 -1
- package/lib/commonjs/layout/index.js +6 -0
- package/lib/commonjs/layout/index.js.map +1 -1
- package/lib/commonjs/tab-bar/TabBarBase.js +22 -6
- package/lib/commonjs/tab-bar/TabBarBase.js.map +1 -1
- package/lib/commonjs/theme/color-scope/ColorScope.web.js +25 -13
- package/lib/commonjs/theme/color-scope/ColorScope.web.js.map +1 -1
- package/lib/module/fab/Fab.js +44 -2
- package/lib/module/fab/Fab.js.map +1 -1
- package/lib/module/fab/Fab.web.js +19 -4
- package/lib/module/fab/Fab.web.js.map +1 -1
- package/lib/module/layout/bottom-edge.js +71 -24
- package/lib/module/layout/bottom-edge.js.map +1 -1
- package/lib/module/layout/index.js +1 -1
- package/lib/module/layout/index.js.map +1 -1
- package/lib/module/tab-bar/TabBarBase.js +24 -8
- package/lib/module/tab-bar/TabBarBase.js.map +1 -1
- package/lib/module/theme/color-scope/ColorScope.web.js +25 -13
- package/lib/module/theme/color-scope/ColorScope.web.js.map +1 -1
- package/lib/typescript/commonjs/fab/Fab.d.ts.map +1 -1
- package/lib/typescript/commonjs/fab/Fab.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/layout/bottom-edge.d.ts +44 -10
- package/lib/typescript/commonjs/layout/bottom-edge.d.ts.map +1 -1
- package/lib/typescript/commonjs/layout/index.d.ts +1 -1
- package/lib/typescript/commonjs/layout/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/tab-bar/TabBarBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-scope/ColorScope.web.d.ts.map +1 -1
- package/lib/typescript/module/fab/Fab.d.ts.map +1 -1
- package/lib/typescript/module/fab/Fab.web.d.ts.map +1 -1
- package/lib/typescript/module/layout/bottom-edge.d.ts +44 -10
- package/lib/typescript/module/layout/bottom-edge.d.ts.map +1 -1
- package/lib/typescript/module/layout/index.d.ts +1 -1
- package/lib/typescript/module/layout/index.d.ts.map +1 -1
- package/lib/typescript/module/tab-bar/TabBarBase.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-scope/ColorScope.web.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/fab/Fab.tsx +41 -3
- package/src/fab/Fab.web.tsx +19 -4
- package/src/layout/bottom-edge.tsx +88 -31
- package/src/layout/index.ts +6 -1
- package/src/tab-bar/TabBarBase.tsx +31 -6
- package/src/theme/color-scope/ColorScope.web.tsx +23 -13
package/src/fab/Fab.web.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import React, {
|
|
|
7
7
|
type MouseEvent,
|
|
8
8
|
} from 'react';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { useBottomEdgeLiveInset } from '../layout/bottom-edge';
|
|
11
11
|
import { useTheme } from '../theme/use-theme';
|
|
12
12
|
import { animation, borderRadius } from '../styles/tokens';
|
|
13
13
|
import { pressedSurface } from '../theme/press-colors';
|
|
@@ -83,8 +83,11 @@ const BLOOM_FAB_CSS = interactiveWebCss({
|
|
|
83
83
|
box-shadow: var(--bloom-fab-shadow);
|
|
84
84
|
font-family: inherit;
|
|
85
85
|
`,
|
|
86
|
+
// `bottom` is the collapse follow (see `placementStyle`) and is deliberately
|
|
87
|
+
// the slow one: it tracks the tab bar's 380ms minimize spring, while every
|
|
88
|
+
// other property here is interaction feedback and wants to feel instant.
|
|
86
89
|
transition:
|
|
87
|
-
'opacity 120ms ease, transform 120ms ease, box-shadow 160ms ease, background-color 120ms ease',
|
|
90
|
+
'opacity 120ms ease, transform 120ms ease, box-shadow 160ms ease, background-color 120ms ease, bottom 380ms ease',
|
|
88
91
|
// A FAB lifts on hover rather than dimming: it floats over the content, so a
|
|
89
92
|
// deeper shadow is the affordance an opacity dip cannot express.
|
|
90
93
|
hover: { declarations: 'box-shadow: var(--bloom-fab-shadow-hover);' },
|
|
@@ -129,6 +132,16 @@ const BLOOM_FAB_CSS = interactiveWebCss({
|
|
|
129
132
|
* behind it. A z-index cannot fix that pairing: the bar's host is the last
|
|
130
133
|
* sibling of the app shell and paints over every descendant, so the FAB has to be
|
|
131
134
|
* somewhere else, not merely on top.
|
|
135
|
+
*
|
|
136
|
+
* It is the LIVE inset here, and `bottom` is transitioned — where the native fork
|
|
137
|
+
* follows a collapsing bar with an Animated TRANSFORM instead. The forks differ
|
|
138
|
+
* because the constraint does. Native must stay off the JS thread mid-scroll, so
|
|
139
|
+
* the follow has to be a native-driver transform. On web a transform cannot carry
|
|
140
|
+
* it: `:active` already sets `transform: scale(...)` from the shared
|
|
141
|
+
* `interactive-web-css` rule, and an inline transform would beat that stylesheet
|
|
142
|
+
* rule and eat the press scale — and one property cannot hold the press's 120ms
|
|
143
|
+
* and the collapse's 380ms at once. `bottom` is a separate property with its own
|
|
144
|
+
* duration, and it moves twice per scroll gesture rather than per frame.
|
|
132
145
|
*/
|
|
133
146
|
function placementStyle(
|
|
134
147
|
placement: FabPlacement,
|
|
@@ -197,7 +210,9 @@ const FabWebComponent: React.FC<FabProps> = ({
|
|
|
197
210
|
type = 'button',
|
|
198
211
|
}) => {
|
|
199
212
|
useInteractiveWebCss(STYLE_ID, BLOOM_FAB_CSS);
|
|
200
|
-
|
|
213
|
+
// LIVE, not reserved: the FAB sits ON the edge, so it rides down with a
|
|
214
|
+
// minimizing tab bar rather than leaving a 14px hole above it.
|
|
215
|
+
const bottomEdgeLive = useBottomEdgeLiveInset();
|
|
201
216
|
const theme = useTheme();
|
|
202
217
|
const reactId = useId();
|
|
203
218
|
const resolvedId = id ?? `bloom-fab-${reactId}`;
|
|
@@ -232,7 +247,7 @@ const FabWebComponent: React.FC<FabProps> = ({
|
|
|
232
247
|
variantColors.foreground,
|
|
233
248
|
),
|
|
234
249
|
['--bloom-fab-press-scale' as string]: animation.pressScale,
|
|
235
|
-
...placementStyle(placement, offset,
|
|
250
|
+
...placementStyle(placement, offset, bottomEdgeLive),
|
|
236
251
|
};
|
|
237
252
|
if (isExtended) {
|
|
238
253
|
const pad = sizeConfig.diameter <= 44 ? 14 : 20;
|
|
@@ -27,6 +27,21 @@
|
|
|
27
27
|
* edge, so two surfaces at that edge overlap rather than stack; the tallest is
|
|
28
28
|
* what a new surface has to clear. Summing would strand it at twice the height.
|
|
29
29
|
*
|
|
30
|
+
* ## Two numbers, because there are two questions
|
|
31
|
+
*
|
|
32
|
+
* A surface that collapses — the tab bar minimizes 58 -> 44 on scroll — occupies
|
|
33
|
+
* less than it reserves. Readers want different halves of that:
|
|
34
|
+
*
|
|
35
|
+
* - RESERVED (`useBottomEdgeInset`) never shrinks while the claimant is
|
|
36
|
+
* mounted. It is what must be kept permanently free: a list's bottom
|
|
37
|
+
* padding, a toast stack's offset. Tracking the collapse here would jitter a
|
|
38
|
+
* list's content size on every scroll and jump a toast 14px mid-display.
|
|
39
|
+
* - LIVE (`useBottomEdgeLiveInset`) follows the collapse. It is what a surface
|
|
40
|
+
* SITTING ON the edge wants, so it rides down with the bar instead of
|
|
41
|
+
* leaving a hole.
|
|
42
|
+
*
|
|
43
|
+
* A claimant that never collapses passes one number and both answers agree.
|
|
44
|
+
*
|
|
30
45
|
* ## Why an external store
|
|
31
46
|
*
|
|
32
47
|
* The claim set is mutable state living outside React. Reading it in a memoized
|
|
@@ -45,33 +60,46 @@ import {
|
|
|
45
60
|
type PropsWithChildren,
|
|
46
61
|
} from 'react';
|
|
47
62
|
|
|
63
|
+
interface Claim {
|
|
64
|
+
/** What the surface keeps permanently free, collapsed or not. */
|
|
65
|
+
reserved: number;
|
|
66
|
+
/** What it occupies right now. */
|
|
67
|
+
current: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
interface BottomEdgeStore {
|
|
49
71
|
subscribe: (onChange: () => void) => () => void;
|
|
50
72
|
/**
|
|
51
|
-
* The cached
|
|
52
|
-
* — `useSyncExternalStore` re-renders forever if the snapshot is
|
|
53
|
-
* per call
|
|
73
|
+
* The cached totals. Each returns the SAME number until a claim actually
|
|
74
|
+
* changes it — `useSyncExternalStore` re-renders forever if the snapshot is
|
|
75
|
+
* recomputed per call, and a reader of one channel must not re-render when
|
|
76
|
+
* only the other moved.
|
|
54
77
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
getReserved: () => number;
|
|
79
|
+
getLive: () => number;
|
|
80
|
+
claim: (id: string, reserved: number, current: number) => void;
|
|
57
81
|
release: (id: string) => void;
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
function createBottomEdgeStore(): BottomEdgeStore {
|
|
61
|
-
const claims = new Map<string,
|
|
85
|
+
const claims = new Map<string, Claim>();
|
|
62
86
|
const listeners = new Set<() => void>();
|
|
63
|
-
let
|
|
87
|
+
let reserved = 0;
|
|
88
|
+
let live = 0;
|
|
64
89
|
|
|
65
90
|
const recompute = () => {
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
let nextReserved = 0;
|
|
92
|
+
let nextLive = 0;
|
|
93
|
+
for (const claim of claims.values()) {
|
|
94
|
+
if (claim.reserved > nextReserved) nextReserved = claim.reserved;
|
|
95
|
+
if (claim.current > nextLive) nextLive = claim.current;
|
|
69
96
|
}
|
|
70
|
-
// Bail before notifying: a re-registration at
|
|
97
|
+
// Bail before notifying: a re-registration at unchanged heights (every
|
|
71
98
|
// render of a claimant whose footprint did not move) must not re-render
|
|
72
99
|
// every reader.
|
|
73
|
-
if (
|
|
74
|
-
|
|
100
|
+
if (nextReserved === reserved && nextLive === live) return;
|
|
101
|
+
reserved = nextReserved;
|
|
102
|
+
live = nextLive;
|
|
75
103
|
for (const listener of listeners) listener();
|
|
76
104
|
};
|
|
77
105
|
|
|
@@ -82,10 +110,12 @@ function createBottomEdgeStore(): BottomEdgeStore {
|
|
|
82
110
|
listeners.delete(onChange);
|
|
83
111
|
};
|
|
84
112
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
claims.
|
|
113
|
+
getReserved: () => reserved,
|
|
114
|
+
getLive: () => live,
|
|
115
|
+
claim(id, nextReserved, nextCurrent) {
|
|
116
|
+
const existing = claims.get(id);
|
|
117
|
+
if (existing?.reserved === nextReserved && existing.current === nextCurrent) return;
|
|
118
|
+
claims.set(id, { reserved: nextReserved, current: nextCurrent });
|
|
89
119
|
recompute();
|
|
90
120
|
},
|
|
91
121
|
release(id) {
|
|
@@ -116,13 +146,15 @@ const NO_SUBSCRIPTION = () => () => {};
|
|
|
116
146
|
const NO_INSET = () => 0;
|
|
117
147
|
|
|
118
148
|
/**
|
|
119
|
-
* How much of the bottom edge is
|
|
149
|
+
* How much of the bottom edge is permanently RESERVED, in px.
|
|
120
150
|
*
|
|
121
|
-
*
|
|
151
|
+
* Never shrinks while its claimant is mounted, so it is the number for anything
|
|
152
|
+
* that must not move as the user scrolls — a list's bottom padding, a toast
|
|
153
|
+
* stack's offset:
|
|
122
154
|
*
|
|
123
155
|
* ```tsx
|
|
124
|
-
* const
|
|
125
|
-
* <
|
|
156
|
+
* const reserved = useBottomEdgeInset();
|
|
157
|
+
* <FlatList contentContainerStyle={{ paddingBottom: reserved + 12 }} />
|
|
126
158
|
* ```
|
|
127
159
|
*
|
|
128
160
|
* `0` outside a provider, and `0` on the first commit even inside one — a claim
|
|
@@ -134,29 +166,54 @@ export function useBottomEdgeInset(): number {
|
|
|
134
166
|
const store = useContext(BottomEdgeContext);
|
|
135
167
|
return useSyncExternalStore(
|
|
136
168
|
store?.subscribe ?? NO_SUBSCRIPTION,
|
|
137
|
-
store?.
|
|
138
|
-
store?.
|
|
169
|
+
store?.getReserved ?? NO_INSET,
|
|
170
|
+
store?.getReserved ?? NO_INSET,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* How much of the bottom edge is occupied RIGHT NOW, in px — the same number as
|
|
176
|
+
* {@link useBottomEdgeInset} for a surface that does not collapse, and smaller
|
|
177
|
+
* while one does.
|
|
178
|
+
*
|
|
179
|
+
* This is what a surface sitting ON the edge wants, so it rides down with a
|
|
180
|
+
* minimizing tab bar rather than leaving a hole above it. It changes when the
|
|
181
|
+
* collapse STATE changes, not per frame: a claimant reports its settled
|
|
182
|
+
* footprint and the reader animates the difference itself, which keeps the
|
|
183
|
+
* motion on whatever animation system that reader already uses instead of
|
|
184
|
+
* forcing a shared one through React state.
|
|
185
|
+
*/
|
|
186
|
+
export function useBottomEdgeLiveInset(): number {
|
|
187
|
+
const store = useContext(BottomEdgeContext);
|
|
188
|
+
return useSyncExternalStore(
|
|
189
|
+
store?.subscribe ?? NO_SUBSCRIPTION,
|
|
190
|
+
store?.getLive ?? NO_INSET,
|
|
191
|
+
store?.getLive ?? NO_INSET,
|
|
139
192
|
);
|
|
140
193
|
}
|
|
141
194
|
|
|
142
195
|
/**
|
|
143
|
-
* Claim
|
|
196
|
+
* Claim the bottom edge for as long as the caller is mounted.
|
|
144
197
|
*
|
|
145
198
|
* The claimant owns its own placement — claiming does not move it. It declares
|
|
146
|
-
* the space it occupies so that everything reading
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
199
|
+
* the space it occupies so that everything reading the edge stays off it. Pass
|
|
200
|
+
* the FULL footprint (the surface's height plus the gap it holds off the window
|
|
201
|
+
* edge), which is the same number the surface positions itself with.
|
|
202
|
+
*
|
|
203
|
+
* `current` is what the surface occupies right now; it defaults to `reserved`,
|
|
204
|
+
* which is correct for anything that does not collapse. A surface that shrinks
|
|
205
|
+
* on scroll passes its live footprint as `current` and keeps `reserved` at its
|
|
206
|
+
* full size — see the two channels above for why both are needed.
|
|
150
207
|
*
|
|
151
208
|
* A no-op outside a provider, so a surface stays usable standalone.
|
|
152
209
|
*/
|
|
153
|
-
export function useClaimBottomEdge(
|
|
210
|
+
export function useClaimBottomEdge(reserved: number, current: number = reserved): void {
|
|
154
211
|
const store = useContext(BottomEdgeContext);
|
|
155
212
|
const id = useId();
|
|
156
213
|
|
|
157
214
|
useEffect(() => {
|
|
158
215
|
if (!store) return;
|
|
159
|
-
store.claim(id,
|
|
216
|
+
store.claim(id, reserved, current);
|
|
160
217
|
return () => store.release(id);
|
|
161
|
-
}, [store, id,
|
|
218
|
+
}, [store, id, reserved, current]);
|
|
162
219
|
}
|
package/src/layout/index.ts
CHANGED
|
@@ -5,4 +5,9 @@
|
|
|
5
5
|
* parked there". A surface that floats at the bottom needs both.
|
|
6
6
|
*/
|
|
7
7
|
export { EDGE_GAP, windowEdgeGap } from './edge';
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
BottomEdgeProvider,
|
|
10
|
+
useBottomEdgeInset,
|
|
11
|
+
useBottomEdgeLiveInset,
|
|
12
|
+
useClaimBottomEdge,
|
|
13
|
+
} from './bottom-edge';
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
useContext,
|
|
21
21
|
useEffect,
|
|
22
22
|
useMemo,
|
|
23
|
+
useState,
|
|
23
24
|
type ComponentType,
|
|
24
25
|
} from 'react';
|
|
25
26
|
import { Pressable, StyleSheet, useWindowDimensions, View, type ViewStyle } from 'react-native';
|
|
@@ -29,6 +30,7 @@ import Animated, {
|
|
|
29
30
|
interpolate,
|
|
30
31
|
interpolateColor,
|
|
31
32
|
runOnJS,
|
|
33
|
+
useAnimatedReaction,
|
|
32
34
|
useAnimatedStyle,
|
|
33
35
|
useSharedValue,
|
|
34
36
|
withSpring,
|
|
@@ -422,13 +424,36 @@ function TabBarBody({
|
|
|
422
424
|
// its own layout can never drift from where the bar actually sits.
|
|
423
425
|
const bottomOffset = windowEdgeGap(insets.bottom);
|
|
424
426
|
|
|
427
|
+
// Whether the bar has settled into its minimized size, as REACT state.
|
|
428
|
+
//
|
|
429
|
+
// Reacting on `target` rather than on `progress` is what keeps this cheap:
|
|
430
|
+
// `target` is the binary 0/1 the minimize spring is heading for, so this fires
|
|
431
|
+
// twice per scroll gesture instead of once per frame. Crossing `progress > 0.5`
|
|
432
|
+
// would work too and would cost the same, but it would report the change
|
|
433
|
+
// halfway through the animation rather than when it was decided.
|
|
434
|
+
const [isMinimized, setIsMinimized] = useState(false);
|
|
435
|
+
useAnimatedReaction(
|
|
436
|
+
() => minimized.target.value,
|
|
437
|
+
(target, previous) => {
|
|
438
|
+
if (target !== previous) runOnJS(setIsMinimized)(target === 1);
|
|
439
|
+
},
|
|
440
|
+
[minimized],
|
|
441
|
+
);
|
|
442
|
+
|
|
425
443
|
// Publish what the bar occupies so anything else at this edge stacks above it
|
|
426
|
-
// rather than behind it. Same
|
|
427
|
-
// the same
|
|
428
|
-
// where the bar actually sits.
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
|
|
444
|
+
// rather than behind it. Same numbers `useTabBarFootprint` reports, derived
|
|
445
|
+
// from the same geometry, so a consumer reading either can never disagree with
|
|
446
|
+
// where the bar actually sits.
|
|
447
|
+
//
|
|
448
|
+
// RESERVED stays the EXPANDED height: the bar re-expands the moment the user
|
|
449
|
+
// scrolls back up, so anything that permanently reserves space (a list's
|
|
450
|
+
// bottom padding, a toast) must keep room for the full pill or it would jitter
|
|
451
|
+
// on every scroll. LIVE follows the minimize, so a surface sitting ON the edge
|
|
452
|
+
// rides down with the bar instead of leaving a 14px hole above it.
|
|
453
|
+
useClaimBottomEdge(
|
|
454
|
+
bottomOffset + EXPANDED_HEIGHT,
|
|
455
|
+
bottomOffset + (isMinimized ? MINIMIZED_HEIGHT : EXPANDED_HEIGHT),
|
|
456
|
+
);
|
|
432
457
|
|
|
433
458
|
// How centring and the animated inset compose: centring is STATIC and belongs
|
|
434
459
|
// to the wrap, the inset stays ANIMATED on the pill inside it. The wrap is
|
|
@@ -35,13 +35,21 @@ export interface BloomColorScopeProps {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* On web, the single `asChild` child
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* react-native-web
|
|
42
|
-
*
|
|
43
|
-
* numeric indices as keys and
|
|
44
|
-
*
|
|
38
|
+
* On web, the single `asChild` child can be either kind of element, and the two
|
|
39
|
+
* kinds want different `style` shapes:
|
|
40
|
+
*
|
|
41
|
+
* - A react-native-web component (RN `<View>`, `<Pressable>`) may already
|
|
42
|
+
* carry a style ARRAY or a numeric registered-style id. Spreading that into
|
|
43
|
+
* an object literal copies its numeric indices as keys, and RNW then commits
|
|
44
|
+
* `0`, `1`, … to the DOM. RNW flattens arrays, so it gets an array.
|
|
45
|
+
* - Anything that ends on a DOM element — an `<a>`, a router `<Link>`, any
|
|
46
|
+
* component that forwards `style` to its host node — hands the prop straight
|
|
47
|
+
* to React DOM, which walks the own keys of whatever it is given. An array
|
|
48
|
+
* there throws `Failed to set an indexed property [0] on
|
|
49
|
+
* 'CSSStyleDeclaration'` during commit, blanking the tree.
|
|
50
|
+
*
|
|
51
|
+
* So the array form is used only when the child's own style is already
|
|
52
|
+
* RN-shaped; every other child gets a plain object, which both runtimes accept.
|
|
45
53
|
*/
|
|
46
54
|
type WebStyle =
|
|
47
55
|
| React.CSSProperties
|
|
@@ -96,13 +104,15 @@ export function BloomColorScope({
|
|
|
96
104
|
'BloomColorScope with `asChild` requires a single React element child that accepts a `style` prop.',
|
|
97
105
|
);
|
|
98
106
|
}
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
// object literal would copy numeric indices as keys and crash RNW.
|
|
107
|
+
// Scope vars first, then the caller's `style`, then the child's own so its
|
|
108
|
+
// explicit styles win. The shape follows the child: an array only when the
|
|
109
|
+
// child's style is already RN-shaped (see the note above), an object
|
|
110
|
+
// otherwise — an array reaching a DOM node throws on commit.
|
|
104
111
|
const childStyle = child.props.style;
|
|
105
|
-
const
|
|
112
|
+
const childIsRnStyled = Array.isArray(childStyle) || typeof childStyle === 'number';
|
|
113
|
+
const mergedStyle: WebStyle = childIsRnStyled
|
|
114
|
+
? [varsStyle, style, childStyle]
|
|
115
|
+
: { ...varsStyle, ...style, ...(childStyle || undefined) };
|
|
106
116
|
content = cloneElement(child, { style: mergedStyle });
|
|
107
117
|
} else {
|
|
108
118
|
// A plain DOM `<div>` does NOT accept style arrays — only the cloned child
|