diff --git a/.changeset/three-oranges-brake.md b/.changeset/three-oranges-brake.md
new file mode 100644
index 0000000..78af945
--- /dev/null
+++ b/.changeset/three-oranges-brake.md
@@ -0,0 +1,5 @@
+---
+"@primer/live-region-element": minor
+---
+
+Update logic for finding, or creating, live regions to work while within dialog elements
diff --git a/packages/live-region-element/src/__tests__/query.test.ts b/packages/live-region-element/src/__tests__/query.test.ts
new file mode 100644
index 0000000..b6da9ad
--- /dev/null
+++ b/packages/live-region-element/src/__tests__/query.test.ts
@@ -0,0 +1,99 @@
+import {describe, test, expect, afterEach} from 'vitest'
+import '../define'
+import {findOrCreateLiveRegion, getClosestLiveRegion} from '../query'
+
+afterEach(() => {
+ document.body.innerHTML = ''
+})
+
+describe('findOrCreateLiveRegion', () => {
+ test('no live region', () => {
+ expect(document.querySelector('live-region')).toBe(null)
+ const liveRegion = findOrCreateLiveRegion()
+ expect(liveRegion).toBeInTheDocument()
+ expect(document.querySelector('live-region')).toBe(liveRegion)
+ })
+
+ test('existing live region', () => {
+ const liveRegion = document.createElement('live-region')
+ document.body.appendChild(liveRegion)
+ expect(findOrCreateLiveRegion()).toBe(liveRegion)
+ })
+
+ test('in dialog with live region', () => {
+ document.body.innerHTML = `
+
+
+ `
+ const liveRegion = findOrCreateLiveRegion(document.getElementById('target')!)
+ expect(liveRegion).toBe(document.getElementById('local'))
+ })
+
+ test('in dialog with no live region', () => {
+ document.body.innerHTML = `
+
+
+ `
+ const dialog = document.querySelector('dialog')!
+ expect(dialog.querySelector('live-region')).toBe(null)
+
+ const liveRegion = findOrCreateLiveRegion(document.getElementById('target')!)
+ expect(dialog).toContainElement(liveRegion)
+ })
+})
+
+describe('getClosestLiveRegion', () => {
+ test('no live region', () => {
+ const element = document.createElement('div')
+ document.body.appendChild(element)
+ expect(getClosestLiveRegion(element)).toBe(null)
+ })
+
+ test('live region in document.body', () => {
+ const element = document.createElement('div')
+ document.body.appendChild(element)
+
+ const liveRegion = document.createElement('live-region')
+ document.body.appendChild(liveRegion)
+
+ expect(getClosestLiveRegion(element)).toBe(liveRegion)
+ })
+
+ test('live region as sibling', () => {
+ document.body.innerHTML = `
+
+
+ `
+
+ expect(getClosestLiveRegion(document.getElementById('target')!)).toBe(document.getElementById('sibling'))
+ })
+
+ test('live region within dialog', () => {
+ document.body.innerHTML = `
+
+
+ `
+ expect(getClosestLiveRegion(document.getElementById('target')!)).toBe(document.getElementById('local'))
+ })
+
+ test('no live region within dialog', () => {
+ document.body.innerHTML = `
+
+
+ `
+ expect(getClosestLiveRegion(document.getElementById('target')!)).toBe(null)
+ })
+})
diff --git a/packages/live-region-element/src/index.ts b/packages/live-region-element/src/index.ts
index 6759452..36750b9 100644
--- a/packages/live-region-element/src/index.ts
+++ b/packages/live-region-element/src/index.ts
@@ -1,5 +1,6 @@
import './define'
import {LiveRegionElement, templateContent, type AnnounceOptions} from './live-region-element'
+import {findOrCreateLiveRegion} from './query'
type GlobalAnnounceOptions = AnnounceOptions & {
/**
@@ -32,52 +33,4 @@ export function announceFromElement(element: HTMLElement, options: GlobalAnnounc
return liveRegion.announceFromElement(element, options)
}
-let liveRegion: LiveRegionElement | null = null
-
-function findOrCreateLiveRegion(from?: HTMLElement, appendTo?: HTMLElement): LiveRegionElement {
- // Check to see if we have already created a `` element and that
- // it is currently connected to the DOM.
- if (liveRegion !== null && liveRegion.isConnected) {
- return liveRegion
- }
-
- // If `from` is defined, try to find the closest `` element
- // relative to the given element
- liveRegion = from ? getClosestLiveRegion(from) : null
- if (liveRegion !== null) {
- return liveRegion
- }
-
- // Otherwise, try to find any `` element in the document
- liveRegion = document.querySelector('live-region')
- if (liveRegion !== null) {
- return liveRegion
- }
-
- // Finally, if none exist, create a new `` element and append it
- // to the given `appendTo` element, if one exists
- liveRegion = document.createElement('live-region') as LiveRegionElement
- if (appendTo) {
- appendTo.appendChild(liveRegion)
- } else {
- document.body.appendChild(liveRegion)
- }
-
- return liveRegion
-}
-
-function getClosestLiveRegion(from: HTMLElement): LiveRegionElement | null {
- let current: HTMLElement | null = from
-
- while ((current = current.parentElement)) {
- for (const child of current.childNodes) {
- if (child instanceof LiveRegionElement) {
- return child
- }
- }
- }
-
- return null
-}
-
export {LiveRegionElement, templateContent}
diff --git a/packages/live-region-element/src/query.ts b/packages/live-region-element/src/query.ts
new file mode 100644
index 0000000..69b7d65
--- /dev/null
+++ b/packages/live-region-element/src/query.ts
@@ -0,0 +1,60 @@
+import {LiveRegionElement} from './live-region-element'
+
+export function findOrCreateLiveRegion(from?: HTMLElement, appendTo?: HTMLElement): LiveRegionElement {
+ let liveRegion: LiveRegionElement | null = null
+
+ // If `from` is defined, try to find the closest `` element
+ // relative to the given element
+ liveRegion = from ? getClosestLiveRegion(from) : null
+ if (liveRegion !== null) {
+ return liveRegion
+ }
+
+ // Get the containing element for the live region. If we know we are within a
+ // dialog element, then live regions must live within that element.
+ let container = document.body
+ if (from) {
+ const dialog = from.closest('dialog')
+ if (dialog) {
+ container = dialog
+ }
+ }
+
+ // Otherwise, try to find any `` element in the document
+ liveRegion = container.querySelector('live-region')
+ if (liveRegion !== null) {
+ return liveRegion
+ }
+
+ // Finally, if none exist, create a new `` element and append it
+ // to the given `appendTo` element, if one exists
+ liveRegion = document.createElement('live-region')
+ if (appendTo) {
+ appendTo.appendChild(liveRegion)
+ } else {
+ container.appendChild(liveRegion)
+ }
+
+ return liveRegion
+}
+
+export function getClosestLiveRegion(from: HTMLElement): LiveRegionElement | null {
+ const dialog = from.closest('dialog')
+ let current: HTMLElement | null = from
+
+ while ((current = current.parentElement)) {
+ // If the element exists within a