130 lines
6.7 KiB
JavaScript
130 lines
6.7 KiB
JavaScript
import * as React from 'react';
|
|
import { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
|
|
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
|
|
const { useCallback, useState, useRef } = React;
|
|
import { useMutationObserver } from './useMutationObserver';
|
|
/**
|
|
* This function will take the rootMargin and flip the sides if we are in RTL based on the computed reading direction of the target element.
|
|
* @param ltrRootMargin the margin to be processed and flipped if required
|
|
* @param target target element that will have its current reading direction determined
|
|
* @returns the corrected rootMargin (if it was necessary to correct)
|
|
*/ export const getRTLRootMargin = (ltrRootMargin, target, win)=>{
|
|
if (target && win) {
|
|
// get the computed dir for the target element
|
|
const newDir = win.getComputedStyle(target).direction;
|
|
// If we're in rtl reading direction, we might need to flip the margins on the left/right sides
|
|
if (newDir === 'rtl') {
|
|
let newMargin = ltrRootMargin;
|
|
const splitMargins = ltrRootMargin.split(' ');
|
|
// We only need to do this if we get four values, otherwise the sides are equal and don't require flipping.
|
|
if (splitMargins.length === 4) {
|
|
newMargin = `${splitMargins[0]} ${splitMargins[3]} ${splitMargins[2]} ${splitMargins[1]}`;
|
|
}
|
|
return newMargin;
|
|
} else {
|
|
return ltrRootMargin;
|
|
}
|
|
}
|
|
return ltrRootMargin;
|
|
};
|
|
/**
|
|
* React hook that allows easy usage of the browser API IntersectionObserver within React
|
|
* @param callback - A function called when the percentage of the target element is visible crosses a threshold.
|
|
* @param options - An optional object which customizes the observer. If options isn't specified, the observer uses the
|
|
* document's viewport as the root, with no margin, and a 0% threshold (meaning that even a one-pixel change is
|
|
* enough to trigger a callback).
|
|
* @returns An array containing a callback to update the list of Elements the observer should listen to, a callback to
|
|
* update the init options of the IntersectionObserver and a ref to the IntersectionObserver instance itself.
|
|
*/ export const useIntersectionObserver = (callback, options)=>{
|
|
'use no memo';
|
|
// TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286
|
|
// eslint-disable-next-line no-restricted-globals
|
|
const observer = useRef();
|
|
const [observerList, setObserverList] = useState();
|
|
const { targetDocument } = useFluent();
|
|
const win = targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.defaultView;
|
|
var _options_rootMargin;
|
|
// set the initial init with corrected margins based on the observed root's calculated reading direction.
|
|
const [observerInit, setObserverInit] = useState(options && {
|
|
...options,
|
|
rootMargin: getRTLRootMargin((_options_rootMargin = options.rootMargin) !== null && _options_rootMargin !== void 0 ? _options_rootMargin : '0px', options.root, win)
|
|
});
|
|
var _options_rootMargin1;
|
|
// We have to assume that any values passed in for rootMargin by the consuming app are ltr values. As such we will store the ltr value.
|
|
const ltrRootMargin = useRef((_options_rootMargin1 = options === null || options === void 0 ? void 0 : options.rootMargin) !== null && _options_rootMargin1 !== void 0 ? _options_rootMargin1 : '0px');
|
|
// Callback function to execute when mutations are observed
|
|
const mutationObserverCallback = useCallback((mutationList)=>{
|
|
for (const mutation of mutationList){
|
|
// Ensuring that the right attribute is being observed and that the root is within the tree of the element being mutated.
|
|
if (mutation.type === 'attributes' && mutation.attributeName === 'dir' && (options === null || options === void 0 ? void 0 : options.root) && mutation.target.contains(options === null || options === void 0 ? void 0 : options.root)) {
|
|
setObserverInit({
|
|
...observerInit,
|
|
rootMargin: getRTLRootMargin(ltrRootMargin.current, observerInit === null || observerInit === void 0 ? void 0 : observerInit.root, win)
|
|
});
|
|
}
|
|
}
|
|
}, [
|
|
ltrRootMargin,
|
|
observerInit,
|
|
options === null || options === void 0 ? void 0 : options.root,
|
|
win
|
|
]);
|
|
// Mutation observer for dir attribute changes in the document
|
|
useMutationObserver(targetDocument, mutationObserverCallback, {
|
|
attributes: true,
|
|
subtree: true,
|
|
attributeFilter: [
|
|
'dir'
|
|
]
|
|
});
|
|
// Observer elements in passed in list and clean up previous list
|
|
// This effect is only triggered when observerList is updated
|
|
useIsomorphicLayoutEffect(()=>{
|
|
if (!win) {
|
|
return;
|
|
}
|
|
observer.current = new win.IntersectionObserver(callback, {
|
|
...observerInit,
|
|
rootMargin: getRTLRootMargin(ltrRootMargin.current, observerInit === null || observerInit === void 0 ? void 0 : observerInit.root, win)
|
|
});
|
|
// If we have an instance of IO and a list with elements, observer the elements
|
|
if (observer.current && observerList && observerList.length > 0) {
|
|
observerList.forEach((element)=>{
|
|
var _observer_current;
|
|
(_observer_current = observer.current) === null || _observer_current === void 0 ? void 0 : _observer_current.observe(element);
|
|
});
|
|
}
|
|
// clean up previous elements being listened to
|
|
return ()=>{
|
|
if (observer.current) {
|
|
observer.current.disconnect();
|
|
}
|
|
};
|
|
}, [
|
|
observerList,
|
|
observerInit,
|
|
callback,
|
|
win
|
|
]);
|
|
// Do not use internally, we need to track external settings only here
|
|
const setObserverInitExternal = useCallback((newInit)=>{
|
|
var _newInit_rootMargin;
|
|
// Since we know this is coming from consumers, we can store this value as LTR somewhat safely.
|
|
ltrRootMargin.current = (_newInit_rootMargin = newInit === null || newInit === void 0 ? void 0 : newInit.rootMargin) !== null && _newInit_rootMargin !== void 0 ? _newInit_rootMargin : '0px';
|
|
// Call the internal setter to update the value and ensure if our calculated direction is rtl, we flip the margin
|
|
setObserverInit({
|
|
...newInit,
|
|
rootMargin: getRTLRootMargin(ltrRootMargin.current, newInit === null || newInit === void 0 ? void 0 : newInit.root, win)
|
|
});
|
|
}, [
|
|
ltrRootMargin,
|
|
setObserverInit,
|
|
win
|
|
]);
|
|
return {
|
|
setObserverList,
|
|
setObserverInit: setObserverInitExternal,
|
|
observer
|
|
};
|
|
};
|