├── packages ├── solid │ ├── demo │ │ ├── src │ │ │ ├── App.module.css │ │ │ ├── global.d.ts │ │ │ ├── index.tsx │ │ │ └── App.tsx │ │ ├── vite.config.ts │ │ ├── tsconfig.json │ │ ├── index.html │ │ ├── package.json │ │ └── README.md │ ├── tsup.config.ts │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── react │ ├── demo │ │ ├── src │ │ │ ├── vite-env.d.ts │ │ │ ├── main.tsx │ │ │ ├── index.css │ │ │ └── App.tsx │ │ ├── .gitignore │ │ ├── vite.config.ts │ │ ├── package.json │ │ ├── index.html │ │ └── tsconfig.json │ ├── tsup.config.ts │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ └── index.ts │ └── README.md ├── vanilla │ ├── demo │ │ ├── src │ │ │ ├── vite-env.d.ts │ │ │ ├── style.css │ │ │ └── main.ts │ │ ├── .gitignore │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── index.html │ │ └── favicon.svg │ ├── tsup.config.ts │ ├── demo-umd │ │ ├── package.json │ │ ├── src │ │ │ ├── style.css │ │ │ └── main.js │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ └── index.html │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ ├── package.json │ └── README.md ├── core │ ├── tsup.config.ts │ ├── tsconfig.json │ └── package.json ├── vue │ ├── tsup.config.ts │ ├── demo │ │ ├── src │ │ │ ├── main.ts │ │ │ ├── components │ │ │ │ └── HelloWorld.vue │ │ │ ├── env.d.ts │ │ │ └── App.vue │ │ ├── tsconfig.node.json │ │ ├── vite.config.ts │ │ ├── index.html │ │ ├── .gitignore │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── README.md │ │ └── CHANGELOG.md │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ ├── package.json │ └── README.md ├── svelte │ ├── tsup.config.ts │ ├── demo │ │ ├── .gitignore │ │ ├── vite.config.ts │ │ ├── src │ │ │ ├── app.html │ │ │ ├── app.d.ts │ │ │ └── routes │ │ │ │ └── 2 │ │ │ │ └── +page.svelte │ │ ├── svelte.config.js │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── README.md │ ├── tests │ │ ├── components │ │ │ ├── CancelDraggable.svelte │ │ │ ├── HandleDraggable.svelte │ │ │ └── Draggable.svelte │ │ ├── testHelpers.ts │ │ ├── CancelDraggable.spec.ts │ │ └── HandleDraggable.spec.ts │ ├── vitest.config.ts │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── src │ │ └── index.ts └── config │ └── index.ts ├── docs ├── .npmrc ├── src │ ├── css │ │ ├── breakpoints.scss │ │ └── globals.scss │ ├── pages │ │ └── docs │ │ │ ├── index.astro │ │ │ ├── migrating │ │ │ └── svelte-drag.mdx │ │ │ └── vue.mdx │ ├── components │ │ ├── home │ │ │ ├── features │ │ │ │ ├── feature-box.scss │ │ │ │ ├── feature-rich │ │ │ │ │ └── FeatureRichFeature.svelte │ │ │ │ ├── ssr-friendly │ │ │ │ │ └── SSRFriendlyFeature.svelte │ │ │ │ ├── bundle-sizes │ │ │ │ │ └── BundleSizeFeature.svelte │ │ │ │ └── multiple-frameworks │ │ │ │ │ └── MultipleFrameworksFeature.svelte │ │ │ ├── RingSVG.svelte │ │ │ ├── ExploreFrameworks.svelte │ │ │ └── ScrollDownIndicator.svelte │ │ ├── options │ │ │ ├── OptionsCode.astro │ │ │ ├── OptionsExample.astro │ │ │ └── Options.astro │ │ ├── MetaThemeColor.svelte │ │ ├── Footer.svelte │ │ ├── MobileNav.svelte │ │ ├── ThemeSwitcher.svelte │ │ └── PawCursor.svelte │ ├── documentation │ │ ├── options │ │ │ ├── applyUserSelectHack │ │ │ │ ├── user-select.mixin.scss │ │ │ │ ├── UserSelect.example.svelte │ │ │ │ ├── NoUserSelect.example.svelte │ │ │ │ └── +option.mdx │ │ │ ├── axis │ │ │ │ ├── XAxis.example.svelte │ │ │ │ ├── YAxis.example.svelte │ │ │ │ ├── BothAxis.example.svelte │ │ │ │ └── NoneAxis.example.svelte │ │ │ ├── grid │ │ │ │ ├── RectangleGrid.example.svelte │ │ │ │ ├── SquareGrid.example.svelte │ │ │ │ ├── InactiveGrid.example.svelte │ │ │ │ └── +option.mdx │ │ │ ├── onDrag │ │ │ │ └── +option.mdx │ │ │ ├── onDragEnd │ │ │ │ └── +option.mdx │ │ │ ├── onDragStart │ │ │ │ └── +option.mdx │ │ │ ├── ignoreMultitouch │ │ │ │ ├── Multitouch.example.svelte │ │ │ │ ├── IgnoredMultitouch.example.svelte │ │ │ │ └── +option.mdx │ │ │ ├── defaultClassDragging │ │ │ │ └── +option.mdx │ │ │ ├── disabled │ │ │ │ ├── Disabled.example.svelte │ │ │ │ └── +option.mdx │ │ │ ├── defaultPosition │ │ │ │ ├── DefaultPosition.example.svelte │ │ │ │ └── +option.mdx │ │ │ ├── handle │ │ │ │ ├── handle-base.mixin.scss │ │ │ │ ├── SingleHandleSelector.example.svelte │ │ │ │ ├── MultipleHandleSelector.example.svelte │ │ │ │ ├── SingleHandleElement.example.svelte │ │ │ │ └── MultipleHandleElement.example.svelte │ │ │ ├── threshold │ │ │ │ ├── Delay.example.svelte │ │ │ │ ├── Distance.example.svelte │ │ │ │ └── +option.mdx │ │ │ ├── defaultClassDragged │ │ │ │ └── +option.mdx │ │ │ ├── cancel │ │ │ │ ├── cancel-base.mixin.scss │ │ │ │ ├── SingleCancelSelector.example.svelte │ │ │ │ ├── MultipleCancelSelector.example.svelte │ │ │ │ ├── SingleCancelElement.example.svelte │ │ │ │ └── MultipleCancelElement.example.svelte │ │ │ ├── bounds │ │ │ │ ├── BodyBounds.example.svelte │ │ │ │ ├── ParentBounds.example.svelte │ │ │ │ └── CoordinateBounds.example.svelte │ │ │ ├── gpuAcceleration │ │ │ │ ├── NoAcceleration.example.svelte │ │ │ │ ├── Acceleration.example.svelte │ │ │ │ └── +option.mdx │ │ │ ├── defaultClass │ │ │ │ └── +option.mdx │ │ │ ├── legacyTranslate │ │ │ │ ├── TranslateGPU.example.svelte │ │ │ │ ├── TranslateNoGPU.example.svelte │ │ │ │ ├── LegacyTranslateGPU.example.svelte │ │ │ │ └── LegacyTranslateNoGPU.example.svelte │ │ │ ├── transform │ │ │ │ ├── ReturnTransform.example.svelte │ │ │ │ └── ManualTransform.example.svelte │ │ │ ├── position │ │ │ │ ├── Position.example.svelte │ │ │ │ └── DisabledPosition.example.svelte │ │ │ └── recomputeBounds │ │ │ │ └── +option.mdx │ │ ├── installation.mdx │ │ └── exported-types.mdx │ ├── helpers │ │ ├── constants.ts │ │ ├── framework-icons.ts │ │ └── utils.ts │ ├── state │ │ ├── user-preferences.svelte.ts │ │ ├── auto-destroy-effect-root.svelte.ts │ │ └── persisted.svelte.ts │ ├── layouts │ │ ├── ThemeWatcher.svelte │ │ └── MainDocsLayout.astro │ ├── actions │ │ ├── typingEffect.ts │ │ └── portal.ts │ ├── data │ │ └── sizes.json │ ├── auto-imports.d.ts │ ├── env.d.ts │ └── worklet │ │ └── squircle.js ├── public │ ├── banner.png │ ├── favicon.png │ ├── logo-2x.png │ ├── home │ │ ├── metaframeworks-dark.webp │ │ └── metaframeworks-light.webp │ ├── logos │ │ ├── vue.svg │ │ ├── vanilla.svg │ │ ├── svelte.svg │ │ ├── solid.svg │ │ └── react.svg │ ├── favicon.svg │ └── logo.svg ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json ├── svelte.config.js ├── postcss.config.cjs ├── .gitignore ├── .prettierrc.cjs ├── tsconfig.json ├── package.json ├── README.md └── astro.config.ts ├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ ├── cr.yml │ └── release.yml ├── .vscode └── settings.json ├── .prettierrc ├── pnpm-workspace.yaml ├── .changeset ├── config.json └── README.md ├── package.json ├── LICENSE.txt ├── scripts └── gather-sizes.ts └── .all-contributorsrc /packages/solid/demo/src/App.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/solid/demo/src/global.d.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | # Expose Astro dependencies for `pnpm` users 2 | -------------------------------------------------------------------------------- /docs/src/css/breakpoints.scss: -------------------------------------------------------------------------------- 1 | @import './include-media'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .DS_Store 4 | .env 5 | .pnpm-store/ 6 | -------------------------------------------------------------------------------- /packages/react/demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/vanilla/demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: PuruVJ 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 100, 4 | "useTabs": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PuruVJ/neodrag/HEAD/docs/public/banner.png -------------------------------------------------------------------------------- /docs/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PuruVJ/neodrag/HEAD/docs/public/favicon.png -------------------------------------------------------------------------------- /docs/public/logo-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PuruVJ/neodrag/HEAD/docs/public/logo-2x.png -------------------------------------------------------------------------------- /packages/react/demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/** 3 | - '!**/test/**' 4 | - docs 5 | - config 6 | -------------------------------------------------------------------------------- /packages/core/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { core_config } from '../config'; 2 | 3 | export default core_config; 4 | -------------------------------------------------------------------------------- /packages/vue/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { core_config } from '../config'; 2 | 3 | export default core_config({}); 4 | -------------------------------------------------------------------------------- /packages/react/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { core_config } from '../config'; 2 | 3 | export default core_config({}); 4 | -------------------------------------------------------------------------------- /packages/svelte/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { core_config } from '../config'; 2 | 3 | export default core_config({}); 4 | -------------------------------------------------------------------------------- /docs/src/pages/docs/index.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /docs/public/home/metaframeworks-dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PuruVJ/neodrag/HEAD/docs/public/home/metaframeworks-dark.webp -------------------------------------------------------------------------------- /docs/public/home/metaframeworks-light.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PuruVJ/neodrag/HEAD/docs/public/home/metaframeworks-light.webp -------------------------------------------------------------------------------- /packages/vue/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | createApp(App).mount('#app'); 5 | -------------------------------------------------------------------------------- /docs/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@astrojs/svelte'; 2 | 3 | export default { 4 | preprocess: vitePreprocess(), 5 | }; 6 | -------------------------------------------------------------------------------- /packages/svelte/demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /docs/src/components/home/features/feature-box.scss: -------------------------------------------------------------------------------- 1 | @mixin paragraph { 2 | font-size: clamp(1rem, 2vw, 1.3rem); 3 | 4 | // max-width: clamp(20ch, 80vw, 100ch); 5 | } 6 | -------------------------------------------------------------------------------- /packages/vanilla/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { core_config } from '../config'; 2 | 3 | export default core_config({ 4 | includeUMD: true, 5 | globalName: 'NeoDrag', 6 | }); 7 | -------------------------------------------------------------------------------- /packages/vue/demo/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/vue/demo/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /packages/solid/demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web'; 3 | 4 | import App from './App'; 5 | 6 | render(() => , document.getElementById('root') as HTMLElement); 7 | -------------------------------------------------------------------------------- /packages/react/demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/svelte/demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /docs/src/documentation/options/applyUserSelectHack/user-select.mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin userSelectKBD() { 2 | display: flex; 3 | align-items: center; 4 | gap: 1ch; 5 | 6 | padding: 0.2rem 0.2rem; 7 | 8 | line-height: 1; 9 | } 10 | -------------------------------------------------------------------------------- /packages/vanilla/demo-umd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": {}, 6 | "devDependencies": {}, 7 | "dependencies": { 8 | "@neodrag/vanilla": "workspace:*" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/vue/demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue({ reactivityTransform: true })], 7 | }); 8 | -------------------------------------------------------------------------------- /docs/src/documentation/options/axis/XAxis.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Horizontal 6 | -------------------------------------------------------------------------------- /docs/src/documentation/options/axis/YAxis.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Vertical 6 | -------------------------------------------------------------------------------- /docs/src/documentation/options/axis/BothAxis.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Both directions 6 | -------------------------------------------------------------------------------- /docs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/documentation/options/axis/NoneAxis.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | None axis: Won't drag 6 | -------------------------------------------------------------------------------- /packages/vanilla/demo/src/style.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /docs/src/documentation/options/grid/RectangleGrid.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Snaps to 72x91 grid 6 | -------------------------------------------------------------------------------- /docs/src/documentation/options/grid/SquareGrid.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Snaps to 50x50 grid 6 | -------------------------------------------------------------------------------- /docs/src/documentation/options/onDrag/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: onDrag 3 | type: '(data: DragEventData) => void' 4 | defaultValue: 'undefined' 5 | --- 6 | 7 | export const shortDescription = 'Fires when dragging is going on'; 8 | 9 | {shortDescription}. 10 | -------------------------------------------------------------------------------- /docs/src/documentation/options/onDragEnd/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: onDragEnd 3 | type: '(data: DragEventData) => void' 4 | defaultValue: 'undefined' 5 | --- 6 | 7 | export const shortDescription = 'Fires when dragging stops'; 8 | 9 | {shortDescription}. 10 | -------------------------------------------------------------------------------- /packages/svelte/tests/components/CancelDraggable.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | This will drag! 7 |
You shall not drag!!
8 |
9 | -------------------------------------------------------------------------------- /packages/svelte/tests/components/HandleDraggable.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | You shall not drag!! 7 |
This will drag!
8 |
9 | -------------------------------------------------------------------------------- /packages/vanilla/demo-umd/src/style.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /docs/src/documentation/options/onDragStart/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: onDragStart 3 | type: '(data: DragEventData) => void' 4 | defaultValue: 'undefined' 5 | --- 6 | 7 | export const shortDescription = 'Fires when dragging start'; 8 | 9 | {shortDescription}. 10 | -------------------------------------------------------------------------------- /docs/src/helpers/constants.ts: -------------------------------------------------------------------------------- 1 | export const FRAMEWORKS = [ 2 | { name: 'svelte' }, 3 | { name: 'react' }, 4 | { name: 'vue' }, 5 | { name: 'solid' }, 6 | { name: 'vanilla' }, 7 | ] as const; 8 | 9 | export type Framework = (typeof FRAMEWORKS)[number]['name']; 10 | -------------------------------------------------------------------------------- /docs/src/documentation/options/grid/InactiveGrid.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Snaps to 0x0 grid - Won't drag at all 6 | -------------------------------------------------------------------------------- /docs/src/documentation/options/ignoreMultitouch/Multitouch.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Multi touch allowed 6 | -------------------------------------------------------------------------------- /docs/src/documentation/options/ignoreMultitouch/IgnoredMultitouch.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | Multi touch ignored 6 | -------------------------------------------------------------------------------- /docs/public/logos/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/react/demo/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /packages/solid/demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | 4 | export default defineConfig({ 5 | plugins: [solidPlugin()], 6 | build: { 7 | target: 'esnext', 8 | polyfillDynamicImport: false, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/svelte/demo/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/src/documentation/options/defaultClassDragging/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: defaultClassDragging 3 | type: 'string' 4 | defaultValue: "'neodrag-dragging'" 5 | --- 6 | 7 | export const shortDescription = 'Class to apply on the parent element when it is dragging'; 8 | 9 | {shortDescription} 10 | -------------------------------------------------------------------------------- /docs/src/documentation/options/disabled/Disabled.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Disabled. Won't drag, won't trigger any events 7 | 8 | -------------------------------------------------------------------------------- /docs/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | const postcssJitProps = require('postcss-jit-props'); 3 | const OpenProps = require('open-props'); 4 | 5 | module.exports = { 6 | // only vars used are in build output 7 | plugins: [postcssJitProps(OpenProps), autoprefixer()], 8 | }; 9 | -------------------------------------------------------------------------------- /docs/src/documentation/options/defaultPosition/DefaultPosition.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Shifted by (100, 40) 7 | 8 | -------------------------------------------------------------------------------- /docs/src/documentation/options/handle/handle-base.mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin handle { 2 | background-color: hsla(var(--app-color-primary-contrast-hsl), 1); 3 | 4 | padding: 0.25rem 0.25rem; 5 | 6 | border-radius: 4px; 7 | } 8 | 9 | @mixin box { 10 | color: hsla(var(--app-color-primary-contrast-hsl), 0.7); 11 | } 12 | -------------------------------------------------------------------------------- /packages/svelte/demo/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | // and what to do when importing types 4 | declare namespace App { 5 | // interface Locals {} 6 | // interface PageData {} 7 | // interface Error {} 8 | // interface Platform {} 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/documentation/options/threshold/Delay.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 200ms delay 13 | 14 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | 22 | .astro -------------------------------------------------------------------------------- /docs/src/documentation/options/threshold/Distance.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 100px distance 13 | 14 | -------------------------------------------------------------------------------- /packages/solid/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { core_config } from '../config'; 2 | 3 | export default core_config({ 4 | dtsBanner: `import 'solid-js'; 5 | 6 | declare module 'solid-js' { 7 | namespace JSX { 8 | interface Directives { 9 | draggable: DragOptions; 10 | } 11 | } 12 | } 13 | `, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/svelte/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | import { defineConfig } from 'vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [svelte({ hot: !process.env.VITEST })], 7 | test: { 8 | global: true, 9 | environment: 'jsdom', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "PuruVJ/neodrag" }], 4 | "commit": false, 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "main", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } 11 | -------------------------------------------------------------------------------- /docs/src/state/user-preferences.svelte.ts: -------------------------------------------------------------------------------- 1 | import { browser } from '$helpers/utils'; 2 | import { persisted } from './persisted.svelte'; 3 | 4 | export type Theme = 'light' | 'dark'; 5 | export const theme = persisted( 6 | 'neodrag:theme', 7 | browser ? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : 'light', 8 | ); 9 | -------------------------------------------------------------------------------- /docs/src/documentation/options/defaultClassDragged/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: defaultClassDragged 3 | type: 'string' 4 | defaultValue: "'neodrag-dragged'" 5 | --- 6 | 7 | export const shortDescription = 8 | 'Class to apply on the parent element if it has been dragged at least once'; 9 | 10 | {shortDescription}. Removed once dragging stops. 11 | -------------------------------------------------------------------------------- /docs/src/documentation/installation.mdx: -------------------------------------------------------------------------------- 1 | import { Code } from 'astro/components'; 2 | 3 | ## Installation 4 | 5 | {/* prettier-ignore */} 6 | 17 | -------------------------------------------------------------------------------- /packages/vue/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite App 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/svelte/demo/svelte.config.js: -------------------------------------------------------------------------------- 1 | import preprocess from 'svelte-preprocess'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | // Consult https://github.com/sveltejs/svelte-preprocess 6 | // for more information about preprocessors 7 | preprocess: preprocess(), 8 | 9 | kit: {}, 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /docs/src/documentation/options/cancel/cancel-base.mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin cancelExample { 2 | text-decoration: line-through; 3 | color: hsla(var(--app-color-primary-contrast-hsl), 0.7); 4 | 5 | background-color: hsla(var(--app-color-primary-contrast-hsl), 0.1); 6 | 7 | padding: 0.25rem 0.25rem; 8 | 9 | border-radius: 4px; 10 | 11 | cursor: not-allowed; 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/layouts/ThemeWatcher.svelte: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /docs/src/documentation/options/bounds/BodyBounds.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | Can't go outside <body> 10 | 11 | -------------------------------------------------------------------------------- /docs/src/actions/typingEffect.ts: -------------------------------------------------------------------------------- 1 | export function typingEffect(element: HTMLElement, speed: number) { 2 | const text = element.innerHTML; 3 | 4 | element.innerHTML = ''; 5 | 6 | let i = 0; 7 | const timer = setInterval(() => { 8 | if (i < text.length) element.append(text.charAt(i++)); 9 | }, speed); 10 | 11 | return { 12 | destroy: () => clearInterval(timer), 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /docs/src/data/sizes.json: -------------------------------------------------------------------------------- 1 | { 2 | "react": { 3 | "size": 2.18, 4 | "version": "2.2.0" 5 | }, 6 | "solid": { 7 | "size": 1.98, 8 | "version": "2.2.0" 9 | }, 10 | "svelte": { 11 | "size": 1.94, 12 | "version": "2.2.0" 13 | }, 14 | "vanilla": { 15 | "size": 2, 16 | "version": "2.2.0" 17 | }, 18 | "vue": { 19 | "size": 1.98, 20 | "version": "2.2.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/vue/demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/vue/demo/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module '*.vue' { 5 | import type { DefineComponent } from 'vue'; 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 7 | const component: DefineComponent<{}, {}, any>; 8 | export default component; 9 | } 10 | -------------------------------------------------------------------------------- /packages/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ESNext", 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "declarationDir": "dist/", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "moduleResolution": "Bundler", 11 | "types": ["vitest/globals", "svelte"] 12 | }, 13 | "files": ["./src/index.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/vanilla/demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/vanilla/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "typescript": "^5.3.3", 12 | "vite": "^5.0.10" 13 | }, 14 | "dependencies": { 15 | "@neodrag/vanilla": "workspace:*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/vanilla/demo-umd/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /docs/src/documentation/options/gpuAcceleration/NoAcceleration.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | GPU acceleration off 7 | 8 | {#snippet pos(x, y)} 9 | transform: translate({x}px, {y}px) 10 | {/snippet} 11 | 12 | -------------------------------------------------------------------------------- /docs/src/documentation/options/gpuAcceleration/Acceleration.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | GPU acceleration on 7 | 8 | {#snippet pos(x, y)} 9 | transform: translate3d({x}px, {y}px, 0) 10 | {/snippet} 11 | 12 | -------------------------------------------------------------------------------- /docs/src/documentation/options/defaultClass/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: defaultClass 3 | type: 'string' 4 | defaultValue: 'undefined' 5 | --- 6 | 7 | export const shortDescription = 'Class to apply to draggable element.'; 8 | 9 | Class to apply to draggable element. 10 | 11 | ::: warning 12 | 13 | If `handle` is provided, it will still apply class on the parent element, **NOT** the handle. 14 | 15 | ::: 16 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "target": "ESNext", 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "declarationDir": "dist/", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "moduleResolution": "bundler", 11 | "isolatedDeclarations": true, 12 | "types": ["vitest/globals"] 13 | }, 14 | "files": ["./src/index.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/react/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "private": true, 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@neodrag/react": "workspace:*" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-react": "^4.2.1", 15 | "typescript": "^5.3.3", 16 | "vite": "^5.0.10" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/solid/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite/client"], 12 | "noEmit": true, 13 | "isolatedModules": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/src/documentation/options/legacyTranslate/TranslateGPU.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Modern translate with GPU acceleration 7 | 8 | {#snippet pos(x, y)} 9 | translate: {x}px {y}px 1px 10 | {/snippet} 11 | 12 | -------------------------------------------------------------------------------- /docs/src/documentation/options/legacyTranslate/TranslateNoGPU.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Modern translate with no GPU acceleration 7 | 8 | {#snippet pos(x, y)} 9 | translate: {x}px {y}px 10 | {/snippet} 11 | 12 | -------------------------------------------------------------------------------- /docs/src/documentation/options/transform/ReturnTransform.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | { 8 | return `translate(${offsetX + 50}px, ${offsetY + 20}px)`; 9 | }, 10 | }} 11 | > 12 | Moving by returning transform string 13 | 14 | -------------------------------------------------------------------------------- /packages/react/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/src/documentation/options/legacyTranslate/LegacyTranslateGPU.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Legacy translate with GPU acceleration 7 | 8 | {#snippet pos(x, y)} 9 | transform: translate3d({x}px, {y}px, 0) 10 | {/snippet} 11 | 12 | -------------------------------------------------------------------------------- /docs/src/documentation/options/legacyTranslate/LegacyTranslateNoGPU.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Legacy translate with no GPU acceleration 7 | 8 | {#snippet pos(x, y)} 9 | transform: translate({x}px, {y}px) 10 | {/snippet} 11 | 12 | -------------------------------------------------------------------------------- /packages/react/demo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/components/home/RingSVG.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /docs/src/components/home/features/feature-rich/FeatureRichFeature.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |

