Skip to content

Commit

Permalink
feat(comp:modal): add draggable props (#905) (#966)
Browse files Browse the repository at this point in the history
feat(cdk:drag-drop): add handle&reset api
  • Loading branch information
tuchg committed Jun 22, 2022
1 parent 437a6ce commit d81b99f
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 25 deletions.
12 changes: 12 additions & 0 deletions packages/cdk/drag-drop/demo/WithHandle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
order: 2
title:
zh: 通过把手的自由拖拽
en: handle usage
---

## zh

可通过把手对整体进行拖拽

## en
17 changes: 17 additions & 0 deletions packages/cdk/drag-drop/demo/WithHandle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div ref="dragRef" :style="{ border: '1px dashed gray' }">
<IxButton ref="dragHandleRef">局部拖放</IxButton>
通过把柄拖动整体
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable } from '../src/composables/useDraggable'
const dragRef = ref(null)
const dragHandleRef = ref(null)
useDraggable(dragRef, { handle: dragHandleRef, free: true })
</script>
10 changes: 10 additions & 0 deletions packages/cdk/drag-drop/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export function useDraggable(
dragging: ComputedRef<boolean>
// 当前拖动中的位置信息
position: ComputedRef<DragPosition>;
//重置拖拽
reset:()=>void;
// 取消可拖拽设置
stop: () => void;
}

