Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

solid-dismiss

Package Overview
Dependencies
Maintainers
1
Versions
65
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

solid-dismiss - npm Package Compare versions

Comparing version 1.0.5 to 1.0.6

673

dist/esm/index.js

@@ -1,2 +0,2 @@

import { insert, template, delegateEvents, addEventListener, effect, setAttribute, classList, createComponent } from 'solid-js/web';
import { insert, template, delegateEvents, addEventListener, effect, setAttribute, classList, createComponent, mergeProps } from 'solid-js/web';
import { sharedConfig, createSignal, children, createComputed, untrack, createUniqueId, onMount, on, createEffect, onCleanup, createMemo } from 'solid-js';

@@ -16,17 +16,21 @@

const html = document.querySelector("html");
html.style.cursor = "pointer";
html.style.cursor = "pointer"; // @ts-ignore
html.style.webkitTapHighlightColor = "rgba(0, 0, 0, 0)";
}
const findItemReverse = (arr, cb) => {
/**
* Iterate stack backwards, checks item, pass it close callback. First falsy value breaks iteration.
*/
const checkThenClose = (arr, checkCb, destroyCb) => {
for (let i = arr.length - 1; i >= 0; i--) {
const item = arr[i];
const foundItem = cb(item);
const item = checkCb(arr[i]);
if (foundItem) {
return [item, i];
if (item) {
destroyCb(item);
continue;
}
return;
}
return [null, -1];
};

@@ -202,2 +206,14 @@ const camelize = s => s.replace(/-./g, x => x.toUpperCase()[1]);

if (type === "focusElementOnOpen") {
if (typeof inputElement === "string") {
return state.containerEl?.querySelector(inputElement);
}
if (inputElement instanceof Element) {
return inputElement;
}
return inputElement();
}
if (inputElement == null && type === "menuPopup") {

@@ -325,20 +341,41 @@ if (!state.containerEl) return null;

const globalState = {
closeByFocusSentinel: false
closeByFocusSentinel: false,
closedBySetOpen: false,
addedDocumentClick: false,
documentClickTimeout: null,
closedByEvents: false
};
const onDocumentClick = e => {
const target = e.target;
checkThenClose(dismissStack, item => {
if (item.overlay || item.overlayElement || item.menuBtnEl.contains(target) || item.containerEl.contains(target)) return;
return item;
}, item => {
const {
setOpen
} = item;
globalState.closedByEvents = true;
setOpen(false);
});
globalState.addedDocumentClick = false;
};
const onWindowBlur = e => {
const item = dismissStack[dismissStack.length - 1]; // menuPopup item was the last tabbable item in the document and current focused item is outside of document, such as browser URL bar, then menuPopup/stacks will close
if (!document.hasFocus()) {
setTimeout(() => {
const difference = e.timeStamp - timestampOfTabkey;
if (difference < 50) {
checkThenClose(dismissStack, item => item, item => {
const {
setOpen
} = item;
setOpen(false);
});
return;
if (!document.hasFocus()) {
if (difference < 50) {
checkThenClose(dismissStack, item => item, item => {
const {
setOpen
} = item;
globalState.closedByEvents = true;
setOpen(false);
});
return;
}
}
}
});

@@ -348,2 +385,3 @@ const onBlurWindow = item => {

item.menuBtnEl.focus();
globalState.closedByEvents = true;
item.setOpen(false);

@@ -379,2 +417,3 @@ };

} = item;
globalState.closedByEvents = true;
setOpen(false);

@@ -412,2 +451,3 @@ });

globalState.closedByEvents = true;
setOpen(false);

@@ -435,2 +475,3 @@ };

} = item;
globalState.closedByEvents = true;
setOpen(false);

