expo-widgets 58.0.9 → 58.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 58.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - Add React hook and component helper stubs to prevent missing API errors when rendering widgets, including `memo` and `forwardRef`. ([#50834](https://github.com/expo/expo/pull/50834) by [@jakex7](https://github.com/jakex7))
8
+ - [iOS] Stop linking WidgetKit into apps that use `@expo/ui` without widgets. The `widgetURL`, `activityBackgroundTint`, `widgetAccentedRenderingMode`, and `containerBackground` (`widget` placement) modifiers and the `AccessoryWidgetBackground` view now take effect only inside `expo-widgets` widgets and Live Activities. ([#50786](https://github.com/expo/expo/pull/50786) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
9
+ - Updated dependencies. ([#50801](https://github.com/expo/expo/pull/50801), [#49986](https://github.com/expo/expo/pull/49986), [#50674](https://github.com/expo/expo/pull/50674), [#50786](https://github.com/expo/expo/pull/50786), [#50851](https://github.com/expo/expo/pull/50851))
10
+ - @expo/ui@58.0.10
11
+
3
12
  ## 58.0.9
4
13
 
5
14
  ### Patch Changes
@@ -6,7 +6,7 @@ plugins {
6
6
  }
7
7
 
8
8
  group = 'expo.modules.widgets'
9
- version = '58.0.9'
9
+ version = '58.0.10'
10
10
 
11
11
  def expoWidgetsPackageDirFile = project.projectDir.parentFile
12
12
  def expoWidgetsPackageDir = expoWidgetsPackageDirFile.absolutePath
@@ -39,7 +39,7 @@ android {
39
39
  namespace "expo.modules.widgets"
40
40
  defaultConfig {
41
41
  versionCode 1
42
- versionName '58.0.9'
42
+ versionName '58.0.10'
43
43
 
44
44
  externalNativeBuild {
45
45
  cmake {
@@ -143,6 +143,6 @@ dependencies {
143
143
  implementation 'com.facebook.react:react-android'
144
144
  implementation 'com.facebook.react:hermes-android'
145
145
  implementation expoModule.getExpoDependency("expo-ui")
146
- api 'io.github.jakex7.peek:peek-emittables:0.3.0'
146
+ api 'io.github.expo.peek:peek-emittables:0.3.1'
147
147
  implementation "androidx.glance:glance:1.2.0"
148
148
  }
@@ -105,7 +105,7 @@ import androidx.glance.unit.ColorProvider
105
105
  import androidx.glance.appwidget.lazy.EmittableLazyColumn
106
106
  import androidx.glance.appwidget.lazy.EmittableLazyListItem
107
107
  import androidx.glance.appwidget.lazy.ReservedItemIdRangeEnd
108
- import io.github.jakex7.peek.glance.determinateCircularProgressIndicatorEmittable
108
+ import io.github.expo.peek.glance.determinateCircularProgressIndicatorEmittable
109
109
  import kotlin.math.roundToInt
110
110
 
111
111
  private val DefaultCheckedColor = ColorProvider(Color(0xff6750a4))
@@ -3,7 +3,7 @@ package expo.modules.widgets
3
3
  import android.content.Context
4
4
  import android.content.Intent
5
5
  import androidx.glance.appwidget.GlanceAppWidgetReceiver
6
- import io.github.jakex7.peek.emittables.PeekEmittableAppWidget
6
+ import io.github.expo.peek.emittables.PeekEmittableAppWidget
7
7
 
8
8
  open class ExpoWidgetsAppWidgetProvider(
9
9
  widgetName: String
@@ -8,7 +8,7 @@ import androidx.compose.ui.unit.DpSize
8
8
  import androidx.glance.Emittable
9
9
  import androidx.glance.appwidget.AppWidgetId
10
10
  import androidx.glance.appwidget.SizeMode
11
- import io.github.jakex7.peek.emittables.PeekEmittableAppWidget
11
+ import io.github.expo.peek.emittables.PeekEmittableAppWidget
12
12
 
13
13
  internal class ExpoWidgetsPeekWidget(
14
14
  private val widgetName: String
@@ -15,16 +15,77 @@ export const isValidElement = (value: unknown): value is ReactElementNode => {
15
15
  };
16
16
 
17
17
  // TODO(@jakex7): Make this a proper React reconciler in the future. For now, we just need a stub to allow the widget bundle to compile with @expo/ui/jetpack-compose.
18
- type StubContext<T> = {
19
- Provider: string;
20
- Consumer: string;
21
- _currentValue: T;
18
+
19
+ // Each widget render is a fresh mount, so there is nothing to memoize.
20
+ export const memo = (component: any, _arePropsEqual?: any) => component;
21
+ export const forwardRef = (render: any) => (props: any) => {
22
+ const { ref = null, ...rest } = props;
23
+ return render(rest, ref);
22
24
  };
25
+ export const createRef = (): any => ({ current: null });
26
+ export function createElement(type: any, config?: any, ...children: any[]) {
27
+ const { key, ...props } = config ?? {};
28
+ if (children.length > 0) {
29
+ props.children = children.length === 1 ? children[0] : children;
30
+ }
31
+ return { type, key: key === undefined ? null : String(key), props };
32
+ }
33
+ export function cloneElement(element: any, config?: any, ...children: any[]) {
34
+ const cloned = createElement(element.type, { ...element.props, ...config }, ...children);
35
+ cloned.key = config?.key === undefined ? element.key : cloned.key;
36
+ return cloned;
37
+ }
23
38
 
24
- export const createContext = <T>(defaultValue: T): StubContext<T> => ({
39
+ const noop = (..._args: any[]) => {};
40
+ export const createContext = (defaultValue: any) => ({
25
41
  Provider: 'react.provider',
26
42
  Consumer: 'react.consumer',
27
43
  _currentValue: defaultValue,
28
44
  });
45
+ export function useContext(context: any) {
46
+ return context._currentValue;
47
+ }
48
+
49
+ export function useState(initialState?: any) {
50
+ return [typeof initialState === 'function' ? initialState() : initialState, noop];
51
+ }
52
+
53
+ export function useReducer(_reducer: any, initialArg: any, init?: any) {
54
+ return [init ? init(initialArg) : initialArg, noop];
55
+ }
56
+
57
+ export const useRef = (initialValue: any) => ({ current: initialValue });
58
+ export const useMemo = (factory: any, _deps?: any) => factory();
59
+ export const useCallback = (callback: any, _deps?: any) => callback;
60
+ export const useEffectEvent = (callback: any) => callback;
61
+
62
+ export const useEffect = noop;
63
+ export const useLayoutEffect = noop;
64
+ export const useInsertionEffect = noop;
65
+ export const useImperativeHandle = noop;
66
+ export const useDebugValue = noop;
67
+
68
+ export const useDeferredValue = (value: any, _initialValue?: any) => value;
69
+ export const startTransition = (callback: any) => {
70
+ callback();
71
+ };
72
+ export const useTransition = (): any[] => [false, startTransition];
73
+ let nextId = 0;
74
+ export const useId = () => `:expo-widget-${nextId++}:`;
75
+
76
+ export const useSyncExternalStore = (_subscribe: any, getSnapshot: any, getServerSnapshot?: any) =>
77
+ (getServerSnapshot ?? getSnapshot)();
78
+
79
+ export const useOptimistic = (state: any, _reducer?: any) => [state, noop];
80
+ export const useActionState = (_action: any, initialState: any, _permalink?: any) => [
81
+ initialState,
82
+ noop,
83
+ false,
84
+ ];
29
85
 
30
- export const useContext = <T>(context: StubContext<T>): T => context._currentValue;
86
+ export function use(resource: any) {
87
+ if ('_currentValue' in resource) {
88
+ return resource._currentValue;
89
+ }
90
+ return resource.status === 'fulfilled' ? resource.value : undefined;
91
+ }
@@ -7,9 +7,9 @@
7
7
  "modules": ["expo.modules.widgets.WidgetsModule"],
8
8
  "gradlePlugins": [
9
9
  {
10
- "id": "io.github.jakex7.peek.glance-fork",
11
- "group": "io.github.jakex7.peek",
12
- "version": "0.3.0"
10
+ "id": "io.github.expo.peek.glance-fork",
11
+ "group": "io.github.expo.peek",
12
+ "version": "0.3.1"
13
13
  }
14
14
  ]
15
15
  }
@@ -0,0 +1,28 @@
1
+ import SwiftUI
2
+ import ExpoModulesCore
3
+ import ExpoUI
4
+ #if !os(tvOS)
5
+ import WidgetKit
6
+ #endif
7
+
8
+ public final class AccessoryWidgetBackgroundProps: UIBaseViewProps {}
9
+
10
+ public struct AccessoryWidgetBackgroundView: ExpoSwiftUI.View {
11
+ @ObservedObject public var props: AccessoryWidgetBackgroundProps
12
+
13
+ public init(props: AccessoryWidgetBackgroundProps) {
14
+ self.props = props
15
+ }
16
+
17
+ public var body: some View {
18
+ #if !os(tvOS)
19
+ if #available(iOS 16.0, *) {
20
+ AccessoryWidgetBackground()
21
+ } else {
22
+ Color.clear
23
+ }
24
+ #else
25
+ EmptyView()
26
+ #endif
27
+ }
28
+ }
@@ -0,0 +1,102 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ import ExpoModulesCore
4
+ import ExpoUI
5
+ import SwiftUI
6
+ #if !os(tvOS)
7
+ import WidgetKit
8
+ #endif
9
+
10
+ internal enum WidgetAccentedRenderingModeOptions: String, Enumerable {
11
+ case accented
12
+ case desaturated
13
+ case accentedDesaturated
14
+ case fullColor
15
+
16
+ #if !os(tvOS)
17
+ @available(iOS 18.0, macOS 15.0, *)
18
+ var toWidgetAccentedRenderingMode: WidgetAccentedRenderingMode {
19
+ switch self {
20
+ case .accented: return .accented
21
+ case .accentedDesaturated: return .accentedDesaturated
22
+ case .desaturated: return .desaturated
23
+ case .fullColor: return .fullColor
24
+ }
25
+ }
26
+ #endif
27
+ }
28
+
29
+ /**
30
+ * This is a unique modifier that exists only on Image, but returns some View, and for this reason it cannot be a ViewModifier.
31
+ */
32
+ internal struct WidgetAccentedRenderingModeModifier: Record {
33
+ @Field var renderingMode: WidgetAccentedRenderingModeOptions?
34
+
35
+ @ViewBuilder
36
+ func apply(to image: Image) -> some View {
37
+ #if !os(tvOS)
38
+ if #available(iOS 18.0, macOS 15.0, *), renderingMode != nil {
39
+ image.widgetAccentedRenderingMode(renderingMode?.toWidgetAccentedRenderingMode)
40
+ } else {
41
+ image
42
+ }
43
+ #else
44
+ image
45
+ #endif
46
+ }
47
+ }
48
+
49
+ internal struct WidgetURLModifier: ViewModifier, Record {
50
+ @Field var url: URL?
51
+
52
+ func body(content: Content) -> some View {
53
+ #if !os(tvOS)
54
+ content.widgetURL(url)
55
+ #else
56
+ content
57
+ #endif
58
+ }
59
+ }
60
+
61
+ internal struct ActivityBackgroundTintModifier: ViewModifier, Record {
62
+ @Field var color: Color?
63
+
64
+ func body(content: Content) -> some View {
65
+ #if !os(tvOS) && !os(macOS) && !targetEnvironment(macCatalyst)
66
+ content.activityBackgroundTint(color)
67
+ #else
68
+ content
69
+ #endif
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Registers the modifiers that need WidgetKit. They live here rather than in ExpoUI,
75
+ * so apps that use ExpoUI without widgets do not link WidgetKit.
76
+ * See: https://github.com/expo/expo/issues/50777
77
+ */
78
+ internal func registerWidgetModifiers() {
79
+ ViewModifierRegistry.register("widgetURL") { params, appContext, _ in
80
+ return try WidgetURLModifier(from: params, appContext: appContext)
81
+ }
82
+ ViewModifierRegistry.register("activityBackgroundTint") { params, appContext, _ in
83
+ return try ActivityBackgroundTintModifier(from: params, appContext: appContext)
84
+ }
85
+ ViewModifierRegistry.widgetKit = WidgetKitModifiersImpl()
86
+ }
87
+
88
+ private struct WidgetKitModifiersImpl: WidgetKitModifiers {
89
+ func containerBackground(_ view: AnyView, style: AnyShapeStyle) -> AnyView {
90
+ guard #available(iOS 17.0, *) else {
91
+ return view
92
+ }
93
+ return AnyView(view.containerBackground(style, for: .widget))
94
+ }
95
+
96
+ func widgetAccentedRenderingMode(_ image: Image, params: [String: Any], appContext: AppContext) -> AnyView? {
97
+ guard #available(iOS 18.0, *), let modifier = try? WidgetAccentedRenderingModeModifier(from: params, appContext: appContext) else {
98
+ return nil
99
+ }
100
+ return AnyView(modifier.apply(to: image))
101
+ }
102
+ }
@@ -4,4 +4,8 @@ import ExpoModulesCore
4
4
  struct WidgetsContext {
5
5
  static let shared = WidgetsContext()
6
6
  let context: AppContext = AppContext()
7
+
8
+ private init() {
9
+ registerWidgetModifiers()
10
+ }
7
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-widgets",
3
- "version": "58.0.9",
3
+ "version": "58.0.10",
4
4
  "description": "Widgets.",
5
5
  "keywords": [
6
6
  "expo",
@@ -28,14 +28,14 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@expo/plist": "^0.10.1",
31
- "@expo/ui": "~58.0.9"
31
+ "@expo/ui": "~58.0.10"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@expo/spawn-async": "^1.8.0",
35
35
  "@types/jest": "^29.2.1",
36
36
  "@types/node": "^22.14.0",
37
37
  "@types/react": "~19.3.0",
38
- "expo": "58.0.0",
38
+ "expo": "58.0.1",
39
39
  "expo-module-scripts": "56.0.4",
40
40
  "react-native": "0.88.0-rc.3",
41
41
  "resolve-workspace-root": "^2.0.0"