react-native-anchored-menu 0.0.15 → 0.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-anchored-menu",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Headless anchored context menu / popover for React Native (iOS/Android) with stable measurement (default view host).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,7 +31,12 @@ function extractMarginsFromChild(children: React.ReactNode): AnchorMargins {
31
31
 
32
32
  export function MenuAnchor({ id, children }: MenuAnchorProps) {
33
33
  const actions = useContext(AnchoredMenuActionsContext);
34
- if (!actions) throw new Error("AnchoredMenuProvider is missing");
34
+ if (!actions) {
35
+ throw new Error(
36
+ "[react-native-anchored-menu] MenuAnchor must be used within an AnchoredMenuProvider. " +
37
+ "Make sure to wrap your component tree with <AnchoredMenuProvider> or <AnchoredMenuLayer>."
38
+ );
39
+ }
35
40
 
36
41
  const ref = useRef<View>(null) as AnchorRefObject;
37
42
 
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
2
- import { Platform } from "react-native";
2
+ import { AppState, Platform } from "react-native";
3
3
  import {
4
4
  AnchoredMenuActionsContext,
5
5
  AnchoredMenuStateContext,
@@ -25,6 +25,7 @@ import type {
25
25
  * Provider config
26
26
  * - defaultHost: which host to use when `open()` doesn't specify one (default: "view")
27
27
  * - autoHost: automatically mounts the host implementation (default: true)
28
+ * - autoCloseOnBackground: automatically close menus when app goes to background (default: true)
28
29
  */
29
30
  export function AnchoredMenuProvider({
30
31
  children,
@@ -32,6 +33,7 @@ export function AnchoredMenuProvider({
32
33
  host,
33
34
  defaultHost = (host ?? "view") as HostType,
34
35
  autoHost = true,
36
+ autoCloseOnBackground = true,
35
37
  }: AnchoredMenuProviderProps) {
36
38
  const anchorsRef = useRef(new Map<string, any>()); // id -> ref
37
39
  const pendingOpenRafRef = useRef<number | null>(null);
@@ -110,9 +112,33 @@ export function AnchoredMenuProvider({
110
112
  // Register this provider globally so parents can route `open({ id })` to the correct layer.
111
113
  useEffect(() => {
112
114
  const entry = { anchors: anchorsRef.current, setRequest };
113
- return registerProvider(entry);
115
+ const unregister = registerProvider(entry);
116
+ return () => {
117
+ // Close menu if open when provider unmounts
118
+ if (storeRef.current?.getSnapshot().isOpen) {
119
+ setRequest(null);
120
+ }
121
+ unregister();
122
+ };
114
123
  }, [setRequest]);
115
124
 
125
+ // Auto-close menu when app goes to background to avoid weird states
126
+ useEffect(() => {
127
+ if (!autoCloseOnBackground) return;
128
+
129
+ const subscription = AppState.addEventListener("change", (nextAppState: string) => {
130
+ if (nextAppState === "background" || nextAppState === "inactive") {
131
+ if (storeRef.current?.getSnapshot().isOpen) {
132
+ setRequest(null);
133
+ }
134
+ }
135
+ });
136
+
137
+ return () => {
138
+ subscription.remove();
139
+ };
140
+ }, [setRequest, autoCloseOnBackground]);
141
+
116
142
  const open = useCallback((payload: OpenMenuOptions) => {
117
143
  // Defer by default to avoid "open tap" being interpreted as an outside press
118
144
  // when a host mounts a Pressable backdrop during the active gesture.
@@ -138,8 +164,18 @@ export function AnchoredMenuProvider({
138
164
  );
139
165
  }
140
166
  const target = findProviderForAnchorId(anchorId);
141
- if (target && target.setRequest)
167
+ if (target && target.setRequest) {
142
168
  return target.setRequest(payload as MenuRequest);
169
+ }
170
+ // Anchor not found in any provider
171
+ if (__DEV__) {
172
+ // eslint-disable-next-line no-console
173
+ console.warn(
174
+ `[react-native-anchored-menu] Anchor with id="${anchorId}" not found in any provider. ` +
175
+ "Make sure the MenuAnchor is mounted and the id matches."
176
+ );
177
+ }
178
+ return; // Don't open menu if anchor doesn't exist
143
179
  }
144
180
 
145
181
  setRequest(payload as MenuRequest);
@@ -191,4 +227,3 @@ export function AnchoredMenuProvider({
191
227
  </AnchoredMenuActionsContext.Provider>
192
228
  );
193
229
  }
194
-
@@ -3,19 +3,27 @@ import {
3
3
  AnchoredMenuActionsContext,
4
4
  AnchoredMenuStateContext,
5
5
  } from "../core/context";
6
+ import { useAnchoredMenuState } from "./useAnchoredMenuState";
6
7
 
7
8
  export function useAnchoredMenu() {
8
9
  const actions = useContext(AnchoredMenuActionsContext);
9
10
  const state = useContext(AnchoredMenuStateContext);
10
- if (!actions || !state) throw new Error("AnchoredMenuProvider is missing");
11
+ if (!actions || !state) {
12
+ throw new Error(
13
+ "[react-native-anchored-menu] useAnchoredMenu must be used within an AnchoredMenuProvider. " +
14
+ "Make sure to wrap your component tree with <AnchoredMenuProvider> or <AnchoredMenuLayer>."
15
+ );
16
+ }
17
+
18
+ // Use useAnchoredMenuState to properly subscribe to isOpen changes
19
+ const isOpen = useAnchoredMenuState((s) => s.isOpen);
11
20
 
12
21
  return useMemo(
13
22
  () => ({
14
23
  open: actions.open,
15
24
  close: actions.close,
16
- isOpen: state.getSnapshot().isOpen,
25
+ isOpen,
17
26
  }),
18
- [actions.open, actions.close, state]
27
+ [actions.open, actions.close, isOpen]
19
28
  );
20
29
  }
21
-
@@ -11,7 +11,12 @@ export function useAnchoredMenuActions(): {
11
11
  close: () => void;
12
12
  } {
13
13
  const actions = useContext(AnchoredMenuActionsContext);
14
- if (!actions) throw new Error("AnchoredMenuProvider is missing");
14
+ if (!actions) {
15
+ throw new Error(
16
+ "[react-native-anchored-menu] useAnchoredMenuActions must be used within an AnchoredMenuProvider. " +
17
+ "Make sure to wrap your component tree with <AnchoredMenuProvider> or <AnchoredMenuLayer>."
18
+ );
19
+ }
15
20
 
16
21
  return useMemo(
17
22
  () => ({
@@ -37,7 +37,12 @@ export function useAnchoredMenuState<T = MenuState>(
37
37
  selector: (state: MenuState) => T = ((s: MenuState) => s) as (state: MenuState) => T
38
38
  ): T {
39
39
  const store = useContext(AnchoredMenuStateContext);
40
- if (!store) throw new Error("AnchoredMenuProvider is missing");
40
+ if (!store) {
41
+ throw new Error(
42
+ "[react-native-anchored-menu] useAnchoredMenuState must be used within an AnchoredMenuProvider. " +
43
+ "Make sure to wrap your component tree with <AnchoredMenuProvider> or <AnchoredMenuLayer>."
44
+ );
45
+ }
41
46
 
42
47
  const getSelectedSnapshot = () => selector(store.getSnapshot());
43
48
  return useSyncExternalStore(
@@ -11,6 +11,7 @@ import {
11
11
  measureInWindowStable,
12
12
  } from "../utils/measure";
13
13
  import { computeMenuPosition } from "../utils/position";
14
+ import { isValidMeasurement, isValidMenuSize } from "../utils/validation";
14
15
  import { isFabricEnabled } from "../utils/runtime";
15
16
  import type {
16
17
  AnchorMeasurement,
@@ -27,7 +28,12 @@ interface MeasureCache {
27
28
  export function ModalHost() {
28
29
  const actions = useContext(AnchoredMenuActionsContext);
29
30
  const store = useContext(AnchoredMenuStateContext);
30
- if (!actions || !store) throw new Error("AnchoredMenuProvider is missing");
31
+ if (!actions || !store) {
32
+ throw new Error(
33
+ "[react-native-anchored-menu] ModalHost must be used within an AnchoredMenuProvider. " +
34
+ "This is usually handled automatically by AnchoredMenuProvider when autoHost=true."
35
+ );
36
+ }
31
37
 
32
38
  const activeHost = useAnchoredMenuState((s) => s.activeHost);
33
39
  if (activeHost !== "modal") return null;
@@ -91,7 +97,28 @@ export function ModalHost() {
91
97
  ]);
92
98
 
93
99
  if (cancelled) return;
100
+
101
+ // Validate measurements before using them
102
+ if (!isValidMeasurement(a) || !isValidMeasurement(h)) {
103
+ if (__DEV__) {
104
+ console.warn(
105
+ `[react-native-anchored-menu] Invalid measurement for anchor "${req.id}". ` +
106
+ "Menu will not be positioned correctly. This can happen during layout transitions."
107
+ );
108
+ }
109
+ return;
110
+ }
111
+
94
112
  const nextAnchorWin = applyAnchorMargins(a, refObj);
113
+ if (!isValidMeasurement(nextAnchorWin)) {
114
+ if (__DEV__) {
115
+ console.warn(
116
+ `[react-native-anchored-menu] Invalid anchor measurement after applying margins for "${req.id}".`
117
+ );
118
+ }
119
+ return;
120
+ }
121
+
95
122
  setAnchorWin(nextAnchorWin);
96
123
  setHostWin(h);
97
124
  measureCacheRef.current.set(req.id, {
@@ -134,7 +161,26 @@ export function ModalHost() {
134
161
  measure(hostRef as any, strategy === "stable" ? { tries } : undefined),
135
162
  ]);
136
163
 
164
+ // Validate measurements before using them
165
+ if (!isValidMeasurement(a) || !isValidMeasurement(h)) {
166
+ if (__DEV__) {
167
+ console.warn(
168
+ `[react-native-anchored-menu] Invalid measurement during keyboard show for anchor "${req.id}".`
169
+ );
170
+ }
171
+ return;
172
+ }
173
+
137
174
  const nextAnchorWin = applyAnchorMargins(a, refObj);
175
+ if (!isValidMeasurement(nextAnchorWin)) {
176
+ if (__DEV__) {
177
+ console.warn(
178
+ `[react-native-anchored-menu] Invalid anchor measurement after applying margins during keyboard show for "${req.id}".`
179
+ );
180
+ }
181
+ return;
182
+ }
183
+
138
184
  setAnchorWin(nextAnchorWin);
139
185
  setHostWin(h);
140
186
  measureCacheRef.current.set(req.id, {
@@ -168,7 +214,26 @@ export function ModalHost() {
168
214
  measure(hostRef as any, strategy === "stable" ? { tries } : undefined),
169
215
  ]);
170
216
 
217
+ // Validate measurements before using them
218
+ if (!isValidMeasurement(a) || !isValidMeasurement(h)) {
219
+ if (__DEV__) {
220
+ console.warn(
221
+ `[react-native-anchored-menu] Invalid measurement during keyboard hide for anchor "${req.id}".`
222
+ );
223
+ }
224
+ return;
225
+ }
226
+
171
227
  const nextAnchorWin = applyAnchorMargins(a, refObj);
228
+ if (!isValidMeasurement(nextAnchorWin)) {
229
+ if (__DEV__) {
230
+ console.warn(
231
+ `[react-native-anchored-menu] Invalid anchor measurement after applying margins during keyboard hide for "${req.id}".`
232
+ );
233
+ }
234
+ return;
235
+ }
236
+
172
237
  setAnchorWin(nextAnchorWin);
173
238
  setHostWin(h);
174
239
  measureCacheRef.current.set(req.id, {
@@ -200,6 +265,17 @@ export function ModalHost() {
200
265
 
201
266
  const position = useMemo(() => {
202
267
  if (!req || !anchorInHost) return null;
268
+
269
+ // Validate anchor measurement before computing position
270
+ if (!isValidMeasurement(anchorInHost)) {
271
+ if (__DEV__) {
272
+ console.warn(
273
+ `[react-native-anchored-menu] Cannot compute position for anchor "${req.id}": invalid measurement.`
274
+ );
275
+ }
276
+ return null;
277
+ }
278
+
203
279
  const viewport: Viewport | undefined =
204
280
  hostSize.width && hostSize.height
205
281
  ? {
@@ -209,7 +285,7 @@ export function ModalHost() {
209
285
  : undefined;
210
286
  return computeMenuPosition({
211
287
  anchor: anchorInHost,
212
- menuSize,
288
+ menuSize: isValidMenuSize(menuSize) ? menuSize : null,
213
289
  viewport,
214
290
  placement: req.placement ?? "auto",
215
291
  offset: req.offset ?? 8,
@@ -219,7 +295,7 @@ export function ModalHost() {
219
295
  });
220
296
  }, [req, anchorInHost, menuSize, hostSize, keyboardHeight]);
221
297
 
222
- const needsInitialMeasure = menuSize.width === 0 || menuSize.height === 0;
298
+ const needsInitialMeasure = !isValidMenuSize(menuSize);
223
299
 
224
300
  const statusBarTranslucent =
225
301
  req?.statusBarTranslucent ?? (Platform.OS === "android" ? false : true);
@@ -11,6 +11,7 @@ import {
11
11
  measureInWindowStable,
12
12
  } from "../utils/measure";
13
13
  import { computeMenuPosition } from "../utils/position";
14
+ import { isValidMeasurement, isValidMenuSize } from "../utils/validation";
14
15
  import type {
15
16
  AnchorMeasurement,
16
17
  MenuSize,
@@ -35,7 +36,12 @@ interface MeasureCache {
35
36
  export function ViewHost() {
36
37
  const actions = useContext(AnchoredMenuActionsContext);
37
38
  const store = useContext(AnchoredMenuStateContext);
38
- if (!actions || !store) throw new Error("AnchoredMenuProvider is missing");
39
+ if (!actions || !store) {
40
+ throw new Error(
41
+ "[react-native-anchored-menu] ViewHost must be used within an AnchoredMenuProvider. " +
42
+ "This is usually handled automatically by AnchoredMenuProvider when autoHost=true."
43
+ );
44
+ }
39
45
 
40
46
  const activeHost = useAnchoredMenuState((s) => s.activeHost);
41
47
  if (activeHost !== "view") return null;
@@ -96,7 +102,28 @@ export function ViewHost() {
96
102
  ]);
97
103
 
98
104
  if (cancelled) return;
105
+
106
+ // Validate measurements before using them
107
+ if (!isValidMeasurement(a) || !isValidMeasurement(h)) {
108
+ if (__DEV__) {
109
+ console.warn(
110
+ `[react-native-anchored-menu] Invalid measurement for anchor "${req.id}". ` +
111
+ "Menu will not be positioned correctly. This can happen during layout transitions."
112
+ );
113
+ }
114
+ return;
115
+ }
116
+
99
117
  const nextAnchorWin = applyAnchorMargins(a, refObj);
118
+ if (!isValidMeasurement(nextAnchorWin)) {
119
+ if (__DEV__) {
120
+ console.warn(
121
+ `[react-native-anchored-menu] Invalid anchor measurement after applying margins for "${req.id}".`
122
+ );
123
+ }
124
+ return;
125
+ }
126
+
100
127
  setAnchorWin(nextAnchorWin);
101
128
  setHostWin(h);
102
129
  measureCacheRef.current.set(req.id, {
@@ -139,7 +166,26 @@ export function ViewHost() {
139
166
  measure(hostRef as any, strategy === "stable" ? { tries } : undefined),
140
167
  ]);
141
168
 
169
+ // Validate measurements before using them
170
+ if (!isValidMeasurement(a) || !isValidMeasurement(h)) {
171
+ if (__DEV__) {
172
+ console.warn(
173
+ `[react-native-anchored-menu] Invalid measurement during keyboard show for anchor "${req.id}".`
174
+ );
175
+ }
176
+ return;
177
+ }
178
+
142
179
  const nextAnchorWin = applyAnchorMargins(a, refObj);
180
+ if (!isValidMeasurement(nextAnchorWin)) {
181
+ if (__DEV__) {
182
+ console.warn(
183
+ `[react-native-anchored-menu] Invalid anchor measurement after applying margins during keyboard show for "${req.id}".`
184
+ );
185
+ }
186
+ return;
187
+ }
188
+
143
189
  setAnchorWin(nextAnchorWin);
144
190
  setHostWin(h);
145
191
  measureCacheRef.current.set(req.id, {
@@ -173,7 +219,26 @@ export function ViewHost() {
173
219
  measure(hostRef as any, strategy === "stable" ? { tries } : undefined),
174
220
  ]);
175
221
 
222
+ // Validate measurements before using them
223
+ if (!isValidMeasurement(a) || !isValidMeasurement(h)) {
224
+ if (__DEV__) {
225
+ console.warn(
226
+ `[react-native-anchored-menu] Invalid measurement during keyboard hide for anchor "${req.id}".`
227
+ );
228
+ }
229
+ return;
230
+ }
231
+
176
232
  const nextAnchorWin = applyAnchorMargins(a, refObj);
233
+ if (!isValidMeasurement(nextAnchorWin)) {
234
+ if (__DEV__) {
235
+ console.warn(
236
+ `[react-native-anchored-menu] Invalid anchor measurement after applying margins during keyboard hide for "${req.id}".`
237
+ );
238
+ }
239
+ return;
240
+ }
241
+
177
242
  setAnchorWin(nextAnchorWin);
178
243
  setHostWin(h);
179
244
  measureCacheRef.current.set(req.id, {
@@ -204,6 +269,17 @@ export function ViewHost() {
204
269
 
205
270
  const position = useMemo(() => {
206
271
  if (!req || !anchorInHost) return null;
272
+
273
+ // Validate anchor measurement before computing position
274
+ if (!isValidMeasurement(anchorInHost)) {
275
+ if (__DEV__) {
276
+ console.warn(
277
+ `[react-native-anchored-menu] Cannot compute position for anchor "${req.id}": invalid measurement.`
278
+ );
279
+ }
280
+ return null;
281
+ }
282
+
207
283
  const viewport: Viewport | undefined =
208
284
  hostSize.width && hostSize.height
209
285
  ? {
@@ -214,7 +290,7 @@ export function ViewHost() {
214
290
 
215
291
  return computeMenuPosition({
216
292
  anchor: anchorInHost,
217
- menuSize,
293
+ menuSize: isValidMenuSize(menuSize) ? menuSize : null,
218
294
  viewport,
219
295
  placement: req.placement ?? "auto",
220
296
  offset: req.offset ?? 8,
@@ -224,7 +300,7 @@ export function ViewHost() {
224
300
  });
225
301
  }, [req, anchorInHost, menuSize, hostSize, keyboardHeight]);
226
302
 
227
- const needsInitialMeasure = menuSize.width === 0 || menuSize.height === 0;
303
+ const needsInitialMeasure = !isValidMenuSize(menuSize);
228
304
  if (!visible) return null;
229
305
 
230
306
  return (
package/src/types.ts CHANGED
@@ -152,6 +152,8 @@ export interface AnchoredMenuProviderProps {
152
152
  children: ReactNode;
153
153
  defaultHost?: HostType;
154
154
  autoHost?: boolean;
155
+ /** Whether to automatically close menus when app goes to background/inactive (default: true) */
156
+ autoCloseOnBackground?: boolean;
155
157
  /** @deprecated Use defaultHost instead */
156
158
  host?: HostType;
157
159
  }
@@ -0,0 +1,55 @@
1
+ import type { AnchorMeasurement, MenuSize } from "../types";
2
+
3
+ /**
4
+ * Validates that a measurement has valid dimensions.
5
+ * Returns true if the measurement is valid (non-null and has positive dimensions).
6
+ */
7
+ export function isValidMeasurement(
8
+ measurement: AnchorMeasurement | null | undefined
9
+ ): measurement is AnchorMeasurement {
10
+ if (!measurement) return false;
11
+ return (
12
+ typeof measurement.width === "number" &&
13
+ typeof measurement.height === "number" &&
14
+ typeof measurement.pageX === "number" &&
15
+ typeof measurement.pageY === "number" &&
16
+ measurement.width > 0 &&
17
+ measurement.height > 0 &&
18
+ !isNaN(measurement.width) &&
19
+ !isNaN(measurement.height) &&
20
+ !isNaN(measurement.pageX) &&
21
+ !isNaN(measurement.pageY)
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Validates that a menu size has valid dimensions.
27
+ * Returns true if the size is valid (non-null and has positive dimensions).
28
+ */
29
+ export function isValidMenuSize(size: MenuSize | null | undefined): size is MenuSize {
30
+ if (!size) return false;
31
+ return (
32
+ typeof size.width === "number" &&
33
+ typeof size.height === "number" &&
34
+ size.width > 0 &&
35
+ size.height > 0 &&
36
+ !isNaN(size.width) &&
37
+ !isNaN(size.height)
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Checks if a measurement looks invalid (e.g., all zeros, which can happen
43
+ * on Android during layout transitions).
44
+ */
45
+ export function isInvalidMeasurement(
46
+ measurement: AnchorMeasurement | null | undefined
47
+ ): boolean {
48
+ if (!measurement) return true;
49
+ return (
50
+ measurement.pageX === 0 &&
51
+ measurement.pageY === 0 &&
52
+ measurement.width === 0 &&
53
+ measurement.height === 0
54
+ );
55
+ }
@@ -1,26 +0,0 @@
1
- import React from "react";
2
- import { View } from "react-native";
3
- import { AnchoredMenuProvider } from "../core/provider";
4
-
5
- /**
6
- * AnchoredMenuLayer
7
- *
8
- * A convenience wrapper that ensures the "view" host has a stable layout box to fill.
9
- * Use it at app root and inside RN <Modal> (wrap the full-screen modal container).
10
- */
11
- export function AnchoredMenuLayer({
12
- children,
13
- style,
14
- defaultHost = "view",
15
- ...providerProps
16
- }) {
17
- return (
18
- <View style={[{ flex: 1, position: "relative" }, style]}>
19
- <AnchoredMenuProvider defaultHost={defaultHost} {...providerProps}>
20
- {children}
21
- </AnchoredMenuProvider>
22
- </View>
23
- );
24
- }
25
-
26
-
@@ -1,55 +0,0 @@
1
- import React, { useContext, useEffect, useRef } from "react";
2
- import { StyleSheet, View } from "react-native";
3
- import { AnchoredMenuActionsContext } from "../core/context";
4
-
5
- function extractMarginsFromChild(children) {
6
- try {
7
- const child = React.Children.only(children);
8
- const flat = StyleSheet.flatten(child?.props?.style) || {};
9
- const mv =
10
- typeof flat.marginVertical === "number" ? flat.marginVertical : undefined;
11
- const mh =
12
- typeof flat.marginHorizontal === "number"
13
- ? flat.marginHorizontal
14
- : undefined;
15
- const m = typeof flat.margin === "number" ? flat.margin : 0;
16
-
17
- const top = (typeof flat.marginTop === "number" ? flat.marginTop : mv) ?? m;
18
- const bottom =
19
- (typeof flat.marginBottom === "number" ? flat.marginBottom : mv) ?? m;
20
- const left =
21
- (typeof flat.marginLeft === "number" ? flat.marginLeft : mh) ?? m;
22
- const right =
23
- (typeof flat.marginRight === "number" ? flat.marginRight : mh) ?? m;
24
-
25
- return { top, bottom, left, right };
26
- } catch {
27
- return { top: 0, bottom: 0, left: 0, right: 0 };
28
- }
29
- }
30
-
31
- export function MenuAnchor({ id, children }) {
32
- const actions = useContext(AnchoredMenuActionsContext);
33
- if (!actions) throw new Error("AnchoredMenuProvider is missing");
34
-
35
- const ref = useRef(null);
36
-
37
- useEffect(() => {
38
- // Store child margins on the ref object so measurement can exclude margins.
39
- // This avoids offset differences when the anchored child uses e.g. marginBottom.
40
- ref.__anchoredMenuMargins = extractMarginsFromChild(children);
41
-
42
- actions.registerAnchor(id, ref);
43
-
44
- return () => {
45
- actions.unregisterAnchor(id);
46
- };
47
- }, [actions, id, children]);
48
-
49
- // collapsable={false} is important for Android measurement reliability
50
- return (
51
- <View ref={ref} collapsable={false}>
52
- {children}
53
- </View>
54
- );
55
- }
@@ -1,10 +0,0 @@
1
- import React, { createContext } from "react";
2
-
3
- /**
4
- * Split contexts to avoid re-rendering all anchors when `request` changes.
5
- *
6
- * - Actions context: stable references (open/close/register/unregister/anchors map)
7
- * - State context: request + derived values that change during open/close
8
- */
9
- export const AnchoredMenuActionsContext = createContext(null);
10
- export const AnchoredMenuStateContext = createContext(null);