vue3-draggable-resizable
Version:
[Vue3 Component] 拖拽缩放并具有自动吸附对齐、参考线等功能
266 lines (260 loc) • 5.65 kB
text/typescript
import { defineComponent, ref, toRef, h, Ref, inject } from 'vue'
import {
initDraggableContainer,
watchProps,
initState,
initParent,
initLimitSizeAndMethods,
initResizeHandle
} from './hooks'
import './index.css'
import { getElSize, filterHandles, IDENTITY } from './utils'
import {
UpdatePosition,
GetPositionStore,
ResizingHandle,
ContainerProvider,
SetMatchedLine
} from './types'
export const ALL_HANDLES: ResizingHandle[] = [
'tl',
'tm',
'tr',
'ml',
'mr',
'bl',
'bm',
'br'
]
const VdrProps = {
initW: {
type: Number,
default: null
},
initH: {
type: Number,
default: null
},
w: {
type: Number,
default: 0
},
h: {
type: Number,
default: 0
},
x: {
type: Number,
default: 0
},
y: {
type: Number,
default: 0
},
draggable: {
type: Boolean,
default: true
},
resizable: {
type: Boolean,
default: true
},
disabledX: {
type: Boolean,
default: false
},
disabledY: {
type: Boolean,
default: false
},
disabledW: {
type: Boolean,
default: false
},
disabledH: {
type: Boolean,
default: false
},
minW: {
type: Number,
default: 20
},
minH: {
type: Number,
default: 20
},
active: {
type: Boolean,
default: false
},
parent: {
type: Boolean,
default: false
},
handles: {
type: Array,
default: ALL_HANDLES,
validator: (handles: ResizingHandle[]) => {
return filterHandles(handles).length === handles.length
}
},
classNameDraggable: {
type: String,
default: 'draggable'
},
classNameResizable: {
type: String,
default: 'resizable'
},
classNameDragging: {
type: String,
default: 'dragging'
},
classNameResizing: {
type: String,
default: 'resizing'
},
classNameActive: {
type: String,
default: 'active'
},
classNameHandle: {
type: String,
default: 'handle'
},
lockAspectRatio: {
type: Boolean,
default: false
}
}
const emits = [
'activated',
'deactivated',
'drag-start',
'resize-start',
'dragging',
'resizing',
'drag-end',
'resize-end',
'update:w',
'update:h',
'update:x',
'update:y',
'update:active'
]
const VueDraggableResizable = defineComponent({
name: 'Vue3DraggableResizable',
props: VdrProps,
emits: emits,
setup(props, { emit }) {
const containerProps = initState(props, emit)
const provideIdentity = inject('identity', Symbol())
let containerProvider: ContainerProvider | null = null
if (provideIdentity === IDENTITY) {
containerProvider = {
updatePosition: inject<UpdatePosition>('updatePosition')!,
getPositionStore: inject<GetPositionStore>('getPositionStore')!,
disabled: inject<Ref<boolean>>('disabled')!,
adsorbParent: inject<Ref<boolean>>('adsorbParent')!,
adsorbCols: inject<number[]>('adsorbCols')!,
adsorbRows: inject<number[]>('adsorbRows')!,
setMatchedLine: inject<SetMatchedLine>('setMatchedLine')!
}
}
const containerRef = ref<HTMLElement>()
const parentSize = initParent(containerRef)
const limitProps = initLimitSizeAndMethods(
props,
parentSize,
containerProps
)
initDraggableContainer(
containerRef,
containerProps,
limitProps,
toRef(props, 'draggable'),
emit,
containerProvider,
parentSize
)
const resizeHandle = initResizeHandle(
containerProps,
limitProps,
parentSize,
props,
emit
)
watchProps(props, limitProps)
return {
containerRef,
containerProvider,
...containerProps,
...parentSize,
...limitProps,
...resizeHandle
}
},
computed: {
style(): { [propName: string]: string } {
return {
width: this.width + 'px',
height: this.height + 'px',
top: this.top + 'px',
left: this.left + 'px'
}
},
klass(): { [propName: string]: string | boolean } {
return {
[this.classNameActive]: this.enable,
[this.classNameDragging]: this.dragging,
[this.classNameResizing]: this.resizing,
[this.classNameDraggable]: this.draggable,
[this.classNameResizable]: this.resizable
}
}
},
mounted() {
if (!this.containerRef) return
this.containerRef.ondragstart = () => false
const { width, height } = getElSize(this.containerRef)
this.setWidth(this.initW === null ? this.w || width : this.initW)
this.setHeight(this.initH === null ? this.h || height : this.initH)
if (this.containerProvider) {
this.containerProvider.updatePosition(this.id, {
x: this.left,
y: this.top,
w: this.width,
h: this.height
})
}
},
render() {
return h(
'div',
{
ref: 'containerRef',
class: ['vdr-container', this.klass],
style: this.style
},
[
this.$slots.default && this.$slots.default(),
...this.handlesFiltered.map((item) =>
h('div', {
class: [
'vdr-handle',
'vdr-handle-' + item,
this.classNameHandle,
`${this.classNameHandle}-${item}`
],
style: { display: this.enable ? 'block' : 'none' },
onMousedown: (e: MouseEvent) =>
this.resizeHandleDown(e, <ResizingHandle>item),
onTouchstart: (e: TouchEvent) =>
this.resizeHandleDown(e, <ResizingHandle>item)
})
)
]
)
}
})
export default VueDraggableResizable