Skip to content

Commit

Permalink
setForcedPosition, position tests
Browse files Browse the repository at this point in the history
  • Loading branch information
PuruVJ committed Jan 5, 2025
1 parent 1d2a888 commit 466461f
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 73 deletions.
17 changes: 14 additions & 3 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,9 +625,9 @@ export function createDraggable({
instance.effects.add(func);
},

propose: (x: number | null, y: number | null) => {
instance.ctx.proposed.x = x;
instance.ctx.proposed.y = y;
propose(x: number | null, y: number | null) {
this.proposed.x = x;
this.proposed.y = y;
},

cancel() {
Expand All @@ -637,6 +637,17 @@ export function createDraggable({
preventStart() {
instance.dragstart_prevented = true;
},

setForcedPosition(x, y) {
this.offset.x = x;
this.offset.y = y;
// Only sync initial with offset when not dragging
// This maintains the drag calculations during active drags
if (!this.isDragging) {
this.initial.x = x;
this.initial.y = y;
}
},
};

// Initial setup
Expand Down
39 changes: 13 additions & 26 deletions packages/core/src/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface PluginContext {
propose: (x: number | null, y: number | null) => void;
cancel: () => void;
preventStart: () => void;
setForcedPosition: (x: number, y: number) => void;
}

export interface Plugin<State = any> {
Expand Down Expand Up @@ -842,44 +843,30 @@ export const controls = unstable_definePlugin<
},
});

export const position = unstable_definePlugin(
export const position = unstable_definePlugin<
[
options?: {
current?: { x: number; y: number } | null;
default?: { x: number; y: number } | null;
} | null,
]
>(
{
name: 'neodrag:position',
priority: 1000,
liveUpdate: true,

setup([options], ctx) {
if (options?.default) {
ctx.offset.x = options.default.x ?? ctx.offset.x;
ctx.offset.y = options.default.y ?? ctx.offset.y;
ctx.initial.x = options.default.x ?? ctx.initial.x;
ctx.initial.y = options.default.y ?? ctx.initial.y;
if (options?.default && !options.current) {
ctx.setForcedPosition(options.default.x ?? ctx.offset.x, options.default.y ?? ctx.offset.y);
}

if (options?.current) {
ctx.offset.x = options.current.x ?? ctx.offset.x;
ctx.offset.y = options.current.y ?? ctx.offset.y;
}
},

drag([options], ctx) {
// Only intervene if position has changed externally
if (
ctx.isDragging &&
options?.current &&
(options.current.x !== ctx.offset.x || options.current.y !== ctx.offset.y)
) {
ctx.propose(options.current.x - ctx.offset.x, options.current.y - ctx.offset.y);
ctx.cancel();
ctx.setForcedPosition(options.current.x, options.current.y);
}
},
},
[{}] as [
options?: {
current?: { x: number; y: number };
default?: { x: number; y: number };
} | null,
],
[null],
);

type TouchActionMode =
Expand Down
42 changes: 0 additions & 42 deletions packages/core/test-app/e2e/plugins/controls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,48 +104,6 @@ test('block-only', async ({ page }) => {
await expect(div).toHaveCSS('translate', '20px 20px');
});

test('block-only', async ({ page }) => {
await setup(page, 'plugins/controls', SCHEMAS.PLUGINS.CONTROLS, {
type: 'allow-block',
});

const div = page.getByTestId('draggable');
const handle = div.locator('.handle');
const cancel = div.locator('.cancel');

await handle.hover();
let { x, y } = await get_mouse_position(page);

await page.mouse.down();
await page.mouse.move(x + 10, y + 10);
await page.mouse.up();

// This should have moved.
await expect(div).toHaveCSS('translate', '10px 10px');

// Now move to some point within the box, near bottom right and attempt to drag
await page.mouse.move(100, 100); // 10x10px from the corner

({ x, y } = await get_mouse_position(page));

await page.mouse.down();
await page.mouse.move(x + 10, y + 10);
await page.mouse.up();

// Should have moved
await expect(div).toHaveCSS('translate', '20px 20px');

await cancel.hover();
({ x, y } = await get_mouse_position(page));

await page.mouse.down();
await page.mouse.move(x + 10, y + 10);
await page.mouse.up();

// Should not have moved
await expect(div).toHaveCSS('translate', '20px 20px');
});

test('allow-block', async ({ page }) => {
await setup(page, 'plugins/controls', SCHEMAS.PLUGINS.CONTROLS, {
type: 'allow-block',
Expand Down
141 changes: 141 additions & 0 deletions packages/core/test-app/e2e/plugins/position.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import test, { expect } from '@playwright/test';
import { get_mouse_position, setup } from '../test-utils';
import { SCHEMAS } from '../../src/lib/schemas';

test('undefined disables the plugin', async ({ page }) => {
await setup(page, 'plugins/position', SCHEMAS.PLUGINS.POSITION, undefined);

const div = page.getByTestId('draggable');

await div.hover();
const { x, y } = await get_mouse_position(page);
await page.mouse.down();
await page.mouse.move(x + 100, y + 100);
await page.mouse.up();

// This will drag as usual
await expect(div).toHaveCSS('translate', '100px 100px');
});

test('null disables the plugin', async ({ page }) => {
await setup(page, 'plugins/position', SCHEMAS.PLUGINS.POSITION, null);

const div = page.getByTestId('draggable');

await div.hover();
const { x, y } = await get_mouse_position(page);
await page.mouse.down();
await page.mouse.move(x + 100, y + 100);
await page.mouse.up();

// This will drag as usual
await expect(div).toHaveCSS('translate', '100px 100px');
});

test('default only', async ({ page }) => {
await setup(page, 'plugins/position', SCHEMAS.PLUGINS.POSITION, {
default: {
x: 20,
y: 80,
},
});

const div = page.getByTestId('draggable');

// Should be translated by `default`
await expect(div).toHaveCSS('translate', '20px 80px');

await div.hover();
const { x, y } = await get_mouse_position(page);
await page.mouse.down();
await page.mouse.move(x + 100, y + 100);
await page.mouse.up();

// This will drag as usual
await expect(div).toHaveCSS('translate', '120px 180px');
});

test('current only', async ({ page }) => {
await setup(page, 'plugins/position', SCHEMAS.PLUGINS.POSITION, {
current: {
x: 20,
y: 80,
},
});

const div = page.getByTestId('draggable');

// Should be translated by `default`
await expect(div).toHaveCSS('translate', '20px 80px');

await div.hover();
const { x, y } = await get_mouse_position(page);
await page.mouse.down();
await page.mouse.move(x + 100, y + 100);
await page.mouse.up();

// This will drag too
await expect(div).toHaveCSS('translate', '120px 180px');
});

test('current-default', async ({ page }) => {
await setup(page, 'plugins/position', SCHEMAS.PLUGINS.POSITION, {
default: {
x: 20,
y: 80,
},
current: {
x: 100,
y: 180,
},
});

const div = page.getByTestId('draggable');

// current ahould be prioritized over default
await expect(div).toHaveCSS('translate', '100px 180px');

await div.hover();
const { x, y } = await get_mouse_position(page);
await page.mouse.down();
await page.mouse.move(x + 100, y + 100);
await page.mouse.up();

// This will drag too
await expect(div).toHaveCSS('translate', '200px 280px');
});

test('two-way-binding', async ({ page }) => {
await setup(page, 'plugins/position', SCHEMAS.PLUGINS.POSITION, {
default: {
x: 20,
y: 80,
},
current: {
x: 100,
y: 180,
},
two_way_binding: true,
});

const div = page.getByTestId('draggable');

// current ahould be prioritized over default
await expect(div).toHaveCSS('translate', '100px 180px');

await div.hover();
const { x, y } = await get_mouse_position(page);
await page.mouse.down();
await page.mouse.move(x + 100, y + 100);
await page.mouse.up();

// This will drag too
await expect(div).toHaveCSS('translate', '200px 280px');

const xSlider = page.getByTestId('x-slider');
const ySlider = page.getByTestId('y-slider');

// Check their values, make sure theyre the same as drag translatye
await expect(xSlider).toHaveValue('200');
await expect(ySlider).toHaveValue('280');
});
3 changes: 2 additions & 1 deletion packages/core/test-app/src/lib/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const PAGES = {
"/plugins/bounds": `/plugins/bounds`,
"/plugins/controls": `/plugins/controls`,
"/plugins/grid": `/plugins/grid`,
"/plugins/position": `/plugins/position`,
"/plugins/threshold": `/plugins/threshold`,
"/plugins/transform": `/plugins/transform`
}
Expand Down Expand Up @@ -138,7 +139,7 @@ export function route<T extends keyof AllTypes>(key: T, ...params: any[]): strin
* ```
*/
export type KIT_ROUTES = {
PAGES: { '/': never, '/defaults': never, '/plugins/applyUserSelectHack': never, '/plugins/axis': never, '/plugins/bounds': never, '/plugins/controls': never, '/plugins/grid': never, '/plugins/threshold': never, '/plugins/transform': never }
PAGES: { '/': never, '/defaults': never, '/plugins/applyUserSelectHack': never, '/plugins/axis': never, '/plugins/bounds': never, '/plugins/controls': never, '/plugins/grid': never, '/plugins/position': never, '/plugins/threshold': never, '/plugins/transform': never }
SERVERS: Record<string, never>
ACTIONS: Record<string, never>
LINKS: Record<string, never>
Expand Down
22 changes: 22 additions & 0 deletions packages/core/test-app/src/lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,27 @@ export const SCHEMAS = {
'block-allow-block',
]),
}),

POSITION: z
.object({
two_way_binding: z.boolean().optional(),

default: z
.object({
x: z.number(),
y: z.number(),
})
.optional()
.nullable(),

current: z
.object({
x: z.number(),
y: z.number(),
})
.optional()
.nullable(),
})
.optional(),
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { extract_options_from_url } from '$lib/helpers.js';
import { SCHEMAS } from '$lib/schemas.js';

export function load({ request }) {
return extract_options_from_url(request, SCHEMAS.PLUGINS.POSITION);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script>
import Box from '$lib/Box.svelte';
import { events, position } from '../../../../../../src/plugins';
const { data } = $props();
let pos = $state(data?.current ?? { x: 0, y: 0 });
const plugins = $derived.by(() => {
if (data.two_way_binding) {
return [
position({
current: $state.snapshot(pos),
default: data.current,
}),
events({
onDrag: ({ offset }) => {
pos.x = offset.x;
pos.y = offset.y;
},
}),
];
}
return [
position({
default: data.default,
current: $state.snapshot(pos),
}),
];
});
</script>
<Box testid="draggable" {plugins}></Box>
<label>
X:
<input type="range" min="0" max="1000" bind:value={pos.x} data-testid="x-slider" />
</label>
<label>
Y:
<input type="range" min="0" max="1000" bind:value={pos.y} data-testid="y-slider" />
</label>
3 changes: 2 additions & 1 deletion playground/svelte/src/routes/modular/position/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { draggable, events, position } from '@neodrag/svelte';
import { disabled, draggable, events, position } from '@neodrag/svelte';
let pos = $state({ x: 0, y: 0 });
Expand All @@ -16,6 +16,7 @@
y: pos.y,
},
}),
disabled(),
]);
</script>

Expand Down

0 comments on commit 466461f

Please sign in to comment.