Skip to content

feat(useFocusTrap): expose updateContainerElements for dynamic contai… #4849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 28, 2025
Merged
Prev Previous commit
Next Next commit
refactor(useFocusTrap): support dynamic focus targets and add documen…
…tation
  • Loading branch information
PeikyLiu committed Jul 19, 2025
commit 7c629e0dbcad7e3d9f1ec53f581df46a2afc29b4
38 changes: 2 additions & 36 deletions packages/integrations/useFocusTrap/demo.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
<script setup lang="ts">
import { useFocusTrap } from '@vueuse/integrations'
import { shallowRef, useTemplateRef } from 'vue'
import { useTemplateRef } from 'vue'

const target = useTemplateRef<HTMLElement>('target')
const target2 = useTemplateRef<HTMLElement>('target2')
const targets = shallowRef<HTMLElement | null>(null)

const { hasFocus, activate, deactivate } = useFocusTrap(targets)

function focusTopForm() {
targets.value = target.value
}

function focusBottomForm() {
targets.value = target2.value
}
const { hasFocus, activate, deactivate } = useFocusTrap(target)
</script>

<template>
Expand Down Expand Up @@ -42,29 +31,6 @@ function focusBottomForm() {
<button @click="deactivate()">
Free Focus
</button>
<button @click="focusBottomForm">
Focus Bottom Form
</button>
</div>
</div>

<div
ref="target2"
class="shadow-lg bg-green-100 rounded max-w-96 mx-auto p-8"
>
<h3 class="text-center mb-4">
Container 2
</h3>
<input type="text" placeholder="First Name" class="block w-full mb-2">
<input type="text" placeholder="Last Name" class="block w-full mb-2">
<textarea placeholder="Comments" class="block w-full mb-4" />
<div class="flex gap-2 justify-center">
<button @click="deactivate()">
Free Focus
</button>
<button @click="focusTopForm">
Focus Top Form
</button>
</div>
</div>
</div>
Expand Down
35 changes: 35 additions & 0 deletions packages/integrations/useFocusTrap/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,41 @@ const { hasFocus, activate, deactivate } = useFocusTrap([targetOne, targetTwo])
</template>
```

**Dynamic Focus Target**

```vue
<script setup lang="ts">
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { computed, shallowRef, useTemplateRef } from 'vue'

const left = useTemplateRef('left')
const right = useTemplateRef('right')
const currentRef = shallowRef<'left' | 'right'>('left')

const target = computed(() => {
return activeRefName.value === 'left' ? left : activeRefName.value === 'right' ? right : null
})
const { hasFocus, activate } = useFocusTrap(target)

activate()

setTimeout(() => {
activeRef.value = 'right'
}, 3000)
</script>

<template>
<div>
<div ref="left" class="left">
...
</div>
<div ref="right" class="right">
...
</div>
</div>
</template>
```

**Automatically Focus**

```vue
Expand Down
16 changes: 9 additions & 7 deletions packages/integrations/useFocusTrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ export function useFocusTrap(
options: UseFocusTrapOptions = {},
): UseFocusTrapReturn {
let trap: undefined | FocusTrap

const { immediate, ...focusTrapOptions } = options
const hasFocus = shallowRef(false)
const isPaused = shallowRef(false)
let initial = true

const activate = (opts?: ActivateOptions) => trap && trap.activate(opts)
const deactivate = (opts?: DeactivateOptions) => trap && trap.deactivate(opts)

Expand All @@ -85,22 +86,24 @@ export function useFocusTrap(
isPaused.value = false
}
}

const targets = computed(() => {
const _targets = toValue(target)

return toArray(_targets)
.map((el) => {
const _el = toValue(el)
return typeof _el === 'string' ? _el : unrefElement(_el)
})
.filter(notNullish)
})

watch(
targets,
(els) => {
if (!els.length)
return
if (initial) {
if (!trap) {
// create the trap
trap = createFocusTrap(els, {
...focusTrapOptions,
onActivate() {
Expand All @@ -120,17 +123,16 @@ export function useFocusTrap(
})

// Focus if immediate is set to true
if (immediate) {
if (immediate)
activate()
}

initial = false
}
else {
// get the active state of the trap
const isActive = trap?.active

// update the container elements
trap?.updateContainerElements(els)

// if the trap is not active and immediate is set to true, activate the trap
if (!isActive && immediate) {
activate()
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy