Skip to content

Commit 1673bd6

Browse files
committed
feat: Finished use-move.
1 parent 9c139f3 commit 1673bd6

File tree

9 files changed

+251
-21
lines changed

9 files changed

+251
-21
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Based on the [@mantine/hooks](https://github.com/mantinedev/mantine/tree/master/
9494
- [ ] use-merged-ref
9595
- [x] use-mounted
9696
- [x] use-mouse
97-
- [ ] use-move
97+
- [x] use-move
9898
- [ ] use-mutation-observer
9999
- [x] use-network
100100
- [x] use-orientation

dev/components/examples/example-base.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ export function ExampleBase(props: FlowProps<ExampleBaseProps>) {
1616
<div class="flex w-full items-center justify-between">
1717
<h2 class="text-xl font-bold">{props.title}</h2>
1818
<button
19-
class={`rounded-md border p-2 transition active:scale-95 ${viewing() === 'code' ? 'bg-background text-white' : ''}`}
19+
class={`flex h-8 w-8 items-center justify-center rounded-md border transition active:scale-95 ${viewing() === 'code' ? 'bg-background text-white' : ''}`}
2020
onClick={() => {
2121
setViewing(viewing() === 'code' ? 'result' : 'code');
2222
}}
2323
>
24-
<IconCode class="h-5 w-5" />
24+
<IconCode class="h-4 w-4" />
2525
</button>
2626
</div>
2727

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
```tsx
2+
const [value, setValue] = createSignal({ x: 0.5, y: 0.5 });
3+
const { ref, active } = useMove(({ x, y }) => {
4+
setValue({ x, y });
5+
}, {});
6+
7+
return (
8+
<div>
9+
<div
10+
ref={ref}
11+
style={{
12+
position: 'relative',
13+
width: '400px',
14+
height: '120px',
15+
backgrund: '#3b82f650',
16+
}}
17+
>
18+
<div
19+
style={{
20+
position: 'absolute',
21+
left: `calc(${value().x * 100}% - ${8}px)`,
22+
top: `calc(${value().y * 100}% - ${8}px)`,
23+
width: '16px',
24+
height: '16px',
25+
'background-color': active() ? '#22c55e' : '#3b82f6',
26+
}}
27+
/>
28+
</div>
29+
</div>
30+
);
31+
```
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useMove } from 'src';
2+
import { ExampleBase } from '../example-base';
3+
import Code from './use-move.code.mdx';
4+
5+
import { createSignal } from 'solid-js';
6+
import { useMDXComponents } from 'solid-jsx';
7+
8+
export function UseMoveExample() {
9+
const [value, setValue] = createSignal({ x: 0.5, y: 0.5 });
10+
const { ref, active } = useMove(({ x, y }) => {
11+
setValue({ x, y });
12+
}, {});
13+
14+
// @ts-ignore
15+
const components: any = useMDXComponents();
16+
17+
return (
18+
<ExampleBase
19+
title="useMove"
20+
description={
21+
<>
22+
handles move behavior over any element inside the constraints of a ref.
23+
<br />
24+
<br />
25+
Can be used to make custom sliders, color pickers, and draggable elements within a
26+
container.
27+
</>
28+
}
29+
code={<Code components={components} />}
30+
>
31+
<div class="flex h-full w-full flex-col items-center justify-center gap-x-1 gap-y-3 rounded-md border p-3 py-10 text-center">
32+
<div
33+
ref={ref}
34+
class="h-40 w-full rounded bg-blue-400/50"
35+
style={{
36+
position: 'relative',
37+
}}
38+
>
39+
<div
40+
style={{
41+
position: 'absolute',
42+
left: `calc(${value().x * 100}% - ${8}px)`,
43+
top: `calc(${value().y * 100}% - ${8}px)`,
44+
width: '16px',
45+
height: '16px',
46+
'background-color': active() ? '#22c55e' : '#3b82f6',
47+
}}
48+
/>
49+
</div>
50+
51+
<div class="flex justify-center">
52+
Values:{' '}
53+
<code class="rounded-md bg-neutral-300 px-1.5 py-0.5">
54+
{JSON.stringify({
55+
x: value().x.toFixed(2),
56+
y: value().y.toFixed(2),
57+
})}
58+
</code>
59+
</div>
60+
</div>
61+
</ExampleBase>
62+
);
63+
}

dev/pages/index/+Page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { UseLocalStorageExample } from 'dev/components/examples/use-local-storag
1818
import { UseMediaQueryExample } from 'dev/components/examples/use-media-query/use-media-query.example';
1919
import { UseMountedExample } from 'dev/components/examples/use-mounted/use-mounted.example';
2020
import { UseMouseExample } from 'dev/components/examples/use-mouse/use-mouse.example';
21+
import { UseMoveExample } from 'dev/components/examples/use-move/use-move.example';
2122
import { UseNetworkExample } from 'dev/components/examples/use-network/use-network.example';
2223
import { UseOrientationExample } from 'dev/components/examples/use-orientation/use-orientation.example';
2324
import { UseOsExample } from 'dev/components/examples/use-os/use-os.example';
@@ -123,6 +124,10 @@ export default function HomePage() {
123124
title: 'useMouse',
124125
example: <UseMouseExample />,
125126
},
127+
{
128+
title: 'useMove',
129+
example: <UseMoveExample />,
130+
},
126131
];
127132

