solid-dismiss
Advanced tools
Comparing version 1.3.0 to 1.3.1
@@ -89,3 +89,2 @@ import { isServer, insert, template, delegateEvents, addEventListener, effect, setAttribute, classList, createComponent, mergeProps } from 'solid-js/web'; | ||
let willWrap = false; | ||
let originalFrom = null; // TODO: does this work with web components, due to shadow root | ||
@@ -129,59 +128,2 @@ const getNextTabbableElement = ({ | ||
const checkChildren = (children, parent, reverse, contentWindow) => { | ||
const length = children.length; | ||
if (length && isHidden(parent)) return null; | ||
if (reverse) { | ||
for (let i = length - 1; i > -1; i--) { | ||
const child = children[i]; | ||
if (ignoreElement.some(el => el.contains(child))) continue; | ||
if (originalFrom === child) continue; | ||
if (!checkHiddenAncestors(child, parent, contentWindow)) { | ||
if (isIframe(child)) { | ||
const iframeChild = queryIframe(child, reverse); | ||
if (iframeChild) return iframeChild; | ||
} | ||
return child; | ||
} | ||
} | ||
return null; | ||
} | ||
for (let i = 0; i < length; i++) { | ||
const child = children[i]; | ||
if (ignoreElement.some(el => el.contains(child))) continue; | ||
if (originalFrom === child) continue; | ||
if (!checkHiddenAncestors(child, parent, contentWindow)) { | ||
if (isIframe(child)) { | ||
const iframeChild = queryIframe(child); | ||
if (iframeChild) return iframeChild; | ||
} | ||
return child; | ||
} | ||
} | ||
return null; | ||
}; | ||
const queryIframe = (el, inverseQuery) => { | ||
if (!el) return null; | ||
if (!isIframe(el)) return el; | ||
const iframeWindow = getIframeWindow(el); // here iframe will get focused whether it has tab index or not, so checking tabindex conditional a couple lines down is redundant | ||
if (!iframeWindow) return el; | ||
const iframeDocument = iframeWindow.document; // conditional used to be here | ||
// if (!iframeWindow) return el as HTMLElement; | ||
const tabindex = el.getAttribute("tabindex"); | ||
if (tabindex) return el; | ||
const els = iframeDocument.querySelectorAll(tabbableSelectors); | ||
const result = checkChildren(els, iframeDocument.documentElement, inverseQuery, iframeWindow); | ||
return queryIframe(result); | ||
}; | ||
const traverseNextSiblingsThenUp = (parent, visitedElement) => { | ||
@@ -202,13 +144,4 @@ let hasPassedVisitedElement = false; | ||
if (ignoreElement.some(el => el === child)) continue; | ||
const el = queryTabbableElement(child, tabbableSelectors, direction); | ||
if (child.matches(tabbableSelectors)) { | ||
if (isHidden(child)) continue; | ||
const el = queryIframe(child); | ||
if (el) return el; | ||
return child; | ||
} | ||
const els = child.querySelectorAll(tabbableSelectors); | ||
const el = checkChildren(els, child); | ||
if (el) { | ||
@@ -236,12 +169,3 @@ return el; | ||
if (ignoreElement.some(el => el === child)) continue; | ||
if (child.matches(tabbableSelectors)) { | ||
if (isHidden(child)) continue; | ||
const el = queryIframe(child); | ||
if (el) return el; | ||
return child; | ||
} | ||
const els = child.querySelectorAll(tabbableSelectors); | ||
const el = checkChildren(els, child, true); | ||
const el = queryTabbableElement(child, tabbableSelectors, direction); | ||
if (el) return el; | ||
@@ -285,5 +209,3 @@ continue; | ||
if (!result && wrap && stopAtRootElement) { | ||
// direction = direction === "forwards" ? "backwards" : "forwards"; | ||
willWrap = true; | ||
originalFrom = from; | ||
result = getNextTabbableElement({ | ||
@@ -300,3 +222,2 @@ from: stopAtRootElement, | ||
willWrap = false; | ||
originalFrom = null; | ||
return result; | ||
@@ -327,7 +248,7 @@ }; | ||
const isHidden = (el, contentWindow = window) => { | ||
const isHidden = (el, windowContext = window) => { | ||
const checkByStyle = style => style.display === "none" || style.visibility === "hidden"; | ||
if (el.style && checkByStyle(el.style) || el.hidden) return true; | ||
const style = contentWindow.getComputedStyle(el); | ||
const style = windowContext.getComputedStyle(el); | ||
if (!style || checkByStyle(style)) return true; | ||
@@ -337,24 +258,101 @@ return false; | ||
const checkHiddenAncestors = (target, parent, contentWindow) => { | ||
const ancestors = []; | ||
let node = target; | ||
if (isHidden(node)) return true; | ||
const queryTabbableElement = (el, selectors = _tabbableSelectors, iterationDirection = "forwards", windowContext = window, init = true) => { | ||
const queryChild = el => { | ||
if (!el.matches(selectors)) return { | ||
el, | ||
matched: false | ||
}; | ||
const tabindex = el.getAttribute("tabindex"); | ||
while (true) { | ||
node = node.parentElement; | ||
if (isIframe(el) && (!tabindex || tabindex === "-1")) { | ||
const iframeWindow = getIframeWindow(el); | ||
if (!node || node === parent) { | ||
break; | ||
if (!iframeWindow) { | ||
return { | ||
el, | ||
matched: true | ||
}; | ||
} | ||
el = iframeWindow.document.documentElement; | ||
windowContext = iframeWindow; | ||
return { | ||
el, | ||
matched: false, | ||
windowContext: iframeWindow | ||
}; | ||
} | ||
ancestors.push(node); | ||
return { | ||
el, | ||
matched: true | ||
}; | ||
}; | ||
if (init) { | ||
if (isHidden(el, windowContext)) return null; | ||
const { | ||
el: elResult, | ||
matched, | ||
windowContext: windowContextResult | ||
} = queryChild(el); | ||
el = elResult; | ||
if (matched) return el; | ||
windowContext = windowContextResult || windowContext; | ||
return queryTabbableElement(el, selectors, iterationDirection, windowContext, false); | ||
} | ||
for (const node of ancestors) { | ||
if (isHidden(node, contentWindow)) { | ||
return true; | ||
const shadowRoot = el.shadowRoot; | ||
if (shadowRoot) el = shadowRoot; | ||
const children = el.children; | ||
const childrenLength = children.length; | ||
const iterateChild = el => { | ||
if (isHidden(el, windowContext)) return { | ||
continue: true | ||
}; | ||
const { | ||
el: elResult, | ||
matched, | ||
windowContext: windowContextResult | ||
} = queryChild(el); | ||
el = elResult; | ||
windowContext = windowContextResult || windowContext; | ||
if (matched) { | ||
return { | ||
returnVal: el | ||
}; | ||
} | ||
const foundChild = queryTabbableElement(el, selectors, iterationDirection, windowContext, false); | ||
if (foundChild) return { | ||
returnVal: foundChild | ||
}; | ||
return null; | ||
}; | ||
if (iterationDirection === "forwards") { | ||
for (let i = 0; i < childrenLength; i++) { | ||
let child = children[i]; | ||
const result = iterateChild(child); | ||
if (result) { | ||
if (result.continue) continue; | ||
if (result.returnVal) return result.returnVal; | ||
} | ||
} | ||
} else { | ||
for (let i = childrenLength - 1; i >= 0; i--) { | ||
let child = children[i]; | ||
const result = iterateChild(child); | ||
if (result) { | ||
if (result.continue) continue; | ||
if (result.returnVal) return result.returnVal; | ||
} | ||
} | ||
} | ||
return false; | ||
return null; | ||
}; | ||
@@ -698,2 +696,3 @@ | ||
} else { | ||
// TODO: why is this needed when next conditional queries element and focuses it | ||
containerEl.focus(); | ||
@@ -700,0 +699,0 @@ } |
@@ -177,2 +177,3 @@ import { dismissStack } from "../global/dismissStack"; | ||
else { | ||
// TODO: why is this needed when next conditional queries element and focuses it | ||
containerEl.focus(); | ||
@@ -179,0 +180,0 @@ } |
@@ -41,60 +41,2 @@ const _tabbableSelectors = [ | ||
return null; | ||
const checkChildren = (children, parent, reverse, contentWindow) => { | ||
const length = children.length; | ||
if (length && isHidden(parent)) | ||
return null; | ||
if (reverse) { | ||
for (let i = length - 1; i > -1; i--) { | ||
const child = children[i]; | ||
if (ignoreElement.some((el) => el.contains(child))) | ||
continue; | ||
if (originalFrom === child) | ||
continue; | ||
if (!checkHiddenAncestors(child, parent, contentWindow)) { | ||
if (isIframe(child)) { | ||
const iframeChild = queryIframe(child, reverse); | ||
if (iframeChild) | ||
return iframeChild; | ||
} | ||
return child; | ||
} | ||
} | ||
return null; | ||
} | ||
for (let i = 0; i < length; i++) { | ||
const child = children[i]; | ||
if (ignoreElement.some((el) => el.contains(child))) | ||
continue; | ||
if (originalFrom === child) | ||
continue; | ||
if (!checkHiddenAncestors(child, parent, contentWindow)) { | ||
if (isIframe(child)) { | ||
const iframeChild = queryIframe(child); | ||
if (iframeChild) | ||
return iframeChild; | ||
} | ||
return child; | ||
} | ||
} | ||
return null; | ||
}; | ||
const queryIframe = (el, inverseQuery) => { | ||
if (!el) | ||
return null; | ||
if (!isIframe(el)) | ||
return el; | ||
const iframeWindow = getIframeWindow(el); | ||
// here iframe will get focused whether it has tab index or not, so checking tabindex conditional a couple lines down is redundant | ||
if (!iframeWindow) | ||
return el; | ||
const iframeDocument = iframeWindow.document; | ||
// conditional used to be here | ||
// if (!iframeWindow) return el as HTMLElement; | ||
const tabindex = el.getAttribute("tabindex"); | ||
if (tabindex) | ||
return el; | ||
const els = iframeDocument.querySelectorAll(tabbableSelectors); | ||
const result = checkChildren(els, iframeDocument.documentElement, inverseQuery, iframeWindow); | ||
return queryIframe(result); | ||
}; | ||
const traverseNextSiblingsThenUp = (parent, visitedElement) => { | ||
@@ -113,12 +55,3 @@ let hasPassedVisitedElement = false; | ||
continue; | ||
if (child.matches(tabbableSelectors)) { | ||
if (isHidden(child)) | ||
continue; | ||
const el = queryIframe(child); | ||
if (el) | ||
return el; | ||
return child; | ||
} | ||
const els = child.querySelectorAll(tabbableSelectors); | ||
const el = checkChildren(els, child); | ||
const el = queryTabbableElement(child, tabbableSelectors, direction); | ||
if (el) { | ||
@@ -144,12 +77,3 @@ return el; | ||
continue; | ||
if (child.matches(tabbableSelectors)) { | ||
if (isHidden(child)) | ||
continue; | ||
const el = queryIframe(child); | ||
if (el) | ||
return el; | ||
return child; | ||
} | ||
const els = child.querySelectorAll(tabbableSelectors); | ||
const el = checkChildren(els, child, true); | ||
const el = queryTabbableElement(child, tabbableSelectors, direction); | ||
if (el) | ||
@@ -185,3 +109,2 @@ return el; | ||
if (!result && wrap && stopAtRootElement) { | ||
// direction = direction === "forwards" ? "backwards" : "forwards"; | ||
willWrap = true; | ||
@@ -225,7 +148,7 @@ originalFrom = from; | ||
}; | ||
const isHidden = (el, contentWindow = window) => { | ||
const isHidden = (el, windowContext = window) => { | ||
const checkByStyle = (style) => style.display === "none" || style.visibility === "hidden"; | ||
if ((el.style && checkByStyle(el.style)) || el.hidden) | ||
return true; | ||
const style = contentWindow.getComputedStyle(el); | ||
const style = windowContext.getComputedStyle(el); | ||
if (!style || checkByStyle(style)) | ||
@@ -235,16 +158,84 @@ return true; | ||
}; | ||
const checkHiddenAncestors = (target, parent, contentWindow) => { | ||
const ancestors = []; | ||
let node = target; | ||
if (isHidden(node)) | ||
return true; | ||
while (true) { | ||
node = node.parentElement; | ||
if (!node || node === parent) { | ||
break; | ||
export const queryTabbableElement = (el, selectors = _tabbableSelectors, iterationDirection = "forwards", windowContext = window, init = true) => { | ||
const queryChild = (el) => { | ||
if (!el.matches(selectors)) | ||
return { | ||
el, | ||
matched: false, | ||
}; | ||
const tabindex = el.getAttribute("tabindex"); | ||
if (isIframe(el) && (!tabindex || tabindex === "-1")) { | ||
const iframeWindow = getIframeWindow(el); | ||
if (!iframeWindow) { | ||
return { el, matched: true }; | ||
} | ||
el = iframeWindow.document.documentElement; | ||
windowContext = iframeWindow; | ||
return { el, matched: false, windowContext: iframeWindow }; | ||
} | ||
ancestors.push(node); | ||
return { | ||
el, | ||
matched: true, | ||
}; | ||
}; | ||
if (init) { | ||
if (isHidden(el, windowContext)) | ||
return null; | ||
const { el: elResult, matched, windowContext: windowContextResult, } = queryChild(el); | ||
el = elResult; | ||
if (matched) | ||
return el; | ||
windowContext = windowContextResult || windowContext; | ||
return queryTabbableElement(el, selectors, iterationDirection, windowContext, false); | ||
} | ||
for (const node of ancestors) { | ||
if (isHidden(node, contentWindow)) { | ||
const shadowRoot = el.shadowRoot; | ||
if (shadowRoot) | ||
el = shadowRoot; | ||
const children = el.children; | ||
const childrenLength = children.length; | ||
const iterateChild = (el) => { | ||
if (isHidden(el, windowContext)) | ||
return { | ||
continue: true, | ||
}; | ||
const { el: elResult, matched, windowContext: windowContextResult, } = queryChild(el); | ||
el = elResult; | ||
windowContext = windowContextResult || windowContext; | ||
if (matched) { | ||
return { returnVal: el }; | ||
} | ||
const foundChild = queryTabbableElement(el, selectors, iterationDirection, windowContext, false); | ||
if (foundChild) | ||
return { returnVal: foundChild }; | ||
return null; | ||
}; | ||
if (iterationDirection === "forwards") { | ||
for (let i = 0; i < childrenLength; i++) { | ||
let child = children[i]; | ||
const result = iterateChild(child); | ||
if (result) { | ||
if (result.continue) | ||
continue; | ||
if (result.returnVal) | ||
return result.returnVal; | ||
} | ||
} | ||
} | ||
else { | ||
for (let i = childrenLength - 1; i >= 0; i--) { | ||
let child = children[i]; | ||
const result = iterateChild(child); | ||
if (result) { | ||
if (result.continue) | ||
continue; | ||
if (result.returnVal) | ||
return result.returnVal; | ||
} | ||
} | ||
} | ||
return null; | ||
}; | ||
const hasShadowParent = (element) => { | ||
while (element.parentNode && (element = element.parentNode)) { | ||
if (element instanceof ShadowRoot) { | ||
return true; | ||
@@ -251,0 +242,0 @@ } |
@@ -33,2 +33,3 @@ declare type GetNextTabbableElement = { | ||
export declare const getNextTabbableElement: ({ from: _from, stopAtRootElement: stopAtRootElement, ignoreElement, allowSelectors, direction, wrap, }: GetNextTabbableElement) => HTMLElement; | ||
export declare const queryTabbableElement: (el: Element, selectors?: string, iterationDirection?: "forwards" | "backwards", windowContext?: Window, init?: boolean) => HTMLElement | null; | ||
export {}; |
{ | ||
"name": "solid-dismiss", | ||
"version": "1.3.0", | ||
"version": "1.3.1", | ||
"homepage": "https://aquaductape.github.io/solid-dismiss/", | ||
@@ -5,0 +5,0 @@ "description": "Handles \"click outside\" behavior for popup menu. Closing is triggered by click/focus outside of popup element or pressing \"Escape\" key.", |
@@ -54,2 +54,4 @@ <p> | ||
**Note:** on solid-start version `0.1.1` and above, no need update vite config with ssr.noExternal, it happens automatically. | ||
On SSR frameworks such as [Astro](https://docs.astro.build/en/guides/integrations-guide/solid-js/) or [solid-start](https://github.com/solidjs/solid-start), you need to include `["solid-dismiss"]` value to the `noExternal` property in the vite config file. | ||
@@ -56,0 +58,0 @@ |
Sorry, the diff of this file is not supported yet
388
329700