82 lines
2.4 KiB
JavaScript
82 lines
2.4 KiB
JavaScript
import { useControlledState, useEvent } from '@rc-component/util';
|
|
import { useRef, useState, useEffect } from 'react';
|
|
const internalMacroTask = fn => {
|
|
const channel = new MessageChannel();
|
|
channel.port1.onmessage = fn;
|
|
channel.port2.postMessage(null);
|
|
};
|
|
export const macroTask = (fn, times = 1) => {
|
|
if (times <= 0) {
|
|
fn();
|
|
return;
|
|
}
|
|
internalMacroTask(() => {
|
|
macroTask(fn, times - 1);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Trigger by latest open call, if nextOpen is undefined, means toggle.
|
|
* `weak` means this call can be ignored if previous call exists.
|
|
*/
|
|
|
|
/**
|
|
* When `open` is controlled, follow the controlled value;
|
|
* Otherwise use uncontrolled logic.
|
|
* Setting `open` takes effect immediately,
|
|
* but setting it to `false` is delayed via MessageChannel.
|
|
*
|
|
* SSR handling: During SSR, `open` is always false to avoid Portal issues.
|
|
* On client-side hydration, it syncs with the actual open state.
|
|
*/
|
|
export default function useOpen(defaultOpen, propOpen, onOpen, postOpen) {
|
|
// SSR not support Portal which means we need delay `open` for the first time render
|
|
const [rendered, setRendered] = useState(false);
|
|
useEffect(() => {
|
|
setRendered(true);
|
|
}, []);
|
|
const [stateOpen, internalSetOpen] = useControlledState(defaultOpen, propOpen);
|
|
|
|
// Lock for options update
|
|
const [lock, setLock] = useState(false);
|
|
|
|
// During SSR, always return false for open state
|
|
const ssrSafeOpen = rendered ? stateOpen : false;
|
|
const mergedOpen = postOpen(ssrSafeOpen);
|
|
const taskIdRef = useRef(0);
|
|
const triggerEvent = useEvent(nextOpen => {
|
|
if (onOpen && mergedOpen !== nextOpen) {
|
|
onOpen(nextOpen);
|
|
}
|
|
internalSetOpen(nextOpen);
|
|
});
|
|
const toggleOpen = useEvent((nextOpen, config = {}) => {
|
|
const {
|
|
cancelFun
|
|
} = config;
|
|
taskIdRef.current += 1;
|
|
const id = taskIdRef.current;
|
|
const nextOpenVal = typeof nextOpen === 'boolean' ? nextOpen : !mergedOpen;
|
|
setLock(!nextOpenVal);
|
|
function triggerUpdate() {
|
|
if (
|
|
// Always check if id is match
|
|
id === taskIdRef.current &&
|
|
// Check if need to cancel
|
|
!cancelFun?.()) {
|
|
triggerEvent(nextOpenVal);
|
|
setLock(false);
|
|
}
|
|
}
|
|
|
|
// Weak update can be ignored
|
|
if (nextOpenVal) {
|
|
triggerUpdate();
|
|
} else {
|
|
macroTask(() => {
|
|
triggerUpdate();
|
|
});
|
|
}
|
|
});
|
|
return [ssrSafeOpen, mergedOpen, toggleOpen, lock];
|
|
} |