128133
const filteredList = createMemo(() => {
@@ -243,7 +248,7 @@ export default function HomePage() {
243248
<input
244249
ref={searchInputRef}
245250
value={searchInput()}
246-
class="relative w-full rounded-md p-2.5 px-4"
251+
class="pjx-4 relative w-full rounded-md p-2.5"
247252
onInput={e => {
248253
setSearchInput(e.currentTarget.value);
249254
}}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from './use-local-storage/use-local-storage';
1313
export * from './use-media-query/use-media-query';
1414
export * from './use-mounted/use-mounted';
1515
export * from './use-mouse/use-mouse';
16+
export * from './use-move/use-move';
1617
export * from './use-network/use-network';
1718
export * from './use-orientation/use-orientation';
1819
export * from './use-os/use-os';

src/use-counter/use-counter.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
1-
// ./clamp.ts
2-
3-
function clamp(value: number, min: number | undefined, max: number | undefined) {
4-
if (min === undefined && max === undefined) {
5-
return value;
6-
}
7-
8-
if (min !== undefined && max === undefined) {
9-
return Math.max(value, min);
10-
}
11-
12-
if (min === undefined && max !== undefined) {
13-
return Math.min(value, max);
14-
}
15-
16-
return Math.min(Math.max(value, min!), max!);
17-
}
1+
import { clamp } from '../utils/clamp';
182

193
// ./use-counter.ts
204
import { createSignal } from 'solid-js';

src/use-move/use-move.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { createEffect, createSignal, onCleanup, onMount } from 'solid-js';
2+
import { clamp } from '../utils/clamp';
3+
4+
export interface UseMovePosition {
5+
x: number;
6+
y: number;
7+
}
8+
9+
export function clampUseMovePosition(position: UseMovePosition) {
10+
return {
11+
x: clamp(position.x, 0, 1),
12+
y: clamp(position.y, 0, 1),
13+
};
14+
}
15+
16+
interface useMoveHandlers {
17+
onScrubStart?: () => void;
18+
onScrubEnd?: () => void;
19+
}
20+
21+
export function useMove<T extends HTMLElement = HTMLDivElement>(
22+
onChange: (value: UseMovePosition) => void,
23+
handlers?: useMoveHandlers,
24+
dir: 'ltr' | 'rtl' = 'ltr',
25+
) {
26+
const [ref, setRef] = createSignal<T>();
27+
let mounted = false;
28+
let isSliding = false;
29+
let frame = 0;
30+
const [active, setActive] = createSignal(false);
31+
32+
onMount(() => {
33+
mounted = true;
34+
});
35+
36+
createEffect(() => {
37+
const onScrub = ({ x, y }: UseMovePosition) => {
38+
cancelAnimationFrame(frame);
39+
40+
frame = requestAnimationFrame(() => {
41+
if (mounted && ref()) {
42+
ref()!.style.userSelect = 'none';
43+
const rect = ref()!.getBoundingClientRect();
44+
45+
if (rect.width && rect.height) {
46+
const _x = clamp((x - rect.left) / rect.width, 0, 1);
47+
48+
onChange({
49+
x: dir === 'ltr' ? _x : 1 - _x,
50+
y: clamp((y - rect.top) / rect.height, 0, 1),
51+
});
52+
}
53+
}
54+
});
55+
};
56+
57+
const bindEvents = () => {
58+
document.addEventListener('mousemove', onMouseMove);
59+
document.addEventListener('mouseup', stopScrubbing);
60+
document.addEventListener('touchmove', onTouchMove);
61+
document.addEventListener('touchend', stopScrubbing);
62+
};
63+
64+
const unbindEvents = () => {
65+
document.removeEventListener('mousemove', onMouseMove);
66+
document.removeEventListener('mouseup', stopScrubbing);
67+
document.removeEventListener('touchmove', onTouchMove);
68+
document.removeEventListener('touchend', stopScrubbing);
69+
};
70+
71+
const startScrubbing = () => {
72+
if (!isSliding && mounted) {
73+
isSliding = true;
74+
typeof handlers?.onScrubStart === 'function' && handlers.onScrubStart();
75+
setActive(true);
76+
bindEvents();
77+
}
78+
};
79+
80+
const stopScrubbing = () => {
81+
if (isSliding && mounted) {
82+
isSliding = false;
83+
setActive(false);
84+
unbindEvents();
85+
setTimeout(() => {
86+
typeof handlers?.onScrubEnd === 'function' && handlers.onScrubEnd();
87+
}, 0);
88+
}
89+
};
90+
91+
const onMouseDown = (event: MouseEvent) => {
92+
startScrubbing();
93+
event.preventDefault();
94+
onMouseMove(event);
95+
};
96+
97+
const onMouseMove = (event: MouseEvent) => onScrub({ x: event.clientX, y: event.clientY });
98+
99+
const onTouchStart = (event: TouchEvent) => {
100+
if (event.cancelable) {
101+
event.preventDefault();
102+
}
103+
104+
startScrubbing();
105+
onTouchMove(event);
106+
};
107+
108+
const onTouchMove = (event: TouchEvent) => {
109+
if (event.cancelable) {
110+
event.preventDefault();
111+
}
112+
113+
onScrub({
114+
x: event?.changedTouches?.[0]?.clientX ?? 0,
115+
y: event?.changedTouches?.[0]?.clientY ?? 0,
116+
});
117+
};
118+
119+
ref()?.addEventListener('mousedown', onMouseDown);
120+
ref()?.addEventListener('touchstart', onTouchStart, { passive: false });
121+
122+
onCleanup(() => {
123+
if (ref()) {
124+
ref()!.removeEventListener('mousedown', onMouseDown);
125+
ref()!.removeEventListener('touchstart', onTouchStart);
126+
}
127+
});
128+
});
129+
130+
return { ref: setRef, active };
131+
}

src/utils/clamp.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function clamp(value: number, min: number | undefined, max: number | undefined) {
2+
if (min === undefined && max === undefined) {
3+
return value;
4+
}
5+
6+
if (min !== undefined && max === undefined) {
7+
return Math.max(value, min);
8+
}
9+
10+
if (min === undefined && max !== undefined) {
11+
return Math.min(value, max);
12+
}
13+
14+
return Math.min(Math.max(value, min!), max!);
15+
}

0 commit comments

Comments
 (0)