@@ -467,3 +508,8 @@ const el = queryElement({}, {

scrollEventAdded = false;
globalState.addedDocumentClick = false; // globalState.menuBtnEl = null;
window.clearTimeout(globalState.documentClickTimeout);
globalState.documentClickTimeout = null;
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("click", onDocumentClick);
window.removeEventListener("blur", onWindowBlur);

@@ -479,3 +525,2 @@ window.removeEventListener("wheel", onScrollClose, {

scrollEventAddedViaTouch = true;
console.log("ontouch added!!");
document.body.addEventListener("touchend", () => {

@@ -492,20 +537,3 @@ scrollEventAddedViaTouch = false;

};
/**
* Iterate stack backwards, checks item, pass it close callback. First falsy value breaks iteration.
*/
const checkThenClose = (arr, checkCb, destroyCb) => {
for (let i = arr.length - 1; i >= 0; i--) {
const item = checkCb(arr[i]);
if (item) {
destroyCb(item);
continue;
}
return;
}
};
const onCursorKeys = e => {

@@ -561,3 +589,2 @@ const keys = ["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"];

const activeElement = document.activeElement;
console.log("polling");

@@ -593,2 +620,3 @@ if (!activeElement) {

} = item;
globalState.closedByEvents = true;
setOpen(false);

@@ -606,18 +634,25 @@ cachedPolledElement = null;

const {
id,
uniqueId,
overlay,
overlayElement,
open,
containerEl,
mount,
onClickDocumentRef,
onFocusFromOutsideAppOrTabRef,
setOpen
setOpen,
timeouts,
stopComponentEventPropagation
} = state;
const relatedTarget = e.relatedTarget;
if (overlay) return;
if (overlayElement) return;
if (!open()) return;
if (mount && globalState.closeByFocusSentinel) {
if (dismissStack.findIndex(item => item.uniqueId === uniqueId) <= 0) {
globalState.closeByFocusSentinel = false;
if (globalState.closedBySetOpen) {
return;
}
if (mount && stopComponentEventPropagation) {
if (!globalState.addedDocumentClick) {
globalState.addedDocumentClick = true;
document.addEventListener("click", onDocumentClick, {
once: true
});
}

@@ -628,19 +663,17 @@

if (!open()) return;
if (!relatedTarget) {
if (dismissStack.find(item => item.overlay)) return;
if (!relatedTarget) {
const [_, overlayIdx] = findItemReverse(dismissStack, item => item.overlay);
const currentIdx = dismissStack.findIndex(item => item.uniqueId === uniqueId);
if (overlayIdx > currentIdx) return;
if (state.addedFocusOutAppEvents) return;
state.addedFocusOutAppEvents = true;
state.prevFocusedEl = e.target;
document.addEventListener("click", onClickDocumentRef);
state.prevFocusedEl.addEventListener("focus", onFocusFromOutsideAppOrTabRef, {
once: true
});
if (!globalState.addedDocumentClick) {
globalState.addedDocumentClick = true;
document.addEventListener("click", onDocumentClick, {
once: true
});
}
return;
}
state.containerFocusTimeoutId = window.setTimeout(() => {
timeouts.containerFocusTimeoutId = window.setTimeout(() => {
globalState.closedByEvents = true;
setOpen(false);

@@ -650,8 +683,8 @@ });

const onFocusInContainer = (state, e) => {
// if (state.stopPropagateFocusInAndFocusOut) {
// e.stopPropagation();
// }
clearTimeout(state.containerFocusTimeoutId);
clearTimeout(state.menuButtonBlurTimeoutId);
state.containerFocusTimeoutId = null;
const {
timeouts
} = state;
clearTimeout(timeouts.containerFocusTimeoutId);
clearTimeout(timeouts.menuButtonBlurTimeoutId);
timeouts.containerFocusTimeoutId = null;
};

@@ -664,3 +697,4 @@ const runFocusOnActive = state => {

const el = queryElement(state, {
inputElement: focusElementOnOpen
inputElement: focusElementOnOpen,
type: "focusElementOnOpen"
});

@@ -682,2 +716,3 @@

if (containerEl.contains(e.target)) return;
globalState.closedByEvents = true;
setOpen(false);

@@ -695,4 +730,14 @@ state.prevFocusedEl = null;

if (!containerEl) return;
if (containerEl.contains(e.target)) return;
if (containerEl.contains(e.target)) {
state.addedFocusOutAppEvents = false;
if (state.prevFocusedEl) {
state.prevFocusedEl.removeEventListener("focus", onFocusFromOutsideAppOrTabRef);
}
state.prevFocusedEl = null;
return;
}
if (state.prevFocusedEl) {

@@ -703,2 +748,3 @@ state.prevFocusedEl.removeEventListener("focus", onFocusFromOutsideAppOrTabRef);

state.prevFocusedEl = null;
globalState.closedByEvents = true;
setOpen(false);

@@ -713,3 +759,2 @@ state.addedFocusOutAppEvents = false;

if (!state.prevFocusedEl) return;
console.log("removeOutsideFocusEvents");
state.prevFocusedEl.removeEventListener("focus", onFocusFromOutsideAppOrTabRef);

@@ -724,3 +769,2 @@ document.removeEventListener("click", onClickDocumentRef);

menuPopup,
useAriaExpanded,
menuBtnId

@@ -737,7 +781,2 @@ } = state;

state.menuPopupEl.setAttribute("tabindex", "-1");
if (!useAriaExpanded) return;
if (!state.menuPopupEl.getAttribute("aria-labelledby")) {
state.menuPopupEl.setAttribute("aria-labelledby", menuBtnId);
}
}

@@ -752,95 +791,32 @@ };

const onClickCloseButtons = state => {
state.setOpen(false);
};
const addCloseButtons = state => {
let mousedownFired = false;
const onClickMenuButton = (state, e) => {
const {
closeButtons,
closeBtnsAdded,
closeBtns,
onClickCloseButtonsRef
timeouts,
menuBtnEl,
closeWhenMenuButtonIsClicked,
setOpen,
open
} = state;
if (!closeButtons) return;
if (closeBtnsAdded) return;
state.menuBtnKeyupTabFired = false;
const getCloseButton = closeButton => {
if (closeButton == null) return;
const el = queryElement(state, {
inputElement: closeButton,
type: "closeButton"
});
console.log({
el
});
if (!el) return;
state.closeBtnsAdded = true;
el.addEventListener("click", onClickCloseButtonsRef);
closeBtns?.push(el);
};
if (Array.isArray(closeButtons)) {
closeButtons.forEach(item => {
getCloseButton(item);
});
if (mousedownFired && !open()) {
mousedownFired = false;
return;
}
if (typeof closeButtons === "function") {
const result = closeButtons();
if (Array.isArray(result)) {
result.forEach(item => {
getCloseButton(item);
});
return;
}
getCloseButton(result);
return;
}
getCloseButton(closeButtons);
};
const removeCloseButtons = state => {
const {
closeBtnsAdded,
closeButtons,
onClickCloseButtonsRef
} = state;
if (!closeButtons) return;
if (!closeBtnsAdded) return;
state.closeBtnsAdded = false;
state.closeBtns.forEach(el => {
el.removeEventListener("click", onClickCloseButtonsRef);
});
state.closeBtns = [];
};
const onClickMenuButton = state => {
const {
menuButtonBlurTimeoutId,
menuBtnEl,
closeWhenMenuButtonIsClicked,
setOpen,
open
} = state;
clearTimeout(state.containerFocusTimeoutId);
clearTimeout(menuButtonBlurTimeoutId);
mousedownFired = false;
globalState.addedDocumentClick = false;
document.removeEventListener("click", onDocumentClick);
menuBtnEl.focus();
state.containerFocusTimeoutId = null; // iOS triggers refocus i think...
clearTimeout(timeouts.containerFocusTimeoutId);
clearTimeout(timeouts.menuButtonBlurTimeoutId);
timeouts.containerFocusTimeoutId = null; // iOS triggers refocus i think...
if (!open()) {
state.menuBtnEl.addEventListener("focus", state.onFocusMenuButtonRef, {
menuBtnEl.addEventListener("focus", state.onFocusMenuButtonRef, {
once: true
});
menuBtnEl.addEventListener("keydown", state.onKeydownMenuButtonRef);
menuBtnEl.addEventListener("blur", state.onBlurMenuButtonRef, {
once: true
});
} else {
if (closeWhenMenuButtonIsClicked) {
state.menuBtnEl.removeEventListener("focus", state.onFocusMenuButtonRef);
state.menuBtnEl.removeEventListener("keydown", state.onKeydownMenuButtonRef);
state.menuBtnEl.removeEventListener("blur", state.onBlurMenuButtonRef);
}
menuBtnEl.addEventListener("blur", state.onBlurMenuButtonRef);
}

@@ -853,2 +829,6 @@

if (open()) {
globalState.closedByEvents = true;
}
setOpen(!open());

@@ -858,7 +838,7 @@ };

const {
onClickDocumentRef,
containerEl,
overlay,
setOpen,
open
timeouts,
closeWhenMenuButtonIsClicked
} = state;

@@ -871,7 +851,14 @@

if (mousedownFired && !closeWhenMenuButtonIsClicked) {
return;
}
if (!e.relatedTarget) {
if (!overlay) {
document.addEventListener("click", onClickDocumentRef, {
once: true
});
if (!globalState.addedDocumentClick) {
globalState.addedDocumentClick = true;
document.addEventListener("click", onDocumentClick, {
once: true
});
}
}

@@ -887,6 +874,23 @@

const run = () => {
globalState.closedByEvents = true;
setOpen(false);
};
state.menuButtonBlurTimeoutId = window.setTimeout(run);
timeouts.menuButtonBlurTimeoutId = window.setTimeout(run);
}; // When reclicking menuButton for closing intention, Safari will trigger blur upon mousedown, which the click event fires after. This results menuPopup close then reopen. This mousedown event prevents that bug.
const onMouseDownMenuButton = state => {
if (!state.open()) {
checkThenClose(dismissStack, item => {
if (item.containerEl.contains(state.menuBtnEl)) return;
return item;
}, item => {
globalState.closedByEvents = true;
item.setOpen(false);
});
mousedownFired = false;
return;
}
mousedownFired = true;
};

@@ -906,2 +910,3 @@ const onKeydownMenuButton = (state, e) => {

if (e.key === "Tab" && e.shiftKey) {
globalState.closedByEvents = true;
setOpen(false);

@@ -933,22 +938,13 @@ state.menuBtnKeyupTabFired = true;

closeWhenMenuButtonIsTabbed,
containerFocusTimeoutId
timeouts
} = state;
if (!closeWhenMenuButtonIsTabbed) {
console.log("clear!!");
clearTimeout(containerFocusTimeoutId);
clearTimeout(timeouts.containerFocusTimeoutId);
}
};
const runAriaExpanded = (state, open) => {
const {
useAriaExpanded,
menuBtnEl
} = state;
if (!useAriaExpanded) return;
menuBtnEl.setAttribute("aria-expanded", `${open}`);
};
const activateLastFocusSentinel = state => {
const {
mountedElseWhere,
enableLastFocusSentinel,
menuBtnEl,

@@ -958,3 +954,3 @@ containerEl,

} = state;
if (mountedElseWhere) return;
if (enableLastFocusSentinel) return;
const menuBtnSibling = menuBtnEl.nextElementSibling;

@@ -969,3 +965,3 @@ if (matchByFirstChild({

const {
containerFocusTimeoutId,
uniqueId,
containerEl,

@@ -980,5 +976,38 @@ menuBtnEl,

setOpen
} = state;
clearTimeout(containerFocusTimeoutId);
} = state; // clearTimeout(containerFocusTimeoutId!);
// if (mount) {
dismissStack.forEach(item => window.clearTimeout(item.timeouts.containerFocusTimeoutId)); // }
const runIfMounted = (el, isFirst) => {
// globalState.closeByFocusSentinel = true;
checkThenClose(dismissStack, item => {
if (isFirst) {
if (item.menuBtnEl === el && !item.closeWhenMenuButtonIsTabbed) {
menuBtnEl.addEventListener("focus", state.onFocusMenuButtonRef, {
once: true
});
menuBtnEl.addEventListener("keydown", state.onKeydownMenuButtonRef);
menuBtnEl.addEventListener("blur", state.onBlurMenuButtonRef, {
once: true
});
return;
}
}
if (item.uniqueId === uniqueId || !item.containerEl.contains(el)) {
return item;
}
return;
}, item => {
globalState.closedByEvents = true;
item.setOpen(false);
});
if (el) {
el.focus();
}
};
if (relatedTarget === containerEl || relatedTarget === menuBtnEl) {

@@ -1003,2 +1032,3 @@ const el = getNextTabbableElement({

if (closeWhenMenuButtonIsTabbed) {
globalState.closedByEvents = true;
setOpen(false);

@@ -1014,11 +1044,3 @@ menuBtnEl.focus();

}) || menuBtnEl;
if (el) {
el.focus();
}
if (el !== menuBtnEl) {
setOpen(false);
}
runIfMounted(el, true);
return;

@@ -1045,9 +1067,6 @@ }

if (mount) {
globalState.closeByFocusSentinel = true;
runIfMounted(el);
return;
}
console.log("sentinel", {
el
});
if (el) {

@@ -1057,3 +1076,3 @@ el.focus();

console.log(dismissStack);
globalState.closedByEvents = true;
setOpen(false);

@@ -1064,8 +1083,6 @@ };

const {
uniqueId,
closeWhenOverlayClicked,
menuPopupEl,
focusElementOnClose,
menuBtnEl,
setOpen
menuBtnEl
} = state;

@@ -1088,4 +1105,14 @@

dismissStack.find(item => item.uniqueId === uniqueId).queueRemoval = true;
setOpen(false);
checkThenClose(dismissStack, item => {
if (item.overlayElement) return;
return item;
}, item => {
const {
setOpen
} = item;
globalState.closedByEvents = true;
setOpen(false);
});
globalState.closedByEvents = true;
state.setOpen(false);
};

@@ -1125,3 +1152,3 @@

if (overlayChildren) {
container.appendChild(overlayChildren);
container.insertAdjacentElement("afterbegin", overlayChildren);
}

@@ -1150,3 +1177,4 @@

onExit,
onAfterExit
onAfterExit,
appendToElement
} = props;

@@ -1162,6 +1190,15 @@

function enterTransition(el, prev) {
const getElement = el => {
if (appendToElement) {
return typeof appendToElement === "string" ? el.querySelector(appendToElement) : appendToElement;
}
return el;
};
function enterTransition(_el, prev) {
const enterClasses = getClassState("enter");
const enterActiveClasses = getClassState("enter-active");
const enterToClasses = getClassState("enter-to");
const el = getElement(_el);
onBeforeEnter && onBeforeEnter(el);

@@ -1189,3 +1226,3 @@ el.classList.add(enterClasses);

el.classList.remove(enterToClasses);
s1() !== el && set1(el);
s1() !== _el && set1(_el);
onAfterEnter && onAfterEnter(el);

@@ -1195,11 +1232,12 @@ }

set1(el);
set1(_el);
}
function exitTransition(el) {
function exitTransition(_el) {
const exitClasses = getClassState("exit");
const exitActiveClasses = getClassState("exit-active");
const exitToClasses = getClassState("exit-to");
if (!el.parentNode) return endTransition();
onBeforeExit && onBeforeExit(el);
const el = getElement(_el);
if (!_el.parentNode) return endTransition();
onBeforeExit && onBeforeExit(_el);
el.classList.add(exitClasses);

@@ -1225,3 +1263,3 @@ el.classList.add(exitActiveClasses);

el.classList.remove(exitToClasses);
s1() === el && set1(undefined);
s1() === _el && set1(undefined);
onAfterExit && onAfterExit(el);

@@ -1263,6 +1301,3 @@ }

const _tmpl$ = template(`<div role="presentation"></div>`, 2),
_tmpl$2 = template(`<div><div tabindex="0" style="position: absolute; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true"></div><div style="position: absolute; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true"></div></div>`, 6);
// buttons can't receive focus on tap, only through invoking `focus()` method
// blur (tested so far on only buttons) will fire even on tapping same focused button (which would be invoked `focus()` )
// For Nested Dropdowns. Since button has to be refocused, when nested button(1) is tapped, it also triggers focusout container(1) for some reason
_tmpl$2 = template(`<div><div tabindex="0" style="position: fixed; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true"></div><div style="position: fixed; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true"></div></div>`, 6);

@@ -1280,3 +1315,2 @@ /**

focusElementOnOpen,
closeButtons,
cursorKeys = false,

@@ -1290,16 +1324,14 @@ closeWhenMenuButtonIsTabbed = false,

overlay = false,
overlayElement = false,
trapFocus = false,
removeScrollbar = false,
useAriaExpanded = false,
mountedElseWhere = false,
enableLastFocusSentinel = false,
mount,
// stopComponentEventPropagation = false,
mount,
show = false,
onOpen
} = props;
const state = {
mount: !!mount,
mount,
addedFocusOutAppEvents: false,
closeBtns: [],
closeBtnsAdded: false,
closeButtons,
closeWhenOverlayClicked,

@@ -1319,13 +1351,16 @@ closeWhenDocumentBlurs,

menuButton,
containerFocusTimeoutId: null,
menuButtonBlurTimeoutId: null,
timeouts: {
containerFocusTimeoutId: null,
menuButtonBlurTimeoutId: null
},
upperStackRemovedByFocusOut: false,
menuPopup,
closeByDismissEvent: false,
menuPopupAdded: false,
mountedElseWhere,
enableLastFocusSentinel,
overlay,
overlayElement,
removeScrollbar,
trapFocus,
useAriaExpanded,
hasFocusSentinels: !!focusElementOnClose || !!overlay || trapFocus || mountedElseWhere,
hasFocusSentinels: !!focusElementOnClose || overlay || !!overlayElement || trapFocus || enableLastFocusSentinel,
open: props.open,

@@ -1338,9 +1373,11 @@ setOpen: props.setOpen,

onBlurMenuButtonRef: e => onBlurMenuButton(state, e),
onClickMenuButtonRef: () => onClickMenuButton(state),
onClickMenuButtonRef: e => onClickMenuButton(state),
onMouseDownMenuButtonRef: () => onMouseDownMenuButton(state),
onFocusFromOutsideAppOrTabRef: e => onFocusFromOutsideAppOrTab(state, e),
onFocusMenuButtonRef: () => onFocusMenuButton(state),
onKeydownMenuButtonRef: e => onKeydownMenuButton(state, e),
onClickCloseButtonsRef: () => onClickCloseButtons(state),
refContainerCb: el => {
el.style.zIndex = `${1000 + dismissStack.length}`;
if (overlayElement) {
el.style.zIndex = "1000";
}

@@ -1360,6 +1397,6 @@ if (props.ref) {

el.style.height = "100%";
el.style.zIndex = `${999 + dismissStack.length}`;
el.style.zIndex = "1000";
if (typeof overlay === "object" && overlay.ref) {
overlay.ref(el);
if (typeof overlayElement === "object" && overlayElement.ref) {
overlayElement.ref(el);
}

@@ -1369,7 +1406,4 @@

}
}; // let marker: Text | null = stopComponentEventPropagation
// ? null
// : document.createTextNode("");
let marker = document.createTextNode("");
};
let marker = mount ? document.createTextNode("") : null;
const initDefer = !props.open();

@@ -1384,2 +1418,10 @@ let containerEl;

function getElement(el, appendToElement) {
if (appendToElement) {
return typeof appendToElement === "string" ? el.querySelector(appendToElement) : appendToElement;
}
return el;
}
function enterTransition(type, el) {

@@ -1393,2 +1435,3 @@ // @ts-ignore

exitRunning = false;
el = getElement(el, animation.appendToElement);
const name = animation.name;

@@ -1406,3 +1449,2 @@ let {

} = animation;
console.log("enterTransition");
const enterClasses = enterClass.split(" ");

@@ -1471,2 +1513,3 @@ const enterActiveClasses = enterActiveClass.split(" ");

exitRunning = true;
el = getElement(el, animation.appendToElement);
const name = animation.name;

@@ -1521,3 +1564,11 @@ let {

mountEl?.removeChild(containerEl);
onAfterExit && onAfterExit(containerEl?.firstElementChild);
globalState.closedBySetOpen = false;
if (state.menuBtnEl && (overlay || overlayElement)) {
if (document.activeElement === document.body) {
state.menuBtnEl.focus();
}
}
onAfterExit && onAfterExit(el);
containerEl = null;

@@ -1541,2 +1592,14 @@ mountEl = null;

const resetFocusOnClose = () => {
const el = queryElement(state, {
inputElement: focusElementOnClose,
type: "focusElementOnClose",
subType: "click"
}) || state.menuBtnEl;
if (el) {
el.focus();
}
};
onMount(() => {

@@ -1546,6 +1609,6 @@ state.menuBtnEl = queryElement(state, {

type: "menuButton"
}); // console.log("onMount!!!", state.menuBtnEl, state.menuBtnEl.isConnected);
});
state.menuBtnEl.setAttribute("type", "button");
state.menuBtnEl.addEventListener("click", state.onClickMenuButtonRef);
state.menuBtnEl.addEventListener("mousedown", state.onMouseDownMenuButtonRef);

@@ -1557,48 +1620,53 @@ if (props.open() && (!state.focusElementOnOpen || state.focusElementOnOpen === "menuButton" || state.focusElementOnOpen === state.menuBtnEl)) {

}
state.menuBtnId = state.menuBtnEl.id;
runAriaExpanded(state, props.open());
if (!state.menuBtnId) {
state.menuBtnId = id || state.uniqueId;
state.menuBtnEl.id = state.menuBtnId;
}
});
createComputed(on(() => !!props.open(), (open, prevOpen) => {
if (open === prevOpen) return;
if (mount) {
createComputed(on(() => !!props.open(), (open, prevOpen) => {
console.log("lib computed");
if (open === prevOpen) return;
if (open) {
if (!mountEl) {
CreatePortal({
mount: typeof mount === "string" ? document.querySelector(mount) : mount,
popupChildren: render(props.children),
overlayChildren: overlay ? renderOverlay() : null,
marker,
onCreate: (mount, container) => {
mountEl = mount;
containerEl = container;
}
if (!open) {
// used to detect programmatic removal
if (!globalState.closedByEvents) {
if (!globalState.closedBySetOpen) {
globalState.addedDocumentClick = false;
document.removeEventListener("click", onDocumentClick);
globalState.closedBySetOpen = true;
globalState.menuBtnEl = state.menuBtnEl;
setTimeout(() => {
globalState.closedBySetOpen = false;
resetFocusOnClose();
});
}
}
} //
enterTransition("popup", containerEl?.firstElementChild);
enterTransition("overlay", state.overlayEl);
} else {
exitTransition("popup", containerEl?.firstElementChild);
exitTransition("overlay", state.overlayEl);
if (!mount) return;
if (open) {
if (!mountEl) {
CreatePortal({
mount: typeof mount === "string" ? document.querySelector(mount) : mount,
popupChildren: render(props.children),
overlayChildren: overlayElement ? renderOverlay() : null,
marker,
onCreate: (mount, container) => {
mountEl = mount;
containerEl = container;
}
});
}
}, {
defer: initDefer
}));
}
enterTransition("popup", containerEl?.firstElementChild);
enterTransition("overlay", state.overlayEl);
} else {
exitTransition("popup", containerEl?.firstElementChild);
exitTransition("overlay", state.overlayEl);
}
}, {
defer: initDefer
}));
createEffect(on(() => !!props.open(), (open, prevOpen) => {
if (open === prevOpen) return;
runAriaExpanded(state, open);
if (open) {
addCloseButtons(state);
globalState.closedByEvents = false;
addMenuPopupEl(state);

@@ -1616,5 +1684,7 @@ runFocusOnActive(state);

menuPopupEl: state.menuPopupEl,
overlay: !!overlay,
overlay,
closeWhenDocumentBlurs,
closeWhenEscapeKeyIsPressed,
closeWhenMenuButtonIsTabbed,
overlayElement,
cursorKeys,

@@ -1624,16 +1694,23 @@ focusElementOnClose,

detectIfMenuButtonObscured: false,
queueRemoval: false
queueRemoval: false,
timeouts: state.timeouts
});
runRemoveScrollbar(open);
onOpen && onOpen(open, dismissStack);
onOpen && onOpen(open, {
uniqueId: state.uniqueId,
dismissStack
});
activateLastFocusSentinel(state);
} else {
globalState.closedByEvents = false;
removeLocalEvents(state);
removeOutsideFocusEvents(state);
removeMenuPopupEl(state);
removeCloseButtons(state);
removeDismissStack(state.uniqueId);
removeGlobalEvents();
runRemoveScrollbar(open);
onOpen && onOpen(open, dismissStack);
onOpen && onOpen(open, {
uniqueId: state.uniqueId,
dismissStack
});
}

@@ -1647,3 +1724,2 @@ }, {

});
removeCloseButtons(state);
removeMenuPopupEl(state);

@@ -1656,2 +1732,3 @@ removeOutsideFocusEvents(state);

exitTransition("popup", containerEl?.firstElementChild);
exitTransition("overlay", state.overlayEl);
}

@@ -1670,14 +1747,11 @@ });

effect(_p$ => {
const _v$ = typeof state.overlay === "object" ? state.overlay.class : undefined,
_v$2 = typeof state.overlay === "object" ? state.overlay.classList || {} : {},
_v$3 = dismissStack.length;
const _v$ = typeof props.overlayElement === "object" ? props.overlayElement.class : undefined,
_v$2 = typeof props.overlayElement === "object" ? props.overlayElement.classList || {} : {};
_v$ !== _p$._v$ && (_el$.className = _p$._v$ = _v$);
_p$._v$2 = classList(_el$, _v$2, _p$._v$2);
_v$3 !== _p$._v$3 && setAttribute(_el$, "data-solid-dismiss-overlay-backdrop-level", _p$._v$3 = _v$3);
return _p$;
}, {
_v$: undefined,
_v$2: undefined,
_v$3: undefined
_v$2: undefined
});

@@ -1719,17 +1793,17 @@

effect(_p$ => {
const _v$4 = state.id,
_v$5 = props.class,
_v$6 = props.classList || {},
_v$7 = state.hasFocusSentinels ? "0" : "-1";
const _v$3 = state.id,
_v$4 = props.class,
_v$5 = props.classList || {},
_v$6 = state.hasFocusSentinels ? "0" : "-1";
_v$4 !== _p$._v$4 && setAttribute(_el$2, "id", _p$._v$4 = _v$4);
_v$5 !== _p$._v$5 && (_el$2.className = _p$._v$5 = _v$5);
_p$._v$6 = classList(_el$2, _v$6, _p$._v$6);
_v$7 !== _p$._v$7 && setAttribute(_el$4, "tabindex", _p$._v$7 = _v$7);
_v$3 !== _p$._v$3 && setAttribute(_el$2, "id", _p$._v$3 = _v$3);
_v$4 !== _p$._v$4 && (_el$2.className = _p$._v$4 = _v$4);
_p$._v$5 = classList(_el$2, _v$5, _p$._v$5);
_v$6 !== _p$._v$6 && setAttribute(_el$4, "tabindex", _p$._v$6 = _v$6);
return _p$;
}, {
_v$3: undefined,
_v$4: undefined,
_v$5: undefined,
_v$6: undefined,
_v$7: undefined
_v$6: undefined
});

@@ -1742,2 +1816,3 @@

if (props.mount) return marker;
if (show) return render(props.children);
let strictEqual = false;

@@ -1757,3 +1832,3 @@ const condition = createMemo(() => props.open(), undefined, {

if (props.animation) {
return createComponent(Transition, {
return createComponent(Transition, mergeProps(props.animation, {
get name() {

@@ -1795,3 +1870,3 @@ return props.animation.name;

});
}));
}

@@ -1798,0 +1873,0 @@

@@ -14,4 +14,4 @@ function userAgent(pattern) {

html.style.cursor = "pointer";
// @ts-ignore
html.style.webkitTapHighlightColor = "rgba(0, 0, 0, 0)";
}
export {};
import { dismissStack } from "./dismissStack";
import { globalState } from "./globalEvents";
import { findItemReverse, queryElement } from "./utils";
import { globalState, onDocumentClick } from "./globalEvents";
import { queryElement } from "./utils";
// Safari, if relatedTarget is not contained within focusout, it will be null
export const onFocusOutContainer = (state, e) => {
const { id, uniqueId, overlay, open, containerEl, mount, onClickDocumentRef, onFocusFromOutsideAppOrTabRef, setOpen, } = state;
const { overlay, overlayElement, open, mount, setOpen, timeouts, stopComponentEventPropagation, } = state;
const relatedTarget = e.relatedTarget;
if (overlay)
return;
if (mount && globalState.closeByFocusSentinel) {
if (dismissStack.findIndex((item) => item.uniqueId === uniqueId) <= 0) {
globalState.closeByFocusSentinel = false;
if (overlayElement)
return;
if (!open())
return;
if (globalState.closedBySetOpen) {
return;
}
if (mount && stopComponentEventPropagation) {
if (!globalState.addedDocumentClick) {
globalState.addedDocumentClick = true;
document.addEventListener("click", onDocumentClick, { once: true });
}
return;
}
if (!open())
return;
if (!relatedTarget) {
const [_, overlayIdx] = findItemReverse(dismissStack, (item) => item.overlay);
const currentIdx = dismissStack.findIndex((item) => item.uniqueId === uniqueId);
if (overlayIdx > currentIdx)
if (dismissStack.find((item) => item.overlay))
return;
if (state.addedFocusOutAppEvents)
return;
state.addedFocusOutAppEvents = true;
state.prevFocusedEl = e.target;
document.addEventListener("click", onClickDocumentRef);
state.prevFocusedEl.addEventListener("focus", onFocusFromOutsideAppOrTabRef, {
once: true,
});
if (!globalState.addedDocumentClick) {
globalState.addedDocumentClick = true;
document.addEventListener("click", onDocumentClick, { once: true });
}
return;
}
state.containerFocusTimeoutId = window.setTimeout(() => {
timeouts.containerFocusTimeoutId = window.setTimeout(() => {
globalState.closedByEvents = true;
setOpen(false);

@@ -37,8 +39,6 @@ });

export const onFocusInContainer = (state, e) => {
// if (state.stopPropagateFocusInAndFocusOut) {
// e.stopPropagation();
// }
clearTimeout(state.containerFocusTimeoutId);
clearTimeout(state.menuButtonBlurTimeoutId);
state.containerFocusTimeoutId = null;
const { timeouts } = state;
clearTimeout(timeouts.containerFocusTimeoutId);
clearTimeout(timeouts.menuButtonBlurTimeoutId);
timeouts.containerFocusTimeoutId = null;
};

@@ -49,3 +49,6 @@ export const runFocusOnActive = (state) => {

return;
const el = queryElement(state, { inputElement: focusElementOnOpen });
const el = queryElement(state, {
inputElement: focusElementOnOpen,
type: "focusElementOnOpen",
});
if (el) {

@@ -52,0 +55,0 @@ setTimeout(() => {

@@ -29,3 +29,3 @@ import { createSignal, sharedConfig } from "solid-js";

if (overlayChildren) {
container.appendChild(overlayChildren);
container.insertAdjacentElement("afterbegin", overlayChildren);
}

@@ -32,0 +32,0 @@ mount.appendChild(container);

import { dismissStack } from "./dismissStack";
import { globalState } from "./globalEvents";
import { getNextTabbableElement, matchByFirstChild, queryElement, } from "./utils";
import { checkThenClose, getNextTabbableElement, matchByFirstChild, queryElement, } from "./utils";
export const activateLastFocusSentinel = (state) => {
const { mountedElseWhere, menuBtnEl, containerEl, focusSentinelLastEl } = state;
if (mountedElseWhere)
const { enableLastFocusSentinel, menuBtnEl, containerEl, focusSentinelLastEl, } = state;
if (enableLastFocusSentinel)
return;

@@ -17,4 +17,34 @@ const menuBtnSibling = menuBtnEl.nextElementSibling;

export const onFocusSentinel = (state, type, relatedTarget) => {
const { containerFocusTimeoutId, containerEl, menuBtnEl, focusSentinelFirstEl, trapFocus, focusSentinelLastEl, closeWhenMenuButtonIsTabbed, focusElementOnClose, mount, setOpen, } = state;
clearTimeout(containerFocusTimeoutId);
const { uniqueId, containerEl, menuBtnEl, focusSentinelFirstEl, trapFocus, focusSentinelLastEl, closeWhenMenuButtonIsTabbed, focusElementOnClose, mount, setOpen, } = state;
// clearTimeout(containerFocusTimeoutId!);
// if (mount) {
dismissStack.forEach((item) => window.clearTimeout(item.timeouts.containerFocusTimeoutId));
// }
const runIfMounted = (el, isFirst) => {
// globalState.closeByFocusSentinel = true;
checkThenClose(dismissStack, (item) => {
if (isFirst) {
if (item.menuBtnEl === el && !item.closeWhenMenuButtonIsTabbed) {
menuBtnEl.addEventListener("focus", state.onFocusMenuButtonRef, {
once: true,
});
menuBtnEl.addEventListener("keydown", state.onKeydownMenuButtonRef);
menuBtnEl.addEventListener("blur", state.onBlurMenuButtonRef, {
once: true,
});
return;
}
}
if (item.uniqueId === uniqueId || !item.containerEl.contains(el)) {
return item;
}
return;
}, (item) => {
globalState.closedByEvents = true;
item.setOpen(false);
});
if (el) {
el.focus();
}
};
if (relatedTarget === containerEl || relatedTarget === menuBtnEl) {

@@ -37,2 +67,3 @@ const el = getNextTabbableElement({

if (closeWhenMenuButtonIsTabbed) {
globalState.closedByEvents = true;
setOpen(false);

@@ -47,8 +78,3 @@ menuBtnEl.focus();

}) || menuBtnEl;
if (el) {
el.focus();
}
if (el !== menuBtnEl) {
setOpen(false);
}
runIfMounted(el, true);
return;

@@ -73,10 +99,10 @@ }

if (mount) {
globalState.closeByFocusSentinel = true;
runIfMounted(el);
return;
}
console.log("sentinel", { el });
if (el) {
el.focus();
}
console.log(dismissStack);
globalState.closedByEvents = true;
setOpen(false);
};
import { dismissStack } from "./dismissStack";
import { getNextTabbableElement, inverseQuerySelector, queryElement, _tabbableSelectors as tabbableSelectors, } from "./utils";
import { checkThenClose, getNextTabbableElement, inverseQuerySelector, queryElement, _tabbableSelectors as tabbableSelectors, } from "./utils";
let scrollEventAddedViaTouch = false;

@@ -11,16 +11,39 @@ let scrollEventAdded = false;

closeByFocusSentinel: false,
closedBySetOpen: false,
addedDocumentClick: false,
documentClickTimeout: null,
closedByEvents: false,
};
export const onDocumentClick = (e) => {
const target = e.target;
checkThenClose(dismissStack, (item) => {
if (item.overlay ||
item.overlayElement ||
item.menuBtnEl.contains(target) ||
item.containerEl.contains(target))
return;
return item;
}, (item) => {
const { setOpen } = item;
globalState.closedByEvents = true;
setOpen(false);
});
globalState.addedDocumentClick = false;
};
export const onWindowBlur = (e) => {
const item = dismissStack[dismissStack.length - 1];
// menuPopup item was the last tabbable item in the document and current focused item is outside of document, such as browser URL bar, then menuPopup/stacks will close
if (!document.hasFocus()) {
setTimeout(() => {
const difference = e.timeStamp - timestampOfTabkey;
if (difference < 50) {
checkThenClose(dismissStack, (item) => item, (item) => {
const { setOpen } = item;
setOpen(false);
});
return;
if (!document.hasFocus()) {
if (difference < 50) {
checkThenClose(dismissStack, (item) => item, (item) => {
const { setOpen } = item;
globalState.closedByEvents = true;
setOpen(false);
});
return;
}
}
}
});
const onBlurWindow = (item) => {

@@ -30,2 +53,3 @@ if (!item.closeWhenDocumentBlurs)

item.menuBtnEl.focus();
globalState.closedByEvents = true;
item.setOpen(false);

@@ -52,2 +76,3 @@ };

const { setOpen, menuBtnEl } = item;
globalState.closedByEvents = true;
setOpen(false);

@@ -75,2 +100,3 @@ });

}
globalState.closedByEvents = true;
setOpen(false);

@@ -91,2 +117,3 @@ };

const { setOpen, focusElementOnClose, menuBtnEl } = item;
globalState.closedByEvents = true;
setOpen(false);

@@ -122,3 +149,8 @@ const el = queryElement({}, {

scrollEventAdded = false;
globalState.addedDocumentClick = false;
// globalState.menuBtnEl = null;
window.clearTimeout(globalState.documentClickTimeout);
globalState.documentClickTimeout = null;
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("click", onDocumentClick);
window.removeEventListener("blur", onWindowBlur);

@@ -134,3 +166,2 @@ window.removeEventListener("wheel", onScrollClose, {

scrollEventAddedViaTouch = true;
console.log("ontouch added!!");
document.body.addEventListener("touchend", () => {

@@ -145,15 +176,2 @@ scrollEventAddedViaTouch = false;

};
/**
* Iterate stack backwards, checks item, pass it close callback. First falsy value breaks iteration.
*/
const checkThenClose = (arr, checkCb, destroyCb) => {
for (let i = arr.length - 1; i >= 0; i--) {
const item = checkCb(arr[i]);
if (item) {
destroyCb(item);
continue;
}
return;
}
};
const onCursorKeys = (e) => {

@@ -201,3 +219,2 @@ const keys = ["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"];

const activeElement = document.activeElement;
console.log("polling");
if (!activeElement) {

@@ -222,2 +239,3 @@ return;

const { setOpen, menuBtnEl } = item;
globalState.closedByEvents = true;
setOpen(false);

@@ -224,0 +242,0 @@ cachedPolledElement = null;

@@ -5,8 +5,7 @@ import "./browserInfo";

import { dismissStack, addDismissStack, removeDismissStack, } from "./dismissStack";
import { addGlobalEvents, removeGlobalEvents } from "./globalEvents";
import { addGlobalEvents, globalState, onDocumentClick, removeGlobalEvents, } from "./globalEvents";
import { onFocusInContainer, onFocusOutContainer, runFocusOnActive, } from "./container";
import { onClickDocument, onFocusFromOutsideAppOrTab, removeOutsideFocusEvents, } from "./outside";
import { addMenuPopupEl, removeMenuPopupEl } from "./menuPopup";
import { addCloseButtons, onClickCloseButtons, removeCloseButtons, } from "./closeButtons";
import { onBlurMenuButton, onClickMenuButton, onFocusMenuButton, onKeydownMenuButton, runAriaExpanded, } from "./menuButton";
import { onBlurMenuButton, onClickMenuButton, onFocusMenuButton, onKeydownMenuButton, onMouseDownMenuButton, } from "./menuButton";
import { activateLastFocusSentinel, onFocusSentinel } from "./focusSentinel";

@@ -22,11 +21,8 @@ import { onClickOverlay } from "./overlay";

const Dismiss = (props) => {
const { id = "", menuButton, menuPopup, focusElementOnClose, focusElementOnOpen, closeButtons, cursorKeys = false, closeWhenMenuButtonIsTabbed = false, closeWhenMenuButtonIsClicked = true, closeWhenScrolling = false, closeWhenDocumentBlurs = false, closeWhenOverlayClicked = true, closeWhenEscapeKeyIsPressed = true, overlay = false, trapFocus = false, removeScrollbar = false, useAriaExpanded = false, mountedElseWhere = false,
const { id = "", menuButton, menuPopup, focusElementOnClose, focusElementOnOpen, cursorKeys = false, closeWhenMenuButtonIsTabbed = false, closeWhenMenuButtonIsClicked = true, closeWhenScrolling = false, closeWhenDocumentBlurs = false, closeWhenOverlayClicked = true, closeWhenEscapeKeyIsPressed = true, overlay = false, overlayElement = false, trapFocus = false, removeScrollbar = false, enableLastFocusSentinel = false, mount,
// stopComponentEventPropagation = false,
mount, onOpen, } = props;
show = false, onOpen, } = props;
const state = {
mount: !!mount,
mount,
addedFocusOutAppEvents: false,
closeBtns: [],
closeBtnsAdded: false,
closeButtons,
closeWhenOverlayClicked,

@@ -46,13 +42,20 @@ closeWhenDocumentBlurs,

menuButton,
containerFocusTimeoutId: null,
menuButtonBlurTimeoutId: null,
timeouts: {
containerFocusTimeoutId: null,
menuButtonBlurTimeoutId: null,
},
upperStackRemovedByFocusOut: false,
menuPopup,
closeByDismissEvent: false,
menuPopupAdded: false,
mountedElseWhere,
enableLastFocusSentinel,
overlay,
overlayElement,
removeScrollbar,
trapFocus,
useAriaExpanded,
hasFocusSentinels: !!focusElementOnClose || !!overlay || trapFocus || mountedElseWhere,
hasFocusSentinels: !!focusElementOnClose ||
overlay ||
!!overlayElement ||
trapFocus ||
enableLastFocusSentinel,
open: props.open,

@@ -65,9 +68,11 @@ setOpen: props.setOpen,

onBlurMenuButtonRef: (e) => onBlurMenuButton(state, e),
onClickMenuButtonRef: () => onClickMenuButton(state),
onClickMenuButtonRef: (e) => onClickMenuButton(state, e),
onMouseDownMenuButtonRef: () => onMouseDownMenuButton(state),
onFocusFromOutsideAppOrTabRef: (e) => onFocusFromOutsideAppOrTab(state, e),
onFocusMenuButtonRef: () => onFocusMenuButton(state),
onKeydownMenuButtonRef: (e) => onKeydownMenuButton(state, e),
onClickCloseButtonsRef: () => onClickCloseButtons(state),
refContainerCb: (el) => {
el.style.zIndex = `${1000 + dismissStack.length}`;
if (overlayElement) {
el.style.zIndex = "1000";
}
if (props.ref) {

@@ -85,5 +90,5 @@ // @ts-ignore

el.style.height = "100%";
el.style.zIndex = `${999 + dismissStack.length}`;
if (typeof overlay === "object" && overlay.ref) {
overlay.ref(el);
el.style.zIndex = "1000";
if (typeof overlayElement === "object" && overlayElement.ref) {
overlayElement.ref(el);
}

@@ -93,6 +98,3 @@ state.overlayEl = el;

};
// let marker: Text | null = stopComponentEventPropagation
// ? null
// : document.createTextNode("");
let marker = document.createTextNode("");
let marker = mount ? document.createTextNode("") : null;
const initDefer = !props.open();

@@ -106,2 +108,10 @@ let containerEl;

let exitRunning = false;
function getElement(el, appendToElement) {
if (appendToElement) {
return typeof appendToElement === "string"
? el.querySelector(appendToElement)
: appendToElement;
}
return el;
}
function enterTransition(type, el) {

@@ -119,5 +129,5 @@ // @ts-ignore

exitRunning = false;
el = getElement(el, animation.appendToElement);
const name = animation.name;
let { onBeforeEnter, onEnter, onAfterEnter, enterActiveClass = name + "-enter-active", enterClass = name + "-enter", enterToClass = name + "-enter-to", exitActiveClass = name + "-exit-active", exitClass = name + "-exit", exitToClass = name + "-exit-to", } = animation;
console.log("enterTransition");
const enterClasses = enterClass.split(" ");

@@ -184,2 +194,3 @@ const enterActiveClasses = enterActiveClass.split(" ");

exitRunning = true;
el = getElement(el, animation.appendToElement);
const name = animation.name;

@@ -221,3 +232,9 @@ let { onBeforeExit, onExit, onAfterExit, exitActiveClass = name + "-exit-active", exitClass = name + "-exit", exitToClass = name + "-exit-to", } = animation;

mountEl?.removeChild(containerEl);
onAfterExit && onAfterExit(containerEl?.firstElementChild);
globalState.closedBySetOpen = false;
if (state.menuBtnEl && (overlay || overlayElement)) {
if (document.activeElement === document.body) {
state.menuBtnEl.focus();
}
}
onAfterExit && onAfterExit(el);
containerEl = null;

@@ -241,2 +258,14 @@ mountEl = null;

};
const resetFocusOnClose = () => {
const menuBtnExists = globalState.menuBtnEl;
const activeElement = document.activeElement;
const el = queryElement(state, {
inputElement: focusElementOnClose,
type: "focusElementOnClose",
subType: "click",
}) || state.menuBtnEl;
if (el) {
el.focus();
}
};
onMount(() => {

@@ -247,5 +276,5 @@ state.menuBtnEl = queryElement(state, {

});
// console.log("onMount!!!", state.menuBtnEl, state.menuBtnEl.isConnected);
state.menuBtnEl.setAttribute("type", "button");
state.menuBtnEl.addEventListener("click", state.onClickMenuButtonRef);
state.menuBtnEl.addEventListener("mousedown", state.onMouseDownMenuButtonRef);
if (props.open() &&

@@ -259,44 +288,51 @@ (!state.focusElementOnOpen ||

}
state.menuBtnId = state.menuBtnEl.id;
runAriaExpanded(state, props.open());
if (!state.menuBtnId) {
state.menuBtnId = id || state.uniqueId;
state.menuBtnEl.id = state.menuBtnId;
}
});
if (mount) {
createComputed(on(() => !!props.open(), (open, prevOpen) => {
console.log("lib computed");
if (open === prevOpen)
return;
if (open) {
if (!mountEl) {
CreatePortal({
mount: typeof mount === "string"
? document.querySelector(mount)
: mount,
popupChildren: render(props.children),
overlayChildren: overlay ? renderOverlay() : null,
marker,
onCreate: (mount, container) => {
mountEl = mount;
containerEl = container;
},
createComputed(on(() => !!props.open(), (open, prevOpen) => {
if (open === prevOpen)
return;
if (!open) {
// used to detect programmatic removal
if (!globalState.closedByEvents) {
if (!globalState.closedBySetOpen) {
globalState.addedDocumentClick = false;
document.removeEventListener("click", onDocumentClick);
globalState.closedBySetOpen = true;
globalState.menuBtnEl = state.menuBtnEl;
setTimeout(() => {
globalState.closedBySetOpen = false;
resetFocusOnClose();
});
}
enterTransition("popup", containerEl?.firstElementChild);
enterTransition("overlay", state.overlayEl);
}
else {
exitTransition("popup", containerEl?.firstElementChild);
exitTransition("overlay", state.overlayEl);
} //
if (!mount)
return;
if (open) {
if (!mountEl) {
CreatePortal({
mount: typeof mount === "string"
? document.querySelector(mount)
: mount,
popupChildren: render(props.children),
overlayChildren: overlayElement ? renderOverlay() : null,
marker,
onCreate: (mount, container) => {
mountEl = mount;
containerEl = container;
},
});
}
}, { defer: initDefer }));
}
enterTransition("popup", containerEl?.firstElementChild);
enterTransition("overlay", state.overlayEl);
}
else {
exitTransition("popup", containerEl?.firstElementChild);
exitTransition("overlay", state.overlayEl);
}
}, { defer: initDefer }));
createEffect(on(() => !!props.open(), (open, prevOpen) => {
if (open === prevOpen)
return;
runAriaExpanded(state, open);
if (open) {
addCloseButtons(state);
globalState.closedByEvents = false;
addMenuPopupEl(state);

@@ -314,5 +350,7 @@ runFocusOnActive(state);

menuPopupEl: state.menuPopupEl,
overlay: !!overlay,
overlay,
closeWhenDocumentBlurs,
closeWhenEscapeKeyIsPressed,
closeWhenMenuButtonIsTabbed,
overlayElement,
cursorKeys,

@@ -323,16 +361,17 @@ focusElementOnClose,

queueRemoval: false,
timeouts: state.timeouts,
});
runRemoveScrollbar(open);
onOpen && onOpen(open, dismissStack);
onOpen && onOpen(open, { uniqueId: state.uniqueId, dismissStack });
activateLastFocusSentinel(state);
}
else {
globalState.closedByEvents = false;
removeLocalEvents(state);
removeOutsideFocusEvents(state);
removeMenuPopupEl(state);
removeCloseButtons(state);
removeDismissStack(state.uniqueId);
removeGlobalEvents();
runRemoveScrollbar(open);
onOpen && onOpen(open, dismissStack);
onOpen && onOpen(open, { uniqueId: state.uniqueId, dismissStack });
}

@@ -342,3 +381,2 @@ }, { defer: initDefer }));

removeLocalEvents(state, { onCleanup: true });
removeCloseButtons(state);
removeMenuPopupEl(state);

@@ -350,6 +388,11 @@ removeOutsideFocusEvents(state);

exitTransition("popup", containerEl?.firstElementChild);
exitTransition("overlay", state.overlayEl);
}
});
function renderOverlay() {
return (<div class={typeof state.overlay === "object" ? state.overlay.class : undefined} classList={typeof state.overlay === "object" ? state.overlay.classList || {} : {}} role="presentation" data-solid-dismiss-overlay-backdrop-level={dismissStack.length} onClick={state.onClickOverlayRef} ref={state.refOverlayCb}></div>);
return (<div class={typeof props.overlayElement === "object"
? props.overlayElement.class
: undefined} classList={typeof props.overlayElement === "object"
? props.overlayElement.classList || {}
: {}} role="presentation" onClick={state.onClickOverlayRef} ref={state.refOverlayCb}></div>);
}

@@ -360,7 +403,7 @@ function render(children) {

onFocusSentinel(state, "first", e.relatedTarget);
}} style="position: absolute; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true" ref={state.focusSentinelFirstEl}></div>
}} style="position: fixed; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true" ref={state.focusSentinelFirstEl}></div>
{children}
<div tabindex={state.hasFocusSentinels ? "0" : "-1"} onFocus={() => {
onFocusSentinel(state, "last");
}} style="position: absolute; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true" ref={state.focusSentinelLastEl}></div>
}} style="position: fixed; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true" ref={state.focusSentinelLastEl}></div>
</div>);

@@ -370,2 +413,4 @@ }

return marker;
if (show)
return render(props.children);
let strictEqual = false;

@@ -385,3 +430,3 @@ const condition = createMemo(() => props.open(), undefined, {

if (props.animation) {
return (<Transition name={props.animation.name} enterClass={props.animation.enterClass} enterActiveClass={props.animation.enterActiveClass} enterToClass={props.animation.enterToClass} exitClass={props.animation.exitClass} exitActiveClass={props.animation.exitActiveClass} exitToClass={props.animation.exitToClass} appear={props.animation.appear}>
return (<Transition {...props.animation} name={props.animation.name} enterClass={props.animation.enterClass} enterActiveClass={props.animation.enterActiveClass} enterToClass={props.animation.enterToClass} exitClass={props.animation.exitClass} exitActiveClass={props.animation.exitActiveClass} exitToClass={props.animation.exitToClass} appear={props.animation.appear}>
{finalRender()}

@@ -388,0 +433,0 @@ </Transition>);

@@ -0,25 +1,37 @@

import { dismissStack } from "./dismissStack";
import { globalState, onDocumentClick } from "./globalEvents";
import { removeOutsideFocusEvents } from "./outside";
import { getNextTabbableElement } from "./utils";
export const onClickMenuButton = (state) => {
const { menuButtonBlurTimeoutId, menuBtnEl, closeWhenMenuButtonIsClicked, setOpen, open, } = state;
clearTimeout(state.containerFocusTimeoutId);
clearTimeout(menuButtonBlurTimeoutId);
import { checkThenClose, getNextTabbableElement } from "./utils";
let mousedownFired = false;
export const onClickMenuButton = (state, e) => {
const { timeouts, menuBtnEl, closeWhenMenuButtonIsClicked, setOpen, open } = state;
state.menuBtnKeyupTabFired = false;
if (mousedownFired && !open()) {
mousedownFired = false;
return;
}
mousedownFired = false;
globalState.addedDocumentClick = false;
document.removeEventListener("click", onDocumentClick);
menuBtnEl.focus();
state.containerFocusTimeoutId = null;
clearTimeout(timeouts.containerFocusTimeoutId);
clearTimeout(timeouts.menuButtonBlurTimeoutId);
timeouts.containerFocusTimeoutId = null;
// iOS triggers refocus i think...
if (!open()) {
state.menuBtnEl.addEventListener("focus", state.onFocusMenuButtonRef, {
menuBtnEl.addEventListener("focus", state.onFocusMenuButtonRef, {
once: true,
});
menuBtnEl.addEventListener("keydown", state.onKeydownMenuButtonRef);
menuBtnEl.addEventListener("blur", state.onBlurMenuButtonRef, {
once: true,
});
menuBtnEl.addEventListener("blur", state.onBlurMenuButtonRef);
}
else {
if (closeWhenMenuButtonIsClicked) {
state.menuBtnEl.removeEventListener("focus", state.onFocusMenuButtonRef);
state.menuBtnEl.removeEventListener("keydown", state.onKeydownMenuButtonRef);
state.menuBtnEl.removeEventListener("blur", state.onBlurMenuButtonRef);
}
// if (closeWhenMenuButtonIsClicked) {
// state.menuBtnEl!.removeEventListener("focus", state.onFocusMenuButtonRef);
// state.menuBtnEl!.removeEventListener(
// "keydown",
// state.onKeydownMenuButtonRef
// );
// state.menuBtnEl!.removeEventListener("blur", state.onBlurMenuButtonRef);
// }
}

@@ -30,6 +42,9 @@ if (!closeWhenMenuButtonIsClicked) {

}
if (open()) {
globalState.closedByEvents = true;
}
setOpen(!open());
};
export const onBlurMenuButton = (state, e) => {
const { onClickDocumentRef, containerEl, overlay, setOpen, open } = state;
const { containerEl, overlay, setOpen, timeouts, closeWhenMenuButtonIsClicked, } = state;
if (state.menuBtnKeyupTabFired) {

@@ -39,5 +54,11 @@ state.menuBtnKeyupTabFired = false;

}
if (mousedownFired && !closeWhenMenuButtonIsClicked) {
return;
}
if (!e.relatedTarget) {
if (!overlay) {
document.addEventListener("click", onClickDocumentRef, { once: true });
if (!globalState.addedDocumentClick) {
globalState.addedDocumentClick = true;
document.addEventListener("click", onDocumentClick, { once: true });
}
}

@@ -52,6 +73,23 @@ return;

const run = () => {
globalState.closedByEvents = true;
setOpen(false);
};
state.menuButtonBlurTimeoutId = window.setTimeout(run);
timeouts.menuButtonBlurTimeoutId = window.setTimeout(run);
};
// When reclicking menuButton for closing intention, Safari will trigger blur upon mousedown, which the click event fires after. This results menuPopup close then reopen. This mousedown event prevents that bug.
export const onMouseDownMenuButton = (state) => {
if (!state.open()) {
checkThenClose(dismissStack, (item) => {
if (item.containerEl.contains(state.menuBtnEl))
return;
return item;
}, (item) => {
globalState.closedByEvents = true;
item.setOpen(false);
});
mousedownFired = false;
return;
}
mousedownFired = true;
};
export const onKeydownMenuButton = (state, e) => {

@@ -62,2 +100,3 @@ const { focusSentinelFirstEl, containerEl, menuBtnEl, setOpen, open, onKeydownMenuButtonRef, onBlurMenuButtonRef, } = state;

if (e.key === "Tab" && e.shiftKey) {
globalState.closedByEvents = true;
setOpen(false);

@@ -84,13 +123,6 @@ state.menuBtnKeyupTabFired = true;

export const onFocusMenuButton = (state) => {
const { closeWhenMenuButtonIsTabbed, containerFocusTimeoutId } = state;
const { closeWhenMenuButtonIsTabbed, timeouts } = state;
if (!closeWhenMenuButtonIsTabbed) {
console.log("clear!!");
clearTimeout(containerFocusTimeoutId);
clearTimeout(timeouts.containerFocusTimeoutId);
}
};
export const runAriaExpanded = (state, open) => {
const { useAriaExpanded, menuBtnEl } = state;
if (!useAriaExpanded)
return;
menuBtnEl.setAttribute("aria-expanded", `${open}`);
};
import { queryElement } from "./utils";
export const addMenuPopupEl = (state) => {
const { menuPopup, useAriaExpanded, menuBtnId } = state;
const { menuPopup, menuBtnId } = state;
if (state.menuPopupAdded)

@@ -13,7 +13,2 @@ return;

state.menuPopupEl.setAttribute("tabindex", "-1");
if (!useAriaExpanded)
return;
if (!state.menuPopupEl.getAttribute("aria-labelledby")) {
state.menuPopupEl.setAttribute("aria-labelledby", menuBtnId);
}
}

@@ -20,0 +15,0 @@ };

@@ -0,1 +1,2 @@

import { globalState } from "./globalEvents";
export const onFocusFromOutsideAppOrTab = (state, e) => {

@@ -5,2 +6,3 @@ const { containerEl, setOpen, onClickDocumentRef } = state;

return;
globalState.closedByEvents = true;
setOpen(false);

@@ -15,4 +17,10 @@ state.prevFocusedEl = null;

return;
if (containerEl.contains(e.target))
if (containerEl.contains(e.target)) {
state.addedFocusOutAppEvents = false;
if (state.prevFocusedEl) {
state.prevFocusedEl.removeEventListener("focus", onFocusFromOutsideAppOrTabRef);
}
state.prevFocusedEl = null;
return;
}
if (state.prevFocusedEl) {

@@ -22,2 +30,3 @@ state.prevFocusedEl.removeEventListener("focus", onFocusFromOutsideAppOrTabRef);

state.prevFocusedEl = null;
globalState.closedByEvents = true;
setOpen(false);

@@ -30,3 +39,2 @@ state.addedFocusOutAppEvents = false;

return;
console.log("removeOutsideFocusEvents");
state.prevFocusedEl.removeEventListener("focus", onFocusFromOutsideAppOrTabRef);

@@ -33,0 +41,0 @@ document.removeEventListener("click", onClickDocumentRef);

import { dismissStack } from "./dismissStack";
import { queryElement } from "./utils";
import { globalState } from "./globalEvents";
import { checkThenClose, queryElement } from "./utils";
export const onClickOverlay = (state) => {
const { uniqueId, closeWhenOverlayClicked, menuPopupEl, focusElementOnClose, menuBtnEl, setOpen, } = state;
const { closeWhenOverlayClicked, menuPopupEl, focusElementOnClose, menuBtnEl, } = state;
if (!closeWhenOverlayClicked) {

@@ -17,4 +18,13 @@ menuPopupEl.focus();

}
dismissStack.find((item) => item.uniqueId === uniqueId).queueRemoval = true;
setOpen(false);
checkThenClose(dismissStack, (item) => {
if (item.overlayElement)
return;
return item;
}, (item) => {
const { setOpen } = item;
globalState.closedByEvents = true;
setOpen(false);
});
globalState.closedByEvents = true;
state.setOpen(false);
};

@@ -8,3 +8,3 @@ import { untrack, createComputed, createSignal, children, } from "solid-js";

const resolved = children(() => props.children);
const { onBeforeEnter, onEnter, onAfterEnter, onBeforeExit, onExit, onAfterExit, } = props;
const { onBeforeEnter, onEnter, onAfterEnter, onBeforeExit, onExit, onAfterExit, appendToElement, } = props;
function getClassState(type) {

@@ -17,6 +17,15 @@ const name = props.name || "s";

}
function enterTransition(el, prev) {
const getElement = (el) => {
if (appendToElement) {
return typeof appendToElement === "string"
? el.querySelector(appendToElement)
: appendToElement;
}
return el;
};
function enterTransition(_el, prev) {
const enterClasses = getClassState("enter");
const enterActiveClasses = getClassState("enter-active");
const enterToClasses = getClassState("enter-to");
const el = getElement(_el);
onBeforeEnter && onBeforeEnter(el);

@@ -38,15 +47,16 @@ el.classList.add(enterClasses);

el.classList.remove(enterToClasses);
s1() !== el && set1(el);
s1() !== _el && set1(_el);
onAfterEnter && onAfterEnter(el);
}
}
set1(el);
set1(_el);
}
function exitTransition(el) {
function exitTransition(_el) {
const exitClasses = getClassState("exit");
const exitActiveClasses = getClassState("exit-active");
const exitToClasses = getClassState("exit-to");
if (!el.parentNode)
const el = getElement(_el);
if (!_el.parentNode)
return endTransition();
onBeforeExit && onBeforeExit(el);
onBeforeExit && onBeforeExit(_el);
el.classList.add(exitClasses);

@@ -66,3 +76,3 @@ el.classList.add(exitActiveClasses);

el.classList.remove(exitToClasses);
s1() === el && set1(undefined);
s1() === _el && set1(undefined);
onAfterExit && onAfterExit(el);

@@ -69,0 +79,0 @@ }

@@ -0,1 +1,14 @@

/**
* Iterate stack backwards, checks item, pass it close callback. First falsy value breaks iteration.
*/
export const checkThenClose = (arr, checkCb, destroyCb) => {
for (let i = arr.length - 1; i >= 0; i--) {
const item = checkCb(arr[i]);
if (item) {
destroyCb(item);
continue;
}
return;
}
};
export const findItemReverse = (arr, cb) => {

@@ -170,2 +183,11 @@ for (let i = arr.length - 1; i >= 0; i--) {

}
if (type === "focusElementOnOpen") {
if (typeof inputElement === "string") {
return state.containerEl?.querySelector(inputElement);
}
if (inputElement instanceof Element) {
return inputElement;
}
return inputElement();
}
if (inputElement == null && type === "menuPopup") {

@@ -172,0 +194,0 @@ if (!state.containerEl)

@@ -1,1 +0,3 @@

export {};
declare function userAgent(pattern: RegExp): boolean;
declare const iOS: boolean;
declare const iOS13: boolean;
import { Accessor } from "solid-js";
import { TFocusElementOnClose } from ".";
export declare type TDismissStack = {
import { TDismiss } from ".";
export declare type TDismissStack = Pick<TDismiss, "focusElementOnClose" | "overlayElement"> & {
id: string;

@@ -15,7 +15,11 @@ uniqueId: string;

closeWhenDocumentBlurs: boolean;
closeWhenMenuButtonIsTabbed: boolean;
cursorKeys: boolean;
closeWhenEscapeKeyIsPressed: boolean;
focusElementOnClose: TFocusElementOnClose;
queueRemoval: boolean;
upperStackRemovedByFocusOut: boolean;
timeouts: {
containerFocusTimeoutId: number | null;
menuButtonBlurTimeoutId: number | null;
};
};

@@ -22,0 +26,0 @@ export declare const dismissStack: TDismissStack[];

export declare const globalState: {
closeByFocusSentinel: boolean;
addedDocumentClick: boolean;
closedBySetOpen: boolean;
documentClickTimeout: number | null;
menuBtnEl?: HTMLElement | null;
closedByEvents: boolean;
};
export declare const onDocumentClick: (e: Event) => void;
export declare const onWindowBlur: (e: Event) => void;

@@ -5,0 +11,0 @@ export declare const onKeyDown: (e: KeyboardEvent) => void;

@@ -19,3 +19,3 @@ import "./browserInfo";

*/
onOpen?: (open: boolean, dismissStack: DismissStack[]) => void;
onOpen?: OnOpenHandler;
/**

@@ -26,82 +26,87 @@ * css selector, queried from document, to get menu button element. Or pass JSX element

/**
* Default: root component element queries first child element
*
* css selector, queried from document, to get menu popup element. Or pass JSX element
*
* @defaultValue root component element queries first child element
*/
menuPopup?: string | JSX.Element | (() => JSX.Element);
/**
* Default: `undefined`
*
* css selector, queried from container element, to get close button element(s). Or pass JSX element(s)
*/
closeButtons?: string | JSX.Element | (string | JSX.Element)[] | (() => JSX.Element) | (() => (string | JSX.Element)[]);
/**
* Default: `false`
*
* Have the behavior to move through a list of "dropdown items" using cursor keys.
*
* @defaultValue `false`
*/
cursorKeys?: boolean;
/**
* Default: `false`
*
* Focus will remain inside menuPopup when pressing Tab key
*
* @defaultValue `false`
*/
trapFocus?: boolean;
/**
* Default: focus remains on `"menuButton"`
*
* which element, via selector*, to recieve focus after popup opens.
*
* *css string queried from document, or if string value is `"menuPopup"` uses menuPopup element.
* *css string queried from root component, or if string value is `"menuPopup"` uses menuPopup element.
*
* @defaultValue focus remains on `"menuButton"`
*/
focusElementOnOpen?: "menuPopup" | string | JSX.Element | (() => JSX.Element);
/**
* Default: When Tabbing forwards, focuses on tabbable element*¹ after menuButton. When Tabbing backwards, focuses on menuButton. When pressing Escape key, menuButton will be focused. When "click", user-agent determines which element recieves focus, however if overlay is `true`, then menuButton will be focused instead.
*
* Which element, via selector*², to recieve focus after popup closes.
* Which element, via selector*, to recieve focus after popup closes.
*
* An example would be to emulate native <select> element behavior, set which sets focus to menuButton after dismiss.
* *selector: css string queried from document, or if string value is `"menuButton"` uses menuButton element
*
* *¹ If menuPopup is mounted elsewhere in the DOM or doesn't share the same parent as menuButton, when tabbing outside menuPopup, this library programmatically grabs the correct next tabbable element after menuButton. However if that next tabbable element is inside an iframe that has different origin, then this library won't be able to grab tabbable elements inside it, instead the iframe will be focused.
* @remarks
*
* *² selector: css string queried from document, or if string value is `"menuButton"` uses menuButton element
* If menuPopup is mounted elsewhere in the DOM or doesn't share the same parent as menuButton, when tabbing outside menuPopup, this library programmatically grabs the correct next tabbable element after menuButton. However if that next tabbable element is inside an iframe that has different origin, then this library won't be able to grab tabbable elements inside it, instead the iframe will be focused.
*
*
* @defaultValue
*
* When Tabbing forwards, focuses on tabbable element after menuButton. When Tabbing backwards, focuses on menuButton. When pressing Escape key, menuButton will be focused. When programmatically closed, such as clicking close button, then menuButton will be focused. When "click" outside, user-agent determines which element recieves focus, however if `Dismiss.overlay` or `Dismiss.overlayElement` are set, then menuButton will be focused instead.
*/
focusElementOnClose?: TFocusElementOnClose;
focusElementOnClose?: "menuButton" | string | JSX.Element | FocusElementOnCloseOptions;
/**
* Default: `false`
*
* When `true`, after focusing within menuPopup, if focused back to menu button via keyboard (Tab key), the menuPopup will close.
*
* @defaultValue `false`
*/
closeWhenMenuButtonIsTabbed?: boolean;
/**
* Default: `true`
*
* If `overlay` is `true`, menuPopup will always close when menu button is clicked
*
* @defaultValue `false`
*/
closeWhenMenuButtonIsClicked?: boolean;
/**
* Default: `false`
*
* Closes menuPopup when any scrollable container (except inside menuPopup) is scrolled
*
* Note: Even when `true`, scrolling in "outside" scrollable iframe won't be able to close menuPopup.
* @remark
*
* Even when `true`, scrolling in "outside" scrollable iframe won't be able to close menuPopup.
*
* @defaultValue `false`
*/
closeWhenScrolling?: boolean;
/**
* Default: `true`
*
* If `false`, menuPopup won't close when overlay backdrop is clicked. When overlay clicked, menuPopup will recieve focus.
*
* @defaultValue `true`
*/
closeWhenOverlayClicked?: boolean;
/**
* Default: `true`
*
* Closes menuPopup when escape key is pressed
*
* @defaultValue `true`
*/
closeWhenEscapeKeyIsPressed?: boolean;
/**
* Default: `false`
*

@@ -112,16 +117,25 @@ * Closes when the document "blurs". This would happen when interacting outside of the page such as Devtools, changing browser tabs, or switch different applications.

/**
* Default: `false`
*
* If `true`, sets "overflow: hidden" declaration to Document.scrollingElement.
*
* Use callback function if author wants customize how the scrollbar is removed.
* @defaultValue `false`
*/
removeScrollbar?: boolean;
/**
* Default `false`
* Prevent page interaction when clicking outside to close menuPopup
*
* Adds root level div that acts as a layer. This removes interaction of the page elements that's underneath the overlay element, that way menuPopup is the only element that can be interacted with. Author must ensure that menuPopup is placed above overlay element, one of the ways, is to nest this Component inside Solid's {@link https://www.solidjs.com/docs/latest/api#%3Cportal%3E Portal}.
* Author must create overlay element within menuPopup, this way page elements underneath the menuPopup can't be interacted with.
*
*
* @defaultValue `false`
*/
overlay?: boolean | {
overlay?: boolean;
/**
* Prevent page interaction when clicking outside to close menuPopup
*
* Adds root level div that acts as a layer. This removes interaction of the page elements that's underneath the overlay element, that way menuPopup is the only element that can be interacted with.
*
* @defaultValue `false`
*/
overlayElement?: boolean | {
ref?: (el: HTMLElement) => void;

@@ -132,51 +146,50 @@ class?: string;

};
animation?: TAnimation;
animation?: DismissAnimation;
};
/**
* Default: `false`
*
* If `true` add aria attributes for generic expand/collapse dropdown.
* activates sentinel element as last tabbable item in menuPopup, that way when Tabbing "forwards" out of menuPopup, the next logical tabblable element after menuButton will be focused.
*
*
* @defaultValue `false` unless `Dismiss.mount` is set, `Dismiss.focusElementOnClosed` is set, `Dismiss.overlay` prop is `true`, or this component's root container is not an adjacent sibling of menuButton.
*/
useAriaExpanded?: boolean;
enableLastFocusSentinel?: boolean;
/**
* Default: `false`
*
* If `true` activates sentinel element as last tabbable item in menuPopup, that way when Tabbing "forwards" out of menuPopup, the next logical tabblable element after menuButton will be focused.
* Inserts menuPopup in the mount node. Useful for inserting menuPopup outside of page layout. Events still propagate through the Component Hierarchy.
*/
mount?: string | Node;
/**
* Place CSS class names or JS Web Animation to fire animation as menuPopup enters/exits
*
* Automatically set to `true` for the following: `overlay` prop is `true`, this component's root container is not an adjacent sibling of menuButton, or `focusElWhenClosed` prop has a value.
* @defaultValue none
*/
mountedElseWhere?: boolean;
mount?: string | Node;
animation?: TAnimation;
animation?: DismissAnimation;
/**
* Determine whether children are rendered always, or conditionally.
*
* If `true`, children are rendered.
*
* @defaultValue `false`, children are conditionally rendered based on `Dismiss.open` value.
*/
show?: boolean;
};
declare type TAnimation = {
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;
appear?: boolean;
};
export declare type TFocusElementOnClose = "menuButton" | string | JSX.Element | {
export declare type FocusElementOnCloseOptions = {
/**
* Default: menuButton
*
* focus on element when exiting menuPopup via tabbing backwards ie "Shift + Tab".
*
* @defaultValue `"menuButton"`
*
*/
tabBackwards?: "menuButton" | string | JSX.Element;
/**
* Default: next tabbable element after menuButton;
*
* focus on element when exiting menuPopup via tabbing forwards ie "Tab".
*
* Note: If popup is mounted elsewhere in the DOM, when tabbing outside, this library is able to grab the correct next tabbable element after menuButton, except for tabbable elements inside iframe with cross domain.
* @remarks
*
* If popup is mounted elsewhere in the DOM, when tabbing outside, this library is able to grab the correct next tabbable element after menuButton, except for tabbable elements inside iframe with cross domain.
*
* @defaultValue next tabbable element after menuButton;
*/

@@ -187,20 +200,62 @@ tabForwards?: "menuButton" | string | JSX.Element;

*
* If overlay present, and popup closes via click, then menuButton will be focused.
* If mounted overlay present, and popup closes via click, then menuButton will be focused.
*
* Note: When clicking, user-agent determines which element recieves focus, to prevent this, use `overlay` prop.
* @remarks
*
* When clicking, user-agent determines which element recieves focus.
*/
click?: "menuButton" | string | JSX.Element;
/**
* Default: menuButton
*
* focus on element when exiting menuPopup via "Escape" key
*
* @defaultValue `"menuButton"`
*/
escapeKey?: "menuButton" | string | JSX.Element;
/**
* Default: menuButton
*
* focus on element when exiting menuPopup via scrolling, from scrollable container that contains menuButton
*
* @dafaultValue `"menuButton"`
*/
scrolling?: "menuButton" | string | JSX.Element;
};
export declare type DismissAnimation = {
/**
* Used to automatically generate transition CSS class names. e.g. name: 'fade' will auto expand to .fade-enter, .fade-enter-active, etc.
*/
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;
/**
* Change element where CSS classes are appended and passed to callbacks.
*
* css selector, queried from root component, to get menu popup element. Or pass JSX element
*
* Using `"container"` value will use root element of the component
*
* @defaultValue The element is the root element of the component, where CSS classes are appended to, and it is also passed to callbacks
*/
appendToElement?: string | Node;
/**
* Whether to apply transition on initial render.
*
* @defaultValue `false`
*/
appear?: boolean;
};
export declare type OnOpenHandler = (open: boolean, props: {
uniqueId: string;
dismissStack: DismissStack[];
}) => void;
export declare type DismissStack = TDismissStack;

@@ -207,0 +262,0 @@ /**

@@ -1,33 +0,7 @@

import { JSX, Accessor } from "solid-js";
import { TFocusElementOnClose } from ".";
import { TDismissStack } from "./dismissStack";
export declare type TLocalState = {
import { Accessor } from "solid-js";
import { TDismiss } from ".";
export declare type TLocalState = Omit<TDismiss, "id" | "ref" | "animation" | "onOpen" | "class" | "classList"> & {
uniqueId: string;
id: string;
menuButton: JSX.Element | (() => JSX.Element);
menuPopup: string | JSX.Element | (() => JSX.Element);
focusElementOnOpen: "menuPopup" | string | JSX.Element | (() => JSX.Element);
focusElementOnClose: TFocusElementOnClose;
closeButtons: string | JSX.Element | (string | JSX.Element)[] | (() => JSX.Element) | (() => (string | JSX.Element)[]);
cursorKeys: boolean;
closeWhenMenuButtonIsTabbed: boolean;
closeWhenMenuButtonIsClicked: boolean;
closeWhenScrolling: boolean;
closeWhenDocumentBlurs: boolean;
closeWhenOverlayClicked: boolean;
closeWhenEscapeKeyIsPressed: boolean;
overlay: boolean | {
ref?: (el: HTMLElement) => void;
class?: string;
classList?: {
[key: string]: boolean;
};
};
trapFocus: boolean;
removeScrollbar: boolean | ((open: boolean, dismissStack: TDismissStack[]) => void);
useAriaExpanded: boolean;
mount: boolean;
mountedElseWhere: boolean;
hasFocusSentinels: boolean;
closeBtns: HTMLElement[];
menuPopupEl?: HTMLElement | null;

@@ -39,3 +13,2 @@ menuBtnEl?: HTMLElement;

overlayEl?: HTMLDivElement;
closeBtnsAdded: boolean;
menuPopupAdded: boolean;

@@ -46,4 +19,7 @@ menuBtnId: string;

prevFocusedEl?: HTMLElement | null;
containerFocusTimeoutId: number | null;
menuButtonBlurTimeoutId: number | null;
stopComponentEventPropagation?: boolean;
timeouts: {
containerFocusTimeoutId: number | null;
menuButtonBlurTimeoutId: number | null;
};
refContainerCb: (el: HTMLElement) => void;

@@ -60,6 +36,7 @@ refOverlayCb: (el: HTMLElement) => void;

onFocusMenuButtonRef: (e: Event) => void;
onClickCloseButtonsRef: (e: Event) => void;
onMouseDownMenuButtonRef: () => void;
setOpen: (v: boolean) => void;
open: Accessor<boolean>;
upperStackRemovedByFocusOut: boolean;
closeByDismissEvent: boolean;
};
import { TLocalState } from "./localState";
export declare const onClickMenuButton: (state: TLocalState) => void;
export declare const onClickMenuButton: (state: TLocalState, e: Event) => void;
export declare const onBlurMenuButton: (state: TLocalState, e: FocusEvent) => void;
export declare const onMouseDownMenuButton: (state: TLocalState) => void;
export declare const onKeydownMenuButton: (state: TLocalState, e: KeyboardEvent) => void;
export declare const onFocusMenuButton: (state: TLocalState) => void;
export declare const runAriaExpanded: (state: TLocalState, open: boolean) => void;
import { Component, JSX } from "solid-js";
declare type TransitionProps = {
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;
children?: JSX.Element;
appear?: boolean;
};
export declare const Transition: Component<TransitionProps>;
export {};
import { DismissAnimation } from ".";
export declare const Transition: Component<DismissAnimation & {
children: JSX.Element;
}>;
import { TLocalState } from "./localState";
/**
* Iterate stack backwards, checks item, pass it close callback. First falsy value breaks iteration.
*/
export declare const checkThenClose: <T extends unknown>(arr: T[], checkCb: (item: T) => T, destroyCb: (item: T) => void) => void;
export declare const findItemReverse: <T extends unknown>(arr: T[], cb: (item: T) => any) => [T, number];

@@ -24,4 +28,4 @@ export declare const parseValToNum: (value?: string | number) => number;

inputElement: any;
type?: "menuButton" | "menuPopup" | "closeButton" | "focusElementOnClose";
type?: "menuButton" | "menuPopup" | "closeButton" | "focusElementOnClose" | "focusElementOnOpen";
subType?: "tabForwards" | "tabBackwards" | "click" | "escapeKey" | "scrolling";
}) => HTMLElement;
{
"name": "solid-dismiss",
"version": "1.0.5",
"version": "1.0.6",
"homepage": "https://aquaductape.github.io/solid-dismiss/",

@@ -36,2 +36,3 @@ "description": "Handles \"click outside\" behavior for popup menu. Closing is triggered by click/focus outside of popup element or pressing \"Escape\" key.",

"build": "rollup -c",
"build:doc": "typedoc --sort source-order --entryPoints ./package/index.tsx ",
"prepublishOnly": "npm run build",

@@ -56,4 +57,5 @@ "release": "release-it",

"rollup-preset-solid": "^0.3.0",
"solid-js": "^1.0.7"
"solid-js": "^1.0.7",
"typedoc": "^0.22.4"
}
}
<h1 align="center">Solid Dismiss</h1>
<!-- https://img.shields.io/badge/size%20(gzip)-~6kb-brightgreen?style=for-the-badge -->
https://solid-dismiss-test.netlify.app/

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc