UNPKG

@wordpress/components

Version:
284 lines (243 loc) 8.8 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _element = require("@wordpress/element"); var _reactNative = require("react-native"); var _compose = require("@wordpress/compose"); var _slotFill = require("../slot-fill"); var _style = _interopRequireDefault(require("./style.scss")); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ const RIGHT_ALIGN_ARROW_OFFSET = 16; const TOOLTIP_VERTICAL_OFFSET = 2; const TooltipContext = (0, _element.createContext)({ onHandleScreenTouch: () => {} }); const { Fill, Slot } = (0, _slotFill.createSlotFill)('Tooltip'); const useKeyboardVisibility = () => { const [keyboardVisible, setKeyboardVisible] = (0, _element.useState)(false); const previousKeyboardVisible = (0, _compose.usePrevious)(keyboardVisible); (0, _element.useEffect)(() => { const showListener = _reactNative.Keyboard.addListener('keyboardDidShow', () => { if (previousKeyboardVisible !== true) { setKeyboardVisible(true); } }); const keyboardHideEvent = _reactNative.Platform.select({ android: 'keyboardDidHide', ios: 'keyboardWillHide' }); const hideListener = _reactNative.Keyboard.addListener(keyboardHideEvent, () => { if (previousKeyboardVisible !== false) { setKeyboardVisible(false); } }); return () => { showListener.remove(); hideListener.remove(); }; // Disable reason: deferring this refactor to the native team. // see https://github.com/WordPress/gutenberg/pull/41166 // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return keyboardVisible; }; const Tooltip = _ref => { var _styles$tooltip__shad; let { children, position = 'top', text, visible: initialVisible = false } = _ref; const referenceElementRef = (0, _element.useRef)(null); const animationValue = (0, _element.useRef)(new _reactNative.Animated.Value(0)).current; const [, horizontalPosition = 'center'] = position.split(' '); const [visible, setVisible] = (0, _element.useState)(initialVisible); const [animating, setAnimating] = (0, _element.useState)(false); const hidden = !visible && !animating; const previousVisible = (0, _compose.usePrevious)(visible); const [referenceLayout, setReferenceLayout] = (0, _element.useState)({ height: 0, width: 0, x: 0, y: 0 }); const [tooltipLayout, setTooltipLayout] = (0, _element.useState)({ height: 0, width: 0 }); const { onHandleScreenTouch } = (0, _element.useContext)(TooltipContext); const keyboardVisible = useKeyboardVisibility(); // Register callback to dismiss the tooltip whenever the screen is touched. (0, _element.useEffect)(() => { if (visible) { onHandleScreenTouch(() => { setAnimating(true); setVisible(false); }); } return () => onHandleScreenTouch(null); // Disable reason: deferring this refactor to the native team. // see https://github.com/WordPress/gutenberg/pull/41166 // eslint-disable-next-line react-hooks/exhaustive-deps }, [visible]); // Manage visibility animation. (0, _element.useEffect)(() => { if ( // Initial render and visibility enabled, animate show. typeof previousVisible === 'undefined' && visible || // Previously visible, animate hide previousVisible && previousVisible !== visible) { setAnimating(true); startAnimation(); } // Disable reason: deferring this refactor to the native team. // see https://github.com/WordPress/gutenberg/pull/41166 // eslint-disable-next-line react-hooks/exhaustive-deps }, [visible]); // Manage tooltip visibility and position in relation to keyboard. (0, _element.useEffect)(() => { if (!visible) { return; } // Update tooltip position if keyboard is visible. if (keyboardVisible) { getReferenceElementPosition(); } // Hide tooltip if keyboard hides if (typeof previousVisible !== 'undefined' && !keyboardVisible) { setAnimating(true); setVisible(false); } // Disable reason: deferring this refactor to the native team. // see https://github.com/WordPress/gutenberg/pull/41166 // eslint-disable-next-line react-hooks/exhaustive-deps }, [visible, keyboardVisible]); // Manage tooltip position during keyboard frame changes. (0, _element.useEffect)(() => { const frameListener = _reactNative.Keyboard.addListener('keyboardWillChangeFrame', () => { if (visible) { getReferenceElementPosition(); } }); return () => { frameListener.remove(); }; }, [visible]); const startAnimation = () => { _reactNative.Animated.timing(animationValue, { toValue: visible ? 1 : 0, duration: visible ? 300 : 150, useNativeDriver: true, delay: visible ? 500 : 0, easing: _reactNative.Easing.out(_reactNative.Easing.quad) }).start(() => { setAnimating(false); }); }; const tooltipStyles = [_style.default.tooltip, { left: referenceLayout.x + Math.floor(referenceLayout.width / 2) - (horizontalPosition === 'right' ? RIGHT_ALIGN_ARROW_OFFSET : Math.floor(tooltipLayout.width / 2)), top: referenceLayout.y - tooltipLayout.height - TOOLTIP_VERTICAL_OFFSET }]; const tooltipBoxStyles = [_style.default.tooltip__box, horizontalPosition === 'right' && _style.default['tooltip--rightAlign'], { elevation: 2, opacity: animationValue, shadowColor: (_styles$tooltip__shad = _style.default.tooltip__shadow) === null || _styles$tooltip__shad === void 0 ? void 0 : _styles$tooltip__shad.color, shadowOffset: { height: 2, width: 0 }, shadowOpacity: 0.25, shadowRadius: 2, transform: [{ translateY: animationValue.interpolate({ inputRange: [0, 1], outputRange: [visible ? 4 : -8, -8] }) }] }]; const arrowStyles = [_style.default.tooltip__arrow, horizontalPosition === 'right' && _style.default['tooltip__arrow--rightAlign']]; const getReferenceElementPosition = () => { // rAF allows render to complete before calculating layout // eslint-disable-next-line no-undef requestAnimationFrame(() => { if (!referenceElementRef.current) { return; } referenceElementRef.current.measure((_x, _y, width, height, pageX, pageY) => { setReferenceLayout({ height, width, x: pageX, y: pageY }); }); }); }; const getTooltipLayout = _ref2 => { let { nativeEvent } = _ref2; const { height, width } = nativeEvent.layout; setTooltipLayout({ height, width }); }; if (hidden) { return children; } return (0, _element.createElement)(_element.Fragment, null, (0, _element.cloneElement)(children, { ref: referenceElementRef, onLayout: getReferenceElementPosition }), (0, _element.createElement)(Fill, null, (0, _element.createElement)(_reactNative.View, { onLayout: getTooltipLayout, style: tooltipStyles }, (0, _element.createElement)(_reactNative.Animated.View, { style: tooltipBoxStyles }, (0, _element.createElement)(_reactNative.Text, { style: _style.default.tooltip__text }, text), (0, _element.createElement)(_reactNative.View, { style: arrowStyles }))))); }; const TooltipSlot = _ref3 => { let { children, ...rest } = _ref3; const [handleScreenTouch, setHandleScreenTouch] = (0, _element.useState)(null); const onHandleScreenTouch = callback => { // Must use function to set state below as `callback` is a function itself. setHandleScreenTouch(() => callback); }; const handleTouchStart = () => { handleScreenTouch(); setHandleScreenTouch(null); }; // Memoize context value to avoid unnecessary rerenders of the Provider's children // Disable reason: deferring this refactor to the native team. // see https://github.com/WordPress/gutenberg/pull/41166 // eslint-disable-next-line react-hooks/exhaustive-deps const value = (0, _element.useMemo)(() => ({ onHandleScreenTouch })); return (0, _element.createElement)(TooltipContext.Provider, { value: value }, (0, _element.createElement)(_reactNative.View, { onTouchStart: typeof handleScreenTouch === 'function' ? handleTouchStart : undefined, pointerEvents: "box-none", style: _reactNative.StyleSheet.absoluteFill, testID: "tooltip-overlay" }, children, (0, _element.createElement)(Slot, rest))); }; Tooltip.Slot = TooltipSlot; var _default = Tooltip; exports.default = _default; //# sourceMappingURL=index.native.js.map