Feature rich

7 |

Play with the kitchen sink demo below

8 | 9 |

10 | 11 |
12 | 13 | 19 | -------------------------------------------------------------------------------- /docs/src/documentation/options/transform/ManualTransform.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | { 8 | rootNode.style.translate = `${offsetX + 50}px ${offsetY + 20}px`; 9 | }, 10 | }} 11 | > 12 | Moving by manually setting rootNode.style 13 | 14 | -------------------------------------------------------------------------------- /packages/svelte/demo/src/routes/2/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
{ 9 | rootNode.style.translate = `${offsetX}px ${offsetY}px 0`; 10 | }, 11 | }} 12 | > 13 | Hello 14 | 15 |
16 | 17 |
Handle 1
18 |
Handle 2
19 |
20 | -------------------------------------------------------------------------------- /docs/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useTabs: true, 3 | semi: true, 4 | printWidth: 100, 5 | singleQuote: true, 6 | 7 | plugins: [require.resolve('prettier-plugin-astro'), require.resolve('prettier-plugin-svelte')], 8 | overrides: [ 9 | { 10 | files: '*.astro', 11 | options: { 12 | parser: 'astro', 13 | }, 14 | }, 15 | { 16 | files: ['*.mdx', '*.md'], 17 | options: { 18 | useTabs: false, 19 | }, 20 | }, 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /packages/vue/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "private": true, 4 | "version": "0.0.11", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@neodrag/vue": "workspace:*", 12 | "vue": "^3.3.12" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^4.5.2", 16 | "typescript": "^5.3.3", 17 | "vite": "^5.0.10", 18 | "vue-tsc": "^1.8.25" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/src/components/options/OptionsCode.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Framework } from '$helpers/constants'; 3 | 4 | const framework = Astro.url.pathname.split('/').filter(Boolean).at(-1) as Framework; 5 | --- 6 | 7 | {framework === 'react' && } 8 | {framework === 'svelte' && } 9 | {framework === 'vue' && } 10 | {framework === 'vanilla' && } 11 | {framework === 'solid' && } 12 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "strictNullChecks": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "$layouts/*": ["src/layouts/*"], 8 | "$components/*": ["src/components/*"], 9 | "$state/*": ["src/state/*"], 10 | "$actions/*": ["src/actions/*"], 11 | "$helpers/*": ["src/helpers/*"], 12 | "$/*": ["src/*"] 13 | } 14 | }, 15 | "include": [".astro/types.d.ts", "**/*"], 16 | "exclude": ["dist"] 17 | } 18 | -------------------------------------------------------------------------------- /docs/src/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | export {} 7 | declare global { 8 | const Code: (typeof import('$components/options/OptionsCode.astro'))['default']; 9 | const Example: (typeof import('$components/options/OptionsExample.astro'))['default']; 10 | const Examples: (typeof import('$components/options/OptionsExamples.svelte'))['default']; 11 | } 12 | -------------------------------------------------------------------------------- /packages/solid/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Solid App 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/solid/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vite", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "typescript": "^5.3.3", 14 | "vite": "^5.0.10", 15 | "vite-plugin-solid": "^2.8.0" 16 | }, 17 | "dependencies": { 18 | "@neodrag/solid": "workspace:*", 19 | "solid-js": "^1.8.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/svelte/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "@sveltejs/kit": "2.0.2", 12 | "svelte": "^4.2.8", 13 | "svelte-preprocess": "^5.1.3", 14 | "typescript": "^5.3.3", 15 | "vite": "^5.0.10" 16 | }, 17 | "dependencies": { 18 | "@neodrag/svelte": "workspace:*" 19 | }, 20 | "type": "module" 21 | } 22 | -------------------------------------------------------------------------------- /packages/vue/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "lib": ["esnext", "dom"] 13 | }, 14 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 15 | "references": [{ "path": "./tsconfig.node.json" }] 16 | } 17 | -------------------------------------------------------------------------------- /docs/src/documentation/options/bounds/ParentBounds.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | Can't go outside the parent element 8 | 9 |
10 | 11 | 21 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /docs/src/components/MetaThemeColor.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/svelte/tests/components/Draggable.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
{body}
13 | -------------------------------------------------------------------------------- /docs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cssvar.files": [ 3 | "./node_modules/open-props/open-props.min.css", 4 | // if you have an alternative path to where your styles are located 5 | // you can add it in this array of files 6 | "../src/css/globals.scss", 7 | "../src/css/themes.scss" 8 | ], 9 | 10 | // Do not ignore node_modules css files, which is ignored by default 11 | "cssvar.ignore": [], 12 | 13 | // add support for autocomplete in JS or JS like files 14 | "cssvar.extensions": ["css", "jsx", "tsx", "astro", "svelte", "vue"] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/cr.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Releases 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: 20 14 | 15 | - uses: pnpm/action-setup@v4 16 | with: 17 | version: 9 18 | run_install: true 19 | 20 | - name: Compile 21 | run: pnpm compile 22 | 23 | - name: Release 24 | run: pnpm dlx pkg-pr-new publish './packages/*' -------------------------------------------------------------------------------- /docs/src/documentation/options/position/Position.example.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | I can be moved with the slider too 9 | 10 | {#snippet caption()} 11 | X: 12 |    Y: 13 | 14 | {/snippet} 15 | 16 | -------------------------------------------------------------------------------- /packages/solid/src/index.ts: -------------------------------------------------------------------------------- 1 | import { draggable, type DragOptions } from '@neodrag/core'; 2 | import { createEffect, onCleanup, type Accessor } from 'solid-js'; 3 | 4 | export const createDraggable = () => ({ 5 | draggable: (node: HTMLElement, options: Accessor) => { 6 | const { update, destroy } = draggable(node, options()); 7 | 8 | onCleanup(destroy); 9 | createEffect(() => update(options())); 10 | }, 11 | }); 12 | 13 | export type { 14 | DragAxis, 15 | DragBounds, 16 | DragBoundsCoords, 17 | DragEventData, 18 | DragOptions, 19 | } from '@neodrag/core'; 20 | -------------------------------------------------------------------------------- /packages/vanilla/demo-umd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/vanilla/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/react/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /docs/src/documentation/options/position/DisabledPosition.example.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | I can be moved only with the slider 9 | 10 | {#snippet caption()} 11 | X: 12 |    Y: 13 | 14 | {/snippet} 15 | 16 | -------------------------------------------------------------------------------- /docs/src/documentation/options/cancel/SingleCancelSelector.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | This will drag! 7 | 8 |

