useDialog
実装
状態遷移と同時に、<dialog>の開閉と<html>のscrolling防止操作を行っている やっていいことなのかわからんtakker.icon
reducerは純粋であるべきだからだめだよなー
複数回呼び出される可能性もある
setState()に渡すstateUpdaterに副作用を含めるのとほぼ同じコードをかくことになる これは構わないのか?
09:55:11 はずした
code:mod.ts
import { RefCallback } from "../preact/mod.tsx";
import { useCallback, useMemo, useReducer } from "../preact/hooks.ts";
export interface UseDialogResult {
isOpen: boolean;
open: VoidFunction;
close: VoidFunction;
ref: RefCallback<HTMLDialogElement>;
}
export const useDialog = (): UseDialogResult => {
isOpen: false,
prevOverflowY: "",
});
const open = useCallback(() => changeDialogState(true), []);
const close = useCallback(() => changeDialogState(false), []);
const ref: RefCallback<HTMLDialogElement> = useMemo(
() => {
let cleanup: VoidFunction | undefined;
return (dialog) => {
if (!dialog) {
cleanup?.();
changeDialogState(dialog);
return;
}
const controller = new AbortController();
// close <dialog> when pressing the Escape key
dialog.addEventListener("cancel", () => changeDialogState(false), {
signal: controller.signal,
});
//close <dialog> when clicking the backdrop
dialog.addEventListener("click", (e) => {
if (e.target !== e.currentTarget) {
e.stopPropagation();
return;
}
changeDialogState(false);
}, {
signal: controller.signal,
});
cleanup = () => {
controller.abort();
};
changeDialogState(dialog);
};
},
[],
);
return { isOpen: dialogState.isOpen, open, close, ref };
};
interface DialogState {
isOpen: boolean;
dialog?: HTMLDialogElement | null;
prevOverflowY: string;
}
const dialogStateReducer = (
state: DialogState,
isOpen: boolean | HTMLDialogElement | null,
): DialogState => {
const isOpenNow = state.dialog?.open ?? false;
if (isOpen instanceof HTMLDialogElement || isOpen === null) {
return isOpenNow === state.isOpen && isOpen === state.dialog ? state : {
isOpen: isOpenNow,
dialog: isOpen,
prevOverflowY: state.prevOverflowY,
};
}
if (isOpen) {
state.dialog?.showModal?.();
const prevOverflowY = !state.isOpen
? document.documentElement.style.overflowY
: state.prevOverflowY;
document.documentElement.style.overflowY = "hidden";
return state.isOpen && isOpenNow
? state
: { isOpen: true, prevOverflowY, dialog: state.dialog };
}
state.dialog?.close?.();
if (state.prevOverflowY === "") {
document.documentElement.style.removeProperty("overflow-y");
} else {
document.documentElement.style.overflowY = state.prevOverflowY;
}
return !state.isOpen && !isOpenNow
? state
: { isOpen: false, prevOverflowY: "", dialog: state.dialog };
};