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

Commit 4e6c12d

Browse files
committed
fix: update focus trap
1 parent e11cf8a commit 4e6c12d

File tree

12 files changed

+245
-3360
lines changed

12 files changed

+245
-3360
lines changed

packages/c-focus-lock/examples/with-focus-lock-component.vue

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@
1111
rounded="lg"
1212
border-color="gray.400"
1313
d="inline-block"
14-
@activate="handleActivate"
15-
@deactivate="handleDeactivate"
16-
:allow-outside-click="false"
17-
:initial-focus-ref="() => initialFocusRef"
18-
#default="{ hasFocus, deactivate }"
14+
#default="{ enabled, deactivate }"
1915
pos="relative"
2016
v-if="isActive"
2117
>
@@ -26,7 +22,7 @@
2622
right="10px"
2723
@click="deactivate"
2824
></c-close-button>
29-
<chakra.pre> Focus trap enabled: {{ hasFocus }} </chakra.pre>
25+
<chakra.pre> Focus trap enabled: {{ enabled }} </chakra.pre>
3026
<chakra.p mb="2">Inside focus trap</chakra.p>
3127
<c-button color-scheme="teal"> Login </c-button>
3228
<c-button :ref="initialFocus" color-scheme="yellow" mx="2"
@@ -35,28 +31,28 @@
3531
<c-button left-icon="user" color-scheme="red"
3632
>Delete account</c-button
3733
>
38-
<chakra.pre> Focus trap enabled: {{ hasFocus }} </chakra.pre>
34+
<chakra.pre> Focus trap enabled: {{ enabled }} </chakra.pre>
3935
<chakra.p mb="2">Inside focus trap</chakra.p>
4036
<c-button color-scheme="teal"> Login </c-button>
4137
<c-button color-scheme="yellow" mx="2">Initial focus!</c-button>
4238
<c-button left-icon="user" color-scheme="red"
4339
>Delete account</c-button
4440
>
45-
<chakra.pre> Focus trap enabled: {{ hasFocus }} </chakra.pre>
41+
<chakra.pre> Focus trap enabled: {{ enabled }} </chakra.pre>
4642
<chakra.p mb="2">Inside focus trap</chakra.p>
4743
<c-button color-scheme="teal"> Login </c-button>
4844
<c-button color-scheme="yellow" mx="2">Initial focus!</c-button>
4945
<c-button left-icon="user" color-scheme="red"
5046
>Delete account</c-button
5147
>
52-
<chakra.pre> Focus trap enabled: {{ hasFocus }} </chakra.pre>
48+
<chakra.pre> Focus trap enabled: {{ enabled }} </chakra.pre>
5349
<chakra.p mb="2">Inside focus trap</chakra.p>
5450
<c-button color-scheme="teal"> Login </c-button>
5551
<c-button color-scheme="yellow" mx="2">Initial focus!</c-button>
5652
<c-button left-icon="user" color-scheme="red"
5753
>Delete account</c-button
5854
>
59-
<chakra.pre> Focus trap enabled: {{ hasFocus }} </chakra.pre>
55+
<chakra.pre> Focus trap enabled: {{ enabled }} </chakra.pre>
6056
<chakra.p mb="2">Inside focus trap</chakra.p>
6157
<c-button color-scheme="teal"> Login </c-button>
6258
<c-button color-scheme="yellow" mx="2">Initial focus!</c-button>
@@ -90,56 +86,33 @@
9086
</chakra.div>
9187
</template>
9288