export interface DragPosition {
Expand All @@ -49,6 +53,8 @@ export interface DraggableOptions {
boundary?: BoundaryType
// 允许元素自由拖放
free?: boolean
// 拖拽把手 除此元素外的区域将不再触发拖动
handle?: MaybeElementRef
onDragStart?: DnDEvent
onDrag?: DnDEvent
onDragEnd?: DnDEvent
Expand All @@ -75,6 +81,10 @@ export function useDroppable(
* @param source
*/
connect: (source: MaybeElementRef) => void
/**
* 取消连接拖拽源和放置目标
*/
stop:()=>void
}

export interface DroppableOptions {
Expand Down
73 changes: 65 additions & 8 deletions packages/cdk/drag-drop/src/composables/useDraggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@ import { ComputedRef, computed, onScopeDispose, toRaw, watch } from 'vue'
import { type MaybeElementRef, convertElement, useEventListener } from '@idux/cdk/utils'

import { initContext } from '../utils'
import { useDragFree } from './useDragFree'
import { withDragFree } from './withDragFree'
import { withDragHandle } from './withDragHandle'

export interface DraggableOptions {
/**
* 作为限制拖拽范围的元素,需自定义droppable时需指定为空
*/
boundary?: BoundaryType
/**
* 指定是否可以拖拽
*/
free?: boolean
/**
* 拖拽把手
*/
handle?: MaybeElementRef

onDragStart?: DnDEvent
onDrag?: DnDEvent
onDragEnd?: DnDEvent
Expand All @@ -39,6 +51,8 @@ export function useDraggable(
canDrop: ComputedRef<boolean>
dragging: ComputedRef<boolean>
position: ComputedRef<DragPosition>
reset: () => void
stop: () => void
} {
context = initContext(context)
let firstPosition: DragEvent | null = null
Expand All @@ -60,19 +74,33 @@ export function useDraggable(

// free drag-drop
if (options?.free) {
useDragFree(source, context!)
withDragFree(source, context!)
}

// drag-handle
if (options?.handle) {
withDragHandle(source, options.handle, context!)
}

installBoundary()

sourceElement.setAttribute('draggable', 'true')
sourceElement.classList.add('cdk-draggable')

!options?.handle && sourceElement.classList.add('cdk-draggable')
}

const offDraggable = (sourceElement: HTMLElement) => {
context!.registry.off(sourceElement, 'source')

sourceElement.setAttribute('draggable', 'false')
sourceElement.classList.remove('cdk-draggable')

if (options?.handle) {
convertElement(options.handle)?.classList.remove('cdk-draggable-handle')
} else {
sourceElement.classList.remove('cdk-draggable')
}
}

const installBoundary = () => {
// avoid repeated install listeners
const boundaryElement = getBoundaryElement.value
Expand All @@ -90,10 +118,12 @@ export function useDraggable(
context!.registry.exec(source, 'source', 'dragstart', [evt])
options?.onDragStart?.(evt, toRaw(context!.state.currPosition.value))
}

const onDrag = (evt: DragEvent) => {
context!.registry.exec(source, 'source', 'drag', [evt])
options?.onDrag?.(evt, toRaw(context!.state.currPosition.value))
}

const onDragEnd = (evt: DragEvent) => {
const diffOffset = diff(firstPosition || evt, evt)
// sync status
Expand All @@ -105,13 +135,24 @@ export function useDraggable(
context!.registry.exec(source, 'source', 'dragend', [evt])
options?.onDragEnd?.(evt, toRaw(context!.state.currPosition.value))
}

const diff = (oldT: DragEvent, newT: DragEvent) => {
return {
// fix scroll offset bug
// TODO: the calc way has scale problem
offsetLeft: newT.pageX - oldT.pageX,
offsetTop: newT.pageY - oldT.pageY,
}
}

const onPointerDown = (evt: MouseEvent) => {
context!.registry.exec(source, 'source', 'pointerdown', [evt as DragEvent])
}

const onPointerUp = (evt: MouseEvent) => {
context!.registry.exec(source, 'source', 'pointerup', [evt as DragEvent])
}

const stopWatch = watch(
[() => convertElement(source), () => options?.free, () => options?.boundary, () => context],
([currSourceEl], [prevSourceEl]) => {
Expand All @@ -124,20 +165,36 @@ export function useDraggable(
},
)

const reset = () => {
firstPosition = null
if (options?.free) {
convertElement(source)!.style.transform = ''
}
}

const { stop: stopDragStart } = useEventListener(source, 'dragstart', onDragStart)
const { stop: stopDrag } = useEventListener(source, 'drag', onDrag)
const { stop: stopDragEnd } = useEventListener(source, 'dragend', onDragEnd)
const { stop: stopPointerDown } = useEventListener(source, 'pointerdown', onPointerDown)
const { stop: stopPointerUp } = useEventListener(source, 'pointerup', onPointerUp)

const stop = () => {
offDraggable(convertElement(source)!)
stopWatch()
stopDragStart()
stopDrag()
stopDragEnd()
stopPointerDown()
stopPointerUp()
}

useEventListener(source, 'dragstart', onDragStart)
useEventListener(source, 'drag', onDrag)
useEventListener(source, 'dragend', onDragEnd)

onScopeDispose(stop)

return {
canDrop: computed(() => context!.state.canDrop.value),
dragging: computed(() => context!.state.isDragging.value),
position: computed(() => context!.state.currPosition.value),
reset,
stop,
}
}
26 changes: 16 additions & 10 deletions packages/cdk/drag-drop/src/composables/useDroppable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function useDroppable(
context?: DnDContext,
): {
connect: (source: MaybeElementRef) => void
stop: () => void
} {
context = initContext(context)

Expand Down Expand Up @@ -77,12 +78,6 @@ export function useDroppable(
{ immediate: true, flush: 'post' },
)

const stop = () => {
offDroppable(convertElement(target)!)
stopWatch()
stopConnectWatch()
}

const onDragEnter = (evt: DragEvent) => {
context?.registry.exec(target, 'target', 'dragenter', [evt])
options?.onDragEnter?.(evt, toRaw(context!.state.currPosition.value))
Expand All @@ -103,14 +98,25 @@ export function useDroppable(
options?.onDrop?.(evt, toRaw(context!.state.currPosition.value))
}

useEventListener(target, 'dragenter', onDragEnter)
useEventListener(target, 'dragover', onDragOver)
useEventListener(target, 'dragleave', onDragLeave)
useEventListener(target, 'drop', onDrop)
const { stop: stopDragEnter } = useEventListener(target, 'dragenter', onDragEnter)
const { stop: stopDragOver } = useEventListener(target, 'dragover', onDragOver)
const { stop: stopDragLeave } = useEventListener(target, 'dragleave', onDragLeave)
const { stop: stopDrop } = useEventListener(target, 'drop', onDrop)

const stop = () => {
offDroppable(convertElement(target)!)
stopWatch()
stopConnectWatch()
stopDragEnter()
stopDragOver()
stopDragLeave()
stopDrop()
}

onScopeDispose(stop)

return {
connect,
stop,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { type MaybeElementRef, convertElement } from '@idux/cdk/utils'

import { type DnDContext } from './useDragDropContext'

export function useDragFree(target: MaybeElementRef, context: DnDContext): void {
export function withDragFree(target: MaybeElementRef, context: DnDContext): void {
const sourceElement = convertElement(target)!

context.registry.on(sourceElement, 'source', 'dragend', (evt: DragEvent) => {
Expand Down
32 changes: 32 additions & 0 deletions packages/cdk/drag-drop/src/composables/withDragHandle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @license
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import type { DnDContext } from './useDragDropContext'

import { MaybeElementRef, convertElement } from '@idux/cdk/utils'

export const withDragHandle = (source: MaybeElementRef, handle: MaybeElementRef, context: DnDContext): void => {
let dragTarget: HTMLElement | null = null
const sourceEl = convertElement(source)!
const handleEl = convertElement(handle)!

handleEl.classList?.add('cdk-draggable-handle')

context.registry.on(sourceEl, 'source', 'pointerdown', e => {
dragTarget = e.target as HTMLElement
})

context.registry.on(sourceEl, 'source', 'pointerup', _ => {
dragTarget = null
})

context.registry.on(sourceEl, 'source', 'dragstart', e => {
if (!handleEl.contains(dragTarget)) {
e.preventDefault()
}
})
}
11 changes: 10 additions & 1 deletion packages/cdk/drag-drop/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@ export interface DragPosition {
export type DnDEvent = (evt: DragEvent, position?: DragPosition) => void
export type DnDElement = HTMLElement | Window | EventTarget
export type DnDElementType = 'source' | 'target'
export type DnDEventName = 'drag' | 'dragstart' | 'dragend' | 'dragenter' | 'dragover' | 'dragleave' | 'drop'
export type DnDEventName =
| 'drag'
| 'dragstart'
| 'dragend'
| 'dragenter'
| 'dragover'
| 'dragleave'
| 'drop'
| 'pointerdown'
| 'pointerup'
export type BoundaryType = 'parent' | 'window' | Window | MaybeElementRef | null

export interface DnDState {
Expand Down
2 changes: 1 addition & 1 deletion packages/cdk/drag-drop/style/index.less
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.cdk-draggable {

&[draggable] {
&-handle,&[draggable] {
cursor: move;

> * {
Expand Down
14 changes: 14 additions & 0 deletions packages/components/modal/demo/DraggableModal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title:
zh: 可拖拽的对话框
en: Quickly create
order: 8
---

## zh

启用`draggable`属性,以支持对话框的自由拖放

## en

enable `draggable` attribute, support draggable dialog
14 changes: 14 additions & 0 deletions packages/components/modal/demo/DraggableModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<IxButton mode="primary" @click="visible = !visible">Open modal</IxButton>
<IxModal v-model:visible="visible" draggable header="This is header">
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</IxModal>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const visible = ref(false)
</script>
1 change: 1 addition & 0 deletions packages/components/modal/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ order: 0
| `closeIcon` | 自定义关闭图标 | `string \| VNode \| #closeIcon='{onClose}'` | `close` || - |
| `closeOnEsc` | 是否支持键盘 `esc` 关闭 | `boolean` | `true` || - |
| `destroyOnHide` | 关闭时销毁子元素 | `boolean` | `false` | - | - |
| `draggable` | 是否支持拖放 | `boolean` | `false` | - | - |
| `footer` | 自定义底部按钮 | `boolean \| ModalButtonProps[] \| VNode \| #footer` | `true` | - | 默认会根据 `type` 的不同渲染相应的按钮,如果传入 `false` 则不显示 |
| `header` | 对话框标题 | `string \| HeaderProps \| #header={closable, closeIcon, onClose}` | - | - | - |
| `icon` | 自定义图标 | `string \| VNode \| #icon` | - ||`type` 不为 `default` 时有效 |
Expand Down
Loading

0 comments on commit d81b99f

Please sign in to comment.