9 | 10 |
This won't drag
11 | 12 | {#snippet caption()} 13 | Single cancel with selector 14 | {/snippet} 15 |
16 | 17 | 24 | -------------------------------------------------------------------------------- /packages/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "declarationDir": "./dist", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "lib": ["DOM", "ESNext"], 9 | "allowJs": false, 10 | "skipLibCheck": false, 11 | "esModuleInterop": false, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "ESNext", 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "preserveSymlinks": false 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/solid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "declarationDir": "./dist", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "lib": ["DOM", "ESNext"], 9 | "allowJs": false, 10 | "skipLibCheck": false, 11 | "esModuleInterop": false, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "ESNext", 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "preserveSymlinks": false 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/vanilla/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 |
I am draggable
12 | 13 | X: 14 | 15 | Y: 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/vanilla/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "declarationDir": "./dist", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "lib": ["DOM", "ESNext"], 9 | "allowJs": false, 10 | "skipLibCheck": false, 11 | "esModuleInterop": false, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "ESNext", 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "preserveSymlinks": false 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/svelte/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "declarationDir": "./dist", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "lib": ["DOM", "ESNext"], 9 | "allowJs": false, 10 | "skipLibCheck": false, 11 | "esModuleInterop": false, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "ESNext", 16 | "moduleResolution": "Node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "react-jsx" 20 | }, 21 | "include": ["./src"] 22 | } 23 | -------------------------------------------------------------------------------- /docs/src/helpers/framework-icons.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import LogosSvelteIcon from '~icons/logos/svelte-icon'; 3 | // @ts-ignore 4 | import LogosReactIcon from '~icons/logos/react'; 5 | // @ts-ignore 6 | import LogosVueIcon from '~icons/logos/vue'; 7 | // @ts-ignore 8 | import LogosSolidIcon from '~icons/logos/solidjs-icon'; 9 | // @ts-ignore 10 | import LogosVanillaIcon from '~icons/logos/javascript'; 11 | // @ts-ignore 12 | import IonReloadIcon from '~icons/ion/reload'; 13 | 14 | export const FRAMEWORK_ICONS = { 15 | svelte: LogosSvelteIcon, 16 | react: LogosReactIcon, 17 | vue: LogosVueIcon, 18 | solid: LogosSolidIcon, 19 | vanilla: LogosVanillaIcon, 20 | }; 21 | -------------------------------------------------------------------------------- /docs/src/documentation/options/cancel/MultipleCancelSelector.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | This will drag! 7 | 8 |

9 | 10 |
This won't drag
11 |
This won't drag
12 | 13 | {#snippet caption()} 14 | Multiple cancel passed as element. 15 | {/snippet} 16 |
17 | 18 | 25 | -------------------------------------------------------------------------------- /docs/src/documentation/options/recomputeBounds/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: recomputeBounds 3 | type: '{ dragStart?: boolean; drag?: boolean; dragEnd?: boolean; }' 4 | defaultValue: '{ dragStart: true, drag: false, dragEnd: false }' 5 | --- 6 | 7 | import Code from '$components/options/OptionsCode.astro'; 8 | import Example from '$components/options/OptionsExample.astro'; 9 | import Examples from '$components/options/OptionsExamples.svelte'; 10 | 11 | export const shortDescription = 'When to recalculate the dimensions of the `bounds` element.'; 12 | 13 |

{shortDescription}

14 | 15 | By default, bounds are recomputed only on dragStart. Use this options to change that behavior. 16 | -------------------------------------------------------------------------------- /packages/vanilla/demo-umd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 |
I am draggable
12 | 13 | X: 14 | 15 | Y: 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/public/logos/vanilla.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/src/documentation/options/cancel/SingleCancelElement.example.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | This will drag! 9 | 10 |

11 | 12 |
This won't drag
13 | 14 | {#snippet caption()} 15 | Single cancel passed as element. 16 | {/snippet} 17 |
18 | 19 | 26 | -------------------------------------------------------------------------------- /docs/src/documentation/options/handle/SingleHandleSelector.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | Won't drag ❌ 8 | 9 |

10 | 11 |
Drag me ✅
12 | 13 | {#snippet caption()} 14 | Single handle with selector 15 | {/snippet} 16 |
17 |
18 | 19 | 30 | -------------------------------------------------------------------------------- /docs/src/documentation/options/handle/MultipleHandleSelector.example.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 | Won't drag ❌ 8 | 9 |

10 | 11 |
Drag me ✅
12 |
Drag me ✅
13 | 14 | {#snippet caption()} 15 | Multiple handle with selector 16 | {/snippet} 17 |
18 |
19 | 20 | 31 | -------------------------------------------------------------------------------- /docs/src/documentation/options/handle/SingleHandleElement.example.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | Won't drag ❌ 10 | 11 |

12 | 13 |
Drag me ✅
14 | 15 | {#snippet caption()} 16 | Single handle with element 17 | {/snippet} 18 |
19 |
20 | 21 | 32 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import { type Directive } from 'vue'; 2 | import { draggable, type DragOptions } from '@neodrag/core'; 3 | 4 | const draggable_map = new WeakMap>(); 5 | 6 | export const vDraggable: Directive = { 7 | mounted: (el, { value = {} }) => 8 | !draggable_map.has(el) && draggable_map.set(el, draggable(el, value)), 9 | 10 | updated: (el, { value = {} }) => draggable_map.get(el)!.update(value), 11 | 12 | unmounted: (el) => { 13 | draggable_map.get(el)!.destroy(); 14 | draggable_map.delete(el); 15 | }, 16 | }; 17 | 18 | export type { 19 | DragAxis, 20 | DragBounds, 21 | DragBoundsCoords, 22 | DragOptions, 23 | DragEventData, 24 | } from '@neodrag/core'; 25 | -------------------------------------------------------------------------------- /docs/src/pages/docs/migrating/svelte-drag.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | layout: '$layouts/DocsLayout.astro' 3 | title: 'Migrating svelte-drag to neodrag' 4 | --- 5 | 6 | # svelte-drag to neodrag 7 | 8 | ## Installing 9 | 10 | ```bash 11 | npm uninstall svelte-drag 12 | npm install @neodrag/svelte 13 | ``` 14 | 15 | Change the imports 16 | 17 | ```diff 18 | - import { draggable } from 'svelte-drag' 19 | + import { draggable } from '@neodrag/svelte' 20 | ``` 21 | 22 | ## Options 23 | 24 | Options passed to the object are still the same as latest version of svelte-drag. No changes there 25 | 26 | ## Events 27 | 28 | Events have been renamed 29 | 30 | `on:svelte-drag` -> `on:neodrag` 31 | 32 | `on:svelte-drag:start` -> `on:neodrag:start` 33 | 34 | `on:svelte-drag:end` -> `on:neodrag:end` 35 | -------------------------------------------------------------------------------- /docs/src/state/auto-destroy-effect-root.svelte.ts: -------------------------------------------------------------------------------- 1 | import { onDestroy } from 'svelte'; 2 | 3 | /** 4 | * Behaves the same as `$effect.root`, but automatically 5 | * cleans up the effect inside Svelte components. 6 | * 7 | * @returns Cleanup function to manually cleanup the effect. 8 | */ 9 | export function auto_destroy_effect_root(fn: () => void | VoidFunction) { 10 | let cleanup: VoidFunction | null = $effect.root(fn); 11 | 12 | function destroy() { 13 | if (cleanup === null) { 14 | return; 15 | } 16 | 17 | cleanup(); 18 | cleanup = null; 19 | } 20 | 21 | try { 22 | onDestroy(destroy); 23 | } catch { 24 | // Ignore the error. The user is responsible for manually 25 | // cleaning up effects created outside Svelte components. 26 | } 27 | 28 | return destroy; 29 | } 30 | -------------------------------------------------------------------------------- /docs/src/helpers/utils.ts: -------------------------------------------------------------------------------- 1 | export const browser = !import.meta.env.SSR; 2 | 3 | export const isMac = browser && navigator.platform.toUpperCase().indexOf('MAC') >= 0; 4 | export const isMobile = 5 | browser && navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i); 6 | export const isDesktop = !isMobile; 7 | 8 | export function elements_overlap(el1: HTMLElement, el2: HTMLElement) { 9 | const dom_rect1 = el1.getBoundingClientRect(); 10 | const dom_rect2 = el2.getBoundingClientRect(); 11 | 12 | return !( 13 | dom_rect1.top > dom_rect2.bottom || 14 | dom_rect1.right < dom_rect2.left || 15 | dom_rect1.bottom < dom_rect2.top || 16 | dom_rect1.left > dom_rect2.right 17 | ); 18 | } 19 | 20 | export function wait_for(ms: number) { 21 | return new Promise((r) => setTimeout(r, ms)); 22 | } 23 | -------------------------------------------------------------------------------- /docs/src/documentation/options/cancel/MultipleCancelElement.example.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | This will drag! 10 | 11 |

12 | 13 |
This won't drag
14 |
This won't drag
15 | 16 | {#snippet caption()} 17 | Multiple cancel passed as array of elements. 18 | {/snippet} 19 |
20 | 21 | 28 | -------------------------------------------------------------------------------- /docs/src/components/home/features/ssr-friendly/SSRFriendlyFeature.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |

SSR-friendly

7 |

8 | Neodrag is Server Side Renderable. Will play well with meta-frameworks like Sveltekit, NextJS, 9 | Nuxt, Vitepress, SolidStart and more 10 |

11 |
12 | 13 |
14 | Metaframeworks 15 |
16 | 17 | 33 | -------------------------------------------------------------------------------- /packages/vanilla/demo-umd/src/main.js: -------------------------------------------------------------------------------- 1 | const Draggable = NeoDrag.Draggable 2 | 3 | const draggableEl = document.querySelector('.box'); 4 | const xSlider = document.querySelector('#x'); 5 | const ySlider = document.querySelector('#y'); 6 | 7 | let position = { x: 0, y: 0 }; 8 | 9 | const dragInstance = new Draggable(draggableEl, { 10 | position, 11 | onDrag: ({ offsetX, offsetY }) => { 12 | position = { x: offsetX, y: offsetY }; 13 | 14 | xSlider.value = offsetX.toString(); 15 | ySlider.value = offsetY.toString(); 16 | }, 17 | }); 18 | 19 | xSlider.addEventListener('input', (e) => { 20 | position.x = +e.target.value; 21 | dragInstance.updateOptions({ position }); 22 | }); 23 | 24 | ySlider.addEventListener('input', (e) => { 25 | position.y = +e.target.value; 26 | dragInstance.updateOptions({ position }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neodrag/core", 3 | "private": "true", 4 | "version": "2.3.1", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.js", 7 | "type": "module", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist/*" 11 | ], 12 | "sideEffects": false, 13 | "exports": { 14 | ".": { 15 | "types": "./dist/index.d.ts", 16 | "import": { 17 | "production": "./dist/index.js", 18 | "development": "./dist/index.js" 19 | }, 20 | "default": "./dist/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "scripts": { 25 | "compile:watch": "tsup --watch", 26 | "compile": "tsup" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/PuruVJ/neodrag.git" 31 | }, 32 | "author": "Puru Vijay", 33 | "license": "MIT" 34 | } 35 | -------------------------------------------------------------------------------- /docs/src/documentation/options/applyUserSelectHack/UserSelect.example.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | User Select enabled 9 | 10 | {#snippet caption()} 11 | {#if isDesktop} 12 | Hit 13 | {#if isMac} 14 | 15 | {:else} 16 | ctrl 17 | {/if} + A 18 | 19 | 20 | while dragging - Text will be selected 21 | {/if} 22 | {/snippet} 23 | 24 | 25 | 32 | -------------------------------------------------------------------------------- /docs/src/documentation/options/applyUserSelectHack/NoUserSelect.example.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | User Select disabled 9 | 10 | {#snippet caption()} 11 | {#if isDesktop} 12 | Hit 13 | {#if isMac} 14 | 15 | {:else} 16 | ctrl 17 | {/if} + A 18 | 19 | 20 | while dragging - Nothing will be selected 21 | {/if} 22 | {/snippet} 23 | 24 | 25 | 32 | -------------------------------------------------------------------------------- /docs/src/documentation/options/handle/MultipleHandleElement.example.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 10 | Won't drag ❌ 11 | 12 |

13 | 14 |
Drag me ✅
15 |
Drag me ✅
16 | 17 | {#snippet caption()} 18 | Multiple handle with element 19 | {/snippet} 20 |
21 |
22 | 23 | 34 | -------------------------------------------------------------------------------- /docs/src/components/home/ExploreFrameworks.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |

Pick your framework

7 | 8 |
9 | {#each FRAMEWORKS as { name }} 10 | {@const Icon = FRAMEWORK_ICONS[name]} 11 | 12 | 13 | 14 | 15 | {/each} 16 |
17 | 18 | 42 | -------------------------------------------------------------------------------- /packages/vanilla/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | import { Draggable } from '@neodrag/vanilla'; 3 | 4 | const draggableEl = document.querySelector('.box')!; 5 | const xSlider = document.querySelector('#x')!; 6 | const ySlider = document.querySelector('#y')!; 7 | 8 | let position = { x: 0, y: 0 }; 9 | 10 | const dragInstance = new Draggable(draggableEl, { 11 | position, 12 | onDrag: ({ offsetX, offsetY }) => { 13 | position = { x: offsetX, y: offsetY }; 14 | 15 | xSlider.value = offsetX.toString(); 16 | ySlider.value = offsetY.toString(); 17 | }, 18 | }); 19 | 20 | xSlider.addEventListener('input', (e: Event) => { 21 | position.x = +e.target.value; 22 | dragInstance.updateOptions({ position }); 23 | }); 24 | 25 | ySlider.addEventListener('input', (e: Event) => { 26 | position.y = +e.target.value; 27 | dragInstance.updateOptions({ position }); 28 | }); 29 | -------------------------------------------------------------------------------- /docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /packages/vanilla/src/index.ts: -------------------------------------------------------------------------------- 1 | import { draggable, type DragOptions } from '@neodrag/core'; 2 | 3 | export class Draggable { 4 | private _drag_instance: ReturnType; 5 | private _options: DragOptions = {}; 6 | 7 | constructor( 8 | public node: HTMLElement, 9 | options: DragOptions = {}, 10 | ) { 11 | this._drag_instance = draggable(node, (this._options = options)); 12 | } 13 | 14 | public updateOptions(options: DragOptions) { 15 | this._drag_instance.update(Object.assign(this._options, options)); 16 | } 17 | 18 | set options(options: DragOptions) { 19 | this._drag_instance.update((this._options = options)); 20 | } 21 | 22 | get options() { 23 | return this._options; 24 | } 25 | 26 | public destroy() { 27 | this._drag_instance.destroy(); 28 | } 29 | } 30 | 31 | export type { 32 | DragAxis, 33 | DragBounds, 34 | DragBoundsCoords, 35 | DragEventData, 36 | DragOptions, 37 | } from '@neodrag/core'; 38 | -------------------------------------------------------------------------------- /packages/react/demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useDraggable } from '@neodrag/react'; 2 | import { useRef, useState } from 'react'; 3 | 4 | function App() { 5 | const [position, setPosition] = useState({ x: 0, y: 0 }); 6 | 7 | const draggableRef = useRef(null); 8 | 9 | useDraggable(draggableRef, { 10 | onDrag: ({ offsetX, offsetY }) => setPosition({ x: offsetX, y: offsetY }), 11 | }); 12 | 13 | return ( 14 | <> 15 |
I can be moved with the slider too
16 | X: 17 | setPosition({ x: +e.target.value, y: position.y })} 23 | /> 24 | Y: 25 | setPosition({ x: position.x, y: +e.target.value })} 31 | /> 32 | 33 | ); 34 | } 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | type ObjectKeys = Obj extends object 6 | ? (keyof Obj)[] 7 | : Obj extends number 8 | ? [] 9 | : Obj extends Array | string 10 | ? string[] 11 | : never; 12 | 13 | interface ObjectConstructor { 14 | keys(o: ObjectType): ObjectKeys; 15 | entries(o: ObjType): [Unpacked>, ObjType[keyof ObjType]][]; 16 | } 17 | 18 | interface Storage { 19 | getItem(key: string): T | null; 20 | } 21 | 22 | type Unpacked = ArrayLike extends (infer RootType)[] ? RootType : ArrayLike; 23 | 24 | type Unpromisify = 25 | PromiseLike extends Promise ? RootType : PromiseLike; 26 | 27 | interface Array { 28 | fill(value: T, start?: number | undefined, end?: number | undefined): T[]; 29 | } 30 | -------------------------------------------------------------------------------- /packages/solid/demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { createDraggable } from '@neodrag/solid'; 2 | import { Component, createSignal } from 'solid-js'; 3 | 4 | const App: Component = () => { 5 | const { draggable } = createDraggable(); 6 | 7 | const [position, setPosition] = createSignal({ x: 0, y: 0 }); 8 | 9 | return ( 10 | <> 11 |
setPosition({ x: offsetX, y: offsetY }), 15 | }} 16 | > 17 | I can be moved with the slider too 18 |
19 | X: 20 | setPosition({ x: +e.target.value, y: position().y })} 26 | /> 27 | Y: 28 | setPosition({ x: position().x, y: +e.target.value })} 34 | /> 35 | 36 | ); 37 | }; 38 | 39 | export default App; 40 | -------------------------------------------------------------------------------- /packages/vue/demo/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Typescript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 ` 12 | 13 | 34 | 35 | 53 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Puru Vijay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/src/components/options/OptionsExample.astro: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 | 8 |
9 |
10 | 11 | 59 | -------------------------------------------------------------------------------- /packages/solid/demo/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. 4 | 5 | This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. 6 | 7 | ```bash 8 | $ npm install # or pnpm install or yarn install 9 | ``` 10 | 11 | ### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm dev` or `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | The page will reload if you make edits.
23 | 24 | ### `npm run build` 25 | 26 | Builds the app for production to the `dist` folder.
27 | It correctly bundles Solid in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed! 31 | 32 | ## Deployment 33 | 34 | You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) 35 | -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/src/actions/portal.ts: -------------------------------------------------------------------------------- 1 | import { tick } from 'svelte'; 2 | 3 | /** 4 | * Usage:
or
5 | * 6 | * @param {HTMLElement} el 7 | * @param {HTMLElement|string} target DOM Element or CSS Selector 8 | */ 9 | export function portal(el: HTMLElement, target: HTMLElement | string = 'body') { 10 | let targetEl: HTMLElement; 11 | 12 | async function update(newTarget: HTMLElement | string) { 13 | target = newTarget; 14 | 15 | if (typeof target === 'string') { 16 | targetEl = document.querySelector(target)!; 17 | 18 | if (targetEl === null) { 19 | await tick(); 20 | targetEl = document.querySelector(target)!; 21 | } 22 | 23 | if (targetEl === null) { 24 | throw new Error(`No element found matching css selector: "${target}"`); 25 | } 26 | } else if (target instanceof HTMLElement) { 27 | targetEl = target; 28 | } else { 29 | throw new TypeError( 30 | `Unknown portal target type: ${ 31 | target === null ? 'null' : typeof target 32 | }. Allowed types: string (CSS selector) or HTMLElement.`, 33 | ); 34 | } 35 | targetEl.appendChild(el); 36 | el.hidden = false; 37 | } 38 | 39 | function destroy() { 40 | if (el.parentNode) { 41 | el.parentNode.removeChild(el); 42 | } 43 | } 44 | 45 | update(target); 46 | 47 | return { 48 | update, 49 | destroy, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /docs/public/logos/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/vue/demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 41 | 42 | 58 | -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neodrag/vue", 3 | "version": "2.3.1", 4 | "description": "Vue library to add dragging to your apps 😉", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.js", 7 | "type": "module", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist/*" 11 | ], 12 | "sideEffects": false, 13 | "exports": { 14 | ".": { 15 | "types": "./dist/index.d.ts", 16 | "import": { 17 | "production": "./dist/min/index.js", 18 | "development": "./dist/index.js" 19 | }, 20 | "default": "./dist/min/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/PuruVJ/neodrag.git" 27 | }, 28 | "keywords": [ 29 | "draggable", 30 | "vue", 31 | "react-draggable", 32 | "drag", 33 | "neodrag", 34 | "small", 35 | "tiny", 36 | "performant", 37 | "neodrag" 38 | ], 39 | "author": "Puru Vijay", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/PuruVJ/neodrag/issues" 43 | }, 44 | "homepage": "https://github.com/PuruVJ/neodrag/tree/main/packages/vue#readme", 45 | "scripts": { 46 | "compile": "tsup", 47 | "compile:watch": "tsup --watch", 48 | "pub": "pnpm compile && pnpm publish --no-git-checks --access public", 49 | "pub:dry": "pnpm compile && pnpm publish --dry-run --no-git-checks --access public" 50 | }, 51 | "devDependencies": { 52 | "@neodrag/core": "workspace:*" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neodrag/react", 3 | "version": "2.3.1", 4 | "description": "React library to add dragging to your apps 😉", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.js", 7 | "type": "module", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist/*" 11 | ], 12 | "sideEffects": false, 13 | "exports": { 14 | ".": { 15 | "types": "./dist/index.d.ts", 16 | "import": { 17 | "production": "./dist/min/index.js", 18 | "development": "./dist/index.js" 19 | }, 20 | "default": "./dist/min/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/PuruVJ/neodrag.git" 27 | }, 28 | "keywords": [ 29 | "draggable", 30 | "react", 31 | "react-draggable", 32 | "drag", 33 | "neodrag", 34 | "preact", 35 | "small", 36 | "tiny", 37 | "performant", 38 | "neodrag" 39 | ], 40 | "author": "Puru Vijay", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/PuruVJ/neodrag/issues" 44 | }, 45 | "homepage": "https://github.com/PuruVJ/neodrag/tree/main/packages/react#readme", 46 | "scripts": { 47 | "compile": "tsup", 48 | "compile:watch": "tsup --watch", 49 | "pub": "pnpm compile && pnpm publish --no-git-checks --access public", 50 | "pub:dry": "pnpm compile && pnpm publish --dry-run --no-git-checks --access public" 51 | }, 52 | "devDependencies": { 53 | "@neodrag/core": "workspace:*" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/src/components/home/ScrollDownIndicator.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 73 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "type": "module", 4 | "version": "0.0.15", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/markdown-remark": "^6.0.0", 14 | "@fontsource/jetbrains-mono": "^5.0.18", 15 | "@fontsource/plus-jakarta-sans": "^5.0.18", 16 | "@neodrag/svelte": "workspace:*", 17 | "astro-seo": "^0.8.0", 18 | "open-props": "^1.7.8", 19 | "popmotion": "^11.0.5", 20 | "runed": "^0.18.0", 21 | "slugify": "^1.6.6", 22 | "svelte-body": "^2.0.0", 23 | "svelte-copy": "^2.0.0", 24 | "svelte-inview": "^4.0.1", 25 | "throttle-debounce": "^5.0.0" 26 | }, 27 | "devDependencies": { 28 | "@astrojs/mdx": "^4.0.1", 29 | "@astrojs/sitemap": "^3.2.1", 30 | "@astrojs/svelte": "^7.0.1", 31 | "@iconify/json": "^2.2.159", 32 | "@types/throttle-debounce": "^5.0.2", 33 | "astro": "^5.0.3", 34 | "astrojs-service-worker": "^2.0.0", 35 | "autoprefixer": "^10.4.16", 36 | "hast-util-to-string": "^3.0.0", 37 | "hastscript": "^9.0.0", 38 | "postcss": "^8.4.32", 39 | "postcss-jit-props": "^1.0.14", 40 | "prettier": "^3.1.1", 41 | "prettier-plugin-astro": "^0.14.1", 42 | "prettier-plugin-svelte": "^3.3.2", 43 | "rehype-autolink-headings": "^7.1.0", 44 | "remark-custom-container": "^1.2.0", 45 | "sass": "^1.82.0", 46 | "svelte": "^5.0.0", 47 | "typescript": "^5.5.0", 48 | "unplugin-icons": "^0.21.0", 49 | "vite": "^6.0.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/solid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neodrag/solid", 3 | "version": "2.3.1", 4 | "description": "SolidJS library to add dragging to your apps 😉", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.js", 7 | "type": "module", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist/*" 11 | ], 12 | "sideEffects": false, 13 | "exports": { 14 | ".": { 15 | "types": "./dist/index.d.ts", 16 | "import": { 17 | "production": "./dist/min/index.js", 18 | "development": "./dist/index.js" 19 | }, 20 | "default": "./dist/min/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/PuruVJ/neodrag.git" 27 | }, 28 | "keywords": [ 29 | "draggable", 30 | "solid", 31 | "react-draggable", 32 | "drag", 33 | "neodrag", 34 | "small", 35 | "tiny", 36 | "performant", 37 | "neodrag" 38 | ], 39 | "author": "Puru Vijay", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/PuruVJ/neodrag/issues" 43 | }, 44 | "homepage": "https://github.com/PuruVJ/neodrag/tree/main/packages/solid#readme", 45 | "scripts": { 46 | "compile": "tsup", 47 | "compile:watch": "tsup --watch", 48 | "pub": "pnpm compile && pnpm publish --no-git-checks --access public", 49 | "pub:dry": "pnpm compile && pnpm publish --dry-run --no-git-checks --access public" 50 | }, 51 | "devDependencies": { 52 | "@neodrag/core": "workspace:*" 53 | }, 54 | "peerDependencies": { 55 | "solid-js": "^1.0.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/vanilla/demo/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/src/layouts/MainDocsLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { MarkdownLayoutProps } from 'astro'; 3 | 4 | import DocsLayout from './DocsLayout.astro'; 5 | 6 | import type { Framework } from '$helpers/constants'; 7 | import SIZES from '../data/sizes.json'; 8 | 9 | type Props = MarkdownLayoutProps<{ 10 | title: string; 11 | tagline: string; 12 | }>; 13 | 14 | const { frontmatter } = Astro.props; 15 | const { tagline, title, url } = frontmatter; 16 | 17 | const framework = url?.split('/').at(-1) as Framework; 18 | const { size, version } = SIZES[framework]; 19 | --- 20 | 21 | 22 |

23 | @neodrag/{framework} 24 |

25 | 26 |
27 | 28 | {version} 29 | 30 | 31 | 32 | {size}KB 33 | 34 |
35 | 36 |

{tagline}

37 | 38 | 39 | 40 |

Credits

41 |

42 | Inspired from the amazing 43 | react-draggable 44 | library, and implements the same API. 45 |

46 |
47 | 48 | 67 | -------------------------------------------------------------------------------- /packages/vanilla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neodrag/vanilla", 3 | "version": "2.3.1", 4 | "description": "JS library to add dragging to your apps 😉", 5 | "main": "./dist/index.js", 6 | "unpkg": "./dist/umd/index.js", 7 | "jsdelivr": "./dist/umd/index.js", 8 | "module": "./dist/index.js", 9 | "type": "module", 10 | "types": "./dist/index.d.ts", 11 | "files": [ 12 | "dist/*" 13 | ], 14 | "sideEffects": false, 15 | "exports": { 16 | ".": { 17 | "types": "./dist/index.d.ts", 18 | "import": { 19 | "production": "./dist/min/index.js", 20 | "development": "./dist/index.js" 21 | }, 22 | "default": "./dist/min/index.js" 23 | }, 24 | "./package.json": "./package.json" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/PuruVJ/neodrag.git" 29 | }, 30 | "keywords": [ 31 | "draggable", 32 | "vanilla", 33 | "javascript", 34 | "typescript", 35 | "react-draggable", 36 | "drag", 37 | "neodrag", 38 | "small", 39 | "tiny", 40 | "performant", 41 | "neodrag" 42 | ], 43 | "author": "Puru Vijay", 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/PuruVJ/neodrag/issues" 47 | }, 48 | "homepage": "https://github.com/PuruVJ/neodrag/tree/main/packages/vanilla#readme", 49 | "scripts": { 50 | "compile": "tsup", 51 | "compile:watch": "tsup --watch", 52 | "pub": "pnpm compile && pnpm publish --no-git-checks --access public", 53 | "pub:dry": "pnpm compile && pnpm publish --dry-run --no-git-checks --access public" 54 | }, 55 | "devDependencies": { 56 | "@neodrag/core": "workspace:*" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@neodrag/svelte", 3 | "version": "2.3.3", 4 | "description": "Svelte Action to add dragging to your apps 😉", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.js", 7 | "type": "module", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist/*" 11 | ], 12 | "sideEffects": false, 13 | "exports": { 14 | ".": { 15 | "types": "./dist/index.d.ts", 16 | "import": { 17 | "production": "./dist/min/index.js", 18 | "development": "./dist/index.js" 19 | }, 20 | "default": "./dist/min/index.js", 21 | "svelte": "./dist/min/index.js" 22 | }, 23 | "./package.json": "./package.json" 24 | }, 25 | "scripts": { 26 | "test": "vitest run test", 27 | "test:watch": "vitest test", 28 | "compile:watch": "tsup --watch", 29 | "compile": "tsup ", 30 | "pub": "pnpm compile && pnpm publish --no-git-checks --access public", 31 | "pub:dry": "pnpm compile && pnpm publish --dry-run --no-git-checks --access public" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/PuruVJ/neodrag.git" 36 | }, 37 | "keywords": [ 38 | "draggable", 39 | "svelte", 40 | "react-draggable", 41 | "drag", 42 | "svelte", 43 | "small", 44 | "tiny", 45 | "performant", 46 | "neodrag" 47 | ], 48 | "author": "Puru Vijay", 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/PuruVJ/neodrag/issues" 52 | }, 53 | "devDependencies": { 54 | "@neodrag/core": "workspace:*" 55 | }, 56 | "peerDependencies": { 57 | "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" 58 | }, 59 | "homepage": "https://neodrag.dev/docs/svelte" 60 | } 61 | -------------------------------------------------------------------------------- /docs/src/documentation/options/disabled/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: disabled 3 | type: 'boolean' 4 | defaultValue: 'false' 5 | --- 6 | 7 | import Code from '$components/options/OptionsCode.astro'; 8 | import Example from '$components/options/OptionsExample.astro'; 9 | import Examples from '$components/options/OptionsExamples.svelte'; 10 | 11 | import DisabledExample from './Disabled.example.svelte'; 12 | 13 | export const shortDescription = 'Disables dragging'; 14 | 15 | {shortDescription}. 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | ```svelte 24 |
25 | Disabled. Won't drag, won't trigger any events 26 |
27 | ``` 28 |
29 | 30 |
31 | ```vue 32 | 37 | ``` 38 |
39 | 40 |
41 | ```jsx 42 |
43 | Disabled. Won't drag, won't trigger any events 44 |
45 | ``` 46 |
47 | 48 |
49 | ```ts 50 | useDraggable(draggableRef, { disabled: true }); 51 | ``` 52 |
53 | 54 |
55 | ```js 56 | new Draggable(el, { disabled: true }); 57 | ``` 58 |
59 | 60 |
61 | 62 |
63 |
64 | -------------------------------------------------------------------------------- /scripts/gather-sizes.ts: -------------------------------------------------------------------------------- 1 | import { file as brotliSize } from 'brotli-size'; 2 | import fg from 'fast-glob'; 3 | import { readFileSync } from 'node:fs'; 4 | import { mkdir, writeFile } from 'node:fs/promises'; 5 | 6 | async function main() { 7 | const files = ( 8 | await fg(new URL('../packages/*/dist/min/index.js', import.meta.url).pathname) 9 | ).filter((path) => !path.includes('core')); 10 | 11 | const versions = (await fg(new URL('../packages/*/package.json', import.meta.url).pathname)) 12 | .filter((path) => !path.includes('core')) 13 | .map((path) => { 14 | const framework = /packages\/(?[^ $]*)\/package\.json/.exec(path)?.groups 15 | ?.framework!; 16 | 17 | return { framework, version: JSON.parse(readFileSync(path, 'utf-8')).version }; 18 | }); 19 | 20 | const contents = ( 21 | await Promise.all( 22 | files.map(async (file) => { 23 | const framework = /packages\/(?[^ $]*)\/dist/.exec(file)?.groups?.framework!; 24 | const size = ((await brotliSize(file)) / 1024).toFixed(2); 25 | 26 | return { framework, size }; 27 | }), 28 | ) 29 | ).reduce( 30 | (acc, { framework, size }) => ({ 31 | ...acc, 32 | [framework]: { 33 | size: +size, 34 | version: versions.find(({ framework: vFw }) => vFw === framework)?.version, 35 | }, 36 | }), 37 | {}, 38 | ); 39 | 40 | // Ensure folder if not exists 41 | try { 42 | await mkdir(new URL('../docs/src/data', import.meta.url).pathname); 43 | } catch (error) {} 44 | 45 | console.table(Object.entries(contents).sort((a, b) => (a[0] > b[0] ? 1 : -1))); 46 | 47 | writeFile( 48 | new URL('../docs/src/data/sizes.json', import.meta.url), 49 | JSON.stringify(contents, null, 2), 50 | ); 51 | } 52 | 53 | main(); 54 | -------------------------------------------------------------------------------- /packages/svelte/tests/testHelpers.ts: -------------------------------------------------------------------------------- 1 | import { fireEvent } from '@testing-library/svelte'; 2 | 3 | /** 4 | * Simulate dragging a draggable element using the mouse. 5 | * 6 | * @param element the element to drag 7 | * @param clientX the X coordinate to drag the element to 8 | * @param clientY the Y coordinate to drag the element to 9 | */ 10 | export async function drag(element: HTMLElement, startX = 0, startY = 0, endX = 0, endY = 0) { 11 | await fireEvent.mouseEnter(element); 12 | await fireEvent.mouseOver(element); 13 | await fireEvent.mouseDown(element, { clientX: startX, clientY: startY }); 14 | await fireEvent.mouseMove(element, { clientX: endX, clientY: endY }); 15 | await fireEvent.mouseUp(element); 16 | } 17 | 18 | function createTouchList(clientX: number, clientY: number) { 19 | let touches: any = { 20 | item: (index: number) => { 21 | return { 22 | clientX, 23 | clientY, 24 | }; 25 | }, 26 | length: 1, 27 | 0: { clientX, clientY }, 28 | }; 29 | 30 | touches[Symbol.iterator] = function* () { 31 | yield { clientX, clientY }; 32 | }; 33 | 34 | return touches; 35 | } 36 | 37 | /** 38 | * Simulate dragging a draggable element using touch. 39 | * 40 | * @param element the element to drag 41 | * @param startX the X coordinate to start the drag event at 42 | * @param startY the Y coordinate to start the drag event at 43 | * @param endX the X coordinate to drag the element to 44 | * @param endY the Y coordinate to drag the element to 45 | */ 46 | export async function touchDrag(element: HTMLElement, startX = 0, startY = 0, endX = 0, endY = 0) { 47 | await fireEvent.touchStart(element, { touches: createTouchList(startX, startY) }); 48 | await fireEvent.touchMove(element, { touches: createTouchList(endX, endY) }); 49 | await fireEvent.touchEnd(element, { touches: createTouchList(endX, endY) }); 50 | } 51 | -------------------------------------------------------------------------------- /docs/public/logos/solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/src/documentation/options/defaultPosition/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: defaultPosition 3 | type: '{ x: number; y: number }' 4 | defaultValue: '{ x: 0, y: 0 }' 5 | --- 6 | 7 | import Code from '$components/options/OptionsCode.astro'; 8 | import Example from '$components/options/OptionsExample.astro'; 9 | import Examples from '$components/options/OptionsExamples.svelte'; 10 | 11 | import DefaultPositionExample from './DefaultPosition.example.svelte'; 12 | 13 | export const shortDescription = 14 | 'Offsets your element to the position you specify in the very beginning'; 15 | 16 | {shortDescription}. `x` and `y` should be in pixels. Ignored if [position](#position) is passed. 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | ```svelte 25 |
26 | Shifted by (100, 40) 27 |
28 | ``` 29 |
30 | 31 |
32 | ```vue 33 | 38 | ``` 39 |
40 | 41 |
42 | ```jsx 43 |
44 | Shifted by (100, 40) 45 |
46 | ``` 47 |
48 | 49 |
50 | ```ts 51 | useDraggable(draggableRef, { defaultPosition: { x: 100, y: 40 } }); 52 | ``` 53 |
54 | 55 |
56 | ```js 57 | new Draggable(el, { defaultPosition: { x: 100, y: 40 } }); 58 | ``` 59 |
60 | 61 |
62 | 63 |
64 |
65 | -------------------------------------------------------------------------------- /docs/src/documentation/exported-types.mdx: -------------------------------------------------------------------------------- 1 | import { Code } from 'astro/components'; 2 | 3 | ### Types Exported from package 4 | 5 | This package exports these types you can use: 6 | 7 | 17 | 18 | `DragOptions` is the documented list of all options provided by the component. 19 | 20 | `DragAxis` is the type of `axis` option, and is equal to `'both' | 'x' | 'y' | 'none'`. 21 | 22 | `DragBounds` is `'parent' | string | Partial`, the complete type of `bounds` option. 23 | 24 | `DragBoundsCoords` is when you're specifying the `bounds` field using an object, this is the type needed for that. 25 | 26 | `DragEventData` is the data provided during the [events](#events) 27 | 28 | ```ts 29 | export type DragAxis = 'both' | 'x' | 'y' | 'none'; 30 | 31 | export type DragBounds = 'parent' | string | Partial; 32 | 33 | export type DragEventData = { 34 | /** How much element moved from its original position horizontally */ 35 | offsetX: number; 36 | 37 | /** How much element moved from its original position vertically */ 38 | offsetY: number; 39 | 40 | /** The node on which the draggable is applied */ 41 | rootNode: HTMLElement; 42 | 43 | /** The element being dragged */ 44 | currentNode: HTMLElement; 45 | 46 | /** The pointer event that triggered the drag */ 47 | event: PointerEvent; 48 | }; 49 | 50 | export type DragBoundsCoords = { 51 | /** Number of pixels from left of the window */ 52 | left: number; 53 | 54 | /** Number of pixels from top of the window */ 55 | top: number; 56 | 57 | /** Number of pixels from the right side of window */ 58 | right: number; 59 | 60 | /** Number of pixels from the bottom of the window */ 61 | bottom: number; 62 | }; 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/src/state/persisted.svelte.ts: -------------------------------------------------------------------------------- 1 | import { browser } from '$helpers/utils.ts'; 2 | import { auto_destroy_effect_root } from './auto-destroy-effect-root.svelte.ts'; 3 | 4 | type Primitive = string | null | symbol | boolean | number | undefined | bigint; 5 | 6 | const is_primitive = (val: any): val is Primitive => { 7 | return val !== Object(val) || val === null; 8 | }; 9 | 10 | function get_value_from_storage(key: string) { 11 | const value = localStorage.getItem(key); 12 | 13 | if (value === null) return { found: false, value: null }; 14 | 15 | try { 16 | return { 17 | found: true, 18 | value: JSON.parse(value), 19 | }; 20 | } catch (e) { 21 | console.error(`Error when parsing ${value} from persisted store "${key}"`, e); 22 | return { 23 | found: false, 24 | value: null, 25 | }; 26 | } 27 | } 28 | 29 | export function persisted(key: string, initial: T) { 30 | const existing = browser ? localStorage.getItem(key) : JSON.stringify(initial); 31 | 32 | const primitive = is_primitive(initial); 33 | const parsed_value = existing ? JSON.parse(existing) : initial; 34 | 35 | let state = $state( 36 | primitive ? { current: parsed_value } : parsed_value, 37 | ); 38 | 39 | auto_destroy_effect_root(() => { 40 | $effect(() => { 41 | const controller = new AbortController(); 42 | 43 | addEventListener( 44 | 'storage', 45 | (event) => { 46 | if (event.key === key) { 47 | const val = get_value_from_storage(key); 48 | if (val.found) { 49 | state = primitive ? { current: val.value } : val.value; 50 | } 51 | } 52 | }, 53 | { signal: controller.signal }, 54 | ); 55 | 56 | return () => controller.abort(); 57 | }); 58 | 59 | $effect(() => { 60 | localStorage.setItem( 61 | key, 62 | // @ts-ignore 63 | JSON.stringify(primitive ? state.current : state), 64 | ); 65 | }); 66 | }); 67 | 68 | return state; 69 | } 70 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Changesets 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - '2.0' 7 | env: 8 | CI: true 9 | PNPM_CACHE_FOLDER: .pnpm-store 10 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 11 | jobs: 12 | version: 13 | # TODO: Change this later if repo changes 14 | if: github.repository == 'PuruVJ/neodrag' 15 | timeout-minutes: 15 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout Repo 19 | uses: actions/checkout@v3 20 | with: 21 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 22 | fetch-depth: 0 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 22.x 27 | 28 | - uses: pnpm/action-setup@v4 29 | name: Install pnpm 30 | id: pnpm-install 31 | with: 32 | version: 9 33 | run_install: true 34 | 35 | - name: Get pnpm store directory 36 | id: pnpm-cache 37 | shell: bash 38 | run: | 39 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 40 | 41 | - uses: actions/cache@v4 42 | name: Setup pnpm cache 43 | with: 44 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 45 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 46 | restore-keys: | 47 | ${{ runner.os }}-pnpm-store- 48 | 49 | - name: Build all packages 50 | run: pnpm -r compile 51 | 52 | - name: Create Release Pull Request or Publish to npm 53 | uses: changesets/action@v1 54 | with: 55 | version: pnpm ci:version 56 | commit: 'chore: update versions' 57 | title: 'chore: update versions' 58 | publish: pnpm ci:release 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 62 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 4 | 5 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 6 | 7 | ![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png) 8 | 9 | ## 🚀 Project Structure 10 | 11 | Inside of your Astro project, you'll see the following folders and files: 12 | 13 | ``` 14 | / 15 | ├── public/ 16 | │ └── favicon.svg 17 | ├── src/ 18 | │ ├── components/ 19 | │ │ └── Card.astro 20 | │ ├── layouts/ 21 | │ │ └── Layout.astro 22 | │ └── pages/ 23 | │ └── index.astro 24 | └── package.json 25 | ``` 26 | 27 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 28 | 29 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 30 | 31 | Any static assets, like images, can be placed in the `public/` directory. 32 | 33 | ## 🧞 Commands 34 | 35 | All commands are run from the root of the project, from a terminal: 36 | 37 | | Command | Action | 38 | | :--------------------- | :------------------------------------------------- | 39 | | `npm install` | Installs dependencies | 40 | | `npm run dev` | Starts local dev server at `localhost:3000` | 41 | | `npm run build` | Build your production site to `./dist/` | 42 | | `npm run preview` | Preview your build locally, before deploying | 43 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` | 44 | | `npm run astro --help` | Get help using the Astro CLI | 45 | 46 | ## 👀 Want to learn more? 47 | 48 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 49 | -------------------------------------------------------------------------------- /docs/astro.config.ts: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { rehypeHeadingIds } from '@astrojs/markdown-remark'; 3 | import mdx from '@astrojs/mdx'; 4 | import sitemap from '@astrojs/sitemap'; 5 | import svelte from '@astrojs/svelte'; 6 | import { defineConfig } from 'astro/config'; 7 | import { h } from 'hastscript'; 8 | import rehypeAutolinkHeadings, { type Options } from 'rehype-autolink-headings'; 9 | import container from 'remark-custom-container/dist/esm/index.js'; 10 | import UnpluginIcons from 'unplugin-icons/vite'; 11 | 12 | const AnchorLinkIcon = h( 13 | 'svg', 14 | { 15 | width: '0.75em', 16 | height: '0.75em', 17 | version: 1.1, 18 | viewBox: '0 0 16 16', 19 | xlmns: 'http://www.w3.org/2000/svg', 20 | }, 21 | h('path', { 22 | fillRule: 'evenodd', 23 | fill: 'currentcolor', 24 | d: 'M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z', 25 | }), 26 | ); 27 | 28 | // https://astro.build/config 29 | export default defineConfig({ 30 | site: 'https://neodrag.dev', 31 | integrations: [svelte(), mdx(), sitemap()], 32 | 33 | markdown: { 34 | extendDefaultPlugins: true, 35 | shikiConfig: { 36 | themes: { 37 | light: 'github-light', 38 | dark: 'github-dark', 39 | }, 40 | }, 41 | // @ts-ignore 42 | remarkPlugins: [container], 43 | rehypePlugins: [ 44 | rehypeHeadingIds, 45 | [ 46 | rehypeAutolinkHeadings, 47 | { 48 | test: (heading) => /^h[1-5]$/i.test(heading.tagName), 49 | behavior: 'append', 50 | properties: { 51 | ariaHidden: 'true', 52 | tabindex: -1, 53 | class: 'unstyled heading-anchor', 54 | }, 55 | content: (heading) => [ 56 | h( 57 | `span`, 58 | { 59 | ariaHidden: 'true', 60 | }, 61 | AnchorLinkIcon, 62 | ), 63 | ], 64 | } as Options, 65 | ], 66 | ], 67 | }, 68 | 69 | vite: { 70 | // @ts-ignore 71 | plugins: [UnpluginIcons({ autoInstall: true, compiler: 'svelte' })], 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /packages/vue/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | @neodrag/vue 7 |

8 | 9 |

10 | One draggable to rule em all 11 |

12 | 13 |

A lightweight Vue directive to make your elements draggable.

14 | 15 |

16 | 17 |

18 | 19 |

Getting Started

20 | 21 | ## Features 22 | 23 | - 🤏 Tiny - Only 1.77KB min+brotli. 24 | - 🐇 Simple - Quite simple to use, and effectively no-config required! 25 | - 🧙‍♀️ Elegant - Vue directive, to keep the usage simple, elegant and straightforward. 26 | - 🗃️ Highly customizable - Offers tons of options that you can modify to get different behavior. 27 | - ⚛️ Reactive - Change options passed to it on the fly, it will **just work 🙂** 28 | 29 | [Try it in Stackblitz](https://stackblitz.com/edit/vitejs-vite-2pg1r1?file=src%2FApp.jsx) 30 | 31 | ## Installing 32 | 33 | ```bash 34 | pnpm add @neodrag/vue 35 | 36 | # npm 37 | npm install @neodrag/vue 38 | 39 | # yarn 40 | yarn add @neodrag/vue 41 | ``` 42 | 43 | ## Usage 44 | 45 | Basic usage 46 | 47 | ```vue 48 | 51 | 52 | 55 | ``` 56 | 57 | With options 58 | 59 | ```vue 60 | 63 | 64 | 67 | ``` 68 | 69 | Defining options elsewhere with typescript 70 | 71 | ```vue 72 | 80 | 81 | 84 | ``` 85 | 86 | Read the docs 87 | 88 | ## Credits 89 | 90 | Inspired from the amazing [react-draggable](https://github.com/react-grid-layout/react-draggable) library, and implements a similar API, but 3x smaller. 91 | 92 | # License 93 | 94 | MIT License © Puru Vijay 95 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitConvention": "angular", 8 | "contributors": [ 9 | { 10 | "login": "PuruVJ", 11 | "name": "Puru Vijay", 12 | "avatar_url": "https://avatars.githubusercontent.com/u/47742487?v=4", 13 | "profile": "https://puruvj.dev", 14 | "contributions": [ 15 | "infra", 16 | "code", 17 | "maintenance", 18 | "content", 19 | "doc", 20 | "financial", 21 | "research" 22 | ] 23 | }, 24 | { 25 | "login": "bluwy", 26 | "name": "Bjorn Lu", 27 | "avatar_url": "https://avatars.githubusercontent.com/u/34116392?v=4", 28 | "profile": "https://bjornlu.com/", 29 | "contributions": [ 30 | "code", 31 | "ideas" 32 | ] 33 | }, 34 | { 35 | "login": "matrushka", 36 | "name": "Baris Gumustas", 37 | "avatar_url": "https://avatars.githubusercontent.com/u/53268?v=4", 38 | "profile": "https://github.com/matrushka", 39 | "contributions": [ 40 | "code" 41 | ] 42 | }, 43 | { 44 | "login": "sidharth-anand", 45 | "name": "Sidharth Anand", 46 | "avatar_url": "https://avatars.githubusercontent.com/u/55060749?v=4", 47 | "profile": "https://github.com/sidharth-anand", 48 | "contributions": [ 49 | "code" 50 | ] 51 | }, 52 | { 53 | "login": "Tropix126", 54 | "name": "Tropical", 55 | "avatar_url": "https://avatars.githubusercontent.com/u/42101043?v=4", 56 | "profile": "https://github.com/Tropix126", 57 | "contributions": [ 58 | "doc" 59 | ] 60 | }, 61 | { 62 | "login": "AphLute", 63 | "name": "AphLute", 64 | "avatar_url": "https://avatars.githubusercontent.com/u/80430144?v=4", 65 | "profile": "https://earth.suncapped.com/", 66 | "contributions": [ 67 | "code" 68 | ] 69 | }, 70 | { 71 | "login": "tascodes", 72 | "name": "Tas", 73 | "avatar_url": "https://avatars.githubusercontent.com/u/32209335?v=4", 74 | "profile": "https://github.com/tascodes", 75 | "contributions": [ 76 | "infra", 77 | "code", 78 | "test" 79 | ] 80 | } 81 | ], 82 | "contributorsPerLine": 7, 83 | "skipCi": true, 84 | "repoType": "github", 85 | "repoHost": "https://github.com", 86 | "projectName": "neodrag", 87 | "projectOwner": "PuruVJ" 88 | } 89 | -------------------------------------------------------------------------------- /packages/svelte/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | @neodrag/svelte 7 |

8 | 9 |

10 | One draggable to rule em all 11 |

12 | 13 |

A lightweight Svelte action to make your elements draggable.

14 | 15 |

16 | 17 |

18 | 19 |

Getting Started

20 | 21 | # Features 22 | 23 | - 🤏 Tiny - Only 1.68KB min+brotli. 24 | - 🐇 Simple - Quite simple to use, and effectively no-config required! 25 | - 🧙‍♀️ Elegant - Svelte Action, to keep the usage simple, elegant and expressive. 26 | - 🗃️ Highly customizable - Offers tons of options that you can modify to get different behavior. 27 | - ⚛️ Reactive - Change options passed to it on the fly, it will **just work 🙂** 28 | 29 | [Try it in Svelte REPL](https://svelte.dev/repl/fc972f90450c4945b6f2481d13eafa00?version=3.38.3) 30 | 31 | # Installing 32 | 33 | ```bash 34 | pnpm add @neodrag/svelte 35 | 36 | # npm 37 | npm install @neodrag/svelte 38 | 39 | # yarn 40 | yarn add @neodrag/svelte 41 | ``` 42 | 43 | # Migrating from svelte-drag 44 | 45 | svelte-drag is the predecessor of this package. To migrate, follow this short guide: [svelte-drag to @neodrag/svelte migration guide](https://www.neodrag.dev/docs/migrating/svelte-drag) 46 | 47 | # Usage 48 | 49 | Basic usage 50 | 51 | ```svelte 52 | 55 | 56 |
Hello
57 | ``` 58 | 59 | With options 60 | 61 | ```svelte 62 | 65 | 66 |
Hello
67 | ``` 68 | 69 | Defining options elsewhere with typescript 70 | 71 | ```svelte 72 | 80 | 81 |
Hello
82 | ``` 83 | 84 | Read the docs 85 | 86 | ## Credits 87 | 88 | Inspired from the amazing [react-draggable](https://github.com/react-grid-layout/react-draggable) library, and implements a similar API, but 3x smaller. 89 | 90 | # License 91 | 92 | MIT License © Puru Vijay 93 | -------------------------------------------------------------------------------- /packages/svelte/tests/CancelDraggable.spec.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/svelte'; 3 | import CancelDraggable from './components/CancelDraggable.svelte'; 4 | import { drag, touchDrag } from './testHelpers'; 5 | 6 | describe('CancelDraggable', () => { 7 | it('renders a basic div', () => { 8 | const { getByText } = render(CancelDraggable); 9 | 10 | const element = getByText('This will drag!'); 11 | 12 | expect(element).toBeInTheDocument(); 13 | expect(element).not.toHaveClass('neodrag'); 14 | expect(element).not.toHaveClass('neodrag-dragged'); 15 | expect(element).toHaveStyle('transform: translate3d(0px, 0px, 0)'); 16 | 17 | const cancelElement = getByText('You shall not drag!!'); 18 | 19 | expect(cancelElement).toBeInTheDocument(); 20 | expect(cancelElement).toHaveClass('cancel'); 21 | }); 22 | 23 | it('should drag by the main element', async () => { 24 | const { getByText } = render(CancelDraggable); 25 | 26 | const element = getByText('This will drag!'); 27 | 28 | expect(element).toBeInTheDocument(); 29 | 30 | await drag(element, 0, 0, 50, 50); 31 | 32 | expect(element).toHaveClass('neodrag'); 33 | expect(element).toHaveClass('neodrag-dragged'); 34 | expect(element).toHaveStyle('transform: translate3d(50px, 50px, 0)'); 35 | }); 36 | 37 | it('should not drag by the cancel element', async () => { 38 | const { getByText } = render(CancelDraggable); 39 | 40 | const cancelElement = getByText('You shall not drag!!'); 41 | 42 | expect(cancelElement).toBeInTheDocument(); 43 | 44 | await drag(cancelElement, 0, 0, 50, 50); 45 | 46 | const element = getByText('This will drag!'); 47 | 48 | expect(element).toHaveClass('neodrag'); 49 | expect(element).not.toHaveClass('neodrag-dragged'); 50 | expect(element).toHaveStyle('transform: translate3d(0px, 0px, 0)'); 51 | }); 52 | 53 | it('should drag by the main element by touch', async () => { 54 | const { getByText } = render(CancelDraggable); 55 | 56 | const element = getByText('This will drag!'); 57 | 58 | expect(element).toBeInTheDocument(); 59 | 60 | await touchDrag(element, 0, 0, 50, 50); 61 | 62 | expect(element).toHaveClass('neodrag'); 63 | expect(element).toHaveClass('neodrag-dragged'); 64 | expect(element).toHaveStyle('transform: translate3d(50px, 50px, 0)'); 65 | }); 66 | 67 | it('should not drag by the cancel element by touch', async () => { 68 | const { getByText } = render(CancelDraggable); 69 | 70 | const cancelElement = getByText('You shall not drag!!'); 71 | 72 | expect(cancelElement).toBeInTheDocument(); 73 | 74 | await touchDrag(cancelElement, 0, 0, 50, 50); 75 | 76 | const element = getByText('This will drag!'); 77 | 78 | expect(element).toHaveClass('neodrag'); 79 | expect(element).not.toHaveClass('neodrag-dragged'); 80 | expect(element).toHaveStyle('transform: translate3d(0px, 0px, 0)'); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /docs/src/components/home/features/bundle-sizes/BundleSizeFeature.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 |

Small bundle size

15 |

16 | Neodrag will never be heavy on your app. It's designed to be as small as possible, so you can 17 | use it without worrying about your bundle size. 18 |

19 | 20 |
21 | 22 |

23 | Ranges from {SIZES.svelte.size}KB for 24 | Svelte 25 | to 26 | {SIZES.react.size}KB for React. 27 | 28 |

29 | 30 | *Sizes in brotli after terser minification 31 |

32 | 33 |

34 |
35 | 36 |
37 | {#each sorted_frameworks.map(([framework, { size }]) => [framework, size, FRAMEWORK_ICONS[framework]] as const) as [framework, size, Icon]} 38 |
39 |
40 | 41 |
42 |
47 | {size} KB 48 |
49 |
50 | {/each} 51 |
52 | 53 | 117 | -------------------------------------------------------------------------------- /packages/solid/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | @neodrag/solid 7 |

8 | 9 |

10 | One draggable to rule em all 11 |

12 | 13 |

A lightweight SolidJS directive to make your elements draggable.

14 | 15 |

16 | 17 |

18 | 19 |

Getting Started

20 | 21 | # Features 22 | 23 | - 🤏 Tiny - Only 1.75KB min+brotli. 24 | - 🐇 Simple - Quite simple to use, and effectively no-config required! 25 | - 🧙‍♀️ Elegant - SolidJS directive, to keep the usage simple, elegant and straightforward. 26 | - 🗃️ Highly customizable - Offers tons of options that you can modify to get different behavior. 27 | - ⚛️ Reactive - Change options passed to it on the fly, it will **just work 🙂** 28 | 29 | # Installing 30 | 31 | ```bash 32 | pnpm add @neodrag/solid 33 | 34 | # npm 35 | npm install @neodrag/solid 36 | 37 | # yarn 38 | yarn add @neodrag/solid 39 | ``` 40 | 41 | # Usage 42 | 43 | Basic usage 44 | 45 | ```tsx 46 | import { createDraggable } from '@neodrag/solid'; 47 | 48 | export const App: Component = () => { 49 | const { draggable } = createDraggable(); 50 | 51 | return
You can drag me
; 52 | }; 53 | ``` 54 | 55 | With options 56 | 57 | ```tsx 58 | import { createDraggable } from '@neodrag/solid'; 59 | 60 | const { draggable } = createDraggable(); 61 | 62 |
I am draggable
; 63 | ``` 64 | 65 | Defining options elsewhere with typescript 66 | 67 | ```tsx 68 | import { createDraggable, type DragOptions } from '@neodrag/solid'; 69 | 70 | const options: DragOptions = { 71 | axis: 'y', 72 | bounds: 'parent', 73 | }; 74 | 75 | const { draggable } = createDraggable(); 76 | 77 |
I am draggable
; 78 | ``` 79 | 80 | Reactive options: 81 | 82 | ```tsx 83 | import { createSignal } from 'solid-js'; 84 | import { createDraggable } from '@neodrag/solid'; 85 | 86 | const [options, setOptions] = createSignal({ 87 | axis: 'y', 88 | bounds: 'parent', 89 | }); 90 | 91 |
I am draggable
; 92 | 93 | // You can update `options` with `setOptions` anytime and it'll change. Neodrag will automatically update to the new options 😉 94 | ``` 95 | 96 | Read the docs 97 | 98 | ## Credits 99 | 100 | Inspired from the amazing [react-draggable](https://github.com/react-grid-layout/react-draggable) library, and implements even more features with a similar API, but 3.7x smaller. 101 | 102 | # License 103 | 104 | MIT License © Puru Vijay 105 | -------------------------------------------------------------------------------- /docs/src/components/options/Options.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { MDXInstance } from 'astro'; 3 | import slugify from 'slugify'; 4 | import PhLinkThin from '~icons/ph/link-thin'; 5 | 6 | type OptionsFrontMatter = { 7 | title: string; 8 | type: string; 9 | defaultValue: string; 10 | shortDescription: string; 11 | deprecated?: boolean; 12 | deprecatedText?: string; 13 | }; 14 | 15 | const ORDER = [ 16 | 'axis', 17 | 'bounds', 18 | 'recomputeBounds', 19 | 'grid', 20 | 'threshold', 21 | 'defaultPosition', 22 | 'position', 23 | 'gpuAcceleration', 24 | 'legacyTranslate', 25 | 'transform', 26 | 'applyUserSelectHack', 27 | 'ignoreMultitouch', 28 | 'disabled', 29 | 'handle', 30 | 'cancel', 31 | 'defaultClass', 32 | 'defaultClassDragging', 33 | 'defaultClassDragged', 34 | 'onDragStart', 35 | 'onDrag', 36 | 'onDragEnd', 37 | ]; 38 | 39 | const optionsMD = Object.values( 40 | import.meta.glob>('../../documentation/options/*/+option.mdx', { 41 | eager: true, 42 | }), 43 | ); 44 | 45 | function validate() { 46 | if (optionsMD.length > ORDER.length) { 47 | const excludedOptions = optionsMD 48 | .map((o) => o.frontmatter.title) 49 | .filter((o) => !ORDER.includes(o)); 50 | 51 | throw new Error(`Add \`${excludedOptions.join(', ')}\` properties to ORDER array`); 52 | } 53 | 54 | for (const option of optionsMD) { 55 | // @ts-ignore 56 | if (!option.shortDescription) { 57 | throw new Error(`Add \`shortDescription\` to ${option.frontmatter.title}`); 58 | } 59 | } 60 | } 61 | 62 | validate(); 63 | 64 | const orderedOptionsMD = ORDER.map( 65 | (property) => optionsMD.find((option) => option.frontmatter.title === property)!, 66 | ) as typeof optionsMD & { shortDescription: string }[]; 67 | --- 68 | 69 |
70 | { 71 | orderedOptionsMD.map( 72 | ({ Content, frontmatter: { defaultValue, title, type, deprecated, deprecatedText } }) => ( 73 | <> 74 |

75 | {title} 76 | 77 | 85 |

86 |

87 | {deprecated === true ? `⚠️ Deprecated: ${deprecatedText}` : ''} 88 |
89 | Type: 90 | 91 | {type} 92 | 93 |
94 | Default Value: {defaultValue} 95 |

96 | 97 | 98 | 99 | ), 100 | ) 101 | } 102 |
103 | 104 | 116 | -------------------------------------------------------------------------------- /packages/svelte/tests/HandleDraggable.spec.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/svelte'; 3 | import HandleDraggable from './components/HandleDraggable.svelte'; 4 | import { drag, touchDrag } from './testHelpers'; 5 | 6 | describe('CancelDraggable', () => { 7 | it('renders a basic div', () => { 8 | const { getByText } = render(HandleDraggable); 9 | 10 | const element = getByText('You shall not drag!!'); 11 | const handleElement = getByText('This will drag!'); 12 | 13 | expect(element).toBeInTheDocument(); 14 | expect(handleElement).toBeInTheDocument(); 15 | 16 | expect(element).not.toHaveClass('svelte-draggable'); 17 | expect(element).not.toHaveClass('svelte-draggable-dragged'); 18 | expect(element).toHaveStyle('transform: translate3d(0px, 0px, 0)'); 19 | }); 20 | 21 | it('should drag by the handle element', async () => { 22 | const { getByText } = render(HandleDraggable); 23 | 24 | const element = getByText('You shall not drag!!'); 25 | const handleElement = getByText('This will drag!'); 26 | 27 | expect(element).toBeInTheDocument(); 28 | expect(handleElement).toBeInTheDocument(); 29 | 30 | await drag(handleElement, 0, 0, 50, 50); 31 | 32 | expect(element).toHaveClass('svelte-draggable'); 33 | expect(element).toHaveClass('svelte-draggable-dragged'); 34 | expect(element).toHaveStyle('transform: translate3d(50px, 50px, 0)'); 35 | }); 36 | 37 | it('should not drag by the main element', async () => { 38 | const { getByText } = render(HandleDraggable); 39 | 40 | const element = getByText('You shall not drag!!'); 41 | const handleElement = getByText('This will drag!'); 42 | 43 | expect(element).toBeInTheDocument(); 44 | expect(handleElement).toBeInTheDocument(); 45 | 46 | await drag(element, 0, 0, 50, 50); 47 | 48 | expect(element).toHaveClass('svelte-draggable'); 49 | expect(element).not.toHaveClass('svelte-draggable-dragged'); 50 | expect(element).toHaveStyle('transform: translate3d(0px, 0px, 0)'); 51 | }); 52 | 53 | it('should drag by the handle element by touch', async () => { 54 | const { getByText } = render(HandleDraggable); 55 | 56 | const element = getByText('You shall not drag!!'); 57 | const handleElement = getByText('This will drag!'); 58 | 59 | expect(element).toBeInTheDocument(); 60 | expect(handleElement).toBeInTheDocument(); 61 | 62 | await touchDrag(handleElement, 0, 0, 50, 50); 63 | 64 | expect(element).toHaveClass('svelte-draggable'); 65 | expect(element).toHaveClass('svelte-draggable-dragged'); 66 | expect(element).toHaveStyle('transform: translate3d(50px, 50px, 0)'); 67 | }); 68 | 69 | it('should not drag by the main element by touch', async () => { 70 | const { getByText } = render(HandleDraggable); 71 | 72 | const element = getByText('You shall not drag!!'); 73 | 74 | expect(element).toBeInTheDocument(); 75 | 76 | await touchDrag(element, 0, 0, 50, 50); 77 | 78 | expect(element).toHaveClass('svelte-draggable'); 79 | expect(element).not.toHaveClass('svelte-draggable-dragged'); 80 | expect(element).toHaveStyle('transform: translate3d(0px, 0px, 0)'); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /docs/src/documentation/options/ignoreMultitouch/+option.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ignoreMultitouch 3 | type: 'boolean' 4 | defaultValue: 'true' 5 | --- 6 | 7 | import Code from '$components/options/OptionsCode.astro'; 8 | import Example from '$components/options/OptionsExample.astro'; 9 | import Examples from '$components/options/OptionsExamples.svelte'; 10 | 11 | import IgnoredMultitouchExample from './IgnoredMultitouch.example.svelte'; 12 | import MultitouchExample from './Multitouch.example.svelte'; 13 | 14 | export const shortDescription = 'Ignores touch events with more than 1 touch.'; 15 | 16 | {shortDescription} 17 | This helps when you have multiple elements on a canvas where you want to implement pinch-to-zoom behaviour. 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | ```svelte 26 |
27 | Multi touch ignored 28 |
29 | ``` 30 |
31 | 32 |
33 | ```vue 34 | 39 | ``` 40 |
41 | 42 |
43 | ```jsx 44 |
45 | Multi touch ignored 46 |
47 | ``` 48 |
49 | 50 |
51 | ```ts 52 | useDraggable(draggableRef, { ignoreMultitouch: true }); 53 | ``` 54 |
55 | 56 |
57 | ```js 58 | new Draggable(el, { ignoreMultitouch: true }); 59 | ``` 60 |
61 | 62 |
63 | 64 |
65 | 66 | 67 | 68 | 69 | 70 |
71 | ```svelte 72 |
73 | Multi touch allowed 74 |
75 | ``` 76 |
77 | 78 |
79 | ```vue 80 | 85 | ``` 86 |
87 | 88 |
89 | ```jsx 90 |
91 | Multi touch allowed 92 |
93 | ``` 94 |
95 | 96 |
97 | ```ts 98 | useDraggable(draggableRef, { ignoreMultitouch: false }); 99 | ``` 100 |
101 | 102 |
103 | ```js 104 | new Draggable(el, { ignoreMultitouch: false }); 105 | ``` 106 |
107 | 108 |
109 | 110 |
111 | 112 |
113 | -------------------------------------------------------------------------------- /docs/public/logos/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | import { DragEventData, draggable, DragOptions } from '@neodrag/core'; 2 | import React, { useEffect, useRef, useState } from 'react'; 3 | 4 | type DragState = DragEventData; 5 | 6 | type HandleCancelType = 7 | | string 8 | | HTMLElement 9 | | React.RefObject 10 | | (React.RefObject | HTMLElement)[] 11 | | undefined; 12 | 13 | function unwrap_handle_cancel( 14 | val: HandleCancelType, 15 | ): string | HTMLElement | HTMLElement[] | undefined { 16 | if (val == undefined || typeof val === 'string' || val instanceof HTMLElement) return val; 17 | if ('current' in val) return val.current!; 18 | 19 | if (Array.isArray(val)) { 20 | // It can only be an array now 21 | return val.map((v) => (v instanceof HTMLElement ? v : v.current!)); 22 | } 23 | } 24 | 25 | type ReactDragOptions = Omit & { 26 | handle?: HandleCancelType; 27 | cancel?: HandleCancelType; 28 | }; 29 | 30 | export function useDraggable( 31 | nodeRef: React.RefObject, 32 | options: ReactDragOptions = {}, 33 | ) { 34 | const update_ref = useRef<(options: DragOptions) => void>(); 35 | 36 | const [isDragging, set_is_dragging] = useState(false); 37 | const [dragState, set_drag_state] = useState(); 38 | 39 | let { onDragStart, onDrag, onDragEnd, handle, cancel } = options; 40 | 41 | let new_handle = unwrap_handle_cancel(handle); 42 | let new_cancel = unwrap_handle_cancel(cancel); 43 | 44 | function call_event(arg: DragState, cb: DragOptions['onDrag']) { 45 | set_drag_state(arg); 46 | cb?.(arg); 47 | } 48 | 49 | function custom_on_drag_start(arg: DragState) { 50 | set_is_dragging(true); 51 | call_event(arg, onDragStart); 52 | } 53 | 54 | function custom_on_drag(arg: DragState) { 55 | call_event(arg, onDrag); 56 | } 57 | 58 | function custom_on_drag_end(arg: DragState) { 59 | set_is_dragging(false); 60 | call_event(arg, onDragEnd); 61 | } 62 | 63 | useEffect(() => { 64 | if (typeof window === 'undefined') return; 65 | const node = nodeRef.current; 66 | if (!node) return; 67 | 68 | // Update callbacks 69 | ({ onDragStart, onDrag, onDragEnd } = options); 70 | 71 | const { update, destroy } = draggable(node, { 72 | ...options, 73 | handle: new_handle, 74 | cancel: new_cancel, 75 | onDragStart: custom_on_drag_start, 76 | onDrag: custom_on_drag, 77 | onDragEnd: custom_on_drag_end, 78 | }); 79 | 80 | update_ref.current = update; 81 | 82 | return destroy; 83 | }, []); 84 | 85 | useEffect(() => { 86 | update_ref.current?.({ 87 | ...options, 88 | handle: unwrap_handle_cancel(handle), 89 | cancel: unwrap_handle_cancel(cancel), 90 | onDragStart: custom_on_drag_start, 91 | onDrag: custom_on_drag, 92 | onDragEnd: custom_on_drag_end, 93 | }); 94 | }, [options]); 95 | 96 | return { isDragging, dragState }; 97 | } 98 | 99 | export type { DragAxis, DragBounds, DragBoundsCoords, DragEventData } from '@neodrag/core'; 100 | export type { ReactDragOptions as DragOptions }; 101 | -------------------------------------------------------------------------------- /docs/src/documentation/options/bounds/CoordinateBounds.example.svelte: -------------------------------------------------------------------------------- 1 | 83 | 84 | 85 | Limited to: 86 | 87 | top: {supposed_bounds.top} 88 |
89 | left: {supposed_bounds.left} 90 |
91 | bottom: {supposed_bounds.bottom} 92 |
93 | right: {supposed_bounds.right} 94 |
95 | 96 | {#snippet caption()} 97 | Bounded by these coordinates from the window's edges. 98 | {/snippet} 99 |
100 | -------------------------------------------------------------------------------- /docs/src/components/MobileNav.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
(shadow = false)} 23 | oninview_leave={() => (shadow = true)} 24 | >
25 | 26 |
27 | 31 | 32 | 33 | 34 | 37 |
38 | 39 | {#if is_nav_open} 40 | 41 |