@wordpress/components
Version:
UI components for WordPress.
263 lines (253 loc) • 8.29 kB
JavaScript
;
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