|
@@ -1,28 +1,41 @@
|
|
import { createFocusTrap } from 'focus-trap';
|
|
import { createFocusTrap } from 'focus-trap';
|
|
|
|
|
|
export default function (Alpine) {
|
|
export default function (Alpine) {
|
|
- Alpine.directive('trap', (el, { expression }, { effect, evaluateLater }) => {
|
|
|
|
|
|
+ Alpine.directive('trap', (el, { expression, modifiers }, { effect, evaluateLater }) => {
|
|
let evaluator = evaluateLater(expression)
|
|
let evaluator = evaluateLater(expression)
|
|
|
|
|
|
let oldValue = false
|
|
let oldValue = false
|
|
|
|
|
|
let trap = createFocusTrap(el, {
|
|
let trap = createFocusTrap(el, {
|
|
escapeDeactivates: false,
|
|
escapeDeactivates: false,
|
|
- allowOutsideClick: true
|
|
|
|
|
|
+ allowOutsideClick: true,
|
|
|
|
+ fallbackFocus: () => el,
|
|
})
|
|
})
|
|
|
|
|
|
|
|
+ let undoInert = () => {}
|
|
|
|
+ let undoDisableScrolling = () => {}
|
|
|
|
+
|
|
effect(() => evaluator(value => {
|
|
effect(() => evaluator(value => {
|
|
if (oldValue === value) return
|
|
if (oldValue === value) return
|
|
|
|
|
|
// Start trapping.
|
|
// Start trapping.
|
|
if (value && ! oldValue) {
|
|
if (value && ! oldValue) {
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
|
+ if (modifiers.includes('inert')) undoInert = setInert(el)
|
|
|
|
+ if (modifiers.includes('noscroll')) undoDisableScrolling = disableScrolling()
|
|
|
|
+
|
|
trap.activate()
|
|
trap.activate()
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
// Stop trapping.
|
|
// Stop trapping.
|
|
if (! value && oldValue) {
|
|
if (! value && oldValue) {
|
|
|
|
+ undoInert()
|
|
|
|
+ undoInert = () => {}
|
|
|
|
+
|
|
|
|
+ undoDisableScrolling()
|
|
|
|
+ undoDisableScrolling = () => {}
|
|
|
|
+
|
|
trap.deactivate()
|
|
trap.deactivate()
|
|
}
|
|
}
|
|
|
|
|
|
@@ -30,3 +43,44 @@ export default function (Alpine) {
|
|
}))
|
|
}))
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+function setInert(el) {
|
|
|
|
+ let undos = []
|
|
|
|
+
|
|
|
|
+ crawlSiblingsUp(el, (sibling) => {
|
|
|
|
+ let cache = sibling.hasAttribute('aria-hidden')
|
|
|
|
+
|
|
|
|
+ sibling.setAttribute('aria-hidden', 'true')
|
|
|
|
+
|
|
|
|
+ undos.push(() => cache || sibling.removeAttribute('aria-hidden'))
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ return () => {
|
|
|
|
+ while(undos.length) undos.pop()()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function crawlSiblingsUp(el, callback) {
|
|
|
|
+ if (el.isSameNode(document.body) || ! el.parentNode) return
|
|
|
|
+
|
|
|
|
+ Array.from(el.parentNode.children).forEach(sibling => {
|
|
|
|
+ if (! sibling.isSameNode(el)) callback(sibling)
|
|
|
|
+
|
|
|
|
+ crawlSiblingsUp(el.parentNode, callback)
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function disableScrolling() {
|
|
|
|
+ let overflow = document.documentElement.style.overflow
|
|
|
|
+ let paddingRight = document.documentElement.style.paddingRight
|
|
|
|
+
|
|
|
|
+ let scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
|
|
|
|
+
|
|
|
|
+ document.documentElement.style.overflow = 'hidden'
|
|
|
|
+ document.documentElement.style.paddingRight = `${scrollbarWidth}px`
|
|
|
|
+
|
|
|
|
+ return () => {
|
|
|
|
+ document.documentElement.style.overflow = overflow
|
|
|
|
+ document.documentElement.style.paddingRight = paddingRight
|
|
|
|
+ }
|
|
|
|
+}
|