UNPKG

@atlaskit/onboarding

Version:

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

84 lines (81 loc) 3.31 kB
import { useCallback, useEffect, useRef, useState } from 'react'; /** * A hook that watches for changes to a node and forces a re-render when it changes. * * @param element The node to watch for changes. * @param opts Options for the hook. * @param [opts.disableWatch=false] Whether to watch for changes or not. * @param [opts.onChange] A callback that is called when the node changes. * * @returns A version number that is incremented when the node changes to force a re-render. * * @internal */ export function useElementObserver(element, opts = {}) { const { onChange, disableWatch = false } = opts; // This state is used to force a re-render when the target changes const [targetContentVersion, setTargetContentVersion] = useState(0); const mutationObserver = useRef(undefined); const cancelIdleCallback = useRef(undefined); const disposer = useCallback(() => { var _cancelIdleCallback$c, _mutationObserver$cur; (_cancelIdleCallback$c = cancelIdleCallback.current) === null || _cancelIdleCallback$c === void 0 ? void 0 : _cancelIdleCallback$c.call(cancelIdleCallback); cancelIdleCallback.current = undefined; (_mutationObserver$cur = mutationObserver.current) === null || _mutationObserver$cur === void 0 ? void 0 : _mutationObserver$cur.disconnect(); mutationObserver.current = undefined; }, []); const onMutation = useCallback(() => { var _cancelIdleCallback$c2; // MutationObserver can make multiple calls to the callback in quick succession so debounce when idle (_cancelIdleCallback$c2 = cancelIdleCallback.current) === null || _cancelIdleCallback$c2 === void 0 ? void 0 : _cancelIdleCallback$c2.call(cancelIdleCallback); cancelIdleCallback.current = callWhenIdle(() => setTargetContentVersion(v => { const version = v + 1; onChange === null || onChange === void 0 ? void 0 : onChange(version); return version; })); }, [onChange]); useEffect(() => { // Clean up any existing observer in the case that the target node has changed disposer(); if (!disableWatch && element) { mutationObserver.current = new MutationObserver(onMutation); mutationObserver.current.observe(element, { attributes: true, childList: true, subtree: true }); } return disposer; }, [disableWatch, disposer, element, onMutation]); return targetContentVersion; } /** * When the browser is idle call the callback. * * This function will use `requestIdleCallback` if available, otherwise it will fallback to `setTimeout`. * * @param callback The function to call when the browser is idle. * @param [opts] Options for the idle callback. * @param [opts.timeout=100] The maximum time to wait for the browser to be idle. * * @returns A function that can be called to cancel the idle callback. * * @internal */ function callWhenIdle(callback, opts = {}) { const { timeout = 100 } = opts; if (typeof window.requestIdleCallback === 'function') { const idleHandle = window.requestIdleCallback(callback, { timeout }); return () => cancelIdleCallback(idleHandle); } // Fallback to setTimeout if requestIdleCallback is not available const timeoutHandle = setTimeout(callback, timeout); return () => clearTimeout(timeoutHandle); }