@octanejs/aria 0.0.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.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/package.json +60 -0
- package/src/index.ts +52 -0
- package/src/interactions/PressResponder.ts +54 -0
- package/src/interactions/Pressable.ts +37 -0
- package/src/interactions/context.ts +20 -0
- package/src/interactions/createEventHandler.ts +94 -0
- package/src/interactions/focusSafely.ts +40 -0
- package/src/interactions/textSelection.ts +96 -0
- package/src/interactions/useFocus.ts +114 -0
- package/src/interactions/useFocusVisible.ts +456 -0
- package/src/interactions/useFocusWithin.ts +165 -0
- package/src/interactions/useFocusable.ts +205 -0
- package/src/interactions/useHover.ts +268 -0
- package/src/interactions/useInteractOutside.ts +161 -0
- package/src/interactions/useKeyboard.ts +50 -0
- package/src/interactions/useLongPress.ts +159 -0
- package/src/interactions/useMove.ts +281 -0
- package/src/interactions/usePress.ts +1249 -0
- package/src/interactions/useScrollWheel.ts +55 -0
- package/src/interactions/utils.ts +212 -0
- package/src/internal.ts +57 -0
- package/src/ssr/SSRProvider.ts +72 -0
- package/src/stately/flags.ts +21 -0
- package/src/stately/index.ts +7 -0
- package/src/stately/utils/number.ts +70 -0
- package/src/stately/utils/useControlledState.ts +75 -0
- package/src/utils/chain.ts +14 -0
- package/src/utils/constants.ts +5 -0
- package/src/utils/domHelpers.ts +36 -0
- package/src/utils/focusWithoutScrolling.ts +83 -0
- package/src/utils/getNonce.ts +53 -0
- package/src/utils/isElementVisible.ts +66 -0
- package/src/utils/isFocusable.ts +57 -0
- package/src/utils/isScrollable.ts +21 -0
- package/src/utils/isVirtualEvent.ts +47 -0
- package/src/utils/mergeProps.ts +76 -0
- package/src/utils/mergeRefs.ts +48 -0
- package/src/utils/openLink.ts +231 -0
- package/src/utils/platform.ts +74 -0
- package/src/utils/runAfterTransition.ts +114 -0
- package/src/utils/shadowdom/DOMFunctions.ts +100 -0
- package/src/utils/shadowdom/ShadowTreeWalker.ts +303 -0
- package/src/utils/useDescription.ts +62 -0
- package/src/utils/useEffectEvent.ts +34 -0
- package/src/utils/useEvent.ts +55 -0
- package/src/utils/useGlobalListeners.ts +91 -0
- package/src/utils/useId.ts +150 -0
- package/src/utils/useLayoutEffect.ts +7 -0
- package/src/utils/useObjectRef.ts +88 -0
- package/src/utils/useSyncRef.ts +39 -0
- package/src/utils/useValueEffect.ts +81 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/interactions/useLongPress.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - `DOMAttributes` is a local structural prop-bag alias (upstream's is typed over React's
|
|
4
|
+
// synthetic handlers); the `contextmenu` listener param carries an explicit native
|
|
5
|
+
// annotation (upstream got it contextually).
|
|
6
|
+
// - Public-hook slot threading (splitSlot/subSlot) per the binding convention.
|
|
7
|
+
import type { FocusableElement, LongPressEvent } from '@react-types/shared';
|
|
8
|
+
import { focusWithoutScrolling } from '../utils/focusWithoutScrolling';
|
|
9
|
+
import { getOwnerDocument, getOwnerWindow } from '../utils/domHelpers';
|
|
10
|
+
import { mergeProps } from '../utils/mergeProps';
|
|
11
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
12
|
+
import { useDescription } from '../utils/useDescription';
|
|
13
|
+
import { useGlobalListeners } from '../utils/useGlobalListeners';
|
|
14
|
+
import { usePress } from './usePress';
|
|
15
|
+
import { useRef } from 'octane';
|
|
16
|
+
|
|
17
|
+
// octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
|
|
18
|
+
// handler/attribute types).
|
|
19
|
+
type DOMAttributes = Record<string, any>;
|
|
20
|
+
|
|
21
|
+
export interface LongPressProps {
|
|
22
|
+
/** Whether long press events should be disabled. */
|
|
23
|
+
isDisabled?: boolean;
|
|
24
|
+
/** Handler that is called when a long press interaction starts. */
|
|
25
|
+
onLongPressStart?: (e: LongPressEvent) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Handler that is called when a long press interaction ends, either
|
|
28
|
+
* over the target or when the pointer leaves the target.
|
|
29
|
+
*/
|
|
30
|
+
onLongPressEnd?: (e: LongPressEvent) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Handler that is called when the threshold time is met while
|
|
33
|
+
* the press is over the target.
|
|
34
|
+
*/
|
|
35
|
+
onLongPress?: (e: LongPressEvent) => void;
|
|
36
|
+
/**
|
|
37
|
+
* The amount of time in milliseconds to wait before triggering a long press.
|
|
38
|
+
*
|
|
39
|
+
* @default 500ms
|
|
40
|
+
*/
|
|
41
|
+
threshold?: number;
|
|
42
|
+
/**
|
|
43
|
+
* A description for assistive techology users indicating that a long press
|
|
44
|
+
* action is available, e.g. "Long press to open menu".
|
|
45
|
+
*/
|
|
46
|
+
accessibilityDescription?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface LongPressResult {
|
|
50
|
+
/** Props to spread on the target element. */
|
|
51
|
+
longPressProps: DOMAttributes;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const DEFAULT_THRESHOLD = 500;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Handles long press interactions across mouse and touch devices. Supports a customizable time
|
|
58
|
+
* threshold, accessibility description, and normalizes behavior across browsers and devices.
|
|
59
|
+
*/
|
|
60
|
+
export function useLongPress(props: LongPressProps, ...args: any[]): LongPressResult;
|
|
61
|
+
export function useLongPress(...args: any[]): LongPressResult {
|
|
62
|
+
const [user, slotArg] = splitSlot(args);
|
|
63
|
+
const slot = slotArg ?? S('useLongPress');
|
|
64
|
+
const props = user[0] as LongPressProps;
|
|
65
|
+
|
|
66
|
+
let {
|
|
67
|
+
isDisabled,
|
|
68
|
+
onLongPressStart,
|
|
69
|
+
onLongPressEnd,
|
|
70
|
+
onLongPress,
|
|
71
|
+
threshold = DEFAULT_THRESHOLD,
|
|
72
|
+
accessibilityDescription,
|
|
73
|
+
} = props;
|
|
74
|
+
|
|
75
|
+
const timeRef = useRef<ReturnType<typeof setTimeout> | undefined>(
|
|
76
|
+
undefined,
|
|
77
|
+
subSlot(slot, 'timer'),
|
|
78
|
+
);
|
|
79
|
+
let { addGlobalListener, removeGlobalListener } = useGlobalListeners(subSlot(slot, 'listeners'));
|
|
80
|
+
|
|
81
|
+
let { pressProps } = usePress(
|
|
82
|
+
{
|
|
83
|
+
isDisabled,
|
|
84
|
+
onPressStart(e) {
|
|
85
|
+
e.continuePropagation();
|
|
86
|
+
if (e.pointerType === 'mouse' || e.pointerType === 'touch') {
|
|
87
|
+
if (onLongPressStart) {
|
|
88
|
+
onLongPressStart({
|
|
89
|
+
...e,
|
|
90
|
+
type: 'longpressstart',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
timeRef.current = setTimeout(() => {
|
|
95
|
+
// Prevent other usePress handlers from also handling this event.
|
|
96
|
+
e.target.dispatchEvent(new PointerEvent('pointercancel', { bubbles: true }));
|
|
97
|
+
|
|
98
|
+
// Ensure target is focused. On touch devices, browsers typically focus on pointer up.
|
|
99
|
+
if (getOwnerDocument(e.target).activeElement !== e.target) {
|
|
100
|
+
focusWithoutScrolling(e.target as FocusableElement);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (onLongPress) {
|
|
104
|
+
onLongPress({
|
|
105
|
+
...e,
|
|
106
|
+
type: 'longpress',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
timeRef.current = undefined;
|
|
110
|
+
}, threshold);
|
|
111
|
+
|
|
112
|
+
// Prevent context menu, which may be opened on long press on touch devices
|
|
113
|
+
if (e.pointerType === 'touch') {
|
|
114
|
+
let onContextMenu = (e: Event) => {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let ownerWindow = getOwnerWindow(e.target);
|
|
119
|
+
addGlobalListener(e.target, 'contextmenu', onContextMenu, { once: true });
|
|
120
|
+
addGlobalListener(
|
|
121
|
+
ownerWindow,
|
|
122
|
+
'pointerup',
|
|
123
|
+
() => {
|
|
124
|
+
// If no contextmenu event is fired quickly after pointerup, remove the handler
|
|
125
|
+
// so future context menu events outside a long press are not prevented.
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
removeGlobalListener(e.target, 'contextmenu', onContextMenu);
|
|
128
|
+
}, 30);
|
|
129
|
+
},
|
|
130
|
+
{ once: true },
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
onPressEnd(e) {
|
|
136
|
+
if (timeRef.current) {
|
|
137
|
+
clearTimeout(timeRef.current);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (onLongPressEnd && (e.pointerType === 'mouse' || e.pointerType === 'touch')) {
|
|
141
|
+
onLongPressEnd({
|
|
142
|
+
...e,
|
|
143
|
+
type: 'longpressend',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
subSlot(slot, 'press'),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
let descriptionProps = useDescription(
|
|
152
|
+
onLongPress && !isDisabled ? accessibilityDescription : undefined,
|
|
153
|
+
subSlot(slot, 'description'),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
longPressProps: mergeProps(pressProps, descriptionProps),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// Ported from react-aria (source: .react-spectrum/packages/react-aria/src/interactions/useMove.ts).
|
|
2
|
+
// octane adaptations:
|
|
3
|
+
// - Handlers receive NATIVE events: React.MouseEvent/React.TouchEvent/React.PointerEvent
|
|
4
|
+
// annotations → native MouseEvent/TouchEvent/PointerEvent; `onKeyDown`'s param carries an
|
|
5
|
+
// explicit native annotation (upstream got it contextually from React's DOMAttributes).
|
|
6
|
+
// - `DOMAttributes` is a local structural prop-bag alias (upstream's is typed over React's
|
|
7
|
+
// synthetic handlers).
|
|
8
|
+
// - Public-hook slot threading (splitSlot/subSlot) per the binding convention.
|
|
9
|
+
import { disableTextSelection, restoreTextSelection } from './textSelection';
|
|
10
|
+
import type { MoveEvents, PointerType } from '@react-types/shared';
|
|
11
|
+
import { getEventTarget } from '../utils/shadowdom/DOMFunctions';
|
|
12
|
+
import { getOwnerWindow } from '../utils/domHelpers';
|
|
13
|
+
import { useCallback, useMemo, useRef } from 'octane';
|
|
14
|
+
import { S, splitSlot, subSlot } from '../internal';
|
|
15
|
+
import { useEffectEvent } from '../utils/useEffectEvent';
|
|
16
|
+
import { useGlobalListeners } from '../utils/useGlobalListeners';
|
|
17
|
+
|
|
18
|
+
// octane adaptation: minimal structural DOMAttributes (upstream's drags React's synthetic
|
|
19
|
+
// handler/attribute types).
|
|
20
|
+
type DOMAttributes = Record<string, any>;
|
|
21
|
+
|
|
22
|
+
export interface MoveResult {
|
|
23
|
+
/** Props to spread on the target element. */
|
|
24
|
+
moveProps: DOMAttributes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface EventBase {
|
|
28
|
+
shiftKey: boolean;
|
|
29
|
+
ctrlKey: boolean;
|
|
30
|
+
metaKey: boolean;
|
|
31
|
+
altKey: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Handles move interactions across mouse, touch, and keyboard, including dragging with
|
|
36
|
+
* the mouse or touch, and using the arrow keys. Normalizes behavior across browsers and
|
|
37
|
+
* platforms, and ignores emulated mouse events on touch devices.
|
|
38
|
+
*/
|
|
39
|
+
export function useMove(props: MoveEvents, ...args: any[]): MoveResult;
|
|
40
|
+
export function useMove(...args: any[]): MoveResult {
|
|
41
|
+
const [user, slotArg] = splitSlot(args);
|
|
42
|
+
const slot = slotArg ?? S('useMove');
|
|
43
|
+
const props = user[0] as MoveEvents;
|
|
44
|
+
|
|
45
|
+
let { onMoveStart, onMove, onMoveEnd } = props;
|
|
46
|
+
|
|
47
|
+
let state = useRef<{
|
|
48
|
+
didMove: boolean;
|
|
49
|
+
lastPosition: { pageX: number; pageY: number } | null;
|
|
50
|
+
id: number | null;
|
|
51
|
+
}>({ didMove: false, lastPosition: null, id: null }, subSlot(slot, 'state'));
|
|
52
|
+
|
|
53
|
+
let { addGlobalListener, removeGlobalListener } = useGlobalListeners(subSlot(slot, 'listeners'));
|
|
54
|
+
|
|
55
|
+
let move = useCallback(
|
|
56
|
+
(originalEvent: EventBase, pointerType: PointerType, deltaX: number, deltaY: number) => {
|
|
57
|
+
if (deltaX === 0 && deltaY === 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!state.current.didMove) {
|
|
62
|
+
state.current.didMove = true;
|
|
63
|
+
onMoveStart?.({
|
|
64
|
+
type: 'movestart',
|
|
65
|
+
pointerType,
|
|
66
|
+
shiftKey: originalEvent.shiftKey,
|
|
67
|
+
metaKey: originalEvent.metaKey,
|
|
68
|
+
ctrlKey: originalEvent.ctrlKey,
|
|
69
|
+
altKey: originalEvent.altKey,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
onMove?.({
|
|
73
|
+
type: 'move',
|
|
74
|
+
pointerType,
|
|
75
|
+
deltaX: deltaX,
|
|
76
|
+
deltaY: deltaY,
|
|
77
|
+
shiftKey: originalEvent.shiftKey,
|
|
78
|
+
metaKey: originalEvent.metaKey,
|
|
79
|
+
ctrlKey: originalEvent.ctrlKey,
|
|
80
|
+
altKey: originalEvent.altKey,
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
[onMoveStart, onMove, state],
|
|
84
|
+
subSlot(slot, 'move'),
|
|
85
|
+
);
|
|
86
|
+
let moveEvent = useEffectEvent(move, subSlot(slot, 'moveEvent'));
|
|
87
|
+
|
|
88
|
+
let end = useCallback(
|
|
89
|
+
(originalEvent: EventBase, pointerType: PointerType) => {
|
|
90
|
+
restoreTextSelection();
|
|
91
|
+
if (state.current.didMove) {
|
|
92
|
+
onMoveEnd?.({
|
|
93
|
+
type: 'moveend',
|
|
94
|
+
pointerType,
|
|
95
|
+
shiftKey: originalEvent.shiftKey,
|
|
96
|
+
metaKey: originalEvent.metaKey,
|
|
97
|
+
ctrlKey: originalEvent.ctrlKey,
|
|
98
|
+
altKey: originalEvent.altKey,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[onMoveEnd, state],
|
|
103
|
+
subSlot(slot, 'end'),
|
|
104
|
+
);
|
|
105
|
+
let endEvent = useEffectEvent(end, subSlot(slot, 'endEvent'));
|
|
106
|
+
|
|
107
|
+
let moveProps = useMemo(
|
|
108
|
+
() => {
|
|
109
|
+
let moveProps: DOMAttributes = {};
|
|
110
|
+
|
|
111
|
+
let start = () => {
|
|
112
|
+
disableTextSelection();
|
|
113
|
+
state.current.didMove = false;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (typeof PointerEvent === 'undefined' && process.env.NODE_ENV === 'test') {
|
|
117
|
+
let onMouseMove = (e: MouseEvent) => {
|
|
118
|
+
if (e.button === 0) {
|
|
119
|
+
// Should be safe to use the useEffectEvent because these are equivalent https://github.com/reactjs/react.dev/issues/8075#issuecomment-3400179389
|
|
120
|
+
// However, the compiler is not smart enough to know that. As such, this whole file must be manually optimised as the compiler will bail.
|
|
121
|
+
moveEvent(
|
|
122
|
+
e,
|
|
123
|
+
'mouse',
|
|
124
|
+
e.pageX - (state.current.lastPosition?.pageX ?? 0),
|
|
125
|
+
e.pageY - (state.current.lastPosition?.pageY ?? 0),
|
|
126
|
+
);
|
|
127
|
+
state.current.lastPosition = { pageX: e.pageX, pageY: e.pageY };
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
let onMouseUp = (e: MouseEvent) => {
|
|
131
|
+
if (e.button === 0) {
|
|
132
|
+
endEvent(e, 'mouse');
|
|
133
|
+
let ownerWindow = getOwnerWindow(getEventTarget(e) as Element);
|
|
134
|
+
removeGlobalListener(ownerWindow, 'mousemove', onMouseMove, false);
|
|
135
|
+
removeGlobalListener(ownerWindow, 'mouseup', onMouseUp, false);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
moveProps.onMouseDown = (e: MouseEvent) => {
|
|
139
|
+
if (e.button === 0) {
|
|
140
|
+
start();
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
state.current.lastPosition = { pageX: e.pageX, pageY: e.pageY };
|
|
144
|
+
let ownerWindow = getOwnerWindow(getEventTarget(e) as Element);
|
|
145
|
+
addGlobalListener(ownerWindow, 'mousemove', onMouseMove, false);
|
|
146
|
+
addGlobalListener(ownerWindow, 'mouseup', onMouseUp, false);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
let onTouchMove = (e: TouchEvent) => {
|
|
151
|
+
let touch = [...e.changedTouches].findIndex(
|
|
152
|
+
({ identifier }) => identifier === state.current.id,
|
|
153
|
+
);
|
|
154
|
+
if (touch >= 0) {
|
|
155
|
+
let { pageX, pageY } = e.changedTouches[touch];
|
|
156
|
+
moveEvent(
|
|
157
|
+
e,
|
|
158
|
+
'touch',
|
|
159
|
+
pageX - (state.current.lastPosition?.pageX ?? 0),
|
|
160
|
+
pageY - (state.current.lastPosition?.pageY ?? 0),
|
|
161
|
+
);
|
|
162
|
+
state.current.lastPosition = { pageX, pageY };
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
let onTouchEnd = (e: TouchEvent) => {
|
|
166
|
+
let touch = [...e.changedTouches].findIndex(
|
|
167
|
+
({ identifier }) => identifier === state.current.id,
|
|
168
|
+
);
|
|
169
|
+
if (touch >= 0) {
|
|
170
|
+
endEvent(e, 'touch');
|
|
171
|
+
state.current.id = null;
|
|
172
|
+
let ownerWindow = getOwnerWindow(getEventTarget(e) as Element);
|
|
173
|
+
removeGlobalListener(ownerWindow, 'touchmove', onTouchMove);
|
|
174
|
+
removeGlobalListener(ownerWindow, 'touchend', onTouchEnd);
|
|
175
|
+
removeGlobalListener(ownerWindow, 'touchcancel', onTouchEnd);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
moveProps.onTouchStart = (e: TouchEvent) => {
|
|
179
|
+
if (e.changedTouches.length === 0 || state.current.id != null) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let { pageX, pageY, identifier } = e.changedTouches[0];
|
|
184
|
+
start();
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
state.current.lastPosition = { pageX, pageY };
|
|
188
|
+
state.current.id = identifier;
|
|
189
|
+
let ownerWindow = getOwnerWindow(getEventTarget(e) as Element);
|
|
190
|
+
addGlobalListener(ownerWindow, 'touchmove', onTouchMove, false);
|
|
191
|
+
addGlobalListener(ownerWindow, 'touchend', onTouchEnd, false);
|
|
192
|
+
addGlobalListener(ownerWindow, 'touchcancel', onTouchEnd, false);
|
|
193
|
+
};
|
|
194
|
+
} else {
|
|
195
|
+
let onPointerMove = (e: PointerEvent) => {
|
|
196
|
+
if (e.pointerId === state.current.id) {
|
|
197
|
+
let pointerType = (e.pointerType || 'mouse') as PointerType;
|
|
198
|
+
|
|
199
|
+
// Problems with PointerEvent#movementX/movementY:
|
|
200
|
+
// 1. it is always 0 on macOS Safari.
|
|
201
|
+
// 2. On Chrome Android, it's scaled by devicePixelRatio, but not on Chrome macOS
|
|
202
|
+
moveEvent(
|
|
203
|
+
e,
|
|
204
|
+
pointerType,
|
|
205
|
+
e.pageX - (state.current.lastPosition?.pageX ?? 0),
|
|
206
|
+
e.pageY - (state.current.lastPosition?.pageY ?? 0),
|
|
207
|
+
);
|
|
208
|
+
state.current.lastPosition = { pageX: e.pageX, pageY: e.pageY };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
let onPointerUp = (e: PointerEvent) => {
|
|
213
|
+
if (e.pointerId === state.current.id) {
|
|
214
|
+
let pointerType = (e.pointerType || 'mouse') as PointerType;
|
|
215
|
+
endEvent(e, pointerType);
|
|
216
|
+
state.current.id = null;
|
|
217
|
+
let ownerWindow = getOwnerWindow(getEventTarget(e) as Element);
|
|
218
|
+
removeGlobalListener(ownerWindow, 'pointermove', onPointerMove, false);
|
|
219
|
+
removeGlobalListener(ownerWindow, 'pointerup', onPointerUp, false);
|
|
220
|
+
removeGlobalListener(ownerWindow, 'pointercancel', onPointerUp, false);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
moveProps.onPointerDown = (e: PointerEvent) => {
|
|
225
|
+
if (e.button === 0 && state.current.id == null) {
|
|
226
|
+
start();
|
|
227
|
+
e.stopPropagation();
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
state.current.lastPosition = { pageX: e.pageX, pageY: e.pageY };
|
|
230
|
+
state.current.id = e.pointerId;
|
|
231
|
+
let ownerWindow = getOwnerWindow(getEventTarget(e) as Element);
|
|
232
|
+
addGlobalListener(ownerWindow, 'pointermove', onPointerMove, false);
|
|
233
|
+
addGlobalListener(ownerWindow, 'pointerup', onPointerUp, false);
|
|
234
|
+
addGlobalListener(ownerWindow, 'pointercancel', onPointerUp, false);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let triggerKeyboardMove = (e: EventBase, deltaX: number, deltaY: number) => {
|
|
240
|
+
start();
|
|
241
|
+
moveEvent(e, 'keyboard', deltaX, deltaY);
|
|
242
|
+
endEvent(e, 'keyboard');
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
moveProps.onKeyDown = (e: KeyboardEvent) => {
|
|
246
|
+
switch (e.key) {
|
|
247
|
+
case 'Left':
|
|
248
|
+
case 'ArrowLeft':
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
e.stopPropagation();
|
|
251
|
+
triggerKeyboardMove(e, -1, 0);
|
|
252
|
+
break;
|
|
253
|
+
case 'Right':
|
|
254
|
+
case 'ArrowRight':
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
e.stopPropagation();
|
|
257
|
+
triggerKeyboardMove(e, 1, 0);
|
|
258
|
+
break;
|
|
259
|
+
case 'Up':
|
|
260
|
+
case 'ArrowUp':
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
e.stopPropagation();
|
|
263
|
+
triggerKeyboardMove(e, 0, -1);
|
|
264
|
+
break;
|
|
265
|
+
case 'Down':
|
|
266
|
+
case 'ArrowDown':
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
triggerKeyboardMove(e, 0, 1);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
return moveProps;
|
|
275
|
+
},
|
|
276
|
+
[addGlobalListener, removeGlobalListener, state],
|
|
277
|
+
subSlot(slot, 'moveProps'),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
return { moveProps };
|
|
281
|
+
}
|