UNPKG

@wordpress/components

Version:
263 lines (253 loc) 8.29 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _reactNative = require("react-native"); var _element = require("@wordpress/element"); var _compose = require("@wordpress/compose"); var _slotFill = require("../slot-fill"); var _style = _interopRequireDefault(require("./style.scss")); var _jsxRuntime = require("react/jsx-runtime"); /** * 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(); }; // See https://github.com/WordPress/gutenberg/pull/41166 }, []); return keyboardVisible; }; const Tooltip = ({ children, position = 'top', text, visible: initialVisible = false }) => { 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); // See https://github.com/WordPress/gutenberg/pull/41166 }, [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(); } // See https://github.com/WordPress/gutenberg/pull/41166 }, [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); } // See https://github.com/WordPress/gutenberg/pull/41166 }, [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: _style.default.tooltip__shadow?.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 = ({ nativeEvent }) => { const { height, width } = nativeEvent.layout; setTooltipLayout({ height, width }); }; if (hidden) { return children; } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [(0, _element.cloneElement)(children, { ref: referenceElementRef, onLayout: getReferenceElementPosition }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Fill, { children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { onLayout: getTooltipLayout, style: tooltipStyles, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, { style: tooltipBoxStyles, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: _style.default.tooltip__text, children: text }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: arrowStyles })] }) }) })] }); }; const TooltipSlot = ({ children, ...rest }) => { 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 // See https://github.com/WordPress/gutenberg/pull/41166 const value = (0, _element.useMemo)(() => ({ onHandleScreenTouch })); return /*#__PURE__*/(0, _jsxRuntime.jsx)(TooltipContext.Provider, { value: value, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { onTouchStart: typeof handleScreenTouch === 'function' ? handleTouchStart : undefined, pointerEvents: "box-none", style: _reactNative.StyleSheet.absoluteFill, testID: "tooltip-overlay", children: [children, /*#__PURE__*/(0, _jsxRuntime.jsx)(Slot, { ...rest })] }) }); }; Tooltip.Slot = TooltipSlot; var _default = exports.default = Tooltip; //# sourceMappingURL=index.native.js.map