@react-native-ohos/elements 2.3.9-rc.1 → 2.4.0-rc.1
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/README.OpenSource +1 -1
- package/lib/module/Header/Header.js +3 -2
- package/lib/module/Header/Header.js.map +1 -1
- package/lib/module/Header/HeaderButton.js +7 -3
- package/lib/module/Header/HeaderButton.js.map +1 -1
- package/lib/module/Header/HeaderSearchBar.js +2 -3
- package/lib/module/Header/HeaderSearchBar.js.map +1 -1
- package/lib/module/Header/HeaderTitle.js +1 -1
- package/lib/module/Header/HeaderTitle.js.map +1 -1
- package/lib/module/PlatformPressable.js +18 -11
- package/lib/module/PlatformPressable.js.map +1 -1
- package/lib/module/SafeAreaProviderCompat.js +19 -17
- package/lib/module/SafeAreaProviderCompat.js.map +1 -1
- package/lib/module/Screen.js +5 -5
- package/lib/module/Screen.js.map +1 -1
- package/lib/module/getNamedContext.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useFrameSize.js +174 -0
- package/lib/module/useFrameSize.js.map +1 -0
- package/lib/typescript/src/Header/Header.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderButton.d.ts +2 -1
- package/lib/typescript/src/Header/HeaderButton.d.ts.map +1 -1
- package/lib/typescript/src/Header/HeaderSearchBar.d.ts.map +1 -1
- package/lib/typescript/src/PlatformPressable.d.ts +13 -7
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
- package/lib/typescript/src/SafeAreaProviderCompat.d.ts.map +1 -1
- package/lib/typescript/src/Screen.d.ts.map +1 -1
- package/lib/typescript/src/getNamedContext.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/useFrameSize.d.ts +15 -0
- package/lib/typescript/src/useFrameSize.d.ts.map +1 -0
- package/package.json +85 -83
- package/src/Header/Header.tsx +3 -5
- package/src/Header/HeaderButton.tsx +21 -12
- package/src/Header/HeaderSearchBar.tsx +2 -3
- package/src/Header/HeaderTitle.tsx +1 -1
- package/src/PlatformPressable.tsx +52 -29
- package/src/SafeAreaProviderCompat.tsx +23 -19
- package/src/Screen.tsx +8 -10
- package/src/getNamedContext.tsx +0 -1
- package/src/index.tsx +1 -0
- package/src/useFrameSize.tsx +254 -0
|
@@ -17,12 +17,15 @@ type HoverEffectProps = {
|
|
|
17
17
|
activeOpacity?: number;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
export type Props = Omit<PressableProps, 'style'> & {
|
|
20
|
+
export type Props = Omit<PressableProps, 'style' | 'onPress'> & {
|
|
21
|
+
href?: string;
|
|
21
22
|
pressColor?: string;
|
|
22
23
|
pressOpacity?: number;
|
|
23
24
|
hoverEffect?: HoverEffectProps;
|
|
24
25
|
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
|
25
|
-
|
|
26
|
+
onPress?: (
|
|
27
|
+
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
|
28
|
+
) => void;
|
|
26
29
|
children: React.ReactNode;
|
|
27
30
|
};
|
|
28
31
|
|
|
@@ -37,19 +40,22 @@ const useNativeDriver = Platform.OS !== 'web';
|
|
|
37
40
|
/**
|
|
38
41
|
* PlatformPressable provides an abstraction on top of Pressable to handle platform differences.
|
|
39
42
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
function PlatformPressableInternal(
|
|
44
|
+
{
|
|
45
|
+
disabled,
|
|
46
|
+
onPress,
|
|
47
|
+
onPressIn,
|
|
48
|
+
onPressOut,
|
|
49
|
+
android_ripple,
|
|
50
|
+
pressColor,
|
|
51
|
+
pressOpacity = 0.3,
|
|
52
|
+
hoverEffect,
|
|
53
|
+
style,
|
|
54
|
+
children,
|
|
55
|
+
...rest
|
|
56
|
+
}: Props,
|
|
57
|
+
ref: React.Ref<React.ComponentRef<typeof AnimatedPressable>>
|
|
58
|
+
) {
|
|
53
59
|
const { dark } = useTheme();
|
|
54
60
|
const [opacity] = React.useState(() => new Animated.Value(1));
|
|
55
61
|
|
|
@@ -66,18 +72,31 @@ export function PlatformPressable({
|
|
|
66
72
|
}).start();
|
|
67
73
|
};
|
|
68
74
|
|
|
69
|
-
const handlePress = (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
e.
|
|
78
|
-
|
|
75
|
+
const handlePress = (
|
|
76
|
+
e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
|
|
77
|
+
) => {
|
|
78
|
+
if (Platform.OS === 'web' && rest.href !== null) {
|
|
79
|
+
// ignore clicks with modifier keys
|
|
80
|
+
const hasModifierKey =
|
|
81
|
+
('metaKey' in e && e.metaKey) ||
|
|
82
|
+
('altKey' in e && e.altKey) ||
|
|
83
|
+
('ctrlKey' in e && e.ctrlKey) ||
|
|
84
|
+
('shiftKey' in e && e.shiftKey);
|
|
85
|
+
|
|
86
|
+
// only handle left clicks
|
|
87
|
+
const isLeftClick =
|
|
88
|
+
'button' in e ? e.button == null || e.button === 0 : true;
|
|
89
|
+
|
|
90
|
+
// let browser handle "target=_blank" etc.
|
|
91
|
+
const isSelfTarget =
|
|
92
|
+
e.currentTarget && 'target' in e.currentTarget
|
|
93
|
+
? [undefined, null, '', 'self'].includes(e.currentTarget.target)
|
|
94
|
+
: true;
|
|
95
|
+
|
|
79
96
|
if (!hasModifierKey && isLeftClick && isSelfTarget) {
|
|
80
97
|
e.preventDefault();
|
|
98
|
+
// call `onPress` only when browser default is prevented
|
|
99
|
+
// this prevents app from handling the click when a link is being opened
|
|
81
100
|
onPress?.(e);
|
|
82
101
|
}
|
|
83
102
|
} else {
|
|
@@ -97,10 +116,9 @@ export function PlatformPressable({
|
|
|
97
116
|
|
|
98
117
|
return (
|
|
99
118
|
<AnimatedPressable
|
|
119
|
+
ref={ref}
|
|
100
120
|
accessible
|
|
101
|
-
|
|
102
|
-
Platform.OS === 'web' && rest.href != null ? 'link' : 'button'
|
|
103
|
-
}
|
|
121
|
+
role={Platform.OS === 'web' && rest.href != null ? 'link' : 'button'}
|
|
104
122
|
onPress={disabled ? undefined : handlePress}
|
|
105
123
|
onPressIn={handlePressIn}
|
|
106
124
|
onPressOut={handlePressOut}
|
|
@@ -137,6 +155,10 @@ export function PlatformPressable({
|
|
|
137
155
|
);
|
|
138
156
|
}
|
|
139
157
|
|
|
158
|
+
export const PlatformPressable = React.forwardRef(PlatformPressableInternal);
|
|
159
|
+
|
|
160
|
+
PlatformPressable.displayName = 'PlatformPressable';
|
|
161
|
+
|
|
140
162
|
const css = String.raw;
|
|
141
163
|
|
|
142
164
|
const CLASS_NAME = `__react-navigation_elements_Pressable_hover`;
|
|
@@ -152,6 +174,7 @@ const CSS_TEXT = css`
|
|
|
152
174
|
background-color: var(--overlay-color);
|
|
153
175
|
opacity: 0;
|
|
154
176
|
transition: opacity 0.15s;
|
|
177
|
+
pointer-events: none;
|
|
155
178
|
}
|
|
156
179
|
|
|
157
180
|
a:hover > .${CLASS_NAME}, button:hover > .${CLASS_NAME} {
|
|
@@ -174,7 +197,7 @@ const HoverEffect = ({
|
|
|
174
197
|
|
|
175
198
|
return (
|
|
176
199
|
<>
|
|
177
|
-
<style
|
|
200
|
+
<style
|
|
178
201
|
// @ts-expect-error: href and precedence are only available on React 19
|
|
179
202
|
href={CLASS_NAME}
|
|
180
203
|
// eslint-disable-next-line @eslint-react/dom/no-unknown-property
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
SafeAreaProvider,
|
|
14
14
|
} from 'react-native-safe-area-context';
|
|
15
15
|
|
|
16
|
+
import { FrameSizeProvider } from './useFrameSize';
|
|
17
|
+
|
|
16
18
|
type Props = {
|
|
17
19
|
children: React.ReactNode;
|
|
18
20
|
style?: StyleProp<ViewStyle>;
|
|
@@ -26,29 +28,31 @@ const { width = 0, height = 0 } = Dimensions.get('window');
|
|
|
26
28
|
const initialMetrics =
|
|
27
29
|
Platform.OS === 'web' || initialWindowMetrics == null
|
|
28
30
|
? {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
frame: { x: 0, y: 0, width, height },
|
|
32
|
+
insets: { top: 0, left: 0, right: 0, bottom: 0 },
|
|
33
|
+
}
|
|
32
34
|
: initialWindowMetrics;
|
|
33
35
|
|
|
34
36
|
export function SafeAreaProviderCompat({ children, style }: Props) {
|
|
37
|
+
const insets = React.useContext(SafeAreaInsetsContext);
|
|
38
|
+
|
|
39
|
+
children = (
|
|
40
|
+
<FrameSizeProvider initialFrame={initialMetrics.frame}>
|
|
41
|
+
{children}
|
|
42
|
+
</FrameSizeProvider>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (insets) {
|
|
46
|
+
// If we already have insets, don't wrap the stack in another safe area provider
|
|
47
|
+
// This avoids an issue with updates at the cost of potentially incorrect values
|
|
48
|
+
// https://github.com/react-navigation/react-navigation/issues/174
|
|
49
|
+
return <View style={[styles.container, style]}>{children}</View>;
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
return (
|
|
36
|
-
<
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
// If we already have insets, don't wrap the stack in another safe area provider
|
|
40
|
-
// This avoids an issue with updates at the cost of potentially incorrect values
|
|
41
|
-
// https://github.com/react-navigation/react-navigation/issues/174
|
|
42
|
-
return <View style={[styles.container, style]}>{children}</View>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<SafeAreaProvider initialMetrics={initialMetrics} style={style}>
|
|
47
|
-
{children}
|
|
48
|
-
</SafeAreaProvider>
|
|
49
|
-
);
|
|
50
|
-
}}
|
|
51
|
-
</SafeAreaInsetsContext.Consumer>
|
|
53
|
+
<SafeAreaProvider initialMetrics={initialMetrics} style={style}>
|
|
54
|
+
{children}
|
|
55
|
+
</SafeAreaProvider>
|
|
52
56
|
);
|
|
53
57
|
}
|
|
54
58
|
|
package/src/Screen.tsx
CHANGED
|
@@ -13,15 +13,13 @@ import {
|
|
|
13
13
|
View,
|
|
14
14
|
type ViewStyle,
|
|
15
15
|
} from 'react-native';
|
|
16
|
-
import {
|
|
17
|
-
useSafeAreaFrame,
|
|
18
|
-
useSafeAreaInsets,
|
|
19
|
-
} from 'react-native-safe-area-context';
|
|
16
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
20
17
|
|
|
21
18
|
import { Background } from './Background';
|
|
22
19
|
import { getDefaultHeaderHeight } from './Header/getDefaultHeaderHeight';
|
|
23
20
|
import { HeaderHeightContext } from './Header/HeaderHeightContext';
|
|
24
21
|
import { HeaderShownContext } from './Header/HeaderShownContext';
|
|
22
|
+
import { useFrameSize } from './useFrameSize';
|
|
25
23
|
|
|
26
24
|
type Props = {
|
|
27
25
|
focused: boolean;
|
|
@@ -37,7 +35,6 @@ type Props = {
|
|
|
37
35
|
};
|
|
38
36
|
|
|
39
37
|
export function Screen(props: Props) {
|
|
40
|
-
const dimensions = useSafeAreaFrame();
|
|
41
38
|
const insets = useSafeAreaInsets();
|
|
42
39
|
|
|
43
40
|
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
|
@@ -56,14 +53,15 @@ export function Screen(props: Props) {
|
|
|
56
53
|
style,
|
|
57
54
|
} = props;
|
|
58
55
|
|
|
59
|
-
const
|
|
60
|
-
getDefaultHeaderHeight(
|
|
56
|
+
const defaultHeaderHeight = useFrameSize((size) =>
|
|
57
|
+
getDefaultHeaderHeight(size, modal, headerStatusBarHeight)
|
|
61
58
|
);
|
|
62
59
|
|
|
60
|
+
const [headerHeight, setHeaderHeight] = React.useState(defaultHeaderHeight);
|
|
61
|
+
|
|
63
62
|
return (
|
|
64
63
|
<Background
|
|
65
|
-
|
|
66
|
-
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
|
|
64
|
+
aria-hidden={!focused}
|
|
67
65
|
style={[styles.container, style]}
|
|
68
66
|
// On Fabric we need to disable collapsing for the background to ensure
|
|
69
67
|
// that we won't render unnecessary views due to the view flattening.
|
|
@@ -94,7 +92,7 @@ export function Screen(props: Props) {
|
|
|
94
92
|
value={isParentHeaderShown || headerShown !== false}
|
|
95
93
|
>
|
|
96
94
|
<HeaderHeightContext.Provider
|
|
97
|
-
value={headerShown ? headerHeight : parentHeaderHeight ?? 0}
|
|
95
|
+
value={headerShown ? headerHeight : (parentHeaderHeight ?? 0)}
|
|
98
96
|
>
|
|
99
97
|
{children}
|
|
100
98
|
</HeaderHeightContext.Provider>
|
package/src/getNamedContext.tsx
CHANGED
package/src/index.tsx
CHANGED
|
@@ -26,6 +26,7 @@ export { ResourceSavingView } from './ResourceSavingView';
|
|
|
26
26
|
export { SafeAreaProviderCompat } from './SafeAreaProviderCompat';
|
|
27
27
|
export { Screen } from './Screen';
|
|
28
28
|
export { Text } from './Text';
|
|
29
|
+
export { useFrameSize } from './useFrameSize';
|
|
29
30
|
|
|
30
31
|
export const Assets = [
|
|
31
32
|
backIcon,
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
type StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
type ViewStyle,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import {
|
|
9
|
+
// eslint-disable-next-line no-restricted-imports
|
|
10
|
+
useSafeAreaFrame,
|
|
11
|
+
} from 'react-native-safe-area-context';
|
|
12
|
+
import useLatestCallback from 'use-latest-callback';
|
|
13
|
+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector';
|
|
14
|
+
|
|
15
|
+
// Load with require to avoid error from webpack due to missing export in older versions
|
|
16
|
+
// eslint-disable-next-line import-x/no-commonjs
|
|
17
|
+
type SafeAreaListenerProps = {
|
|
18
|
+
onChange: (event: { frame: Frame }) => void;
|
|
19
|
+
style?: StyleProp<ViewStyle>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const SafeAreaListener = (require('react-native-safe-area-context') as {
|
|
23
|
+
SafeAreaListener?: React.ComponentType<SafeAreaListenerProps>;
|
|
24
|
+
}).SafeAreaListener;
|
|
25
|
+
|
|
26
|
+
type Frame = {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type Listener = () => void;
|
|
32
|
+
|
|
33
|
+
type RemoveListener = () => void;
|
|
34
|
+
|
|
35
|
+
type FrameContextType = {
|
|
36
|
+
getCurrent: () => Frame;
|
|
37
|
+
subscribe: (listener: Listener) => RemoveListener;
|
|
38
|
+
subscribeThrottled: (listener: Listener) => RemoveListener;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const FrameContext = React.createContext<FrameContextType | undefined>(
|
|
42
|
+
undefined
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export function useFrameSize<T>(
|
|
46
|
+
selector: (frame: Frame) => T,
|
|
47
|
+
throttle?: boolean
|
|
48
|
+
): T {
|
|
49
|
+
const context = React.useContext(FrameContext);
|
|
50
|
+
|
|
51
|
+
if (context == null) {
|
|
52
|
+
throw new Error('useFrameSize must be used within a FrameSizeProvider');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const value = useSyncExternalStoreWithSelector(
|
|
56
|
+
throttle ? context.subscribeThrottled : context.subscribe,
|
|
57
|
+
context.getCurrent,
|
|
58
|
+
context.getCurrent,
|
|
59
|
+
selector
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type FrameSizeProviderProps = {
|
|
66
|
+
initialFrame: Frame;
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
style?: StyleProp<ViewStyle>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function FrameSizeProvider({
|
|
72
|
+
initialFrame,
|
|
73
|
+
children,
|
|
74
|
+
}: FrameSizeProviderProps) {
|
|
75
|
+
const context = React.useContext(FrameContext);
|
|
76
|
+
|
|
77
|
+
if (context != null) {
|
|
78
|
+
// If the context is already present, don't wrap again
|
|
79
|
+
return children;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<FrameSizeProviderInner initialFrame={initialFrame}>
|
|
84
|
+
{children}
|
|
85
|
+
</FrameSizeProviderInner>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function FrameSizeProviderInner({
|
|
90
|
+
initialFrame,
|
|
91
|
+
children,
|
|
92
|
+
}: FrameSizeProviderProps) {
|
|
93
|
+
const frameRef = React.useRef<Frame>({
|
|
94
|
+
width: initialFrame.width,
|
|
95
|
+
height: initialFrame.height,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const listeners = React.useRef<Set<Listener>>(new Set());
|
|
99
|
+
|
|
100
|
+
const getCurrent = useLatestCallback(() => frameRef.current);
|
|
101
|
+
|
|
102
|
+
const subscribe = useLatestCallback((listener: Listener): RemoveListener => {
|
|
103
|
+
listeners.current.add(listener);
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
listeners.current.delete(listener);
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const subscribeThrottled = useLatestCallback(
|
|
111
|
+
(listener: Listener): RemoveListener => {
|
|
112
|
+
const delay = 100; // Throttle delay in milliseconds
|
|
113
|
+
|
|
114
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
115
|
+
let updated = false;
|
|
116
|
+
let waiting = false;
|
|
117
|
+
|
|
118
|
+
const throttledListener = () => {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
|
|
121
|
+
updated = true;
|
|
122
|
+
|
|
123
|
+
if (waiting) {
|
|
124
|
+
// Schedule a timer to call the listener at the end
|
|
125
|
+
timer = setTimeout(() => {
|
|
126
|
+
if (updated) {
|
|
127
|
+
updated = false;
|
|
128
|
+
listener();
|
|
129
|
+
}
|
|
130
|
+
}, delay);
|
|
131
|
+
} else {
|
|
132
|
+
waiting = true;
|
|
133
|
+
setTimeout(function () {
|
|
134
|
+
waiting = false;
|
|
135
|
+
}, delay);
|
|
136
|
+
|
|
137
|
+
// Call the listener immediately at start
|
|
138
|
+
updated = false;
|
|
139
|
+
listener();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const unsubscribe = subscribe(throttledListener);
|
|
144
|
+
|
|
145
|
+
return () => {
|
|
146
|
+
unsubscribe();
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const context = React.useMemo<FrameContextType>(
|
|
153
|
+
() => ({
|
|
154
|
+
getCurrent,
|
|
155
|
+
subscribe,
|
|
156
|
+
subscribeThrottled,
|
|
157
|
+
}),
|
|
158
|
+
[subscribe, subscribeThrottled, getCurrent]
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const onChange = useLatestCallback((frame: Frame) => {
|
|
162
|
+
if (
|
|
163
|
+
frameRef.current.height === frame.height &&
|
|
164
|
+
frameRef.current.width === frame.width
|
|
165
|
+
) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
frameRef.current = { width: frame.width, height: frame.height };
|
|
170
|
+
listeners.current.forEach((listener) => listener());
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<>
|
|
175
|
+
{Platform.OS === 'web' ? (
|
|
176
|
+
<FrameSizeListenerWeb onChange={onChange} />
|
|
177
|
+
) : typeof SafeAreaListener === 'undefined' ? (
|
|
178
|
+
<FrameSizeListenerNativeFallback onChange={onChange} />
|
|
179
|
+
) : (
|
|
180
|
+
<SafeAreaListener
|
|
181
|
+
onChange={({ frame }) => onChange(frame)}
|
|
182
|
+
style={StyleSheet.absoluteFill}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
<FrameContext.Provider value={context}>{children}</FrameContext.Provider>
|
|
186
|
+
</>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// SafeAreaListener is available only on newer versions
|
|
191
|
+
// Fallback to an effect-based shim for older versions
|
|
192
|
+
function FrameSizeListenerNativeFallback({
|
|
193
|
+
onChange,
|
|
194
|
+
}: {
|
|
195
|
+
onChange: (frame: Frame) => void;
|
|
196
|
+
}) {
|
|
197
|
+
const frame = useSafeAreaFrame();
|
|
198
|
+
|
|
199
|
+
React.useLayoutEffect(() => {
|
|
200
|
+
onChange(frame);
|
|
201
|
+
}, [frame, onChange]);
|
|
202
|
+
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// FIXME: On the Web, the safe area frame value doesn't update on resize
|
|
207
|
+
// So we workaround this by measuring the frame on resize
|
|
208
|
+
function FrameSizeListenerWeb({
|
|
209
|
+
onChange,
|
|
210
|
+
}: {
|
|
211
|
+
onChange: (frame: Frame) => void;
|
|
212
|
+
}) {
|
|
213
|
+
const elementRef = React.useRef<HTMLDivElement>(null);
|
|
214
|
+
|
|
215
|
+
React.useEffect(() => {
|
|
216
|
+
if (elementRef.current == null) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const rect = elementRef.current.getBoundingClientRect();
|
|
221
|
+
|
|
222
|
+
onChange({
|
|
223
|
+
width: rect.width,
|
|
224
|
+
height: rect.height,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const observer = new ResizeObserver((entries) => {
|
|
228
|
+
const entry = entries[0];
|
|
229
|
+
|
|
230
|
+
if (entry) {
|
|
231
|
+
const { width, height } = entry.contentRect;
|
|
232
|
+
|
|
233
|
+
onChange({ width, height });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
observer.observe(elementRef.current);
|
|
238
|
+
|
|
239
|
+
return () => {
|
|
240
|
+
observer.disconnect();
|
|
241
|
+
};
|
|
242
|
+
}, [onChange]);
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<div
|
|
246
|
+
ref={elementRef}
|
|
247
|
+
style={{
|
|
248
|
+
...StyleSheet.absoluteFillObject,
|
|
249
|
+
pointerEvents: 'none',
|
|
250
|
+
visibility: 'hidden',
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
}
|