Skip to content

Commit

Permalink
feat(useDraggable): new function (vitest-dev#727)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Sep 8, 2021
1 parent 2c8d4e1 commit a4199cd
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 0 deletions.
8 changes: 8 additions & 0 deletions indexes.json
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,14 @@
"category": "Sensors",
"description": "reactively track [`document.visibilityState`](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState)"
},
{
"name": "useDraggable",
"package": "core",
"component": true,
"docs": "https://vueuse.org/core/useDraggable/",
"category": "Sensors",
"description": "make an element draggable"
},
{
"name": "useElementBounding",
"package": "core",
Expand Down
1 change: 1 addition & 0 deletions packages/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from '../core/useDeviceOrientation/component'
export * from '../core/useDevicePixelRatio/component'
export * from '../core/useDevicesList/component'
export * from '../core/useDocumentVisibility/component'
export * from '../core/useDraggable/component'
export * from '../core/useElementBounding/component'
export * from '../core/useElementSize/component'
export * from '../core/useElementVisibility/component'
Expand Down
1 change: 1 addition & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './useDeviceOrientation'
export * from './useDevicePixelRatio'
export * from './useDevicesList'
export * from './useDocumentVisibility'
export * from './useDraggable'
export * from './useElementBounding'
export * from './useElementSize'
export * from './useElementVisibility'
Expand Down
47 changes: 47 additions & 0 deletions packages/core/useDraggable/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { defineComponent, h, reactive, ref, unref } from 'vue-demi'
import { useDraggable, UseDraggableOptions, useStorage } from '@vueuse/core'

export interface UseDraggableProps extends UseDraggableOptions {
/**
* When provided, use `useStorage` to preserve element's position
*/
storageKey?: string

/**
* Storage type
*
* @default 'local'
*/
storageType?: 'local' | 'session'
}

export const UseDraggable = defineComponent<UseDraggableProps>({
name: 'UseDraggable',
props: [
'storageKey',
'initialValue',
'exact',
'preventDefault',
'pointerTypes',
] as unknown as undefined,
setup(props, { slots }) {
const target = ref()
const initialValue = props.storageKey
? useStorage(
props.storageKey,
unref(props.initialValue) || { x: 0, y: 0 },
props.storageType === 'session' ? sessionStorage : localStorage,
)
: props.initialValue || { x: 0, y: 0 }

const data = reactive(useDraggable(target, {
...props,
initialValue,
}))

return () => {
if (slots.default)
return h('div', { ref: target, style: data.style }, slots.default(data))
}
},
})
50 changes: 50 additions & 0 deletions packages/core/useDraggable/demo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup lang="ts">
import { ref } from 'vue-demi'
import { UseDraggable as Draggable } from './component'
import { useDraggable } from '.'
const el = ref<HTMLElement | null>(null)
const { x, y, style } = useDraggable(el, {
initialValue: { x: window.innerWidth / 4.2, y: 80 },
})
const innerWidth = window.innerWidth
</script>

<template>
<div class="h-30">
<div
ref="el"
p="x-4 y-2"
border="~ gray-400 rounded"
shadow="~ hover:lg"
class="fixed bg-$c-bg select-none cursor-move z-10"
:style="style"
>
👋 Drag me!
<div class="text-sm opacity-50">
I am at {{ Math.round(x) }}, {{ Math.round(y) }}
</div>
</div>

<Draggable
v-slot="{ x, y }"
p="x-4 y-2"
border="~ gray-400 rounded"
shadow="~ hover:lg"
class="fixed bg-$c-bg select-none cursor-move z-10"
:initial-value="{ x: innerWidth / 3.9, y: 150 }"
storage-key="vueuse-draggable-pos"
storage-type="session"
>
Headless component
<div class="text-xs opacity-50">
Position persisted in sessionStorage
</div>
<div class="text-sm opacity-50">
{{ Math.round(x) }}, {{ Math.round(y) }}
</div>
</Draggable>
</div>
</template>
45 changes: 45 additions & 0 deletions packages/core/useDraggable/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
category: Sensors
---

# useDraggable

Make elements draggable.

## Usage

```html
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable } from '.'
const el = ref<HTMLElement | null>(null)
// `style` will be a helper computed for `left: ?px; top: ?px;`
const { x, y, style } = useDraggable(el, {
initialValue: { x: 40, y: 40 },
})
</script>

<template>
<div ref="el" :style="style" style="position: fixed">
Drag me! I am at {{x}}, {{y}}
</div>
</template>
```

## Component

```html
<UseDraggable :initialValue="{ x: 10, y: 10 }" v-slot="{ x, y }">
Drag me! I am at {{x}}, {{y}}
</UseDraggable>
```

For component usage, additional props `storageKey` and `storageType` can be passed to the component and enable the persistence of the element position.

```html
<UseDraggable storage-key="vueuse-draggable" storage-type="session">
Refresh the page and I am still in the same position!
</UseDraggable>
```
120 changes: 120 additions & 0 deletions packages/core/useDraggable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Ref, ref, unref, computed } from 'vue'
import { MaybeRef, toRefs } from '@vueuse/shared'
import { useEventListener } from '../useEventListener'
import { MaybeElementRef } from '../unrefElement'
import { PointerType, Position } from '../_types'
import { defaultWindow } from '../_configurable'

