Skip to content

Commit 2414bbd

Browse files
Ignore "outside click" on removed elements (#1193)
* ignore "outside click" on removed elements Co-authored-by: Colin King <me@colinking.co> * update changelog Co-authored-by: Colin King <me@colinking.co>
1 parent 694fbd5 commit 2414bbd

File tree

5 files changed

+88
-0
lines changed

5 files changed

+88
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- Reset Combobox Input when the value gets reset ([#1181](https://github.com/tailwindlabs/headlessui/pull/1181))
2121
- Fix double `beforeEnter` due to SSR ([#1183](https://github.com/tailwindlabs/headlessui/pull/1183))
2222
- Adjust active {item,option} index ([#1184](https://github.com/tailwindlabs/headlessui/pull/1184))
23+
- Only activate the `Tab` on mouseup ([#1192](https://github.com/tailwindlabs/headlessui/pull/1192))
24+
- Ignore "outside click" on removed elements ([#1193](https://github.com/tailwindlabs/headlessui/pull/1193))
2325

2426
## [Unreleased - @headlessui/vue]
2527

@@ -36,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3638
- Adjust active {item,option} index ([#1184](https://github.com/tailwindlabs/headlessui/pull/1184))
3739
- Fix re-focusing element after close ([#1186](https://github.com/tailwindlabs/headlessui/pull/1186))
3840
- Fix `Dialog` cycling ([#553](https://github.com/tailwindlabs/headlessui/pull/553))
41+
- Only activate the `Tab` on mouseup ([#1192](https://github.com/tailwindlabs/headlessui/pull/1192))
42+
- Ignore "outside click" on removed elements ([#1193](https://github.com/tailwindlabs/headlessui/pull/1193))
3943

4044
## [@headlessui/react@v1.5.0] - 2022-02-17
4145

packages/@headlessui-react/src/components/dialog/dialog.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,43 @@ describe('Mouse interactions', () => {
806806
expect(wrapperFn).toHaveBeenCalledTimes(0)
807807
})
808808
)
809+
810+
it(
811+
'should should be possible to click on removed elements without closing the Dialog',
812+
suppressConsoleLogs(async () => {
813+
function Example() {
814+
let [isOpen, setIsOpen] = useState(true)
815+
let wrapper = useRef<HTMLDivElement | null>(null)
816+
817+
return (
818+
<Dialog open={isOpen} onClose={setIsOpen}>
819+
<div ref={wrapper}>
820+
Contents
821+
<button
822+
onMouseDown={() => {
823+
// Remove this button before the Dialog's mousedown listener fires:
824+
wrapper.current?.remove()
825+
}}
826+
>
827+
Inside
828+
</button>
829+
<TabSentinel />
830+
</div>
831+
</Dialog>
832+
)
833+
}
834+
render(<Example />)
835+
836+
// Verify it is open
837+
assertDialog({ state: DialogState.Visible })
838+
839+
// Click the button inside the the Dialog
840+
await click(getByText('Inside'))
841+
842+
// Verify it is still open
843+
assertDialog({ state: DialogState.Visible })
844+
})
845+
)
809846
})
810847

811848
describe('Nesting', () => {

packages/@headlessui-react/src/hooks/use-outside-click.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export function useOutsideClick(
4747

4848
let target = event.target as HTMLElement
4949

50+
// Ignore if the target doesn't exist in the DOM anymore
51+
if (!target.ownerDocument.documentElement.contains(target)) return
52+
53+
// Ignore if the target exists in one of the containers
5054
for (let container of _containers) {
5155
if (container === null) continue
5256
let domNode = container instanceof HTMLElement ? container : container.current

packages/@headlessui-vue/src/components/dialog/dialog.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,44 @@ describe('Mouse interactions', () => {
997997
expect(wrapperFn).toHaveBeenCalledTimes(0)
998998
})
999999
)
1000+
1001+
it(
1002+
'should should be possible to click on removed elements without closing the Dialog',
1003+
suppressConsoleLogs(async () => {
1004+
renderTemplate({
1005+
template: `
1006+
<Dialog :open="isOpen" @close="setIsOpen">
1007+
<div ref="wrapper">
1008+
Contents
1009+
<!-- Remove this button before the Dialog's mousedown listener fires: -->
1010+
<button @mousedown="wrapper.remove()">Inside</button>
1011+
<TabSentinel />
1012+
</div>
1013+
</Dialog>
1014+
`,
1015+
setup() {
1016+
let isOpen = ref(true)
1017+
let wrapper = ref<HTMLDivElement | null>(null)
1018+
return {
1019+
isOpen,
1020+
wrapper,
1021+
setIsOpen(value: boolean) {
1022+
isOpen.value = value
1023+
},
1024+
}
1025+
},
1026+
})
1027+
1028+
// Verify it is open
1029+
assertDialog({ state: DialogState.Visible })
1030+
1031+
// Click the button inside the the Dialog
1032+
await click(getByText('Inside'))
1033+
1034+
// Verify it is still open
1035+
assertDialog({ state: DialogState.Visible })
1036+
})
1037+
)
10001038
})
10011039

10021040
describe('Nesting', () => {

packages/@headlessui-vue/src/hooks/use-outside-click.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ export function useOutsideClick(
3434
})
3535

3636
let target = event.target as HTMLElement
37+
38+
// Ignore if the target doesn't exist in the DOM anymore
39+
if (!target.ownerDocument.documentElement.contains(target)) return
40+
3741
let _containers = (() => {
3842
if (Array.isArray(containers)) {
3943
return containers
@@ -46,6 +50,7 @@ export function useOutsideClick(
4650
return [containers]
4751
})()
4852

53+
// Ignore if the target exists in one of the containers
4954
for (let container of _containers) {
5055
if (container === null) continue
5156
let domNode = container instanceof HTMLElement ? container : dom(container)

0 commit comments

Comments
 (0)