From d1d7751a1bcddc5f2d3839603b7754f8c4614c98 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Fri, 12 Jun 2026 00:37:39 +0200 Subject: [PATCH 1/4] [ios][e2e] Fix iOS 26 e2e timeout: video fullscreen exit & test-suite deep link (#46796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why The `ios-test-e2e` job has been hitting the 40-minute step timeout (e.g. [this run](https://github.com/expo/expo/actions/runs/27332214136/job/80752810210)). It isn't a hang — the slow Xcode 26.4.1 / iOS 26.4 / iPhone 17 Pro simulator (boot ~135s, Maestro driver connect ~200s, flows 100–340s) is amplified by two flows that fail and burn their retry budgets until the clock runs out. Confirmed both via the run's debug screenshots. # How Two independent root causes, two commits: **1. expo-video fullscreen exit (`[ios][video]`)** — failed all 3 attempts. The flow double-tapped the top-left corner to dismiss the native `AVPlayerViewController`. On iOS 26 the player controls auto-hide, so tapping the corner while they're hidden does nothing and the app stays in fullscreen (the failure screenshot shows Big Buck Bunny still fullscreen, no chrome). Fix: tap the center first to reliably reveal the controls, then tap the close button. Android is unchanged. The exact close-button coordinate may still need a CI tweak — see Test Plan. **2. test-suite `run?tests=` deep link (`[test-suite]`)** — failed 4× before the timeout. `SelectScreen.handleOpenURL` only recognized `/select/` and `/all` paths and fell through to `setModules()` (the selection list) for `run?tests=` URLs. On a cold start it raced React Navigation's linking and could win, leaving the app on the selection list instead of the run screen (confirmed by the failure screenshot). Fix: parse the `tests` query and navigate to the run screen explicitly. The expo-image `header-Appearance` blip in the same run recovered on retry (image comparisons passed) and is not addressed here. # Test Plan These only run on CI (no local iOS 26 simulator). Validating by re-running the `ios-test-e2e` job on this branch: - `expo-video/fullscreen-test` reaches `1 = onFullscreenExit` instead of staying fullscreen. If the reveal-then-dismiss still misses the close button, the top-left coordinate (`11%,10%`) will be tuned in a follow-up. - `maestro-generated` (native modules) shows the run screen (`test_suite_selection_query_text` = "Basic") instead of the selection list. - Overall job completes under the 40-minute step timeout. --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .../e2e/expo-video/fullscreen-test.yaml | 62 ++++++++++++------- .../e2e/expo-video/playback-test.yaml | 12 +++- apps/bare-expo/scripts/lib/e2e-common.ts | 11 ++-- .../scripts/start-android-e2e-test.ts | 4 +- apps/bare-expo/scripts/start-ios-e2e-test.ts | 12 ++-- apps/test-suite/screens/SelectScreen.tsx | 14 +++++ 6 files changed, 77 insertions(+), 38 deletions(-) diff --git a/apps/bare-expo/e2e/expo-video/fullscreen-test.yaml b/apps/bare-expo/e2e/expo-video/fullscreen-test.yaml index d2648c044db9eb..80c358c3f4ad19 100644 --- a/apps/bare-expo/e2e/expo-video/fullscreen-test.yaml +++ b/apps/bare-expo/e2e/expo-video/fullscreen-test.yaml @@ -4,29 +4,45 @@ jsEngine: graaljs --- - openLink: bareexpo://components/video/fullscreen -- tapOn: "Enter Fullscreen" +- tapOn: 'Enter Fullscreen' - runFlow: - when: - platform: Android - commands: - # Dismiss the android system fullscreen instruction popup - - tapOn: - text: "Got it" - optional: true - - assertVisible: - id: "dev.expo.payments:id/exo_content_frame" -- runFlow: - when: - platform: iOS - commands: - - assertVisible: "Video" + when: + platform: Android + commands: + # Dismiss the android system fullscreen instruction popup + - tapOn: + text: 'Got it' + optional: true + - assertVisible: + id: 'dev.expo.payments:id/exo_content_frame' + +# Once the fullscreen player is shown it covers the in-app button, so this confirms +# we actually entered fullscreen on both platforms. (On iOS the native player title +# is not asserted because its controls auto-hide and would flake on slow simulators.) +- assertNotVisible: 'Enter Fullscreen' -- assertNotVisible: "Enter Fullscreen" -- tapOn: - point: '${maestro.platform == "android" ? "94%,97%" : "11%,10%"}' - delay: 400 - repeat: 2 +# Exit fullscreen. Android has a dedicated exit button in the bottom-right of the +# custom fullscreen activity. +- runFlow: + when: + platform: Android + commands: + - tapOn: + point: 94%,97% + delay: 400 + repeat: 2 +# iOS uses the native AVPlayerViewController chrome, whose controls auto-hide. Tap the +# center to reveal them, then tap the close button in the top-left exactly once: a +# second tap would land on the screen's back button and navigate away from the test. +- runFlow: + when: + platform: iOS + commands: + - tapOn: + point: 50%,50% + - tapOn: + point: 11%,10% -- assertVisible: "Enter Fullscreen" -- assertVisible: "0 = onFullscreenEnter" -- assertVisible: "1 = onFullscreenExit" +- assertVisible: 'Enter Fullscreen' +- assertVisible: '0 = onFullscreenEnter' +- assertVisible: '1 = onFullscreenExit' diff --git a/apps/bare-expo/e2e/expo-video/playback-test.yaml b/apps/bare-expo/e2e/expo-video/playback-test.yaml index c780f57e19f321..a136277d873b1d 100644 --- a/apps/bare-expo/e2e/expo-video/playback-test.yaml +++ b/apps/bare-expo/e2e/expo-video/playback-test.yaml @@ -20,12 +20,22 @@ jsEngine: graaljs - tapOn: Play -- assertVisible: 'isPlaying = true' +# Playback takes a moment to start after tapping Play; wait instead of +# asserting instantly to avoid a startup race. +- extendedWaitUntil: + visible: 'isPlaying = true' + timeout: 10000 - assertVisible: 'isAtStart = false' - tapOn: Pause - tapOn: Seek to 30s - assertVisible: 'currentTime = 30' +# The player reports currentTime = 30 before the paused surface repaints the +# seeked frame. Wait for the screen to settle so the screenshot captures the +# t=30s frame and not a stale/blank one. Safe only because the player is paused +# here (a playing video never settles and would burn the full timeout). +- waitForAnimationToEnd: + timeout: 5000 - runFlow: file: ../_nested-flows/viewshot-comparison.yaml env: diff --git a/apps/bare-expo/scripts/lib/e2e-common.ts b/apps/bare-expo/scripts/lib/e2e-common.ts index 796fa9dbdb4e59..ea67aff49f8656 100644 --- a/apps/bare-expo/scripts/lib/e2e-common.ts +++ b/apps/bare-expo/scripts/lib/e2e-common.ts @@ -63,10 +63,13 @@ jsEngine: graaljs for (const testCase of testCases) { contents.push(`\ - openLink: bareexpo://test-suite/run?tests=${testCase} -# make sure we're running the right test -- assertVisible: - id: "test_suite_selection_query_text" - text: "${testCase}" +# make sure we're running the right test. Wait rather than assert instantly: after +# clearState the app cold-starts, and the selection header may not have rendered yet. +- extendedWaitUntil: + visible: + id: "test_suite_selection_query_text" + text: "${testCase}" + timeout: 30000 - extendedWaitUntil: visible: id: "test_suite_text_results" diff --git a/apps/bare-expo/scripts/start-android-e2e-test.ts b/apps/bare-expo/scripts/start-android-e2e-test.ts index 6292fe6fc90978..ab5df54befc5fb 100755 --- a/apps/bare-expo/scripts/start-android-e2e-test.ts +++ b/apps/bare-expo/scripts/start-android-e2e-test.ts @@ -120,7 +120,7 @@ async function testAsync( }, }); console.timeEnd(TEST_DURATION_LABEL); - } catch { + } catch (error: unknown) { stopLogCollectionController.abort(); console.timeEnd(TEST_DURATION_LABEL); console.warn(`\n⚠️ Maestro flow failed, because:\n\n`); @@ -140,7 +140,7 @@ async function testAsync( } } console.log('\n\n'); - throw new Error('e2e tests have failed.'); + throw new Error('e2e tests have failed.', { cause: error }); } finally { endGroup(); stopLogCollectionController.abort(); diff --git a/apps/bare-expo/scripts/start-ios-e2e-test.ts b/apps/bare-expo/scripts/start-ios-e2e-test.ts index f5807252b0b756..b2c6f3bc9cf9b3 100755 --- a/apps/bare-expo/scripts/start-ios-e2e-test.ts +++ b/apps/bare-expo/scripts/start-ios-e2e-test.ts @@ -159,9 +159,8 @@ async function startSimulatorAsync(deviceId: string, timeout: number = 180_000) ); const label = 'device startup duration'; console.time(label); - const bootProc = spawnAsync('xcrun', ['simctl', 'bootstatus', deviceId, '-b'], { - stdio: 'inherit', - }); + // Capture (don't inherit) stdio so the verbose boot/data-migration progress doesn't flood the logs. + const bootProc = spawnAsync('xcrun', ['simctl', 'bootstatus', deviceId, '-b']); let timeoutHandle: NodeJS.Timeout | null = null; const timeoutPromise = new Promise((_, reject) => { @@ -239,7 +238,7 @@ async function testAsync( }, }); console.timeEnd(TEST_DURATION_LABEL); - } catch { + } catch (error: unknown) { stopLogCollectionController.abort(); console.timeEnd(TEST_DURATION_LABEL); console.warn(`\n⚠️ Maestro flow failed, because:\n\n`); @@ -262,11 +261,8 @@ async function testAsync( } console.log('\n\n'); - throw new Error('e2e tests have failed.'); + throw new Error('e2e tests have failed.', { cause: error }); } - } catch (e: unknown) { - console.error('Uncaught Error', e); - throw e; } finally { endGroup(); stopLogCollectionController.abort(); diff --git a/apps/test-suite/screens/SelectScreen.tsx b/apps/test-suite/screens/SelectScreen.tsx index db494a3e29f308..645176f98e83df 100644 --- a/apps/test-suite/screens/SelectScreen.tsx +++ b/apps/test-suite/screens/SelectScreen.tsx @@ -87,6 +87,20 @@ export default function SelectScreen({ navigation }) { ({ url }: { url: string }) => { url = url || ''; // TODO: Use Expo Linking library once parseURL is implemented for web + + // Run a specific set of tests, e.g. bareexpo://test-suite/run?tests=basic,blur + // React Navigation linking also maps this URL to the run screen, but on a cold + // start the two race and this handler used to win by falling through to the + // selection list below. Handle the query explicitly so the deep link always runs. + const testsQueryMatch = url.match(/[?&]tests=([^&]+)/); + + if (testsQueryMatch) { + const tests = getSelectedTestNames(decodeURIComponent(testsQueryMatch[1])); + const query = createQueryString(tests); + navigation.navigate(routeNames.run, { tests: query }); + return; + } + if (url.includes(`/${routeNames.select}/`)) { const selectedTests = url.split('/').pop(); if (selectedTests) { From c4c7fd8173ab5c51804ef8336150e818a25363ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nishan=20=28o=5E=E2=96=BD=5Eo=29?= Date: Fri, 12 Jun 2026 06:58:47 +0530 Subject: [PATCH 2/4] [ui] Fix BottomSheet blocking touches after dismiss (#46805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why The community `BottomSheet` host is absolutely positioned over the screen at all times. After the sheet is dismissed it kept intercepting touches in the area the sheet used to occupy, so controls behind it stopped responding. Closes #45810. # How Set `pointerEvents="none"` on the `Host` wrapper (iOS + Android). The native sheet is presented in its own window, so it still receives touches while open — the anchor host just no longer blocks anything behind it. # Test Plan Repro from #45810: open the sheet, close via `dismiss()` / `close()` / `snapToIndex(-1)`, then tap a control where the sheet was — it's interactive again. Verified on iOS and Android with a bare-expo Tester screen. --- packages/expo-ui/CHANGELOG.md | 1 + .../expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx | 2 +- packages/expo-ui/src/community/bottom-sheet/BottomSheet.ios.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md index 209b5a8d81161a..2848796ccdeaee 100644 --- a/packages/expo-ui/CHANGELOG.md +++ b/packages/expo-ui/CHANGELOG.md @@ -33,6 +33,7 @@ ### 🐛 Bug fixes +- [iOS][android] Fix `community/bottom-sheet` blocking touches behind the sheet after it's dismissed. ([#46805](https://github.com/expo/expo/pull/46805) by [@nishan](https://github.com/intergalacticspacehighway)) - [android] Fix React Native touchables (e.g. `Pressable`) on `community/pager-view` pages not responding, or triggering the wrong page's handler, after navigating between pages. ([#46778](https://github.com/expo/expo/pull/46778) by [@nishan](https://github.com/intergalacticspacehighway)) - [iOS] Fix `font`, `dynamicTypeSize`, and `resizable` modifiers not applying to the SwiftUI `Image`. SF Symbols scale with Dynamic Type when a `font` modifier sets a `textStyle`. ([#46714](https://github.com/expo/expo/pull/46714) by [@ramonclaudio](https://github.com/ramonclaudio)) - [android] Fix React Native `ScrollView` nested scrolling inside `BottomSheet`. ([#46544](https://github.com/expo/expo/pull/46544) by [@nishan](https://github.com/intergalacticspacehighway)) diff --git a/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx b/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx index 0018c10813843d..21670912b78b07 100644 --- a/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx +++ b/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx @@ -197,7 +197,7 @@ export function BottomSheet(props: BottomSheetProps) { return ( - + - + Date: Fri, 12 Jun 2026 09:30:05 +0700 Subject: [PATCH 3/4] [ui][android] Fix ModalBottomSheet expand race with Compose handler binding (#46367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why Fixes #46302. Opening `@expo/ui` `BottomSheet` / `BottomSheetModal` with multiple snap points on Android throws one of two errors, both originating from the same race in the community wrapper: ``` Error: Call to function 'ModalBottomSheetView.expand' has been rejected. → No handler registered for AsyncFunction 'expand' on view 'ModalBottomSheetView' ``` ``` Error: Call to function 'ModalBottomSheetView.partialExpand' has been rejected. → Unable to find ComposeFunctionHolder view with tag N ``` `BottomSheet.android.tsx` had a `useEffect` keyed on `[hasMultipleSnapPoints, isOpen, maxIndex]` that called `sheetRef.current?.expand()` / `partialExpand()` right after `isOpen` flipped true. The AsyncFunction handlers for those methods are registered inside the native `Content { }` block via `DisposableEffect`, which runs only on first composition. The JS `useEffect` ran between view-attach and that first composition, so the dispatcher fired before the handler was bound → first error. The same effect could re-fire while a view was disposing → dispatch to a stale view tag → second error. (Note for #46302: there's no prebuilt AAR shipped — the `local-maven-repo/` directory that appears in `node_modules/@expo/ui/` is Gradle's local build cache, populated from the actual `.kt` sources on first build. The source-only fix in this PR is sufficient.) # How - Add a new `initialFullyExpanded: Boolean` prop to `ModalBottomSheetViewProps` on the native `ModalBottomSheetView`, and to the `@expo/ui/jetpack-compose` `ModalBottomSheet` primitive. - Apply the prop inside a `LaunchedEffect(Unit)` declared **after** the `ModalBottomSheet(…)` composable call, so it runs after Material3's internal show effect. Material3's `animateTo(PartiallyExpanded)` starts first, then our `animateTo(Expanded)` cancels and retargets via `animateTo`'s `MutatorMutex`. Final state: `Expanded`. - The partially-expanded state remains structurally present, so `partialExpand()` and drag-down still work after opening at the max snap point — distinct from `skipPartiallyExpanded`, which permanently removes the partial state. - In `BottomSheet.android.tsx`, delete the racing `useEffect` entirely and pass the new `initialFullyExpanded` prop instead. ### Behavior note The deleted `useEffect` was also (incidentally) re-firing when `snapPoints` changed while the sheet was open, yanking the user back to partial each time. With this patch, mid-open `snapPoints` mutation preserves the current state — net improvement. # Test Plan Manual smoke test on Android (`apps/native-component-list` via `apps/bare-expo`): - `BottomSheetScreen.android.tsx` has a new **"Initial fully expanded"** toggle. Ran all 4 combinations with **"Skip partially expanded"**: - OFF × OFF → opens at partial, no crash - OFF × **ON** (the previously-crashing path) → opens at full, drag-down to partial works, no crash - ON × OFF → opens at full (single-state), no crash - ON × ON → opens at full (skip semantic dominates), no crash - Close → reopen at maxIndex multiple times, no second-open crash. - Swipe-dismiss during the initial expand animation — `CancellationException` caught, no unhandled rejection. - Mid-session `snapToIndex(0)` after opening at maxIndex animates back to partial. # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --------- Co-authored-by: Aman Mittal Co-authored-by: nishan (o^▽^o) --- .../screens/UI/BottomSheetScreen.android.tsx | 11 +++++ .../sdk/ui/jetpack-compose/bottomsheet.mdx | 46 +++++++++++++++++++ .../sdk/ui/jetpack-compose/bottomsheet.mdx | 46 +++++++++++++++++++ .../expo-ui/jetpack-compose/bottomsheet.json | 2 +- .../expo-ui/jetpack-compose/bottomsheet.json | 2 +- packages/expo-ui/CHANGELOG.md | 1 + .../expo/modules/ui/ModalBottomSheetView.kt | 14 ++++++ .../bottom-sheet/BottomSheet.android.d.ts.map | 2 +- .../ModalBottomSheet/index.d.ts | 6 +++ .../ModalBottomSheet/index.d.ts.map | 2 +- .../bottom-sheet/BottomSheet.android.tsx | 13 +----- .../ModalBottomSheet/index.tsx | 7 +++ 12 files changed, 136 insertions(+), 16 deletions(-) diff --git a/apps/native-component-list/src/screens/UI/BottomSheetScreen.android.tsx b/apps/native-component-list/src/screens/UI/BottomSheetScreen.android.tsx index f0f47fbdacca37..7d8456601a75a5 100644 --- a/apps/native-component-list/src/screens/UI/BottomSheetScreen.android.tsx +++ b/apps/native-component-list/src/screens/UI/BottomSheetScreen.android.tsx @@ -44,6 +44,7 @@ export default function BottomSheetScreen() { // Configurable props const [skipPartiallyExpanded, setSkipPartiallyExpanded] = React.useState(false); + const [initialFullyExpanded, setInitialFullyExpanded] = React.useState(false); const [showDragHandle, setShowDragHandle] = React.useState(true); const [useCustomDragHandle, setUseCustomDragHandle] = React.useState(false); const [sheetGesturesEnabled, setSheetGesturesEnabled] = React.useState(true); @@ -91,6 +92,15 @@ export default function BottomSheetScreen() { + + + Initial fully expanded + + + setShowSheet(false)} skipPartiallyExpanded={skipPartiallyExpanded} + initialFullyExpanded={initialFullyExpanded} showDragHandle={showDragHandle} sheetGesturesEnabled={sheetGesturesEnabled} properties={{ diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx index fefd3b4614780a..4731fc1ef84153 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx @@ -111,6 +111,52 @@ export default function SkipPartiallyExpandedExample() { } ``` +### Initial fully expanded state + +When `initialFullyExpanded` is `true`, the sheet opens directly in the fully expanded state on first composition while leaving the partial state reachable. Unlike `skipPartiallyExpanded`, the user can still drag down to the partial state. The `partialExpand()` method also continues to work. + +```tsx InitialFullyExpandedExample.tsx +import { useRef, useState } from 'react'; +import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; +import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; +import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; + +export default function InitialFullyExpandedExample() { + const [visible, setVisible] = useState(false); + const sheetRef = useRef(null); + + const hideSheet = async () => { + await sheetRef.current?.hide(); + setVisible(false); + }; + + return ( + + + {visible && ( + setVisible(false)} + initialFullyExpanded> + + This sheet opened fully expanded. + You can still drag it down to the partial state. + + + + + )} + + ); +} +``` + ### Custom colors Use `containerColor`, `contentColor`, and `scrimColor` to customize the sheet's appearance. diff --git a/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/bottomsheet.mdx b/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/bottomsheet.mdx index fa0484b0138f25..ebb8134698547f 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/bottomsheet.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/bottomsheet.mdx @@ -111,6 +111,52 @@ export default function SkipPartiallyExpandedExample() { } ``` +### Initial fully expanded state + +When `initialFullyExpanded` is `true`, the sheet opens directly in the fully expanded state on first composition while leaving the partial state reachable. Unlike `skipPartiallyExpanded`, the user can still drag down to the partial state. The `partialExpand()` method also continues to work. + +```tsx InitialFullyExpandedExample.tsx +import { useRef, useState } from 'react'; +import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; +import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; +import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; + +export default function InitialFullyExpandedExample() { + const [visible, setVisible] = useState(false); + const sheetRef = useRef(null); + + const hideSheet = async () => { + await sheetRef.current?.hide(); + setVisible(false); + }; + + return ( + + + {visible && ( + setVisible(false)} + initialFullyExpanded> + + This sheet opened fully expanded. + You can still drag it down to the partial state. + + + + + )} + + ); +} +``` + ### Custom colors Use `containerColor`, `contentColor`, and `scrimColor` to customize the sheet's appearance. diff --git a/docs/public/static/data/unversioned/expo-ui/jetpack-compose/bottomsheet.json b/docs/public/static/data/unversioned/expo-ui/jetpack-compose/bottomsheet.json index e787829240cd74..00b30997e801ed 100644 --- a/docs/public/static/data/unversioned/expo-ui/jetpack-compose/bottomsheet.json +++ b/docs/public/static/data/unversioned/expo-ui/jetpack-compose/bottomsheet.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-ui/jetpack-compose/bottomsheet","variant":"project","kind":1,"children":[{"name":"ModalBottomSheetProperties","variant":"declaration","kind":2097152,"children":[{"name":"shouldDismissOnBackPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by pressing the back button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"shouldDismissOnClickOutside","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by clicking outside (on the scrim)."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The children of the "},{"kind":"code","text":"`ModalBottomSheet`"},{"kind":"text","text":" component.\nCan include a "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot for a custom drag handle."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"containerColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The background color of the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"contentColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The preferred color of the content inside the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Modifiers for the component."}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onDismissRequest","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Callback function that is called when the user dismisses the bottom sheet\n(via swipe, back press, or tapping outside the scrim)."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"properties","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Properties for the modal window behavior."}]},"type":{"type":"reference","name":"ModalBottomSheetProperties","package":"@expo/ui"}},{"name":"ref","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Can be used to imperatively hide the bottom sheet with an animation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.Ref"},"typeArguments":[{"type":"reference","name":"ModalBottomSheetRef","package":"@expo/ui"}],"name":"Ref","package":"@types/react","qualifiedName":"React.Ref"}},{"name":"scrimColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The color of the scrim overlay behind the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"sheetGesturesEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether gestures (swipe to dismiss) are enabled on the bottom sheet."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"showDragHandle","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to show the default drag handle at the top of the bottom sheet.\nIgnored if a custom "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot is provided."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"skipPartiallyExpanded","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Immediately opens the bottom sheet in full screen."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetRef","variant":"declaration","kind":2097152,"children":[{"name":"expand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically expands the bottom sheet to full height with an animation."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"hide","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically hides the bottom sheet with an animation.\nThe returned promise resolves after the dismiss animation completes."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"partialExpand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically collapses the bottom sheet to partially expanded (~50%) state.\nOnly works when "},{"kind":"code","text":"`skipPartiallyExpanded`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":"."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}}]},{"name":"ModalBottomSheet","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"query","queryType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/jetpack-compose/ModalBottomSheet/index.tsx","qualifiedName":"ModalBottomSheetComponent"},"name":"ModalBottomSheetComponent","package":"@expo/ui","preferValues":true}},"defaultValue":"ModalBottomSheetComponent"}],"packageName":"@expo/ui"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-ui/jetpack-compose/bottomsheet","variant":"project","kind":1,"children":[{"name":"ModalBottomSheetProperties","variant":"declaration","kind":2097152,"children":[{"name":"shouldDismissOnBackPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by pressing the back button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"shouldDismissOnClickOutside","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by clicking outside (on the scrim)."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The children of the "},{"kind":"code","text":"`ModalBottomSheet`"},{"kind":"text","text":" component.\nCan include a "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot for a custom drag handle."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"containerColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The background color of the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"contentColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The preferred color of the content inside the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"initialFullyExpanded","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Opens the sheet fully expanded on first composition. Ignored when "},{"kind":"code","text":"`skipPartiallyExpanded`"},{"kind":"text","text":" is "},{"kind":"code","text":"`true`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Modifiers for the component."}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onDismissRequest","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Callback function that is called when the user dismisses the bottom sheet\n(via swipe, back press, or tapping outside the scrim)."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"properties","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Properties for the modal window behavior."}]},"type":{"type":"reference","name":"ModalBottomSheetProperties","package":"@expo/ui"}},{"name":"ref","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Can be used to imperatively hide the bottom sheet with an animation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.Ref"},"typeArguments":[{"type":"reference","name":"ModalBottomSheetRef","package":"@expo/ui"}],"name":"Ref","package":"@types/react","qualifiedName":"React.Ref"}},{"name":"scrimColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The color of the scrim overlay behind the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"sheetGesturesEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether gestures (swipe to dismiss) are enabled on the bottom sheet."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"showDragHandle","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to show the default drag handle at the top of the bottom sheet.\nIgnored if a custom "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot is provided."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"skipPartiallyExpanded","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Immediately opens the bottom sheet in full screen."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetRef","variant":"declaration","kind":2097152,"children":[{"name":"expand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically expands the bottom sheet to full height with an animation."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"hide","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically hides the bottom sheet with an animation.\nThe returned promise resolves after the dismiss animation completes."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"partialExpand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically collapses the bottom sheet to partially expanded (~50%) state.\nOnly works when "},{"kind":"code","text":"`skipPartiallyExpanded`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":"."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}}]},{"name":"ModalBottomSheet","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"query","queryType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/jetpack-compose/ModalBottomSheet/index.tsx","qualifiedName":"ModalBottomSheetComponent"},"name":"ModalBottomSheetComponent","package":"@expo/ui","preferValues":true}},"defaultValue":"ModalBottomSheetComponent"}],"packageName":"@expo/ui"} \ No newline at end of file diff --git a/docs/public/static/data/v56.0.0/expo-ui/jetpack-compose/bottomsheet.json b/docs/public/static/data/v56.0.0/expo-ui/jetpack-compose/bottomsheet.json index e787829240cd74..00b30997e801ed 100644 --- a/docs/public/static/data/v56.0.0/expo-ui/jetpack-compose/bottomsheet.json +++ b/docs/public/static/data/v56.0.0/expo-ui/jetpack-compose/bottomsheet.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-ui/jetpack-compose/bottomsheet","variant":"project","kind":1,"children":[{"name":"ModalBottomSheetProperties","variant":"declaration","kind":2097152,"children":[{"name":"shouldDismissOnBackPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by pressing the back button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"shouldDismissOnClickOutside","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by clicking outside (on the scrim)."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The children of the "},{"kind":"code","text":"`ModalBottomSheet`"},{"kind":"text","text":" component.\nCan include a "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot for a custom drag handle."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"containerColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The background color of the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"contentColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The preferred color of the content inside the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Modifiers for the component."}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onDismissRequest","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Callback function that is called when the user dismisses the bottom sheet\n(via swipe, back press, or tapping outside the scrim)."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"properties","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Properties for the modal window behavior."}]},"type":{"type":"reference","name":"ModalBottomSheetProperties","package":"@expo/ui"}},{"name":"ref","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Can be used to imperatively hide the bottom sheet with an animation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.Ref"},"typeArguments":[{"type":"reference","name":"ModalBottomSheetRef","package":"@expo/ui"}],"name":"Ref","package":"@types/react","qualifiedName":"React.Ref"}},{"name":"scrimColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The color of the scrim overlay behind the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"sheetGesturesEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether gestures (swipe to dismiss) are enabled on the bottom sheet."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"showDragHandle","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to show the default drag handle at the top of the bottom sheet.\nIgnored if a custom "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot is provided."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"skipPartiallyExpanded","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Immediately opens the bottom sheet in full screen."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetRef","variant":"declaration","kind":2097152,"children":[{"name":"expand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically expands the bottom sheet to full height with an animation."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"hide","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically hides the bottom sheet with an animation.\nThe returned promise resolves after the dismiss animation completes."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"partialExpand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically collapses the bottom sheet to partially expanded (~50%) state.\nOnly works when "},{"kind":"code","text":"`skipPartiallyExpanded`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":"."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}}]},{"name":"ModalBottomSheet","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"query","queryType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/jetpack-compose/ModalBottomSheet/index.tsx","qualifiedName":"ModalBottomSheetComponent"},"name":"ModalBottomSheetComponent","package":"@expo/ui","preferValues":true}},"defaultValue":"ModalBottomSheetComponent"}],"packageName":"@expo/ui"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-ui/jetpack-compose/bottomsheet","variant":"project","kind":1,"children":[{"name":"ModalBottomSheetProperties","variant":"declaration","kind":2097152,"children":[{"name":"shouldDismissOnBackPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by pressing the back button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"shouldDismissOnClickOutside","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the bottom sheet can be dismissed by clicking outside (on the scrim)."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetProps","variant":"declaration","kind":2097152,"children":[{"name":"children","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The children of the "},{"kind":"code","text":"`ModalBottomSheet`"},{"kind":"text","text":" component.\nCan include a "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot for a custom drag handle."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"containerColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The background color of the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"contentColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The preferred color of the content inside the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"initialFullyExpanded","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Opens the sheet fully expanded on first composition. Ignored when "},{"kind":"code","text":"`skipPartiallyExpanded`"},{"kind":"text","text":" is "},{"kind":"code","text":"`true`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Modifiers for the component."}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onDismissRequest","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Callback function that is called when the user dismisses the bottom sheet\n(via swipe, back press, or tapping outside the scrim)."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"properties","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Properties for the modal window behavior."}]},"type":{"type":"reference","name":"ModalBottomSheetProperties","package":"@expo/ui"}},{"name":"ref","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Can be used to imperatively hide the bottom sheet with an animation."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.Ref"},"typeArguments":[{"type":"reference","name":"ModalBottomSheetRef","package":"@expo/ui"}],"name":"Ref","package":"@types/react","qualifiedName":"React.Ref"}},{"name":"scrimColor","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The color of the scrim overlay behind the bottom sheet."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"ColorValue"},"name":"ColorValue","package":"react-native"}},{"name":"sheetGesturesEnabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether gestures (swipe to dismiss) are enabled on the bottom sheet."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"showDragHandle","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether to show the default drag handle at the top of the bottom sheet.\nIgnored if a custom "},{"kind":"code","text":"`ModalBottomSheet.DragHandle`"},{"kind":"text","text":" slot is provided."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"true"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"skipPartiallyExpanded","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Immediately opens the bottom sheet in full screen."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"ModalBottomSheetRef","variant":"declaration","kind":2097152,"children":[{"name":"expand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically expands the bottom sheet to full height with an animation."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"hide","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically hides the bottom sheet with an animation.\nThe returned promise resolves after the dismiss animation completes."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}},{"name":"partialExpand","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically collapses the bottom sheet to partially expanded (~50%) state.\nOnly works when "},{"kind":"code","text":"`skipPartiallyExpanded`"},{"kind":"text","text":" is "},{"kind":"code","text":"`false`"},{"kind":"text","text":"."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}}]},{"name":"ModalBottomSheet","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"query","queryType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/jetpack-compose/ModalBottomSheet/index.tsx","qualifiedName":"ModalBottomSheetComponent"},"name":"ModalBottomSheetComponent","package":"@expo/ui","preferValues":true}},"defaultValue":"ModalBottomSheetComponent"}],"packageName":"@expo/ui"} \ No newline at end of file diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md index 2848796ccdeaee..fb6cc59a74f4c1 100644 --- a/packages/expo-ui/CHANGELOG.md +++ b/packages/expo-ui/CHANGELOG.md @@ -33,6 +33,7 @@ ### 🐛 Bug fixes +- [android] Fix race between JS imperative `expand()` and Compose handler registration on `ModalBottomSheet`. Adds `initialFullyExpanded` prop to drive initial snap state natively. ([#46367](https://github.com/expo/expo/pull/46367) by [@duyanhv](https://github.com/duyanhv)) - [iOS][android] Fix `community/bottom-sheet` blocking touches behind the sheet after it's dismissed. ([#46805](https://github.com/expo/expo/pull/46805) by [@nishan](https://github.com/intergalacticspacehighway)) - [android] Fix React Native touchables (e.g. `Pressable`) on `community/pager-view` pages not responding, or triggering the wrong page's handler, after navigating between pages. ([#46778](https://github.com/expo/expo/pull/46778) by [@nishan](https://github.com/intergalacticspacehighway)) - [iOS] Fix `font`, `dynamicTypeSize`, and `resizable` modifiers not applying to the SwiftUI `Image`. SF Symbols scale with Dynamic Type when a `font` modifier sets a `textStyle`. ([#46714](https://github.com/expo/expo/pull/46714) by [@ramonclaudio](https://github.com/ramonclaudio)) diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt index 610a022b0ca25d..e800ec2adafaa7 100644 --- a/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt +++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/ModalBottomSheetView.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.ModalBottomSheetProperties import androidx.compose.material3.contentColorFor import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import expo.modules.kotlin.records.Field import expo.modules.kotlin.records.Record @@ -30,6 +31,7 @@ data class ModalBottomSheetPropertiesRecord( @OptimizedComposeProps data class ModalBottomSheetViewProps( val skipPartiallyExpanded: Boolean = false, + val initialFullyExpanded: Boolean = false, val containerColor: Color? = null, val contentColor: Color? = null, val scrimColor: Color? = null, @@ -112,4 +114,16 @@ fun FunctionalComposableScope.ModalBottomSheetContent( ) { Children(UIComposableScope(), filter = { !isSlotView(it) }) } + + LaunchedEffect(Unit) { + if (props.initialFullyExpanded && !props.skipPartiallyExpanded) { + try { + sheetState.expand() + } catch (_: CancellationException) { + // Dismissal can cancel the expand animation. + } catch (_: Exception) { + // Expanded anchor may be unreachable; never crash the view. + } + } + } } diff --git a/packages/expo-ui/build/community/bottom-sheet/BottomSheet.android.d.ts.map b/packages/expo-ui/build/community/bottom-sheet/BottomSheet.android.d.ts.map index da00921dfe520c..384baa0dc41373 100644 --- a/packages/expo-ui/build/community/bottom-sheet/BottomSheet.android.d.ts.map +++ b/packages/expo-ui/build/community/bottom-sheet/BottomSheet.android.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"BottomSheet.android.d.ts","sourceRoot":"","sources":["../../../src/community/bottom-sheet/BottomSheet.android.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMpE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAqC3C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CAsKlD"} \ No newline at end of file +{"version":3,"file":"BottomSheet.android.d.ts","sourceRoot":"","sources":["../../../src/community/bottom-sheet/BottomSheet.android.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMpE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAqC3C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CA2JlD"} \ No newline at end of file diff --git a/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts b/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts index 0530602700e789..48d45ae280db7f 100644 --- a/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts +++ b/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts @@ -49,6 +49,12 @@ export type ModalBottomSheetProps = { * @default false */ skipPartiallyExpanded?: boolean; + /** + * Opens the sheet fully expanded on first composition. Ignored when `skipPartiallyExpanded` is `true`. + * @default false + * @platform android + */ + initialFullyExpanded?: boolean; /** * The background color of the bottom sheet. */ diff --git a/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts.map b/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts.map index 5d2d7aebc2948d..f929aa8fc7b642 100644 --- a/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts.map +++ b/packages/expo-ui/build/jetpack-compose/ModalBottomSheet/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/jetpack-compose/ModalBottomSheet/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAiB,MAAM,OAAO,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlD,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B;;;OAGG;IACH,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC/B;;;OAGG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;OAEG;IACH,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B;;OAEG;IACH,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACxC;;OAEG;IACH,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;CAC9B,CAAC;AAkCF;;GAEG;AACH,iBAAS,yBAAyB,CAAC,KAAK,EAAE,qBAAqB,2CAE9D;kBAFQ,yBAAyB;4BAPP;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE;;AAalD,eAAO,MAAM,gBAAgB,kCAA4B,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/jetpack-compose/ModalBottomSheet/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAiB,MAAM,OAAO,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlD,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B;;;OAGG;IACH,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC/B;;;OAGG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B;;OAEG;IACH,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACxC;;OAEG;IACH,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;CAC9B,CAAC;AAmCF;;GAEG;AACH,iBAAS,yBAAyB,CAAC,KAAK,EAAE,qBAAqB,2CAE9D;kBAFQ,yBAAyB;4BAPP;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE;;AAalD,eAAO,MAAM,gBAAgB,kCAA4B,CAAC"} \ No newline at end of file diff --git a/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx b/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx index 21670912b78b07..7db63f5d991430 100644 --- a/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx +++ b/packages/expo-ui/src/community/bottom-sheet/BottomSheet.android.tsx @@ -118,18 +118,6 @@ export function BottomSheet(props: BottomSheetProps) { } }, [clampIndex, indexProp, fireCloseCallbacks]); - useEffect(() => { - if (!isOpen) return; - - const targetIndex = pendingIndexRef.current ?? 0; - if (hasMultipleSnapPoints && targetIndex === maxIndex) { - sheetRef.current?.expand(); - } else if (hasMultipleSnapPoints) { - sheetRef.current?.partialExpand(); - } - pendingIndexRef.current = null; - }, [hasMultipleSnapPoints, isOpen, maxIndex]); - const handleDismiss = useCallback(() => { setIsOpen(false); fireCloseCallbacks(); @@ -202,6 +190,7 @@ export function BottomSheet(props: BottomSheetProps) { ref={sheetRef} onDismissRequest={handleDismiss} skipPartiallyExpanded={skipPartially} + initialFullyExpanded={hasMultipleSnapPoints && pendingIndexRef.current === maxIndex} showDragHandle={handleComponent !== null} sheetGesturesEnabled={enablePanDownToClose} containerColor={containerColor} diff --git a/packages/expo-ui/src/jetpack-compose/ModalBottomSheet/index.tsx b/packages/expo-ui/src/jetpack-compose/ModalBottomSheet/index.tsx index e62702572183d6..b9c6b0f1a3cf2d 100644 --- a/packages/expo-ui/src/jetpack-compose/ModalBottomSheet/index.tsx +++ b/packages/expo-ui/src/jetpack-compose/ModalBottomSheet/index.tsx @@ -62,6 +62,12 @@ export type ModalBottomSheetProps = { * @default false */ skipPartiallyExpanded?: boolean; + /** + * Opens the sheet fully expanded on first composition. Ignored when `skipPartiallyExpanded` is `true`. + * @default false + * @platform android + */ + initialFullyExpanded?: boolean; /** * The background color of the bottom sheet. */ @@ -111,6 +117,7 @@ function transformProps(props: ModalBottomSheetProps): NativeModalBottomSheetPro ...(modifiers ? createViewModifierEventListener(modifiers) : undefined), ...restProps, skipPartiallyExpanded: props.skipPartiallyExpanded ?? false, + initialFullyExpanded: props.initialFullyExpanded ?? false, onDismissRequest: () => { onDismissRequest?.(); }, From aca0879a3d40ff72202ae67bf5fcd75e94309006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nishan=20=28o=5E=E2=96=BD=5Eo=29?= Date: Fri, 12 Jun 2026 08:02:04 +0530 Subject: [PATCH 4/4] [ui][universal] Fix user modifiers losing to prop-derived modifiers (#46815) # Why User-supplied `modifiers` had no effect on universal components when the component derived a modifier of the same type from its props, e.g. `buttonStyle` on `Button` always lost to the `variant` prop. Fixes - https://github.com/expo/expo/issues/46804, https://github.com/expo/expo/issues/45602 # How - Added `omitUserOverridden`: a modifier passed through `modifiers` takes ownership of its `$type`, and the component skips the same-type modifier it derives from props. - Applied it to style-derived modifiers in `transformToModifiers` (iOS and Android), the `variant` `buttonStyle` in `Button`, and `textStyle` modifiers in `Text`. Event, lifecycle, and behavior modifiers are never dropped. - This also makes `ListItem`'s `buttonStyle('plain')` take effect, it was losing to the `Button` variant default. # Test Plan Unit tests for `omitUserOverridden`, both `transformToModifiers`, universal `Button`, and `Text`. # Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../unversioned/expo-ui/universal/button.json | 2 +- .../v56.0.0/expo-ui/universal/button.json | 2 +- packages/expo-ui/CHANGELOG.md | 1 + .../build/universal/Button/index.ios.d.ts.map | 2 +- .../build/universal/Text/index.ios.d.ts.map | 2 +- .../build/universal/modifierUtils.d.ts | 16 ++++++ .../build/universal/modifierUtils.d.ts.map | 1 + .../universal/transformStyle.android.d.ts | 3 ++ .../universal/transformStyle.android.d.ts.map | 2 +- .../build/universal/transformStyle.ios.d.ts | 3 ++ .../universal/transformStyle.ios.d.ts.map | 2 +- packages/expo-ui/build/universal/types.d.ts | 2 + .../expo-ui/build/universal/types.d.ts.map | 2 +- packages/expo-ui/src/__mocks__/expo.ts | 22 +++++++++ .../Button/__tests__/index.test.ios.tsx | 41 ++++++++++++++++ .../src/universal/Button/index.ios.tsx | 7 ++- .../Text/__tests__/index.test.ios.tsx | 49 +++++++++++++++++++ .../expo-ui/src/universal/Text/index.ios.tsx | 4 +- .../universal/__tests__/modifierUtils.test.ts | 28 +++++++++++ .../__tests__/transformStyle.test.android.ts | 30 ++++++++++++ .../__tests__/transformStyle.test.ios.ts | 40 +++++++++++++++ .../expo-ui/src/universal/modifierUtils.ts | 23 +++++++++ .../src/universal/transformStyle.android.ts | 10 +++- .../src/universal/transformStyle.ios.ts | 10 +++- packages/expo-ui/src/universal/types.ts | 2 + 25 files changed, 295 insertions(+), 11 deletions(-) create mode 100644 packages/expo-ui/build/universal/modifierUtils.d.ts create mode 100644 packages/expo-ui/build/universal/modifierUtils.d.ts.map create mode 100644 packages/expo-ui/src/__mocks__/expo.ts create mode 100644 packages/expo-ui/src/universal/Button/__tests__/index.test.ios.tsx create mode 100644 packages/expo-ui/src/universal/Text/__tests__/index.test.ios.tsx create mode 100644 packages/expo-ui/src/universal/__tests__/modifierUtils.test.ts create mode 100644 packages/expo-ui/src/universal/__tests__/transformStyle.test.android.ts create mode 100644 packages/expo-ui/src/universal/__tests__/transformStyle.test.ios.ts create mode 100644 packages/expo-ui/src/universal/modifierUtils.ts diff --git a/docs/public/static/data/unversioned/expo-ui/universal/button.json b/docs/public/static/data/unversioned/expo-ui/universal/button.json index 891fd023cad2c2..f3f9673a24af2d 100644 --- a/docs/public/static/data/unversioned/expo-ui/universal/button.json +++ b/docs/public/static/data/unversioned/expo-ui/universal/button.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-ui/universal/button","variant":"project","kind":1,"children":[{"name":"ButtonProps","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"Props for the ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button) component."}]},"children":[{"name":"children","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom content rendered inside the button. When provided, "},{"kind":"code","text":"`label`"},{"kind":"text","text":" is ignored."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"disabled","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is disabled. Disabled components do not respond to user interaction."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"hidden","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is hidden."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"label","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Text label displayed inside the button. Ignored when "},{"kind":"code","text":"`children`"},{"kind":"text","text":" is provided."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-specific modifier escape hatch. Pass an array of modifier configs\nfrom "},{"kind":"code","text":"`@expo/ui/swift-ui/modifiers`"},{"kind":"text","text":" or "},{"kind":"code","text":"`@expo/ui/jetpack-compose/modifiers`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onAppear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component appears on screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onDisappear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component is removed from screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Called when the button is pressed."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}},"overwrites":{"type":"reference","name":"UniversalBaseProps.onPress"}},{"name":"style","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-agnostic style properties. These are translated to SwiftUI modifiers on iOS\nand Jetpack Compose modifiers on Android."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Pick"},"typeArguments":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheetTypes.d.ts","qualifiedName":"ViewStyle"},"name":"ViewStyle","package":"react-native"},{"type":"union","types":[{"type":"literal","value":"padding"},{"type":"literal","value":"paddingHorizontal"},{"type":"literal","value":"paddingVertical"},{"type":"literal","value":"paddingTop"},{"type":"literal","value":"paddingBottom"},{"type":"literal","value":"paddingLeft"},{"type":"literal","value":"paddingRight"},{"type":"literal","value":"backgroundColor"},{"type":"literal","value":"borderRadius"},{"type":"literal","value":"borderWidth"},{"type":"literal","value":"borderColor"},{"type":"literal","value":"opacity"},{"type":"literal","value":"width"},{"type":"literal","value":"height"}]}],"name":"Pick","package":"typescript"}},{"name":"testID","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Identifier used to locate the component in end-to-end tests."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"variant","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Visual variant of the button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"'filled'"}]}]},"type":{"type":"reference","name":"ButtonVariant","package":"@expo/ui"}}]},{"name":"ButtonVariant","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Visual variant of a ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button).\n\n- "},{"kind":"code","text":"`'filled'`"},{"kind":"text","text":" — solid background color (default).\n- "},{"kind":"code","text":"`'outlined'`"},{"kind":"text","text":" — transparent background with a border.\n- "},{"kind":"code","text":"`'text'`"},{"kind":"text","text":" — no background or border, text only."}]},"type":{"type":"union","types":[{"type":"literal","value":"filled"},{"type":"literal","value":"outlined"},{"type":"literal","value":"text"}]}},{"name":"Button","variant":"declaration","kind":64,"signatures":[{"name":"Button","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"A pressable button that supports multiple visual variants."}]},"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","name":"ButtonProps","package":"@expo/ui"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}],"packageName":"@expo/ui"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-ui/universal/button","variant":"project","kind":1,"children":[{"name":"ButtonProps","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"Props for the ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button) component."}]},"children":[{"name":"children","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom content rendered inside the button. When provided, "},{"kind":"code","text":"`label`"},{"kind":"text","text":" is ignored."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"disabled","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is disabled. Disabled components do not respond to user interaction."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"hidden","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is hidden."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"label","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Text label displayed inside the button. Ignored when "},{"kind":"code","text":"`children`"},{"kind":"text","text":" is provided."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-specific modifier escape hatch. Pass an array of modifier configs\nfrom "},{"kind":"code","text":"`@expo/ui/swift-ui/modifiers`"},{"kind":"text","text":" or "},{"kind":"code","text":"`@expo/ui/jetpack-compose/modifiers`"},{"kind":"text","text":".\nA modifier supplied here replaces any modifier of the same type that the\ncomponent derives from "},{"kind":"code","text":"`style`"},{"kind":"text","text":" or other props."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onAppear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component appears on screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onDisappear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component is removed from screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Called when the button is pressed."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}},"overwrites":{"type":"reference","name":"UniversalBaseProps.onPress"}},{"name":"style","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-agnostic style properties. These are translated to SwiftUI modifiers on iOS\nand Jetpack Compose modifiers on Android."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Pick"},"typeArguments":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheetTypes.d.ts","qualifiedName":"ViewStyle"},"name":"ViewStyle","package":"react-native"},{"type":"union","types":[{"type":"literal","value":"padding"},{"type":"literal","value":"paddingHorizontal"},{"type":"literal","value":"paddingVertical"},{"type":"literal","value":"paddingTop"},{"type":"literal","value":"paddingBottom"},{"type":"literal","value":"paddingLeft"},{"type":"literal","value":"paddingRight"},{"type":"literal","value":"backgroundColor"},{"type":"literal","value":"borderRadius"},{"type":"literal","value":"borderWidth"},{"type":"literal","value":"borderColor"},{"type":"literal","value":"opacity"},{"type":"literal","value":"width"},{"type":"literal","value":"height"}]}],"name":"Pick","package":"typescript"}},{"name":"testID","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Identifier used to locate the component in end-to-end tests."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"variant","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Visual variant of the button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"'filled'"}]}]},"type":{"type":"reference","name":"ButtonVariant","package":"@expo/ui"}}]},{"name":"ButtonVariant","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Visual variant of a ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button).\n\n- "},{"kind":"code","text":"`'filled'`"},{"kind":"text","text":" — solid background color (default).\n- "},{"kind":"code","text":"`'outlined'`"},{"kind":"text","text":" — transparent background with a border.\n- "},{"kind":"code","text":"`'text'`"},{"kind":"text","text":" — no background or border, text only."}]},"type":{"type":"union","types":[{"type":"literal","value":"filled"},{"type":"literal","value":"outlined"},{"type":"literal","value":"text"}]}},{"name":"Button","variant":"declaration","kind":64,"signatures":[{"name":"Button","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"A pressable button that supports multiple visual variants."}]},"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","name":"ButtonProps","package":"@expo/ui"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}],"packageName":"@expo/ui"} \ No newline at end of file diff --git a/docs/public/static/data/v56.0.0/expo-ui/universal/button.json b/docs/public/static/data/v56.0.0/expo-ui/universal/button.json index 891fd023cad2c2..f3f9673a24af2d 100644 --- a/docs/public/static/data/v56.0.0/expo-ui/universal/button.json +++ b/docs/public/static/data/v56.0.0/expo-ui/universal/button.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-ui/universal/button","variant":"project","kind":1,"children":[{"name":"ButtonProps","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"Props for the ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button) component."}]},"children":[{"name":"children","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom content rendered inside the button. When provided, "},{"kind":"code","text":"`label`"},{"kind":"text","text":" is ignored."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"disabled","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is disabled. Disabled components do not respond to user interaction."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"hidden","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is hidden."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"label","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Text label displayed inside the button. Ignored when "},{"kind":"code","text":"`children`"},{"kind":"text","text":" is provided."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-specific modifier escape hatch. Pass an array of modifier configs\nfrom "},{"kind":"code","text":"`@expo/ui/swift-ui/modifiers`"},{"kind":"text","text":" or "},{"kind":"code","text":"`@expo/ui/jetpack-compose/modifiers`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onAppear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component appears on screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onDisappear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component is removed from screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Called when the button is pressed."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}},"overwrites":{"type":"reference","name":"UniversalBaseProps.onPress"}},{"name":"style","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-agnostic style properties. These are translated to SwiftUI modifiers on iOS\nand Jetpack Compose modifiers on Android."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Pick"},"typeArguments":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheetTypes.d.ts","qualifiedName":"ViewStyle"},"name":"ViewStyle","package":"react-native"},{"type":"union","types":[{"type":"literal","value":"padding"},{"type":"literal","value":"paddingHorizontal"},{"type":"literal","value":"paddingVertical"},{"type":"literal","value":"paddingTop"},{"type":"literal","value":"paddingBottom"},{"type":"literal","value":"paddingLeft"},{"type":"literal","value":"paddingRight"},{"type":"literal","value":"backgroundColor"},{"type":"literal","value":"borderRadius"},{"type":"literal","value":"borderWidth"},{"type":"literal","value":"borderColor"},{"type":"literal","value":"opacity"},{"type":"literal","value":"width"},{"type":"literal","value":"height"}]}],"name":"Pick","package":"typescript"}},{"name":"testID","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Identifier used to locate the component in end-to-end tests."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"variant","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Visual variant of the button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"'filled'"}]}]},"type":{"type":"reference","name":"ButtonVariant","package":"@expo/ui"}}]},{"name":"ButtonVariant","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Visual variant of a ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button).\n\n- "},{"kind":"code","text":"`'filled'`"},{"kind":"text","text":" — solid background color (default).\n- "},{"kind":"code","text":"`'outlined'`"},{"kind":"text","text":" — transparent background with a border.\n- "},{"kind":"code","text":"`'text'`"},{"kind":"text","text":" — no background or border, text only."}]},"type":{"type":"union","types":[{"type":"literal","value":"filled"},{"type":"literal","value":"outlined"},{"type":"literal","value":"text"}]}},{"name":"Button","variant":"declaration","kind":64,"signatures":[{"name":"Button","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"A pressable button that supports multiple visual variants."}]},"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","name":"ButtonProps","package":"@expo/ui"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}],"packageName":"@expo/ui"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-ui/universal/button","variant":"project","kind":1,"children":[{"name":"ButtonProps","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"Props for the ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button) component."}]},"children":[{"name":"children","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom content rendered inside the button. When provided, "},{"kind":"code","text":"`label`"},{"kind":"text","text":" is ignored."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"disabled","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is disabled. Disabled components do not respond to user interaction."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"hidden","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Whether the component is hidden."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"label","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Text label displayed inside the button. Ignored when "},{"kind":"code","text":"`children`"},{"kind":"text","text":" is provided."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"modifiers","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-specific modifier escape hatch. Pass an array of modifier configs\nfrom "},{"kind":"code","text":"`@expo/ui/swift-ui/modifiers`"},{"kind":"text","text":" or "},{"kind":"code","text":"`@expo/ui/jetpack-compose/modifiers`"},{"kind":"text","text":".\nA modifier supplied here replaces any modifier of the same type that the\ncomponent derives from "},{"kind":"code","text":"`style`"},{"kind":"text","text":" or other props."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"type":{"type":"array","elementType":{"type":"reference","target":{"packageName":"@expo/ui","packagePath":"src/types.ts","qualifiedName":"ModifierConfig"},"name":"ModifierConfig","package":"@expo/ui"}}},{"name":"onAppear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component appears on screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onDisappear","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Called when the component is removed from screen."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"onPress","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Called when the button is pressed."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}},"overwrites":{"type":"reference","name":"UniversalBaseProps.onPress"}},{"name":"style","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Platform-agnostic style properties. These are translated to SwiftUI modifiers on iOS\nand Jetpack Compose modifiers on Android."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Pick"},"typeArguments":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheetTypes.d.ts","qualifiedName":"ViewStyle"},"name":"ViewStyle","package":"react-native"},{"type":"union","types":[{"type":"literal","value":"padding"},{"type":"literal","value":"paddingHorizontal"},{"type":"literal","value":"paddingVertical"},{"type":"literal","value":"paddingTop"},{"type":"literal","value":"paddingBottom"},{"type":"literal","value":"paddingLeft"},{"type":"literal","value":"paddingRight"},{"type":"literal","value":"backgroundColor"},{"type":"literal","value":"borderRadius"},{"type":"literal","value":"borderWidth"},{"type":"literal","value":"borderColor"},{"type":"literal","value":"opacity"},{"type":"literal","value":"width"},{"type":"literal","value":"height"}]}],"name":"Pick","package":"typescript"}},{"name":"testID","variant":"declaration","kind":1024,"flags":{"isOptional":true,"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Identifier used to locate the component in end-to-end tests."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"string"}},{"name":"variant","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Visual variant of the button."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"'filled'"}]}]},"type":{"type":"reference","name":"ButtonVariant","package":"@expo/ui"}}]},{"name":"ButtonVariant","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Visual variant of a ["},{"kind":"code","text":"`Button`"},{"kind":"text","text":"](#button).\n\n- "},{"kind":"code","text":"`'filled'`"},{"kind":"text","text":" — solid background color (default).\n- "},{"kind":"code","text":"`'outlined'`"},{"kind":"text","text":" — transparent background with a border.\n- "},{"kind":"code","text":"`'text'`"},{"kind":"text","text":" — no background or border, text only."}]},"type":{"type":"union","types":[{"type":"literal","value":"filled"},{"type":"literal","value":"outlined"},{"type":"literal","value":"text"}]}},{"name":"Button","variant":"declaration","kind":64,"signatures":[{"name":"Button","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"A pressable button that supports multiple visual variants."}]},"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","name":"ButtonProps","package":"@expo/ui"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}],"packageName":"@expo/ui"} \ No newline at end of file diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md index fb6cc59a74f4c1..6c48a49783e211 100644 --- a/packages/expo-ui/CHANGELOG.md +++ b/packages/expo-ui/CHANGELOG.md @@ -33,6 +33,7 @@ ### 🐛 Bug fixes +- [universal] Fix user-supplied `modifiers` having no effect when the component derives a modifier of the same type from its props, e.g. `buttonStyle` on `Button` always losing to the `variant` prop. ([#46815](https://github.com/expo/expo/pull/46815) by [@nishan](https://github.com/intergalacticspacehighway)) - [android] Fix race between JS imperative `expand()` and Compose handler registration on `ModalBottomSheet`. Adds `initialFullyExpanded` prop to drive initial snap state natively. ([#46367](https://github.com/expo/expo/pull/46367) by [@duyanhv](https://github.com/duyanhv)) - [iOS][android] Fix `community/bottom-sheet` blocking touches behind the sheet after it's dismissed. ([#46805](https://github.com/expo/expo/pull/46805) by [@nishan](https://github.com/intergalacticspacehighway)) - [android] Fix React Native touchables (e.g. `Pressable`) on `community/pager-view` pages not responding, or triggering the wrong page's handler, after navigating between pages. ([#46778](https://github.com/expo/expo/pull/46778) by [@nishan](https://github.com/intergalacticspacehighway)) diff --git a/packages/expo-ui/build/universal/Button/index.ios.d.ts.map b/packages/expo-ui/build/universal/Button/index.ios.d.ts.map index af44432002bdda..169e0f1a29319b 100644 --- a/packages/expo-ui/build/universal/Button/index.ios.d.ts.map +++ b/packages/expo-ui/build/universal/Button/index.ios.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.ios.d.ts","sourceRoot":"","sources":["../../../src/universal/Button/index.ios.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,SAAS,CAAC;AAQ1D,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,OAAO,EACP,OAAkB,EAClB,KAAK,EACL,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EAAE,cAAc,GAC1B,EAAE,WAAW,2CAoBb;AAED,cAAc,SAAS,CAAC"} \ No newline at end of file +{"version":3,"file":"index.ios.d.ts","sourceRoot":"","sources":["../../../src/universal/Button/index.ios.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,SAAS,CAAC;AAQ1D,wBAAgB,MAAM,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,OAAO,EACP,OAAkB,EAClB,KAAK,EACL,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EAAE,cAAc,GAC1B,EAAE,WAAW,2CAwBb;AAED,cAAc,SAAS,CAAC"} \ No newline at end of file diff --git a/packages/expo-ui/build/universal/Text/index.ios.d.ts.map b/packages/expo-ui/build/universal/Text/index.ios.d.ts.map index bd509edc25f3d9..7a56841e07a7da 100644 --- a/packages/expo-ui/build/universal/Text/index.ios.d.ts.map +++ b/packages/expo-ui/build/universal/Text/index.ios.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.ios.d.ts","sourceRoot":"","sources":["../../../src/universal/Text/index.ios.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAuB,MAAM,SAAS,CAAC;AAyB9D,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,aAAa,EACb,KAAK,EACL,OAAO,EACP,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EAAE,cAAc,GAC1B,EAAE,SAAS,2CA0DX;AAED,cAAc,SAAS,CAAC"} \ No newline at end of file +{"version":3,"file":"index.ios.d.ts","sourceRoot":"","sources":["../../../src/universal/Text/index.ios.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAuB,MAAM,SAAS,CAAC;AAyB9D,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,aAAa,EACb,KAAK,EACL,OAAO,EACP,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EAAE,cAAc,GAC1B,EAAE,SAAS,2CA2DX;AAED,cAAc,SAAS,CAAC"} \ No newline at end of file diff --git a/packages/expo-ui/build/universal/modifierUtils.d.ts b/packages/expo-ui/build/universal/modifierUtils.d.ts new file mode 100644 index 00000000000000..91603f025e8586 --- /dev/null +++ b/packages/expo-ui/build/universal/modifierUtils.d.ts @@ -0,0 +1,16 @@ +import type { ModifierConfig } from '../types'; +/** + * Drops derived modifiers that the user overrides through the `modifiers` + * escape hatch. A user-supplied modifier takes ownership of its `$type`, so + * the component skips the modifier of that type it would otherwise derive + * from `style`, `variant`, and similar props. + * + * Only pass style-derived modifiers as `derived`. Modifiers backing + * functional props (for example `onPress`, `onAppear`, `disabled`) must not + * go through this filter, or a user modifier of the same type would silently + * disable the prop. + */ +export declare function omitUserOverridden(derived: T[], userModifiers?: readonly ModifierConfig[]): T[]; +//# sourceMappingURL=modifierUtils.d.ts.map \ No newline at end of file diff --git a/packages/expo-ui/build/universal/modifierUtils.d.ts.map b/packages/expo-ui/build/universal/modifierUtils.d.ts.map new file mode 100644 index 00000000000000..ab03bb27e819fb --- /dev/null +++ b/packages/expo-ui/build/universal/modifierUtils.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"modifierUtils.d.ts","sourceRoot":"","sources":["../../src/universal/modifierUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,aAAa,CAAC,EAAE,SAAS,cAAc,EAAE,GACxC,CAAC,EAAE,CAML"} \ No newline at end of file diff --git a/packages/expo-ui/build/universal/transformStyle.android.d.ts b/packages/expo-ui/build/universal/transformStyle.android.d.ts index d3ffb6bcb77c2e..a33eded8e5184b 100644 --- a/packages/expo-ui/build/universal/transformStyle.android.d.ts +++ b/packages/expo-ui/build/universal/transformStyle.android.d.ts @@ -8,6 +8,9 @@ import type { UniversalBaseProps, UniversalStyle } from './types'; * box model where background includes the padding area and border is outermost: * sizing → border → clip → background → padding → opacity * → events → behavior → user escape-hatch + * + * Style-derived modifiers yield to user-supplied modifiers of the same + * `$type`, so the escape hatch can override anything derived from props. */ export declare function transformToModifiers(style: UniversalStyle | undefined, props: Pick, extraModifiers?: ModifierConfig[]): ModifierConfig[]; //# sourceMappingURL=transformStyle.android.d.ts.map \ No newline at end of file diff --git a/packages/expo-ui/build/universal/transformStyle.android.d.ts.map b/packages/expo-ui/build/universal/transformStyle.android.d.ts.map index 7e3d42960693ac..968c360bffcb1c 100644 --- a/packages/expo-ui/build/universal/transformStyle.android.d.ts.map +++ b/packages/expo-ui/build/universal/transformStyle.android.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"transformStyle.android.d.ts","sourceRoot":"","sources":["../../src/universal/transformStyle.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAaL,KAAK,cAAc,EACpB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAElE;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC,EAC7E,cAAc,CAAC,EAAE,cAAc,EAAE,GAChC,cAAc,EAAE,CA8ElB"} \ No newline at end of file +{"version":3,"file":"transformStyle.android.d.ts","sourceRoot":"","sources":["../../src/universal/transformStyle.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAaL,KAAK,cAAc,EACpB,MAAM,oCAAoC,CAAC;AAG5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC,EAC7E,cAAc,CAAC,EAAE,cAAc,EAAE,GAChC,cAAc,EAAE,CAkFlB"} \ No newline at end of file diff --git a/packages/expo-ui/build/universal/transformStyle.ios.d.ts b/packages/expo-ui/build/universal/transformStyle.ios.d.ts index 0fff6ddc16e6fd..735efb8dc8b16f 100644 --- a/packages/expo-ui/build/universal/transformStyle.ios.d.ts +++ b/packages/expo-ui/build/universal/transformStyle.ios.d.ts @@ -9,6 +9,9 @@ import type { UniversalBaseProps, UniversalStyle } from './types'; * To match React Native's box model (background fills the full box): * padding → sizing → background → clip → border → opacity * → events → lifecycle → behavior → user escape-hatch + * + * Style-derived modifiers yield to user-supplied modifiers of the same + * `$type`, so the escape hatch can override anything derived from props. */ export declare function transformToModifiers(style: UniversalStyle | undefined, props: Pick, extraModifiers?: ModifierConfig[], options?: { /** Alignment for the frame modifier (used by Column/Row). */ diff --git a/packages/expo-ui/build/universal/transformStyle.ios.d.ts.map b/packages/expo-ui/build/universal/transformStyle.ios.d.ts.map index 363603bb35fc43..12fb64a24bd983 100644 --- a/packages/expo-ui/build/universal/transformStyle.ios.d.ts.map +++ b/packages/expo-ui/build/universal/transformStyle.ios.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"transformStyle.ios.d.ts","sourceRoot":"","sources":["../../src/universal/transformStyle.ios.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,EAUL,KAAK,cAAc,EACpB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAgBlE;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,KAAK,EAAE,IAAI,CACT,kBAAkB,EAClB,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAC1E,EACD,cAAc,CAAC,EAAE,cAAc,EAAE,EACjC,OAAO,CAAC,EAAE;IACR,6DAA6D;IAC7D,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC1D,wDAAwD;IACxD,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAChC,GACA,cAAc,EAAE,CAwGlB"} \ No newline at end of file +{"version":3,"file":"transformStyle.ios.d.ts","sourceRoot":"","sources":["../../src/universal/transformStyle.ios.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,EAUL,KAAK,cAAc,EACpB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAgBlE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,KAAK,EAAE,IAAI,CACT,kBAAkB,EAClB,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAC1E,EACD,cAAc,CAAC,EAAE,cAAc,EAAE,EACjC,OAAO,CAAC,EAAE;IACR,6DAA6D;IAC7D,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC1D,wDAAwD;IACxD,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAChC,GACA,cAAc,EAAE,CA4GlB"} \ No newline at end of file diff --git a/packages/expo-ui/build/universal/types.d.ts b/packages/expo-ui/build/universal/types.d.ts index 2b0f98aff61c55..fa05884333f3ab 100644 --- a/packages/expo-ui/build/universal/types.d.ts +++ b/packages/expo-ui/build/universal/types.d.ts @@ -22,6 +22,8 @@ export interface UniversalBaseProps { /** * Platform-specific modifier escape hatch. Pass an array of modifier configs * from `@expo/ui/swift-ui/modifiers` or `@expo/ui/jetpack-compose/modifiers`. + * A modifier supplied here replaces any modifier of the same type that the + * component derives from `style` or other props. * @platform android * @platform ios */ diff --git a/packages/expo-ui/build/universal/types.d.ts.map b/packages/expo-ui/build/universal/types.d.ts.map index daf2549e7c2118..0938ad3e9f9e75 100644 --- a/packages/expo-ui/build/universal/types.d.ts.map +++ b/packages/expo-ui/build/universal/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/universal/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,SAAS,EACP,SAAS,GACT,mBAAmB,GACnB,iBAAiB,GACjB,YAAY,GACZ,eAAe,GACf,aAAa,GACb,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,aAAa,GACb,aAAa,GACb,SAAS,GACT,OAAO,GACP,QAAQ,CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAGjC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IAEvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAI7B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAIrB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAIzB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/universal/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,SAAS,EACP,SAAS,GACT,mBAAmB,GACnB,iBAAiB,GACjB,YAAY,GACZ,eAAe,GACf,aAAa,GACb,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,aAAa,GACb,aAAa,GACb,SAAS,GACT,OAAO,GACP,QAAQ,CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAGjC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IAEvB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAI7B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAIrB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAIzB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC"} \ No newline at end of file diff --git a/packages/expo-ui/src/__mocks__/expo.ts b/packages/expo-ui/src/__mocks__/expo.ts new file mode 100644 index 00000000000000..18cf08bd4ef138 --- /dev/null +++ b/packages/expo-ui/src/__mocks__/expo.ts @@ -0,0 +1,22 @@ +import { createElement } from 'react'; +import { View } from 'react-native'; + +// Records a `(viewName, props)` call for every render of a view returned by +// `requireNativeView`. Cleared automatically between tests (`clearMocks`). +export const renderedNativeViews = jest.fn(); + +export function findNativeViewProps(viewName: string) { + const call = renderedNativeViews.mock.calls.find(([name]) => name === viewName); + return call?.[1]; +} + +export const requireNativeModule = jest.fn(() => ({})); + +export const requireNativeView = jest.fn((moduleName: string, viewName: string) => { + return function MockNativeView(props: Record) { + renderedNativeViews(viewName, props); + // Swallow string children — the RN mock View can't render raw text. + const children = typeof props.children === 'string' ? undefined : props.children; + return createElement(View, { ...props, children }); + }; +}); diff --git a/packages/expo-ui/src/universal/Button/__tests__/index.test.ios.tsx b/packages/expo-ui/src/universal/Button/__tests__/index.test.ios.tsx new file mode 100644 index 00000000000000..967482129ab23f --- /dev/null +++ b/packages/expo-ui/src/universal/Button/__tests__/index.test.ios.tsx @@ -0,0 +1,41 @@ +import { render } from '@testing-library/react-native'; + +import { Button } from '..'; +import { findNativeViewProps } from '../../../__mocks__/expo'; +import { buttonStyle, controlSize } from '../../../swift-ui/modifiers'; + +jest.mock('expo'); + +function nativeButtonModifiers() { + return findNativeViewProps('Button')?.modifiers; +} + +describe('Button', () => { + it('maps the variant prop to a buttonStyle modifier', () => { + render(