export interface UseDraggableOptions {
/**
* Only start the dragging when click on the element directly
*
* @default false
*/
exact?: MaybeRef<boolean>

/**
* Prevent events defaults
*
* @default false
*/
preventDefault?: MaybeRef<boolean>

/**
* Element to attach `pointermove` and `pointerup` events to.
*
* @default window
*/
draggingElement?: MaybeElementRef

/**
* Pointer types that listen to.
*
* @default ['mouse', 'touch', 'pen']
*/
pointerTypes?: PointerType[]

/**
* Initial position of the element.
*
* @default { x: 0, y: 0}
*/
initialValue?: MaybeRef<Position>

/**
* Callback when the dragging starts. Return `false` to prevent dragging.
*/
onStart?: (position: Position, event: PointerEvent) => void | false

/**
* Callback during dragging.
*/
onMove?: (position: Position, event: PointerEvent) => void
}

/**
* Make elements draggable.
*
* @see https://vueuse.org/useDraggable
* @param el
* @param options
*/
export function useDraggable(el: MaybeElementRef, options: UseDraggableOptions = {}) {
const draggingElement = options.draggingElement ?? defaultWindow
const position: Ref<Position> = ref(options.initialValue ?? { x: 0, y: 0 })
const pressedDelta = ref<Position>()

const filterEvent = (e: PointerEvent) => {
if (options.pointerTypes)
return options.pointerTypes.includes(e.pointerType as PointerType)
return true
}
const preventDefault = (e: PointerEvent) => {
if (unref(options.preventDefault))
e.preventDefault()
}
const start = (e: PointerEvent) => {
if (!filterEvent(e))
return
if (unref(options.exact) && e.target !== el.value)
return
const react = el.value!.getBoundingClientRect()
const pos = {
x: e.pageX - react.left,
y: e.pageY - react.top,
}
if (options.onStart?.(pos, e) === false)
return
pressedDelta.value = pos
preventDefault(e)
}
const move = (e: PointerEvent) => {
if (!filterEvent(e))
return
if (!pressedDelta.value)
return
position.value = {
x: e.pageX - pressedDelta.value.x,
y: e.pageY - pressedDelta.value.y,
}
options.onMove?.(position.value, e)
preventDefault(e)
}
const end = (e: PointerEvent) => {
if (!filterEvent(e))
return
pressedDelta.value = undefined
preventDefault(e)
}

useEventListener(el, 'pointerdown', start, true)
useEventListener(draggingElement, 'pointermove', move, true)
useEventListener(draggingElement, 'pointerup', end, true)

return {
...toRefs(position),
position,
isDragging: computed(() => !!pressedDelta.value),
style: computed(() => `left:${position.value.x}px;top:${position.value.y}px;`),
}
}
1 change: 1 addition & 0 deletions packages/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
- [`useDevicePixelRatio`](https://vueuse.org/core/useDevicePixelRatio/) — reactively track [`window.devicePixelRatio`](https://developer.mozilla.org/ru/docs/Web/API/Window/devicePixelRatio)
- [`useDevicesList`](https://vueuse.org/core/useDevicesList/) — reactive [enumerateDevices](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices) listing avaliable input/output devices
- [`useDocumentVisibility`](https://vueuse.org/core/useDocumentVisibility/) — reactively track [`document.visibilityState`](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState)
- [`useDraggable`](https://vueuse.org/core/useDraggable/) — make an element draggable
- [`useElementBounding`](https://vueuse.org/core/useElementBounding/) — reactive [bounding box](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) of an HTML element
- [`useElementSize`](https://vueuse.org/core/useElementSize/) — reactive size of an HTML element
- [`useElementVisibility`](https://vueuse.org/core/useElementVisibility/) — tracks the visibility of an element within the viewport
Expand Down
1 change: 1 addition & 0 deletions packages/windi.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default defineConfig({
'.vitepress/theme/**/*.vue',
],
},
attributify: true,
theme: {
extend: {
colors: {
Expand Down

0 comments on commit a4199cd

Please sign in to comment.