solid-dismiss
Advanced tools
Comparing version 1.0.1 to 1.0.2
@@ -176,3 +176,3 @@ import { template, setAttribute, effect, delegateEvents, createComponent, insert, Portal, style, classList } from 'solid-js/web'; | ||
const isTopStackOverlayBlock = () => dismissStack[dismissStack.length - 1].overlay === "block"; | ||
const isTopStackOverlayBlock = () => dismissStack[dismissStack.length - 1].overlay === "backdrop"; | ||
@@ -312,7 +312,7 @@ const getTopClippedOverlayStack = () => { | ||
const { | ||
toggle, | ||
open, | ||
containerEl | ||
} = stack; | ||
if (!containerEl.isConnected) return; | ||
if (!toggle()) return; | ||
if (!open()) return; | ||
updateSVG(stack); | ||
@@ -328,7 +328,7 @@ }, timeoutAmount); | ||
const { | ||
toggle, | ||
open, | ||
containerEl | ||
} = stack; | ||
if (!containerEl.isConnected) return; | ||
if (!toggle()) return; | ||
if (!open()) return; | ||
updateSVG(stack); | ||
@@ -344,3 +344,3 @@ }, resizeTimeoutAmount); | ||
const { | ||
toggle, | ||
open, | ||
containerEl, | ||
@@ -350,3 +350,3 @@ menuPopupEl, | ||
} = stack; | ||
if (!toggle()) return; | ||
if (!open()) return; | ||
const target = e.target; | ||
@@ -364,3 +364,3 @@ if (!(target === containerEl || target === menuPopupEl || target === menuBtnEl)) return; | ||
const { | ||
toggle, | ||
open, | ||
containerEl, | ||
@@ -370,3 +370,3 @@ menuPopupEl, | ||
} = stack; | ||
if (!toggle()) return; | ||
if (!open()) return; | ||
const target = e.target; | ||
@@ -386,5 +386,5 @@ if (!(target === containerEl || target === menuPopupEl || target === menuBtnEl)) return; | ||
containerEl, | ||
toggle | ||
open | ||
} = stack; | ||
if (!toggle()) return; | ||
if (!open()) return; | ||
const target = e.target; | ||
@@ -648,6 +648,5 @@ if (!(target.contains(menuBtnEl) || target.contains(containerEl))) return; | ||
const _tmpl$ = template(`<div tabindex="-1"></div>`, 2), | ||
_tmpl$2 = template(`<div></div>`, 2), | ||
_tmpl$3 = template(`<div role="presentation"></div>`, 2), | ||
_tmpl$4 = template(`<div tabindex="0" style="position: absolute; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true"></div>`, 2); | ||
const _tmpl$ = template(`<div></div>`, 2), | ||
_tmpl$2 = template(`<div role="presentation"></div>`, 2), | ||
_tmpl$3 = template(`<div tabindex="0" style="position: absolute; top: 0; left: 0; outline: none; pointer-events: none; width: 0; height: 0;" aria-hidden="true"></div>`, 2); | ||
// buttons can't receive focus on tap, only through invoking `focus()` method | ||
@@ -669,4 +668,4 @@ // blur (tested so far on only buttons) will fire even on tapping same focused button (which would be invoked `focus()` ) | ||
menuPopup, | ||
focusOnLeave, | ||
focusOnActive, | ||
focusElWhenClosed, | ||
focusElWhenOpened, | ||
closeButton, | ||
@@ -678,2 +677,4 @@ children, | ||
closeWhenScrolling = false, | ||
closeWhenWindowBlurs = false, | ||
closeWhenClickedOutside = true, | ||
overlay = false, | ||
@@ -685,3 +686,3 @@ trapFocus = false, | ||
const uniqueId = createUniqueId(); | ||
const hasFocusSentinels = focusOnLeave || overlay === "block" || trapFocus; | ||
const hasFocusSentinels = focusElWhenClosed || overlay === "backdrop" || trapFocus; | ||
let closeBtn = []; | ||
@@ -702,3 +703,3 @@ let menuPopupEl = null; | ||
const refCb = el => { | ||
const refContainerCb = el => { | ||
if (props.ref) { | ||
@@ -714,7 +715,7 @@ // @ts-ignore | ||
let menuButtonBlurTimeoutId = null; | ||
const initDefer = !props.toggle(); | ||
const initDefer = !props.open(); | ||
const runFocusOnActive = () => { | ||
if (focusOnActive == null) return; | ||
const el = queryElement(focusOnActive); | ||
if (focusElWhenOpened == null) return; | ||
const el = queryElement(focusElWhenOpened); | ||
@@ -726,5 +727,5 @@ if (el) { | ||
const expandToggle = toggle => { | ||
const runAriaExpanded = open => { | ||
if (!useAriaExpanded) return; | ||
menuBtnEl.setAttribute("aria-expanded", `${toggle}`); | ||
menuBtnEl.setAttribute("aria-expanded", `${open}`); | ||
}; | ||
@@ -774,3 +775,3 @@ | ||
if (!item) return; | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "escapeKey") || item.menuBtnEl; | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "escapeKey") || item.menuBtnEl; | ||
@@ -781,3 +782,3 @@ if (el) { | ||
item.setToggle(false); | ||
item.setOpen(false); | ||
}; | ||
@@ -789,6 +790,6 @@ | ||
if (target.contains(menuBtnEl)) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
} | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "scrolling") || menuBtnEl; | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "scrolling") || menuBtnEl; | ||
@@ -800,5 +801,19 @@ if (el) { | ||
const onWindowBlur = () => { | ||
menuBtnEl.focus(); | ||
props.setOpen(false); | ||
}; | ||
const onClickOverlay = () => { | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "click") || menuBtnEl; | ||
console.log({ | ||
closeWhenClickedOutside | ||
}); | ||
if (!closeWhenClickedOutside) { | ||
menuPopupEl.focus(); | ||
return; | ||
} | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "click") || menuBtnEl; | ||
if (el) { | ||
@@ -808,7 +823,7 @@ el.focus(); | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
const onClickCloseButton = () => { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
@@ -821,14 +836,13 @@ | ||
containerFocusTimeoutId = null; | ||
const toggleVal = props.toggle(); | ||
if (!closeWhenMenuButtonIsClicked) { | ||
props.setToggle(true); | ||
props.setOpen(true); | ||
return; | ||
} | ||
props.setToggle(!toggleVal); | ||
props.setOpen(!props.open()); | ||
}; | ||
const onBlurMenuButton = e => { | ||
if (!props.toggle()) return; | ||
if (!props.open()) return; | ||
@@ -854,3 +868,3 @@ if (menuBtnKeyupTabFired) { | ||
const run = () => { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
@@ -862,6 +876,6 @@ | ||
const onKeydownMenuButton = e => { | ||
if (!props.toggle()) return; | ||
if (!props.open()) return; | ||
if (e.key === "Tab" && e.shiftKey) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
menuBtnKeyupTabFired = true; | ||
@@ -898,3 +912,3 @@ menuBtnEl.removeEventListener("keydown", onKeydownMenuButton); | ||
prevFocusedEl = null; | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
addedFocusOutAppEvents = false; | ||
@@ -905,3 +919,3 @@ }; | ||
if (containerEl.contains(e.target)) return; | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
prevFocusedEl = null; | ||
@@ -922,7 +936,7 @@ addedFocusOutAppEvents = false; | ||
if (focusOnLeave || overlay === "block") { | ||
if (focusElWhenClosed || overlay === "backdrop") { | ||
e.stopImmediatePropagation(); | ||
} | ||
if (!props.toggle()) return; | ||
if (!props.open()) return; | ||
@@ -946,3 +960,3 @@ if (!e.relatedTarget) { | ||
console.log("focusout"); | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
@@ -988,3 +1002,3 @@ if (props.setFocus) { | ||
if (closeWhenMenuButtonIsTabbed) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
menuBtnEl.focus(); | ||
@@ -994,3 +1008,3 @@ return; | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "tabBackwards") || menuBtnEl; | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "tabBackwards") || menuBtnEl; | ||
@@ -1002,3 +1016,3 @@ if (el) { | ||
if (el !== menuBtnEl) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
} | ||
@@ -1017,3 +1031,3 @@ | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "tabForwards") || getNextFocusableElement({ | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "tabForwards") || getNextFocusableElement({ | ||
from: menuBtnEl, | ||
@@ -1027,3 +1041,3 @@ ignoreElement: [containerEl] | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
@@ -1042,8 +1056,9 @@ | ||
if (!containerEl) return null; | ||
return menuPopupEl || containerEl; | ||
if (menuPopupEl) return menuPopupEl; | ||
if (hasFocusSentinels) return containerEl.children[1]; | ||
return containerEl.children[0]; | ||
} | ||
if (typeof inputElement === "string" && type === "menuButton") { | ||
if (!containerEl) return null; | ||
return containerEl.querySelector(inputElement); | ||
return document.querySelector(inputElement); | ||
} | ||
@@ -1164,2 +1179,3 @@ | ||
menuPopupAdded = true; | ||
menuPopupEl.setAttribute("tabindex", "-1"); | ||
if (!useAriaExpanded) return; | ||
@@ -1173,2 +1189,21 @@ | ||
const runRemoveScrollbar = open => { | ||
if (!removeScrollbar) return; | ||
if (typeof removeScrollbar === "function") { | ||
removeScrollbar(open, dismissStack); | ||
return; | ||
} | ||
if (dismissStack.length > 1) return; | ||
if (open) { | ||
const el = document.scrollingElement; | ||
el.style.overflow = "hidden"; | ||
} else { | ||
const el = document.scrollingElement; | ||
el.style.overflow = ""; | ||
} | ||
}; | ||
const removeMenuPopupEl = () => { | ||
@@ -1197,3 +1232,3 @@ if (!menuPopupEl) return; | ||
menuBtnId = menuBtnEl.id; | ||
expandToggle(props.toggle()); | ||
runAriaExpanded(props.open()); | ||
@@ -1205,25 +1240,5 @@ if (!menuBtnId) { | ||
}); | ||
createEffect(on(() => !!props.open(), (open, prevOpen) => { | ||
if (open === prevOpen) return; | ||
const runRemoveScrollbar = toggle => { | ||
if (!removeScrollbar) return; | ||
if (typeof removeScrollbar === "function") { | ||
removeScrollbar(toggle, dismissStack); | ||
return; | ||
} | ||
if (dismissStack.length > 1) return; | ||
if (toggle) { | ||
const el = document.scrollingElement; | ||
el.style.overflow = "hidden"; | ||
} else { | ||
const el = document.scrollingElement; | ||
el.style.overflow = ""; | ||
} | ||
}; | ||
createEffect(on(() => !!props.toggle(), (toggle, prevToggle) => { | ||
if (toggle === prevToggle) return; | ||
if (closeWhenScrolling) { | ||
@@ -1236,5 +1251,11 @@ window.addEventListener("scroll", onScrollClose, { | ||
expandToggle(toggle); | ||
if (closeWhenWindowBlurs) { | ||
window.addEventListener("blur", onWindowBlur, { | ||
once: true | ||
}); | ||
} | ||
if (toggle) { | ||
runAriaExpanded(open); | ||
if (open) { | ||
addCloseButtons(); | ||
@@ -1252,4 +1273,4 @@ addMenuPopupEl(); | ||
uniqueId, | ||
toggle: props.toggle, | ||
setToggle: props.setToggle, | ||
open: props.open, | ||
setOpen: props.setOpen, | ||
containerEl, | ||
@@ -1263,3 +1284,3 @@ menuBtnEl, | ||
}); | ||
runRemoveScrollbar(toggle); | ||
runRemoveScrollbar(open); | ||
@@ -1281,3 +1302,3 @@ if (isOverlayClipped) { | ||
runRemoveScrollbar(toggle); | ||
runRemoveScrollbar(open); | ||
@@ -1296,3 +1317,3 @@ if (dismissStack.length < 1) { | ||
() => props.overlay.clipped.redrawClippedPath(), result => { | ||
if (result === null || !props.toggle()) return; | ||
if (result === null || !props.open()) return; | ||
console.log("run redraw"); | ||
@@ -1321,3 +1342,3 @@ updateSVG(null); | ||
get when() { | ||
return props.toggle(); | ||
return props.open(); | ||
}, | ||
@@ -1328,4 +1349,4 @@ | ||
const _ref$ = refCb; | ||
typeof _ref$ === "function" ? _ref$(_el$) : refCb = _el$; | ||
const _ref$ = refContainerCb; | ||
typeof _ref$ === "function" ? _ref$(_el$) : refContainerCb = _el$; | ||
_el$.$$focusout = onFocusOutContainer; | ||
@@ -1340,3 +1361,3 @@ _el$.$$focusin = onFocusInContainer; | ||
get children() { | ||
const _el$2 = _tmpl$2.cloneNode(true); | ||
const _el$2 = _tmpl$.cloneNode(true); | ||
@@ -1365,5 +1386,5 @@ const _ref$2 = overlayEl; | ||
insert(_el$, overlay === "block" && createComponent(Portal, { | ||
insert(_el$, overlay === "backdrop" && createComponent(Portal, { | ||
get children() { | ||
const _el$3 = _tmpl$3.cloneNode(true); | ||
const _el$3 = _tmpl$2.cloneNode(true); | ||
@@ -1374,3 +1395,3 @@ const _ref$3 = overlayEl; | ||
setAttribute(_el$3, "solid-dismiss-overlay", id); | ||
setAttribute(_el$3, "solid-dismiss-overlay-backdrop", id); | ||
@@ -1380,3 +1401,3 @@ effect(_p$ => { | ||
_v$7 = `position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: ${999 + dismissStack.length};`; | ||
_v$6 !== _p$._v$6 && setAttribute(_el$3, "solid-dismiss-overlay-level", _p$._v$6 = _v$6); | ||
_v$6 !== _p$._v$6 && setAttribute(_el$3, "solid-dismiss-overlay-backdrop-level", _p$._v$6 = _v$6); | ||
_p$._v$7 = style(_el$3, _v$7, _p$._v$7); | ||
@@ -1395,3 +1416,3 @@ return _p$; | ||
insert(_el$, hasFocusSentinels && (() => { | ||
const _el$4 = _tmpl$4.cloneNode(true); | ||
const _el$4 = _tmpl$3.cloneNode(true); | ||
@@ -1411,3 +1432,3 @@ const _ref$4 = focusSentinelFirstEl; | ||
insert(_el$, hasFocusSentinels && (() => { | ||
const _el$5 = _tmpl$4.cloneNode(true); | ||
const _el$5 = _tmpl$3.cloneNode(true); | ||
@@ -1425,3 +1446,3 @@ const _ref$5 = focusSentinelLastEl; | ||
_v$2 = props.classList || {}, | ||
_v$3 = overlay === "block" ? `z-index: ${1000 + dismissStack.length}` : ""; | ||
_v$3 = overlay === "backdrop" ? `z-index: ${1000 + dismissStack.length}` : ""; | ||
@@ -1428,0 +1449,0 @@ _v$ !== _p$._v$ && (_el$.className = _p$._v$ = _v$); |
@@ -15,3 +15,3 @@ import { dismissStack } from "./dismissStack"; | ||
const overlaySize = { height: 0, width: 0 }; | ||
const isTopStackOverlayBlock = () => dismissStack[dismissStack.length - 1].overlay === "block"; | ||
const isTopStackOverlayBlock = () => dismissStack[dismissStack.length - 1].overlay === "backdrop"; | ||
const getTopClippedOverlayStack = () => { | ||
@@ -144,6 +144,6 @@ for (let i = dismissStack.length - 1; i >= 0; i--) { | ||
const stack = getTopClippedOverlayStack(); | ||
const { toggle, containerEl } = stack; | ||
const { open, containerEl } = stack; | ||
if (!containerEl.isConnected) | ||
return; | ||
if (!toggle()) | ||
if (!open()) | ||
return; | ||
@@ -159,6 +159,6 @@ updateSVG(stack); | ||
const stack = getTopClippedOverlayStack(); | ||
const { toggle, containerEl } = stack; | ||
const { open, containerEl } = stack; | ||
if (!containerEl.isConnected) | ||
return; | ||
if (!toggle()) | ||
if (!open()) | ||
return; | ||
@@ -174,4 +174,4 @@ updateSVG(stack); | ||
const stack = getTopClippedOverlayStack(); | ||
const { toggle, containerEl, menuPopupEl, menuBtnEl } = stack; | ||
if (!toggle()) | ||
const { open, containerEl, menuPopupEl, menuBtnEl } = stack; | ||
if (!open()) | ||
return; | ||
@@ -192,4 +192,4 @@ const target = e.target; | ||
const stack = getTopClippedOverlayStack(); | ||
const { toggle, containerEl, menuPopupEl, menuBtnEl } = stack; | ||
if (!toggle()) | ||
const { open, containerEl, menuPopupEl, menuBtnEl } = stack; | ||
if (!open()) | ||
return; | ||
@@ -210,4 +210,4 @@ const target = e.target; | ||
const stack = getTopClippedOverlayStack(); | ||
const { menuBtnEl, containerEl, toggle } = stack; | ||
if (!toggle()) | ||
const { menuBtnEl, containerEl, open } = stack; | ||
if (!open()) | ||
return; | ||
@@ -214,0 +214,0 @@ const target = e.target; |
@@ -18,5 +18,5 @@ import "./browserInfo"; | ||
const Dismiss = (props) => { | ||
const { id = "", menuButton, menuPopup, focusOnLeave, focusOnActive, closeButton, children, cursorKeys = false, closeWhenMenuButtonIsTabbed = false, closeWhenMenuButtonIsClicked = true, closeWhenScrolling = false, overlay = false, trapFocus = false, removeScrollbar = false, useAriaExpanded = false, } = props; | ||
const { id = "", menuButton, menuPopup, focusElWhenClosed, focusElWhenOpened, closeButton, children, cursorKeys = false, closeWhenMenuButtonIsTabbed = false, closeWhenMenuButtonIsClicked = true, closeWhenScrolling = false, closeWhenWindowBlurs = false, closeWhenClickedOutside = true, overlay = false, trapFocus = false, removeScrollbar = false, useAriaExpanded = false, } = props; | ||
const uniqueId = createUniqueId(); | ||
const hasFocusSentinels = focusOnLeave || overlay === "block" || trapFocus; | ||
const hasFocusSentinels = focusElWhenClosed || overlay === "backdrop" || trapFocus; | ||
let closeBtn = []; | ||
@@ -36,3 +36,3 @@ let menuPopupEl = null; | ||
let prevFocusedEl = null; | ||
const refCb = (el) => { | ||
const refContainerCb = (el) => { | ||
if (props.ref) { | ||
@@ -44,9 +44,16 @@ // @ts-ignore | ||
}; | ||
const refOverlayCb = (el) => { | ||
if (props.ref) { | ||
// @ts-ignore | ||
props.ref(el); | ||
} | ||
containerEl = el; | ||
}; | ||
let containerFocusTimeoutId = null; | ||
let menuButtonBlurTimeoutId = null; | ||
const initDefer = !props.toggle(); | ||
const initDefer = !props.open(); | ||
const runFocusOnActive = () => { | ||
if (focusOnActive == null) | ||
if (focusElWhenOpened == null) | ||
return; | ||
const el = queryElement(focusOnActive); | ||
const el = queryElement(focusElWhenOpened); | ||
if (el) { | ||
@@ -56,6 +63,6 @@ el.focus(); | ||
}; | ||
const expandToggle = (toggle) => { | ||
const runAriaExpanded = (open) => { | ||
if (!useAriaExpanded) | ||
return; | ||
menuBtnEl.setAttribute("aria-expanded", `${toggle}`); | ||
menuBtnEl.setAttribute("aria-expanded", `${open}`); | ||
}; | ||
@@ -100,7 +107,8 @@ const onCursorKeys = (e) => { | ||
return; | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "escapeKey") || item.menuBtnEl; | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "escapeKey") || | ||
item.menuBtnEl; | ||
if (el) { | ||
el.focus(); | ||
} | ||
item.setToggle(false); | ||
item.setOpen(false); | ||
}; | ||
@@ -110,5 +118,5 @@ const onScrollClose = (e) => { | ||
if (target.contains(menuBtnEl)) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
} | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "scrolling") || menuBtnEl; | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "scrolling") || menuBtnEl; | ||
if (el) { | ||
@@ -118,11 +126,20 @@ el.focus(); | ||
}; | ||
const onWindowBlur = () => { | ||
menuBtnEl.focus(); | ||
props.setOpen(false); | ||
}; | ||
const onClickOverlay = () => { | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "click") || menuBtnEl; | ||
console.log({ closeWhenClickedOutside }); | ||
if (!closeWhenClickedOutside) { | ||
menuPopupEl.focus(); | ||
return; | ||
} | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "click") || menuBtnEl; | ||
if (el) { | ||
el.focus(); | ||
} | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
const onClickCloseButton = () => { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
@@ -134,11 +151,10 @@ const onClickMenuButton = () => { | ||
containerFocusTimeoutId = null; | ||
const toggleVal = props.toggle(); | ||
if (!closeWhenMenuButtonIsClicked) { | ||
props.setToggle(true); | ||
props.setOpen(true); | ||
return; | ||
} | ||
props.setToggle(!toggleVal); | ||
props.setOpen(!props.open()); | ||
}; | ||
const onBlurMenuButton = (e) => { | ||
if (!props.toggle()) | ||
if (!props.open()) | ||
return; | ||
@@ -159,3 +175,3 @@ if (menuBtnKeyupTabFired) { | ||
const run = () => { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
@@ -165,6 +181,6 @@ menuButtonBlurTimeoutId = window.setTimeout(run); | ||
const onKeydownMenuButton = (e) => { | ||
if (!props.toggle()) | ||
if (!props.open()) | ||
return; | ||
if (e.key === "Tab" && e.shiftKey) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
menuBtnKeyupTabFired = true; | ||
@@ -196,3 +212,3 @@ menuBtnEl.removeEventListener("keydown", onKeydownMenuButton); | ||
prevFocusedEl = null; | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
addedFocusOutAppEvents = false; | ||
@@ -203,3 +219,3 @@ }; | ||
return; | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
prevFocusedEl = null; | ||
@@ -218,6 +234,6 @@ addedFocusOutAppEvents = false; | ||
console.log("runfocusout!!!"); | ||
if (focusOnLeave || overlay === "block") { | ||
if (focusElWhenClosed || overlay === "backdrop") { | ||
e.stopImmediatePropagation(); | ||
} | ||
if (!props.toggle()) | ||
if (!props.open()) | ||
return; | ||
@@ -239,3 +255,3 @@ if (!e.relatedTarget) { | ||
console.log("focusout"); | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
if (props.setFocus) { | ||
@@ -274,7 +290,8 @@ props.setFocus(false); | ||
if (closeWhenMenuButtonIsTabbed) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
menuBtnEl.focus(); | ||
return; | ||
} | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "tabBackwards") || menuBtnEl; | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "tabBackwards") || | ||
menuBtnEl; | ||
if (el) { | ||
@@ -284,3 +301,3 @@ el.focus(); | ||
if (el !== menuBtnEl) { | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
} | ||
@@ -296,3 +313,3 @@ return; | ||
} | ||
const el = queryElement(focusOnLeave, "focusOnLeave", "tabForwards") || | ||
const el = queryElement(focusElWhenClosed, "focusOnLeave", "tabForwards") || | ||
getNextFocusableElement({ | ||
@@ -305,3 +322,3 @@ from: menuBtnEl, | ||
} | ||
props.setToggle(false); | ||
props.setOpen(false); | ||
}; | ||
@@ -318,8 +335,10 @@ const queryElement = (inputElement, type, subType) => { | ||
return null; | ||
return menuPopupEl || containerEl; | ||
if (menuPopupEl) | ||
return menuPopupEl; | ||
if (hasFocusSentinels) | ||
return containerEl.children[1]; | ||
return containerEl.children[0]; | ||
} | ||
if (typeof inputElement === "string" && type === "menuButton") { | ||
if (!containerEl) | ||
return null; | ||
return containerEl.querySelector(inputElement); | ||
return document.querySelector(inputElement); | ||
} | ||
@@ -424,2 +443,3 @@ if (typeof inputElement === "string" && type === "closeButton") { | ||
menuPopupAdded = true; | ||
menuPopupEl.setAttribute("tabindex", "-1"); | ||
if (!useAriaExpanded) | ||
@@ -432,2 +452,20 @@ return; | ||
}; | ||
const runRemoveScrollbar = (open) => { | ||
if (!removeScrollbar) | ||
return; | ||
if (typeof removeScrollbar === "function") { | ||
removeScrollbar(open, dismissStack); | ||
return; | ||
} | ||
if (dismissStack.length > 1) | ||
return; | ||
if (open) { | ||
const el = document.scrollingElement; | ||
el.style.overflow = "hidden"; | ||
} | ||
else { | ||
const el = document.scrollingElement; | ||
el.style.overflow = ""; | ||
} | ||
}; | ||
const removeMenuPopupEl = () => { | ||
@@ -455,3 +493,3 @@ if (!menuPopupEl) | ||
menuBtnId = menuBtnEl.id; | ||
expandToggle(props.toggle()); | ||
runAriaExpanded(props.open()); | ||
if (!menuBtnId) { | ||
@@ -462,23 +500,5 @@ menuBtnId = id || uniqueId; | ||
}); | ||
const runRemoveScrollbar = (toggle) => { | ||
if (!removeScrollbar) | ||
createEffect(on(() => !!props.open(), (open, prevOpen) => { | ||
if (open === prevOpen) | ||
return; | ||
if (typeof removeScrollbar === "function") { | ||
removeScrollbar(toggle, dismissStack); | ||
return; | ||
} | ||
if (dismissStack.length > 1) | ||
return; | ||
if (toggle) { | ||
const el = document.scrollingElement; | ||
el.style.overflow = "hidden"; | ||
} | ||
else { | ||
const el = document.scrollingElement; | ||
el.style.overflow = ""; | ||
} | ||
}; | ||
createEffect(on(() => !!props.toggle(), (toggle, prevToggle) => { | ||
if (toggle === prevToggle) | ||
return; | ||
if (closeWhenScrolling) { | ||
@@ -490,4 +510,7 @@ window.addEventListener("scroll", onScrollClose, { | ||
} | ||
expandToggle(toggle); | ||
if (toggle) { | ||
if (closeWhenWindowBlurs) { | ||
window.addEventListener("blur", onWindowBlur, { once: true }); | ||
} | ||
runAriaExpanded(open); | ||
if (open) { | ||
addCloseButtons(); | ||
@@ -503,4 +526,4 @@ addMenuPopupEl(); | ||
uniqueId, | ||
toggle: props.toggle, | ||
setToggle: props.setToggle, | ||
open: props.open, | ||
setOpen: props.setOpen, | ||
containerEl, | ||
@@ -518,3 +541,3 @@ menuBtnEl, | ||
}); | ||
runRemoveScrollbar(toggle); | ||
runRemoveScrollbar(open); | ||
if (isOverlayClipped) { | ||
@@ -534,3 +557,3 @@ mountOverlayClipped(); | ||
} | ||
runRemoveScrollbar(toggle); | ||
runRemoveScrollbar(open); | ||
if (dismissStack.length < 1) { | ||
@@ -546,3 +569,3 @@ addedKeydownListener = false; | ||
() => props.overlay.clipped.redrawClippedPath(), (result) => { | ||
if (result === null || !props.toggle()) | ||
if (result === null || !props.open()) | ||
return; | ||
@@ -565,9 +588,9 @@ console.log("run redraw"); | ||
}); | ||
return (<Show when={props.toggle()}> | ||
<div id={id} class={props.class} data-solid-dismiss-dropdown-container={id} classList={props.classList || {}} onFocusIn={onFocusInContainer} onFocusOut={onFocusOutContainer} tabindex="-1" style={overlay === "block" ? `z-index: ${1000 + dismissStack.length}` : ""} ref={refCb}> | ||
return (<Show when={props.open()}> | ||
<div id={id} class={props.class} data-solid-dismiss-dropdown-container={id} classList={props.classList || {}} onFocusIn={onFocusInContainer} onFocusOut={onFocusOutContainer} style={overlay === "backdrop" ? `z-index: ${1000 + dismissStack.length}` : ""} ref={refContainerCb}> | ||
{isOverlayClipped && (<Portal> | ||
<div style={`position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: ${999 + dismissStack.length};`} solid-dismiss-overlay-clipped={id} solid-dismiss-overlay-clipped-level={dismissStack.length} onClick={onClickOverlay} ref={overlayEl}></div> | ||
</Portal>)} | ||
{overlay === "block" && (<Portal> | ||
<div role="presentation" solid-dismiss-overlay={id} solid-dismiss-overlay-level={dismissStack.length} onClick={onClickOverlay} style={`position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: ${999 + dismissStack.length};`} ref={overlayEl}></div> | ||
{overlay === "backdrop" && (<Portal> | ||
<div role="presentation" solid-dismiss-overlay-backdrop={id} solid-dismiss-overlay-backdrop-level={dismissStack.length} onClick={onClickOverlay} style={`position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: ${999 + dismissStack.length};`} ref={overlayEl}></div> | ||
</Portal>)} | ||
@@ -574,0 +597,0 @@ {hasFocusSentinels && (<div tabindex="0" onFocus={(e) => { |
@@ -5,4 +5,4 @@ import { Accessor } from "solid-js"; | ||
uniqueId: string; | ||
setToggle: (v: boolean) => void; | ||
toggle: Accessor<boolean>; | ||
setOpen: (v: boolean) => void; | ||
open: Accessor<boolean>; | ||
menuBtnEl: HTMLElement; | ||
@@ -12,3 +12,3 @@ menuPopupEl: HTMLElement; | ||
overlayEl?: HTMLDivElement; | ||
overlay: "block" | "clipped" | false; | ||
overlay: "backdrop" | "clipped" | false; | ||
isOverlayClipped: boolean; | ||
@@ -15,0 +15,0 @@ detectIfMenuButtonObscured: boolean; |
@@ -23,4 +23,4 @@ import "./browserInfo"; | ||
/** | ||
* Default: root component element queries the second child element | ||
* css selector, queried from document, to get menuPopup 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 | ||
*/ | ||
@@ -31,3 +31,2 @@ menuPopup?: string | JSX.Element | (() => JSX.Element); | ||
* | ||
* `string` is css selector | ||
* css selector, queried from container element, to get close button element(s). Or pass JSX element(s) | ||
@@ -56,3 +55,3 @@ */ | ||
*/ | ||
focusOnActive?: "menuPopup" | string | JSX.Element | (() => JSX.Element); | ||
focusElWhenOpened?: "menuPopup" | string | JSX.Element | (() => JSX.Element); | ||
/** | ||
@@ -70,3 +69,3 @@ * Default: When Tabbing forwards, focuses on focusable 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, if overlay present, then menuButton will be focused instead. | ||
*/ | ||
focusOnLeave?: "menuButton" | string | JSX.Element | { | ||
focusElWhenClosed?: "menuButton" | string | JSX.Element | { | ||
/** | ||
@@ -117,4 +116,16 @@ * focus on element when exiting menuPopup via tabbing backwards ie "Shift + Tab". | ||
/** | ||
* Default: `true` | ||
* | ||
* If `false`, menuPopup won't close when overlay backdrop is clicked, should use `overlay` prop to work. When backdrop clicked, menuPopup will recieve focus. | ||
*/ | ||
closeWhenClickedOutside?: boolean; | ||
/** | ||
* Default: `false` | ||
* | ||
* Closes when viewport window "blurs". This would happen when interacting outside of the page such as Devtools, changing browser tabs, or switch different applications. | ||
*/ | ||
closeWhenWindowBlurs?: boolean; | ||
/** | ||
* Default: `false` | ||
* | ||
* If `true`, sets "overflow: hidden" declaration to Document.scrollingElement. | ||
@@ -124,7 +135,7 @@ * | ||
*/ | ||
removeScrollbar?: boolean | ((toggle: boolean, dismissStack: TDismissStack[]) => void); | ||
removeScrollbar?: boolean | ((open: boolean, dismissStack: TDismissStack[]) => void); | ||
/** | ||
* Default `"none"` | ||
* Default `false` | ||
* | ||
* When prop is `"block"`, adds root level div that acts as overlay. This removes interaction of the page elements that's underneath the overlay element. Make sure that menuPopup lives in the root level, one way is to nest this Component inside Solid's {@link https://www.solidjs.com/docs/latest/api#%3Cportal%3E Portal}. | ||
* When prop is `"backdrop"`, adds root level div that acts as a layer. This removes interaction of the page elements that's underneath the overlay element. Make sure that menuPopup lives in the root level, one way is to nest this Component inside Solid's {@link https://www.solidjs.com/docs/latest/api#%3Cportal%3E Portal}. | ||
* | ||
@@ -135,3 +146,3 @@ * When prop is `"clipped"`, it's similar to `"block"` where it places an element at root document, but creates a "mask"* that clips around the menuButton and menuPopup. This allows menuPopup to live anywhere in the document, rather than forced to mounting it at top level of root document in order to be interacted with. | ||
*/ | ||
overlay?: "block" | "clipped" | { | ||
overlay?: "backdrop" | "clipped" | { | ||
clipped: { | ||
@@ -172,6 +183,6 @@ menuButton?: () => JSX.Element; | ||
useAriaExpanded?: boolean; | ||
toggle: Accessor<boolean>; | ||
setToggle: (v: boolean) => void; | ||
open: Accessor<boolean>; | ||
setOpen: (v: boolean) => void; | ||
setFocus?: (v: boolean) => void; | ||
}>; | ||
export default Dismiss; |
{ | ||
"name": "solid-dismiss", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"homepage": "https://aquaductape.github.io/solid-dismiss/", | ||
@@ -18,2 +18,3 @@ "description": "Handles \"click outside\" behavior for popup menu. Closing is triggered by click/focus outside of popup element or pressing \"Escape\" key.", | ||
"click outside", | ||
"click elsewhere", | ||
"solid" | ||
@@ -37,3 +38,5 @@ ], | ||
"prepublishOnly": "npm run build", | ||
"release": "release-it" | ||
"release": "release-it", | ||
"start": "cd demo && pnpm start", | ||
"demo-deploy": "cd demo && ./deploy.sh" | ||
}, | ||
@@ -40,0 +43,0 @@ "repository": { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
183479
2484