@expofp/ui 3.43.0 → 3.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -130,7 +130,7 @@ const Overlay = ({ children, open, size = 'medium', particles, className, onChan
|
|
|
130
130
|
const { onPointerDown } = useSheetDrag({
|
|
131
131
|
enabled: mobile && !disableDrag && !forceFull,
|
|
132
132
|
panelRef: overlayPanelRef,
|
|
133
|
-
|
|
133
|
+
scrollElement,
|
|
134
134
|
snapPoints,
|
|
135
135
|
current: currentSnap,
|
|
136
136
|
openedAtRef,
|
|
@@ -6,7 +6,8 @@ export interface SnapPoint<T extends string> {
|
|
|
6
6
|
interface UseSheetDragOptions<T extends string> {
|
|
7
7
|
enabled: boolean;
|
|
8
8
|
panelRef: RefObject<HTMLDivElement | null>;
|
|
9
|
-
|
|
9
|
+
/** An element, not a ref: the touchmove listener has to follow it as it mounts. */
|
|
10
|
+
scrollElement: HTMLDivElement | null;
|
|
10
11
|
/** Ascending by height; the last one is the tallest. */
|
|
11
12
|
snapPoints: readonly SnapPoint<T>[];
|
|
12
13
|
current: T;
|
|
@@ -20,7 +21,7 @@ interface UseSheetDragOptions<T extends string> {
|
|
|
20
21
|
onSettle: () => void;
|
|
21
22
|
onHandleTap: () => void;
|
|
22
23
|
}
|
|
23
|
-
export declare function useSheetDrag<T extends string>({ enabled, panelRef,
|
|
24
|
+
export declare function useSheetDrag<T extends string>({ enabled, panelRef, scrollElement, snapPoints, current, openedAtRef, onDragStart, onSnap, onSettle, onHandleTap, }: UseSheetDragOptions<T>): {
|
|
24
25
|
onPointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
|
|
25
26
|
};
|
|
26
27
|
export {};
|
|
@@ -31,7 +31,20 @@ const readOffset = (panel) => {
|
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
|
-
|
|
34
|
+
/** Whether a horizontal scroller — a chip row, a gallery strip — sits under the finger. */
|
|
35
|
+
const scrollsSideways = (target, panel) => {
|
|
36
|
+
let element = target instanceof Element ? target : null;
|
|
37
|
+
while (element && panel?.contains(element)) {
|
|
38
|
+
if (element.scrollWidth > element.clientWidth) {
|
|
39
|
+
const { overflowX } = getComputedStyle(element);
|
|
40
|
+
if (overflowX === 'auto' || overflowX === 'scroll')
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
element = element.parentElement;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
47
|
+
export function useSheetDrag({ enabled, panelRef, scrollElement, snapPoints, current, openedAtRef, onDragStart, onSnap, onSettle, onHandleTap, }) {
|
|
35
48
|
const gestureRef = useRef(null);
|
|
36
49
|
const scrollBlockedAtRef = useRef(0);
|
|
37
50
|
const settleRef = useRef(null);
|
|
@@ -142,11 +155,44 @@ export function useSheetDrag({ enabled, panelRef, scrollRef, snapPoints, current
|
|
|
142
155
|
const startOffset = liveOffset ?? offsetOf(stateRef.current.current);
|
|
143
156
|
const gesture = {
|
|
144
157
|
pointerId: event.pointerId,
|
|
158
|
+
startX: event.clientX,
|
|
145
159
|
startY: event.clientY,
|
|
160
|
+
lastY: event.clientY,
|
|
146
161
|
startTime: performance.now(),
|
|
147
162
|
startOffset,
|
|
148
163
|
offset: startOffset,
|
|
149
164
|
committed: false,
|
|
165
|
+
commit: (clientX, clientY) => {
|
|
166
|
+
if (gesture.committed)
|
|
167
|
+
return true;
|
|
168
|
+
const deltaX = clientX - gesture.startX;
|
|
169
|
+
const deltaY = clientY - gesture.startY;
|
|
170
|
+
if (Math.hypot(deltaX, deltaY) < MOVE_TOLERANCE)
|
|
171
|
+
return false;
|
|
172
|
+
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
173
|
+
// A sideways move belongs to a chip row or gallery strip under the finger. Anywhere
|
|
174
|
+
// else it is the finger settling, which says nothing about the direction yet.
|
|
175
|
+
if (scrollsSideways(event.target, panelRef.current))
|
|
176
|
+
stop();
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (!shouldDrag(event.target, deltaY > 0)) {
|
|
180
|
+
stop();
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
gesture.committed = true;
|
|
184
|
+
try {
|
|
185
|
+
panelRef.current?.setPointerCapture(gesture.pointerId);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Throws when the pointer is already gone; the window listeners
|
|
189
|
+
// below carry the gesture either way.
|
|
190
|
+
}
|
|
191
|
+
if (scrollElement)
|
|
192
|
+
scrollElement.style.overflow = 'hidden';
|
|
193
|
+
callbacksRef.current.onDragStart();
|
|
194
|
+
return true;
|
|
195
|
+
},
|
|
150
196
|
};
|
|
151
197
|
gestureRef.current = gesture;
|
|
152
198
|
const stop = () => {
|
|
@@ -161,9 +207,8 @@ export function useSheetDrag({ enabled, panelRef, scrollRef, snapPoints, current
|
|
|
161
207
|
if (endEvent.pointerId !== gesture.pointerId)
|
|
162
208
|
return;
|
|
163
209
|
stop();
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
scrollEl.style.removeProperty('overflow');
|
|
210
|
+
if (scrollElement)
|
|
211
|
+
scrollElement.style.removeProperty('overflow');
|
|
167
212
|
if (!gesture.committed) {
|
|
168
213
|
const target = event.target;
|
|
169
214
|
if (target instanceof Element && target.closest('[data-sheet-handle]')) {
|
|
@@ -171,7 +216,7 @@ export function useSheetDrag({ enabled, panelRef, scrollRef, snapPoints, current
|
|
|
171
216
|
}
|
|
172
217
|
return;
|
|
173
218
|
}
|
|
174
|
-
const distance = gesture.startY -
|
|
219
|
+
const distance = gesture.startY - gesture.lastY;
|
|
175
220
|
const elapsed = Math.max(performance.now() - gesture.startTime, 1);
|
|
176
221
|
const velocity = Math.abs(distance) / elapsed;
|
|
177
222
|
const target = resolveTarget(gesture.offset, distance, velocity);
|
|
@@ -182,27 +227,10 @@ export function useSheetDrag({ enabled, panelRef, scrollRef, snapPoints, current
|
|
|
182
227
|
const handleMove = (moveEvent) => {
|
|
183
228
|
if (moveEvent.pointerId !== gesture.pointerId)
|
|
184
229
|
return;
|
|
230
|
+
gesture.lastY = moveEvent.clientY;
|
|
231
|
+
if (!gesture.commit(moveEvent.clientX, moveEvent.clientY))
|
|
232
|
+
return;
|
|
185
233
|
const delta = moveEvent.clientY - gesture.startY;
|
|
186
|
-
if (!gesture.committed) {
|
|
187
|
-
if (Math.abs(delta) < MOVE_TOLERANCE)
|
|
188
|
-
return;
|
|
189
|
-
if (!shouldDrag(event.target, delta > 0)) {
|
|
190
|
-
stop();
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
gesture.committed = true;
|
|
194
|
-
try {
|
|
195
|
-
panelRef.current?.setPointerCapture(gesture.pointerId);
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
// Throws when the pointer is already gone; the window listeners
|
|
199
|
-
// below carry the gesture either way.
|
|
200
|
-
}
|
|
201
|
-
const scrollEl = scrollRef.current;
|
|
202
|
-
if (scrollEl)
|
|
203
|
-
scrollEl.style.overflow = 'hidden';
|
|
204
|
-
callbacksRef.current.onDragStart();
|
|
205
|
-
}
|
|
206
234
|
const limit = offsetOf(stateRef.current.snapPoints[0].name);
|
|
207
235
|
const raw = gesture.startOffset + delta;
|
|
208
236
|
const offset = raw < 0 ? -dampen(-raw) : raw > limit ? limit + dampen(raw - limit) : raw;
|
|
@@ -219,9 +247,28 @@ export function useSheetDrag({ enabled, panelRef, scrollRef, snapPoints, current
|
|
|
219
247
|
panelRef,
|
|
220
248
|
resolveTarget,
|
|
221
249
|
scheduleSettle,
|
|
222
|
-
|
|
250
|
+
scrollElement,
|
|
223
251
|
shouldDrag,
|
|
224
252
|
writeOffset,
|
|
225
253
|
]);
|
|
254
|
+
// Non-passive, so bound natively (React's touch listeners are passive), and in place before the
|
|
255
|
+
// touch starts. A drag that starts on the content must keep the browser from scrolling it: a
|
|
256
|
+
// native scroll takes the touch over and cancels the pointer mid-drag (#1686). Once that scroll
|
|
257
|
+
// starts, touchmoves are no longer cancelable, and a pointermove can arrive a frame after its
|
|
258
|
+
// touchmove, so the drag is decided here as well.
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (!scrollElement || !enabled)
|
|
261
|
+
return;
|
|
262
|
+
const handleTouchMove = (event) => {
|
|
263
|
+
const gesture = gestureRef.current;
|
|
264
|
+
const touch = event.targetTouches[0];
|
|
265
|
+
if (!gesture || !touch || !event.cancelable)
|
|
266
|
+
return;
|
|
267
|
+
if (gesture.commit(touch.clientX, touch.clientY))
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
};
|
|
270
|
+
scrollElement.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
271
|
+
return () => scrollElement.removeEventListener('touchmove', handleTouchMove);
|
|
272
|
+
}, [scrollElement, enabled]);
|
|
226
273
|
return { onPointerDown };
|
|
227
274
|
}
|