bc518174
王天杨
提交两个项目文件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
import * as React from 'react';
import { RemoveScrollBar } from 'react-remove-scroll-bar';
import { styleSingleton } from 'react-style-singleton';
import { nonPassive } from './aggresiveCapture';
import { handleScroll, locationCouldBeScrolled } from './handleScroll';
export const getTouchXY = (event) => 'changedTouches' in event ? [event.changedTouches[0].clientX, event.changedTouches[0].clientY] : [0, 0];
export const getDeltaXY = (event) => [event.deltaX, event.deltaY];
const extractRef = (ref) => ref && 'current' in ref ? ref.current : ref;
const deltaCompare = (x, y) => x[0] === y[0] && x[1] === y[1];
const generateStyle = (id) => `
.block-interactivity-${id} {pointer-events: none;}
.allow-interactivity-${id} {pointer-events: all;}
`;
let idCounter = 0;
let lockStack = [];
export function RemoveScrollSideCar(props) {
const shouldPreventQueue = React.useRef([]);
const touchStartRef = React.useRef([0, 0]);
const activeAxis = React.useRef();
const [id] = React.useState(idCounter++);
const [Style] = React.useState(styleSingleton);
const lastProps = React.useRef(props);
React.useEffect(() => {
lastProps.current = props;
}, [props]);
React.useEffect(() => {
if (props.inert) {
document.body.classList.add(`block-interactivity-${id}`);
const allow = [props.lockRef.current, ...(props.shards || []).map(extractRef)].filter(Boolean);
allow.forEach((el) => el.classList.add(`allow-interactivity-${id}`));
return () => {
document.body.classList.remove(`block-interactivity-${id}`);
allow.forEach((el) => el.classList.remove(`allow-interactivity-${id}`));
};
}
return;
}, [props.inert, props.lockRef.current, props.shards]);
const shouldCancelEvent = React.useCallback((event, parent) => {
if (('touches' in event && event.touches.length === 2) || (event.type === 'wheel' && event.ctrlKey)) {
return !lastProps.current.allowPinchZoom;
}
const touch = getTouchXY(event);
const touchStart = touchStartRef.current;
const deltaX = 'deltaX' in event ? event.deltaX : touchStart[0] - touch[0];
const deltaY = 'deltaY' in event ? event.deltaY : touchStart[1] - touch[1];
let currentAxis;
const target = event.target;
const moveDirection = Math.abs(deltaX) > Math.abs(deltaY) ? 'h' : 'v';
// allow horizontal touch move on Range inputs. They will not cause any scroll
if ('touches' in event && moveDirection === 'h' && target.type === 'range') {
return false;
}
// allow drag selection (iOS); check if selection's anchorNode is the same as target or contains target
const selection = window.getSelection();
const anchorNode = selection && selection.anchorNode;
const isTouchingSelection = anchorNode ? anchorNode === target || anchorNode.contains(target) : false;
if (isTouchingSelection) {
return false;
}
let canBeScrolledInMainDirection = locationCouldBeScrolled(moveDirection, target);
if (!canBeScrolledInMainDirection) {
return true;
}
if (canBeScrolledInMainDirection) {
currentAxis = moveDirection;
}
else {
currentAxis = moveDirection === 'v' ? 'h' : 'v';
canBeScrolledInMainDirection = locationCouldBeScrolled(moveDirection, target);
// other axis might be not scrollable
}
if (!canBeScrolledInMainDirection) {
return false;
}
if (!activeAxis.current && 'changedTouches' in event && (deltaX || deltaY)) {
activeAxis.current = currentAxis;
}
if (!currentAxis) {
return true;
}
const cancelingAxis = activeAxis.current || currentAxis;
return handleScroll(cancelingAxis, parent, event, cancelingAxis === 'h' ? deltaX : deltaY, true);
}, []);
const shouldPrevent = React.useCallback((_event) => {
const event = _event;
if (!lockStack.length || lockStack[lockStack.length - 1] !== Style) {
// not the last active
return;
}
const delta = 'deltaY' in event ? getDeltaXY(event) : getTouchXY(event);
const sourceEvent = shouldPreventQueue.current.filter((e) => e.name === event.type && (e.target === event.target || event.target === e.shadowParent) && deltaCompare(e.delta, delta))[0];
// self event, and should be canceled
if (sourceEvent && sourceEvent.should) {
if (event.cancelable) {
event.preventDefault();
}
return;
}
// outside or shard event
if (!sourceEvent) {
const shardNodes = (lastProps.current.shards || [])
.map(extractRef)
.filter(Boolean)
.filter((node) => node.contains(event.target));
const shouldStop = shardNodes.length > 0 ? shouldCancelEvent(event, shardNodes[0]) : !lastProps.current.noIsolation;
if (shouldStop) {
if (event.cancelable) {
event.preventDefault();
}
}
}
}, []);
const shouldCancel = React.useCallback((name, delta, target, should) => {
const event = { name, delta, target, should, shadowParent: getOutermostShadowParent(target) };
shouldPreventQueue.current.push(event);
setTimeout(() => {
shouldPreventQueue.current = shouldPreventQueue.current.filter((e) => e !== event);
}, 1);
}, []);
const scrollTouchStart = React.useCallback((event) => {
touchStartRef.current = getTouchXY(event);
activeAxis.current = undefined;
}, []);
const scrollWheel = React.useCallback((event) => {
shouldCancel(event.type, getDeltaXY(event), event.target, shouldCancelEvent(event, props.lockRef.current));
}, []);
const scrollTouchMove = React.useCallback((event) => {
shouldCancel(event.type, getTouchXY(event), event.target, shouldCancelEvent(event, props.lockRef.current));
}, []);
React.useEffect(() => {
lockStack.push(Style);
props.setCallbacks({
onScrollCapture: scrollWheel,
onWheelCapture: scrollWheel,
onTouchMoveCapture: scrollTouchMove,
});
document.addEventListener('wheel', shouldPrevent, nonPassive);
document.addEventListener('touchmove', shouldPrevent, nonPassive);
document.addEventListener('touchstart', scrollTouchStart, nonPassive);
return () => {
lockStack = lockStack.filter((inst) => inst !== Style);
document.removeEventListener('wheel', shouldPrevent, nonPassive);
document.removeEventListener('touchmove', shouldPrevent, nonPassive);
document.removeEventListener('touchstart', scrollTouchStart, nonPassive);
};
}, []);
const { removeScrollBar, inert } = props;
return (React.createElement(React.Fragment, null,
inert ? React.createElement(Style, { styles: generateStyle(id) }) : null,
removeScrollBar ? React.createElement(RemoveScrollBar, { noRelative: props.noRelative, gapMode: props.gapMode }) : null));
}
function getOutermostShadowParent(node) {
let shadowParent = null;
while (node !== null) {
if (node instanceof ShadowRoot) {
shadowParent = node.host;
node = node.host;
}
node = node.parentNode;
}
return shadowParent;
}
|