Skip to content

Commit 3b109e8

Browse files
authored
Merge pull request #374 from Found-L/fix/mobile-drag-gestures
fix: support mobile drag gestures
2 parents 50f30dc + 8e5ff65 commit 3b109e8

4 files changed

Lines changed: 257 additions & 161 deletions

File tree

src/extensions/Image/components/ImageView.tsx

Lines changed: 72 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NodeViewWrapper } from '@tiptap/react';
22
import { clamp, isNumber, throttle } from 'lodash-es';
3-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
3+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
44

55
import { IMAGE_MAX_SIZE, IMAGE_MIN_SIZE, IMAGE_THROTTLE_WAIT_TIME } from '@/constants';
66

@@ -17,6 +17,8 @@ const ResizeDirection = {
1717
};
1818

1919
function ImageView(props: any) {
20+
const { updateAttributes } = props;
21+
2022
const [maxSize, setMaxSize] = useState<Size>({
2123
width: IMAGE_MAX_SIZE,
2224
height: IMAGE_MAX_SIZE,
@@ -36,13 +38,12 @@ function ImageView(props: any) {
3638

3739
const [resizing, setResizing] = useState<boolean>(false);
3840

39-
const [resizerState, setResizerState] = useState({
40-
x: 0,
41-
y: 0,
42-
w: 0,
43-
h: 0,
44-
dir: '',
45-
});
41+
const resizeSession = useRef<{
42+
pointerId: number;
43+
x: number;
44+
width: number;
45+
direction: string;
46+
} | null>(null);
4647

4748
const { align, inline } = props?.node?.attrs;
4849
const isBlockNode = props?.node?.type?.name === 'imageBlock';
@@ -109,10 +110,13 @@ function ImageView(props: any) {
109110
[props?.editor]
110111
);
111112

112-
function onMouseDown(e: MouseEvent, dir: string) {
113+
function onPointerDown(e: React.PointerEvent<HTMLSpanElement>, dir: string) {
114+
if (!e.isPrimary || (e.pointerType === 'mouse' && e.button !== 0)) {
115+
return;
116+
}
117+
113118
e.preventDefault();
114119
e.stopPropagation();
115-
116120
const originalWidth = originalSize.width;
117121
const originalHeight = originalSize.height;
118122
const aspectRatio = originalWidth / originalHeight;
@@ -134,85 +138,74 @@ function ImageView(props: any) {
134138
width = width > maxWidth ? maxWidth : width;
135139
}
136140

137-
setResizing(true);
138-
139-
setResizerState({
141+
resizeSession.current = {
142+
pointerId: e.pointerId,
140143
x: e.clientX,
141-
y: e.clientY,
142-
w: width,
143-
h: height,
144-
dir,
145-
});
144+
width,
145+
direction: dir,
146+
};
147+
e.currentTarget.setPointerCapture(e.pointerId);
148+
setResizing(true);
146149
}
147150

148-
const onMouseMove = useCallback(
149-
throttle((e: MouseEvent) => {
150-
e.preventDefault();
151-
e.stopPropagation();
152-
153-
if (!resizing) {
154-
return;
155-
}
156-
157-
const { x, w, dir } = resizerState;
158-
159-
const dx = (e.clientX - x) * (/l/.test(dir) ? -1 : 1);
160-
// const dy = (e.clientY - y) * (/t/.test(dir) ? -1 : 1)
161-
162-
const width = clamp(w + dx, IMAGE_MIN_SIZE, maxSize.width);
163-
const height = null;
164-
165-
props.updateAttributes({
166-
width,
167-
height,
168-
});
169-
}, IMAGE_THROTTLE_WAIT_TIME),
170-
[resizing, resizerState, maxSize, props.updateAttributes]
151+
const resizeToPointer = useMemo(
152+
() =>
153+
throttle((clientX: number, pointerId: number) => {
154+
const session = resizeSession.current;
155+
if (!session || session.pointerId !== pointerId) {
156+
return;
157+
}
158+
159+
const dx = (clientX - session.x) * (/l/.test(session.direction) ? -1 : 1);
160+
const width = clamp(session.width + dx, IMAGE_MIN_SIZE, maxSize.width);
161+
162+
updateAttributes({
163+
width,
164+
height: null,
165+
});
166+
}, IMAGE_THROTTLE_WAIT_TIME),
167+
[maxSize.width, updateAttributes]
171168
);
172169

173-
const onMouseUp = useCallback(
174-
(e: MouseEvent) => {
175-
e.preventDefault();
176-
e.stopPropagation();
177-
if (!resizing) {
178-
return;
179-
}
180-
181-
setResizerState({
182-
x: 0,
183-
y: 0,
184-
w: 0,
185-
h: 0,
186-
dir: '',
187-
});
188-
setResizing(false);
170+
function onPointerMove(e: React.PointerEvent<HTMLSpanElement>) {
171+
if (resizeSession.current?.pointerId !== e.pointerId) {
172+
return;
173+
}
189174

190-
selectImage();
191-
},
192-
[resizing, selectImage]
193-
);
175+
e.preventDefault();
176+
e.stopPropagation();
177+
resizeToPointer(e.clientX, e.pointerId);
178+
}
194179

195-
const onEvents = useCallback(() => {
196-
document?.addEventListener('mousemove', onMouseMove, true);
197-
document?.addEventListener('mouseup', onMouseUp, true);
198-
}, [onMouseMove, onMouseUp]);
180+
function finishResize(e: React.PointerEvent<HTMLSpanElement>, updateFinalPosition: boolean) {
181+
if (resizeSession.current?.pointerId !== e.pointerId) {
182+
return;
183+
}
199184

200-
const offEvents = useCallback(() => {
201-
document?.removeEventListener('mousemove', onMouseMove, true);
202-
document?.removeEventListener('mouseup', onMouseUp, true);
203-
}, [onMouseMove, onMouseUp]);
185+
e.preventDefault();
186+
e.stopPropagation();
204187

205-
useEffect(() => {
206-
if (resizing) {
207-
onEvents();
188+
if (updateFinalPosition) {
189+
resizeToPointer(e.clientX, e.pointerId);
190+
resizeToPointer.flush();
208191
} else {
209-
offEvents();
192+
resizeToPointer.cancel();
193+
}
194+
195+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
196+
e.currentTarget.releasePointerCapture(e.pointerId);
210197
}
211198

199+
resizeSession.current = null;
200+
setResizing(false);
201+
selectImage();
202+
}
203+
204+
useEffect(() => {
212205
return () => {
213-
offEvents();
206+
resizeToPointer.cancel();
214207
};
215-
}, [resizing, onEvents, offEvents]);
208+
}, [resizeToPointer]);
216209

217210
const resizeOb: ResizeObserver = useMemo(() => {
218211
return new ResizeObserver(() => getMaxSize());
@@ -264,7 +257,10 @@ function ImageView(props: any) {
264257
<span
265258
className={`image-resizer__handler image-resizer__handler--${direction}`}
266259
key={`image-dir-${direction}`}
267-
onMouseDown={(e: any) => onMouseDown(e, direction)}
260+
onPointerCancel={(e) => finishResize(e, false)}
261+
onPointerDown={(e) => onPointerDown(e, direction)}
262+
onPointerMove={onPointerMove}
263+
onPointerUp={(e) => finishResize(e, true)}
268264
></span>
269265
);
270266
})}

0 commit comments

Comments
 (0)