const modal = createModal("key", () => {
const { $status } = withModalStatus();
const $anchorElement = withAnchorElement();
const $anchorMeasurement = withAnchorMeasurement({
$anchorElement,
$status,
});
const $floatingElement = withFloatingElement();
const $floatingMeasurement = withFloatingMeasurement({
$floatingElement,
$status,
});
const $boundary = withBoundary({
$status,
transform: (rect) => rect,
});
const $placement = withPlacement({
placement: "down-center",
$anchorMeasurement,
$boundary,
$floatingMeasurement,
});
const $position = withPosition({
$anchorMeasurement,
$boundary,
$floatingMeasurement,
$placement,
});
return {
$anchorElement,
$floatingElement,
$position,
$status,
};
});npm install @monstermann/signals-modalpnpm add @monstermann/signals-modalyarn add @monstermann/signals-modalbun add @monstermann/signals-modalfunction withAnchorElement(
anchorElement?: HTMLElement,
): Signal<HTMLElement | null>;Assigns an anchor element to the current modal. This function must be called inside a createModal callback.
import {
createModal,
withAnchorElement,
setAnchorElement,
getAnchorElement,
} from "@monstermann/signals-modal";
createModal("key", () => {
withAnchorElement();
});
setAnchorElement("key", document.querySelector(".anchor"));
getAnchorElement("key"); // HTMLElementfunction withAnchorMeasurement(options: {
$anchorElement: Reactive<HTMLElement | null>;
$status: Reactive<ModalStatus>;
transform?: (rect: Rect) => Rect;
}): Memo<Rect>;Takes an anchor element and continuously measures its position while the modal is visible, to be used to position eg. a popover next to an element. This function must be called inside a createModal callback.
The optional transform option can be used to eg. make the anchor bigger, resulting with a margin between the anchor and floating popover.
import {
createModal,
withAnchorElement,
withModalStatus,
withAnchorMeasurement,
setAnchorElement,
setModalStatus,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
const $anchorElement = withAnchorElement();
// Memo({ top: number, left: number, width: number, height: number })
const $anchorMeasurement = withAnchorMeasurement({
$status,
$anchorElement,
});
});
setAnchorElement("key", document.querySelector(".anchor"));
setModalStatus("key", "opened");function withMouseAnchor(options: {
$status: Reactive<ModalStatus>;
transform?: (rect: Rect) => Rect;
}): Memo<Rect>;This can be used to make the mouse cursor the anchor, instead of an element. This function must be called inside a createModal callback.
import {
createModal,
withModalStatus,
withMouseAnchor,
setModalStatus,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
// Memo({ top: number, left: number, width: number, height: number })
const $anchorMeasurement = withMouseAnchor({ $status });
});
// Updates $anchorMeasurement to the current mouse coordinates (once).
setModalStatus("key", "opened");function createModal(
key: string,
setup: () => T,
): T & {
key: string;
dispose: () => void;
isDisposed: () => boolean;
onDispose: (dispose: MaybeDispose) => void;
};Creates a new modal.
import { createModal } from "@monstermann/signals-modal";
const modal = createModal("key", () => ({}));
modal.key;
modal.dispose();
modal.onDispose(callback);function currentModal(): {
key: string;
dispose: () => void;
onDispose: (dispose: MaybeDispose) => void;
};Retrieves the current modal.
import { createModal, currentModal } from "@monstermann/signals-modal";
createModal(() => {
const modal = currentModal();
});const onModalDisposed: Emitter<string>;An emitter that fires when a modal gets disposed. The emitted value is the modal key.
import { createModal, onModalDisposed } from "@monstermann/signals-modal";
const modal = createModal("key", () => {});
const stopListening = onModalDisposed((key) => {
console.log(`Modal ${key} disposed`);
});
modal.dispose();
stopListening();function withFloatingElement(
floatingElement?: HTMLElement,
): Signal<HTMLElement | null>;Assigns an floating element to the current modal. This function must be called inside a createModal callback.
import {
createModal,
withFloatingElement,
setFloatingElement,
getFloatingElement,
} from "@monstermann/signals-modal";
createModal("key", () => {
withFloatingElement();
});
setFloatingElement("key", document.querySelector(".floating"));
getFloatingElement("key"); // HTMLElementfunction withFloatingMeasurement(options: {
$floatingElement: Reactive<HTMLElement | null>;
$status: Reactive<ModalStatus>;
transform?: (rect: Rect) => Rect;
}): Memo<Rect>;Takes an floating element and continuously measures its position while the modal is visible, to be used to position eg. a popover next to an element. This function must be called inside a createModal callback.
import {
createModal,
withFloatingElement,
withModalStatus,
withFloatingMeasurement,
setFloatingElement,
setModalStatus,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
const $floatingElement = withFloatingElement();
// Memo({ top: number, left: number, width: number, height: number })
const $floatingMeasurement = withFloatingMeasurement({
$status,
$floatingElement,
});
});
setFloatingElement("key", document.querySelector(".floating"));
setModalStatus("key", "opened");function getDialogs(): ReadonlySet<string>;Returns all dialog keys from the modalGroups.dialog group.
import {
createModal,
withModalGroups,
modalGroups,
getDialogs,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.dialog]);
});
getDialogs(); // Set(["key"])function getGroupsForModal(key: string): ReadonlySet<string>;Returns all groups the given key belongs to.
import {
createModal,
withModalGroups,
modalGroups,
getGroupsForModal,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.dialog]);
});
getGroupsForModal("key"); // Set(["dialog"])function getModalsForGroup(group: string): ReadonlySet<string>;Returns all keys the given group belongs to.
import {
createModal,
withModalGroups,
modalGroups,
getModalsForGroup,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.dialog]);
});
getModalsForGroup(modalGroups.dialog); // Set(["key"])function getPopovers(): ReadonlySet<string>;Returns all popover keys from the modalGroups.popover group.
import {
createModal,
withModalGroups,
modalGroups,
getPopovers,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.popover]);
});
getPopovers(); // Set(["key"])function getTooltips(): ReadonlySet<string>;Returns all tooltip keys from the modalGroups.tooltip group.
import {
createModal,
withModalGroups,
modalGroups,
getTooltips,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.tooltip]);
});
getTooltips(); // Set(["key"])function isDialog(key: string): boolean;Returns a boolean indicating whether the given key belongs to the modalGroups.dialog group.
import {
createModal,
withModalGroups,
modalGroups,
isDialog,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.popover]);
});
isDialog("key"); // truefunction isModalInGroup(key: string, group: string): boolean;Returns a boolean indicating whether the given key belongs to the group.
import {
createModal,
withModalGroups,
modalGroups,
isModalInGroup,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.popover]);
});
isModalInGroup("key", modalGroups.popover); // truefunction isPopover(key: string): boolean;Returns a boolean indicating whether the given key belongs to the modalGroups.popover group.
import {
createModal,
withModalGroups,
modalGroups,
isPopover,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.popover]);
});
isPopover("key"); // truefunction isTooltip(key: string): boolean;Returns a boolean indicating whether the given key belongs to the modalGroups.tooltip group.
import {
createModal,
withModalGroups,
modalGroups,
isTooltip,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups([modalGroups.popover]);
});
isTooltip("key"); // trueconst modalGroups = {
dialog: "dialog",
popover: "popover",
tooltip: "tooltip",
};A record containing common modal groups.
function withModalGroups(groups: Iterable<string>): Memo<ReadonlySet<string>>;Assigns the current modal to a list of groups. Can be used to for example mark the modal as a dialog/popover/tooltip. This function must be called inside a createModal callback.
import {
createModal,
withModalGroups,
getDialogs,
getGroupsForModal,
getModalsForGroup,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalGroups(["dialog"]);
});
getGroupsForModal("key"); // Set(["dialog"])
getModalsForGroup("dialog"); // Set(["key"])function withBoundary(options: {
$status: Reactive<ModalStatus>;
transform?: (rect: Rect) => Rect;
}): Memo<Rect>;Constructs a Rect resembling the window dimensions, to be fed into withPlacement and withPosition, used to constrain the floating element to be within the window boundary. This function must be called inside a createModal callback.
The optional transform option can be used to eg. make the Rect smaller, increasing the distance between the floating element and the edges of the window.
import {
createModal,
withModalStatus,
withBoundary,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
const $boundary = withBoundary({ $status });
});function withPlacement(options: {
placement: ModalPlacementOption;
$boundary: () => Rect;
$anchorMeasurement: () => Rect;
$floatingMeasurement: () => Rect;
}): Memo<ModalPlacement>;
type ModalPlacementOption =
| "vertical-center"
| "vertical-left"
| "vertical-right"
| "horizontal-center"
| "horizontal-up"
| "horizontal-down"
| "up-center"
| "down-center"
| "left-down"
| "right-down";
type ModalPlacement =
| "down-center"
| "down-left"
| "down-right"
| "left-center"
| "left-down"
| "left-up"
| "right-center"
| "right-down"
| "right-up"
| "up-center"
| "up-left"
| "up-right";Takes a ModalPlacementOption and resolves it to ModalPlacement, picking whichever side has more space. This function must be called inside a createModal callback.
import {
createModal,
withModalStatus,
withAnchorElement,
withAnchorMeasurement,
withFloatingElement,
withFloatingMeasurement,
withBoundary,
withPlacement,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
const $anchorElement = withAnchorElement();
const $floatingElement = withFloatingElement();
const $anchorMeasurement = withAnchorMeasurement({
$status,
$anchorElement,
});
const $floatingMeasurement = withFloatingMeasurement({
$status,
$floatingElement,
});
const $boundary = withBoundary({ $status });
const $placement = withPlacement({
placement: "vertical-center",
$boundary,
$anchorMeasurement,
$floatingMeasurement,
});
});function withPosition(options: {
$boundary: () => Rect;
$placement: () => ModalPlacement;
$anchorMeasurement: () => Rect;
$floatingMeasurement: () => Rect;
transform?: (rect: Rect) => Rect;
}): Memo<{
floatingX: number;
floatingY: number;
maxHeight: number;
maxWidth: number;
originX: number;
originY: number;
}>;Consumes a range of measurements and calculates the final position for the floating element. This function must be called inside a createModal callback.
import {
createModal,
withModalStatus,
withAnchorElement,
withAnchorMeasurement,
withFloatingElement,
withFloatingMeasurement,
withBoundary,
withPlacement,
withPosition,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
const $anchorElement = withAnchorElement();
const $floatingElement = withFloatingElement();
const $anchorMeasurement = withAnchorMeasurement({
$status,
$anchorElement,
});
const $floatingMeasurement = withFloatingMeasurement({
$status,
$floatingElement,
});
const $boundary = withBoundary({ $status });
const $placement = withPlacement({
placement: "vertical-center",
$boundary,
$anchorMeasurement,
$floatingMeasurement,
});
const $position = withPosition({
$boundary,
$placement,
$anchorMeasurement,
$floatingMeasurement,
});
});function withCloseOnScroll(options: {
$anchorElement: Reactive<HTMLElement | null>;
$status: Reactive<ModalStatus>;
}): void;Automatically closes the modal when any scrollable ancestor of the anchor element is scrolled. This function must be called inside a createModal callback.
The function listens for scroll events on all scrollable parent elements of the anchor element and triggers a close when scrolling occurs. Scroll listeners are only active when the modal is opening or opened (not when closing or closed).
import {
createModal,
withModalStatus,
withAnchorElement,
withCloseOnScroll,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
const $anchorElement = withAnchorElement();
withCloseOnScroll({
$status,
$anchorElement,
});
});function closeAllModals(): void;Closes all modals by setting their status to "closing". Skips modals that are already "closing" or "closed".
import {
createModal,
withModalStatus,
openModal,
closeAllModals,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus();
});
openModal("modal1");
openModal("modal2");
closeAllModals();function closeModal(key: string): void;Closes a modal by setting its status to "closing". Does nothing if the modal is already "closing" or "closed", or if the modal doesn't exist.
import {
createModal,
withModalStatus,
openModal,
closeModal,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
openModal("key");
closeModal("key");function getClosedModals(): string[];Returns an array of all modal keys with status "closed".
import {
createModal,
withModalStatus,
getClosedModals,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus();
});
getClosedModals(); // ["modal1", "modal2"]function getClosingModals(): string[];Returns an array of all modal keys with status "closing".
import {
createModal,
withModalStatus,
closeModal,
getClosingModals,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus("opened");
});
createModal("modal2", () => {
withModalStatus("opened");
});
closeModal("modal1");
closeModal("modal2");
getClosingModals(); // ["modal1", "modal2"]function getModalStatus(key: string): ModalStatus;Retrieves the current status of a modal. Returns "closed" if the modal doesn't exist.
ModalStatus can be one of: "closed", "opening", "opened", or "closing".
import {
createModal,
withModalStatus,
getModalStatus,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
getModalStatus("key"); // "closed"function getOpenedModals(): string[];Returns an array of all modal keys with status "opened".
import {
createModal,
withModalStatus,
getOpenedModals,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus("opened");
});
createModal("modal2", () => {
withModalStatus("opened");
});
getOpenedModals(); // ["modal1", "modal2"]function getOpeningModals(): string[];Returns an array of all modal keys with status "opening".
import {
createModal,
withModalStatus,
openModal,
getOpeningModals,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus();
});
openModal("modal1");
openModal("modal2");
getOpeningModals(); // ["modal1", "modal2"]function getOpenModals(): string[];Returns an array of all modal keys with status "opening" or "opened".
import {
createModal,
withModalStatus,
getOpenModals,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus("opening");
});
createModal("modal2", () => {
withModalStatus("opened");
});
createModal("modal3", () => {
withModalStatus("closed");
});
getOpenModals(); // ["modal1", "modal2"]function getVisibleModals(): string[];Returns an array of all modal keys that are visible (not "closed"). This includes "opening", "opened", and "closing" statuses.
import {
createModal,
withModalStatus,
openModal,
getVisibleModals,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus();
});
openModal("modal1");
openModal("modal2");
getVisibleModals(); // ["modal1", "modal2"]function isAnyModalClosed(): boolean;Returns true if any modal has status "closed".
import {
createModal,
withModalStatus,
isAnyModalClosed,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus("opened");
});
isAnyModalClosed(); // truefunction isAnyModalClosing(): boolean;Returns true if any modal has status "closing".
import {
createModal,
withModalStatus,
closeModal,
isAnyModalClosing,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus("opened");
});
createModal("modal2", () => {
withModalStatus();
});
closeModal("modal1");
isAnyModalClosing(); // truefunction isAnyModalOpen(): boolean;Returns true if any modal has status "opening" or "opened".
import {
createModal,
withModalStatus,
isAnyModalOpen,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus("opened");
});
isAnyModalOpen(); // truefunction isAnyModalOpened(): boolean;Returns true if any modal has status "opened".
import {
createModal,
withModalStatus,
isAnyModalOpened,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus("opened");
});
isAnyModalOpened(); // truefunction isAnyModalOpening(): boolean;Returns true if any modal has status "opening".
import {
createModal,
withModalStatus,
openModal,
isAnyModalOpening,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus();
});
openModal("modal1");
isAnyModalOpening(); // truefunction isAnyModalVisible(): boolean;Returns true if any modal is visible (not "closed"). This includes "opening", "opened", and "closing" statuses.
import {
createModal,
withModalStatus,
isAnyModalVisible,
} from "@monstermann/signals-modal";
createModal("modal1", () => {
withModalStatus();
});
createModal("modal2", () => {
withModalStatus("opened");
});
isAnyModalVisible(); // truefunction isModalClosed(key: string): boolean;Returns true if the modal's status is "closed" or if the modal doesn't exist.
import {
createModal,
withModalStatus,
isModalClosed,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
isModalClosed("key"); // truefunction isModalClosing(key: string): boolean;Returns true if the modal's status is "closing".
import {
createModal,
withModalStatus,
openModal,
closeModal,
isModalClosing,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
$status("opened");
});
closeModal("key");
isModalClosing("key"); // truefunction isModalOpen(key: string): boolean;Returns true if the modal's status is "opening" or "opened".
import {
createModal,
withModalStatus,
isModalOpen,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus("opening");
});
isModalOpen("key"); // truefunction isModalOpened(key: string): boolean;Returns true if the modal's status is "opened".
import {
createModal,
withModalStatus,
openModal,
isModalOpened,
} from "@monstermann/signals-modal";
createModal("key", () => {
const { $status } = withModalStatus();
$status("opened");
});
isModalOpened("key"); // truefunction isModalOpening(key: string): boolean;Returns true if the modal's status is "opening".
import {
createModal,
withModalStatus,
openModal,
isModalOpening,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
openModal("key");
isModalOpening("key"); // truefunction isModalVisible(key: string): boolean;Returns true if the modal is visible (not "closed"). This includes "opening", "opened", and "closing" statuses.
import {
createModal,
withModalStatus,
openModal,
isModalVisible,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
openModal("key");
isModalVisible("key"); // trueconst onModalClosed: Emitter<string>;An emitter that fires when a modal transitions to the "closed" status. The emitted value is the modal key.
import {
createModal,
withModalStatus,
onModalClosed,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
const stopListening = onModalClosed((key) => {
console.log(`Modal ${key} closed`);
});
stopListening();const onModalClosing: Emitter<string>;An emitter that fires when a modal transitions to the "closing" status. The emitted value is the modal key.
import {
createModal,
withModalStatus,
onModalClosing,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
const stopListening = onModalClosing((key) => {
console.log(`Modal ${key} closing`);
});
stopListening();const onModalOpened: Emitter<string>;An emitter that fires when a modal transitions to the "opened" status. The emitted value is the modal key.
import {
createModal,
withModalStatus,
onModalOpened,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
const stopListening = onModalOpened((key) => {
console.log(`Modal ${key} opened`);
});
stopListening();const onModalOpening: Emitter<string>;An emitter that fires when a modal transitions to the "opening" status. The emitted value is the modal key.
import {
createModal,
withModalStatus,
onModalOpening,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
const stopListening = onModalOpening((key) => {
console.log(`Modal ${key} opening`);
});
stopListening();function openModal(key: string): void;Opens a modal by setting its status to "opening". Does nothing if the modal is already "opening" or "opened", or if the modal doesn't exist.
import {
createModal,
withModalStatus,
openModal,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
openModal("key");function setModalStatus(key: string, status: ModalStatus): void;Sets the status of a modal. Does nothing if the modal doesn't exist.
ModalStatus can be one of: "closed", "opening", "opened", or "closing".
import {
createModal,
withModalStatus,
setModalStatus,
} from "@monstermann/signals-modal";
createModal("key", () => {
withModalStatus();
});
setModalStatus("key", "opened");function withModalStatus(status: ModalStatus = "closed"): {
$status: Signal<ModalStatus>;
$isOpen: Memo<boolean>;
close: () => void;
open: () => void;
};Creates and returns a status signal for the current modal. This function must be called inside a createModal callback.
The optional status parameter sets the initial status of the modal (defaults to "closed").
ModalStatus can be one of: "closed", "opening", "opened", or "closing".
import { createModal, withModalStatus } from "@monstermann/signals-modal";
// Default to "closed"
createModal("modal1", () => {
const { $status } = withModalStatus();
console.log($status()); // "closed"
});
// Start with a different initial status
createModal("modal2", () => {
const { $status } = withModalStatus("opened");
console.log($status()); // "opened"
// Update the status
$status("closing");
});