diff --git a/src/NativeSafeAreaProvider.web.tsx b/src/NativeSafeAreaProvider.web.tsx index 8282869..1fb7214 100644 --- a/src/NativeSafeAreaProvider.web.tsx +++ b/src/NativeSafeAreaProvider.web.tsx @@ -16,6 +16,7 @@ export function NativeSafeAreaProvider({ children, style, onInsetsChange, + unstable_disableViewOnWeb, }: NativeSafeAreaProviderProps) { const viewRef = React.useRef(null); @@ -107,6 +108,10 @@ export function NativeSafeAreaProvider({ }; }, [onInsetsChange]); + if (unstable_disableViewOnWeb) { + return <>{children}; + } + return ( {children} diff --git a/src/SafeArea.types.ts b/src/SafeArea.types.ts index d3d23b6..4505212 100644 --- a/src/SafeArea.types.ts +++ b/src/SafeArea.types.ts @@ -34,6 +34,12 @@ export type InsetChangeNativeCallback = (event: InsetChangedEvent) => void; export interface NativeSafeAreaProviderProps extends ViewProps { children?: React.ReactNode; onInsetsChange: InsetChangeNativeCallback; + /** + * Web only, undocumented and unstable. Render children without the wrapping + * view. Since there is no view to measure, insets and frame fall back to + * window values. Has no effect on other platforms. + */ + unstable_disableViewOnWeb?: boolean; } export interface NativeSafeAreaViewProps extends ViewProps { diff --git a/src/SafeAreaContext.tsx b/src/SafeAreaContext.tsx index ed192d7..d3a052d 100644 --- a/src/SafeAreaContext.tsx +++ b/src/SafeAreaContext.tsx @@ -29,6 +29,12 @@ export interface SafeAreaProviderProps extends ViewProps { * @deprecated */ initialSafeAreaInsets?: EdgeInsets | null; + /** + * Web only, undocumented and unstable. Render children without the wrapping + * view. Since there is no view to measure, insets and frame fall back to + * window values. Has no effect on other platforms. + */ + unstable_disableViewOnWeb?: boolean; } export function SafeAreaProvider({ diff --git a/src/__tests__/NativeSafeAreaProvider.web-test.tsx b/src/__tests__/NativeSafeAreaProvider.web-test.tsx index c34aaf4..1e0bb24 100644 --- a/src/__tests__/NativeSafeAreaProvider.web-test.tsx +++ b/src/__tests__/NativeSafeAreaProvider.web-test.tsx @@ -17,6 +17,7 @@ import type { EdgeInsets, InsetChangeNativeCallback, Metrics, + NativeSafeAreaProviderProps, } from '../SafeArea.types'; import { NativeSafeAreaProvider } from '../NativeSafeAreaProvider.web'; @@ -123,12 +124,17 @@ let root: Root | null = null; let host: HTMLElement | null = null; let rectMock: ReturnType; -function mountProvider(onInsetsChange: InsetChangeNativeCallback) { +function mountProvider( + onInsetsChange: InsetChangeNativeCallback, + props?: Partial, +) { const newHost = document.createElement('div'); document.body.appendChild(newHost); const newRoot = createRoot(newHost); act(() => { - newRoot.render(); + newRoot.render( + , + ); }); root = newRoot; host = newHost; @@ -281,4 +287,47 @@ describe('NativeSafeAreaProvider.web', () => { frame: { x: 0, y: 0, width: 800, height: 600 }, }); }); + + describe('unstable_disableViewOnWeb', () => { + it('wraps children in a view by default', () => { + const onInsetsChange = jest.fn(); + mountProvider(onInsetsChange, { children: }); + expect(host?.innerHTML).toBe('
'); + }); + + it('renders children without a wrapping element when enabled', () => { + const onInsetsChange = jest.fn(); + mountProvider(onInsetsChange, { + unstable_disableViewOnWeb: true, + children: , + }); + expect(host?.innerHTML).toBe(''); + }); + + it('reports window insets and frame when enabled', () => { + // Element rects are still measurable, but there is no provider view to + // measure, so metrics come from the window rather than from this rect. + rectMock.mockReturnValue(makeRect(20, 30, 200, 300)); + const onInsetsChange = jest.fn(); + mountProvider(onInsetsChange, { unstable_disableViewOnWeb: true }); + expect(lastMetrics(onInsetsChange)).toEqual({ + insets: WINDOW_INSETS, + frame: { x: 0, y: 0, width: WINDOW_WIDTH, height: WINDOW_HEIGHT }, + }); + }); + + it('keeps reporting window metrics on resize when enabled', () => { + const onInsetsChange = jest.fn(); + mountProvider(onInsetsChange, { unstable_disableViewOnWeb: true }); + setWindowDimensions(800, 600); + mockWindowInsets({ top: 20, bottom: 10, left: 0, right: 0 }); + act(() => { + window.dispatchEvent(new Event('resize')); + }); + expect(lastMetrics(onInsetsChange)).toEqual({ + insets: { top: 20, bottom: 10, left: 0, right: 0 }, + frame: { x: 0, y: 0, width: 800, height: 600 }, + }); + }); + }); });