diff --git a/packages/radix-vue/src/DismissableLayer/utils.ts b/packages/radix-vue/src/DismissableLayer/utils.ts index 4d761da7b..fd48a820c 100644 --- a/packages/radix-vue/src/DismissableLayer/utils.ts +++ b/packages/radix-vue/src/DismissableLayer/utils.ts @@ -107,7 +107,7 @@ export function usePointerDownOutside( else { // We need to remove the event listener in case the outside click has been canceled. // See: https://github.com/radix-ui/primitives/issues/2171 - ownerDocumentClickCleanup() + ownerDocumentClickCleanup && ownerDocumentClickCleanup() } isPointerInsideDOMTree.value = false } @@ -130,7 +130,7 @@ export function usePointerDownOutside( cleanupFn(() => { window.clearTimeout(timerId) - ownerDocumentPointerdownCleanup() + ownerDocumentPointerdownCleanup && ownerDocumentPointerdownCleanup() }) }) @@ -175,6 +175,8 @@ export function useFocusOutside( } ownerDocumentFocusinCleanup = useEventListener(ownerDocument, 'focusin', handleFocus) + + cleanupFn(() => ownerDocumentFocusinCleanup()) }) return { diff --git a/packages/radix-vue/src/FocusScope/FocusScope.vue b/packages/radix-vue/src/FocusScope/FocusScope.vue index dc100db8d..5c10ad91e 100644 --- a/packages/radix-vue/src/FocusScope/FocusScope.vue +++ b/packages/radix-vue/src/FocusScope/FocusScope.vue @@ -69,7 +69,7 @@ const focusScope = reactive({ }, }) -watchEffect((cleanupFn) => { +watchEffect((onCleanup) => { if (!isClient) return const container = currentElement.value @@ -123,19 +123,21 @@ watchEffect((cleanupFn) => { focus(container) } - useEventListener(document, 'focusin', handleFocusIn) - useEventListener(document, 'focusout', handleFocusOut) + const documentFocusInCleanup = useEventListener(document, 'focusin', handleFocusIn) + const documentFocusOutCleanup = useEventListener(document, 'focusout', handleFocusOut) const mutationObserver = new MutationObserver(handleMutations) if (container) mutationObserver.observe(container, { childList: true, subtree: true }) - cleanupFn(() => { + onCleanup(() => { + documentFocusInCleanup() + documentFocusOutCleanup() mutationObserver.disconnect() }) }) -watchEffect(async (cleanupFn) => { +watchEffect(async (onCleanup) => { const container = currentElement.value await nextTick() @@ -160,7 +162,7 @@ watchEffect(async (cleanupFn) => { } } - cleanupFn(() => { + onCleanup(() => { container.removeEventListener(AUTOFOCUS_ON_MOUNT, (ev: Event) => emits('mountAutoFocus', ev)) diff --git a/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue b/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue index 2f451cb06..9ab7ae5aa 100644 --- a/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue +++ b/packages/radix-vue/src/NavigationMenu/NavigationMenuContentImpl.vue @@ -108,7 +108,7 @@ function handlePointerDownOutside(ev: PointerDownOutsideEvent) { } } -watchEffect(() => { +watchEffect((onCleanup) => { const content = currentElement.value if (menuContext.isRootMenu && content) { // Bubble dismiss to the root content node and focus its trigger @@ -119,7 +119,11 @@ watchEffect(() => { itemContext.triggerRef.value?.focus() } - useEventListener(document, EVENT_ROOT_CONTENT_DISMISS, handleClose) + const documentDismissCleanup = useEventListener(document, EVENT_ROOT_CONTENT_DISMISS, handleClose) + + onCleanup(() => { + documentDismissCleanup() + }) } }) diff --git a/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarScroll.vue b/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarScroll.vue index ece9d0d3f..737b79ba2 100644 --- a/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarScroll.vue +++ b/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarScroll.vue @@ -55,7 +55,7 @@ watchEffect((onCleanup) => { const debounceScrollEnd = useDebounceFn(() => dispatch('SCROLL_END'), 100) -watchEffect(() => { +watchEffect((onCleanup) => { const viewport = rootContext.viewport.value const scrollDirection = scrollbarContext.isHorizontal.value ? 'scrollLeft' @@ -73,7 +73,11 @@ watchEffect(() => { prevScrollPos = scrollPos } - useEventListener(viewport, 'scroll', handleScroll) + const viewportScrollCleanup = useEventListener(viewport, 'scroll', handleScroll) + + onCleanup(() => { + viewportScrollCleanup() + }) } }) diff --git a/packages/radix-vue/src/Select/SelectContentImpl.vue b/packages/radix-vue/src/Select/SelectContentImpl.vue index 4b60a4277..ffde01b90 100644 --- a/packages/radix-vue/src/Select/SelectContentImpl.vue +++ b/packages/radix-vue/src/Select/SelectContentImpl.vue @@ -130,7 +130,10 @@ watch(isPositioned, () => { // prevent selecting items on `pointerup` in some cases after opening from `pointerdown` // and close on `pointerup` outside. const { onOpenChange, triggerPointerDownPosRef } = rootContext -watchEffect((cleanupFn) => { + +let documentPointermoveCleanup: ReturnType + +watchEffect((onCleanup) => { if (!content.value) return let pointerMoveDelta = { x: 0, y: 0 } @@ -160,19 +163,20 @@ watchEffect((cleanupFn) => { if (!content.value?.contains(event.target as HTMLElement)) onOpenChange(false) } - useEventListener(document, 'pointermove', handlePointerMove) + documentPointermoveCleanup = useEventListener(document, 'pointermove', handlePointerMove) triggerPointerDownPosRef.value = null } if (triggerPointerDownPosRef.value !== null) { - useEventListener(document, 'pointermove', handlePointerMove) + documentPointermoveCleanup = useEventListener(document, 'pointermove', handlePointerMove) document.addEventListener('pointerup', handlePointerUp, { capture: true, once: true, }) } - cleanupFn(() => { + onCleanup(() => { + documentPointermoveCleanup && documentPointermoveCleanup() document.removeEventListener('pointerup', handlePointerUp, { capture: true, }) diff --git a/packages/radix-vue/src/Select/SelectScrollDownButton.vue b/packages/radix-vue/src/Select/SelectScrollDownButton.vue index 093346d81..40abb51a0 100644 --- a/packages/radix-vue/src/Select/SelectScrollDownButton.vue +++ b/packages/radix-vue/src/Select/SelectScrollDownButton.vue @@ -24,7 +24,7 @@ const { forwardRef, currentElement } = useForwardExpose() const canScrollDown = ref(false) -watchEffect(() => { +watchEffect((onCleanup) => { if (contentContext.viewport?.value && contentContext.isPositioned?.value) { const viewport = contentContext.viewport.value @@ -36,7 +36,11 @@ watchEffect(() => { } handleScroll() - useEventListener(viewport, 'scroll', handleScroll) + const viewportScrollCleanup = useEventListener(viewport, 'scroll', handleScroll) + + onCleanup(() => { + viewportScrollCleanup() + }) } }) diff --git a/packages/radix-vue/src/Select/SelectScrollUpButton.vue b/packages/radix-vue/src/Select/SelectScrollUpButton.vue index 542ee205b..87c8c8df1 100644 --- a/packages/radix-vue/src/Select/SelectScrollUpButton.vue +++ b/packages/radix-vue/src/Select/SelectScrollUpButton.vue @@ -24,7 +24,7 @@ const { forwardRef, currentElement } = useForwardExpose() const canScrollUp = ref(false) -watchEffect(() => { +watchEffect((onCleanup) => { if (contentContext.viewport?.value && contentContext.isPositioned?.value) { const viewport = contentContext.viewport.value @@ -32,7 +32,12 @@ watchEffect(() => { canScrollUp.value = viewport.scrollTop > 0 } handleScroll() - useEventListener(viewport, 'scroll', handleScroll) + + const viewportScrollCleanup = useEventListener(viewport, 'scroll', handleScroll) + + onCleanup(() => { + viewportScrollCleanup() + }) } }) diff --git a/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterBehavior.ts b/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterBehavior.ts index 85715c35c..c6e44af61 100644 --- a/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterBehavior.ts +++ b/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterBehavior.ts @@ -17,7 +17,7 @@ export function useWindowSplitterResizeHandlerBehavior({ resizeHandler: Ref panelGroupElement: Ref }): void { - watchEffect(() => { + watchEffect((onCleanup) => { const _panelGroupElement = panelGroupElement.value if (disabled.value || resizeHandler.value === null || _panelGroupElement === null) return @@ -76,6 +76,10 @@ export function useWindowSplitterResizeHandlerBehavior({ } } - useEventListener(handleElement, 'keydown', onKeyDown) + const handleElementKeydownCleanup = useEventListener(handleElement, 'keydown', onKeyDown) + + onCleanup(() => { + handleElementKeydownCleanup() + }) }) } diff --git a/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterPanelGroupBehavior.ts b/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterPanelGroupBehavior.ts index a76f218af..3aa0412a9 100644 --- a/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterPanelGroupBehavior.ts +++ b/packages/radix-vue/src/Splitter/utils/composables/useWindowSplitterPanelGroupBehavior.ts @@ -94,7 +94,7 @@ export function useWindowSplitterPanelGroupBehavior({ const handles = getResizeHandleElementsForGroup(groupId, _panelGroupElement) assert(handles) - handles.forEach((handle) => { + const cleanupFunctions = handles.map((handle) => { const handleId = handle.getAttribute('data-panel-resize-handle-id') assert(handleId) @@ -155,7 +155,15 @@ export function useWindowSplitterPanelGroupBehavior({ } } - useEventListener(handle, 'keydown', onKeyDown) + const handleKeydownCleanup = useEventListener(handle, 'keydown', onKeyDown) + + return () => { + handleKeydownCleanup() + } + }) + + onCleanup(() => { + cleanupFunctions.forEach(cleanupFunction => cleanupFunction()) }) }) } diff --git a/packages/radix-vue/src/Toast/ToastRootImpl.vue b/packages/radix-vue/src/Toast/ToastRootImpl.vue index 37ed00310..3d96c210a 100644 --- a/packages/radix-vue/src/Toast/ToastRootImpl.vue +++ b/packages/radix-vue/src/Toast/ToastRootImpl.vue @@ -104,7 +104,7 @@ if (props.type && !['foreground', 'background'].includes(props.type)) { throw new Error(error) } -watchEffect(() => { +watchEffect((onCleanup) => { const viewport = providerContext.viewport.value if (viewport) { const handleResume = () => { @@ -120,8 +120,13 @@ watchEffect(() => { emits('pause') } - useEventListener(viewport, VIEWPORT_PAUSE, handlePause) - useEventListener(viewport, VIEWPORT_RESUME, handleResume) + const viewportPauseCleanup = useEventListener(viewport, VIEWPORT_PAUSE, handlePause) + const viewportResumeCleanup = useEventListener(viewport, VIEWPORT_RESUME, handleResume) + + onCleanup(() => { + viewportPauseCleanup() + viewportResumeCleanup() + }) } }) diff --git a/packages/radix-vue/src/Toast/ToastViewport.vue b/packages/radix-vue/src/Toast/ToastViewport.vue index 78646c607..b421f9865 100644 --- a/packages/radix-vue/src/Toast/ToastViewport.vue +++ b/packages/radix-vue/src/Toast/ToastViewport.vue @@ -58,7 +58,7 @@ onMounted(() => { providerContext.onViewportChange(currentElement.value) }) -watchEffect(() => { +watchEffect((onCleanup) => { const viewport = currentElement.value if (hasToasts.value && viewport) { const handlePause = () => { @@ -125,12 +125,21 @@ watchEffect(() => { } } - useEventListener(viewport, ['focusin', 'pointermove'], handlePause) - useEventListener(viewport, 'focusout', handleFocusOutResume) - useEventListener(viewport, 'pointerleave', handlePointerLeaveResume) - useEventListener(viewport, 'keydown', handleKeyDown) - useEventListener(window, 'blur', handlePause) - useEventListener(window, 'focus', handleResume) + const viewportEventsCleanup = useEventListener(viewport, ['focusin', 'pointermove'], handlePause) + const viewportFocusoutCleanup = useEventListener(viewport, 'focusout', handleFocusOutResume) + const viewportPointerLeaveCleanup = useEventListener(viewport, 'pointerleave', handlePointerLeaveResume) + const viewportKeydownCleanup = useEventListener(viewport, 'keydown', handleKeyDown) + const windowBlurCleanup = useEventListener(window, 'blur', handlePause) + const windowFocusCleanup = useEventListener(window, 'focus', handleResume) + + onCleanup(() => { + viewportEventsCleanup() + viewportFocusoutCleanup() + viewportPointerLeaveCleanup() + viewportKeydownCleanup() + windowBlurCleanup() + windowFocusCleanup() + }) } }) diff --git a/packages/radix-vue/src/shared/useGraceArea.ts b/packages/radix-vue/src/shared/useGraceArea.ts index 0b0275fcf..bcedd61aa 100644 --- a/packages/radix-vue/src/shared/useGraceArea.ts +++ b/packages/radix-vue/src/shared/useGraceArea.ts @@ -4,7 +4,7 @@ import { createEventHook, refAutoReset } from '@vueuse/shared' import { useEventListener } from '@vueuse/core' export function useGraceArea(triggerElement: Ref, containerElement: Ref) { -// Reset the inTransit state if idle/scrolled. + // Reset the inTransit state if idle/scrolled. const isPointerInTransit = refAutoReset(false, 300) const pointerGraceArea = ref(null) @@ -26,17 +26,22 @@ export function useGraceArea(triggerElement: Ref, conta isPointerInTransit.value = true } - watchEffect(() => { + watchEffect((onCleanup) => { if (triggerElement.value && containerElement.value) { const handleTriggerLeave = (event: PointerEvent) => handleCreateGraceArea(event, containerElement.value!) const handleContentLeave = (event: PointerEvent) => handleCreateGraceArea(event, triggerElement.value!) - useEventListener(triggerElement, 'pointerleave', handleTriggerLeave) - useEventListener(containerElement, 'pointerleave', handleContentLeave) + const triggerElementPointerLeaveCleanup = useEventListener(triggerElement, 'pointerleave', handleTriggerLeave) + const triggerElementPointerLeaveContentCleanup = useEventListener(containerElement, 'pointerleave', handleContentLeave) + + onCleanup(() => { + triggerElementPointerLeaveCleanup() + triggerElementPointerLeaveContentCleanup() + }) } }) - watchEffect(() => { + watchEffect((onCleanup) => { if (pointerGraceArea.value) { const handleTrackPointerGrace = (event: PointerEvent) => { if (!pointerGraceArea.value) @@ -56,7 +61,11 @@ export function useGraceArea(triggerElement: Ref, conta } } - useEventListener(document, 'pointermove', handleTrackPointerGrace) + const documentPointermoveCleanup = useEventListener(document, 'pointermove', handleTrackPointerGrace) + + onCleanup(() => { + documentPointermoveCleanup() + }) } })