diff --git a/.eslintrc b/.eslintrc index 130afeee..d2d267da 100644 --- a/.eslintrc +++ b/.eslintrc @@ -45,6 +45,7 @@ "ReactPropsChainableTypeChecker": false, "ReactPropsCheckType": false, "ReactPropTypes": false, + "ResizeObserver": false, "SyntheticEvent": false }, "rules": { diff --git a/packages/react-native-web/src/hooks/useElementLayout.js b/packages/react-native-web/src/hooks/useElementLayout.js new file mode 100644 index 00000000..5f64c6a5 --- /dev/null +++ b/packages/react-native-web/src/hooks/useElementLayout.js @@ -0,0 +1,83 @@ +/** + * Copyright (c) Nicolas Gallagher. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type { ElementRef } from 'react'; +import type { LayoutEvent } from '../types'; + +import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; +import useLayoutEffect from './useLayoutEffect'; +import UIManager from '../exports/UIManager'; + +const DOM_LAYOUT_HANDLER_NAME = '__reactLayoutHandler'; + +let didWarn = false; +let resizeObserver = null; + +function getResizeObserver(): ?ResizeObserver { + if (canUseDOM && typeof window.ResizeObserver !== 'undefined') { + if (resizeObserver == null) { + resizeObserver = new window.ResizeObserver(function(entries) { + entries.forEach(entry => { + const node = entry.target; + const onLayout = node[DOM_LAYOUT_HANDLER_NAME]; + if (typeof onLayout === 'function') { + // We still need to measure the view because browsers don't yet provide + // border-box dimensions in the entry + UIManager.measure(node, (x, y, width, height, left, top) => { + const event: LayoutEvent = { + // $FlowFixMe + nativeEvent: { + layout: { x, y, width, height, left, top } + }, + timeStamp: Date.now() + }; + Object.defineProperty(event.nativeEvent, 'target', { + enumerable: true, + get: () => entry.target + }); + onLayout(event); + }); + } + }); + }); + } + } else if (!didWarn) { + if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { + console.warn( + 'onLayout relies on ResizeObserver which is not supported by your browser. ' + + 'Please include a polyfill, e.g., https://github.com/que-etc/resize-observer-polyfill.' + ); + didWarn = true; + } + } + return resizeObserver; +} + +export default function useElementLayout( + ref: ElementRef, + onLayout?: (e: LayoutEvent) => void +) { + const observer = getResizeObserver(); + + useLayoutEffect(() => { + const node = ref.current; + if (node != null && observer != null && typeof onLayout === 'function') { + observer.observe(node); + // $FlowFixMe + node[DOM_LAYOUT_HANDLER_NAME] = onLayout; + } + return () => { + if (node != null && observer != null) { + observer.unobserve(node); + // $FlowFixMe + delete node[DOM_LAYOUT_HANDLER_NAME]; + } + }; + }, [ref, onLayout, observer]); +} diff --git a/packages/react-native-web/src/hooks/useLayoutEffect.js b/packages/react-native-web/src/hooks/useLayoutEffect.js new file mode 100644 index 00000000..ba3ee240 --- /dev/null +++ b/packages/react-native-web/src/hooks/useLayoutEffect.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Nicolas Gallagher. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * useLayoutEffect throws an error on the server. On the few occasions where is + * problematic, use this hook. + * + * @flow + */ + +import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; +import { useEffect, useLayoutEffect } from 'react'; + +const useLayoutEffectImpl: typeof useLayoutEffect = canUseDOM ? useLayoutEffect : useEffect; + +export default useLayoutEffectImpl; diff --git a/packages/react-native-web/src/types/index.js b/packages/react-native-web/src/types/index.js index 3337b99e..a7105ccb 100644 --- a/packages/react-native-web/src/types/index.js +++ b/packages/react-native-web/src/types/index.js @@ -35,8 +35,10 @@ export type LayoutValue = { export type LayoutEvent = { nativeEvent: { - layout: LayoutValue - } + layout: LayoutValue, + target: any + }, + timeStamp: number }; export type PointValue = {|