panelui-native 0.82.0 → 0.84.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/lib/module/components/bottom-sheet/index.js +111 -16
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/circular-text/circular-text-geometry.js +63 -0
- package/lib/module/components/circular-text/circular-text-geometry.js.map +1 -0
- package/lib/module/components/circular-text/index.js +146 -0
- package/lib/module/components/circular-text/index.js.map +1 -0
- package/lib/module/components/image-generation/dot-field.js +66 -9
- package/lib/module/components/image-generation/dot-field.js.map +1 -1
- package/lib/module/components/image-generation/index.js +65 -23
- package/lib/module/components/image-generation/index.js.map +1 -1
- package/lib/module/components/marquee/index.js +12 -5
- package/lib/module/components/marquee/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +15 -417
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/popover/index.js +119 -7
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/icons/index.js +24 -4
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/index.js +52 -0
- package/lib/module/native/index.js.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +17 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/circular-text/circular-text-geometry.d.ts +39 -0
- package/lib/typescript/src/components/circular-text/circular-text-geometry.d.ts.map +1 -0
- package/lib/typescript/src/components/circular-text/index.d.ts +69 -0
- package/lib/typescript/src/components/circular-text/index.d.ts.map +1 -0
- package/lib/typescript/src/components/image-generation/dot-field.d.ts +36 -1
- package/lib/typescript/src/components/image-generation/dot-field.d.ts.map +1 -1
- package/lib/typescript/src/components/image-generation/index.d.ts +16 -8
- package/lib/typescript/src/components/image-generation/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +10 -202
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/popover/index.d.ts +25 -1
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +66 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/bottom-sheet/index.tsx +139 -16
- package/src/components/circular-text/circular-text-geometry.ts +80 -0
- package/src/components/circular-text/index.tsx +217 -0
- package/src/components/image-generation/dot-field.ts +65 -9
- package/src/components/image-generation/index.tsx +67 -18
- package/src/components/marquee/index.tsx +12 -5
- package/src/components/panelside/index.tsx +16 -604
- package/src/components/popover/index.tsx +146 -4
- package/src/icons/index.tsx +23 -2
- package/src/index.ts +1 -6
- package/src/native/index.ts +100 -0
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* and jump into place.
|
|
31
31
|
*/
|
|
32
32
|
import {
|
|
33
|
+
Children,
|
|
33
34
|
cloneElement,
|
|
34
35
|
createContext,
|
|
35
36
|
isValidElement,
|
|
@@ -62,6 +63,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
62
63
|
import { FocusRestorePortal } from '../../primitives/portal';
|
|
63
64
|
import { Scrim } from '../../primitives/scrim';
|
|
64
65
|
import { useBackHandler } from '../../hooks/use-back-handler';
|
|
66
|
+
import { getSwiftUI } from '../../native';
|
|
65
67
|
import { BottomSheet } from '../bottom-sheet';
|
|
66
68
|
import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
67
69
|
import { cn } from '../../utils/cn';
|
|
@@ -116,6 +118,8 @@ interface PopoverContextValue {
|
|
|
116
118
|
setArrowOffset: (offset: number) => void;
|
|
117
119
|
/** Whether Content is presenting as a bottom sheet — Arrow is null then. */
|
|
118
120
|
presentation: PopoverPresentation;
|
|
121
|
+
/** Whether the platform is drawing the panel. Trigger and Content both read it. */
|
|
122
|
+
native: boolean;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
export type PopoverPresentation = 'popover' | 'bottom-sheet';
|
|
@@ -176,6 +180,30 @@ export interface PopoverProps {
|
|
|
176
180
|
* sheet.
|
|
177
181
|
*/
|
|
178
182
|
presentation?: PopoverPresentation;
|
|
183
|
+
/**
|
|
184
|
+
* Present the platform's own popover instead of this one. Requires the
|
|
185
|
+
* optional `@expo/ui`.
|
|
186
|
+
*
|
|
187
|
+
* **iOS only.** SwiftUI has a popover that anchors to a view and keeps its
|
|
188
|
+
* anchored shape on a phone rather than becoming a sheet; Compose's nearest
|
|
189
|
+
* relative is a dropdown menu, which is a different control with different
|
|
190
|
+
* rules. Android and web keep the styled panel, as does an iOS device
|
|
191
|
+
* without `@expo/ui` installed.
|
|
192
|
+
*
|
|
193
|
+
* **The platform draws the container, so theme tokens do not reach it.** The
|
|
194
|
+
* panel's surface, its corner radius, its shadow and its arrow are the
|
|
195
|
+
* system's; `className` on `Popover.Content` styles what is *inside* it.
|
|
196
|
+
* `align`, `offset`, `alignOffset`, `scrim` and `blur` have no native
|
|
197
|
+
* equivalent and are ignored; `placement` becomes the edge the arrow is
|
|
198
|
+
* asked for.
|
|
199
|
+
*
|
|
200
|
+
* **Give the content a `width`.** The platform sizes its popover to what is
|
|
201
|
+
* hosted in it, and a React Native subtree with no width of its own has
|
|
202
|
+
* nothing to report — the same rule that governs every hosted view.
|
|
203
|
+
* `Popover.Content` defaults to a sensible one under `native`, but a panel
|
|
204
|
+
* whose rows need more room should say so.
|
|
205
|
+
*/
|
|
206
|
+
native?: boolean;
|
|
179
207
|
}
|
|
180
208
|
|
|
181
209
|
function PopoverRoot({
|
|
@@ -184,6 +212,7 @@ function PopoverRoot({
|
|
|
184
212
|
onOpenChange,
|
|
185
213
|
defaultOpen = false,
|
|
186
214
|
presentation = 'popover',
|
|
215
|
+
native = false,
|
|
187
216
|
}: PopoverProps) {
|
|
188
217
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
189
218
|
const [trigger, setTrigger] = useState<TriggerRect | null>(null);
|
|
@@ -201,6 +230,16 @@ function PopoverRoot({
|
|
|
201
230
|
[isControlled, onOpenChange]
|
|
202
231
|
);
|
|
203
232
|
|
|
233
|
+
/*
|
|
234
|
+
* The platform's popover, where it is reachable and the caller asked for it.
|
|
235
|
+
*
|
|
236
|
+
* A sheet presentation is left alone: `native` names which popover to draw,
|
|
237
|
+
* and asking for both a sheet and a popover is a contradiction rather than a
|
|
238
|
+
* combination.
|
|
239
|
+
*/
|
|
240
|
+
const swiftUI = native && presentation === 'popover' ? getSwiftUI() : null;
|
|
241
|
+
const nativeActive = swiftUI !== null;
|
|
242
|
+
|
|
204
243
|
const context = useMemo(
|
|
205
244
|
() => ({
|
|
206
245
|
open: resolvedOpen,
|
|
@@ -212,11 +251,95 @@ function PopoverRoot({
|
|
|
212
251
|
arrowOffset,
|
|
213
252
|
setArrowOffset,
|
|
214
253
|
presentation,
|
|
254
|
+
native: nativeActive,
|
|
215
255
|
}),
|
|
216
|
-
[resolvedOpen, setOpen, trigger, placement, arrowOffset, presentation]
|
|
256
|
+
[resolvedOpen, setOpen, trigger, placement, arrowOffset, presentation, nativeActive]
|
|
217
257
|
);
|
|
218
258
|
|
|
219
|
-
return
|
|
259
|
+
return (
|
|
260
|
+
<PopoverContext.Provider value={context}>
|
|
261
|
+
{swiftUI ? <NativePopover swiftUI={swiftUI}>{children}</NativePopover> : children}
|
|
262
|
+
</PopoverContext.Provider>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Default width for a hosted panel, in points. Room for a short menu row. */
|
|
267
|
+
const NATIVE_PANEL_WIDTH = 240;
|
|
268
|
+
|
|
269
|
+
/** `placement` in the platform's vocabulary. */
|
|
270
|
+
const ARROW_EDGE = {
|
|
271
|
+
top: 'top',
|
|
272
|
+
bottom: 'bottom',
|
|
273
|
+
left: 'leading',
|
|
274
|
+
right: 'trailing',
|
|
275
|
+
} as const;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The platform's popover, with our trigger and our content hosted inside it.
|
|
279
|
+
*
|
|
280
|
+
* SwiftUI attaches a popover to a view, so the two halves this component keeps
|
|
281
|
+
* as siblings have to become parent and child: the trigger is what the panel
|
|
282
|
+
* points at, and the platform will not anchor to something it cannot see. So
|
|
283
|
+
* the children are read here and placed into the two slots the platform
|
|
284
|
+
* expects, rather than the caller having to write a different tree under
|
|
285
|
+
* `native` than without it.
|
|
286
|
+
*
|
|
287
|
+
* Both halves are React Native, so both are wrapped in the host view that lets
|
|
288
|
+
* the platform measure them. The trigger is given no press handling of its
|
|
289
|
+
* own — `Popover.Trigger` already toggles the state this reads, and the
|
|
290
|
+
* platform presents from that.
|
|
291
|
+
*/
|
|
292
|
+
function NativePopover({
|
|
293
|
+
swiftUI,
|
|
294
|
+
children,
|
|
295
|
+
}: {
|
|
296
|
+
swiftUI: NonNullable<ReturnType<typeof getSwiftUI>>;
|
|
297
|
+
children: ReactNode;
|
|
298
|
+
}) {
|
|
299
|
+
const { open, setOpen } = usePopover('Popover');
|
|
300
|
+
const { Host, RNHostView, Popover: PlatformPopover } = swiftUI;
|
|
301
|
+
|
|
302
|
+
let trigger: ReactNode = null;
|
|
303
|
+
let content: ReactElement<PopoverContentProps> | null = null;
|
|
304
|
+
|
|
305
|
+
for (const child of Children.toArray(children)) {
|
|
306
|
+
if (!isValidElement(child)) continue;
|
|
307
|
+
if (child.type === PopoverTrigger) trigger = child;
|
|
308
|
+
else if (child.type === PopoverContent) {
|
|
309
|
+
content = child as ReactElement<PopoverContentProps>;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const contentProps = content?.props;
|
|
314
|
+
const width =
|
|
315
|
+
typeof contentProps?.width === 'number' ? contentProps.width : NATIVE_PANEL_WIDTH;
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<Host matchContents ignoreSafeArea="keyboard">
|
|
319
|
+
<PlatformPopover
|
|
320
|
+
isPresented={open}
|
|
321
|
+
onIsPresentedChange={setOpen}
|
|
322
|
+
arrowEdge={ARROW_EDGE[contentProps?.placement ?? 'bottom']}
|
|
323
|
+
>
|
|
324
|
+
<PlatformPopover.Trigger>
|
|
325
|
+
<RNHostView matchContents>{trigger}</RNHostView>
|
|
326
|
+
</PlatformPopover.Trigger>
|
|
327
|
+
<PlatformPopover.Content>
|
|
328
|
+
<RNHostView matchContents>
|
|
329
|
+
{/*
|
|
330
|
+
An explicit width, not a class. Inside the host there is no
|
|
331
|
+
parent for a percentage or a flex basis to resolve against, so a
|
|
332
|
+
panel that does not state its width reports none and the platform
|
|
333
|
+
sizes its popover to nothing.
|
|
334
|
+
*/}
|
|
335
|
+
<View style={{ width }} className={cn('gap-1 p-2', contentProps?.className)}>
|
|
336
|
+
{textChildren(contentProps?.children)}
|
|
337
|
+
</View>
|
|
338
|
+
</RNHostView>
|
|
339
|
+
</PlatformPopover.Content>
|
|
340
|
+
</PlatformPopover>
|
|
341
|
+
</Host>
|
|
342
|
+
);
|
|
220
343
|
}
|
|
221
344
|
|
|
222
345
|
export interface PopoverTriggerProps {
|
|
@@ -231,7 +354,7 @@ export interface PopoverTriggerProps {
|
|
|
231
354
|
* and only a wrapper we own is guaranteed to be measurable.
|
|
232
355
|
*/
|
|
233
356
|
function PopoverTrigger({ children }: PopoverTriggerProps) {
|
|
234
|
-
const { open, setOpen, setTrigger } = usePopover('Popover.Trigger');
|
|
357
|
+
const { open, setOpen, setTrigger, native } = usePopover('Popover.Trigger');
|
|
235
358
|
const ref = useRef<View>(null);
|
|
236
359
|
|
|
237
360
|
const measureThenToggle = (...args: unknown[]) => {
|
|
@@ -242,6 +365,17 @@ function PopoverTrigger({ children }: PopoverTriggerProps) {
|
|
|
242
365
|
return;
|
|
243
366
|
}
|
|
244
367
|
|
|
368
|
+
/*
|
|
369
|
+
* Nothing to measure when the platform is drawing the panel: it anchors to
|
|
370
|
+
* this trigger itself, and asking a view hosted inside the native tree for
|
|
371
|
+
* its window coordinates is a callback that may never come back — which
|
|
372
|
+
* would leave the press doing nothing at all.
|
|
373
|
+
*/
|
|
374
|
+
if (native) {
|
|
375
|
+
setOpen(true);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
245
379
|
// Measured on every open rather than on layout: the trigger may have
|
|
246
380
|
// scrolled since it was laid out, and a stale rect anchors the panel to
|
|
247
381
|
// where the trigger used to be.
|
|
@@ -363,7 +497,8 @@ function PopoverContent({
|
|
|
363
497
|
...props
|
|
364
498
|
}: PopoverContentProps) {
|
|
365
499
|
const context = usePopover('Popover.Content');
|
|
366
|
-
const { open, setOpen, trigger, setPlacement, setArrowOffset, presentation } =
|
|
500
|
+
const { open, setOpen, trigger, setPlacement, setArrowOffset, presentation, native } =
|
|
501
|
+
context;
|
|
367
502
|
|
|
368
503
|
// The anchored panel owns the Android back button while it is up. The sheet
|
|
369
504
|
// presentation is left alone — BottomSheet installs its own handler.
|
|
@@ -509,6 +644,13 @@ function PopoverContent({
|
|
|
509
644
|
* the surface it is merged into the sheet's own padding classes and replaces
|
|
510
645
|
* them, so a panel asking for `p-3` silently strips the sheet's `px-5 pt-2`.
|
|
511
646
|
*/
|
|
647
|
+
/*
|
|
648
|
+
* Already drawn. Under `native` the root reads this element's props and
|
|
649
|
+
* hosts its children inside the platform's own popover, so rendering the
|
|
650
|
+
* styled panel here as well would put a second one on the screen.
|
|
651
|
+
*/
|
|
652
|
+
if (native) return null;
|
|
653
|
+
|
|
512
654
|
if (presentation === 'bottom-sheet') {
|
|
513
655
|
return (
|
|
514
656
|
<BottomSheet open={open} onOpenChange={setOpen}>
|
package/src/icons/index.tsx
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* outline could only ignore. All four are drawn here rather than mapped.
|
|
51
51
|
*/
|
|
52
52
|
import { createContext, useContext, type ReactNode } from 'react';
|
|
53
|
+
import { View } from 'react-native';
|
|
53
54
|
import { HugeiconsIcon, type IconSvgElement } from '@hugeicons/react-native';
|
|
54
55
|
import Svg, { G, Path, type SvgProps } from 'react-native-svg';
|
|
55
56
|
import { useDirection } from '../hooks/use-direction';
|
|
@@ -208,17 +209,37 @@ function icon(glyph: IconSvgElement, defaults: IconDefaults) {
|
|
|
208
209
|
}: ToggleIconProps) {
|
|
209
210
|
const resolved = useResolvedColor(color, defaults.color);
|
|
210
211
|
const rtl = useDirection() === 'rtl';
|
|
211
|
-
|
|
212
|
+
|
|
213
|
+
const drawing = (
|
|
212
214
|
<HugeiconsIcon
|
|
213
215
|
icon={glyph}
|
|
214
216
|
size={size}
|
|
215
217
|
color={resolved}
|
|
216
218
|
strokeWidth={strokeWidth ?? defaults.strokeWidth ?? STROKE}
|
|
217
219
|
fill={defaults.fillable && filled ? resolved : 'none'}
|
|
218
|
-
style={defaults.flip ?
|
|
220
|
+
style={defaults.flip ? undefined : style}
|
|
219
221
|
{...props}
|
|
220
222
|
/>
|
|
221
223
|
);
|
|
224
|
+
|
|
225
|
+
if (!defaults.flip) return drawing;
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* The mirror goes on a view around the glyph, not on the glyph.
|
|
229
|
+
*
|
|
230
|
+
* The drawing component takes a `style` prop and drops it: it destructures
|
|
231
|
+
* `style` out of its props and never puts it back, and the interop layer
|
|
232
|
+
* that would otherwise have carried it is a package this library does not
|
|
233
|
+
* use. A transform handed to it is silently discarded, which is a chevron
|
|
234
|
+
* that keeps pointing right in a right-to-left row — and a failure with
|
|
235
|
+
* nothing to see, since every other prop on the same element does arrive.
|
|
236
|
+
*
|
|
237
|
+
* A plain view cannot lose it. It also puts the caller's own `style` back
|
|
238
|
+
* on the outside, where it is applied rather than thrown away.
|
|
239
|
+
*/
|
|
240
|
+
return (
|
|
241
|
+
<View style={[{ transform: [{ scaleX: rtl ? -1 : 1 }] }, style]}>{drawing}</View>
|
|
242
|
+
);
|
|
222
243
|
}
|
|
223
244
|
|
|
224
245
|
return Icon;
|
package/src/index.ts
CHANGED
|
@@ -421,6 +421,7 @@ export {
|
|
|
421
421
|
type ChipVariant,
|
|
422
422
|
type ChipSize,
|
|
423
423
|
} from './components/chip';
|
|
424
|
+
export { CircularText, type CircularTextProps } from './components/circular-text';
|
|
424
425
|
export {
|
|
425
426
|
Collapsible,
|
|
426
427
|
type CollapsibleProps,
|
|
@@ -557,12 +558,6 @@ export {
|
|
|
557
558
|
type PanelsidePageProps,
|
|
558
559
|
type PanelsideTriggerProps,
|
|
559
560
|
type PanelsideSearchTriggerProps,
|
|
560
|
-
type PanelsideSearchSheetProps,
|
|
561
|
-
type PanelsideSearchTabsProps,
|
|
562
|
-
type PanelsideSearchTabProps,
|
|
563
|
-
type PanelsideSearchResultsProps,
|
|
564
|
-
type PanelsideSearchResultProps,
|
|
565
|
-
type PanelsideSearchFieldProps,
|
|
566
561
|
type PanelsideMode,
|
|
567
562
|
type PanelsideSwipeFrom,
|
|
568
563
|
type PanelsideItemSize,
|
package/src/native/index.ts
CHANGED
|
@@ -145,6 +145,75 @@ interface SwiftUIModifiers {
|
|
|
145
145
|
cornerRadius?: number
|
|
146
146
|
) => unknown;
|
|
147
147
|
controlSize: (size: 'mini' | 'small' | 'regular' | 'large' | 'extraLarge') => unknown;
|
|
148
|
+
/**
|
|
149
|
+
* Paints a sheet's own surface, rather than a background behind its content.
|
|
150
|
+
*
|
|
151
|
+
* It is the only way out of the material a sheet is drawn in by default, and
|
|
152
|
+
* it reaches what an ordinary background cannot: the grabber's strip at the
|
|
153
|
+
* top and the safe-area inset at the bottom, which belong to the sheet's
|
|
154
|
+
* chrome and not to anything hosted inside it.
|
|
155
|
+
*
|
|
156
|
+
* **iOS 16.4 and up.** Below that it is inert and the sheet keeps its
|
|
157
|
+
* material — the same shape as glass being inert below iOS 26, and the same
|
|
158
|
+
* trap: indistinguishable from the prop not working. Check the OS before
|
|
159
|
+
* changing any code on a report of "the colour did nothing".
|
|
160
|
+
*/
|
|
161
|
+
presentationBackground: (color: string) => unknown;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The SwiftUI-only components, for the ones the universal set does not carry.
|
|
166
|
+
*
|
|
167
|
+
* `@expo/ui` exports a portable component for everything both toolkits agree
|
|
168
|
+
* on, and that is what `getNativeUI` reaches. A popover is not on that list:
|
|
169
|
+
* SwiftUI has one that anchors to a view and adapts itself on a compact
|
|
170
|
+
* screen, and Compose's nearest relative is a dropdown menu, which is a
|
|
171
|
+
* different control with different rules. Rather than pretend the two are one
|
|
172
|
+
* thing, the iOS one is reached here and Android keeps the styled panel.
|
|
173
|
+
*/
|
|
174
|
+
interface SwiftUIComponents {
|
|
175
|
+
Host: ComponentType<{
|
|
176
|
+
children?: ReactNode;
|
|
177
|
+
matchContents?: boolean | { vertical?: boolean; horizontal?: boolean };
|
|
178
|
+
ignoreSafeArea?: unknown;
|
|
179
|
+
style?: unknown;
|
|
180
|
+
}>;
|
|
181
|
+
RNHostView: ComponentType<{ children?: ReactNode; matchContents?: boolean }>;
|
|
182
|
+
Popover: ComponentType<{
|
|
183
|
+
children?: ReactNode;
|
|
184
|
+
isPresented?: boolean;
|
|
185
|
+
onIsPresentedChange?: (isPresented: boolean) => void;
|
|
186
|
+
attachmentAnchor?: 'leading' | 'trailing' | 'center' | 'top' | 'bottom';
|
|
187
|
+
arrowEdge?: 'leading' | 'trailing' | 'top' | 'bottom' | 'none';
|
|
188
|
+
}> & {
|
|
189
|
+
Trigger: ComponentType<{ children?: ReactNode }>;
|
|
190
|
+
Content: ComponentType<{ children?: ReactNode }>;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let swiftUIResolved = false;
|
|
195
|
+
let swiftUI: SwiftUIComponents | null = null;
|
|
196
|
+
|
|
197
|
+
export function getSwiftUI(): SwiftUIComponents | null {
|
|
198
|
+
if (swiftUIResolved) return swiftUI;
|
|
199
|
+
swiftUIResolved = true;
|
|
200
|
+
|
|
201
|
+
if (Platform.OS !== 'ios') return null;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
205
|
+
const module = require('@expo/ui/swift-ui') as Partial<SwiftUIComponents>;
|
|
206
|
+
// A version without the popover is not an error; it is a fallback to the
|
|
207
|
+
// styled panel, which is what every missing native path here does.
|
|
208
|
+
swiftUI =
|
|
209
|
+
module.Host && module.RNHostView && module.Popover
|
|
210
|
+
? (module as SwiftUIComponents)
|
|
211
|
+
: null;
|
|
212
|
+
} catch {
|
|
213
|
+
swiftUI = null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return swiftUI;
|
|
148
217
|
}
|
|
149
218
|
|
|
150
219
|
let modifiersResolved = false;
|
|
@@ -165,3 +234,34 @@ export function getSwiftUIModifiers(): SwiftUIModifiers | null {
|
|
|
165
234
|
|
|
166
235
|
return modifiers;
|
|
167
236
|
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The Compose view modifiers — the other half of the same door.
|
|
240
|
+
*
|
|
241
|
+
* Android's toolkit has its own modifier system and its own vocabulary, so a
|
|
242
|
+
* modifier built for one platform is not a modifier the other can read. Where
|
|
243
|
+
* both are asked the same question the answers are written separately, here and
|
|
244
|
+
* above, rather than one being sent to both.
|
|
245
|
+
*/
|
|
246
|
+
interface ComposeModifiers {
|
|
247
|
+
background: (color: string) => unknown;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let composeResolved = false;
|
|
251
|
+
let compose: ComposeModifiers | null = null;
|
|
252
|
+
|
|
253
|
+
export function getComposeModifiers(): ComposeModifiers | null {
|
|
254
|
+
if (composeResolved) return compose;
|
|
255
|
+
composeResolved = true;
|
|
256
|
+
|
|
257
|
+
if (Platform.OS !== 'android') return null;
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
261
|
+
compose = require('@expo/ui/jetpack-compose/modifiers') as ComposeModifiers;
|
|
262
|
+
} catch {
|
|
263
|
+
compose = null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return compose;
|
|
267
|
+
}
|