@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
JavaScript
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);
}