diff --git a/docs/en-US/component/drawer.md b/docs/en-US/component/drawer.md index 178aaef27b..4b374bebb6 100644 --- a/docs/en-US/component/drawer.md +++ b/docs/en-US/component/drawer.md @@ -137,14 +137,17 @@ Drawer provides an API called `destroy-on-close`, which is a flag variable that ### Events -| Name | Description | Type | -| ---------------- | ------------------------------------------------ | ----------------------- | -| open | Triggered before Drawer opening animation begins | ^[Function]`() => void` | -| opened | Triggered after Drawer opening animation ended | ^[Function]`() => void` | -| close | Triggered before Drawer closing animation begins | ^[Function]`() => void` | -| closed | Triggered after Drawer closing animation ended | ^[Function]`() => void` | -| open-auto-focus | triggers after Drawer opens and content focused | ^[Function]`() => void` | -| close-auto-focus | triggers after Drawer closed and content focused | ^[Function]`() => void` | +| Name | Description | Type | +| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------ | +| open | Triggered before Drawer opening animation begins | ^[Function]`() => void` | +| opened | Triggered after Drawer opening animation ended | ^[Function]`() => void` | +| close | Triggered before Drawer closing animation begins | ^[Function]`() => void` | +| closed | Triggered after Drawer closing animation ended | ^[Function]`() => void` | +| open-auto-focus | triggers after Drawer opens and content focused | ^[Function]`() => void` | +| close-auto-focus | triggers after Drawer closed and content focused | ^[Function]`() => void` | +| resize-start ^(2.11.8) | Triggered when resizing starts (when `resizable` is enabled) | ^[Function]`(event: MouseEvent, size: number) => void` | +| resize ^(2.11.8) | Triggered while resizing (when `resizable` is enabled) | ^[Function]`(event: MouseEvent, size: number) => void` | +| resize-end ^(2.11.8) | Triggered when resizing ends (when `resizable` is enabled) | ^[Function]`(event: MouseEvent, size: number) => void` | ### Slots diff --git a/packages/components/drawer/__tests__/drawer.test.ts b/packages/components/drawer/__tests__/drawer.test.ts index 6d0b70c062..2b8992f8cb 100644 --- a/packages/components/drawer/__tests__/drawer.test.ts +++ b/packages/components/drawer/__tests__/drawer.test.ts @@ -581,6 +581,100 @@ describe('Drawer', () => { cleanup() }) + + test('should emit resize-start, resize and resize-end events', async () => { + const cleanup = defineGetter(window, 'innerWidth', '100') + const onResizeStart = vi.fn() + const onResize = vi.fn() + const onResizeEnd = vi.fn() + + const wrapper = _mount( + ` + + ${content} + + `, + () => ({ + visible: true, + }), + { + methods: { + onResizeStart, + onResize, + onResizeEnd, + }, + } + ) + + await nextTick() + + const dragger = wrapper.find('.el-drawer__dragger') + const drawerEl = wrapper.find('.el-drawer').element as HTMLDivElement + Object.defineProperty(drawerEl, 'offsetWidth', { + value: 50, + configurable: true, + }) + + const prop = 'pageX' + + // Simulate mouse down - should trigger resize-start + const mousedown = new MouseEvent('mousedown', { bubbles: true }) + const mousedownCleanup = defineGetter(mousedown, prop, 50) + dragger.element.dispatchEvent(mousedown) + await nextTick() + + expect(onResizeStart).toHaveBeenCalledTimes(1) + expect(onResizeStart).toHaveBeenCalledWith( + expect.any(MouseEvent), + expect.any(Number) + ) + + // Simulate mouse move - should trigger resize + const mousemove = new MouseEvent('mousemove', { bubbles: true }) + const mousemoveCleanup = defineGetter(mousemove, prop, 80) + window.dispatchEvent(mousemove) + await nextTick() + + expect(onResize).toHaveBeenCalled() + expect(onResize).toHaveBeenCalledWith( + expect.any(MouseEvent), + expect.any(Number) + ) + const resizeCallCount = onResize.mock.calls.length + + // Simulate another mouse move + const mousemove2 = new MouseEvent('mousemove', { bubbles: true }) + const mousemove2Cleanup = defineGetter(mousemove2, prop, 100) + window.dispatchEvent(mousemove2) + await nextTick() + + expect(onResize).toHaveBeenCalledTimes(resizeCallCount + 1) + + // Simulate mouse up - should trigger resize-end + const mouseup = new MouseEvent('mouseup', { bubbles: true }) + const mouseupCleanup = defineGetter(mouseup, prop, 100) + window.dispatchEvent(mouseup) + await nextTick() + + expect(onResizeEnd).toHaveBeenCalledTimes(1) + expect(onResizeEnd).toHaveBeenCalledWith( + expect.any(MouseEvent), + expect.any(Number) + ) + + mousedownCleanup() + mousemoveCleanup() + mousemove2Cleanup() + mouseupCleanup() + cleanup() + }) }) describe('accessibility', () => { diff --git a/packages/components/drawer/src/composables/useResizable.ts b/packages/components/drawer/src/composables/useResizable.ts index ef344847ab..dcd802a570 100644 --- a/packages/components/drawer/src/composables/useResizable.ts +++ b/packages/components/drawer/src/composables/useResizable.ts @@ -2,12 +2,13 @@ import { computed, onBeforeUnmount, ref, watch } from 'vue' import { addUnit } from '@element-plus/utils' import { clamp, useEventListener, useWindowSize } from '@vueuse/core' -import type { Ref } from 'vue' -import type { DrawerProps } from '../drawer' +import type { Ref, SetupContext } from 'vue' +import type { DrawerEmits, DrawerProps } from '../drawer' export function useResizable( props: DrawerProps, - target: Ref + target: Ref, + emit: SetupContext['emit'] ) { const { width, height } = useWindowSize() @@ -61,6 +62,7 @@ export function useResizable( startPos = [e.pageX, e.pageY] isResizing.value = true + emit('resize-start', e, startSize.value) cleanups.push( useEventListener(window, 'mouseup', onMouseUp), useEventListener(window, 'mousemove', onMouseMove) @@ -72,15 +74,23 @@ export function useResizable( const offsetX = pageX - startPos[0] const offsetY = pageY - startPos[1] offset.value = isHorizontal.value ? offsetX : offsetY + emit('resize', e, getSize.value) } - const onMouseUp = () => { + const onMouseUp = (e?: MouseEvent) => { + // premature interruption + // avoid triggering meaningless execution due to watch size/resizable constraints. + if (!isResizing.value) return + startPos = [] startSize.value = getSize.value offset.value = 0 isResizing.value = false cleanups.forEach((cleanup) => cleanup?.()) cleanups = [] + if (e) { + emit('resize-end', e, startSize.value) + } } const cleanup = useEventListener(target, 'mousedown', onMousedown) diff --git a/packages/components/drawer/src/drawer.ts b/packages/components/drawer/src/drawer.ts index 77198bea21..2ba02c7bde 100644 --- a/packages/components/drawer/src/drawer.ts +++ b/packages/components/drawer/src/drawer.ts @@ -32,4 +32,14 @@ export const drawerProps = buildProps({ export type DrawerProps = ExtractPropTypes export type DrawerPropsPublic = __ExtractPublicPropTypes -export const drawerEmits = dialogEmits +export const drawerEmits = { + ...dialogEmits, + 'resize-start': (event: MouseEvent, size: number) => + event instanceof MouseEvent && typeof size === 'number', + resize: (event: MouseEvent, size: number) => + event instanceof MouseEvent && typeof size === 'number', + 'resize-end': (event: MouseEvent, size: number) => + event instanceof MouseEvent && typeof size === 'number', +} + +export type DrawerEmits = typeof drawerEmits diff --git a/packages/components/drawer/src/drawer.vue b/packages/components/drawer/src/drawer.vue index 985c9823b6..6c6207b9e2 100644 --- a/packages/components/drawer/src/drawer.vue +++ b/packages/components/drawer/src/drawer.vue @@ -123,7 +123,7 @@ defineOptions({ }) const props = defineProps(drawerProps) -defineEmits(drawerEmits) +const emit = defineEmits(drawerEmits) const slots = useSlots() useDeprecated( @@ -160,7 +160,7 @@ const { handleClose, } = useDialog(props, drawerRef) -const { isHorizontal, size, isResizing } = useResizable(props, draggerRef) +const { isHorizontal, size, isResizing } = useResizable(props, draggerRef, emit) const penetrable = computed(() => props.modalPenetrable && !props.modal)