Skip to content
This repository was archived by the owner on Sep 20, 2024. It is now read-only.

Commit 662da3a

Browse files
committed
fix(modal): fallback tp return focus on disable
1 parent 72cfb30 commit 662da3a

File tree

4 files changed

+58
-91
lines changed

4 files changed

+58
-91
lines changed

packages/c-modal/examples/base-c-modal.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
h="full"
77
w="100%"
88
>
9-
<c-button @click="isOpen = true">Checkout</c-button>
9+
<c-button color-scheme="blue" @click="isOpen = true">Open modal</c-button>
10+
<c-button ml="3">Other button</c-button>
1011
<!-- eslint-disable-next-line -->
1112
<c-modal v-model:is-open="isOpen">
1213
<c-modal-overlay />

packages/c-modal/src/use-modal.ts

Lines changed: 43 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import {
22
computed,
33
getCurrentInstance,
4-
nextTick,
54
onBeforeMount,
65
onBeforeUnmount,
7-
onMounted,
86
ref,
97
Ref,
10-
SetupContext,
118
ToRefs,
129
toRefs,
1310
VNodeProps,
@@ -16,7 +13,7 @@ import {
1613
} from 'vue'
1714
import { useIds } from '@chakra-ui/vue-composables'
1815
import { FocusLockProps, useFocusLock } from '@chakra-ui/c-focus-lock'
19-
import { MaybeElementRef, useRef } from '@chakra-ui/vue-utils'
16+
import { MaybeElementRef, useRef, getSelector } from '@chakra-ui/vue-utils'
2017
import { hideOthers, Undo } from 'aria-hidden'
2118
import { FocusTarget } from 'focus-trap'
2219
import { focus, FocusableElement } from '@chakra-ui/utils'
@@ -128,7 +125,7 @@ export function useModal(options: UseModalOptions) {
128125
*/
129126
const shouldHide = computed(() => isOpen.value && useInert?.value)
130127
useAriaHidden(dialogRefEl, shouldHide)
131-
const { lastFocused, lastFocusedSelector } = useReturnFocus(isOpen)
128+
const { lastFocusedSelector } = useReturnFocusSelector(isOpen)
132129

133130
const hasHeader = ref(false)
134131
const hasBody = ref(false)
@@ -142,23 +139,42 @@ export function useModal(options: UseModalOptions) {
142139
delayInitialFocus: true,
143140
initialFocus: initialFocusRef?.value as FocusTarget,
144141
onDeactivate() {
145-
console.log('lastFocused', lastFocused.value)
142+
/**
143+
* There appears to be a bug in which
144+
* the DOM refreshes and elements are modified
145+
* in a way that displaces the DOM.
146+
*
147+
* At the time of writing this composable, I am
148+
* unable to ascertain where it came from. However,
149+
* this acts a failsafe to allow the `useFocusLock()`
150+
* hook to always track the last focused element
151+
* before it was activated.
152+
*/
146153
setTimeout(() => {
147-
console.log('Getting last focused', lastFocusedSelector.value)
148154
const lastfocusedNode = document.querySelector(
149155
lastFocusedSelector.value as string
150156
)
151157

152-
focus(lastfocusedNode as HTMLElement)
158+
if (finalFocusElement.value) {
159+
focus(finalFocusElement.value)
160+
} else {
161+
focus(lastfocusedNode as HTMLElement)
162+
}
153163
}, 100)
154-
// if (finalFocusElement.value) {
155-
// focus(finalFocusElement.value)
156-
// }
157164
},
158165
immediate: true,
159166
})
167+
160168
const { scrollLockRef } = useBodyScrollLock(isOpen)
161169

170+
/**
171+
* This watcher is being used to track
172+
* the element refs for the dialog container
173+
* element.
174+
*
175+
* When the ref is bound, we activate
176+
* the focus lock and body scroll lock refs.
177+
*/
162178
watch(dialogRefEl, (newVal) => {
163179
if (newVal) {
164180
lock(newVal)
@@ -264,31 +280,31 @@ export function useAriaHidden(
264280
) {
265281
let undo: Undo | null = null
266282

267-
watchEffect(
268-
(onInvalidate) => {
269-
if (!node.value) return
283+
watchEffect((onInvalidate) => {
284+
if (!node.value) return
270285

271-
if (shouldHide.value && node.value) {
272-
undo = hideOthers(node.value)
273-
}
286+
console.log({
287+
hasNode: !!node.value,
288+
shouldHide: shouldHide.value,
289+
})
274290

275-
onInvalidate(() => {
276-
undo?.()
277-
})
278-
},
279-
{
280-
flush: 'post',
291+
if (shouldHide.value && node.value) {
292+
undo = hideOthers(node.value)
281293
}
282-
)
294+
295+
onInvalidate(() => {
296+
undo?.()
297+
})
298+
})
283299
}
284300

285-
/** Tracks last opened element before Modal is opened */
286-
export function useReturnFocus(isOpen: Ref<boolean>) {
301+
/** Tracks last focused element selector before Modal/dialog is opened */
302+
export function useReturnFocusSelector(shouldTrack: Ref<boolean>) {
287303
const lastFocused = ref<EventTarget | null>(null)
288304
const lastFocusedSelector = ref<string | undefined>()
289305

290306
const trackFocus = (event: Event) => {
291-
if (!isOpen.value) {
307+
if (!shouldTrack.value) {
292308
lastFocusedSelector.value = getSelector(event.target as HTMLElement)
293309
}
294310
}
@@ -308,55 +324,3 @@ export function useReturnFocus(isOpen: Ref<boolean>) {
308324
lastFocusedSelector,
309325
}
310326
}
311-
312-
function getSelector(node: HTMLElement) {
313-
var id = node.getAttribute('id')
314-
315-
if (id) {
316-
return '#' + id
317-
}
318-
319-
var path = ''
320-
321-
while (node) {
322-
var name = node.localName
323-
var parent = node.parentNode
324-
325-
if (!parent) {
326-
path = name + ' > ' + path
327-
continue
328-
}
329-
330-
if (node.getAttribute('id')) {
331-
path = '#' + node.getAttribute('id') + ' > ' + path
332-
break
333-
}
334-
335-
var sameTagSiblings = []
336-
var children = parent.childNodes
337-
children = Array.prototype.slice.call(children)
338-
339-
children.forEach(function (child) {
340-
if (child.localName == name) {
341-
sameTagSiblings.push(child)
342-
}
343-
})
344-
345-
// if there are more than one children of that type use nth-of-type
346-
347-
if (sameTagSiblings.length > 1) {
348-
var index = sameTagSiblings.indexOf(node)
349-
name += ':nth-of-type(' + (index + 1) + ')'
350-
}
351-
352-
if (path) {
353-
path = name + ' > ' + path
354-
} else {
355-
path = name
356-
}
357-
358-
node = parent
359-
}
360-
361-
return path
362-
}

packages/utils/src/dom-query.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ interface IChildNode extends ChildNode {
1212
*
1313
* This was breaking the behaviour of the `useFocusLock`
1414
* hook.
15+
*
16+
* Adopted from stack overflow:
17+
* https://stackoverflow.com/questions/22515835/javascript-find-selector-of-an-element
1518
*/
1619
export function getSelector(node: HTMLElement) {
1720
const id = node.getAttribute('id')
1821

19-
if (id) return `#${id}`
22+
if (id) return '#' + id
2023

2124
let path = ''
2225

@@ -25,30 +28,28 @@ export function getSelector(node: HTMLElement) {
2528
const parent = node.parentNode
2629

2730
if (!parent) {
28-
path = `${name} > ${path}`
31+
path = name + ' > ' + path
2932
continue
3033
}
3134

32-
const idAttr = node.getAttribute('id')
33-
if (idAttr) {
35+
if (node.getAttribute('id')) {
3436
path = '#' + node.getAttribute('id') + ' > ' + path
35-
path = `#${idAttr} > ${path}`
3637
break
3738
}
3839

3940
const sameTagSiblings: any = []
40-
let children: NodeListOf<IChildNode> = parent.childNodes
41-
// @ts-ignore
42-
children = Array.prototype.slice.call(children)
41+
let children = parent.childNodes
42+
children = Array.prototype.slice.call(children) as any
4343

4444
children.forEach((child) => {
45-
if (child.localName === name) {
45+
// @ts-ignore
46+
if (child.localName == name) {
4647
sameTagSiblings.push(child)
4748
}
4849
})
4950

50-
// if there are more than one children
51-
// of that type use nth-of-type
51+
// if there are more than one
52+
// children of that type use nth-of-type
5253
if (sameTagSiblings.length > 1) {
5354
const index = sameTagSiblings.indexOf(node)
5455
name += ':nth-of-type(' + (index + 1) + ')'

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './vue-utils'
22
export * from './layout'
33
export * from './dom'
4+
export * from './dom-query'
45
export * from './types'
56
export * from './props'

0 commit comments

Comments
 (0)