Skip to content

Commit

Permalink
feat(comp:modal): add draggable props (IDuxFE#905)
Browse files Browse the repository at this point in the history
feat(cdk:drag-drop): add handle api
  • Loading branch information
tuchg committed Jun 21, 2022
1 parent 5db6616 commit 5919e7d
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 6 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>
2 changes: 2 additions & 0 deletions packages/cdk/drag-drop/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export interface DraggableOptions {
boundary?: BoundaryType
// 允许元素自由拖放
free?: boolean
// 拖拽把手 除此元素外的区域将不再触发拖动
handle?: MaybeElementRef
onDragStart?: DnDEvent
onDrag?: DnDEvent
onDragEnd?: DnDEvent
Expand Down
37 changes: 34 additions & 3 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 Down Expand Up @@ -60,11 +72,19 @@ 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) => {
Expand All @@ -73,6 +93,7 @@ export function useDraggable(
sourceElement.setAttribute('draggable', 'false')
sourceElement.classList.remove('cdk-draggable')
}

const installBoundary = () => {
// avoid repeated install listeners
const boundaryElement = getBoundaryElement.value
Expand All @@ -90,10 +111,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 +128,20 @@ 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 onMouseDown = (evt: MouseEvent) => {
context!.registry.exec(source, 'source', 'mousedown', [evt as DragEvent])
}

const stopWatch = watch(
[() => convertElement(source), () => options?.free, () => options?.boundary, () => context],
([currSourceEl], [prevSourceEl]) => {
Expand All @@ -132,6 +162,7 @@ export function useDraggable(
useEventListener(source, 'dragstart', onDragStart)
useEventListener(source, 'drag', onDrag)
useEventListener(source, 'dragend', onDragEnd)
useEventListener(source, 'mousedown', onMouseDown)

onScopeDispose(stop)

Expand Down
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
28 changes: 28 additions & 0 deletions packages/cdk/drag-drop/src/composables/withDragHandle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @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
const sourceEl = convertElement(source)!
const handleEl = convertElement(handle)!

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

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

context.registry.on(sourceEl, 'source', 'dragstart', e => {
if (!handleEl.contains(dragTarget)) {
e.preventDefault()
}
})
}
10 changes: 9 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,15 @@ 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'
| 'mousedown'
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</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
15 changes: 15 additions & 0 deletions packages/components/modal/src/ModalWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import {
inject,
onBeforeUnmount,
onMounted,
onUnmounted,
ref,
watch,
watchEffect,
} from 'vue'

import { isFunction, isString } from 'lodash-es'

import { useDraggable } from '@idux/cdk/drag-drop'
import { callEmit, convertCssPixel, getOffset } from '@idux/cdk/utils'
import { ɵFooter } from '@idux/components/_private/footer'
import { ɵHeader, type ɵHeaderProps } from '@idux/components/_private/header'
Expand Down Expand Up @@ -85,6 +88,7 @@ export default defineComponent({
})

const wrapperRef = ref<HTMLDivElement>()
const headerRef = ref<HTMLDivElement>()
const modalRef = ref<HTMLDivElement>()
const sentinelStartRef = ref<HTMLDivElement>()
const sentinelEndRef = ref<HTMLDivElement>()
Expand All @@ -108,6 +112,16 @@ export default defineComponent({

onMounted(() => watchVisibleChange(props, wrapperRef, sentinelStartRef, mask))

const stopWatch = watchEffect(() => {
if (props.draggable) {
useDraggable(wrapperRef, { handle: headerRef, free: true })
}
})

onUnmounted(() => {
stopWatch()
})

return () => {
const prefixCls = mergedPrefixCls.value

Expand Down Expand Up @@ -151,6 +165,7 @@ export default defineComponent({
<div ref={sentinelStartRef} tabindex={0} class={`${prefixCls}-sentinel`} aria-hidden={true}></div>
<div class={`${prefixCls}-content`}>
<ɵHeader
ref={headerRef}
v-slots={slots}
closable={closable.value}
closeIcon={closeIcon.value}
Expand Down
1 change: 1 addition & 0 deletions packages/components/modal/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const modalProps = {
width: IxPropTypes.oneOfType([String, Number]),
wrapperClassName: IxPropTypes.string,
zIndex: IxPropTypes.number,
draggable: { type: Boolean, default: false },

// events
'onUpdate:visible': IxPropTypes.emit<(visible: boolean) => void>(),
Expand Down

0 comments on commit 5919e7d

Please sign in to comment.