@astryxdesign/core 0.7.8 → 0.7.9-canary.7400c1f
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/dist/Calendar/styles.d.ts +1 -1
- package/dist/ClickableCard/ClickableCard.d.ts +1 -1
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +5 -1
- package/dist/DateInput/PickerWheel.d.ts.map +1 -1
- package/dist/DateInput/PickerWheel.js +7 -1
- package/dist/DateRangeInput/DateRangeInput.js +1 -1
- package/dist/Dialog/Dialog.d.ts +2 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +8 -1
- package/dist/Drawer/useDrawerDialogPresence.d.ts +2 -1
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
- package/dist/Drawer/useDrawerDialogPresence.js +11 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts +9 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +9 -4
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +3 -2
- package/dist/Field/inputDisabledFocus.d.ts +4 -4
- package/dist/Field/inputStyles.stylex.d.ts +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -2
- package/dist/Icon/Icon.d.ts +1 -1
- package/dist/InfoTip/InfoTip.d.ts +1 -0
- package/dist/InfoTip/InfoTip.d.ts.map +1 -1
- package/dist/InfoTip/InfoTip.js +1 -0
- package/dist/Item/Item.d.ts +2 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +29 -6
- package/dist/Layer/holdEntranceInput.d.ts +24 -0
- package/dist/Layer/holdEntranceInput.d.ts.map +1 -0
- package/dist/Layer/holdEntranceInput.js +65 -0
- package/dist/Layer/inputHold.d.ts +49 -0
- package/dist/Layer/inputHold.d.ts.map +1 -0
- package/dist/Layer/inputHold.js +636 -0
- package/dist/Layer/preserveAnimations.d.ts +1 -5
- package/dist/Layer/preserveAnimations.d.ts.map +1 -1
- package/dist/Layer/preserveAnimations.js +12 -214
- package/dist/MetadataList/MetadataList.js +1 -1
- package/dist/MobileNav/MobileNav.d.ts +11 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +34 -1
- package/dist/MoreMenu/MoreMenu.d.ts +5 -3
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +4 -1
- package/dist/SelectableCard/SelectableCard.d.ts +1 -1
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +5 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +12 -2
- package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +9 -0
- package/dist/Token/TokenLink.d.ts +1 -1
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +4 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +162 -59
- package/dist/TreeList/TreeListItem.d.ts +2 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +27 -3
- package/dist/astryx.css +30 -7
- package/dist/gestures/GestureHost.d.ts +2 -2
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +21 -6
- package/dist/gestures/nativeScroll.d.ts +3 -6
- package/dist/gestures/nativeScroll.d.ts.map +1 -1
- package/dist/gestures/nativeScroll.js +7 -52
- package/dist/gestures/wheelSegmenter.d.ts +68 -1
- package/dist/gestures/wheelSegmenter.d.ts.map +1 -1
- package/dist/gestures/wheelSegmenter.js +199 -10
- package/dist/hooks/menuPressGesture.d.ts +133 -21
- package/dist/hooks/menuPressGesture.d.ts.map +1 -1
- package/dist/hooks/menuPressGesture.js +155 -30
- package/dist/hooks/menuPressValues.d.ts +16 -0
- package/dist/hooks/menuPressValues.d.ts.map +1 -0
- package/dist/hooks/menuPressValues.js +18 -0
- package/dist/hooks/useClickableContainer.d.ts +27 -5
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +41 -5
- package/dist/hooks/useMenuPress.d.ts +34 -1
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +93 -5
- package/dist/hooks/usePressFeedback.d.ts +6 -6
- package/dist/hooks/usePressFeedback.js +6 -6
- package/dist/motion/carryAnimations.d.ts +58 -0
- package/dist/motion/carryAnimations.d.ts.map +1 -0
- package/dist/motion/carryAnimations.js +305 -0
- package/dist/motion/transientCopy.d.ts +4 -2
- package/dist/motion/transientCopy.d.ts.map +1 -1
- package/dist/motion/transientCopy.js +34 -5
- package/dist/motion/uikit/gestures.d.ts +41 -0
- package/dist/motion/uikit/gestures.d.ts.map +1 -1
- package/dist/motion/uikit/gestures.js +25 -0
- package/dist/motion/uikit/index.d.ts +1 -1
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +1 -1
- package/dist/privateAttributes/menuPressLit.stylex.d.ts +52 -0
- package/dist/privateAttributes/menuPressLit.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/menuPressLit.stylex.js +99 -0
- package/dist/privateAttributes/press.stylex.d.ts +139 -0
- package/dist/privateAttributes/press.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/press.stylex.js +98 -0
- package/dist/theme/tokens.stylex.d.ts +2 -2
- package/dist/theme/tokens.stylex.js +1 -1
- package/dist/utils/flatTree.d.ts +6 -3
- package/dist/utils/flatTree.d.ts.map +1 -1
- package/dist/utils/flatTree.js +13 -3
- package/dist/utils/hitArea.d.ts.map +1 -1
- package/dist/utils/hitArea.js +82 -5
- package/dist/utils/inputStream.d.ts +14 -1
- package/dist/utils/inputStream.d.ts.map +1 -1
- package/dist/utils/inputStream.js +40 -9
- package/dist/utils/interactionOverlay.stylex.d.ts +2 -1
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +7 -2
- package/dist/utils/pressFeedback.d.ts +7 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +32 -7
- package/dist/utils/pressGesture.d.ts +6 -0
- package/dist/utils/pressGesture.d.ts.map +1 -1
- package/dist/utils/pressGesture.js +17 -0
- package/dist/utils/rowPressLayer.stylex.d.ts +14 -0
- package/dist/utils/rowPressLayer.stylex.d.ts.map +1 -0
- package/dist/utils/rowPressLayer.stylex.js +25 -0
- package/dist/utils/scrollView.d.ts +25 -0
- package/dist/utils/scrollView.d.ts.map +1 -0
- package/dist/utils/scrollView.js +108 -0
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +1 -10
- package/dist/utils/triggerFocus.d.ts +5 -0
- package/dist/utils/triggerFocus.d.ts.map +1 -1
- package/dist/utils/triggerFocus.js +9 -2
- package/package.json +5 -5
- package/src/ClickableCard/ClickableCard.tsx +8 -1
- package/src/DateInput/PickerWheel.tsx +7 -2
- package/src/DateRangeInput/DateRangeInput.tsx +1 -1
- package/src/Dialog/Dialog.doc.mjs +1 -1
- package/src/Dialog/Dialog.tsx +8 -1
- package/src/Drawer/Drawer.doc.mjs +1 -1
- package/src/Drawer/useDrawerDialogPresence.ts +9 -2
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -2
- package/src/DropdownMenu/DropdownMenu.tsx +29 -5
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +8 -4
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -1
- package/src/FileInput/FileInput.tsx +16 -2
- package/src/InfoTip/InfoTip.doc.mjs +26 -2
- package/src/InfoTip/InfoTip.tsx +1 -0
- package/src/Item/Item.tsx +51 -9
- package/src/Layer/holdEntranceInput.ts +84 -0
- package/src/Layer/inputHold.ts +734 -0
- package/src/Layer/preserveAnimations.ts +12 -275
- package/src/MetadataList/MetadataList.tsx +1 -1
- package/src/MobileNav/MobileNav.doc.mjs +3 -3
- package/src/MobileNav/MobileNav.tsx +34 -1
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -4
- package/src/MoreMenu/MoreMenu.tsx +5 -3
- package/src/RadioList/RadioListItem.tsx +4 -5
- package/src/SelectableCard/SelectableCard.tsx +8 -1
- package/src/Spinner/Spinner.tsx +13 -3
- package/src/Table/plugins/sortable/useTableSortable.tsx +1 -1
- package/src/Token/Token.tsx +18 -1
- package/src/Token/TokenLink.tsx +7 -1
- package/src/Tooltip/useTooltip.tsx +175 -79
- package/src/TreeList/TreeListItem.tsx +36 -2
- package/src/gestures/GestureHost.ts +21 -5
- package/src/gestures/README.md +12 -4
- package/src/gestures/nativeScroll.ts +12 -68
- package/src/gestures/wheelSegmenter.ts +243 -10
- package/src/hooks/menuPressGesture.ts +244 -38
- package/src/hooks/menuPressValues.ts +18 -0
- package/src/hooks/useClickableContainer.doc.mjs +8 -2
- package/src/hooks/useClickableContainer.ts +72 -6
- package/src/hooks/useMenuPress.ts +118 -11
- package/src/hooks/usePressFeedback.doc.mjs +3 -3
- package/src/hooks/usePressFeedback.ts +6 -6
- package/src/motion/MEASUREMENTS.md +11 -0
- package/src/motion/carryAnimations.ts +389 -0
- package/src/motion/transientCopy.ts +45 -5
- package/src/motion/uikit/gestures.ts +22 -0
- package/src/motion/uikit/index.ts +1 -0
- package/src/privateAttributes/menuPressLit.stylex.ts +104 -0
- package/src/privateAttributes/press.stylex.ts +244 -0
- package/src/theme/tokens.stylex.ts +1 -1
- package/src/utils/flatTree.ts +15 -3
- package/src/utils/hitArea.ts +107 -5
- package/src/utils/inputStream.ts +47 -10
- package/src/utils/interactionOverlay.stylex.ts +7 -2
- package/src/utils/pressFeedback.ts +31 -6
- package/src/utils/pressGesture.ts +24 -1
- package/src/utils/rowPressLayer.stylex.ts +27 -0
- package/src/utils/scrollView.ts +135 -0
- package/src/utils/selectionLock.ts +5 -10
- package/src/utils/triggerFocus.ts +17 -4
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file inputHold.ts
|
|
7
|
+
* @input A layer root, what ends its hold (a promise or an animator handle),
|
|
8
|
+
* and the hold's scope
|
|
9
|
+
* @output holdInput, holdsTouch, isInHeldLayer and isInputShield: input held
|
|
10
|
+
* away from a layer while it presents or dismisses
|
|
11
|
+
* @position The Layer module's one input hold, for components whose layer
|
|
12
|
+
* presents under a finger
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When modified, update inputHold.test.tsx,
|
|
15
|
+
* __tests__/inputHold.interaction.browser.spec.ts and the holdInput callers.
|
|
16
|
+
*/
|
|
17
|
+
import { containsAlongFlatTree, deepActiveElement, parentAcrossShadow } from "../utils/flatTree.js";
|
|
18
|
+
import { getLastPointerType, subscribeInput } from "../utils/inputStream.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What a hold does. `'viewport'` and `'layer'` add one shield over the whole
|
|
22
|
+
* viewport. `'layer'` also makes the layer's content inert and moves focus
|
|
23
|
+
* out of it. `'surface'` shields only the layer's own box, and the page
|
|
24
|
+
* around it stays live.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** What ends a hold: a promise, or an animator handle (its `finished`). */
|
|
28
|
+
|
|
29
|
+
/** A document while any of its layers is held. */
|
|
30
|
+
|
|
31
|
+
/** A document's press hold, while its window listeners are on. */
|
|
32
|
+
|
|
33
|
+
const held = new WeakMap();
|
|
34
|
+
const heldDocuments = new WeakMap();
|
|
35
|
+
const pressHolds = new WeakMap();
|
|
36
|
+
const liveShields = new WeakSet();
|
|
37
|
+
|
|
38
|
+
/** Whether the shared input stream last delivered a mouse's pointer event. */
|
|
39
|
+
function isMouseLast(doc) {
|
|
40
|
+
const view = doc?.defaultView;
|
|
41
|
+
return view != null && getLastPointerType(view) === 'mouse';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether `node` is the shield of a live hold. A hit test that must see past
|
|
46
|
+
* held input takes the first element of `elementsFromPoint` that is not one.
|
|
47
|
+
*/
|
|
48
|
+
export function isInputShield(node) {
|
|
49
|
+
return node != null && liveShields.has(node);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether `target` is inside a held layer. It walks the flat tree as the
|
|
54
|
+
* gesture host does: a slotted node goes to its slot, and a shadow root to
|
|
55
|
+
* its host.
|
|
56
|
+
*/
|
|
57
|
+
export function isInHeldLayer(target) {
|
|
58
|
+
for (let node = target; node != null; node = parentAcrossShadow(node)) {
|
|
59
|
+
if (node instanceof Element && held.has(node)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Whether the gesture host holds `target` away for the current pointer. A
|
|
68
|
+
* dismissing layer holds every pointer. A presenting layer holds every
|
|
69
|
+
* pointer but a mouse, which keeps today's behavior. The host asks this for
|
|
70
|
+
* every touch. So this module imports nothing from gestures/, and a
|
|
71
|
+
* root-entry component can hold its layer without gesture code. With nothing
|
|
72
|
+
* held, this answers false.
|
|
73
|
+
*/
|
|
74
|
+
export function holdsTouch(target) {
|
|
75
|
+
const doc = target instanceof Document ? target : target.ownerDocument;
|
|
76
|
+
// The gesture host asks for every touch: with nothing held, answer at once.
|
|
77
|
+
if (doc == null || !heldDocuments.has(doc)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// What lands on a shield is held, a mouse's press included.
|
|
81
|
+
if (isInputShield(target)) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
let isHeld = false;
|
|
85
|
+
for (let node = target; node != null; node = parentAcrossShadow(node)) {
|
|
86
|
+
const state = node instanceof Element ? held.get(node) : undefined;
|
|
87
|
+
if (state != null) {
|
|
88
|
+
if (state.layer > 0) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
isHeld = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return isHeld && !isMouseLast(doc);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Starts holding `layer`. A document's first held layer turns its press hold
|
|
99
|
+
* on, with one stream subscription per event type, however many layers are
|
|
100
|
+
* held. The hold subscribes to nothing while nothing is held.
|
|
101
|
+
*/
|
|
102
|
+
function startHolding(layer) {
|
|
103
|
+
const doc = layer.ownerDocument;
|
|
104
|
+
let state = heldDocuments.get(doc);
|
|
105
|
+
if (state == null) {
|
|
106
|
+
state = {
|
|
107
|
+
layers: new Set()
|
|
108
|
+
};
|
|
109
|
+
heldDocuments.set(doc, state);
|
|
110
|
+
}
|
|
111
|
+
state.layers.add(layer);
|
|
112
|
+
pressHoldFor(doc)?.hold();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Stops holding `layer`; after the document's last one, ends its holding. */
|
|
116
|
+
function stopHolding(layer) {
|
|
117
|
+
heldDocuments.get(layer.ownerDocument)?.layers.delete(layer);
|
|
118
|
+
liveLayers(layer.ownerDocument);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The document's held layers still in it, oldest first. A layer that has
|
|
123
|
+
* left the document holds nothing more. When none is left, `holdsTouch`
|
|
124
|
+
* answers false and the press hold takes no new press. Its subscriptions end
|
|
125
|
+
* once the presses it holds have ended.
|
|
126
|
+
*/
|
|
127
|
+
function liveLayers(doc) {
|
|
128
|
+
const state = heldDocuments.get(doc);
|
|
129
|
+
if (state == null) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
for (const layer of state.layers) {
|
|
133
|
+
if (!layer.isConnected) {
|
|
134
|
+
state.layers.delete(layer);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (state.layers.size > 0) {
|
|
138
|
+
return [...state.layers];
|
|
139
|
+
}
|
|
140
|
+
heldDocuments.delete(doc);
|
|
141
|
+
pressHolds.get(doc)?.release();
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const SWALLOWED = ['pointerdown', 'pointerup', 'touchstart', 'touchend', 'mousedown', 'mouseup', 'click', 'contextmenu'];
|
|
145
|
+
const swallow = event => {
|
|
146
|
+
// A touch's start stays uncancelled: cancelling it would cancel the page's
|
|
147
|
+
// pinch-zoom too. The shield's `touch-action: pinch-zoom` keeps a finger
|
|
148
|
+
// from panning. A tap's mouse events and click land on the shield, which
|
|
149
|
+
// swallows them.
|
|
150
|
+
if (event.cancelable && event.type !== 'touchstart') {
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
}
|
|
153
|
+
event.stopPropagation();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A transparent cover inside `layer`: of the viewport from the top layer
|
|
158
|
+
* (`isFull`), or of the layer's own box alone. Because the cover is inside
|
|
159
|
+
* the layer, a tap on it lands in the layer, and a light-dismissing layer
|
|
160
|
+
* stays open.
|
|
161
|
+
*/
|
|
162
|
+
function addShield(layer, isFull) {
|
|
163
|
+
const shield = layer.ownerDocument.createElement('div');
|
|
164
|
+
if (isFull) {
|
|
165
|
+
shield.setAttribute('popover', 'manual');
|
|
166
|
+
}
|
|
167
|
+
shield.setAttribute('aria-hidden', 'true');
|
|
168
|
+
Object.assign(shield.style, {
|
|
169
|
+
position: isFull ? 'fixed' : 'absolute',
|
|
170
|
+
inset: '0',
|
|
171
|
+
width: isFull ? '100vw' : '100%',
|
|
172
|
+
height: isFull ? '100vh' : '100%',
|
|
173
|
+
// Above the layer's own content.
|
|
174
|
+
zIndex: isFull ? 'auto' : '2147483647',
|
|
175
|
+
maxWidth: 'none',
|
|
176
|
+
maxHeight: 'none',
|
|
177
|
+
margin: '0',
|
|
178
|
+
padding: '0',
|
|
179
|
+
border: '0',
|
|
180
|
+
background: 'transparent',
|
|
181
|
+
overflow: 'hidden',
|
|
182
|
+
touchAction: 'pinch-zoom',
|
|
183
|
+
// These two inherited properties decide hit-testing. A layer root can opt
|
|
184
|
+
// out of hits, as a full-viewport layer does when a backdrop child takes
|
|
185
|
+
// them. Inherited from such a root, they would let a held tap fall through
|
|
186
|
+
// to the page.
|
|
187
|
+
pointerEvents: 'auto',
|
|
188
|
+
visibility: 'visible'
|
|
189
|
+
});
|
|
190
|
+
for (const type of SWALLOWED) {
|
|
191
|
+
shield.addEventListener(type, swallow, {
|
|
192
|
+
capture: true,
|
|
193
|
+
passive: false
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
layer.append(shield);
|
|
197
|
+
liveShields.add(shield);
|
|
198
|
+
if (isFull && typeof shield.showPopover === 'function') {
|
|
199
|
+
try {
|
|
200
|
+
shield.showPopover();
|
|
201
|
+
} catch {
|
|
202
|
+
// Not connected, or popovers unsupported: the shield still covers
|
|
203
|
+
// what it can as a fixed element.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return shield;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Holds input away from `layer` until `until` settles. Returns the release
|
|
211
|
+
* (idempotent), for a hold that ends early. Holds on one layer stack, and
|
|
212
|
+
* the layer comes back when the last one ends. Each event is judged by its
|
|
213
|
+
* own pointer, never by the primary pointer. So a mouse on a touch screen
|
|
214
|
+
* keeps its clicks during a presentation.
|
|
215
|
+
*/
|
|
216
|
+
export function holdInput(layer, {
|
|
217
|
+
until,
|
|
218
|
+
scope
|
|
219
|
+
}) {
|
|
220
|
+
let state = held.get(layer);
|
|
221
|
+
if (state == null) {
|
|
222
|
+
state = {
|
|
223
|
+
viewport: 0,
|
|
224
|
+
layer: 0,
|
|
225
|
+
surface: 0,
|
|
226
|
+
shield: null,
|
|
227
|
+
isShieldFull: false,
|
|
228
|
+
inertContent: null
|
|
229
|
+
};
|
|
230
|
+
held.set(layer, state);
|
|
231
|
+
}
|
|
232
|
+
startHolding(layer);
|
|
233
|
+
const isFull = scope !== 'surface';
|
|
234
|
+
if (state.shield != null && isFull && !state.isShieldFull) {
|
|
235
|
+
// A surface hold's shield gives way to one over the viewport.
|
|
236
|
+
liveShields.delete(state.shield);
|
|
237
|
+
state.shield.remove();
|
|
238
|
+
state.shield = null;
|
|
239
|
+
}
|
|
240
|
+
if (state.shield == null) {
|
|
241
|
+
state.isShieldFull = isFull || state.viewport + state.layer > 0;
|
|
242
|
+
state.shield = addShield(layer, state.isShieldFull);
|
|
243
|
+
// A presentation lets a mouse through from the start when the stream's
|
|
244
|
+
// last pointer was a mouse. A press where the mouse rests then acts.
|
|
245
|
+
if (scope !== 'layer' && isMouseLast(layer.ownerDocument)) {
|
|
246
|
+
state.shield.style.pointerEvents = 'none';
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (scope === 'viewport') {
|
|
250
|
+
state.viewport += 1;
|
|
251
|
+
} else if (scope === 'surface') {
|
|
252
|
+
state.surface += 1;
|
|
253
|
+
} else {
|
|
254
|
+
state.layer += 1;
|
|
255
|
+
// A dismissal's shield takes every hit, a mouse's included.
|
|
256
|
+
state.shield.style.pointerEvents = 'auto';
|
|
257
|
+
if (state.layer === 1) {
|
|
258
|
+
state.inertContent = makeContentInert(layer, state.shield);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
let isReleased = false;
|
|
262
|
+
const release = () => {
|
|
263
|
+
if (isReleased) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
isReleased = true;
|
|
267
|
+
const current = held.get(layer);
|
|
268
|
+
if (current == null) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (scope === 'viewport') {
|
|
272
|
+
current.viewport -= 1;
|
|
273
|
+
} else if (scope === 'surface') {
|
|
274
|
+
current.surface -= 1;
|
|
275
|
+
} else {
|
|
276
|
+
current.layer -= 1;
|
|
277
|
+
if (current.layer === 0 && current.inertContent != null) {
|
|
278
|
+
restoreInert(current.inertContent);
|
|
279
|
+
current.inertContent = null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (current.viewport === 0 && current.layer === 0 && current.surface === 0) {
|
|
283
|
+
if (current.shield != null) {
|
|
284
|
+
liveShields.delete(current.shield);
|
|
285
|
+
current.shield.remove();
|
|
286
|
+
}
|
|
287
|
+
held.delete(layer);
|
|
288
|
+
stopHolding(layer);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
const end = 'finished' in until ? until.finished : until;
|
|
292
|
+
void Promise.resolve(end).then(release, release);
|
|
293
|
+
return release;
|
|
294
|
+
}
|
|
295
|
+
const HELD_POINTERS = new Set(['touch', 'pen']);
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* How long after a held press ends its click and compatibility mouse events
|
|
299
|
+
* still count as its own (WebKit sends them after `touchend`).
|
|
300
|
+
*/
|
|
301
|
+
const CLICK_WINDOW_MS = 800;
|
|
302
|
+
|
|
303
|
+
/** How long a held press is followed past the hold's end, at most. */
|
|
304
|
+
const STRAY_PRESS_MS = 10_000;
|
|
305
|
+
const POINTER_EVENTS = ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'];
|
|
306
|
+
const TOUCH_EVENTS = ['touchstart', 'touchmove', 'touchend', 'touchcancel'];
|
|
307
|
+
const PRESS_EVENTS = ['mousedown', 'mouseup', 'click', 'auxclick', 'dblclick', 'contextmenu'];
|
|
308
|
+
|
|
309
|
+
/** The document's press hold, turned on if it is off. */
|
|
310
|
+
function pressHoldFor(doc) {
|
|
311
|
+
let hold = pressHolds.get(doc) ?? null;
|
|
312
|
+
if (hold == null) {
|
|
313
|
+
hold = createPressHold(doc);
|
|
314
|
+
if (hold != null) {
|
|
315
|
+
pressHolds.set(doc, hold);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return hold;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Holds each finger or pen press that lands in `doc`'s window while any of
|
|
323
|
+
* its layers is held. Each press is held to its own end, even past the
|
|
324
|
+
* hold's end. A moving mouse passes through a presentation's shield. The
|
|
325
|
+
* hold hears input through the shared input stream, before its other
|
|
326
|
+
* subscribers, and stops each held event there. So no document or element
|
|
327
|
+
* listener hears it, and neither does press feedback or the gesture host.
|
|
328
|
+
* There is one per document. Its subscriptions end once nothing is held and
|
|
329
|
+
* the held presses and their clicks have ended.
|
|
330
|
+
*/
|
|
331
|
+
function createPressHold(doc) {
|
|
332
|
+
const view = doc.defaultView;
|
|
333
|
+
if (view == null) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
/** Whether a new press is held: until the release. */
|
|
337
|
+
let isHolding = true;
|
|
338
|
+
let isListening = true;
|
|
339
|
+
/** The held presses still down, by pointer id and by touch identifier. */
|
|
340
|
+
const pointers = new Set();
|
|
341
|
+
const touches = new Set();
|
|
342
|
+
/** Whose press the next click is: a held one's, or any other's. */
|
|
343
|
+
let lastPress = null;
|
|
344
|
+
/** Until when a click with no pointer type is a held press's. */
|
|
345
|
+
let clickUntil = -Infinity;
|
|
346
|
+
let timer;
|
|
347
|
+
const now = () => view.performance.now();
|
|
348
|
+
const isPressDown = () => pointers.size > 0 || touches.size > 0;
|
|
349
|
+
const swallow = event => {
|
|
350
|
+
if (event.cancelable) {
|
|
351
|
+
event.preventDefault();
|
|
352
|
+
}
|
|
353
|
+
event.stopImmediatePropagation();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/** After the release: stops once the last held press and its click end. */
|
|
357
|
+
const settle = () => {
|
|
358
|
+
if (isHolding || !isListening) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (timer !== undefined) {
|
|
362
|
+
clearTimeout(timer);
|
|
363
|
+
}
|
|
364
|
+
if (isPressDown()) {
|
|
365
|
+
timer = setTimeout(stop, STRAY_PRESS_MS);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const wait = clickUntil - now();
|
|
369
|
+
if (wait > 0) {
|
|
370
|
+
timer = setTimeout(stop, wait);
|
|
371
|
+
} else {
|
|
372
|
+
stop();
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
/** Whether a layer is still held (one that left the document is not). */
|
|
377
|
+
const holdsNewPress = () => {
|
|
378
|
+
if (isHolding) {
|
|
379
|
+
liveLayers(doc);
|
|
380
|
+
}
|
|
381
|
+
return isHolding;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Whether a new press that lands on `target` is held. While a layer is held
|
|
386
|
+
* over the viewport, a press anywhere is held. While a layer is held only
|
|
387
|
+
* to its box (a `'surface'` hold), only a press on the layer is held. The
|
|
388
|
+
* page around it stays live.
|
|
389
|
+
*/
|
|
390
|
+
const holdsPressAt = target => {
|
|
391
|
+
if (!holdsNewPress()) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
for (const layer of liveLayers(doc)) {
|
|
395
|
+
const state = held.get(layer);
|
|
396
|
+
if (state == null) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (state.viewport > 0 || state.layer > 0) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
if (state.surface > 0 && target instanceof Node && containsAlongFlatTree(layer, target)) {
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return false;
|
|
407
|
+
};
|
|
408
|
+
const pressEnded = () => {
|
|
409
|
+
clickUntil = now() + CLICK_WINDOW_MS;
|
|
410
|
+
settle();
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/** A shield takes hits for a finger or a pen, not for a moving mouse. */
|
|
414
|
+
const followPointer = pointerType => {
|
|
415
|
+
for (const layer of liveLayers(doc)) {
|
|
416
|
+
const state = held.get(layer);
|
|
417
|
+
if (state?.shield == null) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const value = pointerType === 'mouse' && state.layer === 0 ? 'none' : 'auto';
|
|
421
|
+
if (state.shield.style.pointerEvents !== value) {
|
|
422
|
+
state.shield.style.pointerEvents = value;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* A held press that landed past the shield, which had let a mouse through,
|
|
429
|
+
* is captured to the shield. Its release then stays the hold's, and a
|
|
430
|
+
* light-dismissing layer stays open. A synthetic press has no active
|
|
431
|
+
* pointer to capture, so `setPointerCapture` throws NotFoundError. Its
|
|
432
|
+
* events are swallowed all the same.
|
|
433
|
+
*/
|
|
434
|
+
const captureToShield = event => {
|
|
435
|
+
if (!event.isTrusted) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const top = liveLayers(doc).at(-1);
|
|
439
|
+
const shield = top == null ? null : held.get(top)?.shield;
|
|
440
|
+
if (shield == null || isInputShield(event.target)) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
shield.setPointerCapture(event.pointerId);
|
|
444
|
+
};
|
|
445
|
+
const onPointer = event => {
|
|
446
|
+
const {
|
|
447
|
+
pointerId,
|
|
448
|
+
pointerType,
|
|
449
|
+
type
|
|
450
|
+
} = event;
|
|
451
|
+
// A finger or a pen raises the shields. Only a mouse that moves with no
|
|
452
|
+
// button down lowers them. So a mouse press that began on a shield stays
|
|
453
|
+
// the shield's to its end. Otherwise its click would go to the two
|
|
454
|
+
// targets' common ancestor, a dialog that reads it as a backdrop click.
|
|
455
|
+
const isFollowed = HELD_POINTERS.has(pointerType) ? type === 'pointerdown' || type === 'pointermove' : pointerType === 'mouse' && type === 'pointermove' && event.buttons === 0;
|
|
456
|
+
if (isFollowed && holdsNewPress()) {
|
|
457
|
+
followPointer(pointerType);
|
|
458
|
+
}
|
|
459
|
+
if (type === 'pointerdown') {
|
|
460
|
+
if (!HELD_POINTERS.has(pointerType) || !holdsPressAt(event.target)) {
|
|
461
|
+
// A mouse's press, or one after the hold: its click is its own, and a
|
|
462
|
+
// held press with its id ended unheard.
|
|
463
|
+
pointers.delete(pointerId);
|
|
464
|
+
lastPress = 'free';
|
|
465
|
+
clickUntil = -Infinity;
|
|
466
|
+
settle();
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
pointers.add(pointerId);
|
|
470
|
+
lastPress = 'held';
|
|
471
|
+
captureToShield(event);
|
|
472
|
+
} else if (!pointers.has(pointerId)) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
// Canceling the press keeps the browser from sending its compatibility
|
|
476
|
+
// mouse events, and with them a focus move.
|
|
477
|
+
swallow(event);
|
|
478
|
+
if (type === 'pointerup' || type === 'pointercancel') {
|
|
479
|
+
pointers.delete(pointerId);
|
|
480
|
+
pressEnded();
|
|
481
|
+
}
|
|
482
|
+
return true;
|
|
483
|
+
};
|
|
484
|
+
const onTouch = event => {
|
|
485
|
+
const {
|
|
486
|
+
changedTouches,
|
|
487
|
+
touches: down,
|
|
488
|
+
type
|
|
489
|
+
} = event;
|
|
490
|
+
const changed = Array.from(changedTouches);
|
|
491
|
+
if (type === 'touchstart') {
|
|
492
|
+
// A held touch that the screen no longer has ended unheard. Its target,
|
|
493
|
+
// the shield, left the document at the release, so its end reached no
|
|
494
|
+
// listener here.
|
|
495
|
+
const live = new Set(Array.from(down, touch => touch.identifier));
|
|
496
|
+
for (const identifier of touches) {
|
|
497
|
+
if (!live.has(identifier)) {
|
|
498
|
+
touches.delete(identifier);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const heldTouches = changed.filter(touch => holdsPressAt(touch.target));
|
|
502
|
+
if (heldTouches.length === 0) {
|
|
503
|
+
// A touch after the hold, or one beside a layer held only to its
|
|
504
|
+
// box, is a new one, whatever its identifier.
|
|
505
|
+
for (const touch of changed) {
|
|
506
|
+
touches.delete(touch.identifier);
|
|
507
|
+
}
|
|
508
|
+
lastPress = 'free';
|
|
509
|
+
clickUntil = -Infinity;
|
|
510
|
+
settle();
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
for (const touch of heldTouches) {
|
|
514
|
+
touches.add(touch.identifier);
|
|
515
|
+
}
|
|
516
|
+
lastPress = 'held';
|
|
517
|
+
// Never canceled: canceling a touch's start would cancel the page's
|
|
518
|
+
// pinch-zoom with it.
|
|
519
|
+
event.stopImmediatePropagation();
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
if (!changed.some(touch => touches.has(touch.identifier))) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
if (type === 'touchmove') {
|
|
526
|
+
event.stopImmediatePropagation();
|
|
527
|
+
// One finger pans nothing; two still zoom the page.
|
|
528
|
+
if (down.length === 1 && event.cancelable) {
|
|
529
|
+
event.preventDefault();
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
// A canceled end sends no click and no compatibility mouse events.
|
|
534
|
+
swallow(event);
|
|
535
|
+
for (const touch of changed) {
|
|
536
|
+
touches.delete(touch.identifier);
|
|
537
|
+
}
|
|
538
|
+
pressEnded();
|
|
539
|
+
return true;
|
|
540
|
+
};
|
|
541
|
+
const onPress = event => {
|
|
542
|
+
// A click a key or assistive technology sends counts no press.
|
|
543
|
+
if (event.detail === 0) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
// A click that names its pointer is judged by that pointer. A
|
|
547
|
+
// compatibility mouse event, or a click sent as a plain mouse event,
|
|
548
|
+
// belongs to the last press.
|
|
549
|
+
const {
|
|
550
|
+
pointerType
|
|
551
|
+
} = event;
|
|
552
|
+
const isHeldPress = lastPress === 'held' && (pointerType == null || pointerType === '' || HELD_POINTERS.has(pointerType));
|
|
553
|
+
if (isHeldPress && (isPressDown() || now() <= clickUntil)) {
|
|
554
|
+
swallow(event);
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
return false;
|
|
558
|
+
};
|
|
559
|
+
function stop() {
|
|
560
|
+
if (!isListening) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
isListening = false;
|
|
564
|
+
if (pressHolds.get(doc) === pressHold) {
|
|
565
|
+
pressHolds.delete(doc);
|
|
566
|
+
}
|
|
567
|
+
if (timer !== undefined) {
|
|
568
|
+
clearTimeout(timer);
|
|
569
|
+
}
|
|
570
|
+
for (const unsubscribe of unsubscribers.splice(0)) {
|
|
571
|
+
unsubscribe();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// A touch's start and cancel are never canceled, so their subscriptions
|
|
576
|
+
// keep the stream passive for them. The hold hears each event first, so a
|
|
577
|
+
// held one reaches no other subscriber: press feedback and the gesture host
|
|
578
|
+
// never hear it.
|
|
579
|
+
const unsubscribers = [...POINTER_EVENTS.map(type => subscribeInput(view, type, onPointer, {
|
|
580
|
+
cancels: true,
|
|
581
|
+
first: true
|
|
582
|
+
})), ...TOUCH_EVENTS.map(type => subscribeInput(view, type, onTouch, {
|
|
583
|
+
cancels: type === 'touchmove' || type === 'touchend',
|
|
584
|
+
first: true
|
|
585
|
+
})), ...PRESS_EVENTS.map(type => subscribeInput(view, type, onPress, {
|
|
586
|
+
cancels: true,
|
|
587
|
+
first: true
|
|
588
|
+
}))];
|
|
589
|
+
const pressHold = {
|
|
590
|
+
hold() {
|
|
591
|
+
isHolding = true;
|
|
592
|
+
if (timer !== undefined) {
|
|
593
|
+
clearTimeout(timer);
|
|
594
|
+
timer = undefined;
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
release() {
|
|
598
|
+
if (!isHolding) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
isHolding = false;
|
|
602
|
+
settle();
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
return pressHold;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Makes the layer's content inert, except the shield, and moves focus out of
|
|
610
|
+
* it. An element that turns inert while focused keeps its focus and its key
|
|
611
|
+
* activation. Enter and Space still click such a button until the engine's
|
|
612
|
+
* focus fixup runs, if it runs. Blurring it leaves focus on the document, as
|
|
613
|
+
* that fixup does. The layer element itself stays live. An inert subtree is
|
|
614
|
+
* no hit target, so the shield inside the layer still catches every hit.
|
|
615
|
+
*/
|
|
616
|
+
function makeContentInert(layer, shield) {
|
|
617
|
+
const content = new Map();
|
|
618
|
+
for (const child of layer.children) {
|
|
619
|
+
if (child !== shield) {
|
|
620
|
+
content.set(child, child.hasAttribute('inert'));
|
|
621
|
+
child.setAttribute('inert', '');
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const focused = deepActiveElement(layer.ownerDocument);
|
|
625
|
+
if (focused instanceof HTMLElement && [...content.keys()].some(child => containsAlongFlatTree(child, focused))) {
|
|
626
|
+
focused.blur();
|
|
627
|
+
}
|
|
628
|
+
return content;
|
|
629
|
+
}
|
|
630
|
+
function restoreInert(content) {
|
|
631
|
+
for (const [element, wasInert] of content) {
|
|
632
|
+
if (!wasInert) {
|
|
633
|
+
element.removeAttribute('inert');
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run `reset`, which takes `root` out of rendering and back, and put every
|
|
3
3
|
* CSS animation and transition of `root` and its subtree back in the state it
|
|
4
|
-
* was in before.
|
|
5
|
-
* element, pseudo-element and name, in order, and then by name alone (an
|
|
6
|
-
* effect script moved to another element or pseudo-element); a recreated
|
|
7
|
-
* animation with no predecessor (an entry animation that had already played
|
|
8
|
-
* and been dropped) is finished.
|
|
4
|
+
* was in before.
|
|
9
5
|
*/
|
|
10
6
|
export declare function preserveAnimations(root: Element, reset: () => void): void;
|
|
11
7
|
//# sourceMappingURL=preserveAnimations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preserveAnimations.d.ts","sourceRoot":"","sources":["../../src/Layer/preserveAnimations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preserveAnimations.d.ts","sourceRoot":"","sources":["../../src/Layer/preserveAnimations.ts"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,IAAI,CASzE"}
|