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 |
28 |
29 |
30 |
31 | 2nd
32 |
33 |
Cancel me out
34 |
Cancel me out pt 2
35 |
36 |
37 |
38 |
39 | Change axis
40 |
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 |
11 |
12 |
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 |
33 |
34 | Disabled. Won't drag, won't trigger any events
35 |
36 |
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 |
34 |
35 | Shifted by (100, 40)
36 |
37 |
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 | [](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
4 |
5 | > 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
6 |
7 | 
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 |
53 | I am draggable
54 |
55 | ```
56 |
57 | With options
58 |
59 | ```vue
60 |
63 |
64 |
65 | I am draggable
66 |
67 | ```
68 |
69 | Defining options elsewhere with typescript
70 |
71 | ```vue
72 |
80 |
81 |
82 | I am draggable
83 |
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 |
83 |
84 |
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 |
35 |
36 | Multi touch ignored
37 |
38 |
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 |
81 |
82 | Multi touch allowed
83 |
84 |
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 |
38 |
39 | {#if is_nav_open}
40 |
41 |
46 | (is_nav_open = false)}>
47 |
48 |
49 |
50 |
51 | {/if}
52 |
53 |
132 |
--------------------------------------------------------------------------------
/docs/src/documentation/options/applyUserSelectHack/+option.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: applyUserSelectHack
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 UserSelectExample from './UserSelect.example.svelte';
12 | import NoUserSelectExample from './NoUserSelect.example.svelte';
13 |
14 | export const shortDescription = 'Applies `user-select: none` on ` ` when dragging';
15 |
16 | Applies `user-select: none` on ` ` element when dragging, to prevent the irritating effect where dragging doesn't happen and the text is selected. Applied when dragging starts and removed when it stops.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ```svelte
25 |
26 | User Select disabled
27 |
28 | ```
29 |
30 |
31 |
32 | ```vue
33 |
34 |
35 | User Select disabled
36 |
37 |
38 | ```
39 |
40 |
41 |
42 | ```jsx
43 |
44 | User Select disabled
45 |
46 | ```
47 |
48 |
49 |
50 | ```ts
51 | useDraggable(draggableRef, { applyUserSelectHack: true });
52 | ```
53 |
54 |
55 |
56 | ```js
57 | new Draggable(el, { applyUserSelectHack: true });
58 | ```
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | ```svelte
71 |
72 | User Select enabled
73 |
74 | ```
75 |
76 |
77 |
78 | ```vue
79 |
80 |
81 | User Select enabled
82 |
83 |
84 | ```
85 |
86 |
87 |
88 | ```jsx
89 |
90 | User Select enabled
91 |
92 | ```
93 |
94 |
95 |
96 | ```ts
97 | useDraggable(draggableRef, { applyUserSelectHack: false });
98 | ```
99 |
100 |
101 |
102 | ```js
103 | new Draggable(el, { applyUserSelectHack: false });
104 | ```
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/docs/src/documentation/options/threshold/+option.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: threshold
3 | type: '{ distance?: number; delay?: number }'
4 | defaultValue: '{ distance: 3, delay: 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 DelayExample from './Delay.example.svelte';
12 | import DistanceExample from './Distance.example.svelte';
13 |
14 | export const shortDescription =
15 | "Threshold for dragging to start. If the user moves the mouse/finger less than this distance, the dragging won't start.";
16 |
17 | Allows you to set a threshold for dragging to start. If the user moves the mouse/finger less than this distance, the dragging won't start. Or user must hold the mouse/finger for the specified delay for dragging to begin.
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ```svelte
26 |
27 | 200ms Delay
28 |
29 | ```
30 |
31 |
32 |
33 | ```vue
34 |
35 |
36 | 200ms Delay
37 |
38 |
39 | ```
40 |
41 |
42 |
43 | ```jsx
44 |
45 | 200ms Delay
46 |
47 | ```
48 |
49 |
50 |
51 | ```ts
52 | useDraggable(draggableRef, { threshold: { delay: 200 } });
53 | ```
54 |
55 |
56 |
57 | ```js
58 | new Draggable(el, { threshold: { delay: 200 } });
59 | ```
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | ```svelte
72 |
73 | 100px distance
74 |
75 | ```
76 |
77 |
78 |
79 | ```vue
80 |
81 |
82 | 100px distance
83 |
84 |
85 | ```
86 |
87 |
88 |
89 | ```jsx
90 |
91 | 100px distance
92 |
93 | ```
94 |
95 |
96 |
97 | ```ts
98 | useDraggable(draggableRef, { threshold: { distance: 100 } });
99 | ```
100 |
101 |
102 |
103 | ```js
104 | new Draggable(el, { threshold: { distance: 100 } });
105 | ```
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/packages/vue/demo/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # demo
2 |
3 | ## 0.0.11
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [[`1061842`](https://github.com/PuruVJ/neodrag/commit/1061842aac696335fc6c0d8e9e57c764c4a5b005)]:
8 | - @neodrag/vue@2.3.1
9 |
10 | ## 0.0.10
11 |
12 | ### Patch Changes
13 |
14 | - Updated dependencies [[`a917373`](https://github.com/PuruVJ/neodrag/commit/a917373e56378ae9443f3162e428abc8c058b191)]:
15 | - @neodrag/vue@2.3.0
16 |
17 | ## 0.0.9
18 |
19 | ### Patch Changes
20 |
21 | - Updated dependencies [[`0cead87`](https://github.com/PuruVJ/neodrag/commit/0cead8701f132670bd5618ceeb8fdee8e9a3ad27)]:
22 | - @neodrag/vue@2.2.0
23 |
24 | ## 0.0.8
25 |
26 | ### Patch Changes
27 |
28 | - Updated dependencies [[`45d9eeb`](https://github.com/PuruVJ/neodrag/commit/45d9eeb375b18eb0530cc079613dcdc21cce81d4), [`45d9eeb`](https://github.com/PuruVJ/neodrag/commit/45d9eeb375b18eb0530cc079613dcdc21cce81d4), [`ac0e10b`](https://github.com/PuruVJ/neodrag/commit/ac0e10bf287b3577fb926d6ba585e906abeaab72)]:
29 | - @neodrag/vue@2.1.0
30 |
31 | ## 0.0.7
32 |
33 | ### Patch Changes
34 |
35 | - Updated dependencies [[`e19ce73`](https://github.com/PuruVJ/neodrag/commit/e19ce732a9494dc3eb05e0c8702cd802abc0af9a)]:
36 | - @neodrag/vue@2.0.4
37 |
38 | ## 0.0.6
39 |
40 | ### Patch Changes
41 |
42 | - Updated dependencies [[`b736fa6`](https://github.com/PuruVJ/neodrag/commit/b736fa689e06491e348638311900900e35342e6e)]:
43 | - @neodrag/vue@2.0.3
44 |
45 | ## 0.0.5
46 |
47 | ### Patch Changes
48 |
49 | - Updated dependencies [[`0e6a36a`](https://github.com/PuruVJ/neodrag/commit/0e6a36a8ab1be01b97d8604dbc931c6e7ce4f16b)]:
50 | - @neodrag/vue@2.0.2
51 |
52 | ## 0.0.4
53 |
54 | ### Patch Changes
55 |
56 | - Updated dependencies []:
57 | - @neodrag/vue@2.0.1
58 |
59 | ## 0.0.3
60 |
61 | ### Patch Changes
62 |
63 | - Updated dependencies [[`bd831dcc`](https://github.com/PuruVJ/neodrag/commit/bd831dcc101d967b78505acd064cdfcde03b62ff), [`3c10f6ae`](https://github.com/PuruVJ/neodrag/commit/3c10f6ae377c3e9fc9fea963ea99204a4649806c), [`9e5c4647`](https://github.com/PuruVJ/neodrag/commit/9e5c46477c7781bc75a57944983434a0c8ceff77), [`da98e910`](https://github.com/PuruVJ/neodrag/commit/da98e910469d63e53e2462e74196bad3b90ea053), [`a1572bce`](https://github.com/PuruVJ/neodrag/commit/a1572bce5186051a5114dd580017a49fc2b3c7fc), [`8dd0d88f`](https://github.com/PuruVJ/neodrag/commit/8dd0d88ff0458c0bd6d20e3649371fdf732c9ebb)]:
64 | - @neodrag/vue@2.0.0
65 |
66 | ## 0.0.3-next.5
67 |
68 | ### Patch Changes
69 |
70 | - Updated dependencies [[`a1572bc`](https://github.com/PuruVJ/neodrag/commit/a1572bce5186051a5114dd580017a49fc2b3c7fc)]:
71 | - @neodrag/vue@2.0.0-next.6
72 |
73 | ## 0.0.3-next.4
74 |
75 | ### Patch Changes
76 |
77 | - Updated dependencies [[`9e5c464`](https://github.com/PuruVJ/neodrag/commit/9e5c46477c7781bc75a57944983434a0c8ceff77)]:
78 | - @neodrag/vue@2.0.0-next.5
79 |
80 | ## 0.0.3-next.3
81 |
82 | ### Patch Changes
83 |
84 | - Updated dependencies []:
85 | - @neodrag/vue@2.0.0-next.4
86 |
87 | ## 0.0.3-next.2
88 |
89 | ### Patch Changes
90 |
91 | - Updated dependencies [[`0f513db2`](https://github.com/PuruVJ/neodrag/commit/0f513db2c0a88ed03f0472311a03b6ae0e4f9483), [`8dd0d88f`](https://github.com/PuruVJ/neodrag/commit/8dd0d88ff0458c0bd6d20e3649371fdf732c9ebb)]:
92 | - @neodrag/vue@2.0.0-next.3
93 |
--------------------------------------------------------------------------------
/docs/src/documentation/options/gpuAcceleration/+option.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'gpuAcceleration'
3 | type: 'boolean'
4 | defaultValue: 'true'
5 | deprecated: true
6 | deprecatedText: 'Will be removed in v3'
7 | ---
8 |
9 | import Code from '$components/options/OptionsCode.astro';
10 | import Example from '$components/options/OptionsExample.astro';
11 | import Examples from '$components/options/OptionsExamples.svelte';
12 |
13 | import NoAccelerationExample from './NoAcceleration.example.svelte';
14 | import AccelerationExample from './Acceleration.example.svelte';
15 |
16 | export let shortDescription =
17 | 'If true, uses `translate3d` instead of `translate` to move the element around, and the hardware acceleration kicks in.';
18 |
19 | {shortDescription}
20 |
21 | `true` by default, but can be set to `false` if [blurry text issue](https://developpaper.com/question/why-does-the-use-of-css3-translate3d-result-in-blurred-display/) occurs.
22 |
23 | ::: info
24 |
25 | Depending on the device, you may not see much difference
26 |
27 | :::
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ```svelte
36 |
37 | GPU acceleration on
38 |
39 | ```
40 |
41 |
42 |
43 | ```vue
44 |
45 |
46 | GPU acceleration on
47 |
48 |
49 | ```
50 |
51 |
52 |
53 | ```jsx
54 |
55 | GPU acceleration on
56 |
57 | ```
58 |
59 |
60 |
61 | ```ts
62 | useDraggable(draggableRef, { gpuAcceleration: true });
63 | ```
64 |
65 |
66 |
67 | ```js
68 | new Draggable(el, { gpuAcceleration: true });
69 | ```
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | ```svelte
82 |
83 | GPU acceleration off
84 |
85 | ```
86 |
87 |
88 |
89 | ```vue
90 |
91 |
92 | GPU acceleration off
93 |
94 |
95 | ```
96 |
97 |
98 |
99 | ```jsx
100 |
101 | GPU acceleration off
102 |
103 | ```
104 |
105 |
106 |
107 | ```ts
108 | useDraggable(draggableRef, { gpuAcceleration: false });
109 | ```
110 |
111 |
112 |
113 | ```js
114 | new Draggable(el, { gpuAcceleration: false });
115 | ```
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/packages/react/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @neodrag/react
7 |
8 |
9 |
10 | One draggable to rule em all
11 |
12 |
13 | A lightweight React hook to make your elements draggable.
14 |
15 |
16 |
17 |
18 |
19 |
Getting Started
20 |
21 | # Features
22 |
23 | - 🤏 Tiny - Only 1.95KB min+brotli.
24 | - 🐇 Simple - Quite simple to use, and effectively no-config required!
25 | - 🧙♀️ Elegant - React hook, 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 | # Installing
30 |
31 | ```bash
32 | pnpm add @neodrag/react
33 |
34 | # npm
35 | npm install @neodrag/react
36 |
37 | # yarn
38 | yarn add @neodrag/react
39 | ```
40 |
41 | # Usage
42 |
43 | Basic usage
44 |
45 | ```tsx
46 | import { useDraggable } from '@neodrag/react';
47 |
48 | function App() {
49 | const draggableRef = useRef(null);
50 | useDraggable(draggableRef);
51 |
52 | return Hello
;
53 | }
54 | ```
55 |
56 | With options
57 |
58 | ```tsx
59 | import { useDraggable } from '@neodrag/react';
60 |
61 | function App() {
62 | const draggableRef = useRef(null);
63 | useDraggable(draggableRef, { axis: 'x', grid: [10, 10] });
64 |
65 | return Hello
;
66 | }
67 | ```
68 |
69 | Defining options elsewhere with typescript
70 |
71 | ```tsx
72 | import { useDraggable, type DragOptions } from '@neodrag/react';
73 |
74 | function App() {
75 | const draggableRef = useRef(null);
76 |
77 | const options: DragOptions = {
78 | axis: 'y',
79 | bounds: 'parent',
80 | };
81 |
82 | useDraggable(draggableRef, options);
83 |
84 | return Hello
;
85 | }
86 | ```
87 |
88 | Getting state
89 |
90 | ```tsx
91 | import { useDraggable } from '@neodrag/react';
92 |
93 | function App() {
94 | const draggableRef = useRef(null);
95 |
96 | const { isDragging, dragState } = useDraggable(draggableRef);
97 |
98 | useEffect(() => {
99 | console.log({ isDragging, dragState });
100 | }, [isDragging, dragState]);
101 |
102 | return Hello
;
103 | }
104 | ```
105 |
106 | `dragState` is of type:
107 |
108 | ```ts
109 | {
110 | /** Distance of element from original position on x-axis */
111 | offsetX: number,
112 |
113 | /** Distance of element from original position on y-axis */
114 | offsetY: number,
115 |
116 | /** element.getBoundingClientRect() result */
117 | domRect: DOMRect,
118 | }
119 | ```
120 |
121 | Read the docs
122 |
123 | ## Credits
124 |
125 | Inspired from the amazing [react-draggable](https://github.com/react-grid-layout/react-draggable) library, and implements even more features with similar API, but 3.7x smaller.
126 |
127 | # License
128 |
129 | MIT License © Puru Vijay
130 |
--------------------------------------------------------------------------------
/packages/svelte/src/index.ts:
--------------------------------------------------------------------------------
1 | import { draggable as core_draggable, type DragEventData, type DragOptions } from '@neodrag/core';
2 |
3 | //!THIS IS HACK, WE WANNA IMPORT THE TYPE WHEN THE ISSUE IS FIXED LATER
4 | /**
5 | * Actions can return an object containing the two properties defined in this interface. Both are optional.
6 | * - update: An action can have a parameter. This method will be called whenever that parameter changes,
7 | * immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn` both
8 | * mean that the action accepts no parameters.
9 | * - destroy: Method that is called after the element is unmounted
10 | *
11 | * Additionally, you can specify which additional attributes and events the action enables on the applied element.
12 | * This applies to TypeScript typings only and has no effect at runtime.
13 | *
14 | * Example usage:
15 | * ```ts
16 | * interface Attributes {
17 | * newprop?: string;
18 | * 'on:event': (e: CustomEvent) => void;
19 | * }
20 | *
21 | * export function myAction(node: HTMLElement, parameter: Parameter): ActionReturn {
22 | * // ...
23 | * return {
24 | * update: (updatedParameter) => {...},
25 | * destroy: () => {...}
26 | * };
27 | * }
28 | * ```
29 | *
30 | * Docs: https://svelte.dev/docs/svelte-action
31 | */
32 | export interface ActionReturn<
33 | Parameter = undefined,
34 | Attributes extends Record = Record,
35 | > {
36 | update?: (parameter: Parameter) => void;
37 | destroy?: () => void;
38 | /**
39 | * ### DO NOT USE THIS
40 | * This exists solely for type-checking and has no effect at runtime.
41 | * Set this through the `Attributes` generic instead.
42 | */
43 | $$_attributes?: Attributes;
44 | }
45 |
46 | /**
47 | * Actions are functions that are called when an element is created.
48 | * You can use this interface to type such actions.
49 | * The following example defines an action that only works on `` elements
50 | * and optionally accepts a parameter which it has a default value for:
51 | * ```ts
52 | * export const myAction: Action
= (node, param = { someProperty: true }) => {
53 | * // ...
54 | * }
55 | * ```
56 | * `Action` and `Action` both signal that the action accepts no parameters.
57 | *
58 | * You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has.
59 | * See interface `ActionReturn` for more details.
60 | *
61 | * Docs: https://svelte.dev/docs/svelte-action
62 | */
63 | export interface Action<
64 | Element = HTMLElement,
65 | Parameter = undefined,
66 | Attributes extends Record = Record,
67 | > {
68 | (
69 | ...args: undefined extends Parameter
70 | ? [node: Node, parameter?: Parameter]
71 | : [node: Node, parameter: Parameter]
72 | ): void | ActionReturn;
73 | }
74 |
75 | export const draggable = core_draggable as Action<
76 | HTMLElement,
77 | DragOptions | undefined,
78 | {
79 | 'on:neodrag:start': (e: CustomEvent) => void;
80 | 'on:neodrag': (e: CustomEvent) => void;
81 | 'on:neodrag:end': (e: CustomEvent) => void;
82 | }
83 | >;
84 |
85 | export type {
86 | DragAxis,
87 | DragBounds,
88 | DragBoundsCoords,
89 | DragEventData,
90 | DragOptions,
91 | } from '@neodrag/core';
92 |
--------------------------------------------------------------------------------
/docs/src/pages/docs/vue.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | layout: '$layouts/MainDocsLayout.astro'
3 | title: '@neodrag/vue'
4 | tagline: 'A lightweight directive to make your elements draggable.'
5 | ---
6 |
7 | import ExportedTypesMDX from '../../documentation/exported-types.mdx';
8 |
9 | import Options from '$components/options/Options.astro';
10 |
11 | ```sh
12 | pnpm add @neodrag/vue
13 |
14 | # or
15 |
16 | bun install @neodrag/vue
17 |
18 | # or
19 |
20 | npm install @neodrag/vue
21 | ```
22 |
23 | ## Usage
24 |
25 | Basic usage
26 |
27 | ```vue
28 |
31 |
32 |
33 | I am draggable
34 |
35 | ```
36 |
37 | With options
38 |
39 | ```vue
40 |
43 |
44 |
45 | I am draggable
46 |
47 | ```
48 |
49 | Defining options elsewhere with typescript
50 |
51 | ```vue
52 |
60 |
61 |
62 | I am draggable
63 |
64 | ```
65 |
66 | ## Options
67 |
68 |
69 |
70 | ## Events
71 |
72 | `@neodrag/vue` emits 3 events, `onDrag`, `onDragStart` & `onDragEnd`.
73 | Example:
74 |
75 | ```vue
76 |
77 |
84 | Hello
85 |
86 |
87 | ```
88 |
89 | ## TypeScript
90 |
91 | This library ships with proper TypeScript typings, for the best Developer Experience, whether authoring JS or TS.
92 |
93 |
94 |
95 | ## Controlled vs Uncontrolled
96 |
97 | This is taken straight from React's philosophy(After all, this package is inspired from [react-draggable](https://github.com/react-grid-layout/react-draggable)).
98 |
99 | Uncontrolled means your app doesn't control the dragging of the app. Meaning, the user drags the element, it changes position, and you do something with that action. You yourself don't change position of the element or anything. This is the default behavior of this library.
100 |
101 | Controlled means your app, using state variables, changes the position of the element, or in simple terms, programmatically drag the element. You basically set the `position` property to `{ x: 10, y: 50 }`(or any other numbers), and voila! yur now controlling the position of the element programmatically 🥳🥳
102 |
103 | OFC, this library doesn't go fully **Controlled**. The user can still drag it around even when `position` is set.
104 |
105 | So, when you change `position`, the element position changes. However, when the element is dragged by user interaction, `position` is not changed. This is done intentionally, as two-way data binding here isn't possible and also will lead to unexpected behavior. To keep the `position` variable up to date, use the `onDrag` event to keep your state up to date to the draggable's internal state.
106 |
107 | To have it be strictly **Controlled**, meaning it can only be moved programmatically, add the `disabled` option to your draggable element's config
108 |
109 | ```vue
110 |
111 | ```
112 |
--------------------------------------------------------------------------------
/docs/src/css/globals.scss:
--------------------------------------------------------------------------------
1 | /* optional imports that use the props */
2 | @import url('open-props/normalize.min.css');
3 |
4 | @import './themes';
5 | @import './breakpoints';
6 | @import './include-media';
7 |
8 | * {
9 | color: var(--app-color-dark);
10 | }
11 |
12 | *:focus-visible {
13 | box-shadow: 0 0 0 4px hsla(var(--secondary-color-hsl, --app-color-primary-hsl), 0.25);
14 | }
15 |
16 | html {
17 | width: 100%;
18 | }
19 |
20 | html,
21 | body {
22 | height: 100%;
23 | width: 100%;
24 |
25 | background-color: var(--app-color-shell);
26 |
27 | scroll-behavior: smooth;
28 | overflow-x: hidden;
29 |
30 | font-family: var(--app-font-main);
31 | }
32 |
33 | body {
34 | height: auto !important;
35 |
36 | accent-color: var(--app-color-primary);
37 | }
38 |
39 | :is(h1, h2, h3, h4, h5, .h1, .h2, .h3, .h4, .h5) {
40 | width: max-content;
41 |
42 | color: var(--app-color-dark);
43 | font-family: var(--app-font-heading);
44 | word-break: break-all;
45 |
46 | margin: 0.75em 0 0.25em;
47 |
48 | max-width: 100%;
49 | }
50 |
51 | h1,
52 | .h1 {
53 | margin-top: 0;
54 | font-size: clamp(2.35rem, 10vw, 3.998rem);
55 | }
56 |
57 | h2,
58 | .h2 {
59 | font-size: clamp(1.77rem, 5vw, 2.827rem);
60 | }
61 |
62 | h3,
63 | .h3 {
64 | font-size: clamp(1.33rem, 2.5vw, 1.999rem);
65 | }
66 |
67 | h4,
68 | .h4 {
69 | font-size: clamp(1rem, 1.25vw, 1.414rem);
70 | }
71 |
72 | code {
73 | color: var(--app-color-dark);
74 | background-color: var(--app-color-dark-contrast);
75 |
76 | font-family: var(--app-font-mono);
77 | }
78 |
79 | pre code {
80 | color: initial;
81 | background-color: initial;
82 | }
83 |
84 | kbd {
85 | font-family: var(--app-font-mono);
86 | font-weight: 400;
87 | }
88 |
89 | :is(a[href], a[href]:visited):where(:not(.unstyled)) {
90 | --distance: calc(50% - 0.375rem);
91 | --opacity: 0.35;
92 | --duration: 150ms;
93 | --easing: ease-in-out;
94 |
95 | color: var(--app-color-primary);
96 | font-weight: 600 !important;
97 | text-decoration: none;
98 |
99 | padding: 0 0.25rem;
100 |
101 | background-image: linear-gradient(
102 | transparent 0%,
103 | transparent var(--distance),
104 | hsla(var(--app-color-primary-hsl), var(--opacity)) var(--distance),
105 | hsla(var(--app-color-primary-hsl), var(--opacity)) 100%
106 | );
107 | background-size: 100% 200%;
108 | background-position: 0 0;
109 |
110 | transition: var(--duration) var(--easing);
111 | transition-property: color, background-position, background-image, border-radius;
112 |
113 | &:hover,
114 | &:focus-visible {
115 | background-position: 0 100%;
116 |
117 | color: var(--app-color-primary-contrast);
118 | font-weight: 600 !important;
119 |
120 | border-radius: 0.25rem;
121 |
122 | --opacity: 1;
123 | }
124 | }
125 |
126 | .sr-only {
127 | position: absolute;
128 | width: 1px;
129 | height: 1px;
130 | padding: 0;
131 | margin: -1px;
132 | overflow: hidden;
133 | clip: rect(0, 0, 0, 0);
134 | white-space: nowrap;
135 | border-width: 0;
136 | }
137 |
138 | // Set the text-FRAMEWORK
139 | // Map of frameworks
140 | $frameworks: svelte, react, vue, solid, vanilla;
141 |
142 | @each $framework in $frameworks {
143 | // Set the text-FRAMEWORK
144 | .text-#{$framework} {
145 | color: var(--app-color-brand-#{$framework});
146 | fill: var(--app-color-brand-#{$framework});
147 | }
148 |
149 | .bg-#{$framework} {
150 | background-color: var(--app-color-brand-#{$framework});
151 | }
152 | }
153 |
154 | ::selection {
155 | background-color: hsla(var(--secondary-color-hsl, var(--app-color-primary-hsl)), 0.3);
156 | // color: var(--app-color-primary-contrast);
157 | // -webkit-text-fill-color: var(--app-color-primary-contrast);
158 | }
159 |
160 | p {
161 | word-wrap: normal;
162 | }
163 |
164 | button {
165 | background-color: transparent;
166 | }
167 |
168 | .font-mono {
169 | font-family: var(--app-font-mono);
170 | }
171 |
--------------------------------------------------------------------------------
/docs/src/components/home/features/multiple-frameworks/MultipleFrameworksFeature.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
Multi-framework
25 |
26 | One tool, endless possibilities: integrate with Svelte, Vue, React, Solid, and more.
28 | Core logic is implemented only once, so you can use Neodrag in different frameworks, and get
30 | the same predictible behavior
32 |
33 |
34 |
35 |
36 |
37 | npm install @neodrag/
38 |
39 | {#key selected_framework}
40 | {@html selected_framework}
46 | {/key}
47 |
48 |
49 | (copied = true),
56 | }}
57 | >
58 |
59 |
60 | {#if copied}
61 |
62 |
63 |
64 | {:else}
65 |
66 |
67 |
68 | {/if}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | (selected_framework = framework)} />
77 |
78 |
79 |
158 |
--------------------------------------------------------------------------------
/docs/src/components/ThemeSwitcher.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 | {
37 | const { clientX } = e;
38 |
39 | // Get difference
40 | position_x.target =
41 | clientX -
42 | theme_switcher_container!.getBoundingClientRect().left -
43 | draggable_el!.getBoundingClientRect().width / 2;
44 |
45 | if (position_x.current / container_width >= 0.5) {
46 | position_x.target = container_width - draggable_el!.getBoundingClientRect().width;
47 | } else {
48 | position_x.target = 0;
49 | }
50 |
51 | change_theme();
52 | }}
53 | bind:clientWidth={container_width}
54 | bind:this={theme_switcher_container}
55 | >
56 |
{
61 | position_x.target = 0;
62 | change_theme();
63 | }}
64 | >
65 |
66 |
67 |
68 |
{
77 | position_x.set(offsetX, { duration: 0 });
78 | change_theme();
79 | },
80 | onDragEnd: ({ offsetX, rootNode }) => {
81 | if (offsetX / container_width > 0.3) {
82 | position_x.target = container_width - rootNode.getBoundingClientRect().width;
83 | } else {
84 | position_x.target = 0;
85 | }
86 |
87 | change_theme();
88 | },
89 | }}
90 | >
91 |
92 |
{
97 | position_x.target = container_width - draggable_el!.getBoundingClientRect().width;
98 | change_theme();
99 | }}
100 | >
101 |
102 |
103 |
104 |
105 |
158 |
--------------------------------------------------------------------------------
/docs/src/worklet/squircle.js:
--------------------------------------------------------------------------------
1 | const drawSquircle = (ctx, geom, radius, smooth, lineWidth, color) => {
2 | const defaultFill = color;
3 | const lineWidthOffset = lineWidth / 2;
4 | // OPEN LEFT-TOP CORNER
5 | ctx.beginPath();
6 | ctx.lineTo(radius, lineWidthOffset);
7 | // TOP-RIGHT CORNER
8 | ctx.lineTo(geom.width - radius, lineWidthOffset);
9 | ctx.bezierCurveTo(
10 | geom.width - radius / smooth,
11 | lineWidthOffset, // first bezier point
12 | geom.width - lineWidthOffset,
13 | radius / smooth, // second bezier point
14 | geom.width - lineWidthOffset,
15 | radius, // last connect point
16 | );
17 | // BOTTOM-RIGHT CORNER
18 | ctx.lineTo(geom.width - lineWidthOffset, geom.height - radius);
19 | ctx.bezierCurveTo(
20 | geom.width - lineWidthOffset,
21 | geom.height - radius / smooth, // first bezier point
22 | geom.width - radius / smooth,
23 | geom.height - lineWidthOffset, // second bezier point
24 | geom.width - radius,
25 | geom.height - lineWidthOffset, // last connect point
26 | );
27 | // BOTTOM-LEFT CORNER
28 | ctx.lineTo(radius, geom.height - lineWidthOffset);
29 | ctx.bezierCurveTo(
30 | radius / smooth,
31 | geom.height - lineWidthOffset, // first bezier point
32 | lineWidthOffset,
33 | geom.height - radius / smooth, // second bezier point
34 | lineWidthOffset,
35 | geom.height - radius, // last connect point
36 | );
37 | // CLOSE LEFT-TOP CORNER
38 | ctx.lineTo(lineWidthOffset, radius);
39 | ctx.bezierCurveTo(
40 | lineWidthOffset,
41 | radius / smooth, // first bezier point
42 | radius / smooth,
43 | lineWidthOffset, // second bezier point
44 | radius,
45 | lineWidthOffset, // last connect point
46 | );
47 | ctx.closePath();
48 |
49 | if (lineWidth) {
50 | // console.log(lineWidth);
51 | ctx.strokeStyle = defaultFill;
52 | ctx.lineWidth = lineWidth;
53 | ctx.stroke();
54 | } else {
55 | ctx.fillStyle = defaultFill;
56 | ctx.fill();
57 | }
58 | };
59 |
60 | if (typeof registerPaint !== 'undefined') {
61 | class SquircleClass {
62 | static get contextOptions() {
63 | return { alpha: true };
64 | }
65 | static get inputProperties() {
66 | return [
67 | '--squircle-radius',
68 | '--squircle-smooth',
69 | '--squircle-outline',
70 | '--squircle-fill',
71 | '--squircle-ratio',
72 | ];
73 | }
74 |
75 | paint(ctx, geom, properties) {
76 | const customRatio = properties.get('--squircle-ratio');
77 | const smoothRatio = 10;
78 | const distanceRatio = parseFloat(customRatio) ? parseFloat(customRatio) : 1.8;
79 | const squircleSmooth = parseFloat(properties.get('--squircle-smooth') * smoothRatio);
80 | const squircleRadius = parseInt(properties.get('--squircle-radius'), 10) * distanceRatio;
81 | const squrcleOutline = parseFloat(properties.get('--squircle-outline'), 10);
82 | const squrcleColor = properties.get('--squircle-fill').toString().replace(/\s/g, '');
83 |
84 | const isSmooth = () => {
85 | if (typeof properties.get('--squircle-smooth')[0] !== 'undefined') {
86 | if (squircleSmooth === 0) {
87 | return 1;
88 | }
89 | return squircleSmooth;
90 | } else {
91 | return 10;
92 | }
93 | };
94 |
95 | const isOutline = () => {
96 | if (squrcleOutline) {
97 | return squrcleOutline;
98 | } else {
99 | return 0;
100 | }
101 | };
102 |
103 | const isColor = () => {
104 | if (squrcleColor) {
105 | return squrcleColor;
106 | } else {
107 | return '#f45';
108 | }
109 | };
110 |
111 | if (squircleRadius < geom.width / 2 && squircleRadius < geom.height / 2) {
112 | drawSquircle(ctx, geom, squircleRadius, isSmooth(), isOutline(), isColor());
113 | } else {
114 | drawSquircle(
115 | ctx,
116 | geom,
117 | Math.min(geom.width / 2, geom.height / 2),
118 | isSmooth(),
119 | isOutline(),
120 | isColor(),
121 | );
122 | }
123 | }
124 | }
125 |
126 | // eslint-disable-next-line no-undef
127 | registerPaint('squircle', SquircleClass);
128 | }
129 |
--------------------------------------------------------------------------------
/docs/src/components/PawCursor.svelte:
--------------------------------------------------------------------------------
1 |
103 |
104 |
114 |
115 |
149 |
--------------------------------------------------------------------------------
/packages/vanilla/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @neodrag/vanilla
7 |
8 |
9 |
10 | One draggable to rule em all
11 |
12 |
13 | A lightweight vanillaJS library to make your elements draggable.
14 |
15 |
16 |
17 |
18 |
19 |
Getting Started
20 |
21 | # Features
22 |
23 | - 🤏 Tiny - Only 1.79KB min+brotli.
24 | - 🐇 Simple - Quite simple to use, and effectively no-config required!
25 | - 🧙♀️ Elegant - Single class, very easy to use.
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/vanilla
33 |
34 | # npm
35 | npm install @neodrag/vanilla
36 |
37 | # yarn
38 | yarn add @neodrag/vanilla
39 | ```
40 |
41 | # Usage
42 |
43 | Basic usage
44 |
45 | ```tsx
46 | import { Draggable } from '@neodrag/vanilla';
47 |
48 | const dragInstance = new Draggable(document.querySelector('#drag'));
49 | ```
50 |
51 | With options
52 |
53 | ```tsx
54 | import { Draggable } from '@neodrag/vanilla';
55 |
56 | const dragInstance = new Draggable(document.querySelector('#drag'), {
57 | axis: 'x',
58 | grid: [10, 10],
59 | });
60 | ```
61 |
62 | Defining options elsewhere with typescript
63 |
64 | ```tsx
65 | import { type Draggable } from '@neodrag/vanilla';
66 |
67 | const options: DragOptions = {
68 | axis: 'y',
69 | bounds: 'parent',
70 | };
71 |
72 | const dragInstance = new Draggable(document.querySelector('#drag'), options);
73 | ```
74 |
75 | Update options:
76 |
77 | ```ts
78 | import { Draggable } from '@neodrag/vanilla';
79 |
80 | const dragInstance = new Draggable(document.querySelector('#drag'), {
81 | axis: 'x',
82 | grid: [10, 10],
83 | });
84 |
85 | // Update the specific options. Will be merged with the existing options.
86 | dragInstance.update({
87 | axis: 'y',
88 | });
89 |
90 | // Completely overrides existing options, in this case, the `grid` property is removed
91 | dragInstance.options = {
92 | axis: 'y',
93 | };
94 | ```
95 |
96 | Using via CDN
97 |
98 | For users who prefer not to install the package and instead use it directly in their projects via a CDN, you can include `@neodrag/vanilla` directly in your HTML files. This is particularly useful for quick prototyping or projects where you want to avoid a build step. Here’s how to include it using different CDNs:
99 |
100 | Using Unpkg
101 |
102 | Include the library in your HTML using the following `
106 | ```
107 |
108 | Using jsDelivr
109 |
110 | Alternatively, you can use jsDelivr as a CDN to load `@neodrag/vanilla`. Include the following line in your HTML:
111 |
112 | ```html
113 |
114 | ```
115 |
116 | Usage with CDN
117 |
118 | After including the library via a CDN, `@neodrag/vanilla` will be available as a global variable `NeoDrag`. Here’s how you can use it to make an element draggable:
119 |
120 | ```html
121 | Drag me!
122 | ```
123 |
124 | This method allows you to use `@neodrag/vanilla` without any build tools or npm installations, directly in your browser.
125 |
126 | Read the docs
127 |
128 | ## Credits
129 |
130 | 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.
131 |
132 | # License
133 |
134 | MIT License © Puru Vijay
135 |
--------------------------------------------------------------------------------
/docs/src/documentation/options/grid/+option.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'grid'
3 | type: '[number, number]'
4 | defaultValue: 'undefined'
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 SquareGridExample from './SquareGrid.example.svelte';
12 | import RectangleGridExample from './RectangleGrid.example.svelte';
13 | import InactiveGridExample from './InactiveGrid.example.svelte';
14 |
15 | export const shortDescription =
16 | 'Applies a grid on the page to which the element snaps to when dragging, rather than the default continuous grid.';
17 |
18 | {shortDescription}
19 |
20 | ::: warning
21 |
22 | If you're programmatically creating the grid, do not set it to \[0, 0\] ever, that will stop drag at all. Set it to `undefined` to make it continuous once again
23 |
24 | :::
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ```svelte
34 |
35 | Snaps to 50x50 grid
36 |
37 | ```
38 |
39 |
40 |
41 | ```vue
42 |
43 |
44 | Snaps to 50x50 grid
45 |
46 |
47 | ```
48 |
49 |
50 |
51 | ```jsx
52 |
53 | Snaps to 50x50 grid
54 |
55 | ```
56 |
57 |
58 |
59 | ```ts
60 | useDraggable(draggableRef, { grid: [50, 50] });
61 | ```
62 |
63 |
64 |
65 | ```js
66 | new Draggable(el, { grid: [50, 50] });
67 | ```
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | ```svelte
80 |
81 | Snaps to 72x91 grid
82 |
83 | ```
84 |
85 |
86 |
87 | ```vue
88 |
89 |
90 | Snaps to 72x91 grid
91 |
92 |
93 | ```
94 |
95 |
96 |
97 | ```jsx
98 |
99 | Snaps to 72x91 grid
100 |
101 | ```
102 |
103 |
104 |
105 | ```ts
106 | useDraggable(draggableRef, { grid: [72, 91] });
107 | ```
108 |
109 |
110 |
111 | ```js
112 | new Draggable(el, { grid: [72, 91] });
113 | ```
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | ```svelte
126 |
127 | Snaps to 0x0 grid - Won't drag at all
128 |
129 | ```
130 |
131 |
132 |
133 | ```vue
134 |
135 |
136 | Snaps to 0x0 grid - Won't drag at all
137 |
138 |
139 | ```
140 |
141 |
142 |
143 | ```jsx
144 |
145 | Snaps to 0x0 grid - Won't drag at all
146 |
147 | ```
148 |
149 |
150 |
151 | ```ts
152 | useDraggable(draggableRef, { grid: [0, 0] });
153 | ```
154 |
155 |
156 |
157 | ```js
158 | new Draggable(el, { grid: [0, 0] });
159 | ```
160 |
161 |
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------