From 9a82d23c12d7de951182b9123d3202ed2a1e65cb Mon Sep 17 00:00:00 2001 From: Sadegh Barati Date: Thu, 8 Aug 2024 19:57:11 +0330 Subject: [PATCH 1/3] refactor: use vueuse useEventListener composable in order to remove listener easier --- .../radix-vue/src/DismissableLayer/utils.ts | 26 +++++++++--------- .../radix-vue/src/FocusScope/FocusScope.vue | 14 +++++----- .../src/HoverCard/HoverCardContentImpl.vue | 4 +-- .../NavigationMenuContentImpl.vue | 8 +++--- .../ScrollArea/ScrollAreaScrollbarHover.vue | 10 +++---- .../ScrollArea/ScrollAreaScrollbarImpl.vue | 9 +++---- .../ScrollArea/ScrollAreaScrollbarScroll.vue | 9 +++---- .../src/ScrollArea/ScrollAreaThumb.vue | 6 ++--- .../src/Select/SelectContentImpl.vue | 7 +++-- .../src/Select/SelectScrollDownButton.vue | 6 ++--- .../src/Select/SelectScrollUpButton.vue | 7 +++-- .../composables/useWindowSplitterBehavior.ts | 8 +++--- .../useWindowSplitterPanelGroupBehavior.ts | 12 +++------ .../radix-vue/src/Toast/ToastRootImpl.vue | 17 +++++------- .../radix-vue/src/Toast/ToastViewport.vue | 27 ++++++------------- .../radix-vue/src/Tooltip/TooltipTrigger.vue | 3 ++- .../shared/handleAndDispatchCustomEvent.ts | 4 ++- .../radix-vue/src/shared/onFocusOutside.ts | 12 +++------ packages/radix-vue/src/shared/trap-focus.ts | 5 +++- packages/radix-vue/src/shared/useGraceArea.ts | 23 ++++++---------- 20 files changed, 90 insertions(+), 127 deletions(-) diff --git a/packages/radix-vue/src/DismissableLayer/utils.ts b/packages/radix-vue/src/DismissableLayer/utils.ts index 3a8358a50..4d761da7b 100644 --- a/packages/radix-vue/src/DismissableLayer/utils.ts +++ b/packages/radix-vue/src/DismissableLayer/utils.ts @@ -1,4 +1,5 @@ import { isClient } from '@vueuse/shared' +import { useEventListener } from '@vueuse/core' import { handleAndDispatchCustomEvent } from '@/shared' import { type Ref, nextTick, ref, watchEffect } from 'vue' @@ -30,12 +31,11 @@ function isLayerExist(layerElement: HTMLElement, targetElement: HTMLElement) { (targetLayer && mainLayer === targetLayer) || nodeList.indexOf(mainLayer) < nodeList.indexOf(targetLayer) - ) { + ) return true - } - else { + + else return false - } } /** @@ -53,6 +53,9 @@ export function usePointerDownOutside( const isPointerInsideDOMTree = ref(false) const handleClickRef = ref(() => {}) + let ownerDocumentClickCleanup: ReturnType + let ownerDocumentPointerdownCleanup: ReturnType + watchEffect((cleanupFn) => { if (!isClient) return @@ -93,7 +96,7 @@ export function usePointerDownOutside( if (event.pointerType === 'touch') { ownerDocument.removeEventListener('click', handleClickRef.value) handleClickRef.value = handleAndDispatchPointerDownOutsideEvent - ownerDocument.addEventListener('click', handleClickRef.value, { + ownerDocumentClickCleanup = useEventListener(ownerDocument, 'click', handleClickRef.value, { once: true, }) } @@ -104,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 - ownerDocument.removeEventListener('click', handleClickRef.value) + ownerDocumentClickCleanup() } isPointerInsideDOMTree.value = false } @@ -122,13 +125,12 @@ export function usePointerDownOutside( * }); */ const timerId = window.setTimeout(() => { - ownerDocument.addEventListener('pointerdown', handlePointerDown) + ownerDocumentPointerdownCleanup = useEventListener(ownerDocument, 'pointerdown', handlePointerDown) }, 0) cleanupFn(() => { window.clearTimeout(timerId) - ownerDocument.removeEventListener('pointerdown', handlePointerDown) - ownerDocument.removeEventListener('click', handleClickRef.value) + ownerDocumentPointerdownCleanup() }) }) @@ -148,6 +150,8 @@ export function useFocusOutside( const ownerDocument: Document = element?.value?.ownerDocument ?? globalThis?.document + let ownerDocumentFocusinCleanup: ReturnType + const isFocusInsideDOMTree = ref(false) watchEffect((cleanupFn) => { if (!isClient) @@ -170,9 +174,7 @@ export function useFocusOutside( } } - ownerDocument.addEventListener('focusin', handleFocus) - - cleanupFn(() => ownerDocument.removeEventListener('focusin', handleFocus)) + ownerDocumentFocusinCleanup = useEventListener(ownerDocument, 'focusin', handleFocus) }) return { diff --git a/packages/radix-vue/src/FocusScope/FocusScope.vue b/packages/radix-vue/src/FocusScope/FocusScope.vue index 38630a015..dc100db8d 100644 --- a/packages/radix-vue/src/FocusScope/FocusScope.vue +++ b/packages/radix-vue/src/FocusScope/FocusScope.vue @@ -35,6 +35,7 @@ export interface FocusScopeProps extends PrimitiveProps { diff --git a/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarImpl.vue b/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarImpl.vue index 81a5c39dd..0ae71e739 100644 --- a/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarImpl.vue +++ b/packages/radix-vue/src/ScrollArea/ScrollAreaScrollbarImpl.vue @@ -11,8 +11,8 @@ export interface ScrollAreaScrollbarImplProps { diff --git a/packages/radix-vue/src/ScrollArea/ScrollAreaThumb.vue b/packages/radix-vue/src/ScrollArea/ScrollAreaThumb.vue index ff7468db3..81b7b9f09 100644 --- a/packages/radix-vue/src/ScrollArea/ScrollAreaThumb.vue +++ b/packages/radix-vue/src/ScrollArea/ScrollAreaThumb.vue @@ -7,7 +7,7 @@ export interface ScrollAreaThumbProps extends PrimitiveProps {} diff --git a/packages/radix-vue/src/Select/SelectContentImpl.vue b/packages/radix-vue/src/Select/SelectContentImpl.vue index 99b515a3f..4b60a4277 100644 --- a/packages/radix-vue/src/Select/SelectContentImpl.vue +++ b/packages/radix-vue/src/Select/SelectContentImpl.vue @@ -85,7 +85,7 @@ import { watch, watchEffect, } from 'vue' -import { unrefElement } from '@vueuse/core' +import { unrefElement, useEventListener } from '@vueuse/core' import { injectSelectRootContext } from './SelectRoot.vue' import SelectItemAlignedPosition from './SelectItemAlignedPosition.vue' import SelectPopperPosition from './SelectPopperPosition.vue' @@ -160,12 +160,12 @@ watchEffect((cleanupFn) => { if (!content.value?.contains(event.target as HTMLElement)) onOpenChange(false) } - document.removeEventListener('pointermove', handlePointerMove) + useEventListener(document, 'pointermove', handlePointerMove) triggerPointerDownPosRef.value = null } if (triggerPointerDownPosRef.value !== null) { - document.addEventListener('pointermove', handlePointerMove) + useEventListener(document, 'pointermove', handlePointerMove) document.addEventListener('pointerup', handlePointerUp, { capture: true, once: true, @@ -173,7 +173,6 @@ watchEffect((cleanupFn) => { } cleanupFn(() => { - document.removeEventListener('pointermove', handlePointerMove) 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 51c6192ae..093346d81 100644 --- a/packages/radix-vue/src/Select/SelectScrollDownButton.vue +++ b/packages/radix-vue/src/Select/SelectScrollDownButton.vue @@ -7,6 +7,7 @@ export interface SelectScrollDownButtonProps extends PrimitiveProps {} 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() + }) } })