` o
## Usage
-
-
-
-
-DOM components use [`@expo/dom-webview`](https://www.npmjs.com/package/@expo/dom-webview) by default and no additional install is required.
-
-If you want to use `react-native-webview` instead, install it and opt out via the `dom` prop:
-
-
-
-```tsx App.tsx (native)
-import DOMComponent from './my-component';
-
-export default function App() {
- return ;
-}
-```
-
-
-
-
-
-Install `react-native-webview` in your project:
-
-
-
-
-
-
-
To render a React component to the DOM, add the `'use dom'` directive to the top of the web component file:
```tsx my-component.tsx (web)
diff --git a/docs/pages/versions/unversioned/sdk/ui/drop-in-replacements/index.mdx b/docs/pages/versions/unversioned/sdk/ui/drop-in-replacements/index.mdx
index 280d3b73f31119..76efc5d8b32ff3 100644
--- a/docs/pages/versions/unversioned/sdk/ui/drop-in-replacements/index.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/drop-in-replacements/index.mdx
@@ -13,3 +13,4 @@ The following components provide API-compatible replacements for popular React N
- **[BottomSheet](bottomsheet)**: Compatible with `@gorhom/bottom-sheet`
- **[Picker](picker)**: Compatible with `@react-native-picker/picker`
- **[SegmentedControl](segmentedcontrol)**: Compatible with `@react-native-segmented-control/segmented-control`
+- **[MaskedView](maskedview)**: Compatible with `@react-native-masked-view/masked-view`
diff --git a/docs/pages/versions/unversioned/sdk/ui/drop-in-replacements/maskedview.mdx b/docs/pages/versions/unversioned/sdk/ui/drop-in-replacements/maskedview.mdx
new file mode 100644
index 00000000000000..77b4a8b4969166
--- /dev/null
+++ b/docs/pages/versions/unversioned/sdk/ui/drop-in-replacements/maskedview.mdx
@@ -0,0 +1,96 @@
+---
+title: MaskedView
+description: A masked view compatible with @react-native-masked-view/masked-view.
+sourceCodeUrl: 'https://github.com/expo/expo/tree/main/packages/expo-ui'
+packageName: '@expo/ui'
+platforms: ['android', 'ios']
+---
+
+import APISection from '~/components/plugins/APISection';
+import { APIInstallSection } from '~/components/plugins/InstallSection';
+
+A `MaskedView` component with an API compatible with `@react-native-masked-view/masked-view`. The opaque pixels of `maskElement` reveal the masked content behind it; transparent pixels hide it.
+
+Under the hood this component bridges arbitrary React Native children into the platform-specific `@expo/ui` masking primitives:
+
+- **Android**: Jetpack Compose graphics layer compositing with `BlendMode.DstIn`.
+- **iOS**: SwiftUI [`.mask`](
) modifier.
+
+## Installation
+
+
+
+## Migrating from `@react-native-masked-view/masked-view`
+
+- Update the import from `import MaskedView from '@react-native-masked-view/masked-view'` to `import { MaskedView } from '@expo/ui/community/masked-view'`.
+- The `androidRenderingMode` prop is not supported. The Compose-based implementation always uses an offscreen graphics layer, so the prop has no equivalent and is omitted from the public types.
+- Web is not implemented. On web, children render unmasked and a one-time console warning is logged. For web targets, prefer the CSS primitive that fits your specific case:
+ - **Gradient text** — `background-clip: text` with `color: 'transparent'` and a CSS gradient/image as the background.
+ - **Alpha fade** — `mask-image: linear-gradient(...)` (or `WebkitMaskImage`) directly on the content view.
+ - **Shape mask** (circle, rounded rectangle, and so on) — `clip-path: circle(...)` / `inset(...)` / `path(...)`, or `border-radius` + `overflow: 'hidden'`.
+
+## Basic usage
+
+```tsx MaskedViewExample.tsx
+import { MaskedView } from '@expo/ui/community/masked-view';
+import { StyleSheet, Text, View } from 'react-native';
+
+export default function MaskedViewExample() {
+ return (
+
+ EXPO
+
+ }>
+
+
+ );
+}
+```
+
+## Alpha-fade mask
+
+Only the alpha channel of the `maskElement` matters: opaque pixels reveal content, transparent pixels hide it. Use a `LinearGradient` (from `expo-linear-gradient`) that goes from opaque to transparent — `'black'` (for example) to `'transparent'` below — to fade content out along an axis.
+
+```tsx AlphaFadeExample.tsx
+import { MaskedView } from '@expo/ui/community/masked-view';
+import { LinearGradient } from 'expo-linear-gradient';
+import { StyleSheet, View } from 'react-native';
+
+export default function AlphaFadeExample() {
+ return (
+
+ }>
+
+
+
+
+ );
+}
+```
+
+## API
+
+```tsx
+import { MaskedView } from '@expo/ui/community/masked-view';
+```
+
+
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx
index 17fef9f8949d2c..b47dee34517b96 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI BottomSheet matches the official SwiftUI [sheet API]() and presents content from the bottom of the screen.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/button.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/button.mdx
index b17162ead4ecbc..ace80418e1bb92 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/button.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/button.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Button matches the official SwiftUI [Button API](https://developer.apple.com/documentation/swiftui/button) and supports styling via the [`buttonStyle`](modifiers/#buttonstylestyle), [`controlSize`](modifiers/#controlsizesize), and other modifiers.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/colorpicker.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/colorpicker.mdx
index 4368339f8597c8..6f691f5c921bd8 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/colorpicker.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/colorpicker.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ColorPicker matches the official SwiftUI [ColorPicker API](https://developer.apple.com/documentation/swiftui/colorpicker) and allows app users to select colors from a palette.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/confirmationdialog.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/confirmationdialog.mdx
index 5354513489e918..90661e27d3249a 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/confirmationdialog.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/confirmationdialog.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ConfirmationDialog matches the official SwiftUI [confirmationDialog API]() and presents an action sheet-style dialog with a title, actions, and an optional message.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/contextmenu.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/contextmenu.mdx
index 8f8c9ef248a0f4..956308b09dcf56 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/contextmenu.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/contextmenu.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ContextMenu matches the official SwiftUI [contextMenu API]() and displays a menu when long-pressed. For single-tap menu interactions, use [`Menu`](menu) instead.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/controlgroup.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/controlgroup.mdx
index 93eebfc28d7fae..f9ff07aeac520e 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/controlgroup.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/controlgroup.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ControlGroup matches the official SwiftUI [ControlGroup API](https://developer.apple.com/documentation/swiftui/controlgroup). When placed inside a [`Menu`](menu), the children are rendered as a compact horizontal row of buttons.
+
+
+
+
> **Note:** On tvOS, `ControlGroup` requires tvOS 17.0 or later.
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/datepicker.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/datepicker.mdx
index 38fc11b71aa01d..31cb0df51f34c4 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/datepicker.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/datepicker.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI DatePicker matches the official SwiftUI [DatePicker API](https://developer.apple.com/documentation/swiftui/datepicker) and supports styling via the [`datePickerStyle`](modifiers/#datepickerstylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/disclosuregroup.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/disclosuregroup.mdx
index 6ccc48d28b6a89..bcecc4b324b838 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/disclosuregroup.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/disclosuregroup.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI DisclosureGroup matches the official SwiftUI [DisclosureGroup API](https://developer.apple.com/documentation/swiftui/disclosuregroup) and displays a disclosure indicator that reveals or hides content.
+
+
+
+
## Installation
@@ -19,15 +29,28 @@ Expo UI DisclosureGroup matches the official SwiftUI [DisclosureGroup API](https
### Basic disclosure group
+`DisclosureGroup` is most commonly used inside a [`Form`](form) so it picks up the standard iOS list styling with a chevron indicator.
+
```tsx BasicDisclosureGroupExample.tsx
-import { Host, DisclosureGroup, Text } from '@expo/ui/swift-ui';
+import { useState } from 'react';
+import { DisclosureGroup, Form, Host, Section, Text } from '@expo/ui/swift-ui';
export default function BasicDisclosureGroupExample() {
+ const [isExpanded, setIsExpanded] = useState(true);
return (
-
-
- This content is revealed when the disclosure group is expanded.
-
+
+
);
}
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/divider.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/divider.mdx
index c71915bd7ce99a..d511375821a28e 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/divider.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/divider.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Divider matches the official SwiftUI [Divider API](https://developer.apple.com/documentation/swiftui/divider) and creates a visual separator between content.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/form.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/form.mdx
index cc7aa281156d6d..818c413fb2e5ad 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/form.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/form.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Form matches the official SwiftUI [Form API](https://developer.apple.com/documentation/swiftui/form). It provides a container for grouping controls used for data entry, such as in settings or inspection panes.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/gauge.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/gauge.mdx
index 64cf7f63b3653e..c677a9ac1a08c4 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/gauge.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/gauge.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Gauge matches the official SwiftUI [Gauge API](https://developer.apple.com/documentation/swiftui/gauge) and supports styling via the [`gaugeStyle`](modifiers/#gaugestylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx
index 6cd4050aadc0a7..ce33d4c26b2a17 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/group.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Group matches the official SwiftUI [Group API](https://developer.apple.com/documentation/swiftui/group) and groups views together without introducing additional layout structure.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx
index aab1b451022c96..bcb48a8e107cac 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI HStack matches the official SwiftUI [HStack API](https://developer.apple.com/documentation/swiftui/hstack) and arranges its children horizontally.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/image.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/image.mdx
index ad048acbb932c3..fd0fb44feb8f92 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/image.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/image.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Image displays SF Symbols using the SwiftUI [Image API](https://developer.apple.com/documentation/swiftui/image). SF Symbols are a library of configurable symbols provided by Apple.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/label.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/label.mdx
index 6c4c1c7eb5a2ca..824b7834c5f2a7 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/label.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/label.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Label matches the official SwiftUI [Label API](https://developer.apple.com/documentation/swiftui/label) and displays a title alongside an icon.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/link.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/link.mdx
index 1b3c8c3bf068f2..7855d2b712f371 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/link.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/link.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Link matches the official SwiftUI [Link API](https://developer.apple.com/documentation/swiftui/link).
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/list.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/list.mdx
index 7623a49fafb964..11bee532346722 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/list.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/list.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI List matches the official SwiftUI [List API](https://developer.apple.com/documentation/swiftui/list) and supports styling via the [`listStyle`](modifiers/#liststylestyle) modifier, various row/section modifiers, as well as selection, reordering, and editing capabilities.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx
index 12041dcc55f918..10b59def5254ba 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/menu.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Menu matches the official SwiftUI [Menu API](https://developer.apple.com/documentation/swiftui/menu) and supports styling via the [`buttonStyle`](modifiers/#buttonstylestyle) modifier. Menu opens on a single tap. For long-press interactions, use [`ContextMenu`](contextmenu) instead.
+
+
+
+
> **Note:** On tvOS, Menu requires tvOS 17.0 or later.
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/overlay.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/overlay.mdx
index 6b57489d86795d..81bd323851a1b7 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/overlay.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/overlay.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvOS', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Overlay matches the official SwiftUI [overlay]() modifier and provides a way to layer secondary content on top of a view, positioned with a specified alignment.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/picker.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/picker.mdx
index 2c8fbf27bdbd88..41060ebb438968 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/picker.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/picker.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Picker matches the official SwiftUI [Picker API](https://developer.apple.com/documentation/swiftui/picker) and supports all picker styles via the [`pickerStyle`](modifiers/#pickerstylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/popover.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/popover.mdx
index e3bbd30c4732c4..6e24c8b3d2ff8a 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/popover.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/popover.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Popover matches the official SwiftUI [Popover API]() and provides a way to present content in a floating overlay anchored to a trigger element.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/progressview.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/progressview.mdx
index ddede21e9e9cc2..1b7817d78a0ba8 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/progressview.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/progressview.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ProgressView matches the official SwiftUI [ProgressView API](https://developer.apple.com/documentation/swiftui/progressview) and supports styling via the [`progressViewStyle`](modifiers/#progressviewstylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx
index 860396b0316851..bba87442a39cd8 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/scrollview.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ScrollView matches the official SwiftUI [ScrollView API](https://developer.apple.com/documentation/swiftui/scrollview) and provides a scrollable container for its children.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/section.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/section.mdx
index d8b959732dd117..3b59105c76cc76 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/section.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/section.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Section matches the official SwiftUI [Section API](https://developer.apple.com/documentation/swiftui/section) and is used to group related content within [`List`](list), [`Form`](form) or [`Picker`](picker) components.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/securefield.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/securefield.mdx
index 12f97cad80bacc..b2ffab241e288c 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/securefield.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/securefield.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI SecureField matches the official SwiftUI [SecureField API](https://developer.apple.com/documentation/swiftui/securefield) and provides a text field that masks user input for passwords and other sensitive text.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx
index 59db555d25c583..b096864bdf9076 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Slider matches the official SwiftUI [Slider API](https://developer.apple.com/documentation/swiftui/slider) and allows selecting values from a bounded range.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/spacer.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/spacer.mdx
index da1fb0ff037d96..448b895a224532 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/spacer.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/spacer.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Spacer matches the official SwiftUI [Spacer API](https://developer.apple.com/documentation/swiftui/spacer) and expands to fill available space in a stack.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/tabview.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/tabview.mdx
index 62cd0e85d3e1ca..062ede0d9ef456 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/tabview.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/tabview.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI TabView matches the official SwiftUI [TabView API](https://developer.apple.com/documentation/swiftui/tabview) and switches between styles via the [`tabViewStyle`](modifiers/#tabviewstyleconfig) modifier.
+
+
+
+
> **Note:** For routed bottom-tab navigation across full-screen routes, use [expo-router/unstable-native-tabs`](/router/advanced/native-tabs/) instead.
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx
index 56c998588a94fa..9ba0fd860f7988 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/text.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Text matches the official SwiftUI [Text API](https://developer.apple.com/documentation/swiftui/text).
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/textfield.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/textfield.mdx
index c7e4f635870c53..e87c5e5bf25107 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/textfield.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/textfield.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI TextField matches the official SwiftUI [TextField API](https://developer.apple.com/documentation/swiftui/textfield) and supports single-line and multiline input, keyboard configuration, submit handling, and an imperative `ref` for programmatic control.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/toggle.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/toggle.mdx
index 28b0ca8b4dcdf7..330905081bd2ad 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/toggle.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/toggle.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Toggle matches the official SwiftUI [Toggle API](https://developer.apple.com/documentation/swiftui/toggle) and supports styling via the [`toggleStyle`](modifiers/#togglestylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/vstack.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/vstack.mdx
index 528122cd826552..2a9bb3bc1cd58a 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/vstack.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/vstack.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI VStack matches the official SwiftUI [VStack API](https://developer.apple.com/documentation/swiftui/vstack) and arranges its children vertically.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/zstack.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/zstack.mdx
index 97baea93b65f57..fd570748f029fd 100644
--- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/zstack.mdx
+++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/zstack.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ZStack matches the official SwiftUI [ZStack API](https://developer.apple.com/documentation/swiftui/zstack) and overlays its children on top of each other.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v53.0.0/sdk/ui.mdx b/docs/pages/versions/v53.0.0/sdk/ui.mdx
index 317d0138501469..9f8a6fd23601d7 100644
--- a/docs/pages/versions/v53.0.0/sdk/ui.mdx
+++ b/docs/pages/versions/v53.0.0/sdk/ui.mdx
@@ -130,7 +130,7 @@ import { ColorPicker } from '@expo/ui/swift-ui';
@@ -533,7 +533,7 @@ import { Button } from '@expo/ui/jetpack-compose';
diff --git a/docs/pages/versions/v54.0.0/sdk/ui/jetpack-compose.mdx b/docs/pages/versions/v54.0.0/sdk/ui/jetpack-compose.mdx
index d3028276bc1f1c..492b37657586bf 100644
--- a/docs/pages/versions/v54.0.0/sdk/ui/jetpack-compose.mdx
+++ b/docs/pages/versions/v54.0.0/sdk/ui/jetpack-compose.mdx
@@ -79,7 +79,7 @@ The Jetpack Compose components in `@expo/ui/jetpack-compose` allow you to build
diff --git a/docs/pages/versions/v54.0.0/sdk/ui/swift-ui.mdx b/docs/pages/versions/v54.0.0/sdk/ui/swift-ui.mdx
index 157f4c5bf904e8..e22a85def9025b 100644
--- a/docs/pages/versions/v54.0.0/sdk/ui/swift-ui.mdx
+++ b/docs/pages/versions/v54.0.0/sdk/ui/swift-ui.mdx
@@ -186,7 +186,7 @@ For an in-depth explanation of how `Host` works, see the following resources:
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/index.mdx b/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/index.mdx
index 0864ca32b541b4..56d4c0cd81827f 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/index.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/index.mdx
@@ -11,4 +11,6 @@ The following components provide API-compatible replacements for popular React N
- **[DateTimePicker](datetimepicker)**: Compatible with `@react-native-community/datetimepicker`
- **[BottomSheet](bottomsheet)**: Compatible with `@gorhom/bottom-sheet`
+- **[Picker](picker)**: Compatible with `@react-native-picker/picker`
- **[SegmentedControl](segmentedcontrol)**: Compatible with `@react-native-segmented-control/segmented-control`
+- **[MaskedView](maskedview)**: Compatible with `@react-native-masked-view/masked-view`
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/maskedview.mdx b/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/maskedview.mdx
new file mode 100644
index 00000000000000..52d960e61529a0
--- /dev/null
+++ b/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/maskedview.mdx
@@ -0,0 +1,96 @@
+---
+title: MaskedView
+description: A masked view compatible with @react-native-masked-view/masked-view.
+sourceCodeUrl: 'https://github.com/expo/expo/tree/sdk-56/packages/expo-ui'
+packageName: '@expo/ui'
+platforms: ['android', 'ios']
+---
+
+import APISection from '~/components/plugins/APISection';
+import { APIInstallSection } from '~/components/plugins/InstallSection';
+
+A `MaskedView` component with an API compatible with `@react-native-masked-view/masked-view`. The opaque pixels of `maskElement` reveal the masked content behind it; transparent pixels hide it.
+
+Under the hood this component bridges arbitrary React Native children into the platform-specific `@expo/ui` masking primitives:
+
+- **Android**: Jetpack Compose graphics layer compositing with `BlendMode.DstIn`.
+- **iOS**: SwiftUI [`.mask`]() modifier.
+
+## Installation
+
+
+
+## Migrating from `@react-native-masked-view/masked-view`
+
+- Update the import from `import MaskedView from '@react-native-masked-view/masked-view'` to `import { MaskedView } from '@expo/ui/community/masked-view'`.
+- The `androidRenderingMode` prop is not supported. The Compose-based implementation always uses an offscreen graphics layer, so the prop has no equivalent and is omitted from the public types.
+- Web is not implemented. On web, children render unmasked and a one-time console warning is logged. For web targets, prefer the CSS primitive that fits your specific case:
+ - **Gradient text** — `background-clip: text` with `color: 'transparent'` and a CSS gradient/image as the background.
+ - **Alpha fade** — `mask-image: linear-gradient(...)` (or `WebkitMaskImage`) directly on the content view.
+ - **Shape mask** (circle, rounded rectangle, and so on) — `clip-path: circle(...)` / `inset(...)` / `path(...)`, or `border-radius` + `overflow: 'hidden'`.
+
+## Basic usage
+
+```tsx MaskedViewExample.tsx
+import { MaskedView } from '@expo/ui/community/masked-view';
+import { StyleSheet, Text, View } from 'react-native';
+
+export default function MaskedViewExample() {
+ return (
+
+ EXPO
+
+ }>
+
+
+ );
+}
+```
+
+## Alpha-fade mask
+
+Only the alpha channel of the `maskElement` matters: opaque pixels reveal content, transparent pixels hide it. Use a `LinearGradient` (from `expo-linear-gradient`) that goes from opaque to transparent — `'black'` (for example) to `'transparent'` below — to fade content out along an axis.
+
+```tsx AlphaFadeExample.tsx
+import { MaskedView } from '@expo/ui/community/masked-view';
+import { LinearGradient } from 'expo-linear-gradient';
+import { StyleSheet, View } from 'react-native';
+
+export default function AlphaFadeExample() {
+ return (
+
+ }>
+
+
+
+
+ );
+}
+```
+
+## API
+
+```tsx
+import { MaskedView } from '@expo/ui/community/masked-view';
+```
+
+
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/picker.mdx b/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/picker.mdx
new file mode 100644
index 00000000000000..7bf49f5c64da82
--- /dev/null
+++ b/docs/pages/versions/v56.0.0/sdk/ui/drop-in-replacements/picker.mdx
@@ -0,0 +1,142 @@
+---
+title: Picker
+description: A picker compatible with @react-native-picker/picker.
+sourceCodeUrl: 'https://github.com/expo/expo/tree/sdk-56/packages/expo-ui'
+packageName: '@expo/ui'
+platforms: ['android', 'ios', 'web', 'expo-go']
+---
+
+import APISection from '~/components/plugins/APISection';
+import { APIInstallSection } from '~/components/plugins/InstallSection';
+
+A `Picker` component with an API compatible with `@react-native-picker/picker`. It uses a SwiftUI wheel `Picker` on iOS, a Material 3 `ExposedDropdownMenuBox` on Android, and a native `` element on web.
+
+Under the hood this component wraps the platform-specific `@expo/ui` primitives:
+
+- **Android**: [Jetpack Compose ExposedDropdownMenuBox](../jetpack-compose/exposeddropdownmenubox)
+- **iOS**: [SwiftUI Picker](../swift-ui/picker) with `pickerStyle('wheel')`
+
+If you need lower-level control, use those primitives directly.
+
+## Installation
+
+
+
+## Migrating from `@react-native-picker/picker`
+
+- Update the import from `import { Picker } from '@react-native-picker/picker'` to `import { Picker } from '@expo/ui/community/picker'`.
+- `mode`, `prompt`, `dropdownIconColor`, `dropdownIconRippleColor`, `numberOfLines`, `selectionColor`, `itemStyle`, and `accessibilityLabel` props are not supported.
+- On `Picker.Item`, the `style` prop only applies `color`, `backgroundColor`, `fontFamily`, and `fontSize`. The top-level `color` and `fontFamily` props are still supported as aliases for the corresponding `style` values.
+- `enabled` on `Picker.Item` only applies on Android.
+- The `ref` `focus()` and `blur()` methods only have an effect on Android (open/close the dropdown). On iOS, the wheel picker is always visible.
+
+## Basic usage
+
+```tsx PickerExample.tsx
+import { useState } from 'react';
+import { Text, View } from 'react-native';
+import { Picker } from '@expo/ui/community/picker';
+
+export default function PickerExample() {
+ const [language, setLanguage] = useState('java');
+
+ return (
+
+ setLanguage(value)}>
+
+
+
+
+
+ Selected: {language}
+
+ );
+}
+```
+
+## Per-item styling and state
+
+Pass a `style` to `Picker.Item` to control `color`, `backgroundColor`, `fontFamily`, and `fontSize` per item, and `enabled={false}` to disable specific items on Android.
+
+`fontFamily` accepts iOS font names (for example, `'Menlo'`) on iOS, and Compose generic families (`'monospace'`, `'serif'`, `'sansSerif'`, `'cursive'`) or fonts loaded with [`expo-font`](../../font) on Android.
+
+```tsx StyledPickerExample.tsx
+import { useState } from 'react';
+import { Platform } from 'react-native';
+import { Picker } from '@expo/ui/community/picker';
+
+const monospace = Platform.select({ ios: 'Menlo', android: 'monospace' });
+const serif = Platform.select({ ios: 'Georgia', android: 'serif' });
+
+export default function StyledPickerExample() {
+ const [language, setLanguage] = useState('java');
+
+ return (
+ setLanguage(value)}>
+
+
+
+
+
+ );
+}
+```
+
+## Imperative focus and blur (Android)
+
+Use a ref to programmatically open and close the dropdown on Android. On iOS, these methods are no-ops because the wheel picker is always visible.
+
+```tsx RefPickerExample.tsx
+import { useRef, useState } from 'react';
+import { Button } from 'react-native';
+import { Picker, type PickerRef } from '@expo/ui/community/picker';
+
+export default function RefPickerExample() {
+ const [language, setLanguage] = useState('java');
+ const pickerRef = useRef(null);
+
+ return (
+ <>
+ {
+ pickerRef.current?.focus();
+ setTimeout(() => pickerRef.current?.blur(), 2000);
+ }}
+ />
+
+
+
+
+
+
+ >
+ );
+}
+```
+
+## API
+
+```tsx
+import { Picker } from '@expo/ui/community/picker';
+```
+
+
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx
index 1311a380fe95a6..1209871f4911f4 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI BottomSheet matches the official SwiftUI [sheet API]() and presents content from the bottom of the screen.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/button.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/button.mdx
index 9c666f7b1e662e..dccab2cc435c1b 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/button.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/button.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Button matches the official SwiftUI [Button API](https://developer.apple.com/documentation/swiftui/button) and supports styling via the [`buttonStyle`](modifiers/#buttonstylestyle), [`controlSize`](modifiers/#controlsizesize), and other modifiers.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/colorpicker.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/colorpicker.mdx
index ed07ba9d115751..4afa687bbd82d5 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/colorpicker.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/colorpicker.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ColorPicker matches the official SwiftUI [ColorPicker API](https://developer.apple.com/documentation/swiftui/colorpicker) and allows app users to select colors from a palette.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/confirmationdialog.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/confirmationdialog.mdx
index 842030ba67f692..cc05d58a8edfdf 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/confirmationdialog.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/confirmationdialog.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ConfirmationDialog matches the official SwiftUI [confirmationDialog API]() and presents an action sheet-style dialog with a title, actions, and an optional message.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/contextmenu.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/contextmenu.mdx
index ba2a8cb8aa0f5e..a7fdddb2d33233 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/contextmenu.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/contextmenu.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ContextMenu matches the official SwiftUI [contextMenu API]() and displays a menu when long-pressed. For single-tap menu interactions, use [`Menu`](menu) instead.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/controlgroup.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/controlgroup.mdx
index be1891759b7d6c..22fdd7325dcf88 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/controlgroup.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/controlgroup.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ControlGroup matches the official SwiftUI [ControlGroup API](https://developer.apple.com/documentation/swiftui/controlgroup). When placed inside a [`Menu`](menu), the children are rendered as a compact horizontal row of buttons.
+
+
+
+
> **Note:** On tvOS, `ControlGroup` requires tvOS 17.0 or later.
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/datepicker.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/datepicker.mdx
index 8732a6d0b0a3b0..ec42b408b5c7cb 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/datepicker.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/datepicker.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI DatePicker matches the official SwiftUI [DatePicker API](https://developer.apple.com/documentation/swiftui/datepicker) and supports styling via the [`datePickerStyle`](modifiers/#datepickerstylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/disclosuregroup.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/disclosuregroup.mdx
index 38d245fe5f08fb..50401ddeef0d72 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/disclosuregroup.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/disclosuregroup.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI DisclosureGroup matches the official SwiftUI [DisclosureGroup API](https://developer.apple.com/documentation/swiftui/disclosuregroup) and displays a disclosure indicator that reveals or hides content.
+
+
+
+
## Installation
@@ -19,15 +29,28 @@ Expo UI DisclosureGroup matches the official SwiftUI [DisclosureGroup API](https
### Basic disclosure group
+`DisclosureGroup` is most commonly used inside a [`Form`](form) so it picks up the standard iOS list styling with a chevron indicator.
+
```tsx BasicDisclosureGroupExample.tsx
-import { Host, DisclosureGroup, Text } from '@expo/ui/swift-ui';
+import { useState } from 'react';
+import { DisclosureGroup, Form, Host, Section, Text } from '@expo/ui/swift-ui';
export default function BasicDisclosureGroupExample() {
+ const [isExpanded, setIsExpanded] = useState(true);
return (
-
-
- This content is revealed when the disclosure group is expanded.
-
+
+
);
}
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/divider.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/divider.mdx
index 07af029a4ac168..9eb4c2c331f11a 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/divider.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/divider.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Divider matches the official SwiftUI [Divider API](https://developer.apple.com/documentation/swiftui/divider) and creates a visual separator between content.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/form.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/form.mdx
index 74b4dbf7fb4946..c3c7a501ba9ca7 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/form.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/form.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Form matches the official SwiftUI [Form API](https://developer.apple.com/documentation/swiftui/form). It provides a container for grouping controls used for data entry, such as in settings or inspection panes.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/gauge.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/gauge.mdx
index d26254fd2ef4ef..28b8b11206d7fb 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/gauge.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/gauge.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Gauge matches the official SwiftUI [Gauge API](https://developer.apple.com/documentation/swiftui/gauge) and supports styling via the [`gaugeStyle`](modifiers/#gaugestylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/group.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/group.mdx
index a330aa6fb8989c..5a24f8aaf6b7c3 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/group.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/group.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Group matches the official SwiftUI [Group API](https://developer.apple.com/documentation/swiftui/group) and groups views together without introducing additional layout structure.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx
index 9cb3fc80da7851..ab30d279c4bb0e 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI HStack matches the official SwiftUI [HStack API](https://developer.apple.com/documentation/swiftui/hstack) and arranges its children horizontally.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/image.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/image.mdx
index bbcf7d0865ae55..df9a93c8650f9d 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/image.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/image.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Image displays SF Symbols using the SwiftUI [Image API](https://developer.apple.com/documentation/swiftui/image). SF Symbols are a library of configurable symbols provided by Apple.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/label.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/label.mdx
index d248ec57a97949..b425c5a9c130cb 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/label.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/label.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Label matches the official SwiftUI [Label API](https://developer.apple.com/documentation/swiftui/label) and displays a title alongside an icon.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/link.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/link.mdx
index 9eceab846b97aa..782ea74032ef4a 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/link.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/link.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Link matches the official SwiftUI [Link API](https://developer.apple.com/documentation/swiftui/link).
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/list.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/list.mdx
index 431b8157144bad..630841bde7a69e 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/list.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/list.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI List matches the official SwiftUI [List API](https://developer.apple.com/documentation/swiftui/list) and supports styling via the [`listStyle`](modifiers/#liststylestyle) modifier, various row/section modifiers, as well as selection, reordering, and editing capabilities.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx
index 5dacbf17bb4503..14c83e61d2172d 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/menu.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Menu matches the official SwiftUI [Menu API](https://developer.apple.com/documentation/swiftui/menu) and supports styling via the [`buttonStyle`](modifiers/#buttonstylestyle) modifier. Menu opens on a single tap. For long-press interactions, use [`ContextMenu`](contextmenu) instead.
+
+
+
+
> **Note:** On tvOS, Menu requires tvOS 17.0 or later.
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/overlay.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/overlay.mdx
index d7880f569c687e..f85820d26d61dc 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/overlay.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/overlay.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvOS', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Overlay matches the official SwiftUI [overlay]() modifier and provides a way to layer secondary content on top of a view, positioned with a specified alignment.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/picker.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/picker.mdx
index adb8c9424d43a2..946fcd019d0c33 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/picker.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/picker.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Picker matches the official SwiftUI [Picker API](https://developer.apple.com/documentation/swiftui/picker) and supports all picker styles via the [`pickerStyle`](modifiers/#pickerstylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/popover.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/popover.mdx
index cab78464db8999..8d3d07e90e171a 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/popover.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/popover.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Popover matches the official SwiftUI [Popover API]() and provides a way to present content in a floating overlay anchored to a trigger element.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/progressview.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/progressview.mdx
index 817b019f3eb50d..8a8615624f1fe6 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/progressview.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/progressview.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ProgressView matches the official SwiftUI [ProgressView API](https://developer.apple.com/documentation/swiftui/progressview) and supports styling via the [`progressViewStyle`](modifiers/#progressviewstylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/scrollview.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/scrollview.mdx
index 2aae9c114c6957..b69b27b2c85b01 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/scrollview.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/scrollview.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ScrollView matches the official SwiftUI [ScrollView API](https://developer.apple.com/documentation/swiftui/scrollview) and provides a scrollable container for its children.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/section.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/section.mdx
index c9c917c2f7c57b..7d51e726658698 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/section.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/section.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Section matches the official SwiftUI [Section API](https://developer.apple.com/documentation/swiftui/section) and is used to group related content within [`List`](list), [`Form`](form) or [`Picker`](picker) components.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/securefield.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/securefield.mdx
index 4ca25ddc80c18e..300025663d8409 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/securefield.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/securefield.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI SecureField matches the official SwiftUI [SecureField API](https://developer.apple.com/documentation/swiftui/securefield) and provides a text field that masks user input for passwords and other sensitive text.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx
index a63a38015b56a7..a7986c87cf7eae 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Slider matches the official SwiftUI [Slider API](https://developer.apple.com/documentation/swiftui/slider) and allows selecting values from a bounded range.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/spacer.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/spacer.mdx
index 12952adbe5a8d8..090f38d5c10ca9 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/spacer.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/spacer.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Spacer matches the official SwiftUI [Spacer API](https://developer.apple.com/documentation/swiftui/spacer) and expands to fill available space in a stack.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/tabview.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/tabview.mdx
index 3dfbbbc8d7d3ff..d85fb16ce06192 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/tabview.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/tabview.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI TabView matches the official SwiftUI [TabView API](https://developer.apple.com/documentation/swiftui/tabview) and switches between styles via the [`tabViewStyle`](modifiers/#tabviewstyleconfig) modifier.
+
+
+
+
> **Note:** For routed bottom-tab navigation across full-screen routes, use [expo-router/unstable-native-tabs`](/router/advanced/native-tabs/) instead.
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/text.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/text.mdx
index 723b19d4d2977a..8df56627049732 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/text.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/text.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Text matches the official SwiftUI [Text API](https://developer.apple.com/documentation/swiftui/text).
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/textfield.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/textfield.mdx
index 4c4bb30320af6f..90d9de3fc256c6 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/textfield.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/textfield.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI TextField matches the official SwiftUI [TextField API](https://developer.apple.com/documentation/swiftui/textfield) and supports single-line and multiline input, keyboard configuration, submit handling, and an imperative `ref` for programmatic control.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/toggle.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/toggle.mdx
index 6bfa2652d9fca5..ac5d04a7cbfd13 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/toggle.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/toggle.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI Toggle matches the official SwiftUI [Toggle API](https://developer.apple.com/documentation/swiftui/toggle) and supports styling via the [`toggleStyle`](modifiers/#togglestylestyle) modifier.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/vstack.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/vstack.mdx
index 2d7eb0c4d68350..002f8709030e83 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/vstack.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/vstack.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI VStack matches the official SwiftUI [VStack API](https://developer.apple.com/documentation/swiftui/vstack) and arranges its children vertically.
+
+
+
+
## Installation
diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/zstack.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/zstack.mdx
index 14dc6ae72c4e45..9d14202aee3f3a 100644
--- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/zstack.mdx
+++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/zstack.mdx
@@ -8,9 +8,19 @@ platforms: ['ios', 'tvos', 'expo-go']
import APISection from '~/components/plugins/APISection';
import { APIInstallSection } from '~/components/plugins/InstallSection';
+import { Diagram } from '~/ui/components/Diagram';
Expo UI ZStack matches the official SwiftUI [ZStack API](https://developer.apple.com/documentation/swiftui/zstack) and overlays its children on top of each other.
+
+
+
+
## Installation
diff --git a/docs/public/static/data/unversioned/expo-ui/community/masked-view.json b/docs/public/static/data/unversioned/expo-ui/community/masked-view.json
new file mode 100644
index 00000000000000..8ac1370c2c8c29
--- /dev/null
+++ b/docs/public/static/data/unversioned/expo-ui/community/masked-view.json
@@ -0,0 +1 @@
+{"schemaVersion":"2.0","name":"expo-ui/community/masked-view","variant":"project","kind":1,"children":[{"name":"MaskedViewProps","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"Drop-in props for "},{"kind":"code","text":"`@react-native-masked-view/masked-view`"},{"kind":"text","text":"'s "},{"kind":"code","text":"`MaskedView`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@see","content":[{"kind":"text","text":"https://github.com/callstack/masked-view"}]}]},"children":[{"name":"children","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Content rendered behind the mask."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"},"overwrites":{"type":"reference","name":"ViewProps.children"}},{"name":"maskElement","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The element used as the mask. Only opaque pixels of "},{"kind":"code","text":"`maskElement`"},{"kind":"text","text":" make the\nmasked content visible — transparent pixels hide it."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactElement"},"name":"ReactElement","package":"@types/react","qualifiedName":"React.ReactElement"}}],"extendedTypes":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/Components/View/ViewPropTypes.d.ts","qualifiedName":"ViewProps"},"name":"ViewProps","package":"react-native"}]},{"name":"MaskedView","variant":"declaration","kind":64,"signatures":[{"name":"MaskedView","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Renders "},{"kind":"code","text":"`children`"},{"kind":"text","text":" with the alpha channel of "},{"kind":"code","text":"`maskElement`"},{"kind":"text","text":" applied as a mask:\nopaque pixels of "},{"kind":"code","text":"`maskElement`"},{"kind":"text","text":" reveal "},{"kind":"code","text":"`children`"},{"kind":"text","text":", transparent pixels hide them.\n\nAPI-compatible with "},{"kind":"code","text":"`@react-native-masked-view/masked-view`"},{"kind":"text","text":"."}]},"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"MaskedViewProps","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"}}]},{"name":"default","variant":"reference","kind":4194304}],"packageName":"@expo/ui"}
\ No newline at end of file
diff --git a/docs/public/static/data/v56.0.0/expo-ui/community/masked-view.json b/docs/public/static/data/v56.0.0/expo-ui/community/masked-view.json
new file mode 100644
index 00000000000000..8ac1370c2c8c29
--- /dev/null
+++ b/docs/public/static/data/v56.0.0/expo-ui/community/masked-view.json
@@ -0,0 +1 @@
+{"schemaVersion":"2.0","name":"expo-ui/community/masked-view","variant":"project","kind":1,"children":[{"name":"MaskedViewProps","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"Drop-in props for "},{"kind":"code","text":"`@react-native-masked-view/masked-view`"},{"kind":"text","text":"'s "},{"kind":"code","text":"`MaskedView`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@see","content":[{"kind":"text","text":"https://github.com/callstack/masked-view"}]}]},"children":[{"name":"children","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Content rendered behind the mask."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"},"overwrites":{"type":"reference","name":"ViewProps.children"}},{"name":"maskElement","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The element used as the mask. Only opaque pixels of "},{"kind":"code","text":"`maskElement`"},{"kind":"text","text":" make the\nmasked content visible — transparent pixels hide it."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactElement"},"name":"ReactElement","package":"@types/react","qualifiedName":"React.ReactElement"}}],"extendedTypes":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/Components/View/ViewPropTypes.d.ts","qualifiedName":"ViewProps"},"name":"ViewProps","package":"react-native"}]},{"name":"MaskedView","variant":"declaration","kind":64,"signatures":[{"name":"MaskedView","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Renders "},{"kind":"code","text":"`children`"},{"kind":"text","text":" with the alpha channel of "},{"kind":"code","text":"`maskElement`"},{"kind":"text","text":" applied as a mask:\nopaque pixels of "},{"kind":"code","text":"`maskElement`"},{"kind":"text","text":" reveal "},{"kind":"code","text":"`children`"},{"kind":"text","text":", transparent pixels hide them.\n\nAPI-compatible with "},{"kind":"code","text":"`@react-native-masked-view/masked-view`"},{"kind":"text","text":"."}]},"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"MaskedViewProps","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"}}]},{"name":"default","variant":"reference","kind":4194304}],"packageName":"@expo/ui"}
\ No newline at end of file
diff --git a/docs/public/static/data/v56.0.0/expo-ui/community/picker.json b/docs/public/static/data/v56.0.0/expo-ui/community/picker.json
new file mode 100644
index 00000000000000..c293f9417515fc
--- /dev/null
+++ b/docs/public/static/data/v56.0.0/expo-ui/community/picker.json
@@ -0,0 +1 @@
+{"schemaVersion":"2.0","name":"expo-ui/community/picker","variant":"project","kind":1,"children":[{"name":"PickerItemProps","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Props for the "},{"kind":"code","text":"`Picker.Item`"},{"kind":"text","text":" component.\nCompatible with "},{"kind":"code","text":"`@react-native-picker/picker`"},{"kind":"text","text":"."}]},"children":[{"name":"color","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Text color for the item. Equivalent to setting "},{"kind":"code","text":"`color`"},{"kind":"text","text":" in the "},{"kind":"code","text":"`style`"},{"kind":"text","text":" prop."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"enabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the item is enabled."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"fontFamily","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Custom font family for the item. Equivalent to setting "},{"kind":"code","text":"`fontFamily`"},{"kind":"text","text":" in the "},{"kind":"code","text":"`style`"},{"kind":"text","text":" prop."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"label","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Display text for the item."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"style","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Style applied to the item label. Only the following values take effect:\n"},{"kind":"code","text":"`color`"},{"kind":"text","text":", "},{"kind":"code","text":"`backgroundColor`"},{"kind":"text","text":", "},{"kind":"code","text":"`fontFamily`"},{"kind":"text","text":", and "},{"kind":"code","text":"`fontSize`"},{"kind":"text","text":". When also set\nvia the top-level "},{"kind":"code","text":"`color`"},{"kind":"text","text":" or "},{"kind":"code","text":"`fontFamily`"},{"kind":"text","text":" props, values from "},{"kind":"code","text":"`style`"},{"kind":"text","text":" win."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"StyleProp"},"typeArguments":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheetTypes.d.ts","qualifiedName":"TextStyle"},"name":"TextStyle","package":"react-native"}],"name":"StyleProp","package":"react-native"}},{"name":"testID","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Test identifier."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"value","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Value passed to "},{"kind":"code","text":"`onValueChange`"},{"kind":"text","text":" when this item is selected."}]},"type":{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}}],"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"type":{"type":"reference","name":"PickerItemValue","package":"@expo/ui"},"default":{"type":"reference","name":"PickerItemValue","package":"@expo/ui"}}]},{"name":"PickerItemValue","variant":"declaration","kind":2097152,"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"intrinsic","name":"number"},{"type":"literal","value":null}]}},{"name":"PickerProps","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Props for the "},{"kind":"code","text":"`Picker`"},{"kind":"text","text":" component.\nCompatible with "},{"kind":"code","text":"`@react-native-picker/picker`"},{"kind":"text","text":"."}]},"children":[{"name":"children","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"code","text":"`Picker.Item`"},{"kind":"text","text":" children that define the available options."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ReactNode"},"name":"ReactNode","package":"@types/react","qualifiedName":"React.ReactNode"}},{"name":"enabled","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the picker is enabled."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"onValueChange","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Callback when an item is selected. Called with "},{"kind":"code","text":"`(itemValue, itemIndex)`"},{"kind":"text","text":"."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"itemValue","variant":"param","kind":32768,"type":{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}},{"name":"itemIndex","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"ref","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Ref handle exposing "},{"kind":"code","text":"`focus()`"},{"kind":"text","text":" and "},{"kind":"code","text":"`blur()`"},{"kind":"text","text":" methods."}]},"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.Ref"},"typeArguments":[{"type":"reference","name":"PickerRef","package":"@expo/ui"}],"name":"Ref","package":"@types/react","qualifiedName":"React.Ref"}},{"name":"selectedValue","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The currently selected value. Must match the "},{"kind":"code","text":"`value`"},{"kind":"text","text":" of one of the "},{"kind":"code","text":"`Picker.Item`"},{"kind":"text","text":" children."}]},"type":{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}},{"name":"style","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Style applied to the picker container."}]},"type":{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheet.d.ts","qualifiedName":"StyleProp"},"typeArguments":[{"type":"reference","target":{"packageName":"react-native","packagePath":"Libraries/StyleSheet/StyleSheetTypes.d.ts","qualifiedName":"ViewStyle"},"name":"ViewStyle","package":"react-native"}],"name":"StyleProp","package":"react-native"}},{"name":"testID","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Test identifier."}]},"type":{"type":"intrinsic","name":"string"}}],"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"type":{"type":"reference","name":"PickerItemValue","package":"@expo/ui"},"default":{"type":"reference","name":"PickerItemValue","package":"@expo/ui"}}]},{"name":"PickerRef","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Ref handle for the "},{"kind":"code","text":"`Picker`"},{"kind":"text","text":" component.\nCompatible with "},{"kind":"code","text":"`@react-native-picker/picker`"},{"kind":"text","text":"."}]},"children":[{"name":"blur","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically closes the picker."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}},{"name":"focus","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Programmatically opens the picker."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}}]},{"name":"Picker","variant":"declaration","kind":64,"children":[{"name":"Item","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.ComponentType"},"typeArguments":[{"type":"reference","name":"PickerItemProps","package":"@expo/ui"}],"name":"ComponentType","package":"@types/react","qualifiedName":"React.ComponentType"}}],"signatures":[{"name":"Picker","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"A drop-in replacement for "},{"kind":"code","text":"`@react-native-picker/picker`"},{"kind":"text","text":" on web.\nRenders a native "},{"kind":"code","text":"``"},{"kind":"text","text":" element."}]},"typeParameters":[{"name":"T","variant":"typeParam","kind":131072,"type":{"type":"reference","name":"PickerItemValue","package":"@expo/ui"}}],"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","typeArguments":[{"type":"reference","name":"T","package":"@expo/ui","refersToTypeParameter":true}],"name":"PickerProps","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"}}]},{"name":"default","variant":"reference","kind":4194304}],"packageName":"@expo/ui"}
\ No newline at end of file
diff --git a/docs/public/static/images/expo-ui/bottomsheet/ios-dark.webp b/docs/public/static/images/expo-ui/bottomsheet/ios-dark.webp
new file mode 100644
index 00000000000000..0d3c7b30a9799e
Binary files /dev/null and b/docs/public/static/images/expo-ui/bottomsheet/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/bottomsheet/ios-light.webp b/docs/public/static/images/expo-ui/bottomsheet/ios-light.webp
new file mode 100644
index 00000000000000..323407ac710bb7
Binary files /dev/null and b/docs/public/static/images/expo-ui/bottomsheet/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/button/ios-dark.webp b/docs/public/static/images/expo-ui/button/ios-dark.webp
new file mode 100644
index 00000000000000..c5c6c93006899f
Binary files /dev/null and b/docs/public/static/images/expo-ui/button/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/button/ios-light.webp b/docs/public/static/images/expo-ui/button/ios-light.webp
new file mode 100644
index 00000000000000..51e1644c6f2355
Binary files /dev/null and b/docs/public/static/images/expo-ui/button/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/button/ios.png b/docs/public/static/images/expo-ui/button/ios.png
deleted file mode 100644
index bc883123a1267b..00000000000000
Binary files a/docs/public/static/images/expo-ui/button/ios.png and /dev/null differ
diff --git a/docs/public/static/images/expo-ui/colorpicker/ios-dark.webp b/docs/public/static/images/expo-ui/colorpicker/ios-dark.webp
new file mode 100644
index 00000000000000..498213ef6b0f95
Binary files /dev/null and b/docs/public/static/images/expo-ui/colorpicker/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/colorpicker/ios-light.webp b/docs/public/static/images/expo-ui/colorpicker/ios-light.webp
new file mode 100644
index 00000000000000..a78306f4d9df1b
Binary files /dev/null and b/docs/public/static/images/expo-ui/colorpicker/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/colorpicker/ios.png b/docs/public/static/images/expo-ui/colorpicker/ios.png
deleted file mode 100644
index b20042dda01091..00000000000000
Binary files a/docs/public/static/images/expo-ui/colorpicker/ios.png and /dev/null differ
diff --git a/docs/public/static/images/expo-ui/confirmationdialog/ios-dark.webp b/docs/public/static/images/expo-ui/confirmationdialog/ios-dark.webp
new file mode 100644
index 00000000000000..d5348af987db17
Binary files /dev/null and b/docs/public/static/images/expo-ui/confirmationdialog/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/confirmationdialog/ios-light.webp b/docs/public/static/images/expo-ui/confirmationdialog/ios-light.webp
new file mode 100644
index 00000000000000..8355e4ea6cf19a
Binary files /dev/null and b/docs/public/static/images/expo-ui/confirmationdialog/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/contextMenu/android.png b/docs/public/static/images/expo-ui/contextmenu/android.png
similarity index 100%
rename from docs/public/static/images/expo-ui/contextMenu/android.png
rename to docs/public/static/images/expo-ui/contextmenu/android.png
diff --git a/docs/public/static/images/expo-ui/contextmenu/ios-dark.webp b/docs/public/static/images/expo-ui/contextmenu/ios-dark.webp
new file mode 100644
index 00000000000000..ae5b927dac3d8b
Binary files /dev/null and b/docs/public/static/images/expo-ui/contextmenu/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/contextmenu/ios-light.webp b/docs/public/static/images/expo-ui/contextmenu/ios-light.webp
new file mode 100644
index 00000000000000..e068ee3b0cbf5b
Binary files /dev/null and b/docs/public/static/images/expo-ui/contextmenu/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/contextmenu/ios.png b/docs/public/static/images/expo-ui/contextmenu/ios.png
new file mode 100644
index 00000000000000..8956b8d5dc2064
Binary files /dev/null and b/docs/public/static/images/expo-ui/contextmenu/ios.png differ
diff --git a/docs/public/static/images/expo-ui/controlgroup/ios-dark.webp b/docs/public/static/images/expo-ui/controlgroup/ios-dark.webp
new file mode 100644
index 00000000000000..e9f5dce472a96f
Binary files /dev/null and b/docs/public/static/images/expo-ui/controlgroup/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/controlgroup/ios-light.webp b/docs/public/static/images/expo-ui/controlgroup/ios-light.webp
new file mode 100644
index 00000000000000..9cf18eebe0806d
Binary files /dev/null and b/docs/public/static/images/expo-ui/controlgroup/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/datepicker/ios-dark.webp b/docs/public/static/images/expo-ui/datepicker/ios-dark.webp
new file mode 100644
index 00000000000000..66e94013cb59c9
Binary files /dev/null and b/docs/public/static/images/expo-ui/datepicker/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/datepicker/ios-light.webp b/docs/public/static/images/expo-ui/datepicker/ios-light.webp
new file mode 100644
index 00000000000000..c126d5c3152974
Binary files /dev/null and b/docs/public/static/images/expo-ui/datepicker/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/disclosuregroup/ios-dark.webp b/docs/public/static/images/expo-ui/disclosuregroup/ios-dark.webp
new file mode 100644
index 00000000000000..6ac252c7e14e54
Binary files /dev/null and b/docs/public/static/images/expo-ui/disclosuregroup/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/disclosuregroup/ios-light.webp b/docs/public/static/images/expo-ui/disclosuregroup/ios-light.webp
new file mode 100644
index 00000000000000..06d85bf3e3c1ef
Binary files /dev/null and b/docs/public/static/images/expo-ui/disclosuregroup/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/divider/ios-dark.webp b/docs/public/static/images/expo-ui/divider/ios-dark.webp
new file mode 100644
index 00000000000000..d7df206c779e42
Binary files /dev/null and b/docs/public/static/images/expo-ui/divider/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/divider/ios-light.webp b/docs/public/static/images/expo-ui/divider/ios-light.webp
new file mode 100644
index 00000000000000..c88fa607cecf16
Binary files /dev/null and b/docs/public/static/images/expo-ui/divider/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/form/ios-dark.webp b/docs/public/static/images/expo-ui/form/ios-dark.webp
new file mode 100644
index 00000000000000..d34a311207b31f
Binary files /dev/null and b/docs/public/static/images/expo-ui/form/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/form/ios-light.webp b/docs/public/static/images/expo-ui/form/ios-light.webp
new file mode 100644
index 00000000000000..2114d7cfea5662
Binary files /dev/null and b/docs/public/static/images/expo-ui/form/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/gauge/ios-dark.webp b/docs/public/static/images/expo-ui/gauge/ios-dark.webp
new file mode 100644
index 00000000000000..f36546f74e5ee8
Binary files /dev/null and b/docs/public/static/images/expo-ui/gauge/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/gauge/ios-light.webp b/docs/public/static/images/expo-ui/gauge/ios-light.webp
new file mode 100644
index 00000000000000..9de07c29960c0f
Binary files /dev/null and b/docs/public/static/images/expo-ui/gauge/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/gauge/ios.png b/docs/public/static/images/expo-ui/gauge/ios.png
deleted file mode 100644
index cd47b53dd0a86b..00000000000000
Binary files a/docs/public/static/images/expo-ui/gauge/ios.png and /dev/null differ
diff --git a/docs/public/static/images/expo-ui/group/ios-dark.webp b/docs/public/static/images/expo-ui/group/ios-dark.webp
new file mode 100644
index 00000000000000..685dbfcf048675
Binary files /dev/null and b/docs/public/static/images/expo-ui/group/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/group/ios-light.webp b/docs/public/static/images/expo-ui/group/ios-light.webp
new file mode 100644
index 00000000000000..d7b2c521394e68
Binary files /dev/null and b/docs/public/static/images/expo-ui/group/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/hstack/ios-dark.webp b/docs/public/static/images/expo-ui/hstack/ios-dark.webp
new file mode 100644
index 00000000000000..4cb4cb48a4483d
Binary files /dev/null and b/docs/public/static/images/expo-ui/hstack/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/hstack/ios-light.webp b/docs/public/static/images/expo-ui/hstack/ios-light.webp
new file mode 100644
index 00000000000000..9085637670e685
Binary files /dev/null and b/docs/public/static/images/expo-ui/hstack/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/image/ios-dark.webp b/docs/public/static/images/expo-ui/image/ios-dark.webp
new file mode 100644
index 00000000000000..870042b9fce5d2
Binary files /dev/null and b/docs/public/static/images/expo-ui/image/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/image/ios-light.webp b/docs/public/static/images/expo-ui/image/ios-light.webp
new file mode 100644
index 00000000000000..5e598e94c97247
Binary files /dev/null and b/docs/public/static/images/expo-ui/image/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/label/ios-dark.webp b/docs/public/static/images/expo-ui/label/ios-dark.webp
new file mode 100644
index 00000000000000..e062dbaaf67d60
Binary files /dev/null and b/docs/public/static/images/expo-ui/label/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/label/ios-light.webp b/docs/public/static/images/expo-ui/label/ios-light.webp
new file mode 100644
index 00000000000000..2b6979dd7620e3
Binary files /dev/null and b/docs/public/static/images/expo-ui/label/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/link/ios-dark.webp b/docs/public/static/images/expo-ui/link/ios-dark.webp
new file mode 100644
index 00000000000000..b94ac0dba3ba15
Binary files /dev/null and b/docs/public/static/images/expo-ui/link/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/link/ios-light.webp b/docs/public/static/images/expo-ui/link/ios-light.webp
new file mode 100644
index 00000000000000..c549ecd6becc73
Binary files /dev/null and b/docs/public/static/images/expo-ui/link/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/list/ios-dark.webp b/docs/public/static/images/expo-ui/list/ios-dark.webp
new file mode 100644
index 00000000000000..62a46e4480d71e
Binary files /dev/null and b/docs/public/static/images/expo-ui/list/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/list/ios-light.webp b/docs/public/static/images/expo-ui/list/ios-light.webp
new file mode 100644
index 00000000000000..98d7da7d5155d7
Binary files /dev/null and b/docs/public/static/images/expo-ui/list/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/list/ios.png b/docs/public/static/images/expo-ui/list/ios.png
deleted file mode 100644
index 81661a31d3d109..00000000000000
Binary files a/docs/public/static/images/expo-ui/list/ios.png and /dev/null differ
diff --git a/docs/public/static/images/expo-ui/menu/ios-dark.webp b/docs/public/static/images/expo-ui/menu/ios-dark.webp
new file mode 100644
index 00000000000000..d6049a0f2ed46c
Binary files /dev/null and b/docs/public/static/images/expo-ui/menu/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/menu/ios-light.webp b/docs/public/static/images/expo-ui/menu/ios-light.webp
new file mode 100644
index 00000000000000..d00df5253931f2
Binary files /dev/null and b/docs/public/static/images/expo-ui/menu/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/overlay/ios-dark.webp b/docs/public/static/images/expo-ui/overlay/ios-dark.webp
new file mode 100644
index 00000000000000..32df7c1df288ac
Binary files /dev/null and b/docs/public/static/images/expo-ui/overlay/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/overlay/ios-light.webp b/docs/public/static/images/expo-ui/overlay/ios-light.webp
new file mode 100644
index 00000000000000..9608291934dabf
Binary files /dev/null and b/docs/public/static/images/expo-ui/overlay/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/picker/ios-dark.webp b/docs/public/static/images/expo-ui/picker/ios-dark.webp
new file mode 100644
index 00000000000000..e2f7ceb4578eac
Binary files /dev/null and b/docs/public/static/images/expo-ui/picker/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/picker/ios-light.webp b/docs/public/static/images/expo-ui/picker/ios-light.webp
new file mode 100644
index 00000000000000..f7347febab767d
Binary files /dev/null and b/docs/public/static/images/expo-ui/picker/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/popover/ios-dark.webp b/docs/public/static/images/expo-ui/popover/ios-dark.webp
new file mode 100644
index 00000000000000..3e5369abed9154
Binary files /dev/null and b/docs/public/static/images/expo-ui/popover/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/popover/ios-light.webp b/docs/public/static/images/expo-ui/popover/ios-light.webp
new file mode 100644
index 00000000000000..f1de4f1b52428b
Binary files /dev/null and b/docs/public/static/images/expo-ui/popover/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/progressview/ios-dark.webp b/docs/public/static/images/expo-ui/progressview/ios-dark.webp
new file mode 100644
index 00000000000000..77bb5b3d483ea9
Binary files /dev/null and b/docs/public/static/images/expo-ui/progressview/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/progressview/ios-light.webp b/docs/public/static/images/expo-ui/progressview/ios-light.webp
new file mode 100644
index 00000000000000..d3988f9e70fea8
Binary files /dev/null and b/docs/public/static/images/expo-ui/progressview/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/scrollview/ios-dark.webp b/docs/public/static/images/expo-ui/scrollview/ios-dark.webp
new file mode 100644
index 00000000000000..39a253f3abd2fb
Binary files /dev/null and b/docs/public/static/images/expo-ui/scrollview/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/scrollview/ios-light.webp b/docs/public/static/images/expo-ui/scrollview/ios-light.webp
new file mode 100644
index 00000000000000..2bf507fe58e7ab
Binary files /dev/null and b/docs/public/static/images/expo-ui/scrollview/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/section/ios-dark.webp b/docs/public/static/images/expo-ui/section/ios-dark.webp
new file mode 100644
index 00000000000000..5e6ec1efcf662c
Binary files /dev/null and b/docs/public/static/images/expo-ui/section/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/section/ios-light.webp b/docs/public/static/images/expo-ui/section/ios-light.webp
new file mode 100644
index 00000000000000..3f066623a24934
Binary files /dev/null and b/docs/public/static/images/expo-ui/section/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/securefield/ios-dark.webp b/docs/public/static/images/expo-ui/securefield/ios-dark.webp
new file mode 100644
index 00000000000000..854a67f2883ab0
Binary files /dev/null and b/docs/public/static/images/expo-ui/securefield/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/securefield/ios-light.webp b/docs/public/static/images/expo-ui/securefield/ios-light.webp
new file mode 100644
index 00000000000000..2f3cff2948c9d0
Binary files /dev/null and b/docs/public/static/images/expo-ui/securefield/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/slider/ios-dark.webp b/docs/public/static/images/expo-ui/slider/ios-dark.webp
new file mode 100644
index 00000000000000..4a83fb6665d5c0
Binary files /dev/null and b/docs/public/static/images/expo-ui/slider/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/slider/ios-light.webp b/docs/public/static/images/expo-ui/slider/ios-light.webp
new file mode 100644
index 00000000000000..0bd07abd981483
Binary files /dev/null and b/docs/public/static/images/expo-ui/slider/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/slider/ios.png b/docs/public/static/images/expo-ui/slider/ios.png
deleted file mode 100644
index ff4110014d094f..00000000000000
Binary files a/docs/public/static/images/expo-ui/slider/ios.png and /dev/null differ
diff --git a/docs/public/static/images/expo-ui/spacer/ios-dark.webp b/docs/public/static/images/expo-ui/spacer/ios-dark.webp
new file mode 100644
index 00000000000000..a9de7050c079e7
Binary files /dev/null and b/docs/public/static/images/expo-ui/spacer/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/spacer/ios-light.webp b/docs/public/static/images/expo-ui/spacer/ios-light.webp
new file mode 100644
index 00000000000000..033bad73a49bb3
Binary files /dev/null and b/docs/public/static/images/expo-ui/spacer/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/tabview/ios-dark.webp b/docs/public/static/images/expo-ui/tabview/ios-dark.webp
new file mode 100644
index 00000000000000..a619e1006277e4
Binary files /dev/null and b/docs/public/static/images/expo-ui/tabview/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/tabview/ios-light.webp b/docs/public/static/images/expo-ui/tabview/ios-light.webp
new file mode 100644
index 00000000000000..11fa6154aceb76
Binary files /dev/null and b/docs/public/static/images/expo-ui/tabview/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/text/ios-dark.webp b/docs/public/static/images/expo-ui/text/ios-dark.webp
new file mode 100644
index 00000000000000..10d601a9efa615
Binary files /dev/null and b/docs/public/static/images/expo-ui/text/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/text/ios-light.webp b/docs/public/static/images/expo-ui/text/ios-light.webp
new file mode 100644
index 00000000000000..62863ec170fb43
Binary files /dev/null and b/docs/public/static/images/expo-ui/text/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/textfield/ios-dark.webp b/docs/public/static/images/expo-ui/textfield/ios-dark.webp
new file mode 100644
index 00000000000000..4eff7c678fdd2a
Binary files /dev/null and b/docs/public/static/images/expo-ui/textfield/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/textfield/ios-light.webp b/docs/public/static/images/expo-ui/textfield/ios-light.webp
new file mode 100644
index 00000000000000..90996faa40e4e0
Binary files /dev/null and b/docs/public/static/images/expo-ui/textfield/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/toggle/ios-dark.webp b/docs/public/static/images/expo-ui/toggle/ios-dark.webp
new file mode 100644
index 00000000000000..96482eeb819859
Binary files /dev/null and b/docs/public/static/images/expo-ui/toggle/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/toggle/ios-light.webp b/docs/public/static/images/expo-ui/toggle/ios-light.webp
new file mode 100644
index 00000000000000..b9504ef0198abc
Binary files /dev/null and b/docs/public/static/images/expo-ui/toggle/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/vstack/ios-dark.webp b/docs/public/static/images/expo-ui/vstack/ios-dark.webp
new file mode 100644
index 00000000000000..feda3c6c93b783
Binary files /dev/null and b/docs/public/static/images/expo-ui/vstack/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/vstack/ios-light.webp b/docs/public/static/images/expo-ui/vstack/ios-light.webp
new file mode 100644
index 00000000000000..13f4e12aaced5f
Binary files /dev/null and b/docs/public/static/images/expo-ui/vstack/ios-light.webp differ
diff --git a/docs/public/static/images/expo-ui/zstack/ios-dark.webp b/docs/public/static/images/expo-ui/zstack/ios-dark.webp
new file mode 100644
index 00000000000000..1a5316f1c06757
Binary files /dev/null and b/docs/public/static/images/expo-ui/zstack/ios-dark.webp differ
diff --git a/docs/public/static/images/expo-ui/zstack/ios-light.webp b/docs/public/static/images/expo-ui/zstack/ios-light.webp
new file mode 100644
index 00000000000000..dcfac35b8f9254
Binary files /dev/null and b/docs/public/static/images/expo-ui/zstack/ios-light.webp differ
diff --git a/docs/ui/components/EASCLIReference/index.tsx b/docs/ui/components/EASCLIReference/index.tsx
index 263fbc1aa245d6..1f1ebe33efb61e 100644
--- a/docs/ui/components/EASCLIReference/index.tsx
+++ b/docs/ui/components/EASCLIReference/index.tsx
@@ -1,5 +1,7 @@
+import { type ReactNode } from 'react';
+
import { Terminal } from '~/ui/components/Snippet';
-import { CODE, H3, H4, LI, P, UL } from '~/ui/components/Text';
+import { CODE, H3, LI, P, UL } from '~/ui/components/Text';
import easCliData from './data/eas-cli-commands.json';
import {
@@ -25,6 +27,12 @@ type EasCliReferenceData = {
commands: CommandData[];
};
+const SubsectionLabel = ({ children }: { children: ReactNode }) => (
+
+ {children}
+
+);
+
const ListSection = ({ entries }: { entries: ListEntry[] }) => {
if (entries.length === 0) {
return null;
@@ -81,41 +89,37 @@ const CommandSection = ({ command }: { command: CommandData }) => {
{sections.usage && (
-
Usage
+ Usage
)}
{argumentsList.length > 0 && (
-
- {argumentsList.length === 1 ? 'Argument' : 'Arguments'}
-
+ {argumentsList.length === 1 ? 'Argument' : 'Arguments'}
)}
{flagsList.length > 0 && (
-
- {flagsList.length === 1 ? 'Flag' : 'Flags'}
-
+ {flagsList.length === 1 ? 'Flag' : 'Flags'}
)}
{sections.aliases && (
-
+
{countNonEmptyLines(sections.aliases) === 1 ? 'Alias' : 'Aliases'}
-
+
)}
{sections.examples && (
-
Examples
+ Examples
)}
diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md
index 68c6fc942a6469..9901eb7538b63e 100644
--- a/packages/@expo/cli/CHANGELOG.md
+++ b/packages/@expo/cli/CHANGELOG.md
@@ -8,8 +8,13 @@
### 🐛 Bug fixes
+- Drop obsolete webview installation check ([#45489](https://github.com/expo/expo/pull/45489) by [@kitten](https://github.com/kitten))
+
### 💡 Others
+- Bump to `@expo/xcpretty@^4.4.4` ([#45473](https://github.com/expo/expo/pull/45473) by [@EvanBacon](https://github.com/EvanBacon))
+- Tweak dependency check message and placement on `expo start` ([#45487](https://github.com/expo/expo/pull/45487) by [@kitten](https://github.com/kitten))
+
## 56.0.5 — 2026-05-06
_This version does not introduce any user-facing changes._
diff --git a/packages/@expo/cli/e2e/__tests__/export-embed-test.ts b/packages/@expo/cli/e2e/__tests__/export-embed-test.ts
index 26378cc4b79663..42e6014c36bab1 100644
--- a/packages/@expo/cli/e2e/__tests__/export-embed-test.ts
+++ b/packages/@expo/cli/e2e/__tests__/export-embed-test.ts
@@ -389,13 +389,7 @@ it('runs `npx expo export:embed --platform android` with source maps', async ()
'output.js.map',
'raw/__e2e___staticrendering_sweet.ttf',
- expect.stringMatching(/raw\/.*_100thin\.ttf$/),
- expect.stringMatching(/raw\/.*_200extralight\.ttf$/),
- expect.stringMatching(/raw\/.*_300light\.ttf$/),
expect.stringMatching(/raw\/.*_400regular\.ttf$/),
- expect.stringMatching(/raw\/.*_500medium\.ttf$/),
- expect.stringMatching(/raw\/.*_600semibold\.ttf$/),
- expect.stringMatching(/raw\/.*_700bold\.ttf$/),
expect.stringMatching(/raw\/.*_evilicons\.ttf$/),
'raw/keep.xml',
diff --git a/packages/@expo/cli/e2e/__tests__/start-test.ts b/packages/@expo/cli/e2e/__tests__/start-test.ts
index 776042c74e04fb..5da4132024d1c0 100644
--- a/packages/@expo/cli/e2e/__tests__/start-test.ts
+++ b/packages/@expo/cli/e2e/__tests__/start-test.ts
@@ -132,7 +132,7 @@ describeSkipWin('server', () => {
// Manifest
expect(manifest.runtimeVersion).toBe('1.0');
- expect(manifest.extra.expoClient?.sdkVersion).toBe('55.0.0');
+ expect(manifest.extra.expoClient?.sdkVersion).toMatch(/\d+\.0\.0/);
expect(manifest.extra.expoClient?.slug).toBe('basic-start');
expect(manifest.extra.expoClient?.name).toBe('basic-start');
diff --git a/packages/@expo/cli/package.json b/packages/@expo/cli/package.json
index 526b9c67357b6f..83fbc33d870e9b 100644
--- a/packages/@expo/cli/package.json
+++ b/packages/@expo/cli/package.json
@@ -70,7 +70,7 @@
"@expo/schema-utils": "workspace:^56.0.0",
"@expo/spawn-async": "^1.7.2",
"@expo/ws-tunnel": "^1.0.1",
- "@expo/xcpretty": "^4.4.0",
+ "@expo/xcpretty": "^4.4.4",
"@react-native/dev-middleware": "0.85.3",
"accepts": "^1.3.8",
"arg": "^5.0.2",
diff --git a/packages/@expo/cli/src/start/checkDependenciesOnStart.ts b/packages/@expo/cli/src/start/checkDependenciesOnStart.ts
index cc60841b2051c4..9382340b17192d 100644
--- a/packages/@expo/cli/src/start/checkDependenciesOnStart.ts
+++ b/packages/@expo/cli/src/start/checkDependenciesOnStart.ts
@@ -4,16 +4,21 @@ import chalk from 'chalk';
import * as Log from '../log';
import { getVersionedDependenciesAsync } from './doctor/dependencies/validateDependenciesVersions';
-export type DependencyCheckResult = {
+export interface DependencyCheckResult {
expo?: { actualVersion: string; expectedVersionOrRange: string };
otherCount: number;
-};
+}
+
+export interface DependencyCheckRef {
+ result: DependencyCheckResult | null;
+ promise: Promise;
+}
/**
* Fetch dependency version check results.
* Returns null if everything is up-to-date.
*/
-export async function checkDependenciesAsync(
+async function checkDependenciesAsync(
projectRoot: string,
exp: Pick,
pkg: PackageJSONConfig
@@ -37,16 +42,55 @@ export async function checkDependenciesAsync(
};
}
+let _checkDependenciesRef: DependencyCheckRef | undefined;
+
+export function checkDependencies(
+ projectRoot: string,
+ exp: Pick,
+ pkg: PackageJSONConfig
+) {
+ if (_checkDependenciesRef) {
+ return _checkDependenciesRef;
+ }
+
+ const ref: DependencyCheckRef = {
+ result: null,
+ promise: Promise.resolve(null),
+ };
+
+ ref.promise = checkDependenciesAsync(projectRoot, exp, pkg).then(
+ (result) => {
+ ref.result = result;
+ return result;
+ },
+ (_error) => null
+ );
+
+ _checkDependenciesRef = ref;
+ return ref;
+}
+
/** Print the condensed dependency check messages to the terminal. */
-export function printDependencyCheckResult(result: DependencyCheckResult): void {
- if (result.expo) {
- Log.warn(
- chalk`An update for {bold expo} is available: {red ${result.expo.actualVersion}} {dim →} {green ${result.expo.expectedVersionOrRange}}`
- );
+export function getDependencyCheckMessage(
+ result: DependencyCheckResult | null | undefined
+): string[] {
+ if (result?.expo) {
+ return [
+ chalk.yellow`An update for {bold expo} is available: {red ${result.expo.actualVersion}} {dim →} {green ${result.expo.expectedVersionOrRange}}`,
+ chalk.yellow`${result.otherCount} other package${result.otherCount === 1 ? '' : 's'} may need updating. Run {bold npx expo install --check} for details.`,
+ ];
+ } else if (result?.otherCount) {
+ return [
+ chalk.yellow`${result.otherCount} package${result.otherCount === 1 ? '' : 's'} may need updating. Run {bold npx expo install --check} for details.`,
+ ];
+ } else {
+ return [];
}
- if (result.otherCount > 0) {
- Log.warn(
- chalk`${result.otherCount} other package${result.otherCount === 1 ? '' : 's'} may need updating. Run {bold npx expo install --check} for details.`
- );
+}
+
+/** Print the condensed dependency check messages to the terminal. */
+export function printDependencyCheckResult(result: DependencyCheckResult): void {
+ for (const line of getDependencyCheckMessage(result)) {
+ Log.log(line);
}
}
diff --git a/packages/@expo/cli/src/start/interface/commandsTable.ts b/packages/@expo/cli/src/start/interface/commandsTable.ts
index e0fbe121884d4f..4fd1a888a186a2 100644
--- a/packages/@expo/cli/src/start/interface/commandsTable.ts
+++ b/packages/@expo/cli/src/start/interface/commandsTable.ts
@@ -3,7 +3,7 @@ import chalk from 'chalk';
import wrapAnsi from 'wrap-ansi';
import * as Log from '../../log';
-import type { DependencyCheckResult } from '../checkDependenciesOnStart';
+import type { DependencyCheckRef } from '../checkDependenciesOnStart';
import type { McpServer } from '../server/MCP';
// Approximately how many rows apart from the commands table (usage guide on `expo start`)
@@ -21,7 +21,7 @@ export type StartOptions = {
maxWorkers?: number;
platforms?: ExpoConfig['platforms'];
mcpServer?: McpServer;
- dependencyCheckPromise?: Promise;
+ dependencyCheckRef?: DependencyCheckRef;
};
export const printHelp = (): void => {
diff --git a/packages/@expo/cli/src/start/interface/interactiveActions.ts b/packages/@expo/cli/src/start/interface/interactiveActions.ts
index 1073a8fec3f7c7..094c9627fec97d 100644
--- a/packages/@expo/cli/src/start/interface/interactiveActions.ts
+++ b/packages/@expo/cli/src/start/interface/interactiveActions.ts
@@ -9,6 +9,7 @@ import { learnMore } from '../../utils/link';
import type { ExpoChoice } from '../../utils/prompts';
import { selectAsync } from '../../utils/prompts';
import { printQRCode } from '../../utils/qr';
+import { getDependencyCheckMessage } from '../checkDependenciesOnStart';
import type { DevServerManager } from '../server/DevServerManager';
import {
openJsInspector,
@@ -30,7 +31,10 @@ export class DevServerManagerActions {
) {}
printDevServerInfo(
- options: Pick
+ options: Pick<
+ StartOptions,
+ 'devClient' | 'isWebSocketsEnabled' | 'platforms' | 'dependencyCheckRef'
+ >
) {
// Keep track of approximately how much space we have to print our usage guide
let rows = process.stdout.rows || Infinity;
@@ -100,9 +104,16 @@ export class DevServerManagerActions {
}
}
+ const dependencyCheckLines = getDependencyCheckMessage(options.dependencyCheckRef?.result);
+ rows -= dependencyCheckLines.length;
+
printUsage(options, { verbose: false, rows });
printHelp();
Log.log();
+
+ for (const line of dependencyCheckLines) {
+ Log.log(line);
+ }
}
async openJsInspectorAsync() {
diff --git a/packages/@expo/cli/src/start/interface/startInterface.ts b/packages/@expo/cli/src/start/interface/startInterface.ts
index f3f2ac8583b09f..e3eca8e30e85a3 100644
--- a/packages/@expo/cli/src/start/interface/startInterface.ts
+++ b/packages/@expo/cli/src/start/interface/startInterface.ts
@@ -10,8 +10,6 @@ import { AbortCommandError } from '../../utils/errors';
import { getAllSpinners, ora } from '../../utils/ora';
import { getProgressBar, setProgressBar } from '../../utils/progress';
import { addInteractionListener, pauseInteractions } from '../../utils/prompts';
-import type { DependencyCheckResult } from '../checkDependenciesOnStart';
-import { printDependencyCheckResult } from '../checkDependenciesOnStart';
import { WebSupportProjectPrerequisite } from '../doctor/web/WebSupportProjectPrerequisite';
import type { DevServerManager } from '../server/DevServerManager';
@@ -39,45 +37,22 @@ const PLATFORM_SETTINGS: Record<
export async function startInterfaceAsync(
devServerManager: DevServerManager,
- options: Pick
+ options: Pick
) {
- const actions = new DevServerManagerActions(devServerManager, options);
+ // Spend one-tick waiting for the dependency check result
+ if (options.dependencyCheckRef) {
+ await Promise.race([options.dependencyCheckRef.promise, Promise.resolve(null)]);
+ }
+ const actions = new DevServerManagerActions(devServerManager, options);
const isWebSocketsEnabled = devServerManager.getDefaultDevServer()?.isTargetingNative();
-
const usageOptions = {
isWebSocketsEnabled,
devClient: devServerManager.options.devClient,
...options,
};
- // Print the dependency check if it completed (it runs in the background since early startup).
- // With a warm fetch cache this resolves near-instantly, so we defer by a tick
- // On cold starts it may not be ready, in which case it will appear on the next reprint or restart
- let dependencyCheckResult: DependencyCheckResult | null | undefined;
- if (options.dependencyCheckPromise) {
- dependencyCheckResult = await Promise.race([
- options.dependencyCheckPromise,
- Promise.resolve(null),
- ]);
- if (!dependencyCheckResult) {
- // Not ready yet — capture once resolved for display on next reprint
- options.dependencyCheckPromise.then((result) => {
- if (result) {
- dependencyCheckResult = result;
- }
- });
- }
- }
-
- const printDependencyCheckIfAvailable = () => {
- if (dependencyCheckResult) {
- printDependencyCheckResult(dependencyCheckResult);
- }
- };
-
actions.printDevServerInfo(usageOptions);
- printDependencyCheckIfAvailable();
const onPressAsync = async (key: string) => {
// Auxillary commands all escape.
@@ -173,7 +148,6 @@ export async function startInterfaceAsync(
if (await devServerManager.toggleRuntimeMode()) {
usageOptions.devClient = devServerManager.options.devClient;
actions.printDevServerInfo(usageOptions);
- printDependencyCheckIfAvailable();
return;
}
break;
@@ -219,7 +193,6 @@ export async function startInterfaceAsync(
case 'c':
Log.clear();
actions.printDevServerInfo(usageOptions);
- printDependencyCheckIfAvailable();
return;
case 'j':
return actions.openJsInspectorAsync();
diff --git a/packages/@expo/cli/src/start/server/middleware/DomComponentsMiddleware.ts b/packages/@expo/cli/src/start/server/middleware/DomComponentsMiddleware.ts
index f162bcc2e99682..32f4d4bb74c7f6 100644
--- a/packages/@expo/cli/src/start/server/middleware/DomComponentsMiddleware.ts
+++ b/packages/@expo/cli/src/start/server/middleware/DomComponentsMiddleware.ts
@@ -7,24 +7,12 @@ import type { ExpoMetroOptions } from './metroOptions';
import { createBundleUrlPath } from './metroOptions';
import type { ServerRequest, ServerResponse } from './server.types';
import { toPosixPath } from '../../../utils/filePath';
-import { memoize } from '../../../utils/fn';
import { fileURLToFilePath } from '../metro/createServerComponentsMiddleware';
export type PickPartial = Omit & Partial>;
export const DOM_COMPONENTS_BUNDLE_DIR = 'www.bundle';
-const checkWebViewInstalled = memoize((projectRoot: string) => {
- const webViewInstalled =
- resolveFrom.silent(projectRoot, 'react-native-webview') ||
- resolveFrom.silent(projectRoot, '@expo/dom-webview');
- if (!webViewInstalled) {
- throw new Error(
- `To use DOM Components, you must install the 'react-native-webview' package. Run 'npx expo install react-native-webview' to install it.`
- );
- }
-});
-
type CreateDomComponentsMiddlewareOptions = {
/** The absolute project root, used to resolve the `expo/dom/entry.js` path */
projectRoot: string;
@@ -53,8 +41,6 @@ export function createDomComponentsMiddleware(
return res.end();
}
- checkWebViewInstalled(projectRoot);
-
// NOTE(@kitten): Keep in sync with `src/export/exportDomComponents.ts`
// Generate a unique entry file for the webview.
const virtualEntry = resolveFrom(projectRoot, 'expo/dom/entry.js');
diff --git a/packages/@expo/cli/src/start/server/middleware/__tests__/DomComponentsMiddleware-test.ts b/packages/@expo/cli/src/start/server/middleware/__tests__/DomComponentsMiddleware-test.ts
deleted file mode 100644
index 31c0d56cf41243..00000000000000
--- a/packages/@expo/cli/src/start/server/middleware/__tests__/DomComponentsMiddleware-test.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { getMetroServerRoot } from '@expo/config/paths';
-import path from 'path';
-import resolveFrom from 'resolve-from';
-
-import { createDomComponentsMiddleware } from '../DomComponentsMiddleware';
-import type { ServerNext, ServerRequest, ServerResponse } from '../server.types';
-
-jest.mock('resolve-from');
-
-const asRequest = (req: Partial) => req as ServerRequest;
-
-function createMockResponse() {
- return {
- getHeader: jest.fn(),
- setHeader: jest.fn(),
- on: jest.fn(),
- once: jest.fn(),
- emit: jest.fn(),
- end: jest.fn(),
- statusCode: 200,
- } as unknown as ServerResponse;
-}
-
-describe('Check webview package installation', () => {
- const projectRoot = '/project';
- const metroRoot = getMetroServerRoot(projectRoot);
-
- const mockResolveFrom = resolveFrom as jest.MockedFunction;
- mockResolveFrom.mockImplementation((fromDirectory: string, moduleId: string) => {
- if (moduleId === 'expo/dom/entry.js') {
- return path.join(fromDirectory, 'node_modules/expo/dom/entry.js');
- }
- throw new Error(`Cannot resolve module '${moduleId}' from '${fromDirectory}'`);
- });
- const mockResolveSilent = resolveFrom.silent as jest.MockedFunction;
- const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
-
- let req: ServerRequest;
- let res: ServerResponse;
- let next: jest.Mock;
- let middleware: ReturnType;
-
- beforeEach(() => {
- req = asRequest({
- url: 'http://localhost:8081/_expo/@dom/hello.tsx?file=file:///path/to/file.js',
- headers: { host: 'localhost:8081' },
- });
- res = createMockResponse();
- next = jest.fn();
- middleware = createDomComponentsMiddleware(
- { metroRoot, projectRoot },
- {
- mode: 'development',
- routerRoot: projectRoot,
- reactCompiler: false,
- isExporting: false,
- }
- );
- });
-
- afterEach(() => {
- mockResolveSilent.mockReset();
- });
-
- afterAll(() => {
- consoleWarnSpy.mockRestore();
- });
-
- it('should show an error message if the webview package is not installed', () => {
- mockResolveSilent.mockReturnValue(undefined);
- expect(() => middleware(req, res, next)).toThrow(
- /To use DOM Components, you must install the 'react-native-webview' package. Run 'npx expo install react-native-webview' to install it./
- );
- });
-
- it('should not show error messages if react-native-webview is installed', () => {
- mockResolveSilent.mockReturnValue('/project/node_modules/react-native-webview');
- expect(() => middleware(req, res, next)).not.toThrow();
- });
-
- it('should not show error messages if @expo/dom-webview is installed', () => {
- mockResolveSilent.mockReturnValue('/project/node_modules/@expo/dom-webview');
- expect(() => middleware(req, res, next)).not.toThrow();
- });
-});
diff --git a/packages/@expo/cli/src/start/startAsync.ts b/packages/@expo/cli/src/start/startAsync.ts
index 8a48cd691ae6e8..feff22237ed4f4 100644
--- a/packages/@expo/cli/src/start/startAsync.ts
+++ b/packages/@expo/cli/src/start/startAsync.ts
@@ -2,8 +2,11 @@ import { getConfig } from '@expo/config';
import chalk from 'chalk';
import { getLogFile, shouldReduceLogs } from '../events';
-import type { DependencyCheckResult } from './checkDependenciesOnStart';
-import { checkDependenciesAsync, printDependencyCheckResult } from './checkDependenciesOnStart';
+import {
+ checkDependencies,
+ printDependencyCheckResult,
+ type DependencyCheckRef,
+} from './checkDependenciesOnStart';
import { SimulatorAppPrerequisite } from './doctor/apple/SimulatorAppPrerequisite';
import { getXcodeVersionAsync } from './doctor/apple/XcodePrerequisite';
import { WebSupportProjectPrerequisite } from './doctor/web/WebSupportProjectPrerequisite';
@@ -84,9 +87,9 @@ export async function startAsync(
// Start dependency version check in the background as early as possible (non-blocking).
// The result will be displayed in the TUI once it resolves.
- let dependencyCheckPromise: Promise | undefined;
+ let dependencyCheckRef: DependencyCheckRef | undefined;
if (!env.EXPO_OFFLINE && !env.EXPO_NO_DEPENDENCY_VALIDATION && !settings.webOnly) {
- dependencyCheckPromise = checkDependenciesAsync(projectRoot, exp, pkg).catch(() => null);
+ dependencyCheckRef = checkDependencies(projectRoot, exp, pkg);
}
if (exp.platforms?.includes('ios') && process.platform !== 'win32') {
@@ -140,7 +143,7 @@ export async function startAsync(
await profile(startInterfaceAsync)(devServerManager, {
platforms: exp.platforms ?? ['ios', 'android', 'web'],
mcpServer,
- dependencyCheckPromise,
+ dependencyCheckRef,
});
} else {
// Display the server location in CI...
@@ -151,10 +154,9 @@ export async function startAsync(
}
Log.log(chalk`Waiting on {underline ${defaultServerUrl}}`);
}
- // In non-interactive mode, await the check and print if available.
- const result = await dependencyCheckPromise;
- if (result) {
- printDependencyCheckResult(result);
+ // In non-interactive mode, print the check outside of an interface, if it's available
+ if (dependencyCheckRef?.result) {
+ printDependencyCheckResult(dependencyCheckRef.result);
}
}
diff --git a/packages/@expo/metro-config/CHANGELOG.md b/packages/@expo/metro-config/CHANGELOG.md
index 82f30f0f4667d9..793461c7b8cb57 100644
--- a/packages/@expo/metro-config/CHANGELOG.md
+++ b/packages/@expo/metro-config/CHANGELOG.md
@@ -10,6 +10,8 @@
### 💡 Others
+- Drop obsolete `EXPO_USE_EXOTIC` flag warning ([#45494](https://github.com/expo/expo/pull/45494) by [@kitten](https://github.com/kitten))
+
## 56.0.3 — 2026-05-06
_This version does not introduce any user-facing changes._
diff --git a/packages/@expo/metro-config/build/ExpoMetroConfig.js b/packages/@expo/metro-config/build/ExpoMetroConfig.js
index 51479f49c8f36d..1520b818c698f7 100644
--- a/packages/@expo/metro-config/build/ExpoMetroConfig.js
+++ b/packages/@expo/metro-config/build/ExpoMetroConfig.js
@@ -29,7 +29,6 @@ const filePath_1 = require("./utils/filePath");
const getPkgVersion_1 = require("./utils/getPkgVersion");
const setOnReadonly_1 = require("./utils/setOnReadonly");
const debug = require('debug')('expo:metro:config');
-let hasWarnedAboutExotic = false;
let hasWarnedAboutReactNative = false;
// Patch Metro's graph to support always parsing certain modules. This enables
// things like Tailwind CSS which update based on their own heuristics.
@@ -134,11 +133,6 @@ function getDefaultConfig(projectRoot, { mode, isCSSEnabled = true, unstable_bef
if (isCSSEnabled) {
patchMetroGraphToSupportUncachedModules();
}
- const isExotic = mode === 'exotic' || env_1.env.EXPO_USE_EXOTIC;
- if (isExotic && !hasWarnedAboutExotic) {
- hasWarnedAboutExotic = true;
- console.log(chalk_1.default.gray(`\u203A Feature ${chalk_1.default.bold `EXPO_USE_EXOTIC`} has been removed in favor of the default transformer.`));
- }
const reactNativePath = path_1.default.dirname(resolve_from_1.default.silent(projectRoot, 'react-native/package.json') ?? 'react-native/package.json');
if (reactNativePath === 'react-native' && !hasWarnedAboutReactNative) {
hasWarnedAboutReactNative = true;
diff --git a/packages/@expo/metro-config/build/ExpoMetroConfig.js.map b/packages/@expo/metro-config/build/ExpoMetroConfig.js.map
index a2bb4a8fab5354..3b116adc82754b 100644
--- a/packages/@expo/metro-config/build/ExpoMetroConfig.js.map
+++ b/packages/@expo/metro-config/build/ExpoMetroConfig.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoMetroConfig.js","sourceRoot":"","sources":["../src/ExpoMetroConfig.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"ExpoMetroConfig.js","sourceRoot":"","sources":["../src/ExpoMetroConfig.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/@expo/metro-config/build/env.d.ts b/packages/@expo/metro-config/build/env.d.ts
index 251ecef5cf4b5a..a346131090e404 100644
--- a/packages/@expo/metro-config/build/env.d.ts
+++ b/packages/@expo/metro-config/build/env.d.ts
@@ -1,8 +1,6 @@
declare class Env {
/** Enable debug logging */
get EXPO_DEBUG(): boolean;
- /** Enable the experimental "exotic" mode. [Learn more](https://blog.expo.dev/drastically-faster-bundling-in-react-native-a54f268e0ed1). */
- get EXPO_USE_EXOTIC(): boolean;
/** The React Metro port that's baked into react-native scripts and tools. */
get RCT_METRO_PORT(): number;
/** Disable Environment Variable injection in client bundles. */
diff --git a/packages/@expo/metro-config/build/env.js b/packages/@expo/metro-config/build/env.js
index 88b736b2b920a2..f1f4d83d314a6a 100644
--- a/packages/@expo/metro-config/build/env.js
+++ b/packages/@expo/metro-config/build/env.js
@@ -7,10 +7,6 @@ class Env {
get EXPO_DEBUG() {
return (0, getenv_1.boolish)('EXPO_DEBUG', false);
}
- /** Enable the experimental "exotic" mode. [Learn more](https://blog.expo.dev/drastically-faster-bundling-in-react-native-a54f268e0ed1). */
- get EXPO_USE_EXOTIC() {
- return (0, getenv_1.boolish)('EXPO_USE_EXOTIC', false);
- }
/** The React Metro port that's baked into react-native scripts and tools. */
get RCT_METRO_PORT() {
return (0, getenv_1.int)('RCT_METRO_PORT', 8081);
diff --git a/packages/@expo/metro-config/build/env.js.map b/packages/@expo/metro-config/build/env.js.map
index a456d1366c20a1..a93a7c193125b9 100644
--- a/packages/@expo/metro-config/build/env.js.map
+++ b/packages/@expo/metro-config/build/env.js.map
@@ -1 +1 @@
-{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":";;;AAAA,mCAAsC;AAEtC,MAAM,GAAG;IACP,2BAA2B;IAC3B,IAAI,UAAU;QACZ,OAAO,IAAA,gBAAO,EAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,2IAA2I;IAC3I,IAAI,eAAe;QACjB,OAAO,IAAA,gBAAO,EAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,6EAA6E;IAC7E,IAAI,cAAc;QAChB,OAAO,IAAA,YAAG,EAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,gEAAgE;IAChE,IAAI,uBAAuB;QACzB,OAAO,IAAA,gBAAO,EAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,6JAA6J;IAC7J,IAAI,sBAAsB;QACxB,OAAO,IAAA,gBAAO,EAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;CACF;AAEY,QAAA,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC"}
\ No newline at end of file
+{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":";;;AAAA,mCAAsC;AAEtC,MAAM,GAAG;IACP,2BAA2B;IAC3B,IAAI,UAAU;QACZ,OAAO,IAAA,gBAAO,EAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,6EAA6E;IAC7E,IAAI,cAAc;QAChB,OAAO,IAAA,YAAG,EAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,gEAAgE;IAChE,IAAI,uBAAuB;QACzB,OAAO,IAAA,gBAAO,EAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,6JAA6J;IAC7J,IAAI,sBAAsB;QACxB,OAAO,IAAA,gBAAO,EAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;CACF;AAEY,QAAA,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC"}
\ No newline at end of file
diff --git a/packages/@expo/metro-config/src/ExpoMetroConfig.ts b/packages/@expo/metro-config/src/ExpoMetroConfig.ts
index 773eece4701bc3..fe027e468f5d34 100644
--- a/packages/@expo/metro-config/src/ExpoMetroConfig.ts
+++ b/packages/@expo/metro-config/src/ExpoMetroConfig.ts
@@ -56,7 +56,6 @@ export interface DefaultConfigOptions {
}) => Module[])[];
}
-let hasWarnedAboutExotic = false;
let hasWarnedAboutReactNative = false;
// Patch Metro's graph to support always parsing certain modules. This enables
@@ -197,17 +196,6 @@ export function getDefaultConfig(
patchMetroGraphToSupportUncachedModules();
}
- const isExotic = mode === 'exotic' || env.EXPO_USE_EXOTIC;
-
- if (isExotic && !hasWarnedAboutExotic) {
- hasWarnedAboutExotic = true;
- console.log(
- chalk.gray(
- `\u203A Feature ${chalk.bold`EXPO_USE_EXOTIC`} has been removed in favor of the default transformer.`
- )
- );
- }
-
const reactNativePath = path.dirname(
resolveFrom.silent(projectRoot, 'react-native/package.json') ?? 'react-native/package.json'
);
diff --git a/packages/@expo/metro-config/src/env.ts b/packages/@expo/metro-config/src/env.ts
index e2c9dbef1e8bf2..f15c0752c226c6 100644
--- a/packages/@expo/metro-config/src/env.ts
+++ b/packages/@expo/metro-config/src/env.ts
@@ -6,11 +6,6 @@ class Env {
return boolish('EXPO_DEBUG', false);
}
- /** Enable the experimental "exotic" mode. [Learn more](https://blog.expo.dev/drastically-faster-bundling-in-react-native-a54f268e0ed1). */
- get EXPO_USE_EXOTIC() {
- return boolish('EXPO_USE_EXOTIC', false);
- }
-
/** The React Metro port that's baked into react-native scripts and tools. */
get RCT_METRO_PORT() {
return int('RCT_METRO_PORT', 8081);
diff --git a/packages/create-expo/CHANGELOG.md b/packages/create-expo/CHANGELOG.md
index 7d55dea475f66f..9b3b39d6006ee1 100644
--- a/packages/create-expo/CHANGELOG.md
+++ b/packages/create-expo/CHANGELOG.md
@@ -10,6 +10,8 @@
### 💡 Others
+- Drop automatically setting `node-linker=hoisted` for pnpm ([#45491](https://github.com/expo/expo/pull/45491) by [@kitten](https://github.com/kitten))
+
## 3.7.2 — 2026-05-06
_This version does not introduce any user-facing changes._
diff --git a/packages/create-expo/e2e/__tests__/index-test.ts b/packages/create-expo/e2e/__tests__/index-test.ts
index dc6e93ca0454a8..a4f1332520bea7 100644
--- a/packages/create-expo/e2e/__tests__/index-test.ts
+++ b/packages/create-expo/e2e/__tests__/index-test.ts
@@ -93,12 +93,6 @@ it('uses pnpm', async () => {
expectFileExists(projectName, '.gitignore');
// Check if it skipped install
expectFileNotExists(projectName, 'node_modules');
-
- // Check if `pnpm` node linker is set
- const { stdout } = expectExecutePassing(
- await spawnAsync('pnpm', ['config', 'get', 'node-linker'], { cwd: getTestPath(projectName) })
- );
- expect(stdout).toContain('hoisted');
});
it('uses Bun', async () => {
diff --git a/packages/create-expo/src/resolvePackageManager.ts b/packages/create-expo/src/resolvePackageManager.ts
index df3105115dc915..cbb801d6720104 100644
--- a/packages/create-expo/src/resolvePackageManager.ts
+++ b/packages/create-expo/src/resolvePackageManager.ts
@@ -105,10 +105,6 @@ export async function configurePackageManager(
) {
const manager = createPackageManager(packageManager, { cwd: projectRoot, ...flags });
switch (manager.name) {
- case 'pnpm':
- await manager.runAsync(['config', '--location', 'project', 'set', 'node-linker', 'hoisted']);
- break;
-
case 'yarn': {
const yarnVersion = await manager.versionAsync();
const majorVersion = parseInt(yarnVersion.split('.')[0] ?? '', 10);
diff --git a/packages/expo-calendar/CHANGELOG.md b/packages/expo-calendar/CHANGELOG.md
index 62d7de9f588396..1453ca1360f729 100644
--- a/packages/expo-calendar/CHANGELOG.md
+++ b/packages/expo-calendar/CHANGELOG.md
@@ -30,6 +30,7 @@ _This version does not introduce any user-facing changes._
### 🎉 New features
+- [iOS][next] Add support for writeOnly permissions ([#44967](https://github.com/expo/expo/pull/44967) by [@Wenszel](https://github.com/Wenszel))
- [iOS][next] Add `calendar.addEventWithForm()` ([#44966](https://github.com/expo/expo/pull/44966) by [@Wenszel](https://github.com/Wenszel))
- [iOS][next] Add `presentPicker()` ([#44965](https://github.com/expo/expo/pull/44965) by [@Wenszel](https://github.com/Wenszel))
- Expose a typed config plugin function ([#44098](https://github.com/expo/expo/pull/44098) by [@zoontek](https://github.com/zoontek))
diff --git a/packages/expo-calendar/android/src/main/java/expo/modules/calendar/next/CalendarNextModule.kt b/packages/expo-calendar/android/src/main/java/expo/modules/calendar/next/CalendarNextModule.kt
index e1456a2489730c..95990fa80261b0 100644
--- a/packages/expo-calendar/android/src/main/java/expo/modules/calendar/next/CalendarNextModule.kt
+++ b/packages/expo-calendar/android/src/main/java/expo/modules/calendar/next/CalendarNextModule.kt
@@ -115,7 +115,16 @@ class CalendarNextModule : Module() {
ExpoCalendar.getById(calendarId, calendarRepository, expoCalendarFactory)
}
- AsyncFunction("requestCalendarPermissions") { promise: Promise ->
+ AsyncFunction("getCalendarPermissions") { _: Boolean?, promise: Promise ->
+ Permissions.getPermissionsWithPermissionsManager(
+ appContext.permissions,
+ promise,
+ Manifest.permission.READ_CALENDAR,
+ Manifest.permission.WRITE_CALENDAR
+ )
+ }
+
+ AsyncFunction("requestCalendarPermissions") { _: Boolean?, promise: Promise ->
Permissions.askForPermissionsWithPermissionsManager(
appContext.permissions,
promise,
diff --git a/packages/expo-calendar/build/next/Calendar.d.ts b/packages/expo-calendar/build/next/Calendar.d.ts
index 0306ec9ace11a0..15516dafe8c5fd 100644
--- a/packages/expo-calendar/build/next/Calendar.d.ts
+++ b/packages/expo-calendar/build/next/Calendar.d.ts
@@ -1,4 +1,4 @@
-import type { Calendar, Attendee, DialogEventResult, EntityTypes, Event, RecurringEventOptions, Reminder, ReminderStatus } from '../Calendar';
+import type { Calendar, Attendee, DialogEventResult, EntityTypes, Event, RecurringEventOptions, Reminder, ReminderStatus, PermissionResponse } from '../Calendar';
import InternalExpoCalendar from './ExpoCalendar';
import type { ModifiableEventProperties, ModifiableReminderProperties, ModifiableCalendarProperties, ModifiableAttendeeProperties, AddEventWithFormOptions } from './ExpoCalendar.types';
/**
@@ -78,24 +78,28 @@ export declare function presentPicker(): Promise;
export declare function listEvents(calendars: (string | ExpoCalendar)[], startDate: Date, endDate: Date): Promise;
/**
* Asks the user to grant permissions for accessing user's calendars.
+ * @param writeOnly - On iOS, whether to request write-only access, which allows creating calendar events
+ * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
-export declare const requestCalendarPermissions: () => Promise;
+export declare const requestCalendarPermissions: (writeOnly?: boolean) => Promise;
/**
* Checks user's permissions for accessing user's calendars.
+ * @param writeOnly - On iOS, whether to check write-only access, which allows creating calendar events
+ * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
-export declare const getCalendarPermissions: () => Promise;
+export declare const getCalendarPermissions: (writeOnly?: boolean) => Promise;
/**
* Asks the user to grant permissions for accessing user's reminders.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
-export declare const requestRemindersPermissions: () => Promise;
+export declare const requestRemindersPermissions: () => Promise;
/**
* Checks user's permissions for accessing user's reminders.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
-export declare const getRemindersPermissions: () => Promise;
+export declare const getRemindersPermissions: () => Promise;
/**
* Gets an array of Source objects with details about the different sources stored on the device.
* @returns An array of Source objects representing the sources found.
@@ -108,13 +112,17 @@ export { AlarmMethod, AttendeeRole, AttendeeStatus, AttendeeType, Availability,
* Check or request permissions to access the user's calendars.
* This uses both `getCalendarPermissions` and `requestCalendarPermissions` to interact
* with the permissions.
+ * On iOS, `writeOnly` requests permission to create calendar events without reading
+ * existing calendars or events. It does not grant permission to create, update, or delete calendars.
*
* @example
* ```ts
* const [status, requestPermission] = Calendar.useCalendarPermissions();
* ```
*/
-export declare const useCalendarPermissions: (options?: import("expo-modules-core").PermissionHookOptions | undefined) => [import("expo-modules-core").PermissionResponse | null, () => Promise, () => Promise];
+export declare const useCalendarPermissions: (options?: import("expo-modules-core").PermissionHookOptions<{
+ writeOnly?: boolean;
+}> | undefined) => [PermissionResponse | null, () => Promise, () => Promise];
/**
* Check or request permissions to access the user's reminders.
* This uses both `getRemindersPermissions` and `requestRemindersPermissions` to interact
@@ -125,5 +133,5 @@ export declare const useCalendarPermissions: (options?: import("expo-modules-cor
* const [status, requestPermission] = Calendar.useRemindersPermissions();
* ```
*/
-export declare const useRemindersPermissions: (options?: import("expo-modules-core").PermissionHookOptions | undefined) => [import("expo-modules-core").PermissionResponse | null, () => Promise, () => Promise];
+export declare const useRemindersPermissions: (options?: import("expo-modules-core").PermissionHookOptions | undefined) => [PermissionResponse | null, () => Promise, () => Promise];
//# sourceMappingURL=Calendar.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-calendar/build/next/Calendar.d.ts.map b/packages/expo-calendar/build/next/Calendar.d.ts.map
index 1c88f49971e75f..c89eb9f96b6a2c 100644
--- a/packages/expo-calendar/build/next/Calendar.d.ts.map
+++ b/packages/expo-calendar/build/next/Calendar.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/next/Calendar.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/next/Calendar.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/expo-calendar/build/next/Calendar.js b/packages/expo-calendar/build/next/Calendar.js
index 90421d603a7f1c..e5c2a59c2ba2c6 100644
--- a/packages/expo-calendar/build/next/Calendar.js
+++ b/packages/expo-calendar/build/next/Calendar.js
@@ -201,11 +201,15 @@ export async function listEvents(calendars, startDate, endDate) {
}
/**
* Asks the user to grant permissions for accessing user's calendars.
+ * @param writeOnly - On iOS, whether to request write-only access, which allows creating calendar events
+ * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
export const requestCalendarPermissions = InternalExpoCalendar.requestCalendarPermissions;
/**
* Checks user's permissions for accessing user's calendars.
+ * @param writeOnly - On iOS, whether to check write-only access, which allows creating calendar events
+ * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
export const getCalendarPermissions = InternalExpoCalendar.getCalendarPermissions;
@@ -229,6 +233,8 @@ export { AlarmMethod, AttendeeRole, AttendeeStatus, AttendeeType, Availability,
* Check or request permissions to access the user's calendars.
* This uses both `getCalendarPermissions` and `requestCalendarPermissions` to interact
* with the permissions.
+ * On iOS, `writeOnly` requests permission to create calendar events without reading
+ * existing calendars or events. It does not grant permission to create, update, or delete calendars.
*
* @example
* ```ts
@@ -236,8 +242,8 @@ export { AlarmMethod, AttendeeRole, AttendeeStatus, AttendeeType, Availability,
* ```
*/
export const useCalendarPermissions = createPermissionHook({
- getMethod: getCalendarPermissions,
- requestMethod: requestCalendarPermissions,
+ getMethod: (options) => getCalendarPermissions(options?.writeOnly),
+ requestMethod: (options) => requestCalendarPermissions(options?.writeOnly),
});
/**
* Check or request permissions to access the user's reminders.
diff --git a/packages/expo-calendar/build/next/Calendar.js.map b/packages/expo-calendar/build/next/Calendar.js.map
index 2979b334795949..7bd955c9e8deb6 100644
--- a/packages/expo-calendar/build/next/Calendar.js.map
+++ b/packages/expo-calendar/build/next/Calendar.js.map
@@ -1 +1 @@
-{"version":3,"file":"Calendar.js","sourceRoot":"","sources":["../../src/next/Calendar.ts"],"names":[],"mappings":"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","sourcesContent":["import { createPermissionHook, UnavailabilityError } from 'expo-modules-core';\nimport { Platform, processColor } from 'react-native';\n\nimport type {\n Calendar,\n Attendee,\n DialogEventResult,\n EntityTypes,\n Event,\n RecurringEventOptions,\n Reminder,\n ReminderStatus,\n} from '../Calendar';\nimport InternalExpoCalendar from './ExpoCalendar';\nimport { stringifyDateValues, stringifyIfDate, getNullableDetailsFields } from '../utils';\nimport type {\n ModifiableEventProperties,\n ModifiableReminderProperties,\n ModifiableCalendarProperties,\n ModifiableAttendeeProperties,\n AddEventWithFormOptions,\n} from './ExpoCalendar.types';\n\n/**\n * Represents a calendar attendee object.\n */\nexport class ExpoCalendarAttendee extends InternalExpoCalendar.ExpoCalendarAttendee {\n override async update(details: Partial): Promise {\n if (!super.update) {\n throw new UnavailabilityError('ExpoCalendarAttendee', 'update');\n }\n const nullableDetailsFields = getNullableDetailsFields(details);\n return super.update(stringifyDateValues(details), nullableDetailsFields);\n }\n\n override async delete(): Promise {\n if (!super.delete) {\n throw new UnavailabilityError('ExpoCalendarAttendee', 'delete');\n }\n await super.delete();\n }\n}\n\n/**\n * Represents a calendar event object that can be accessed and modified using the Expo Calendar Next API.\n */\nexport class ExpoCalendarEvent extends InternalExpoCalendar.ExpoCalendarEvent {\n override getOccurrenceSync(recurringEventOptions: RecurringEventOptions = {}): ExpoCalendarEvent {\n const result = super.getOccurrenceSync(stringifyDateValues(recurringEventOptions));\n Object.setPrototypeOf(result, ExpoCalendarEvent.prototype);\n return result;\n }\n\n override async getAttendees(): Promise {\n const attendees = await super.getAttendees();\n return attendees.map((attendee) => {\n Object.setPrototypeOf(attendee, ExpoCalendarAttendee.prototype);\n return attendee;\n });\n }\n\n override async createAttendee(attendee: Attendee): Promise {\n if (!super.createAttendee) {\n throw new UnavailabilityError('ExpoCalendarEvent', 'createAttendee');\n }\n const newAttendee = await super.createAttendee(attendee);\n Object.setPrototypeOf(newAttendee, ExpoCalendarAttendee.prototype);\n return newAttendee;\n }\n\n override async update(details: Partial): Promise {\n const nullableDetailsFields = getNullableDetailsFields(details);\n return await super.update(stringifyDateValues(details), nullableDetailsFields);\n }\n\n override async delete(): Promise {\n await super.delete();\n }\n\n static override async get(eventId: string): Promise {\n const event = await InternalExpoCalendar.getEventById(eventId);\n Object.setPrototypeOf(event, ExpoCalendarEvent.prototype);\n return event;\n }\n}\n\n/**\n * Represents a calendar reminder object that can be accessed and modified using the Expo Calendar Next API.\n */\nexport class ExpoCalendarReminder extends InternalExpoCalendar.ExpoCalendarReminder {\n override async update(details: Partial): Promise {\n const nullableDetailsFields = getNullableDetailsFields(details);\n await super.update(stringifyDateValues(details), nullableDetailsFields);\n }\n\n static override async get(reminderId: string): Promise {\n const reminder = await InternalExpoCalendar.getReminderById(reminderId);\n Object.setPrototypeOf(reminder, ExpoCalendarReminder.prototype);\n return reminder;\n }\n}\n\n/**\n * Represents a calendar object that can be accessed and modified using the Expo Calendar Next API.\n *\n * This class provides properties and methods for interacting with a specific calendar on the device,\n * such as retrieving its events, updating its details, and accessing its metadata.\n */\nexport class ExpoCalendar extends InternalExpoCalendar.ExpoCalendar {\n override async createEvent(\n details: Partial<\n Omit<\n Event,\n | 'creationDate'\n | 'lastModifiedDate'\n | 'originalStartDate'\n | 'isDetached'\n | 'status'\n | 'organizer'\n >\n >\n ): Promise {\n const newEvent = await super.createEvent(stringifyDateValues(details));\n Object.setPrototypeOf(newEvent, ExpoCalendarEvent.prototype);\n return newEvent;\n }\n\n override async createReminder(details: Partial): Promise {\n const newReminder = await super.createReminder(stringifyDateValues(details));\n Object.setPrototypeOf(newReminder, ExpoCalendarReminder.prototype);\n return newReminder;\n }\n\n override async listEvents(startDate: Date, endDate: Date): Promise {\n if (!startDate) {\n throw new Error('listEvents must be called with a startDate (date) to search for events');\n }\n if (!endDate) {\n throw new Error('listEvents must be called with an endDate (date) to search for events');\n }\n const events = await super.listEvents(stringifyIfDate(startDate), stringifyIfDate(endDate));\n return events.map((event) => {\n Object.setPrototypeOf(event, ExpoCalendarEvent.prototype);\n return event;\n });\n }\n\n override async listReminders(\n startDate: Date | null = null,\n endDate: Date | null = null,\n status: ReminderStatus | null = null\n ): Promise {\n const reminders = await super.listReminders(\n startDate ? stringifyIfDate(startDate) : null,\n endDate ? stringifyIfDate(endDate) : null,\n status\n );\n return reminders.map((reminder) => {\n Object.setPrototypeOf(reminder, ExpoCalendarReminder.prototype);\n return reminder;\n });\n }\n\n override async update(details: Partial): Promise {\n const color = details.color ? processColor(details.color) : undefined;\n const newDetails = { ...details, color: color || undefined };\n return await super.update(newDetails as Partial);\n }\n\n override async addEventWithForm(options?: AddEventWithFormOptions): Promise {\n if (!super.addEventWithForm) {\n throw new UnavailabilityError('ExpoCalendar', 'addEventWithForm');\n }\n return super.addEventWithForm(options && stringifyDateValues(options));\n }\n\n static override async get(calendarId: string): Promise {\n const calendar = await InternalExpoCalendar.getCalendarById(calendarId);\n Object.setPrototypeOf(calendar, ExpoCalendar.prototype);\n return calendar;\n }\n}\n\n/**\n * Gets an instance of the default calendar object.\n * @return An [`ExpoCalendar`](#expocalendar) object that is the user's default calendar.\n */\nexport function getDefaultCalendarSync(): ExpoCalendar {\n if (Platform.OS === 'android' || !InternalExpoCalendar.getDefaultCalendarSync) {\n throw new UnavailabilityError('Calendar', 'getDefaultCalendar');\n }\n const defaultCalendar = InternalExpoCalendar.getDefaultCalendarSync();\n Object.setPrototypeOf(defaultCalendar, ExpoCalendar.prototype);\n return defaultCalendar as ExpoCalendar;\n}\n\n/**\n * Gets an array of [`ExpoCalendar`](#expocalendar) shared objects with details about the different calendars stored on the device.\n * @param entityType __iOS Only.__ Not required, but if defined, filters the returned calendars to\n * a specific [entity type](#entitytypes). Possible values are `Calendar.EntityTypes.EVENT` (for calendars shown in\n * the Calendar app) and `Calendar.EntityTypes.REMINDER` (for the Reminders app).\n * > **Note:** If not defined, you will need both permissions: **CALENDAR** and **REMINDERS**.\n * @return An array of [`ExpoCalendar`](#expocalendar) shared objects matching the provided entity type (if provided).\n */\nexport async function getCalendars(type?: EntityTypes): Promise {\n if (!InternalExpoCalendar.getCalendars) {\n throw new UnavailabilityError('Calendar', 'getCalendars');\n }\n const calendars = await InternalExpoCalendar.getCalendars(type);\n return calendars.map((calendar) => {\n Object.setPrototypeOf(calendar, ExpoCalendar.prototype);\n return calendar;\n });\n}\n\n/**\n * Creates a new calendar on the device, allowing events to be added later and displayed in the OS Calendar app.\n * @param details A map of details for the calendar to be created.\n * @returns An [`ExpoCalendar`](#expocalendar) object representing the newly created calendar.\n */\nexport async function createCalendar(details: Partial = {}): Promise {\n const color = details.color ? processColor(details.color) : undefined;\n const newDetails = { ...details, id: undefined, color: color || undefined };\n const createdCalendar = await InternalExpoCalendar.createCalendar(newDetails);\n Object.setPrototypeOf(createdCalendar, ExpoCalendar.prototype);\n return createdCalendar;\n}\n\n/**\n * Presents the OS calendar picker and returns the selected calendar.\n * @return An [`ExpoCalendar`](#expocalendar) object or `null` when the picker is cancelled.\n * @platform ios\n */\nexport async function presentPicker(): Promise {\n if (!InternalExpoCalendar.presentPicker) {\n throw new UnavailabilityError('Calendar', 'presentPicker');\n }\n const calendar = await InternalExpoCalendar.presentPicker();\n if (calendar) {\n Object.setPrototypeOf(calendar, ExpoCalendar.prototype);\n }\n return calendar;\n}\n\n/**\n * Lists events from the device's calendar. It can be used to search events in multiple calendars.\n * > **Note:** If you want to search events in a single calendar, you can use [`ExpoCalendar.listEvents`](#listeventsstartdate-enddate) instead.\n * @param calendars An array of calendar IDs (`string[]`) or [`ExpoCalendar`](#expocalendar) objects to search for events.\n * @param startDate The start date of the time range to search for events.\n * @param endDate The end date of the time range to search for events.\n * @returns An array of [`ExpoCalendarEvent`](#expocalendarevent) objects representing the events found.\n */\nexport async function listEvents(\n calendars: (string | ExpoCalendar)[],\n startDate: Date,\n endDate: Date\n): Promise {\n if (!InternalExpoCalendar.listEvents) {\n throw new UnavailabilityError('Calendar', 'listEvents');\n }\n const calendarIds = Array.isArray(calendars)\n ? calendars.map((c) => (typeof c === 'string' ? c : c.id))\n : [];\n return InternalExpoCalendar.listEvents(\n calendarIds,\n stringifyIfDate(startDate),\n stringifyIfDate(endDate)\n );\n}\n\n/**\n * Asks the user to grant permissions for accessing user's calendars.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const requestCalendarPermissions = InternalExpoCalendar.requestCalendarPermissions;\n\n/**\n * Checks user's permissions for accessing user's calendars.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const getCalendarPermissions = InternalExpoCalendar.getCalendarPermissions;\n\n/**\n * Asks the user to grant permissions for accessing user's reminders.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const requestRemindersPermissions = InternalExpoCalendar.requestRemindersPermissions;\n\n/**\n * Checks user's permissions for accessing user's reminders.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const getRemindersPermissions = InternalExpoCalendar.getRemindersPermissions;\n\n/**\n * Gets an array of Source objects with details about the different sources stored on the device.\n * @returns An array of Source objects representing the sources found.\n */\nexport const getSourcesSync = InternalExpoCalendar.getSourcesSync;\n\nexport type {\n ModifiableEventProperties,\n ModifiableReminderProperties,\n ModifiableCalendarProperties,\n AddEventWithFormOptions,\n} from './ExpoCalendar.types';\n\nexport type {\n PermissionResponse,\n Alarm,\n AlarmLocation,\n CalendarDialogParams,\n DaysOfTheWeek,\n DialogEventResult,\n OpenEventDialogResult,\n OpenEventPresentationOptions,\n PermissionExpiration,\n PermissionHookOptions,\n PresentationOptions,\n RecurrenceRule,\n RecurringEventOptions,\n Source,\n} from '../Calendar';\nexport {\n AlarmMethod,\n AttendeeRole,\n AttendeeStatus,\n AttendeeType,\n Availability,\n CalendarAccessLevel,\n CalendarDialogResultActions,\n CalendarType,\n DayOfTheWeek,\n EntityTypes,\n EventAccessLevel,\n EventStatus,\n Frequency,\n MonthOfTheYear,\n ReminderStatus,\n SourceType,\n createEventInCalendarAsync,\n openEventInCalendarAsync,\n} from '../Calendar';\n/**\n * Check or request permissions to access the user's calendars.\n * This uses both `getCalendarPermissions` and `requestCalendarPermissions` to interact\n * with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Calendar.useCalendarPermissions();\n * ```\n */\nexport const useCalendarPermissions = createPermissionHook({\n getMethod: getCalendarPermissions,\n requestMethod: requestCalendarPermissions,\n});\n\n/**\n * Check or request permissions to access the user's reminders.\n * This uses both `getRemindersPermissions` and `requestRemindersPermissions` to interact\n * with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Calendar.useRemindersPermissions();\n * ```\n */\nexport const useRemindersPermissions = createPermissionHook({\n getMethod: getRemindersPermissions,\n requestMethod: requestRemindersPermissions,\n});\n"]}
\ No newline at end of file
+{"version":3,"file":"Calendar.js","sourceRoot":"","sources":["../../src/next/Calendar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAatD,OAAO,oBAAoB,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAS1F;;GAEG;AACH,MAAM,OAAO,oBAAqB,SAAQ,oBAAoB,CAAC,oBAAoB;IACxE,KAAK,CAAC,MAAM,CAAC,OAA8C;QAClE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,IAAI,mBAAmB,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,qBAAqB,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAChE,OAAO,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IAEQ,KAAK,CAAC,MAAM;QACnB,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,IAAI,mBAAmB,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,iBAAkB,SAAQ,oBAAoB,CAAC,iBAAiB;IAClE,iBAAiB,CAAC,wBAA+C,EAAE;QAC1E,MAAM,MAAM,GAAG,KAAK,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,CAAC,CAAC;QACnF,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC3D,OAAO,MAAM,CAAC;IAChB,CAAC;IAEQ,KAAK,CAAC,YAAY;QACzB,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,YAAY,EAAE,CAAC;QAC7C,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;YAChC,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;YAChE,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,cAAc,CAAC,QAAkB;QAC9C,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAC1B,MAAM,IAAI,mBAAmB,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACzD,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;QACnE,OAAO,WAAW,CAAC;IACrB,CAAC;IAEQ,KAAK,CAAC,MAAM,CAAC,OAA2C;QAC/D,MAAM,qBAAqB,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAChE,OAAO,MAAM,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACjF,CAAC;IAEQ,KAAK,CAAC,MAAM;QACnB,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,CAAU,KAAK,CAAC,GAAG,CAAC,OAAe;QACvC,MAAM,KAAK,GAAG,MAAM,oBAAoB,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC/D,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,oBAAqB,SAAQ,oBAAoB,CAAC,oBAAoB;IACxE,KAAK,CAAC,MAAM,CAAC,OAA8C;QAClE,MAAM,qBAAqB,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAChE,MAAM,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,CAAU,KAAK,CAAC,GAAG,CAAC,UAAkB;QAC1C,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QACxE,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAChE,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,oBAAoB,CAAC,YAAY;IACxD,KAAK,CAAC,WAAW,CACxB,OAUC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEQ,KAAK,CAAC,cAAc,CAAC,OAA0B;QACtD,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7E,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;QACnE,OAAO,WAAW,CAAC;IACrB,CAAC;IAEQ,KAAK,CAAC,UAAU,CAAC,SAAe,EAAE,OAAa;QACtD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QAC3F,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5F,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;YAC1D,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,aAAa,CAC1B,YAAyB,IAAI,EAC7B,UAAuB,IAAI,EAC3B,SAAgC,IAAI;QAEpC,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,aAAa,CACzC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAC7C,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EACzC,MAAM,CACP,CAAC;QACF,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;YAChC,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;YAChE,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAEQ,KAAK,CAAC,MAAM,CAAC,OAA8C;QAClE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,MAAM,UAAU,GAAG,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC;QAC7D,OAAO,MAAM,KAAK,CAAC,MAAM,CAAC,UAAmD,CAAC,CAAC;IACjF,CAAC;IAEQ,KAAK,CAAC,gBAAgB,CAAC,OAAiC;QAC/D,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;YAC5B,MAAM,IAAI,mBAAmB,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,KAAK,CAAC,gBAAgB,CAAC,OAAO,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,CAAU,KAAK,CAAC,GAAG,CAAC,UAAkB;QAC1C,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QACxE,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;QACxD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,oBAAoB,CAAC,sBAAsB,EAAE,CAAC;QAC9E,MAAM,IAAI,mBAAmB,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,eAAe,GAAG,oBAAoB,CAAC,sBAAsB,EAAE,CAAC;IACtE,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAC/D,OAAO,eAA+B,CAAC;AACzC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAkB;IACnD,IAAI,CAAC,oBAAoB,CAAC,YAAY,EAAE,CAAC;QACvC,MAAM,IAAI,mBAAmB,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,oBAAoB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAChC,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;QACxD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,UAA6B,EAAE;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtE,MAAM,UAAU,GAAG,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC;IAC5E,MAAM,eAAe,GAAG,MAAM,oBAAoB,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9E,MAAM,CAAC,cAAc,CAAC,eAAe,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAC/D,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,CAAC,oBAAoB,CAAC,aAAa,EAAE,CAAC;QACxC,MAAM,IAAI,mBAAmB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,aAAa,EAAE,CAAC;IAC5D,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,SAAoC,EACpC,SAAe,EACf,OAAa;IAEb,IAAI,CAAC,oBAAoB,CAAC,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QAC1C,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1D,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,oBAAoB,CAAC,UAAU,CACpC,WAAW,EACX,eAAe,CAAC,SAAS,CAAC,EAC1B,eAAe,CAAC,OAAO,CAAC,CACzB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,oBAAoB,CAAC,0BAA0B,CAAC;AAE1F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,sBAAsB,CAAC;AAElF;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,oBAAoB,CAAC,2BAA2B,CAAC;AAE5F;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC,uBAAuB,CAAC;AAEpF;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAC,cAAc,CAAC;AAyBlE,OAAO,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,2BAA2B,EAC3B,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,cAAc,EACd,cAAc,EACd,UAAU,EACV,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAGxD;IACA,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,sBAAsB,CAAC,OAAO,EAAE,SAAS,CAAC;IAClE,aAAa,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,0BAA0B,CAAC,OAAO,EAAE,SAAS,CAAC;CAC3E,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;IAC1D,SAAS,EAAE,uBAAuB;IAClC,aAAa,EAAE,2BAA2B;CAC3C,CAAC,CAAC","sourcesContent":["import { createPermissionHook, UnavailabilityError } from 'expo-modules-core';\nimport { Platform, processColor } from 'react-native';\n\nimport type {\n Calendar,\n Attendee,\n DialogEventResult,\n EntityTypes,\n Event,\n RecurringEventOptions,\n Reminder,\n ReminderStatus,\n PermissionResponse,\n} from '../Calendar';\nimport InternalExpoCalendar from './ExpoCalendar';\nimport { stringifyDateValues, stringifyIfDate, getNullableDetailsFields } from '../utils';\nimport type {\n ModifiableEventProperties,\n ModifiableReminderProperties,\n ModifiableCalendarProperties,\n ModifiableAttendeeProperties,\n AddEventWithFormOptions,\n} from './ExpoCalendar.types';\n\n/**\n * Represents a calendar attendee object.\n */\nexport class ExpoCalendarAttendee extends InternalExpoCalendar.ExpoCalendarAttendee {\n override async update(details: Partial): Promise {\n if (!super.update) {\n throw new UnavailabilityError('ExpoCalendarAttendee', 'update');\n }\n const nullableDetailsFields = getNullableDetailsFields(details);\n return super.update(stringifyDateValues(details), nullableDetailsFields);\n }\n\n override async delete(): Promise {\n if (!super.delete) {\n throw new UnavailabilityError('ExpoCalendarAttendee', 'delete');\n }\n await super.delete();\n }\n}\n\n/**\n * Represents a calendar event object that can be accessed and modified using the Expo Calendar Next API.\n */\nexport class ExpoCalendarEvent extends InternalExpoCalendar.ExpoCalendarEvent {\n override getOccurrenceSync(recurringEventOptions: RecurringEventOptions = {}): ExpoCalendarEvent {\n const result = super.getOccurrenceSync(stringifyDateValues(recurringEventOptions));\n Object.setPrototypeOf(result, ExpoCalendarEvent.prototype);\n return result;\n }\n\n override async getAttendees(): Promise {\n const attendees = await super.getAttendees();\n return attendees.map((attendee) => {\n Object.setPrototypeOf(attendee, ExpoCalendarAttendee.prototype);\n return attendee;\n });\n }\n\n override async createAttendee(attendee: Attendee): Promise {\n if (!super.createAttendee) {\n throw new UnavailabilityError('ExpoCalendarEvent', 'createAttendee');\n }\n const newAttendee = await super.createAttendee(attendee);\n Object.setPrototypeOf(newAttendee, ExpoCalendarAttendee.prototype);\n return newAttendee;\n }\n\n override async update(details: Partial): Promise {\n const nullableDetailsFields = getNullableDetailsFields(details);\n return await super.update(stringifyDateValues(details), nullableDetailsFields);\n }\n\n override async delete(): Promise {\n await super.delete();\n }\n\n static override async get(eventId: string): Promise {\n const event = await InternalExpoCalendar.getEventById(eventId);\n Object.setPrototypeOf(event, ExpoCalendarEvent.prototype);\n return event;\n }\n}\n\n/**\n * Represents a calendar reminder object that can be accessed and modified using the Expo Calendar Next API.\n */\nexport class ExpoCalendarReminder extends InternalExpoCalendar.ExpoCalendarReminder {\n override async update(details: Partial): Promise {\n const nullableDetailsFields = getNullableDetailsFields(details);\n await super.update(stringifyDateValues(details), nullableDetailsFields);\n }\n\n static override async get(reminderId: string): Promise {\n const reminder = await InternalExpoCalendar.getReminderById(reminderId);\n Object.setPrototypeOf(reminder, ExpoCalendarReminder.prototype);\n return reminder;\n }\n}\n\n/**\n * Represents a calendar object that can be accessed and modified using the Expo Calendar Next API.\n *\n * This class provides properties and methods for interacting with a specific calendar on the device,\n * such as retrieving its events, updating its details, and accessing its metadata.\n */\nexport class ExpoCalendar extends InternalExpoCalendar.ExpoCalendar {\n override async createEvent(\n details: Partial<\n Omit<\n Event,\n | 'creationDate'\n | 'lastModifiedDate'\n | 'originalStartDate'\n | 'isDetached'\n | 'status'\n | 'organizer'\n >\n >\n ): Promise {\n const newEvent = await super.createEvent(stringifyDateValues(details));\n Object.setPrototypeOf(newEvent, ExpoCalendarEvent.prototype);\n return newEvent;\n }\n\n override async createReminder(details: Partial): Promise {\n const newReminder = await super.createReminder(stringifyDateValues(details));\n Object.setPrototypeOf(newReminder, ExpoCalendarReminder.prototype);\n return newReminder;\n }\n\n override async listEvents(startDate: Date, endDate: Date): Promise {\n if (!startDate) {\n throw new Error('listEvents must be called with a startDate (date) to search for events');\n }\n if (!endDate) {\n throw new Error('listEvents must be called with an endDate (date) to search for events');\n }\n const events = await super.listEvents(stringifyIfDate(startDate), stringifyIfDate(endDate));\n return events.map((event) => {\n Object.setPrototypeOf(event, ExpoCalendarEvent.prototype);\n return event;\n });\n }\n\n override async listReminders(\n startDate: Date | null = null,\n endDate: Date | null = null,\n status: ReminderStatus | null = null\n ): Promise {\n const reminders = await super.listReminders(\n startDate ? stringifyIfDate(startDate) : null,\n endDate ? stringifyIfDate(endDate) : null,\n status\n );\n return reminders.map((reminder) => {\n Object.setPrototypeOf(reminder, ExpoCalendarReminder.prototype);\n return reminder;\n });\n }\n\n override async update(details: Partial): Promise {\n const color = details.color ? processColor(details.color) : undefined;\n const newDetails = { ...details, color: color || undefined };\n return await super.update(newDetails as Partial);\n }\n\n override async addEventWithForm(options?: AddEventWithFormOptions): Promise {\n if (!super.addEventWithForm) {\n throw new UnavailabilityError('ExpoCalendar', 'addEventWithForm');\n }\n return super.addEventWithForm(options && stringifyDateValues(options));\n }\n\n static override async get(calendarId: string): Promise {\n const calendar = await InternalExpoCalendar.getCalendarById(calendarId);\n Object.setPrototypeOf(calendar, ExpoCalendar.prototype);\n return calendar;\n }\n}\n\n/**\n * Gets an instance of the default calendar object.\n * @return An [`ExpoCalendar`](#expocalendar) object that is the user's default calendar.\n */\nexport function getDefaultCalendarSync(): ExpoCalendar {\n if (Platform.OS === 'android' || !InternalExpoCalendar.getDefaultCalendarSync) {\n throw new UnavailabilityError('Calendar', 'getDefaultCalendar');\n }\n const defaultCalendar = InternalExpoCalendar.getDefaultCalendarSync();\n Object.setPrototypeOf(defaultCalendar, ExpoCalendar.prototype);\n return defaultCalendar as ExpoCalendar;\n}\n\n/**\n * Gets an array of [`ExpoCalendar`](#expocalendar) shared objects with details about the different calendars stored on the device.\n * @param entityType __iOS Only.__ Not required, but if defined, filters the returned calendars to\n * a specific [entity type](#entitytypes). Possible values are `Calendar.EntityTypes.EVENT` (for calendars shown in\n * the Calendar app) and `Calendar.EntityTypes.REMINDER` (for the Reminders app).\n * > **Note:** If not defined, you will need both permissions: **CALENDAR** and **REMINDERS**.\n * @return An array of [`ExpoCalendar`](#expocalendar) shared objects matching the provided entity type (if provided).\n */\nexport async function getCalendars(type?: EntityTypes): Promise {\n if (!InternalExpoCalendar.getCalendars) {\n throw new UnavailabilityError('Calendar', 'getCalendars');\n }\n const calendars = await InternalExpoCalendar.getCalendars(type);\n return calendars.map((calendar) => {\n Object.setPrototypeOf(calendar, ExpoCalendar.prototype);\n return calendar;\n });\n}\n\n/**\n * Creates a new calendar on the device, allowing events to be added later and displayed in the OS Calendar app.\n * @param details A map of details for the calendar to be created.\n * @returns An [`ExpoCalendar`](#expocalendar) object representing the newly created calendar.\n */\nexport async function createCalendar(details: Partial = {}): Promise {\n const color = details.color ? processColor(details.color) : undefined;\n const newDetails = { ...details, id: undefined, color: color || undefined };\n const createdCalendar = await InternalExpoCalendar.createCalendar(newDetails);\n Object.setPrototypeOf(createdCalendar, ExpoCalendar.prototype);\n return createdCalendar;\n}\n\n/**\n * Presents the OS calendar picker and returns the selected calendar.\n * @return An [`ExpoCalendar`](#expocalendar) object or `null` when the picker is cancelled.\n * @platform ios\n */\nexport async function presentPicker(): Promise {\n if (!InternalExpoCalendar.presentPicker) {\n throw new UnavailabilityError('Calendar', 'presentPicker');\n }\n const calendar = await InternalExpoCalendar.presentPicker();\n if (calendar) {\n Object.setPrototypeOf(calendar, ExpoCalendar.prototype);\n }\n return calendar;\n}\n\n/**\n * Lists events from the device's calendar. It can be used to search events in multiple calendars.\n * > **Note:** If you want to search events in a single calendar, you can use [`ExpoCalendar.listEvents`](#listeventsstartdate-enddate) instead.\n * @param calendars An array of calendar IDs (`string[]`) or [`ExpoCalendar`](#expocalendar) objects to search for events.\n * @param startDate The start date of the time range to search for events.\n * @param endDate The end date of the time range to search for events.\n * @returns An array of [`ExpoCalendarEvent`](#expocalendarevent) objects representing the events found.\n */\nexport async function listEvents(\n calendars: (string | ExpoCalendar)[],\n startDate: Date,\n endDate: Date\n): Promise {\n if (!InternalExpoCalendar.listEvents) {\n throw new UnavailabilityError('Calendar', 'listEvents');\n }\n const calendarIds = Array.isArray(calendars)\n ? calendars.map((c) => (typeof c === 'string' ? c : c.id))\n : [];\n return InternalExpoCalendar.listEvents(\n calendarIds,\n stringifyIfDate(startDate),\n stringifyIfDate(endDate)\n );\n}\n\n/**\n * Asks the user to grant permissions for accessing user's calendars.\n * @param writeOnly - On iOS, whether to request write-only access, which allows creating calendar events\n * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const requestCalendarPermissions = InternalExpoCalendar.requestCalendarPermissions;\n\n/**\n * Checks user's permissions for accessing user's calendars.\n * @param writeOnly - On iOS, whether to check write-only access, which allows creating calendar events\n * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const getCalendarPermissions = InternalExpoCalendar.getCalendarPermissions;\n\n/**\n * Asks the user to grant permissions for accessing user's reminders.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const requestRemindersPermissions = InternalExpoCalendar.requestRemindersPermissions;\n\n/**\n * Checks user's permissions for accessing user's reminders.\n * @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).\n */\nexport const getRemindersPermissions = InternalExpoCalendar.getRemindersPermissions;\n\n/**\n * Gets an array of Source objects with details about the different sources stored on the device.\n * @returns An array of Source objects representing the sources found.\n */\nexport const getSourcesSync = InternalExpoCalendar.getSourcesSync;\n\nexport type {\n ModifiableEventProperties,\n ModifiableReminderProperties,\n ModifiableCalendarProperties,\n AddEventWithFormOptions,\n} from './ExpoCalendar.types';\n\nexport type {\n PermissionResponse,\n Alarm,\n AlarmLocation,\n CalendarDialogParams,\n DaysOfTheWeek,\n DialogEventResult,\n OpenEventDialogResult,\n OpenEventPresentationOptions,\n PermissionExpiration,\n PermissionHookOptions,\n PresentationOptions,\n RecurrenceRule,\n RecurringEventOptions,\n Source,\n} from '../Calendar';\nexport {\n AlarmMethod,\n AttendeeRole,\n AttendeeStatus,\n AttendeeType,\n Availability,\n CalendarAccessLevel,\n CalendarDialogResultActions,\n CalendarType,\n DayOfTheWeek,\n EntityTypes,\n EventAccessLevel,\n EventStatus,\n Frequency,\n MonthOfTheYear,\n ReminderStatus,\n SourceType,\n createEventInCalendarAsync,\n openEventInCalendarAsync,\n} from '../Calendar';\n\n/**\n * Check or request permissions to access the user's calendars.\n * This uses both `getCalendarPermissions` and `requestCalendarPermissions` to interact\n * with the permissions.\n * On iOS, `writeOnly` requests permission to create calendar events without reading\n * existing calendars or events. It does not grant permission to create, update, or delete calendars.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Calendar.useCalendarPermissions();\n * ```\n */\nexport const useCalendarPermissions = createPermissionHook<\n PermissionResponse,\n { writeOnly?: boolean }\n>({\n getMethod: (options) => getCalendarPermissions(options?.writeOnly),\n requestMethod: (options) => requestCalendarPermissions(options?.writeOnly),\n});\n\n/**\n * Check or request permissions to access the user's reminders.\n * This uses both `getRemindersPermissions` and `requestRemindersPermissions` to interact\n * with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Calendar.useRemindersPermissions();\n * ```\n */\nexport const useRemindersPermissions = createPermissionHook({\n getMethod: getRemindersPermissions,\n requestMethod: requestRemindersPermissions,\n});\n"]}
\ No newline at end of file
diff --git a/packages/expo-calendar/build/next/ExpoCalendar.d.ts b/packages/expo-calendar/build/next/ExpoCalendar.d.ts
index d2edbf93e009ab..508c5d6696f0bc 100644
--- a/packages/expo-calendar/build/next/ExpoCalendar.d.ts
+++ b/packages/expo-calendar/build/next/ExpoCalendar.d.ts
@@ -19,8 +19,8 @@ declare class ExpoCalendarNextModule extends NativeModule {
presentPicker(): Promise;
getEventById(eventId: string): Promise;
getReminderById(reminderId: string): Promise;
- requestCalendarPermissions(): Promise;
- getCalendarPermissions(): Promise;
+ requestCalendarPermissions(writeOnly?: boolean): Promise;
+ getCalendarPermissions(writeOnly?: boolean): Promise;
requestRemindersPermissions(): Promise;
getRemindersPermissions(): Promise;
getSourcesSync(): Source[];
diff --git a/packages/expo-calendar/build/next/ExpoCalendar.d.ts.map b/packages/expo-calendar/build/next/ExpoCalendar.d.ts.map
index b358c8957da626..4271b6830561ef 100644
--- a/packages/expo-calendar/build/next/ExpoCalendar.d.ts.map
+++ b/packages/expo-calendar/build/next/ExpoCalendar.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoCalendar.d.ts","sourceRoot":"","sources":["../../src/next/ExpoCalendar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,OAAO,sBAAuB,SAAQ,YAAY;IACvD,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,oBAAoB,EAAE,OAAO,oBAAoB,CAAC;IAClD,oBAAoB,EAAE,OAAO,oBAAoB,CAAC;IAElD,cAAc,CACZ,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,GAAG;QAAE,KAAK,EAAE,mBAAmB,GAAG,SAAS,CAAA;KAAE,GACrF,OAAO,CAAC,YAAY,CAAC;IAExB,kBAAkB,IAAI,YAAY;IAClC,sBAAsB,IAAI,OAAO;IACjC,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAEzD,UAAU,CACR,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAE/B,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAC1D,aAAa,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IACzD,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAElE,0BAA0B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IACzD,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IACrD,2BAA2B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAC1D,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAEtD,cAAc,IAAI,MAAM,EAAE;CAC3B;;AAED,wBAEgE"}
\ No newline at end of file
+{"version":3,"file":"ExpoCalendar.d.ts","sourceRoot":"","sources":["../../src/next/ExpoCalendar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,OAAO,sBAAuB,SAAQ,YAAY;IACvD,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,oBAAoB,EAAE,OAAO,oBAAoB,CAAC;IAClD,oBAAoB,EAAE,OAAO,oBAAoB,CAAC;IAElD,cAAc,CACZ,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,GAAG;QAAE,KAAK,EAAE,mBAAmB,GAAG,SAAS,CAAA;KAAE,GACrF,OAAO,CAAC,YAAY,CAAC;IAExB,kBAAkB,IAAI,YAAY;IAClC,sBAAsB,IAAI,OAAO;IACjC,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAEzD,UAAU,CACR,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAE/B,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAC1D,aAAa,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IACzD,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAElE,0BAA0B,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAC5E,sBAAsB,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IACxE,2BAA2B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAC1D,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAEtD,cAAc,IAAI,MAAM,EAAE;CAC3B;;AAED,wBAEgE"}
\ No newline at end of file
diff --git a/packages/expo-calendar/build/next/ExpoCalendar.js.map b/packages/expo-calendar/build/next/ExpoCalendar.js.map
index 5e659eb533e803..15c09f0146212b 100644
--- a/packages/expo-calendar/build/next/ExpoCalendar.js.map
+++ b/packages/expo-calendar/build/next/ExpoCalendar.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoCalendar.js","sourceRoot":"","sources":["../../src/next/ExpoCalendar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAUtE,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAmC9D,eAAe,iBAAiB,EAAE;IAChC,CAAC,CAAE,sBAAwD;IAC3D,CAAC,CAAC,mBAAmB,CAAyB,cAAc,CAAC,CAAC","sourcesContent":["import { isRunningInExpoGo } from 'expo';\nimport type { PermissionResponse } from 'expo-modules-core';\nimport { NativeModule, requireNativeModule } from 'expo-modules-core';\nimport type { ProcessedColorValue } from 'react-native';\n\nimport type {\n ExpoCalendar,\n ExpoCalendarAttendee,\n ExpoCalendarEvent,\n ExpoCalendarReminder,\n} from './ExpoCalendar.types';\nimport type { Calendar, EntityTypes, Source } from '../Calendar';\nimport ExpoGoCalendarNextStub from './ExpoGoCalendarNextStub';\n\ndeclare class ExpoCalendarNextModule extends NativeModule {\n ExpoCalendar: typeof ExpoCalendar;\n ExpoCalendarEvent: typeof ExpoCalendarEvent;\n ExpoCalendarAttendee: typeof ExpoCalendarAttendee;\n ExpoCalendarReminder: typeof ExpoCalendarReminder;\n\n createCalendar(\n details: Omit, 'color'> & { color: ProcessedColorValue | undefined }\n ): Promise;\n\n getDefaultCalendar(): ExpoCalendar;\n getDefaultCalendarSync(): unknown;\n getCalendars(type?: EntityTypes): Promise;\n\n listEvents(\n calendars: string[],\n startDate: string | Date,\n endDate: string | Date\n ): Promise;\n\n getCalendarById(calendarId: string): Promise;\n presentPicker(): Promise;\n getEventById(eventId: string): Promise;\n getReminderById(reminderId: string): Promise;\n\n requestCalendarPermissions(): Promise;\n getCalendarPermissions(): Promise;\n requestRemindersPermissions(): Promise;\n getRemindersPermissions(): Promise;\n\n getSourcesSync(): Source[];\n}\n\nexport default isRunningInExpoGo()\n ? (ExpoGoCalendarNextStub as any as ExpoCalendarNextModule)\n : requireNativeModule('CalendarNext');\n"]}
\ No newline at end of file
+{"version":3,"file":"ExpoCalendar.js","sourceRoot":"","sources":["../../src/next/ExpoCalendar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAUtE,OAAO,sBAAsB,MAAM,0BAA0B,CAAC;AAmC9D,eAAe,iBAAiB,EAAE;IAChC,CAAC,CAAE,sBAAwD;IAC3D,CAAC,CAAC,mBAAmB,CAAyB,cAAc,CAAC,CAAC","sourcesContent":["import { isRunningInExpoGo } from 'expo';\nimport type { PermissionResponse } from 'expo-modules-core';\nimport { NativeModule, requireNativeModule } from 'expo-modules-core';\nimport type { ProcessedColorValue } from 'react-native';\n\nimport type {\n ExpoCalendar,\n ExpoCalendarAttendee,\n ExpoCalendarEvent,\n ExpoCalendarReminder,\n} from './ExpoCalendar.types';\nimport type { Calendar, EntityTypes, Source } from '../Calendar';\nimport ExpoGoCalendarNextStub from './ExpoGoCalendarNextStub';\n\ndeclare class ExpoCalendarNextModule extends NativeModule {\n ExpoCalendar: typeof ExpoCalendar;\n ExpoCalendarEvent: typeof ExpoCalendarEvent;\n ExpoCalendarAttendee: typeof ExpoCalendarAttendee;\n ExpoCalendarReminder: typeof ExpoCalendarReminder;\n\n createCalendar(\n details: Omit, 'color'> & { color: ProcessedColorValue | undefined }\n ): Promise;\n\n getDefaultCalendar(): ExpoCalendar;\n getDefaultCalendarSync(): unknown;\n getCalendars(type?: EntityTypes): Promise;\n\n listEvents(\n calendars: string[],\n startDate: string | Date,\n endDate: string | Date\n ): Promise;\n\n getCalendarById(calendarId: string): Promise;\n presentPicker(): Promise;\n getEventById(eventId: string): Promise;\n getReminderById(reminderId: string): Promise;\n\n requestCalendarPermissions(writeOnly?: boolean): Promise;\n getCalendarPermissions(writeOnly?: boolean): Promise;\n requestRemindersPermissions(): Promise;\n getRemindersPermissions(): Promise;\n\n getSourcesSync(): Source[];\n}\n\nexport default isRunningInExpoGo()\n ? (ExpoGoCalendarNextStub as any as ExpoCalendarNextModule)\n : requireNativeModule('CalendarNext');\n"]}
\ No newline at end of file
diff --git a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts
index a179653ebe8664..0602a11f698e7c 100644
--- a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts
+++ b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts
@@ -19,8 +19,8 @@ declare class ExpoGoCalendarNextStub {
getCalendars(): Promise;
listEvents(): Promise;
presentPicker(): Promise;
- requestCalendarPermissions(): Promise;
- getCalendarPermissions(): Promise;
+ requestCalendarPermissions(writeOnly?: boolean): Promise;
+ getCalendarPermissions(writeOnly?: boolean): Promise;
requestRemindersPermissions(): Promise;
getRemindersPermissions(): Promise;
getSourcesSync(): void;
diff --git a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts.map b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts.map
index 84cd6cba602da8..5d19d54eab9d7b 100644
--- a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts.map
+++ b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoGoCalendarNextStub.d.ts","sourceRoot":"","sources":["../../src/next/ExpoGoCalendarNextStub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,cAAM,sBAAsB;IAC1B,MAAM,CAAC,QAAQ,CAAC,YAAY;;;;MAQ1B;IACF,MAAM,CAAC,QAAQ,CAAC,iBAAiB;;MAI/B;IAEF,MAAM,CAAC,QAAQ,CAAC,oBAAoB;;MAMlC;IAEF,MAAM,CAAC,QAAQ,CAAC,oBAAoB;;MAMlC;IAEF,sBAAsB,IAAI,IAAI;IAI9B,cAAc,IAAI,IAAI;IAIhB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,0BAA0B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAIzD,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAIrD,2BAA2B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI1D,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI5D,cAAc,IAAI,IAAI;CAGvB;AAED,eAAe,sBAAsB,CAAC"}
\ No newline at end of file
+{"version":3,"file":"ExpoGoCalendarNextStub.d.ts","sourceRoot":"","sources":["../../src/next/ExpoGoCalendarNextStub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,cAAM,sBAAsB;IAC1B,MAAM,CAAC,QAAQ,CAAC,YAAY;;;;MAQ1B;IACF,MAAM,CAAC,QAAQ,CAAC,iBAAiB;;MAI/B;IAEF,MAAM,CAAC,QAAQ,CAAC,oBAAoB;;MAMlC;IAEF,MAAM,CAAC,QAAQ,CAAC,oBAAoB;;MAMlC;IAEF,sBAAsB,IAAI,IAAI;IAI9B,cAAc,IAAI,IAAI;IAIhB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,0BAA0B,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAI5E,sBAAsB,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAIxE,2BAA2B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI1D,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI5D,cAAc,IAAI,IAAI;CAGvB;AAED,eAAe,sBAAsB,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js
index 7a38ba4581a7e9..e9fdf44d5cbb87 100644
--- a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js
+++ b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js
@@ -37,10 +37,10 @@ class ExpoGoCalendarNextStub {
async presentPicker() {
throw new Error('Calendar@next functionality is not available in Expo Go');
}
- async requestCalendarPermissions() {
+ async requestCalendarPermissions(writeOnly) {
throw new Error('Calendar@next functionality is not available in Expo Go');
}
- async getCalendarPermissions() {
+ async getCalendarPermissions(writeOnly) {
throw new Error('Calendar@next functionality is not available in Expo Go');
}
async requestRemindersPermissions() {
diff --git a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js.map b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js.map
index 6e1261c1975393..f7b3cb5e87fd5e 100644
--- a/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js.map
+++ b/packages/expo-calendar/build/next/ExpoGoCalendarNextStub.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoGoCalendarNextStub.js","sourceRoot":"","sources":["../../src/next/ExpoGoCalendarNextStub.ts"],"names":[],"mappings":"AAEA,MAAM,sBAAsB;IAC1B,MAAM,CAAU,YAAY,GAAG,MAAM,YAAY;QAC/C;YACE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;QAED,gBAAgB;YACd,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;KACF,CAAC;IACF,MAAM,CAAU,iBAAiB,GAAG,MAAM,iBAAiB;QACzD;YACE,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;QAC/F,CAAC;KACF,CAAC;IAEF,MAAM,CAAU,oBAAoB,GAAG,MAAM,oBAAoB;QAC/D;YACE,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,MAAM,CAAU,oBAAoB,GAAG,MAAM,oBAAoB;QAC/D;YACE,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,sBAAsB;QACpB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,cAAc;QACZ,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,0BAA0B;QAC9B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,sBAAsB;QAC1B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,2BAA2B;QAC/B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,cAAc;QACZ,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;;AAGH,eAAe,sBAAsB,CAAC","sourcesContent":["import type { PermissionResponse } from 'expo-modules-core';\n\nclass ExpoGoCalendarNextStub {\n static readonly ExpoCalendar = class ExpoCalendar {\n constructor() {\n throw new Error('`ExpoCalendar` is not yet available in the Expo Go managed workflow.');\n }\n\n addEventWithForm() {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n };\n static readonly ExpoCalendarEvent = class ExpoCalendarEvent {\n constructor() {\n throw new Error('`ExpoCalendarEvent` is not yet available in the Expo Go managed workflow.');\n }\n };\n\n static readonly ExpoCalendarReminder = class ExpoCalendarReminder {\n constructor() {\n throw new Error(\n '`ExpoCalendarReminder` is not yet available in the Expo Go managed workflow.'\n );\n }\n };\n\n static readonly ExpoCalendarAttendee = class ExpoCalendarAttendee {\n constructor() {\n throw new Error(\n '`ExpoCalendarAttendee` is not yet available in the Expo Go managed workflow.'\n );\n }\n };\n\n getDefaultCalendarSync(): void {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n createCalendar(): void {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async getCalendars(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async listEvents(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async presentPicker(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async requestCalendarPermissions(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async getCalendarPermissions(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async requestRemindersPermissions(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async getRemindersPermissions(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n getSourcesSync(): void {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n}\n\nexport default ExpoGoCalendarNextStub;\n"]}
\ No newline at end of file
+{"version":3,"file":"ExpoGoCalendarNextStub.js","sourceRoot":"","sources":["../../src/next/ExpoGoCalendarNextStub.ts"],"names":[],"mappings":"AAEA,MAAM,sBAAsB;IAC1B,MAAM,CAAU,YAAY,GAAG,MAAM,YAAY;QAC/C;YACE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC1F,CAAC;QAED,gBAAgB;YACd,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;KACF,CAAC;IACF,MAAM,CAAU,iBAAiB,GAAG,MAAM,iBAAiB;QACzD;YACE,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;QAC/F,CAAC;KACF,CAAC;IAEF,MAAM,CAAU,oBAAoB,GAAG,MAAM,oBAAoB;QAC/D;YACE,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,MAAM,CAAU,oBAAoB,GAAG,MAAM,oBAAoB;QAC/D;YACE,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,sBAAsB;QACpB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,cAAc;QACZ,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,0BAA0B,CAAC,SAAmB;QAClD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,SAAmB;QAC9C,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,2BAA2B;QAC/B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,cAAc;QACZ,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;;AAGH,eAAe,sBAAsB,CAAC","sourcesContent":["import type { PermissionResponse } from 'expo-modules-core';\n\nclass ExpoGoCalendarNextStub {\n static readonly ExpoCalendar = class ExpoCalendar {\n constructor() {\n throw new Error('`ExpoCalendar` is not yet available in the Expo Go managed workflow.');\n }\n\n addEventWithForm() {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n };\n static readonly ExpoCalendarEvent = class ExpoCalendarEvent {\n constructor() {\n throw new Error('`ExpoCalendarEvent` is not yet available in the Expo Go managed workflow.');\n }\n };\n\n static readonly ExpoCalendarReminder = class ExpoCalendarReminder {\n constructor() {\n throw new Error(\n '`ExpoCalendarReminder` is not yet available in the Expo Go managed workflow.'\n );\n }\n };\n\n static readonly ExpoCalendarAttendee = class ExpoCalendarAttendee {\n constructor() {\n throw new Error(\n '`ExpoCalendarAttendee` is not yet available in the Expo Go managed workflow.'\n );\n }\n };\n\n getDefaultCalendarSync(): void {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n createCalendar(): void {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async getCalendars(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async listEvents(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async presentPicker(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async requestCalendarPermissions(writeOnly?: boolean): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async getCalendarPermissions(writeOnly?: boolean): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async requestRemindersPermissions(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n async getRemindersPermissions(): Promise {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n\n getSourcesSync(): void {\n throw new Error('Calendar@next functionality is not available in Expo Go');\n }\n}\n\nexport default ExpoGoCalendarNextStub;\n"]}
\ No newline at end of file
diff --git a/packages/expo-calendar/ios/Next/ExpoCalendarPermissions.swift b/packages/expo-calendar/ios/Next/CalendarAccessGuard.swift
similarity index 60%
rename from packages/expo-calendar/ios/Next/ExpoCalendarPermissions.swift
rename to packages/expo-calendar/ios/Next/CalendarAccessGuard.swift
index fdb6fc1ee398b2..1035b28cef0ba8 100644
--- a/packages/expo-calendar/ios/Next/ExpoCalendarPermissions.swift
+++ b/packages/expo-calendar/ios/Next/CalendarAccessGuard.swift
@@ -2,7 +2,7 @@ import EventKit
import ExpoModulesCore
import Foundation
-public class ExpoCalendarPermissions {
+public class CalendarAccessGuard {
private var permittedEntities: EKEntityMask = .event
private let eventStore: EKEventStore
private weak var appContext: AppContext?
@@ -18,12 +18,12 @@ public class ExpoCalendarPermissions {
}
var permittedEntities: EKEntityMask = []
if permissionsManager.hasGrantedPermission(
- usingRequesterClass: CalendarPermissionsRequester.self) {
+ usingRequesterClass: CalendarNextPermissionsRequester.self) {
permittedEntities.insert(.event)
}
if permissionsManager.hasGrantedPermission(
- usingRequesterClass: RemindersPermissionRequester.self) {
+ usingRequesterClass: RemindersNextPermissionRequester.self) {
permittedEntities.insert(.reminder)
}
@@ -31,30 +31,40 @@ public class ExpoCalendarPermissions {
}
public func checkCalendarPermissions() throws {
- try self.checkPermissions(entity: .event)
+ try self.checkPermissions(
+ entity: .event,
+ requester: CalendarNextPermissionsRequester.self,
+ permissionName: "CALENDAR"
+ )
+ }
+
+ public func checkCalendarWritePermissions() throws {
+ try self.checkPermissions(
+ entity: .event,
+ requester: CalendarWriteOnlyNextPermissionsRequester.self,
+ permissionName: "CALENDARWRITEONLY"
+ )
}
public func checkRemindersPermissions() throws {
- try self.checkPermissions(entity: .reminder)
+ try self.checkPermissions(
+ entity: .reminder,
+ requester: RemindersNextPermissionRequester.self,
+ permissionName: "REMINDERS"
+ )
}
- private func checkPermissions(entity: EKEntityType) throws {
+ private func checkPermissions(
+ entity: EKEntityType,
+ requester: EXPermissionsRequester.Type,
+ permissionName: String
+ ) throws {
guard let permissionsManager = appContext?.permissions else {
throw PermissionsManagerNotFoundException()
}
- var requester: EXPermissionsRequester.Type?
- switch entity {
- case .event:
- requester = CalendarPermissionsRequester.self
- case .reminder:
- requester = RemindersPermissionRequester.self
- @unknown default:
- requester = nil
- }
- if let requester, !permissionsManager.hasGrantedPermission(usingRequesterClass: requester) {
- let message = requester.permissionType().uppercased()
- throw MissionPermissionsException(message)
+ if !permissionsManager.hasGrantedPermission(usingRequesterClass: requester) {
+ throw MissionPermissionsException(permissionName)
}
resetEventStoreIfPermissionWasChanged(entity: entity)
diff --git a/packages/expo-calendar/ios/Next/CalendarNextModule.swift b/packages/expo-calendar/ios/Next/CalendarNextModule.swift
index 4274c0d1002b94..d7fde433fe8b1f 100644
--- a/packages/expo-calendar/ios/Next/CalendarNextModule.swift
+++ b/packages/expo-calendar/ios/Next/CalendarNextModule.swift
@@ -10,7 +10,8 @@ public final class CalendarNextModule: Module {
}
private var calendarDialogDelegate: CalendarDialogDelegate?
private var calendarPickerDelegate: CalendarPickerDelegate?
- private var calendarPermissions: ExpoCalendarPermissions?
+ private var calendarAccessGuard: CalendarAccessGuard?
+ private var calendarPermissionRequester: CalendarPermissionRequester?
// swiftlint:disable:next function_body_length cyclomatic_complexity
public func definition() -> ModuleDefinition {
@@ -18,15 +19,17 @@ public final class CalendarNextModule: Module {
OnCreate {
self.appContext?.permissions?.register([
- CalendarPermissionsRequester(eventStore: eventStore),
- RemindersPermissionRequester(eventStore: eventStore)
+ CalendarNextPermissionsRequester(eventStore: eventStore),
+ CalendarWriteOnlyNextPermissionsRequester(eventStore: eventStore),
+ RemindersNextPermissionRequester(eventStore: eventStore)
])
- self.calendarPermissions = ExpoCalendarPermissions(eventStore: eventStore, appContext: appContext)
- self.calendarPermissions?.initializePermittedEntities()
+ self.calendarAccessGuard = CalendarAccessGuard(eventStore: eventStore, appContext: appContext)
+ self.calendarPermissionRequester = CalendarPermissionRequester(appContext: appContext)
+ self.calendarAccessGuard?.initializePermittedEntities()
}
Function("getDefaultCalendarSync") { () -> ExpoCalendar in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
guard let defaultCalendar = eventStore.defaultCalendarForNewEvents else {
throw DefaultCalendarNotFoundException()
}
@@ -37,21 +40,21 @@ public final class CalendarNextModule: Module {
let calendars: [EKCalendar]
switch type {
case nil:
- try calendarPermissions?.checkCalendarPermissions()
- try calendarPermissions?.checkRemindersPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkRemindersPermissions()
calendars = eventStore.calendars(for: .event) + eventStore.calendars(for: .reminder)
case .event:
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
calendars = eventStore.calendars(for: .event)
case .reminder:
- try calendarPermissions?.checkRemindersPermissions()
+ try calendarAccessGuard?.checkRemindersPermissions()
calendars = eventStore.calendars(for: .reminder)
}
return calendars.map { ExpoCalendar(calendar: $0) }
}
AsyncFunction("getCalendarById") { (calendarId: String) -> ExpoCalendar in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
guard let calendar = eventStore.calendar(withIdentifier: calendarId) else {
throw CalendarIdNotFoundException(calendarId)
}
@@ -85,10 +88,10 @@ public final class CalendarNextModule: Module {
let calendar: EKCalendar
switch calendarRecord.entityType {
case .event:
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
calendar = EKCalendar(for: .event, eventStore: eventStore)
case .reminder:
- try calendarPermissions?.checkRemindersPermissions()
+ try calendarAccessGuard?.checkRemindersPermissions()
calendar = EKCalendar(for: .reminder, eventStore: eventStore)
case .none:
throw EntityNotSupportedException(calendarRecord.entityType?.rawValue)
@@ -119,7 +122,7 @@ public final class CalendarNextModule: Module {
startDateStr: Either,
endDateStr: Either,
promise: Promise) throws in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
let startDate = try requireDate(from: startDateStr)
let endDate = try requireDate(from: endDateStr)
@@ -144,6 +147,7 @@ public final class CalendarNextModule: Module {
AsyncFunction("getEventById") {
(eventId: String) -> ExpoCalendarEvent in
+ try calendarAccessGuard?.checkCalendarPermissions()
guard let event = eventStore.event(withIdentifier: eventId) else {
throw EventNotFoundException(eventId)
}
@@ -152,31 +156,24 @@ public final class CalendarNextModule: Module {
AsyncFunction("getReminderById") {
(reminderId: String) -> ExpoCalendarReminder in
+ try calendarAccessGuard?.checkRemindersPermissions()
guard let reminder = eventStore.calendarItem(withIdentifier: reminderId) as? EKReminder else {
throw ReminderNotFoundException(reminderId)
}
return ExpoCalendarReminder(reminder: reminder)
}
- AsyncFunction("getCalendarPermissions") { (promise: Promise) in
- appContext?.permissions?.getPermissionUsingRequesterClass(
- CalendarPermissionsRequester.self,
- resolve: promise.legacyResolver,
- reject: promise.legacyRejecter
- )
+ AsyncFunction("getCalendarPermissions") { (writeOnly: Bool?, promise: Promise) in
+ try calendarPermissionRequester?.getCalendarPermissions(writeOnly ?? false, promise: promise)
}
- AsyncFunction("requestCalendarPermissions") { (promise: Promise) in
- appContext?.permissions?.askForPermission(
- usingRequesterClass: CalendarPermissionsRequester.self,
- resolve: promise.legacyResolver,
- reject: promise.legacyRejecter
- )
+ AsyncFunction("requestCalendarPermissions") { (writeOnly: Bool?, promise: Promise) in
+ try calendarPermissionRequester?.requestCalendarPermissions(writeOnly ?? false, promise: promise)
}
AsyncFunction("getRemindersPermissions") { (promise: Promise) in
appContext?.permissions?.getPermissionUsingRequesterClass(
- RemindersPermissionRequester.self,
+ RemindersNextPermissionRequester.self,
resolve: promise.legacyResolver,
reject: promise.legacyRejecter
)
@@ -184,7 +181,7 @@ public final class CalendarNextModule: Module {
AsyncFunction("requestRemindersPermissions") { (promise: Promise) in
appContext?.permissions?.askForPermission(
- usingRequesterClass: RemindersPermissionRequester.self,
+ usingRequesterClass: RemindersNextPermissionRequester.self,
resolve: promise.legacyResolver,
reject: promise.legacyRejecter)
}
@@ -263,7 +260,7 @@ public final class CalendarNextModule: Module {
startDateStr: Either,
endDateStr: Either,
promise: Promise) throws in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
let startDate = try requireDate(from: startDateStr)
let endDate = try requireDate(from: endDateStr)
promise.resolve(try expoCalendar.listEvents(startDate: startDate, endDate: endDate))
@@ -275,7 +272,7 @@ public final class CalendarNextModule: Module {
endDateStr: String?,
status: String?,
promise: Promise) throws in
- try calendarPermissions?.checkRemindersPermissions()
+ try calendarAccessGuard?.checkRemindersPermissions()
var startDate: Date?
var endDate: Date?
@@ -293,7 +290,7 @@ public final class CalendarNextModule: Module {
// swiftlint:enable closure_parameter_position
AsyncFunction("createEvent") { (expoCalendar: ExpoCalendar, eventRecord: EventNext, options: RecurringEventOptions?) -> ExpoCalendarEvent in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarWritePermissions()
guard let calendar = expoCalendar.calendar else {
throw CalendarNoLongerExistsException()
@@ -315,7 +312,7 @@ public final class CalendarNextModule: Module {
}
AsyncFunction("createReminder") { (expoCalendar: ExpoCalendar, reminderRecord: Reminder, _: RecurringEventOptions?) -> ExpoCalendarReminder in
- try calendarPermissions?.checkRemindersPermissions()
+ try calendarAccessGuard?.checkRemindersPermissions()
guard let calendarInstance = expoCalendar.calendar else {
throw CalendarNoLongerExistsException()
@@ -337,6 +334,8 @@ public final class CalendarNextModule: Module {
}
AsyncFunction("addEventWithForm") { (expoCalendar: ExpoCalendar, options: AddEventWithFormOptions?, promise: Promise) in
+ try calendarAccessGuard?.checkCalendarWritePermissions()
+
let event = EKEvent(eventStore: eventStore)
event.calendar = expoCalendar.calendar
if let options {
@@ -365,12 +364,12 @@ public final class CalendarNextModule: Module {
}.runOnQueue(.main)
AsyncFunction("update") { (expoCalendar: ExpoCalendar, calendarRecord: CalendarRecordNext) throws in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
try expoCalendar.update(calendarRecord: calendarRecord)
}
AsyncFunction("delete") { (expoCalendar: ExpoCalendar) in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
try expoCalendar.delete()
}
}
@@ -469,7 +468,7 @@ public final class CalendarNextModule: Module {
}
AsyncFunction("openInCalendar") { (expoEvent: ExpoCalendarEvent, options: OpenInCalendarOptions?, promise: Promise) in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
let startDate = parse(date: options?.instanceStartDate)
guard let calendarEvent = expoEvent.getOccurrence(startDate: startDate) else {
@@ -504,7 +503,7 @@ public final class CalendarNextModule: Module {
}.runOnQueue(.main)
AsyncFunction("editInCalendar") { (expoEvent: ExpoCalendarEvent, _: OpenInCalendarOptions?, promise: Promise) in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
guard let event = expoEvent.event else {
throw ItemNoLongerExistsException()
@@ -514,7 +513,7 @@ public final class CalendarNextModule: Module {
}.runOnQueue(.main)
Function("getOccurrenceSync") { (expoEvent: ExpoCalendarEvent, options: RecurringEventOptions?) throws in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
guard let ekEvent = try expoEvent.getOccurrence(options: options) else {
throw EventNotFoundException(options?.instanceStartDate ?? "")
}
@@ -523,17 +522,17 @@ public final class CalendarNextModule: Module {
}
AsyncFunction("getAttendees") { (expoEvent: ExpoCalendarEvent) throws in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
return try expoEvent.getAttendees()
}
AsyncFunction("update") { (expoEvent: ExpoCalendarEvent, eventRecord: EventNext, nullableFields: [String]?) throws in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
try expoEvent.update(eventRecord: eventRecord, nullableFields: nullableFields)
}
AsyncFunction("delete") { (expoEvent: ExpoCalendarEvent) in
- try calendarPermissions?.checkCalendarPermissions()
+ try calendarAccessGuard?.checkCalendarPermissions()
try expoEvent.delete()
}
}
@@ -634,12 +633,12 @@ public final class CalendarNextModule: Module {
}
AsyncFunction("update") { (expoReminder: ExpoCalendarReminder, reminderRecord: Reminder, nullableFields: [String]?) in
- try calendarPermissions?.checkRemindersPermissions()
+ try calendarAccessGuard?.checkRemindersPermissions()
try expoReminder.update(reminderRecord: reminderRecord, nullableFields: nullableFields)
}
AsyncFunction("delete") { (expoReminder: ExpoCalendarReminder) in
- try calendarPermissions?.checkRemindersPermissions()
+ try calendarAccessGuard?.checkRemindersPermissions()
try expoReminder.delete()
}
}
diff --git a/packages/expo-calendar/ios/Next/CalendarPermissionRequester.swift b/packages/expo-calendar/ios/Next/CalendarPermissionRequester.swift
new file mode 100644
index 00000000000000..cc8fe641960a53
--- /dev/null
+++ b/packages/expo-calendar/ios/Next/CalendarPermissionRequester.swift
@@ -0,0 +1,47 @@
+import ExpoModulesCore
+
+class CalendarPermissionRequester {
+ private weak var appContext: AppContext?
+
+ init(appContext: AppContext?) {
+ self.appContext = appContext
+ }
+
+ func getCalendarPermissions(_ writeOnly: Bool, promise: Promise) throws {
+ try validatePlistKey(writeOnly)
+ appContext?.permissions?.getPermissionUsingRequesterClass(
+ requesterClass(writeOnly),
+ resolve: promise.legacyResolver,
+ reject: promise.legacyRejecter
+ )
+ }
+
+ func requestCalendarPermissions(_ writeOnly: Bool, promise: Promise) throws {
+ try validatePlistKey(writeOnly)
+ appContext?.permissions?.askForPermission(
+ usingRequesterClass: requesterClass(writeOnly),
+ resolve: promise.legacyResolver,
+ reject: promise.legacyRejecter
+ )
+ }
+
+ private func validatePlistKey(_ writeOnly: Bool) throws {
+ if writeOnly {
+ guard CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarWriteOnly)
+ || CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarFullAccess) else {
+ throw MissingCalendarPListValueException(CalendarPlistKeys.calendarWriteOnly)
+ }
+ } else {
+ guard CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarFullAccess) else {
+ throw MissingCalendarPListValueException(CalendarPlistKeys.calendarFullAccess)
+ }
+ }
+ }
+
+ private func requesterClass(_ writeOnly: Bool) -> EXPermissionsRequester.Type {
+ if writeOnly {
+ return CalendarWriteOnlyNextPermissionsRequester.self
+ }
+ return CalendarNextPermissionsRequester.self
+ }
+}
diff --git a/packages/expo-calendar/ios/Next/CalendarPlistKeys.swift b/packages/expo-calendar/ios/Next/CalendarPlistKeys.swift
new file mode 100644
index 00000000000000..5e00154e012f92
--- /dev/null
+++ b/packages/expo-calendar/ios/Next/CalendarPlistKeys.swift
@@ -0,0 +1,21 @@
+import Foundation
+
+enum CalendarPlistKeys {
+ static var calendarFullAccess: String {
+ if #available(iOS 17.0, *) {
+ return "NSCalendarsFullAccessUsageDescription"
+ }
+ return "NSCalendarsUsageDescription"
+ }
+
+ static var calendarWriteOnly: String {
+ if #available(iOS 17.0, *) {
+ return "NSCalendarsWriteOnlyAccessUsageDescription"
+ }
+ return "NSCalendarsUsageDescription"
+ }
+
+ static func isIncludedInInfoPlist(_ key: String) -> Bool {
+ return Bundle.main.object(forInfoDictionaryKey: key) != nil
+ }
+}
diff --git a/packages/expo-calendar/ios/Next/Requesters/CalendarNextPermissionsRequester.swift b/packages/expo-calendar/ios/Next/Requesters/CalendarNextPermissionsRequester.swift
new file mode 100644
index 00000000000000..97ee65cd607f65
--- /dev/null
+++ b/packages/expo-calendar/ios/Next/Requesters/CalendarNextPermissionsRequester.swift
@@ -0,0 +1,72 @@
+import ExpoModulesCore
+import EventKit
+internal import React
+
+public class CalendarNextPermissionsRequester: NSObject, EXPermissionsRequester {
+ private let eventStore: EKEventStore
+
+ init(eventStore: EKEventStore) {
+ self.eventStore = eventStore
+ }
+
+ static public func permissionType() -> String {
+ return "calendarNext"
+ }
+
+ public func getPermissions() -> [AnyHashable: Any] {
+ guard CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarFullAccess) else {
+ return ["status": EXPermissionStatusDenied.rawValue, "canAskAgain": false]
+ }
+ let status = convertToExpoPermissionStatus(EKEventStore.authorizationStatus(for: .event))
+ return ["status": status.rawValue, "canAskAgain": status == EXPermissionStatusUndetermined]
+ }
+
+ public func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: @escaping EXPromiseRejectBlock) {
+ guard validateFullAccessPlistKey(reject) else {
+ return
+ }
+
+ requestFullAccess(resolve: resolve, reject: reject)
+ }
+
+ private func convertToExpoPermissionStatus(_ authorizationStatus: EKAuthorizationStatus) -> EXPermissionStatus {
+ switch authorizationStatus {
+ case .restricted, .denied, .writeOnly:
+ return EXPermissionStatusDenied
+ case .notDetermined:
+ return EXPermissionStatusUndetermined
+ case .fullAccess:
+ return EXPermissionStatusGranted
+ @unknown default:
+ return EXPermissionStatusUndetermined
+ }
+ }
+
+ private func validateFullAccessPlistKey(_ reject: EXPromiseRejectBlock) -> Bool {
+ guard CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarFullAccess) else {
+ reject("E_MISSING_PLIST", "Cannot request calendar permissions because \(CalendarPlistKeys.calendarFullAccess) is missing from your Info.plist. Add it via the expo-calendar config plugin or manually.", nil)
+ return false
+ }
+ return true
+ }
+
+ private func requestFullAccess(resolve: @escaping EXPromiseResolveBlock, reject: @escaping EXPromiseRejectBlock) {
+ if #available(iOS 17.0, *) {
+ eventStore.requestFullAccessToEvents { [weak self] _, error in
+ self?.resolvePermissionsRequest(error: error, resolve: resolve, reject: reject)
+ }
+ } else {
+ eventStore.requestAccess(to: .event) { [weak self] _, error in
+ self?.resolvePermissionsRequest(error: error, resolve: resolve, reject: reject)
+ }
+ }
+ }
+
+ private func resolvePermissionsRequest(error: Error?, resolve: EXPromiseResolveBlock, reject: EXPromiseRejectBlock) {
+ if let error {
+ reject("E_CALENDAR_ERROR_UNKNOWN", error.localizedDescription, error)
+ } else {
+ resolve(self.getPermissions())
+ }
+ }
+}
diff --git a/packages/expo-calendar/ios/Next/Requesters/CalendarWriteOnlyNextPermissionsRequester.swift b/packages/expo-calendar/ios/Next/Requesters/CalendarWriteOnlyNextPermissionsRequester.swift
new file mode 100644
index 00000000000000..0b1d37e27ae01c
--- /dev/null
+++ b/packages/expo-calendar/ios/Next/Requesters/CalendarWriteOnlyNextPermissionsRequester.swift
@@ -0,0 +1,69 @@
+import ExpoModulesCore
+import EventKit
+internal import React
+
+public class CalendarWriteOnlyNextPermissionsRequester: NSObject, EXPermissionsRequester {
+ private let eventStore: EKEventStore
+
+ init(eventStore: EKEventStore) {
+ self.eventStore = eventStore
+ }
+
+ static public func permissionType() -> String {
+ return "calendarWriteOnly"
+ }
+
+ public func getPermissions() -> [AnyHashable: Any] {
+ guard CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarWriteOnly)
+ || CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarFullAccess) else {
+ return ["status": EXPermissionStatusDenied.rawValue, "canAskAgain": false]
+ }
+
+ let status = convertToExpoPermissionStatus(EKEventStore.authorizationStatus(for: .event))
+
+ return ["status": status.rawValue, "canAskAgain": status == EXPermissionStatusUndetermined]
+ }
+
+ public func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: @escaping EXPromiseRejectBlock) {
+ guard CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarWriteOnly)
+ || CalendarPlistKeys.isIncludedInInfoPlist(CalendarPlistKeys.calendarFullAccess) else {
+ reject("E_MISSING_PLIST", "Cannot request write-only calendar permissions because \(CalendarPlistKeys.calendarWriteOnly) is missing from your Info.plist. Enable `writeOnlyAccess` in the expo-calendar config plugin.", nil)
+ return
+ }
+
+ requestWriteOnlyAccess(resolve: resolve, reject: reject)
+ }
+
+ private func convertToExpoPermissionStatus(_ authorizationStatus: EKAuthorizationStatus) -> EXPermissionStatus {
+ switch authorizationStatus {
+ case .restricted, .denied:
+ return EXPermissionStatusDenied
+ case .notDetermined:
+ return EXPermissionStatusUndetermined
+ case .writeOnly, .fullAccess:
+ return EXPermissionStatusGranted
+ @unknown default:
+ return EXPermissionStatusUndetermined
+ }
+ }
+
+ private func requestWriteOnlyAccess(resolve: @escaping EXPromiseResolveBlock, reject: @escaping EXPromiseRejectBlock) {
+ if #available(iOS 17.0, *) {
+ eventStore.requestWriteOnlyAccessToEvents { [weak self] _, error in
+ self?.resolvePermissionsRequest(error: error, resolve: resolve, reject: reject)
+ }
+ } else {
+ eventStore.requestAccess(to: .event) { [weak self] _, error in
+ self?.resolvePermissionsRequest(error: error, resolve: resolve, reject: reject)
+ }
+ }
+ }
+
+ private func resolvePermissionsRequest(error: Error?, resolve: EXPromiseResolveBlock, reject: EXPromiseRejectBlock) {
+ if let error {
+ reject("E_CALENDAR_ERROR_UNKNOWN", error.localizedDescription, error)
+ } else {
+ resolve(self.getPermissions())
+ }
+ }
+}
diff --git a/packages/expo-calendar/ios/Next/Requesters/RemindersNextPermissionsRequester.swift b/packages/expo-calendar/ios/Next/Requesters/RemindersNextPermissionsRequester.swift
new file mode 100644
index 00000000000000..395a81abc81fb8
--- /dev/null
+++ b/packages/expo-calendar/ios/Next/Requesters/RemindersNextPermissionsRequester.swift
@@ -0,0 +1,58 @@
+import ExpoModulesCore
+import EventKit
+internal import React
+
+public class RemindersNextPermissionRequester: NSObject, EXPermissionsRequester {
+ private let eventStore: EKEventStore
+
+ init(eventStore: EKEventStore) {
+ self.eventStore = eventStore
+ }
+
+ static public func permissionType() -> String {
+ return "remindersNext"
+ }
+
+ public func getPermissions() -> [AnyHashable: Any] {
+ let status = convertToExpoPermissionStatus(EKEventStore.authorizationStatus(for: .reminder))
+
+ return ["status": status.rawValue, "canAskAgain": status == EXPermissionStatusUndetermined]
+ }
+
+ public func requestPermissions(resolver resolve: @escaping EXPromiseResolveBlock, rejecter reject: @escaping EXPromiseRejectBlock) {
+ requestFullAccess(resolve: resolve, reject: reject)
+ }
+
+ private func convertToExpoPermissionStatus(_ authorizationStatus: EKAuthorizationStatus) -> EXPermissionStatus {
+ switch authorizationStatus {
+ case .restricted, .denied, .writeOnly:
+ return EXPermissionStatusDenied
+ case .notDetermined:
+ return EXPermissionStatusUndetermined
+ case .fullAccess:
+ return EXPermissionStatusGranted
+ @unknown default:
+ return EXPermissionStatusUndetermined
+ }
+ }
+
+ private func requestFullAccess(resolve: @escaping EXPromiseResolveBlock, reject: @escaping EXPromiseRejectBlock) {
+ if #available(iOS 17.0, *) {
+ eventStore.requestFullAccessToReminders { [weak self] _, error in
+ self?.resolvePermissionsRequest(error: error, resolve: resolve, reject: reject)
+ }
+ } else {
+ eventStore.requestAccess(to: .reminder) { [weak self] _, error in
+ self?.resolvePermissionsRequest(error: error, resolve: resolve, reject: reject)
+ }
+ }
+ }
+
+ private func resolvePermissionsRequest(error: Error?, resolve: EXPromiseResolveBlock, reject: EXPromiseRejectBlock) {
+ if let error {
+ reject("E_REMINDERS_ERROR_UNKNOWN", error.localizedDescription, error)
+ } else {
+ resolve(self.getPermissions())
+ }
+ }
+}
diff --git a/packages/expo-calendar/ios/Requesters/CalendarPermissionsRequester.swift b/packages/expo-calendar/ios/Requesters/CalendarPermissionsRequester.swift
index 6201a12e47f72e..5cba4627bfc744 100644
--- a/packages/expo-calendar/ios/Requesters/CalendarPermissionsRequester.swift
+++ b/packages/expo-calendar/ios/Requesters/CalendarPermissionsRequester.swift
@@ -27,7 +27,6 @@ public class CalendarPermissionsRequester: NSObject, EXPermissionsRequester {
if Bundle.main.object(forInfoDictionaryKey: description) != nil {
permissions = EKEventStore.authorizationStatus(for: .event)
} else {
- RCTFatal(MissingCalendarPListValueException(description))
permissions = .denied
}
diff --git a/packages/expo-calendar/ios/Requesters/RemindersPermissionsRequester.swift b/packages/expo-calendar/ios/Requesters/RemindersPermissionsRequester.swift
index e606ed732b49c5..a371b53dff222b 100644
--- a/packages/expo-calendar/ios/Requesters/RemindersPermissionsRequester.swift
+++ b/packages/expo-calendar/ios/Requesters/RemindersPermissionsRequester.swift
@@ -27,7 +27,6 @@ public class RemindersPermissionRequester: NSObject, EXPermissionsRequester {
if Bundle.main.object(forInfoDictionaryKey: description) != nil {
permissions = EKEventStore.authorizationStatus(for: .reminder)
} else {
- RCTFatal(MissingCalendarPListValueException(description))
permissions = .denied
}
diff --git a/packages/expo-calendar/plugin/build/withCalendar.d.ts b/packages/expo-calendar/plugin/build/withCalendar.d.ts
index c7903ebf2b1c93..3cd2046980a722 100644
--- a/packages/expo-calendar/plugin/build/withCalendar.d.ts
+++ b/packages/expo-calendar/plugin/build/withCalendar.d.ts
@@ -6,12 +6,27 @@ export type Props = {
* @platform ios
*/
calendarPermission?: string | false;
+ /**
+ * A string to set the `NSCalendarsWriteOnlyAccessUsageDescription` permission message,
+ * shown when requesting write-only calendar access (iOS 17+).
+ * Only used when `writeOnlyAccess` is `true`.
+ * @default "Allow $(PRODUCT_NAME) to add events to your calendars"
+ * @platform ios
+ */
+ writeOnlyCalendarPermission?: string | false;
/**
* A string to set the `NSRemindersUsageDescription` permission message.
* @default "Allow $(PRODUCT_NAME) to access your reminders"
* @platform ios
*/
remindersPermission?: string | false;
+ /**
+ * When `true`, requests write-only calendar access (iOS 17+). Sets
+ * `NSCalendarsWriteOnlyAccessUsageDescription` and omits `NSCalendarsFullAccessUsageDescription`.
+ * @default false
+ * @platform ios
+ */
+ writeOnlyAccess?: boolean;
};
declare const _default: ConfigPlugin;
export default _default;
diff --git a/packages/expo-calendar/plugin/build/withCalendar.js b/packages/expo-calendar/plugin/build/withCalendar.js
index cc58a0b1408cac..6890680472c1fe 100644
--- a/packages/expo-calendar/plugin/build/withCalendar.js
+++ b/packages/expo-calendar/plugin/build/withCalendar.js
@@ -3,19 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
const config_plugins_1 = require("expo/config-plugins");
const pkg = require('../../package.json');
const CALENDARS_USAGE = 'Allow $(PRODUCT_NAME) to access your calendars';
+const CALENDARS_WRITE_ONLY_USAGE = 'Allow $(PRODUCT_NAME) to add events to your calendars';
const REMINDERS_USAGE = 'Allow $(PRODUCT_NAME) to access your reminders';
-const withCalendar = (config, { calendarPermission, remindersPermission } = {}) => {
- config_plugins_1.IOSConfig.Permissions.createPermissionsPlugin({
+const withCalendar = (config, { calendarPermission, writeOnlyCalendarPermission, remindersPermission, writeOnlyAccess } = {}) => {
+ const defaultDescriptions = {
NSCalendarsUsageDescription: CALENDARS_USAGE,
NSRemindersUsageDescription: REMINDERS_USAGE,
- NSCalendarsFullAccessUsageDescription: CALENDARS_USAGE,
NSRemindersFullAccessUsageDescription: REMINDERS_USAGE,
- })(config, {
+ ...(writeOnlyAccess
+ ? { NSCalendarsWriteOnlyAccessUsageDescription: CALENDARS_WRITE_ONLY_USAGE }
+ : {
+ NSCalendarsFullAccessUsageDescription: CALENDARS_USAGE,
+ }),
+ };
+ const customDescriptions = {
NSCalendarsUsageDescription: calendarPermission,
NSRemindersUsageDescription: remindersPermission,
- NSCalendarsFullAccessUsageDescription: calendarPermission,
NSRemindersFullAccessUsageDescription: remindersPermission,
- });
+ ...(writeOnlyAccess
+ ? { NSCalendarsWriteOnlyAccessUsageDescription: writeOnlyCalendarPermission }
+ : {
+ NSCalendarsFullAccessUsageDescription: calendarPermission,
+ }),
+ };
+ config_plugins_1.IOSConfig.Permissions.createPermissionsPlugin(defaultDescriptions)(config, customDescriptions);
return config_plugins_1.AndroidConfig.Permissions.withPermissions(config, [
'android.permission.READ_CALENDAR',
'android.permission.WRITE_CALENDAR',
diff --git a/packages/expo-calendar/plugin/src/withCalendar.ts b/packages/expo-calendar/plugin/src/withCalendar.ts
index 14fefddb25912b..1aecfd88facb02 100644
--- a/packages/expo-calendar/plugin/src/withCalendar.ts
+++ b/packages/expo-calendar/plugin/src/withCalendar.ts
@@ -3,6 +3,7 @@ import { AndroidConfig, ConfigPlugin, IOSConfig, createRunOncePlugin } from 'exp
const pkg = require('../../package.json');
const CALENDARS_USAGE = 'Allow $(PRODUCT_NAME) to access your calendars';
+const CALENDARS_WRITE_ONLY_USAGE = 'Allow $(PRODUCT_NAME) to add events to your calendars';
const REMINDERS_USAGE = 'Allow $(PRODUCT_NAME) to access your reminders';
export type Props = {
@@ -12,30 +13,56 @@ export type Props = {
* @platform ios
*/
calendarPermission?: string | false;
+ /**
+ * A string to set the `NSCalendarsWriteOnlyAccessUsageDescription` permission message,
+ * shown when requesting write-only calendar access (iOS 17+).
+ * Only used when `writeOnlyAccess` is `true`.
+ * @default "Allow $(PRODUCT_NAME) to add events to your calendars"
+ * @platform ios
+ */
+ writeOnlyCalendarPermission?: string | false;
/**
* A string to set the `NSRemindersUsageDescription` permission message.
* @default "Allow $(PRODUCT_NAME) to access your reminders"
* @platform ios
*/
remindersPermission?: string | false;
+ /**
+ * When `true`, requests write-only calendar access (iOS 17+). Sets
+ * `NSCalendarsWriteOnlyAccessUsageDescription` and omits `NSCalendarsFullAccessUsageDescription`.
+ * @default false
+ * @platform ios
+ */
+ writeOnlyAccess?: boolean;
};
const withCalendar: ConfigPlugin = (
config,
- { calendarPermission, remindersPermission } = {}
+ { calendarPermission, writeOnlyCalendarPermission, remindersPermission, writeOnlyAccess } = {}
) => {
- IOSConfig.Permissions.createPermissionsPlugin({
+ const defaultDescriptions = {
NSCalendarsUsageDescription: CALENDARS_USAGE,
NSRemindersUsageDescription: REMINDERS_USAGE,
- NSCalendarsFullAccessUsageDescription: CALENDARS_USAGE,
NSRemindersFullAccessUsageDescription: REMINDERS_USAGE,
- })(config, {
+ ...(writeOnlyAccess
+ ? { NSCalendarsWriteOnlyAccessUsageDescription: CALENDARS_WRITE_ONLY_USAGE }
+ : {
+ NSCalendarsFullAccessUsageDescription: CALENDARS_USAGE,
+ }),
+ };
+
+ const customDescriptions = {
NSCalendarsUsageDescription: calendarPermission,
NSRemindersUsageDescription: remindersPermission,
- NSCalendarsFullAccessUsageDescription: calendarPermission,
NSRemindersFullAccessUsageDescription: remindersPermission,
- });
+ ...(writeOnlyAccess
+ ? { NSCalendarsWriteOnlyAccessUsageDescription: writeOnlyCalendarPermission }
+ : {
+ NSCalendarsFullAccessUsageDescription: calendarPermission,
+ }),
+ };
+ IOSConfig.Permissions.createPermissionsPlugin(defaultDescriptions)(config, customDescriptions);
return AndroidConfig.Permissions.withPermissions(config, [
'android.permission.READ_CALENDAR',
'android.permission.WRITE_CALENDAR',
diff --git a/packages/expo-calendar/src/next/Calendar.ts b/packages/expo-calendar/src/next/Calendar.ts
index a30d2cf15a93a1..db755139ed23e2 100644
--- a/packages/expo-calendar/src/next/Calendar.ts
+++ b/packages/expo-calendar/src/next/Calendar.ts
@@ -10,6 +10,7 @@ import type {
RecurringEventOptions,
Reminder,
ReminderStatus,
+ PermissionResponse,
} from '../Calendar';
import InternalExpoCalendar from './ExpoCalendar';
import { stringifyDateValues, stringifyIfDate, getNullableDetailsFields } from '../utils';
@@ -270,12 +271,16 @@ export async function listEvents(
/**
* Asks the user to grant permissions for accessing user's calendars.
+ * @param writeOnly - On iOS, whether to request write-only access, which allows creating calendar events
+ * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
export const requestCalendarPermissions = InternalExpoCalendar.requestCalendarPermissions;
/**
* Checks user's permissions for accessing user's calendars.
+ * @param writeOnly - On iOS, whether to check write-only access, which allows creating calendar events
+ * without reading existing calendars or events. This does not grant permission to create, update, or delete calendars.
* @return A promise that resolves to an object of type [`PermissionResponse`](#permissionresponse).
*/
export const getCalendarPermissions = InternalExpoCalendar.getCalendarPermissions;
@@ -341,19 +346,25 @@ export {
createEventInCalendarAsync,
openEventInCalendarAsync,
} from '../Calendar';
+
/**
* Check or request permissions to access the user's calendars.
* This uses both `getCalendarPermissions` and `requestCalendarPermissions` to interact
* with the permissions.
+ * On iOS, `writeOnly` requests permission to create calendar events without reading
+ * existing calendars or events. It does not grant permission to create, update, or delete calendars.
*
* @example
* ```ts
* const [status, requestPermission] = Calendar.useCalendarPermissions();
* ```
*/
-export const useCalendarPermissions = createPermissionHook({
- getMethod: getCalendarPermissions,
- requestMethod: requestCalendarPermissions,
+export const useCalendarPermissions = createPermissionHook<
+ PermissionResponse,
+ { writeOnly?: boolean }
+>({
+ getMethod: (options) => getCalendarPermissions(options?.writeOnly),
+ requestMethod: (options) => requestCalendarPermissions(options?.writeOnly),
});
/**
diff --git a/packages/expo-calendar/src/next/ExpoCalendar.ts b/packages/expo-calendar/src/next/ExpoCalendar.ts
index 0e8d1b21934013..ca1d7325ae4a10 100644
--- a/packages/expo-calendar/src/next/ExpoCalendar.ts
+++ b/packages/expo-calendar/src/next/ExpoCalendar.ts
@@ -37,8 +37,8 @@ declare class ExpoCalendarNextModule extends NativeModule {
getEventById(eventId: string): Promise;
getReminderById(reminderId: string): Promise;
- requestCalendarPermissions(): Promise;
- getCalendarPermissions(): Promise;
+ requestCalendarPermissions(writeOnly?: boolean): Promise;
+ getCalendarPermissions(writeOnly?: boolean): Promise;
requestRemindersPermissions(): Promise;
getRemindersPermissions(): Promise;
diff --git a/packages/expo-calendar/src/next/ExpoGoCalendarNextStub.ts b/packages/expo-calendar/src/next/ExpoGoCalendarNextStub.ts
index 3b43abb7dfee4b..8b744f569513fe 100644
--- a/packages/expo-calendar/src/next/ExpoGoCalendarNextStub.ts
+++ b/packages/expo-calendar/src/next/ExpoGoCalendarNextStub.ts
@@ -52,11 +52,11 @@ class ExpoGoCalendarNextStub {
throw new Error('Calendar@next functionality is not available in Expo Go');
}
- async requestCalendarPermissions(): Promise {
+ async requestCalendarPermissions(writeOnly?: boolean): Promise {
throw new Error('Calendar@next functionality is not available in Expo Go');
}
- async getCalendarPermissions(): Promise {
+ async getCalendarPermissions(writeOnly?: boolean): Promise {
throw new Error('Calendar@next functionality is not available in Expo Go');
}
diff --git a/packages/expo-linear-gradient/CHANGELOG.md b/packages/expo-linear-gradient/CHANGELOG.md
index 0eaec30d296b23..53012b630cb13b 100644
--- a/packages/expo-linear-gradient/CHANGELOG.md
+++ b/packages/expo-linear-gradient/CHANGELOG.md
@@ -6,6 +6,8 @@
### 🎉 New features
+- Add macOS support ([#45448](https://github.com/expo/expo/pull/45448) by [@gabrieldonadel](https://github.com/gabrieldonadel))
+
### 🐛 Bug fixes
### 💡 Others
diff --git a/packages/expo-linear-gradient/ios/ExpoLinearGradient.podspec b/packages/expo-linear-gradient/ios/ExpoLinearGradient.podspec
index 5de52a56ced6e0..967b9deb8ad542 100644
--- a/packages/expo-linear-gradient/ios/ExpoLinearGradient.podspec
+++ b/packages/expo-linear-gradient/ios/ExpoLinearGradient.podspec
@@ -12,7 +12,8 @@ Pod::Spec.new do |s|
s.homepage = package['homepage']
s.platforms = {
:ios => '16.4',
- :tvos => '16.4'
+ :tvos => '16.4',
+ :osx => '13.4'
}
s.swift_version = '5.9'
s.source = { git: 'https://github.com/expo/expo.git' }
diff --git a/packages/expo-linear-gradient/ios/LinearGradientLayer.swift b/packages/expo-linear-gradient/ios/LinearGradientLayer.swift
index fec7037c8790ec..a86d42d8f71dd8 100644
--- a/packages/expo-linear-gradient/ios/LinearGradientLayer.swift
+++ b/packages/expo-linear-gradient/ios/LinearGradientLayer.swift
@@ -2,6 +2,7 @@
import QuartzCore
import CoreGraphics
+import ExpoModulesCore
var defaultStartPoint = CGPoint(x: 0.5, y: 0.0)
var defaultEndPoint = CGPoint(x: 0.5, y: 1.0)
@@ -59,6 +60,15 @@ final class LinearGradientLayer: CALayer {
return result || color.cgColor.alpha < 1.0
}
+ #if os(macOS)
+ setLayerContentsMacOS(hasAlpha: hasAlpha)
+ #else
+ setLayerContents(hasAlpha: hasAlpha)
+ #endif
+ }
+
+ #if !os(macOS)
+ private func setLayerContents(hasAlpha: Bool) {
UIGraphicsBeginImageContextWithOptions(bounds.size, !hasAlpha, 0.0)
guard let contextRef = UIGraphicsGetCurrentContext() else {
@@ -76,6 +86,35 @@ final class LinearGradientLayer: CALayer {
UIGraphicsEndImageContext()
}
+ #else
+ private func setLayerContentsMacOS(hasAlpha: Bool) {
+ let scale = contentsScale > 0 ? contentsScale : (NSScreen.main?.backingScaleFactor ?? 2.0)
+ let pixelWidth = Int(bounds.size.width * scale)
+ let pixelHeight = Int(bounds.size.height * scale)
+ if pixelWidth == 0 || pixelHeight == 0 {
+ return
+ }
+ let colorSpace = CGColorSpaceCreateDeviceRGB()
+ let bitmapInfo: UInt32 = hasAlpha
+ ? CGImageAlphaInfo.premultipliedLast.rawValue
+ : CGImageAlphaInfo.noneSkipLast.rawValue
+ guard let ctx = CGContext(
+ data: nil,
+ width: pixelWidth,
+ height: pixelHeight,
+ bitsPerComponent: 8,
+ bytesPerRow: 0,
+ space: colorSpace,
+ bitmapInfo: bitmapInfo
+ ) else {
+ return
+ }
+ ctx.scaleBy(x: scale, y: scale)
+ draw(in: ctx)
+ self.contents = ctx.makeImage()
+ self.contentsScale = scale
+ }
+ #endif
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
diff --git a/packages/expo-linear-gradient/ios/LinearGradientView.swift b/packages/expo-linear-gradient/ios/LinearGradientView.swift
index 9abde4f32e4429..6ef1951cb3da59 100644
--- a/packages/expo-linear-gradient/ios/LinearGradientView.swift
+++ b/packages/expo-linear-gradient/ios/LinearGradientView.swift
@@ -1,19 +1,34 @@
// Copyright 2021-present 650 Industries. All rights reserved.
+#if !os(macOS)
import UIKit
+#endif
import ExpoModulesCore
final class LinearGradientView: ExpoView {
+ #if !os(macOS)
override class var layerClass: AnyClass {
return LinearGradientLayer.self
}
+ #else
+ override func makeBackingLayer() -> CALayer {
+ return LinearGradientLayer()
+ }
+ #endif
public var gradientLayer: LinearGradientLayer {
return layer as! LinearGradientLayer
}
+ #if !os(macOS)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
layer.setNeedsDisplay()
}
+ #else
+ override func viewDidChangeEffectiveAppearance() {
+ super.viewDidChangeEffectiveAppearance()
+ layer?.setNeedsDisplay()
+ }
+ #endif
}
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverter.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverter.kt
index 94bf346cb70e03..d45125a6f551d5 100644
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverter.kt
+++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverter.kt
@@ -3,7 +3,6 @@ package expo.modules.kotlin.types
import android.net.Uri
import android.os.Bundle
import expo.modules.kotlin.jni.ReturnType
-import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.records.formatters.FormattedRecord
import expo.modules.kotlin.typedarray.RawTypedArrayHolder
@@ -121,7 +120,7 @@ interface JSTypeConverter {
object RecordConverter : JSTypeConverter {
override fun convertToJS(value: Any?): Any? {
enforceType(value)
- return value?.toJSValueExperimental()
+ return value?.toJSValueExperimental(null)
}
override val returnType: ReturnType
@@ -129,49 +128,11 @@ interface JSTypeConverter {
}
class IntrospectableRecordConverter(
- introspectableData: PIntrospectionData
+ private val introspectableData: PIntrospectionData
) : JSTypeConverter {
- data class Property(
- val key: String,
- val getter: (Record) -> Any?
- )
-
- private val properties = introspectableData
- .properties
- .mapNotNull { property ->
- val fieldAnnotation = property
- .annotations
- .firstOrNull { annotation -> annotation.jClass == Field::class.java }
- ?: return@mapNotNull null
-
- val propertyName = (
- fieldAnnotation
- .arguments
- .getOrDefault("key", property.name) as String
- )
- .ifEmpty { property.name }
-
- Property(
- propertyName,
- property::get
- )
- }
-
override fun convertToJS(value: Any?): Any? {
- if (value == null) {
- return null
- }
-
- enforceType(value)
- val result = mutableMapOf()
-
- properties.forEach { property ->
- val propValue = property.getter(value)
- val convertedValue = JSTypeConverterProvider.convertToJSValue(propValue, useExperimentalConverter = true)
- result[property.key] = convertedValue
- }
-
- return result
+ enforceType(value)
+ return value?.toJSValueExperimental(introspectableData)
}
override val returnType: ReturnType
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverterHelper.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverterHelper.kt
index e98ca88c66e6e2..a964224f7bb1f1 100644
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverterHelper.kt
+++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/types/JSTypeConverterHelper.kt
@@ -10,6 +10,9 @@ import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.records.formatters.FormattedRecord
import expo.modules.kotlin.records.formatters.ValueOrSkip
+import io.github.lukmccall.pika.PIntrospectionData
+import io.github.lukmccall.pika.PIntrospectionProvider
+import io.github.lukmccall.pika.PProperty
import java.io.File
import java.net.URI
import java.net.URL
@@ -20,22 +23,63 @@ import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible
-fun Record.toJSValueExperimental(): Map {
+/**
+ * Returns field key if property is annotated with [Field]
+ */
+private fun PProperty.asFieldKey(): String? {
+ val fieldAnnotation = annotations
+ .firstOrNull { annotation -> annotation.jClass == Field::class.java }
+ ?: return null
+
+ val propertyName = (
+ fieldAnnotation
+ .arguments
+ .getOrDefault("key", name) as String
+ )
+ .ifEmpty { name }
+
+ return propertyName
+}
+
+private fun Record.getIntrospectionData(): PIntrospectionData? {
+ return if (this is PIntrospectionProvider) {
+ @Suppress("UNCHECKED_CAST")
+ getIntrospectionData() as? PIntrospectionData
+ } else {
+ null
+ }
+}
+
+fun Record.toJSValueExperimental(
+ introspectionData: PIntrospectionData? = this.getIntrospectionData()
+): Map {
val result = mutableMapOf()
- javaClass
- .kotlin
- .memberProperties
- .forEach { property ->
- val fieldInformation = property.findAnnotation() ?: return@forEach
- val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
+ if (introspectionData == null) {
+ javaClass
+ .kotlin
+ .memberProperties
+ .forEach { property ->
+ val fieldInformation = property.findAnnotation() ?: return@forEach
+ val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
- property.isAccessible = true
+ property.isAccessible = true
- val value = property.get(this)
- val convertedValue = JSTypeConverterProvider.convertToJSValue(value, useExperimentalConverter = true)
- result[jsKey] = convertedValue
- }
+ val value = property.get(this)
+ val convertedValue = JSTypeConverterProvider.convertToJSValue(value, useExperimentalConverter = true)
+ result[jsKey] = convertedValue
+ }
+ } else {
+ introspectionData
+ .properties
+ .forEach { property ->
+ val propertyKey = property.asFieldKey() ?: return@forEach
+
+ val propValue = property.get(this)
+ val convertedValue = JSTypeConverterProvider.convertToJSValue(propValue, useExperimentalConverter = true)
+ result[propertyKey] = convertedValue
+ }
+ }
return result
}
@@ -74,21 +118,37 @@ fun FormattedRecord<*>.toJSValueExperimental(): Map {
return result
}
-fun Record.toJSValue(containerProvider: JSTypeConverterProvider.ContainerProvider): WritableMap {
+fun Record.toJSValue(
+ containerProvider: JSTypeConverterProvider.ContainerProvider,
+ introspectionData: PIntrospectionData? = this.getIntrospectionData()
+): WritableMap {
val result = containerProvider.createMap()
- javaClass
- .kotlin
- .memberProperties.map { property ->
- val fieldInformation = property.findAnnotation() ?: return@map
- val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
+ if (introspectionData == null) {
+ javaClass
+ .kotlin
+ .memberProperties
+ .forEach { property ->
+ val fieldInformation = property.findAnnotation() ?: return@forEach
+ val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
- property.isAccessible = true
+ property.isAccessible = true
- val value = property.get(this)
- val convertedValue = JSTypeConverterProvider.legacyConvertToJSValue(value, containerProvider)
- result.putGeneric(jsKey, convertedValue)
- }
+ val value = property.get(this)
+ val convertedValue = JSTypeConverterProvider.legacyConvertToJSValue(value, containerProvider)
+ result.putGeneric(jsKey, convertedValue)
+ }
+ } else {
+ introspectionData
+ .properties
+ .forEach { property ->
+ val propertyKey = property.asFieldKey() ?: return@forEach
+
+ val value = property.get(this)
+ val convertedValue = JSTypeConverterProvider.legacyConvertToJSValue(value, containerProvider)
+ result.putGeneric(propertyKey, convertedValue)
+ }
+ }
return result
}
diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md
index 1d6c542a41caeb..727fabd3aea34f 100644
--- a/packages/expo-router/CHANGELOG.md
+++ b/packages/expo-router/CHANGELOG.md
@@ -10,6 +10,8 @@
### 💡 Others
+- Move `@jest/globals` to `devDependencies` ([#45469](https://github.com/expo/expo/pull/45469) by [@kitten](https://github.com/kitten))
+
## 56.0.4 — 2026-05-06
_This version does not introduce any user-facing changes._
diff --git a/packages/expo-router/package.json b/packages/expo-router/package.json
index 78ce4cd2875ec0..23a9d5510912cd 100644
--- a/packages/expo-router/package.json
+++ b/packages/expo-router/package.json
@@ -125,6 +125,7 @@
}
},
"devDependencies": {
+ "@jest/globals": "^29.7.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.3.0",
"@testing-library/react-native": "^13.3.0",
@@ -151,7 +152,6 @@
"@expo/metro-runtime": "workspace:^56.0.4",
"@expo/schema-utils": "workspace:^56.0.0",
"@expo/ui": "workspace:^56.0.1",
- "@jest/globals": "^29.7.0",
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-tabs": "^1.1.12",
"@react-native-masked-view/masked-view": "^0.3.2",
diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md
index 16fd16628704aa..e44dde28701c34 100644
--- a/packages/expo-ui/CHANGELOG.md
+++ b/packages/expo-ui/CHANGELOG.md
@@ -39,6 +39,7 @@ _This version does not introduce any user-facing changes._
- [android] Add `colors` prop to `HorizontalFloatingToolbar` to override the variant's default toolbar and FAB container/content colors. ([#45244](https://github.com/expo/expo/pull/45244) by [@Ubax](https://github.com/Ubax))
- [android] Added `HorizontalPager` component wrapping Compose's `HorizontalPager`. ([#45163](https://github.com/expo/expo/pull/45163) by [@vonovak](https://github.com/vonovak))
- [compose] Added worklet and `ObservableState` support to `TextField`. Added `value` prop accepting `ObservableState` (create via `useNativeState`). `onValueChange` now supports worklets for synchronous UI-thread updates. Added `TextFieldValue` type with `text` + `selection` for worklet-driven caret control. Replaced `defaultValue`, callers pass state via `useNativeState` or omit for an empty field. ([#45024](https://github.com/expo/expo/pull/45024) by [@nishan](https://github.com/intergalacticspacehighway))
+- Added `@expo/ui/community/masked-view` — a drop-in replacement for `@react-native-masked-view/masked-view`. ([#45488](https://github.com/expo/expo/pull/45488) by [@vonovak](https://github.com/vonovak))
- [android] Add `WorkletCallback` shared object for synchronous UI thread callbacks. ([#44681](https://github.com/expo/expo/pull/44681) by [@nishan](https://github.com/intergalacticspacehighway))
- [android] Add `ObservableState` shared object and `useNativeState` hook for controlling native Compose state from JS. ([#44655](https://github.com/expo/expo/pull/44655) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
- [iOS] Added `Mask` component wrapping SwiftUI's `.mask(alignment:_:)` modifier, with a `Mask.Content` slot for the mask element. ([#44934](https://github.com/expo/expo/pull/44934) by [@vonovak](https://github.com/vonovak))
diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/ExpoUIModule.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/ExpoUIModule.kt
index 2e1b367ca4b1c0..ae88b6a457a6cf 100644
--- a/packages/expo-ui/android/src/main/java/expo/modules/ui/ExpoUIModule.kt
+++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/ExpoUIModule.kt
@@ -672,6 +672,12 @@ class ExpoUIModule : Module() {
}
}
+ ExpoUIView("MaskView") {
+ Content { props ->
+ MaskViewContent(props)
+ }
+ }
+
//endregion Expo UI views
}
}
diff --git a/packages/expo-ui/android/src/main/java/expo/modules/ui/MaskView.kt b/packages/expo-ui/android/src/main/java/expo/modules/ui/MaskView.kt
new file mode 100644
index 00000000000000..5044f861d1ff4d
--- /dev/null
+++ b/packages/expo-ui/android/src/main/java/expo/modules/ui/MaskView.kt
@@ -0,0 +1,56 @@
+package expo.modules.ui
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.CompositingStrategy
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.graphics.rememberGraphicsLayer
+import expo.modules.kotlin.views.ComposeProps
+import expo.modules.kotlin.views.FunctionalComposableScope
+import expo.modules.kotlin.views.OptimizedComposeProps
+import expo.modules.ui.convertibles.ContentAlignment
+
+@OptimizedComposeProps
+data class MaskViewProps(
+ val alignment: ContentAlignment = ContentAlignment.CENTER,
+ val modifiers: ModifierList = emptyList()
+) : ComposeProps
+
+@Composable
+fun FunctionalComposableScope.MaskViewContent(props: MaskViewProps) {
+ val maskSlotView = findChildSlotView(view, "content")
+ val maskLayer = rememberGraphicsLayer().apply { blendMode = BlendMode.DstIn }
+
+ Box(
+ modifier = ModifierRegistry
+ .applyModifiers(props.modifiers, appContext, composableScope, globalEventDispatcher)
+ .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
+ .drawWithContent {
+ drawContent()
+ drawLayer(maskLayer)
+ }
+ ) {
+ Children(UIComposableScope(), filter = { !isSlotView(it) })
+
+ if (maskSlotView != null) {
+ Box(
+ modifier = Modifier
+ .matchParentSize()
+ .drawWithContent {
+ maskLayer.record { this@drawWithContent.drawContent() }
+ },
+ contentAlignment = props.alignment.toComposeAlignment()
+ ) {
+ with(UIComposableScope()) {
+ with(maskSlotView) {
+ Content()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/expo-ui/build/community/masked-view/MaskedView.android.d.ts b/packages/expo-ui/build/community/masked-view/MaskedView.android.d.ts
new file mode 100644
index 00000000000000..28786a4008b6d3
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/MaskedView.android.d.ts
@@ -0,0 +1,8 @@
+import type { MaskedViewProps } from './types';
+/**
+ * Android implementation of `MaskedView`. Bridges arbitrary React Native children
+ * (and `maskElement`) into the Compose `MaskView` primitive via `RNHostView`.
+ */
+export declare function MaskedView(props: MaskedViewProps): import("react/jsx-runtime").JSX.Element;
+export default MaskedView;
+//# sourceMappingURL=MaskedView.android.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/MaskedView.android.d.ts.map b/packages/expo-ui/build/community/masked-view/MaskedView.android.d.ts.map
new file mode 100644
index 00000000000000..9f6721012af02c
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/MaskedView.android.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"MaskedView.android.d.ts","sourceRoot":"","sources":["../../../src/community/masked-view/MaskedView.android.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,2CAkBhD;AAED,eAAe,UAAU,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/MaskedView.d.ts b/packages/expo-ui/build/community/masked-view/MaskedView.d.ts
new file mode 100644
index 00000000000000..edac8045d4db16
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/MaskedView.d.ts
@@ -0,0 +1,10 @@
+import type { MaskedViewProps } from './types';
+/**
+ * Renders `children` with the alpha channel of `maskElement` applied as a mask:
+ * opaque pixels of `maskElement` reveal `children`, transparent pixels hide them.
+ *
+ * API-compatible with `@react-native-masked-view/masked-view`.
+ */
+export declare function MaskedView(props: MaskedViewProps): import("react/jsx-runtime").JSX.Element;
+export default MaskedView;
+//# sourceMappingURL=MaskedView.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/MaskedView.d.ts.map b/packages/expo-ui/build/community/masked-view/MaskedView.d.ts.map
new file mode 100644
index 00000000000000..63d099e448c64c
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/MaskedView.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"MaskedView.d.ts","sourceRoot":"","sources":["../../../src/community/masked-view/MaskedView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C;;;;;GAKG;AAGH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,2CAgBhD;AAED,eAAe,UAAU,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/MaskedView.ios.d.ts b/packages/expo-ui/build/community/masked-view/MaskedView.ios.d.ts
new file mode 100644
index 00000000000000..566515c8ca15c6
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/MaskedView.ios.d.ts
@@ -0,0 +1,8 @@
+import type { MaskedViewProps } from './types';
+/**
+ * iOS implementation of `MaskedView`. Bridges arbitrary React Native children
+ * (and `maskElement`) into the SwiftUI `Mask` primitive via `RNHostView`.
+ */
+export declare function MaskedView(props: MaskedViewProps): import("react/jsx-runtime").JSX.Element;
+export default MaskedView;
+//# sourceMappingURL=MaskedView.ios.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/MaskedView.ios.d.ts.map b/packages/expo-ui/build/community/masked-view/MaskedView.ios.d.ts.map
new file mode 100644
index 00000000000000..58c924e2c0540f
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/MaskedView.ios.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"MaskedView.ios.d.ts","sourceRoot":"","sources":["../../../src/community/masked-view/MaskedView.ios.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK/C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,2CAkBhD;AAED,eAAe,UAAU,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/index.d.ts b/packages/expo-ui/build/community/masked-view/index.d.ts
new file mode 100644
index 00000000000000..1c7e35c2d9fb08
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/index.d.ts
@@ -0,0 +1,4 @@
+export { MaskedView } from './MaskedView';
+export { MaskedView as default } from './MaskedView';
+export type { MaskedViewProps } from './types';
+//# sourceMappingURL=index.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/index.d.ts.map b/packages/expo-ui/build/community/masked-view/index.d.ts.map
new file mode 100644
index 00000000000000..3a2461f780069d
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/index.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/community/masked-view/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/types.d.ts b/packages/expo-ui/build/community/masked-view/types.d.ts
new file mode 100644
index 00000000000000..71404a8ec9e12f
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/types.d.ts
@@ -0,0 +1,19 @@
+import type { ReactElement, ReactNode } from 'react';
+import type { ViewProps } from 'react-native';
+/**
+ * Drop-in props for `@react-native-masked-view/masked-view`'s `MaskedView`.
+ *
+ * @see https://github.com/callstack/masked-view
+ */
+export interface MaskedViewProps extends ViewProps {
+ /**
+ * The element used as the mask. Only opaque pixels of `maskElement` make the
+ * masked content visible — transparent pixels hide it.
+ */
+ maskElement: ReactElement;
+ /**
+ * Content rendered behind the mask.
+ */
+ children?: ReactNode;
+}
+//# sourceMappingURL=types.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-ui/build/community/masked-view/types.d.ts.map b/packages/expo-ui/build/community/masked-view/types.d.ts.map
new file mode 100644
index 00000000000000..33647ff593da38
--- /dev/null
+++ b/packages/expo-ui/build/community/masked-view/types.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/community/masked-view/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD;;;OAGG;IACH,WAAW,EAAE,YAAY,CAAC;IAC1B;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB"}
\ No newline at end of file
diff --git a/packages/expo-ui/build/jetpack-compose/modifiers/index.d.ts b/packages/expo-ui/build/jetpack-compose/modifiers/index.d.ts
index 94cf8f79c4e78e..f743133d010763 100644
--- a/packages/expo-ui/build/jetpack-compose/modifiers/index.d.ts
+++ b/packages/expo-ui/build/jetpack-compose/modifiers/index.d.ts
@@ -82,7 +82,7 @@ export declare const imePadding: () => import("./createModifier").ModifierConfig
export declare const offset: (x: number, y: number) => import("./createModifier").ModifierConfig;
/**
* Sets the background color.
- * @param color - Color string (hex, e.g., '#FF0000').
+ * @param color - A color string (hex, e.g., `'#FF0000'`).
*/
export declare const background: (color: ColorValue) => import("./createModifier").ModifierConfig;
/**
diff --git a/packages/expo-ui/package.json b/packages/expo-ui/package.json
index 8f551e0e4574c5..dfa4eee7b084fc 100644
--- a/packages/expo-ui/package.json
+++ b/packages/expo-ui/package.json
@@ -43,6 +43,10 @@
"types": "./build/community/picker/index.d.ts",
"default": "./src/community/picker/index.tsx"
},
+ "./community/masked-view": {
+ "types": "./build/community/masked-view/index.d.ts",
+ "default": "./src/community/masked-view/index.tsx"
+ },
"./babel-plugin": {
"types": "./plugin/babel-plugin.d.ts",
"default": "./plugin/babel-plugin.js"
diff --git a/packages/expo-ui/src/community/masked-view/MaskedView.android.tsx b/packages/expo-ui/src/community/masked-view/MaskedView.android.tsx
new file mode 100644
index 00000000000000..dae15ebeec0f82
--- /dev/null
+++ b/packages/expo-ui/src/community/masked-view/MaskedView.android.tsx
@@ -0,0 +1,40 @@
+import { requireNativeView } from 'expo';
+import { StyleSheet, View } from 'react-native';
+
+import type { MaskedViewProps } from './types';
+import { Host } from '../../jetpack-compose/Host';
+import { RNHostView } from '../../jetpack-compose/RNHostView';
+import { Slot } from '../../jetpack-compose/SlotView';
+import { fillMaxSize } from '../../jetpack-compose/modifiers';
+
+const MaskNativeView: React.ComponentType<{
+ alignment?: 'topStart';
+ modifiers?: ReturnType[];
+ children?: React.ReactNode;
+}> = requireNativeView('ExpoUI', 'MaskView');
+
+/**
+ * Android implementation of `MaskedView`. Bridges arbitrary React Native children
+ * (and `maskElement`) into the Compose `MaskView` primitive via `RNHostView`.
+ */
+export function MaskedView(props: MaskedViewProps) {
+ const { maskElement, children, style, ...viewProps } = props;
+ return (
+
+
+
+
+ {children}
+
+
+
+ {maskElement}
+
+
+
+
+
+ );
+}
+
+export default MaskedView;
diff --git a/packages/expo-ui/src/community/masked-view/MaskedView.ios.tsx b/packages/expo-ui/src/community/masked-view/MaskedView.ios.tsx
new file mode 100644
index 00000000000000..be346b648f5bd7
--- /dev/null
+++ b/packages/expo-ui/src/community/masked-view/MaskedView.ios.tsx
@@ -0,0 +1,32 @@
+import { StyleSheet, View } from 'react-native';
+
+import type { MaskedViewProps } from './types';
+import { Host } from '../../swift-ui/Host';
+import { Mask } from '../../swift-ui/Mask';
+import { RNHostView } from '../../swift-ui/RNHostView';
+
+/**
+ * iOS implementation of `MaskedView`. Bridges arbitrary React Native children
+ * (and `maskElement`) into the SwiftUI `Mask` primitive via `RNHostView`.
+ */
+export function MaskedView(props: MaskedViewProps) {
+ const { maskElement, children, style, ...viewProps } = props;
+ return (
+
+
+
+
+ {children}
+
+
+
+ {maskElement}
+
+
+
+
+
+ );
+}
+
+export default MaskedView;
diff --git a/packages/expo-ui/src/community/masked-view/MaskedView.tsx b/packages/expo-ui/src/community/masked-view/MaskedView.tsx
new file mode 100644
index 00000000000000..95202b645c5e3f
--- /dev/null
+++ b/packages/expo-ui/src/community/masked-view/MaskedView.tsx
@@ -0,0 +1,34 @@
+import { useEffect } from 'react';
+import { View } from 'react-native';
+
+import type { MaskedViewProps } from './types';
+
+let warned = false;
+
+/**
+ * Renders `children` with the alpha channel of `maskElement` applied as a mask:
+ * opaque pixels of `maskElement` reveal `children`, transparent pixels hide them.
+ *
+ * API-compatible with `@react-native-masked-view/masked-view`.
+ */
+// This default file is used on platforms without a `..tsx` override
+// (notably web). It renders children unmasked and warns once.
+export function MaskedView(props: MaskedViewProps) {
+ const { maskElement: _maskElement, children, style, ...rest } = props;
+ useEffect(() => {
+ if (!warned) {
+ warned = true;
+ console.warn(
+ '[@expo/ui/community/masked-view] MaskedView is not implemented on this platform. ' +
+ 'Children will render without a mask.'
+ );
+ }
+ }, []);
+ return (
+
+ {children}
+
+ );
+}
+
+export default MaskedView;
diff --git a/packages/expo-ui/src/community/masked-view/index.tsx b/packages/expo-ui/src/community/masked-view/index.tsx
new file mode 100644
index 00000000000000..40a970bce0d52f
--- /dev/null
+++ b/packages/expo-ui/src/community/masked-view/index.tsx
@@ -0,0 +1,3 @@
+export { MaskedView } from './MaskedView';
+export { MaskedView as default } from './MaskedView';
+export type { MaskedViewProps } from './types';
diff --git a/packages/expo-ui/src/community/masked-view/types.ts b/packages/expo-ui/src/community/masked-view/types.ts
new file mode 100644
index 00000000000000..f6373f6565e6db
--- /dev/null
+++ b/packages/expo-ui/src/community/masked-view/types.ts
@@ -0,0 +1,19 @@
+import type { ReactElement, ReactNode } from 'react';
+import type { ViewProps } from 'react-native';
+
+/**
+ * Drop-in props for `@react-native-masked-view/masked-view`'s `MaskedView`.
+ *
+ * @see https://github.com/callstack/masked-view
+ */
+export interface MaskedViewProps extends ViewProps {
+ /**
+ * The element used as the mask. Only opaque pixels of `maskElement` make the
+ * masked content visible — transparent pixels hide it.
+ */
+ maskElement: ReactElement;
+ /**
+ * Content rendered behind the mask.
+ */
+ children?: ReactNode;
+}
diff --git a/packages/expo-ui/src/jetpack-compose/modifiers/index.ts b/packages/expo-ui/src/jetpack-compose/modifiers/index.ts
index 0710c5f3098bcc..02408810c1ab93 100644
--- a/packages/expo-ui/src/jetpack-compose/modifiers/index.ts
+++ b/packages/expo-ui/src/jetpack-compose/modifiers/index.ts
@@ -142,7 +142,7 @@ export const offset = (x: number, y: number) => createModifier('offset', { x, y
/**
* Sets the background color.
- * @param color - Color string (hex, e.g., '#FF0000').
+ * @param color - A color string (hex, e.g., `'#FF0000'`).
*/
export const background = (color: ColorValue) => createModifier('background', { color });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d4884ebaae2499..a4141f5a3f8dc6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1837,8 +1837,8 @@ importers:
specifier: ^1.0.1
version: 1.0.6
'@expo/xcpretty':
- specifier: ^4.4.0
- version: 4.4.1
+ specifier: ^4.4.4
+ version: 4.4.4
'@react-native/dev-middleware':
specifier: 0.85.3
version: 0.85.3(patch_hash=563dd3c12ac9064f72bebd975abb206061af92d430d10341a2fce54810b5128f)
@@ -5068,9 +5068,6 @@ importers:
'@expo/ui':
specifier: workspace:^56.0.1
version: link:../expo-ui
- '@jest/globals':
- specifier: ^29.7.0
- version: 29.7.0
'@radix-ui/react-slot':
specifier: ^1.2.0
version: 1.2.4(@types/react@19.2.14)(react@19.2.3)
@@ -5162,6 +5159,9 @@ importers:
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
devDependencies:
+ '@jest/globals':
+ specifier: ^29.7.0
+ version: 29.7.0
'@testing-library/dom':
specifier: ^10.4.0
version: 10.4.1
@@ -6131,8 +6131,8 @@ importers:
specifier: ^0.57.1
version: 0.57.1
'@expo/xcpretty':
- specifier: ^4.4.0
- version: 4.4.1
+ specifier: ^4.4.4
+ version: 4.4.4
'@linear/sdk':
specifier: ^2.6.0
version: 2.6.0(encoding@0.1.13)
@@ -7577,8 +7577,8 @@ packages:
'@expo/ws-tunnel@1.0.6':
resolution: {integrity: sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==}
- '@expo/xcpretty@4.4.1':
- resolution: {integrity: sha512-KZNxZvnGCtiM2aYYZ6Wz0Ix5r47dAvpNLApFtZWnSoERzAdOMzVBOPysBoM0JlF6FKWZ8GPqgn6qt3dV/8Zlpg==}
+ '@expo/xcpretty@4.4.4':
+ resolution: {integrity: sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==}
hasBin: true
'@firebase/analytics-compat@0.2.6':
@@ -17429,7 +17429,7 @@ snapshots:
'@expo/ws-tunnel@1.0.6': {}
- '@expo/xcpretty@4.4.1':
+ '@expo/xcpretty@4.4.4':
dependencies:
'@babel/code-frame': 7.29.0
chalk: 4.1.2
diff --git a/tools/package.json b/tools/package.json
index 5011e4f52d5ad1..d8cb0b5964dfdc 100644
--- a/tools/package.json
+++ b/tools/package.json
@@ -35,7 +35,7 @@
"@expo/multipart-body-parser": "^1.1.0",
"@expo/osascript": "workspace:*",
"@expo/plist": "workspace:*",
- "@expo/xcpretty": "^4.4.0",
+ "@expo/xcpretty": "^4.4.4",
"@expo/spawn-async": "^1.7.2",
"@expo/swiftlint": "^0.57.1",
"@linear/sdk": "^2.6.0",
diff --git a/tools/src/Logger.ts b/tools/src/Logger.ts
index 4c63b52bb283b8..db3eb53a9591ef 100644
--- a/tools/src/Logger.ts
+++ b/tools/src/Logger.ts
@@ -9,13 +9,35 @@ const CONSOLE_RESOLVER: LoggerResolver = (level: LogLevel, color: Chalk | null,
return console[level](...(color ? args.map((arg) => color(arg)) : args));
};
+// Process-wide verbose flag. All Logger instances (including LoggerBatch)
+// share this — verbosity is a property of the run, not of a logger object.
+let _verbose = false;
+
/**
* Basic logger just for simple console logging with colored output.
*/
export class Logger {
constructor(readonly resolver: LoggerResolver = CONSOLE_RESOLVER) {}
+ /**
+ * Enables or disables verbose logging globally. When `false` (default),
+ * `logger.verbose(...)` calls are silently dropped.
+ */
+ setVerbose(value: boolean): void {
+ _verbose = value;
+ }
+
+ isVerbose(): boolean {
+ return _verbose;
+ }
+
+ /**
+ * Emits a low-priority detail line. No-op unless `setVerbose(true)` has
+ * been called — used for progress chatter that buries signal in CI logs
+ * (per-step "Cleaning ...", "Generating ...", subprocess line streams, etc).
+ */
verbose(...args: any[]): void {
+ if (!_verbose) return;
this.resolver('debug', chalk.dim, args);
}
diff --git a/tools/src/Packages.test.ts b/tools/src/Packages.test.ts
index d8138b841b806e..2b8025339ca61a 100644
--- a/tools/src/Packages.test.ts
+++ b/tools/src/Packages.test.ts
@@ -27,4 +27,9 @@ describe('getPackageByName', () => {
it('returns null for an unknown package name', () => {
assert.equal(getPackageByName('definitely-not-a-real-package'), null);
});
+
+ it('returns null for third-party scoped packages installed only under node_modules', () => {
+ // @babel/core is reachable via the node_modules walk-up but is not a workspace package.
+ assert.equal(getPackageByName('@babel/core'), null);
+ });
});
diff --git a/tools/src/Packages.ts b/tools/src/Packages.ts
index 4b54f982a9bf1f..007d31e1d104b1 100644
--- a/tools/src/Packages.ts
+++ b/tools/src/Packages.ts
@@ -487,7 +487,13 @@ function readExpoModuleConfigJson(expoModuleConfigJsonPath: string) {
function pathToLocalPackageJson(packageName: string): string {
if (packageName.startsWith('@')) {
try {
- return require.resolve(`${packageName}/package.json`, { paths: [PACKAGES_DIR] });
+ const resolved = require.resolve(`${packageName}/package.json`, { paths: [PACKAGES_DIR] });
+ // require.resolve walks up node_modules/. Reject realpaths outside PACKAGES_DIR
+ // so third-party scoped installs (e.g. @babel/core) fall through to the cache lookup.
+ const rel = path.relative(PACKAGES_DIR, fs.realpathSync(resolved));
+ if (!rel.startsWith('..') && !path.isAbsolute(rel)) {
+ return resolved;
+ }
} catch {
// Fall through — caller's cachedPackages fallback still applies.
}
diff --git a/tools/src/commands/BumpReactNativeVersion.ts b/tools/src/commands/BumpReactNativeVersion.ts
index b00411e2e2e3c7..a260b8dcbcb50e 100644
--- a/tools/src/commands/BumpReactNativeVersion.ts
+++ b/tools/src/commands/BumpReactNativeVersion.ts
@@ -14,6 +14,7 @@ const APPS_DIR = path.join(EXPO_DIR, 'apps');
const PACKAGES_DIR = path.join(EXPO_DIR, 'packages');
const TEMPLATES_DIR = path.join(EXPO_DIR, 'templates');
+const ROOT_PACKAGE_JSON_PATH = path.join(EXPO_DIR, 'package.json');
const BUNDLED_NATIVE_MODULES_PATH = path.join(EXPO_DIR, 'packages/expo/bundledNativeModules.json');
const REACT_NATIVE_PACKAGE = 'react-native';
@@ -48,6 +49,10 @@ async function main(options: { version?: string }) {
}
}
+ if (await updateRootResolutions(newVersion)) {
+ totalUpdated++;
+ }
+
await updateBundledNativeModules(newVersion);
logger.success(`\nUpdated ${totalUpdated} package.json files and bundledNativeModules.json.\n`);
@@ -83,8 +88,11 @@ async function findAllPackageJsonPaths(): Promise {
'**/__fixtures__/**',
];
+ // Expo Go Vendored third-party modules shouldn't have their react-native versions bumped
+ const appsIgnore = [...ignore, 'expo-go/modules/**'];
+
const [appPaths, packagePaths, templatePaths] = await Promise.all([
- glob('**/package.json', { cwd: APPS_DIR, ignore }),
+ glob('**/package.json', { cwd: APPS_DIR, ignore: appsIgnore }),
glob('**/package.json', { cwd: PACKAGES_DIR, ignore }),
glob('*/package.json', { cwd: TEMPLATES_DIR, ignore }),
]);
@@ -136,6 +144,39 @@ async function updatePackageJson(packageJsonPath: string, newVersion: string): P
return modified;
}
+/**
+ * Updates react-native and @react-native/* versions in the root package.json's
+ * `resolutions` field. Returns true if the file was modified.
+ */
+async function updateRootResolutions(newVersion: string): Promise {
+ const json = await JsonFile.readAsync(ROOT_PACKAGE_JSON_PATH);
+ const resolutions = json.resolutions as Record | undefined;
+ if (!resolutions) return false;
+
+ let modified = false;
+
+ for (const [name, currentVersion] of Object.entries(resolutions)) {
+ if (name === REACT_NATIVE_PACKAGE || name.startsWith(REACT_NATIVE_SCOPE)) {
+ if (currentVersion === '*') continue;
+
+ const prefix = currentVersion.match(/^([~^]?)/)?.[1] ?? '';
+ const updatedVersion = `${prefix}${newVersion}`;
+
+ if (currentVersion !== updatedVersion) {
+ resolutions[name] = updatedVersion;
+ modified = true;
+ }
+ }
+ }
+
+ if (modified) {
+ await JsonFile.writeAsync(ROOT_PACKAGE_JSON_PATH, json);
+ logger.log(` Updated ${chalk.cyan('package.json')} (resolutions)`);
+ }
+
+ return modified;
+}
+
/**
* Updates react-native version in bundledNativeModules.json.
*/
diff --git a/tools/src/commands/GenerateDocsAPIData.ts b/tools/src/commands/GenerateDocsAPIData.ts
index 224c593ab7aab7..d32eca15d2d099 100644
--- a/tools/src/commands/GenerateDocsAPIData.ts
+++ b/tools/src/commands/GenerateDocsAPIData.ts
@@ -24,9 +24,10 @@ const MINIFY_JSON = true;
const uiPackagesMapping: Record = {
// drop-in replacements
- 'expo-ui/community/segmented-control': ['community/segmented-control/index.tsx', 'expo-ui'],
'expo-ui/community/datetime-picker': ['community/datetime-picker/index.tsx', 'expo-ui'],
+ 'expo-ui/community/masked-view': ['community/masked-view/index.tsx', 'expo-ui'],
'expo-ui/community/picker': ['community/picker/index.tsx', 'expo-ui'],
+ 'expo-ui/community/segmented-control': ['community/segmented-control/index.tsx', 'expo-ui'],
// Swift UI
'expo-ui/swift-ui/bottomsheet': ['swift-ui/BottomSheet/index.tsx', 'expo-ui'],
diff --git a/tools/src/commands/PrebuildPackages.ts b/tools/src/commands/PrebuildPackages.ts
index e052a4271f5e8d..ea4c2bc70d2214 100644
--- a/tools/src/commands/PrebuildPackages.ts
+++ b/tools/src/commands/PrebuildPackages.ts
@@ -35,7 +35,11 @@ export default (program: Command) => {
'Generates `.xcframework` artifacts for iOS packages. If no package names are provided, discovers all packages with spm.config.json.'
)
.alias('prebuild')
- .option('-v, --verbose', 'Enable verbose output (full build logs instead of spinners).', false)
+ .option(
+ '-v, --verbose',
+ 'Print every build step and the full xcodebuild line stream. Off by default; in CI only success/failure lines, compile warnings/errors, and the run summary are printed.',
+ false
+ )
.option(
'--react-native-version ',
'Provides the current React Native version. Auto-detected from bare-expo if not set.'
diff --git a/tools/src/prebuilds/Dependencies.ts b/tools/src/prebuilds/Dependencies.ts
index 53bbf1a3c205bc..124aed6f5a693c 100644
--- a/tools/src/prebuilds/Dependencies.ts
+++ b/tools/src/prebuilds/Dependencies.ts
@@ -90,7 +90,7 @@ export const Dependencies = {
* @returns Whether downloading artifacts should be skipped.
*/
cleanArtifactsAsync: async (artifactsPath: string): Promise => {
- logger.info(
+ logger.verbose(
`🧹 Clearing artifacts folder: ${chalk.gray(path.relative(process.cwd(), artifactsPath))}`
);
await fs.remove(artifactsPath);
@@ -122,7 +122,7 @@ export const Dependencies = {
react: `${currentVersions.reactNativeVersion}-${currentVersions.buildFlavor}`,
};
- logger.info(`🧹 Pruning unused cache entries from ${chalk.gray(cachePath)}...`);
+ logger.verbose(`🧹 Pruning unused cache entries from ${chalk.gray(cachePath)}...`);
// Iterate through artifact directories (hermes, react-native-dependencies, react)
const artifactDirs = await fs.readdir(cachePath, { withFileTypes: true });
@@ -153,12 +153,12 @@ export const Dependencies = {
if (versionDir.name === currentVersion) {
// This is the current version - keep it
keptCount++;
- logger.info(
+ logger.verbose(
` ✓ Keeping ${chalk.green(artifactDir.name)}/${chalk.cyan(versionDir.name)}`
);
} else {
// Old version - remove it
- logger.info(
+ logger.verbose(
` 🗑 Removing ${chalk.yellow(artifactDir.name)}/${chalk.gray(versionDir.name)}`
);
await fs.remove(versionPath);
@@ -168,9 +168,9 @@ export const Dependencies = {
}
if (removed.length > 0) {
- logger.info(`🧹 Pruned ${chalk.yellow(removed.length)} old cache entries`);
+ logger.verbose(`🧹 Pruned ${chalk.yellow(removed.length)} old cache entries`);
} else {
- logger.info(`🧹 Cache is clean - no old entries to prune`);
+ logger.verbose(`🧹 Cache is clean - no old entries to prune`);
}
return { removed, keptCount };
@@ -196,7 +196,7 @@ export const Dependencies = {
skipArtifacts,
} = options;
- logger.info(
+ logger.verbose(
`⬇️ ${options.skipArtifacts ? 'Verifying' : 'Preparing'} centralized cache at ${chalk.gray(path.relative(process.cwd(), cachePath))}...`
);
@@ -250,7 +250,7 @@ export const Dependencies = {
const reactNativeSourcePath = resolvePackagePath('react-native');
const xcframeworkPath = path.join(reactNativePath, 'React.xcframework');
if (fs.existsSync(xcframeworkPath) && !isVFSGenerated(reactNativePath, reactNativeSourcePath)) {
- logger.info('🔄 Generating VFS overlay for stock React.xcframework...');
+ logger.verbose('🔄 Generating VFS overlay for stock React.xcframework...');
await transformReactXCFrameworkAsync({
outputPath: reactNativePath,
reactNativePath: reactNativeSourcePath,
@@ -287,7 +287,7 @@ export const Dependencies = {
depsDestinationPath: string,
copyDependencies: boolean
): Promise => {
- logger.info(
+ logger.verbose(
`📋 ${copyDependencies ? 'Syncing' : 'Checking'} package dependencies for ${chalk.green(pkg.packageName)}`
);
@@ -349,7 +349,9 @@ export const Dependencies = {
* @param pkg Package
*/
cleanDependenciesFolderAsync: async (pkg: SPMPackageSource): Promise => {
- logger.info(`🧹 Cleaning dependencies folder for package ${chalk.green(pkg.packageName)}...`);
+ logger.verbose(
+ `🧹 Cleaning dependencies folder for package ${chalk.green(pkg.packageName)}...`
+ );
const buildFolderToClean = Dependencies.getPackageDependenciesPath(pkg);
await fs.remove(buildFolderToClean);
},
@@ -367,7 +369,7 @@ export const Dependencies = {
const outputPath = path.join(pkg.buildPath, 'output', flavor.toLowerCase());
if (fs.existsSync(outputPath)) {
- logger.info(
+ logger.verbose(
`🧹 Cleaning output folder for package ${chalk.green(pkg.packageName)}/${flavor}...`
);
await fs.remove(outputPath);
@@ -382,7 +384,7 @@ export const Dependencies = {
cleanGeneratedFolderAsync: async (pkg: SPMPackageSource): Promise => {
const generatedPath = path.join(pkg.path, '.generated');
if (fs.existsSync(generatedPath)) {
- logger.info(`🧹 Cleaning generated folder for package ${chalk.green(pkg.packageName)}...`);
+ logger.verbose(`🧹 Cleaning generated folder for package ${chalk.green(pkg.packageName)}...`);
await fs.remove(generatedPath);
}
},
diff --git a/tools/src/prebuilds/Frameworks.ts b/tools/src/prebuilds/Frameworks.ts
index e5ae5704296c29..b5bb44711e470f 100644
--- a/tools/src/prebuilds/Frameworks.ts
+++ b/tools/src/prebuilds/Frameworks.ts
@@ -41,7 +41,7 @@ const signXCFramework = (
identity: string,
useTimestamp: boolean = true
): void => {
- logger.info(`🔏 Signing XCFramework with identity "${identity}"...`);
+ logger.verbose(`🔏 Signing XCFramework with identity "${identity}"...`);
const timestampFlag = useTimestamp ? '--timestamp' : '';
const command = `codesign ${timestampFlag} --sign "${identity}" "${xcframeworkPath}"`.trim();
@@ -74,7 +74,7 @@ export const Frameworks = {
): Promise => {
const spmConfig = pkg.getSwiftPMConfiguration();
- logger.info(
+ logger.verbose(
`🧩 Composing XCFramework for ${chalk.green(pkg.packageName) + '/' + chalk.green(product.name)}...`
);
@@ -484,7 +484,7 @@ const copySPMDependencyXCFrameworksAsync = async (
const sharedPath = Frameworks.getSharedSPMDepFrameworkPath(productName, buildType);
const destPath = path.join(outputDir, `${productName}.xcframework`);
- logger.info(
+ logger.verbose(
`📦 Copying shared SPM dep ${chalk.cyan(productName)} from shared location → ${path.relative(pkg.path, destPath)}`
);
await fs.remove(destPath);
@@ -552,7 +552,7 @@ const copySPMDependencyXCFrameworksAsync = async (
const sourceXCFrameworkPath = path.join(artifactsDir, xcframeworkName);
if (await fs.pathExists(sourceXCFrameworkPath)) {
- logger.info(
+ logger.verbose(
`📦 Copying SPM dependency ${chalk.cyan(xcframeworkName)} → ${path.relative(pkg.path, destXCFrameworkPath)}`
);
await fs.remove(destXCFrameworkPath);
@@ -569,7 +569,7 @@ const copySPMDependencyXCFrameworksAsync = async (
}
// Compose the dependency xcframework with only the relevant slices
- logger.info(
+ logger.verbose(
`📦 Composing SPM dependency ${chalk.cyan(xcframeworkName)} → ${path.relative(pkg.path, destXCFrameworkPath)}`
);
await fs.remove(destXCFrameworkPath);
@@ -648,7 +648,7 @@ const createProductTarballAsync = async (
}
}
- logger.info(
+ logger.verbose(
`📦 Creating tarball for ${chalk.green(product.name)} (${buildType}): ${xcframeworkEntries.join(', ')}`
);
diff --git a/tools/src/prebuilds/SPMBuild.ts b/tools/src/prebuilds/SPMBuild.ts
index 1e20b95eb786fe..1dcb308278a407 100644
--- a/tools/src/prebuilds/SPMBuild.ts
+++ b/tools/src/prebuilds/SPMBuild.ts
@@ -34,7 +34,7 @@ export const SPMBuild = {
platform?: BuildPlatform,
hermesIncludeDirs?: string[]
): Promise => {
- logger.info(
+ logger.verbose(
`🏗 Build Package.swift for ${chalk.green(pkg.packageName)}/${chalk.green(product.name)} [${buildType.toLowerCase()}]`
);
@@ -69,7 +69,7 @@ export const SPMBuild = {
);
}
- logger.info(`🏗 Swift package successfully built.`);
+ logger.verbose(`🏗 Swift package successfully built.`);
},
/**
@@ -84,7 +84,7 @@ export const SPMBuild = {
buildType: BuildFlavor
): Promise => {
const buildFolderToClean = SPMBuild.getPackageBuildPath(pkg, product, buildType);
- logger.info(
+ logger.verbose(
`🧹 Cleaning build folder ${chalk.green(path.relative(pkg.buildPath, buildFolderToClean))}...`
);
await fs.remove(buildFolderToClean);
@@ -414,7 +414,7 @@ async function resolveSPMDependenciesAndPatch(packageDir: string): Promise
'public typealias NetworkUnreachable = (_ReachabilityRef) -> ()'
);
fs.writeFileSync(reachabilitySource, content, 'utf8');
- logger.info('🩹 Patched Reachability.swift (library evolution typealias fix)');
+ logger.verbose('🩹 Patched Reachability.swift (library evolution typealias fix)');
}
}
}
@@ -854,7 +854,7 @@ export async function buildSharedSPMDependencyAsync(
return;
}
- logger.info(
+ logger.verbose(
`🔨 Building shared SPM dependency ${chalk.cyan(productName)} [${buildType.toLowerCase()}]...`
);
@@ -890,7 +890,7 @@ export async function buildSharedSPMDependencyAsync(
// Package.swift changed or first run — resolve from scratch
await resolveSPMDependenciesAndPatch(buildDir);
} else {
- logger.info(` ⏭️ SPM dependencies already resolved (Package.swift unchanged)`);
+ logger.verbose(` ⏭️ SPM dependencies already resolved (Package.swift unchanged)`);
}
// Build from the dependency's own checkout so we get a framework with the correct
@@ -973,7 +973,7 @@ export async function buildSharedSPMDependencyAsync(
const externalInterDeps = interDeps.filter((d) => d !== productName);
if (externalInterDeps.length > 0) {
- logger.info(
+ logger.verbose(
` 🔗 ${productName} depends on shared deps: ${externalInterDeps.map((d) => chalk.cyan(d)).join(', ')}`
);
@@ -981,7 +981,7 @@ export async function buildSharedSPMDependencyAsync(
for (const interDepName of externalInterDeps) {
const interDep = allSharedDeps.get(interDepName);
if (interDep && !Frameworks.hasSharedSPMDepFramework(interDepName, buildType)) {
- logger.info(` ↳ Building dependency ${chalk.cyan(interDepName)} first...`);
+ logger.verbose(` ↳ Building dependency ${chalk.cyan(interDepName)} first...`);
await buildSharedSPMDependencyAsync(interDep, buildType, allSharedDeps, platforms);
}
}
diff --git a/tools/src/prebuilds/SPMGenerator.hardlinkRefresh.test.ts b/tools/src/prebuilds/SPMGenerator.hardlinkRefresh.test.ts
new file mode 100644
index 00000000000000..eea3e0fb35dc7f
--- /dev/null
+++ b/tools/src/prebuilds/SPMGenerator.hardlinkRefresh.test.ts
@@ -0,0 +1,52 @@
+import fs from 'fs-extra';
+import assert from 'node:assert/strict';
+import os from 'node:os';
+import { describe, it } from 'node:test';
+import path from 'path';
+
+import { refreshHardlinkIfNeeded } from './SPMGenerator';
+
+describe('refreshHardlinkIfNeeded', () => {
+ it('relinks when the source inode changed even though content is identical', async () => {
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'spm-hardlink-'));
+ try {
+ const src = path.join(tmp, 'src.h');
+ const dest = path.join(tmp, 'staging', 'dest.h');
+ const content = '#pragma once\nint X = 1;\n';
+ await fs.outputFile(src, content);
+
+ assert.equal(await refreshHardlinkIfNeeded(src, dest), true);
+ const inodeBefore = (await fs.stat(dest)).ino;
+ assert.equal(inodeBefore, (await fs.stat(src)).ino);
+
+ // Simulate pnpm reinstall: same bytes, fresh inode.
+ await fs.remove(src);
+ await fs.outputFile(src, content);
+ const newSrcInode = (await fs.stat(src)).ino;
+ assert.notEqual(newSrcInode, inodeBefore);
+ assert.equal((await fs.stat(dest)).ino, inodeBefore);
+
+ assert.equal(await refreshHardlinkIfNeeded(src, dest), true);
+ assert.equal((await fs.stat(dest)).ino, newSrcInode);
+ } finally {
+ await fs.remove(tmp);
+ }
+ });
+
+ it('is a no-op when source and dest already share an inode', async () => {
+ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'spm-hardlink-'));
+ try {
+ const src = path.join(tmp, 'src.h');
+ const dest = path.join(tmp, 'staging', 'dest.h');
+ await fs.outputFile(src, 'x');
+
+ assert.equal(await refreshHardlinkIfNeeded(src, dest), true);
+ const ctimeAfterFirst = (await fs.stat(dest)).ctimeMs;
+
+ assert.equal(await refreshHardlinkIfNeeded(src, dest), false);
+ assert.equal((await fs.stat(dest)).ctimeMs, ctimeAfterFirst);
+ } finally {
+ await fs.remove(tmp);
+ }
+ });
+});
diff --git a/tools/src/prebuilds/SPMGenerator.ts b/tools/src/prebuilds/SPMGenerator.ts
index bf9c8e10d4ffff..45d85775228255 100644
--- a/tools/src/prebuilds/SPMGenerator.ts
+++ b/tools/src/prebuilds/SPMGenerator.ts
@@ -3,13 +3,38 @@ import fs from 'fs-extra';
import { glob } from 'glob';
import path from 'path';
+import logger from '../Logger';
import type { DownloadedDependencies } from './Artifacts.types';
import type { SPMPackageSource } from './ExternalPackage';
import { BuildFlavor } from './Prebuilder.types';
import { SPMProduct, SPMTarget } from './SPMConfig.types';
import { SPMPackage } from './SPMPackage';
import { createAsyncSpinner, hasFileContentChanged } from './Utils';
-import logger from '../Logger';
+
+/**
+ * Ensures `destPath` is a hardlink to `sourcePath`. For staged headers, inode equality
+ * is the source of truth — clang's `#pragma once` keys on inode, so a stale dest with
+ * identical bytes but a different inode is processed as a separate header within the
+ * same TU and produces redefinition errors. This is reachable when the package manager
+ * rewrites the source file with the same content but a fresh inode (pnpm reinstalls
+ * into a new content-addressed `.pnpm/` dir on patch-hash or version changes).
+ */
+export async function refreshHardlinkIfNeeded(
+ sourcePath: string,
+ destPath: string
+): Promise {
+ const srcStat = await fs.stat(sourcePath);
+ const destStat = await fs.lstat(destPath).catch(() => null);
+ if (destStat && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
+ return false;
+ }
+ if (destStat) {
+ await fs.remove(destPath);
+ }
+ await fs.ensureDir(path.dirname(destPath));
+ await fs.link(sourcePath, destPath);
+ return true;
+}
/**
* Writes content to file only if it differs from existing content.
@@ -42,7 +67,7 @@ export const SPMGenerator = {
buildType: BuildFlavor,
artifacts?: DownloadedDependencies
): Promise => {
- logger.info(
+ logger.verbose(
`📦 Generating Package.swift for ${chalk.green(pkg.packageName)}/${chalk.green(product.name)}...`
);
// Use SPMPackage to generate Package.swift
@@ -73,7 +98,7 @@ export const SPMGenerator = {
*/
generateIsolatedSourcesForTargetsAsync: async (pkg: SPMPackageSource, product: SPMProduct) => {
const loggerTitle = `${chalk.green(pkg.packageName)}/${chalk.green(product.name)}`;
- logger.info(`📂 Generating files for ${loggerTitle}`);
+ logger.verbose(`📂 Generating files for ${loggerTitle}`);
// Walk through all targets for the product and collect source files to copy into spm code structure
for (const target of product.targets) {
@@ -243,16 +268,11 @@ export const SPMGenerator = {
path.basename(file)
);
- await fs.ensureDir(path.dirname(destinationFilePath));
- if (hasFileContentChanged(sourceFilePath, destinationFilePath)) {
+ const linked = await refreshHardlinkIfNeeded(sourceFilePath, destinationFilePath);
+ if (linked) {
spinner.info(
`Linking header file for target ${chalk.green(target.name)} ${path.basename(file)}...`
);
- // Remove existing file before creating hardlink
- if (await fs.pathExists(destinationFilePath)) {
- await fs.remove(destinationFilePath);
- }
- await fs.link(sourceFilePath, destinationFilePath);
}
}
}
@@ -357,7 +377,7 @@ ${allImports.join('\n')}
pkg: SPMPackageSource,
product: SPMProduct
): Promise => {
- logger.info(
+ logger.verbose(
`🧹 Cleaning generated source code for ${chalk.green(pkg.packageName)}/${chalk.green(product.name)}...`
);
diff --git a/tools/src/prebuilds/TransformReactXCFramework.ts b/tools/src/prebuilds/TransformReactXCFramework.ts
index f4941175a07336..396743091e3f56 100644
--- a/tools/src/prebuilds/TransformReactXCFramework.ts
+++ b/tools/src/prebuilds/TransformReactXCFramework.ts
@@ -52,7 +52,7 @@ export async function transformReactXCFrameworkAsync(options: TransformOptions):
// When present, use it directly instead of generating our own.
const bundledTemplatePath = path.join(xcframeworkPath, 'React-VFS-template.yaml');
if (fs.existsSync(bundledTemplatePath)) {
- logger.info(' Using bundled React-VFS-template.yaml from xcframework (RN 0.85+)');
+ logger.verbose(' Using bundled React-VFS-template.yaml from xcframework (RN 0.85+)');
// Copy bundled template to expected location (where resolveVFSOverlayTemplate reads it)
fs.copyFileSync(bundledTemplatePath, path.join(outputPath, 'React-VFS-template.yaml'));
@@ -61,7 +61,7 @@ export async function transformReactXCFrameworkAsync(options: TransformOptions):
const stagingDir = path.join(outputPath, 'React-extra-headers');
if (fs.existsSync(stagingDir)) {
fs.removeSync(stagingDir);
- logger.info(' Removed stale React-extra-headers/ directory');
+ logger.verbose(' Removed stale React-extra-headers/ directory');
}
// Write version stamp for cache invalidation
@@ -70,23 +70,23 @@ export async function transformReactXCFrameworkAsync(options: TransformOptions):
).version;
VersionStamp.write(outputPath, { reactNativeVersion: rnVersion }, VFS_STAMP_FILENAME);
- logger.info(' VFS overlay setup complete (using bundled template).');
+ logger.verbose(' VFS overlay setup complete (using bundled template).');
return;
}
- logger.info(' Collecting header mappings from podspecs...');
+ logger.verbose(' Collecting header mappings from podspecs...');
const headerMappings = getHeaderFilesFromPodspecs(reactNativePath);
- logger.info(' Inventorying stock xcframework headers...');
+ logger.verbose(' Inventorying stock xcframework headers...');
const stockHeaders = inventoryStockHeaders(xcframeworkPath);
- logger.info(' Detecting duplicate header basenames...');
+ logger.verbose(' Detecting duplicate header basenames...');
const duplicateBasenames = findDuplicateBasenames(headerMappings);
- logger.info(' Staging missing headers to React-extra-headers/...');
+ logger.verbose(' Staging missing headers to React-extra-headers/...');
await stageMissingHeadersAsync(outputPath, headerMappings, stockHeaders, duplicateBasenames);
- logger.info(' Generating VFS overlay template...');
+ logger.verbose(' Generating VFS overlay template...');
const vfsYaml = createVFSOverlay(reactNativePath, stockHeaders, duplicateBasenames);
fs.writeFileSync(path.join(outputPath, 'React-VFS-template.yaml'), vfsYaml);
@@ -96,7 +96,7 @@ export async function transformReactXCFrameworkAsync(options: TransformOptions):
).version;
VersionStamp.write(outputPath, { reactNativeVersion: rnVersion }, VFS_STAMP_FILENAME);
- logger.info(' VFS overlay generation complete (xcframework untouched).');
+ logger.verbose(' VFS overlay generation complete (xcframework untouched).');
}
/**
@@ -214,7 +214,7 @@ async function stageMissingHeadersAsync(
}
}
- logger.info(` Staged ${stagedCount} missing headers to React-extra-headers/`);
+ logger.verbose(` Staged ${stagedCount} missing headers to React-extra-headers/`);
}
const VFS_STAMP_FILENAME = '.vfs-version-stamp';
diff --git a/tools/src/prebuilds/Utils.ts b/tools/src/prebuilds/Utils.ts
index 36389eb76d623b..6a169451203ca9 100644
--- a/tools/src/prebuilds/Utils.ts
+++ b/tools/src/prebuilds/Utils.ts
@@ -546,6 +546,7 @@ export const createAsyncSpinner = (
logger.log(`${Prefix} ${chalk.yellow('⚠')} ${effectivePrefix(chalk.yellow)}${text ?? ''}`);
},
info: (text?: string) => {
+ if (!logger.isVerbose()) return;
logger.log(`${Prefix} ${chalk.blue('ℹ')} ${effectivePrefix(chalk.green)}${text ?? ''}`);
},
};
diff --git a/tools/src/prebuilds/Verifier.ts b/tools/src/prebuilds/Verifier.ts
index f80cac529ca97f..44e526df22e51f 100644
--- a/tools/src/prebuilds/Verifier.ts
+++ b/tools/src/prebuilds/Verifier.ts
@@ -35,7 +35,7 @@ export const FrameworkVerifier = {
buildFlavor: BuildFlavor,
options?: XCFrameworkVerifyOptions
): Promise> => {
- logger.info(
+ logger.verbose(
`🔍 Verifying xcframework for ${chalk.green(pkg.packageName)}/${chalk.green(product.name)} [${buildFlavor.toLowerCase()}]`
);
@@ -246,7 +246,7 @@ export const FrameworkVerifier = {
` ${chalk.red('✗')} Resource bundle ${chalk.cyan(bundleName)} missing from slices: ${missingSlices.join(', ')}`
);
} else {
- logger.info(
+ logger.verbose(
` ${chalk.green('✓')} Resource bundle ${chalk.cyan(bundleName)} present in all slices (${foundSlices.join(', ')})`
);
}
diff --git a/tools/src/prebuilds/XCodeRunner.ts b/tools/src/prebuilds/XCodeRunner.ts
index b144eb24f7da6b..56e454d73c7fa0 100644
--- a/tools/src/prebuilds/XCodeRunner.ts
+++ b/tools/src/prebuilds/XCodeRunner.ts
@@ -75,44 +75,18 @@ export async function spawnXcodeBuildWithSpinner(
const statusText = `${spinnerText} ${summary.trim()}${warningText}`;
if (hasWarnings) {
spinner.warn(statusText);
+ // Surface the actual warnings even on success — without this, CI
+ // logs only see the count and have no way to triage what changed.
+ printDiagnostics(formatter.warnings, []);
} else {
spinner.succeed(statusText);
}
} else {
spinner.fail(`${spinnerText} failed with code ${code}`);
summary && logger.error('\n' + summary.trim() + '\n');
-
- // On failure, show warnings first (consistent with FrameworkVerifier)
- if (hasWarnings) {
- logger.log(chalk.gray(' Warnings:'));
- formatter.warnings
- .slice(0, 10)
- .forEach((warn) => logger.log(chalk.gray(` ${warn}`)));
- if (warningCount > 10) {
- logger.log(chalk.gray(` ... and ${warningCount - 10} more warnings`));
- }
- }
-
- // Show errors (consistent with FrameworkVerifier style)
- if (formatter.errors.length > 0) {
- logger.log(chalk.gray(' Errors:'));
- formatter.errors
- .slice(0, 10)
- .forEach((err) => logger.log(chalk.gray(` ${err.trim()}`)));
- if (formatter.errors.length > 10) {
- logger.log(chalk.gray(` ... and ${formatter.errors.length - 10} more errors`));
- }
- }
-
- // If formatter didn't capture any errors, fall back to showing raw output
+ printDiagnostics(formatter.warnings, formatter.errors);
if (formatter.errors.length === 0) {
- const rawErrors = extractRawCompileErrors(results);
- if (rawErrors.length > 0) {
- logger.log(chalk.gray(' Raw errors:'));
- rawErrors.slice(0, 10).forEach((err) => logger.log(chalk.gray(` ${err}`)));
- } else if (error) {
- logger.log(chalk.gray(error));
- }
+ printRawErrorFallback(results, error);
}
}
@@ -121,6 +95,45 @@ export async function spawnXcodeBuildWithSpinner(
});
}
+/**
+ * Print formatted compile warnings and errors. Used on both success-with-
+ * warnings and failure, so CI always surfaces diagnostics regardless of
+ * overall build status.
+ */
+function printDiagnostics(warnings: string[], errors: string[]): void {
+ const MAX = 10;
+
+ if (warnings.length > 0) {
+ logger.log(chalk.gray(' Warnings:'));
+ warnings.slice(0, MAX).forEach((warn) => logger.log(chalk.gray(` ${warn}`)));
+ if (warnings.length > MAX) {
+ logger.log(chalk.gray(` ... and ${warnings.length - MAX} more warnings`));
+ }
+ }
+
+ if (errors.length > 0) {
+ logger.log(chalk.gray(' Errors:'));
+ errors.slice(0, MAX).forEach((err) => logger.log(chalk.gray(` ${err.trim()}`)));
+ if (errors.length > MAX) {
+ logger.log(chalk.gray(` ... and ${errors.length - MAX} more errors`));
+ }
+ }
+}
+
+/**
+ * Print fallback raw errors when the xcodebuild failed but the formatter
+ * didn't capture structured errors. Called only on failure.
+ */
+function printRawErrorFallback(rawOutput: string, stderr: string): void {
+ const rawErrors = extractRawCompileErrors(rawOutput);
+ if (rawErrors.length > 0) {
+ logger.log(chalk.gray(' Raw errors:'));
+ rawErrors.slice(0, 10).forEach((err) => logger.log(chalk.gray(` ${err}`)));
+ } else if (stderr) {
+ logger.log(chalk.gray(stderr));
+ }
+}
+
/**
* Extract raw compile errors from xcodebuild output.
* This is a fallback for when xcpretty's formatter doesn't capture errors
diff --git a/tools/src/prebuilds/pipeline/RunSteps.ts b/tools/src/prebuilds/pipeline/RunSteps.ts
index 29e03a9b7fed7f..2d30e5e714eeae 100644
--- a/tools/src/prebuilds/pipeline/RunSteps.ts
+++ b/tools/src/prebuilds/pipeline/RunSteps.ts
@@ -382,10 +382,14 @@ export const prepareInputsStep: Step = {
async run(ctx) {
const { request } = ctx;
- // Enable verbose output (full build logs instead of spinners).
- // Also force non-interactive when building in parallel — ora spinners
- // from concurrent packages would overwrite each other's terminal lines.
- if (request.verbose || request.concurrency > 1) {
+ // Verbose widens what gets logged but doesn't change interactive vs.
+ // non-interactive — TTY users can opt into the full per-step trace and
+ // still see spinners. CI is non-interactive on its own.
+ logger.setVerbose(request.verbose);
+
+ // Force non-interactive when building in parallel — ora spinners from
+ // concurrent packages would overwrite each other's terminal lines.
+ if (request.concurrency > 1) {
setForceNonInteractive(true);
}