93-
<script lang="ts">
94-
import { useRef } from '@chakra-ui/vue-utils'
95-
import { defineComponent, ref, watch } from 'vue'
96-
import { CFocusLock } from '../src/c-focus-lock'
89+
<script lang="ts" setup>
90+
import { chakra } from "@chakra-ui/vue-next"
91+
import { useRef } from "@chakra-ui/vue-utils"
92+
import { ref } from "vue"
93+
import { CFocusLock } from "../src/c-focus-lock"
9794
98-
if (!document.getElementById('new-target')) {
99-
const target = document.createElement('div')
100-
target.style.display = 'inline-block'
101-
target.style.position = 'absolute'
102-
target.style.top = '50px'
103-
target.style.left = '250px'
95+
if (!document.getElementById("new-target")) {
96+
const target = document.createElement("div")
97+
target.style.display = "inline-block"
98+
target.style.position = "absolute"
99+
target.style.top = "50px"
100+
target.style.left = "250px"
104101
105-
target.id = 'new-target'
102+
target.id = "new-target"
106103
document.body.appendChild(target)
107104
}
105+
const isActive = ref(false)
106+
const [finalFocus, finalFocusRef] = useRef()
107+
const [initialFocus, initialFocusRef] = useRef()
108108
109-
export default defineComponent({
110-
components: {
111-
CFocusLock,
112-
},
113-
setup() {
114-
const isActive = ref(false)
115-
const [finalFocus, finalFocusRef] = useRef()
116-
const [initialFocus, initialFocusRef] = useRef()
117-
118-
const handleActivate = () => {
119-
console.log('focuslock activated')
120-
}
121-
122-
const handleDeactivate = () => {
123-
console.log('focuslock deactivated')
124-
isActive.value = false
125-
setTimeout(() => {})
126-
}
127-
128-
watch(isActive, () => {
129-
setTimeout(() => {
130-
isActive.value = !isActive.value
131-
}, 3000)
132-
})
109+
const handleActivate = () => {
110+
console.log("focuslock activated")
111+
}
133112
134-
return {
135-
isActive,
136-
handleActivate,
137-
handleDeactivate,
138-
finalFocus,
139-
finalFocusRef,
140-
initialFocus,
141-
initialFocusRef,
142-
}
143-
},
144-
})
113+
const handleDeactivate = () => {
114+
console.log("focuslock deactivated")
115+
isActive.value = false
116+
setTimeout(() => {})
117+
}
145118
</script>

packages/c-focus-lock/examples/with-focus-lock-hook.vue

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<c-portal to="#new-target">
88
<c-motion type="fade">
99
<chakra.div
10-
:ref="lock"
10+
ref="_target"
1111
p="4"
1212
border="4px dashed"
1313
rounded="lg"
@@ -24,9 +24,7 @@
2424
></c-close-button>
2525
<chakra.p mb="2">Inside focus trap</chakra.p>
2626
<c-button color-scheme="teal"> Login </c-button>
27-
<c-button color-scheme="yellow" :ref="initialFocus" mx="2"
28-
>Initial focus!</c-button
29-
>
27+
<c-button color-scheme="yellow" mx="2">Initial focus!</c-button>
3028
<c-button left-icon="user" color-scheme="red"
3129
>Delete account</c-button
3230
>
@@ -49,15 +47,16 @@
4947
<c-button @click="activate" ml="3" color-scheme="blue">Enable</c-button>
5048
</chakra.div>
5149
<chakra.pre font-weight="bold">
52-
Focus lock enabled: {{ hasFocus }}
50+
Focus lock enabled: {{ isLocked }}
5351
</chakra.pre>
5452
</chakra.div>
5553
</template>
5654

5755
<script setup lang="ts">
5856
import { chakra } from "@chakra-ui/vue-next"
59-
import { useFocusLock } from "../src/use-focus-lock"
60-
import { ref } from "vue"
57+
import { ref, watch, watchEffect } from "vue"
58+
import { useFocusTrap } from "../src/use-focus-trap"
59+
import { unrefElement } from "@chakra-ui/vue-utils"
6160
6261
const isLocked = ref(false)
6362
@@ -72,26 +71,32 @@ if (!document.getElementById("new-target")) {
7271
document.body.appendChild(target)
7372
}
7473
75-
const {
76-
hasFocus,
77-
lock,
78-
activate: lockactivate,
79-
deactivate: lockdeactivate,
80-
initialFocus,
81-
} = useFocusLock({
82-
escapeDeactivates: false,
83-
delayInitialFocus: true,
84-
immediate: true,
85-
})
74+
const _target = ref()
75+
const containers = ref<Set<HTMLElement>>(new Set())
76+
77+
watchEffect(
78+
(onInvalidate) => {
79+
let el: HTMLElement
80+
if (_target.value) {
81+
el = unrefElement(_target)
82+
containers.value.add(el)
83+
}
84+
85+
console.log("Adding containers", containers.value)
86+
87+
onInvalidate(() => {
88+
containers.value.delete(el)
89+
})
90+
},
91+
{ flush: "post" }
92+
)
93+
useFocusTrap(containers, ref(true))
8694
8795
const activate = async () => {
8896
isLocked.value = true
89-
// setTimeout(lockactivate)
90-
setTimeout(lockactivate)
9197
}
9298
9399
const deactivate = () => {
94-
lockdeactivate()
95100
isLocked.value = false
96101
}
97102
</script>

packages/c-focus-lock/src/c-focus-lock.ts renamed to packages/c-focus-lock/src/c-focus-lock.tsx

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,18 @@
1414
* and my suspicion is that it is happening inside the library
1515
*/
1616

17-
import { defineComponent, PropType, computed, cloneVNode, VNode } from "vue"
17+
import {
18+
defineComponent,
19+
PropType,
20+
computed,
21+
cloneVNode,
22+
VNode,
23+
ref,
24+
unref,
25+
h,
26+
watchEffect,
27+
onUnmounted,
28+
} from "vue"
1829
import {
1930
focus,
2031
FocusableElement,
@@ -25,6 +36,13 @@ import {
2536
import { useFocusLock } from "./use-focus-lock"
2637
import type { UseFocusLockOptions } from "./use-focus-lock"
2738
import type { FocusTarget } from "focus-trap"
39+
import { useFocusTrap, useReturnFocusSelector } from "./use-focus-trap"
40+
import {
41+
MaybeElementRef,
42+
unrefElement,
43+
VueComponentInstance,
44+
} from "@chakra-ui/vue-utils"
45+
import { chakra } from '@chakra-ui/vue-system'
2846

2947
type RefProp =
3048
| (() => HTMLElement | string | object | undefined | unknown)
@@ -78,46 +96,61 @@ export const CFocusLock = defineComponent({
7896
},
7997
},
8098
setup(props, { attrs, slots, emit }) {
81-
const finalFocusElement = computed(() => {
82-
let finalFocus
83-
if (props.finalFocusRef) {
84-
const finalFocusRef = isFunction(props.finalFocusRef)
85-
? props.finalFocusRef?.()
86-
: props.finalFocusRef
87-
if (typeof finalFocusRef === "string") {
88-
finalFocus = document.querySelector<FocusableElement & Element>(
89-
finalFocusRef
99+
const target = ref<HTMLElement | VueComponentInstance>()
100+
const initialFocusElement = computed<HTMLElement>(() => {
101+
let initialFocus
102+
if (props.initialFocusRef) {
103+
let resolvedInitialFocusRef: MaybeElementRef =
104+
typeof props.initialFocusRef === "function"
105+
? props.initialFocusRef()
106+
: props.initialFocusRef
107+
108+
resolvedInitialFocusRef = unref(resolvedInitialFocusRef)
109+
if (typeof resolvedInitialFocusRef === "string") {
110+
initialFocus = document.querySelector<FocusableElement & Element>(
111+
resolvedInitialFocusRef
90112
)
91113
} else {
92-
// @ts-expect-error
93-
finalFocus = finalFocusRef?.$el || finalFocusRef
114+
initialFocus = resolvedInitialFocusRef?.$el || resolvedInitialFocusRef
94115
}
95116
}
96-
return finalFocus
117+
return initialFocus
97118
})
98119

99-
/**
100-
* Basic state for focus lock component.
101-
*/
102-
const { lock } = useFocusLock({
103-
...props,
104-
onActivate() {
105-
emit("activate")
106-
},
107-
onDeactivate() {
108-
setTimeout(() => {
109-
emit("deactivate")
110-
if (finalFocusElement.value) {
111-
focus(finalFocusElement.value)
112-
}
120+
const enabled = ref(true)
121+
function activate() {
122+
enabled.value = true
123+
}
124+
function deactivate() {
125+
enabled.value = false
126+
}
127+
const hasFocus = computed(() => enabled.value === true)
128+
129+
const containers = ref<Set<HTMLElement>>(new Set())
130+
watchEffect(
131+
(onInvalidate) => {
132+
let el: HTMLElement
133+
if (target.value) {
134+
el = unrefElement(target)
135+
containers.value.add(el)
136+
}
137+
138+
onInvalidate(() => {
139+
containers.value.delete(el)
113140
})
114141
},
115-
initialFocus: props.initialFocusRef as FocusTarget,
116-
// Should only return focus to original element
117-
// when the final focus element is not set
118-
returnFocusOnDeactivate: !!!finalFocusElement.value,
119-
immediate: props.autoFocus,
120-
})
142+
{ flush: "post" }
143+
)
144+
145+
useReturnFocusSelector(enabled)
146+
147+
useFocusTrap(
148+
containers,
149+
enabled,
150+
computed(() => ({
151+
initialFocus: initialFocusElement.value,
152+
}))
153+
)
121154

122155
return () => {
123156
const [firstChild] = slots.default?.({}) as VNode[]
@@ -130,11 +163,16 @@ export const CFocusLock = defineComponent({
130163
return
131164
}
132165

133-
return cloneVNode(firstChild, {
134-
ref: lock,
166+
return h(cloneVNode(firstChild, {
167+
ref: target,
135168
...attrs,
136169
"data-chakra-focus-lock": "",
137-
})
170+
}), {}, () => slots?.default?.({
171+
enabled,
172+
hasFocus,
173+
activate,
174+
deactivate
175+
}))
138176
}
139177
},
140178
})

0 commit comments

Comments
 (0)