UNPKG

@atlaskit/onboarding

Version:

An onboarding spotlight introduces new features to users through focused messages or multi-step tours.

176 lines (172 loc) 6.64 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ElementBox = void 0; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = require("react"); var _bindEventListener = require("bind-event-listener"); var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop")); var _fg = require("@atlaskit/platform-feature-flags/fg"); // The minimum interval between position updates in milliseconds var POSITION_UPDATE_INTERVAL = 200; var getElementRect = function getElementRect(element) { var _element$getBoundingC = element.getBoundingClientRect(), height = _element$getBoundingC.height, left = _element$getBoundingC.left, top = _element$getBoundingC.top, width = _element$getBoundingC.width; return { height: height, left: left, top: top, width: width }; }; var useResizeAwareElementBox = function useResizeAwareElementBox(element, updateMethod) { var _useState = (0, _react.useState)({ width: 0, height: 0, left: 0, top: 0 }), _useState2 = (0, _slicedToArray2.default)(_useState, 2), box = _useState2[0], setBox = _useState2[1]; (0, _react.useLayoutEffect)(function () { if (updateMethod === 'resizeListener') { if ((0, _fg.fg)('scroll-lock-replacement')) { // use setTimeout 0 to defer the state update to avoid content shifting when pages have scrollbars // more details are https://www.loom.com/share/96a5d7c2afd74146a3c005bf20a8c69e?sid=968b00c1-e5ab-4ea0-9fe4-e534fe7088e4 setTimeout(function () { setBox(getElementRect(element)); }, 0); } else { setBox(getElementRect(element)); } } }, [element, updateMethod]); (0, _react.useEffect)(function () { var onResize = function onResize() { requestAnimationFrame(function () { setBox(getElementRect(element)); }); }; if (updateMethod === 'resizeListener') { return (0, _bindEventListener.bind)(window, { type: 'resize', listener: onResize }); } return _noop.default; }, [element, updateMethod]); return box; }; var usePollingElementBox = function usePollingElementBox(element, updateMethod) { // These are intentionally tracked as number primitives rather than as a shared `box` object. // Since the requestAnimationFrame code below updates this often, we want to avoid re-renders // when the values are the same. React uses `Object.is` to figure out if the state changed after a setState. // If we represent this as a shared `box` object, this will re-render even if the two objects have identical contents. var _useState3 = (0, _react.useState)(0), _useState4 = (0, _slicedToArray2.default)(_useState3, 2), width = _useState4[0], setWidth = _useState4[1]; var _useState5 = (0, _react.useState)(0), _useState6 = (0, _slicedToArray2.default)(_useState5, 2), height = _useState6[0], setHeight = _useState6[1]; var _useState7 = (0, _react.useState)(0), _useState8 = (0, _slicedToArray2.default)(_useState7, 2), left = _useState8[0], setLeft = _useState8[1]; var _useState9 = (0, _react.useState)(0), _useState0 = (0, _slicedToArray2.default)(_useState9, 2), top = _useState0[0], setTop = _useState0[1]; (0, _react.useLayoutEffect)(function () { if (updateMethod === 'polling') { if ((0, _fg.fg)('scroll-lock-replacement')) { // use setTimeout 0 to defer the state update to avoid content shifting when pages have scrollbars // more details are https://www.loom.com/share/96a5d7c2afd74146a3c005bf20a8c69e?sid=968b00c1-e5ab-4ea0-9fe4-e534fe7088e4 setTimeout(function () { var newBox = getElementRect(element); setWidth(newBox.width); setHeight(newBox.height); setLeft(newBox.left); setTop(newBox.top); }, 0); } else { var newBox = getElementRect(element); setWidth(newBox.width); setHeight(newBox.height); setLeft(newBox.left); setTop(newBox.top); } } }, [element, updateMethod]); // Souce: https://css-tricks.com/using-requestanimationframe-with-react-hooks/ // Use useRef for mutable variables that we want to persist // without triggering a re-render on their change var requestRef = (0, _react.useRef)(); var previousUpdateTimeRef = (0, _react.useRef)(); var animate = (0, _react.useCallback)(function (time) { if (previousUpdateTimeRef.current !== undefined) { var timeSinceLastUpdate = time - previousUpdateTimeRef.current; if (timeSinceLastUpdate > POSITION_UPDATE_INTERVAL) { var newBox = getElementRect(element); setWidth(newBox.width); setHeight(newBox.height); setLeft(newBox.left); setTop(newBox.top); previousUpdateTimeRef.current = time; } } else { // Initialize previousUpdateTimeRef previousUpdateTimeRef.current = time; } requestRef.current = requestAnimationFrame(animate); }, [element]); (0, _react.useEffect)(function () { if (updateMethod === 'polling') { requestRef.current = requestAnimationFrame(animate); } return function () { if (requestRef.current !== undefined) { cancelAnimationFrame(requestRef.current); } }; // This useEffect should only run on mount and when `element` or `updateMethod` changes. }, [animate, element, updateMethod]); var box = (0, _react.useMemo)(function () { return { width: width, height: height, left: left, top: top }; }, [width, height, left, top]); return box; }; /** * Will listen to the document resizing to see if an element has moved positions. * Not using ResizeObserver because of IE11 support. * @param element HTMLElement to watch when resizing. */ var useElementBox = function useElementBox(element, resizeUpdateMethod) { var updateMethod = resizeUpdateMethod || 'resizeListener'; var boxViaResizeListener = useResizeAwareElementBox(element, updateMethod); var boxViaPolling = usePollingElementBox(element, updateMethod); return updateMethod === 'resizeListener' ? boxViaResizeListener : boxViaPolling; }; /** * __Element box__ * * Allows consumption of `userElementBox` hook through render props. * * @internal */ var ElementBox = exports.ElementBox = function ElementBox(props) { var box = useElementBox(props.element, props.resizeUpdateMethod); return props.children(box); };