Solid Dismiss
Handles "click outside" behavior for popup menu. Closing is triggered by click/focus outside of popup element or pressing "Escape" key. It can also deal with stacks/layers of popups.
Install
npm i solid-dismiss
yarn add solid-dismiss
Example
import Dismiss from "solid-dismiss";
import { createSignal } from "solid-js";
const Popup = () => {
const [open, setOpen] = createSignal(false);
let btnEl;
return (
<div style="position: relative;">
<button ref={btnEl}>Open</button>
<Dismiss menuButton={btnEl} open={open} setOpen={setOpen}>
<div class="popup">
<p>Popup text!</p>
<p>
Lorem, <a href="#">ipsum</a> dolor.
</p>
</div>
</Dismiss>
</div>
);
};
Caveat
For iOS Safari: when clicking outside, without overlay, and the element that happened to be clicked upon was an iframe, there's a chance that the popup won't close. iframe detection interaction is feasible by checking if window blurs, but in iOS, unless the user taps on a clickable element inside iframe, window won't blur because the main page focus hasn't been blurred.
If the iframe body element has click listener, then tapping anywhere on iframe will blur window, thus closing the popup as intended. Thus if author is dealing with same domain iframes, the author can easily add empty click event listener to the body.
const iframeEl = document.querySelector("iframe");
const doc = iframeEl.contentWindow.document;
doc.body.addEventListener("click", () => {});
Docs
Dismiss
id?: string;
ref?: JSX.Element;
class?: string;
classList?: { [key: string]: boolean };
open: Accessor<boolean>;
setOpen: (v: boolean) => void;
onOpen?: OnOpenHandler;
menuButton:
| string
| JSX.Element
| Accessor<JSX.Element>
| (string | JSX.Element)[];
menuPopup?: string | JSX.Element | (() => JSX.Element);
cursorKeys?: boolean;
trapFocus?: boolean;
focusElementOnOpen?:
| "menuPopup"
| "firstChild"
| string
| JSX.Element
| (() => JSX.Element);
focusElementOnClose?:
| "menuButton"
| string
| JSX.Element
| FocusElementOnCloseOptions;
closeWhenMenuButtonIsTabbed?: boolean;
closeWhenMenuButtonIsClicked?: boolean;
closeWhenScrolling?: boolean;
closeWhenOverlayClicked?: boolean;
closeWhenEscapeKeyIsPressed?: boolean;
closeWhenDocumentBlurs?: boolean;
removeScrollbar?: boolean;
overlay?: boolean;
overlayElement?:
| boolean
| {
ref?: (el: HTMLElement) => void;
class?: string;
classList?: { [key: string]: boolean };
animation?: DismissAnimation;
};
enableLastFocusSentinel?: boolean;
mount?: string | Node;
animation?: DismissAnimation;
show?: boolean;
FocusElementOnCloseOptions
tabBackwards?: "menuButton" | string | JSX.Element;
tabForwards?: "menuButton" | string | JSX.Element;
click?: "menuButton" | string | JSX.Element;
escapeKey?: "menuButton" | string | JSX.Element;
scrolling?: "menuButton" | string | JSX.Element;
DismissAnimation
name?: string;
enterActiveClass?: string;
enterClass?: string;
enterToClass?: string;
exitActiveClass?: string;
exitClass?: string;
exitToClass?: string;
onBeforeEnter?: (el: Element) => void;
onEnter?: (el: Element, done: () => void) => void;
onAfterEnter?: (el: Element) => void;
onBeforeExit?: (el: Element) => void;
onExit?: (el: Element, done: () => void) => void;
onAfterExit?: (el: Element) => void;
appendToElement?: string | Node;
appear?: boolean;