* refactor: menuRow directive to MenuRow component * refactor: migrate footer to react * refactor: migrate actions menu to react * refactor: migrate history menu to react * fix: click outside handler use capture to trigger event before re-render occurs which would otherwise cause node.contains to return incorrect result (specifically for the account menu) * refactor: migrate revision preview modal to react * refactor: migrate permissions modal to react * refactor: migrate password wizard to react * refactor: remove unused input modal directive * refactor: remove unused delay hide component * refactor: remove unused filechange directive * refactor: remove unused elemReady directive * refactor: remove unused sn-enter directive * refactor: remove unused lowercase directive * refactor: remove unused autofocus directive * refactor(wip): note view to react * refactor: use mutation observer to deinit textarea listeners * refactor: migrate challenge modal to react * refactor: migrate note group view to react * refactor(wip): migrate remaining classes * fix: navigation parent ref * refactor: fully remove angular assets * fix: account switcher * fix: application view state * refactor: remove unused password wizard type * fix: revision preview and permissions modal * fix: remove angular comment * refactor: react panel resizers for editor * feat: simple panel resizer * fix: use simple panel resizer everywhere * fix: simplify panel resizer state * chore: rename simple panel resizer to panel resizer * refactor: simplify column layout * fix: editor mount safety check * fix: use inline onLoad callback for iframe, as setting onload after it loads will never call it * chore: fix note view test * chore(deps): upgrade snjs
60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
import { FunctionComponent, h, render } from 'preact';
|
|
import { unmountComponentAtNode } from 'preact/compat';
|
|
import { StateUpdater, useCallback, useState, useEffect } from 'preact/hooks';
|
|
|
|
/**
|
|
* @returns a callback that will close a dropdown if none of its children has
|
|
* focus. Use the returned function as the onBlur callback of children that need to be
|
|
* monitored.
|
|
*/
|
|
export function useCloseOnBlur(
|
|
container: { current?: HTMLDivElement | null },
|
|
setOpen: (open: boolean) => void
|
|
): [
|
|
(event: { relatedTarget: EventTarget | null }) => void,
|
|
StateUpdater<boolean>
|
|
] {
|
|
const [locked, setLocked] = useState(false);
|
|
return [
|
|
useCallback(
|
|
function onBlur(event: { relatedTarget: EventTarget | null }) {
|
|
if (
|
|
!locked &&
|
|
!container.current?.contains(event.relatedTarget as Node)
|
|
) {
|
|
setOpen(false);
|
|
}
|
|
},
|
|
[container, setOpen, locked]
|
|
),
|
|
setLocked,
|
|
];
|
|
}
|
|
|
|
export function useCloseOnClickOutside(
|
|
container: { current: HTMLDivElement | null },
|
|
callback: () => void
|
|
): void {
|
|
const closeOnClickOutside = useCallback(
|
|
(event: { target: EventTarget | null }) => {
|
|
if (!container.current) {
|
|
return;
|
|
}
|
|
const isDescendant = container.current.contains(event.target as Node);
|
|
if (!isDescendant) {
|
|
callback();
|
|
}
|
|
},
|
|
[container, callback]
|
|
);
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('click', closeOnClickOutside, { capture: true });
|
|
return () => {
|
|
document.removeEventListener('click', closeOnClickOutside, {
|
|
capture: true,
|
|
});
|
|
};
|
|
}, [closeOnClickOutside]);
|
|
}
|