MagicDraggable
MagicDraggable is a touch enabled, draggable component that can snap to configurable points inside its container.
<template>
<magic-draggable id="magic-draggable-demo" class="bg-surface-base rounded-md">
<div
class="bg-surface-high flex aspect-square w-32 items-center justify-center rounded-md"
>
<m-badge>Drag me</m-badge>
</div>
</magic-draggable>
</template>
<style>
[data-id='magic-draggable-demo'] {
--magic-draggable-position: relative;
--magic-draggable-z-index: 50;
--magic-draggable-height: 20rem;
}
</style>Overview
Anatomy
<template>
<magic-draggable id="your-draggable-id">
<!-- your content -->
</magic-draggable>
</template>
<script setup>
const { open } = useMagicDraggable('your-draggable-id')
</script>Installation
CLI
Add @maas/vue-equipment to your dependencies.
pnpm install @maas/vue-equipmentnpm install @maas/vue-equipmentyarn add @maas/vue-equipmentbun install @maas/vue-equipmentVue
If you are using Vue, import and add MagicDraggablePlugin to your app.
import { createApp } from 'vue'
import { MagicDraggablePlugin } from '@maas/vue-equipment/plugins/MagicDraggable'
const app = createApp({})
app.use(MagicDraggablePlugin)Nuxt
The component is available as a Nuxt module. In your Nuxt config file add @maas/vue-equipment/nuxt to your modules and add MagicDraggable to the plugins in your configuration.
export default defineNuxtConfig({
modules: ['@maas/vue-equipment/nuxt'],
vueEquipment: {
plugins: ['MagicDraggable'],
},
})Direct Import
If you prefer a more granular approach, the component can also be directly imported into any Vue component.
<script setup>
import { MagicDraggable } from '@maas/vue-equipment/plugins/MagicDraggable'
</script>
<template>
<magic-draggable id="your-draggable-id">
<!-- your content -->
</magic-draggable>
</template>Composable
In order to interact with the component from anywhere within your app, we provide a useMagicDraggable composable. Import it directly when needed.
import { useMagicDraggable } from '@maas/vue-equipment/plugins/MagicDraggable'
const { snapTo } = useMagicDraggable('your-draggable-id')
function handleClick() {
snapTo('top-right')
}TIP
If you have installed the component as a Nuxt module, the composable will be auto-imported and is automatically available in your Nuxt app.
Peer Dependencies
If you haven’t installed the required peer dependencies automatically, you’ll need to install the following packages manually.
| Package |
|---|
@nuxt/kit |
@vueuse/core |
defu |
Installation
pnpm install @nuxt/kit @vueuse/core defunpm install @nuxt/kit @vueuse/core defuyarn add @nuxt/kit @vueuse/core defubun install @nuxt/kit @vueuse/core defuAPI Reference
Props
The component comes with a simple set of props. Only the id is required.
| Prop | Type | Required |
|---|---|---|
MaybeRef<string> | true | |
MagicDraggableOptions | false |
Options
To customize the component, override the necessary options. Any custom options will be merged with the default options.
| Option | Type | Default |
|---|---|---|
'div' | ||
number | 128 | |
number | 1.5 | |
number | 250 | |
number | 0 | |
number | 500 | |
'center' | ||
boolean | object | object | |
boolean | true | |
boolean | true | |
boolean | false |
CSS Variables
In order to provide its basic functionality the component comes with some CSS. To ensure that the component is customizable, relevant values are available as CSS variables.
| Variable | Default |
|---|---|
--magic-draggable-position | fixed |
--magic-draggable-width | 100% |
--magic-draggable-height | 100% |
--magic-draggable-z-index | 999 |
--magic-draggable-inset | 0 |
--magic-draggable-cursor | grab |
--magic-draggable-cursor-dragging | grabbing |
Errors
| Source | ErrorCode | Message |
|---|---|---|
MagicDraggable | missing_snap_point | MagicDraggable must have at least one snap point set |
Caveats
On iOS, a touch that starts near a vertical viewport edge arms the browser’s back and forward navigation swipe — in Safari as well as in installed web apps. A draggable resting near either edge would lose its drag to that gesture, so it cancels touchstart within 44px of the edge, which is the one signal WebKit honors. Canceling also swallows the tap’s synthesized click; the draggable dispatches a replacement when the touch ends within 12px of where it started, so click listeners keep firing. Set preventEdgeNavigation to false to opt out.
Examples
Resize
<template>
<magic-draggable
id="magic-draggable-resize-demo"
class="bg-surface-base rounded-md"
:options="{
threshold: {
lock: 10,
},
}"
>
<div
:class="[
'bg-surface-high flex w-40 items-center justify-center rounded-md transition-all duration-150',
{
'aspect-square': square,
'aspect-[2/3]': !square,
},
]"
>
<m-button @click="toggle">Resize</m-button>
</div>
</magic-draggable>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
const square = ref(true)
function toggle() {
square.value = !square.value
}
</script>
<style>
[data-id='magic-draggable-resize-demo'] {
--magic-draggable-position: relative;
--magic-draggable-z-index: 50;
--magic-draggable-height: 20rem;
}
</style>Snap Points
<template>
<magic-draggable
id="magic-draggable-snap-points-demo"
:options="{
snapPoints: ['top', 'bottom', 'left', 'right'],
initial: { snapPoint: 'top' },
}"
class="bg-surface-base rounded-md"
>
<div
class="bg-surface-high flex aspect-square w-32 items-center justify-center rounded-md"
>
<m-badge>Drag me</m-badge>
</div>
</magic-draggable>
</template>
<style>
[data-id='magic-draggable-snap-points-demo'] {
--magic-draggable-position: relative;
--magic-draggable-z-index: 50;
--magic-draggable-height: 20rem;
}
</style>