@astryxdesign/core 0.7.6 → 0.7.7-canary.0eb44a2

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.
Files changed (272) hide show
  1. package/dist/AppShell/AppShell.d.ts.map +1 -1
  2. package/dist/AppShell/AppShell.js +23 -3
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +1 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetPanel.js +16 -9
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  8. package/dist/Breadcrumbs/BreadcrumbItem.js +2 -1
  9. package/dist/Button/Button.d.ts.map +1 -1
  10. package/dist/Button/Button.js +3 -2
  11. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  12. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  13. package/dist/Chat/ChatComposerInput.js +2 -0
  14. package/dist/Chat/ChatMessage.js +2 -2
  15. package/dist/Chat/chatMessage.vars.stylex.d.ts +8 -8
  16. package/dist/Chat/chatMessage.vars.stylex.js +8 -8
  17. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  18. package/dist/CommandPalette/CommandPaletteInput.js +8 -1
  19. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  20. package/dist/DateInput/MonthScroller.js +72 -2
  21. package/dist/DateInput/Wheel.d.ts +17 -1
  22. package/dist/DateInput/Wheel.d.ts.map +1 -1
  23. package/dist/DateInput/Wheel.js +179 -19
  24. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  25. package/dist/DateRangeInput/DateRangeInput.js +63 -49
  26. package/dist/Dialog/Dialog.d.ts.map +1 -1
  27. package/dist/Dialog/Dialog.js +37 -1
  28. package/dist/Drawer/Drawer.d.ts.map +1 -1
  29. package/dist/Drawer/Drawer.js +6 -0
  30. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  31. package/dist/Drawer/useDrawerDialogPresence.js +4 -0
  32. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  33. package/dist/DropdownMenu/DropdownMenu.js +20 -4
  34. package/dist/DropdownMenu/useMenuOverflow.d.ts +13 -3
  35. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -1
  36. package/dist/DropdownMenu/useMenuOverflow.js +31 -8
  37. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  38. package/dist/Field/PanelSearchInput.js +8 -1
  39. package/dist/Field/textEntryTraits.d.ts +6 -6
  40. package/dist/Field/textEntryTraits.js +6 -6
  41. package/dist/Layer/layerClickBoundary.d.ts +7 -1
  42. package/dist/Layer/layerClickBoundary.d.ts.map +1 -1
  43. package/dist/Layer/layerClickBoundary.js +2 -2
  44. package/dist/Layer/usePressPin.d.ts +95 -0
  45. package/dist/Layer/usePressPin.d.ts.map +1 -0
  46. package/dist/Layer/usePressPin.js +365 -0
  47. package/dist/Layer/useTouchTrigger.d.ts +23 -1
  48. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  49. package/dist/Layer/useTouchTrigger.js +45 -7
  50. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  51. package/dist/Lightbox/Lightbox.js +2 -1
  52. package/dist/Link/Link.d.ts.map +1 -1
  53. package/dist/Link/Link.js +1 -0
  54. package/dist/Popover/usePopover.d.ts.map +1 -1
  55. package/dist/Popover/usePopover.js +56 -7
  56. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  57. package/dist/PowerSearch/PowerSearch.js +38 -30
  58. package/dist/ProgressBar/ProgressBar.d.ts +1 -1
  59. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  60. package/dist/ProgressBar/ProgressBar.js +52 -14
  61. package/dist/ProgressBar/ProgressBarTouch.d.ts +29 -0
  62. package/dist/ProgressBar/ProgressBarTouch.d.ts.map +1 -0
  63. package/dist/ProgressBar/ProgressBarTouch.js +91 -0
  64. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  65. package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
  66. package/dist/Selector/Selector.d.ts.map +1 -1
  67. package/dist/Selector/Selector.js +25 -13
  68. package/dist/Slider/Slider.d.ts.map +1 -1
  69. package/dist/Slider/Slider.js +404 -131
  70. package/dist/Slider/sliderTouchController.d.ts +189 -0
  71. package/dist/Slider/sliderTouchController.d.ts.map +1 -0
  72. package/dist/Slider/sliderTouchController.js +1017 -0
  73. package/dist/Slider/useSliderTouch.d.ts +22 -0
  74. package/dist/Slider/useSliderTouch.d.ts.map +1 -0
  75. package/dist/Slider/useSliderTouch.js +56 -0
  76. package/dist/Switch/Switch.d.ts +1 -1
  77. package/dist/Switch/Switch.d.ts.map +1 -1
  78. package/dist/Switch/Switch.js +200 -46
  79. package/dist/Switch/switchFill.d.ts +31 -0
  80. package/dist/Switch/switchFill.d.ts.map +1 -0
  81. package/dist/Switch/switchFill.js +88 -0
  82. package/dist/Switch/switchTouch.d.ts +81 -0
  83. package/dist/Switch/switchTouch.d.ts.map +1 -0
  84. package/dist/Switch/switchTouch.js +133 -0
  85. package/dist/Switch/useSwitchTouch.d.ts +285 -0
  86. package/dist/Switch/useSwitchTouch.d.ts.map +1 -0
  87. package/dist/Switch/useSwitchTouch.js +998 -0
  88. package/dist/TabList/Tab.d.ts.map +1 -1
  89. package/dist/TabList/Tab.js +4 -3
  90. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  91. package/dist/Tokenizer/Tokenizer.js +12 -5
  92. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts +3 -0
  93. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -0
  94. package/dist/Tokenizer/TokenizerItemRemovableContext.js +17 -0
  95. package/dist/Tooltip/Tooltip.d.ts +30 -6
  96. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  97. package/dist/Tooltip/Tooltip.js +5 -0
  98. package/dist/Tooltip/index.d.ts +1 -1
  99. package/dist/Tooltip/index.d.ts.map +1 -1
  100. package/dist/Tooltip/useTooltip.d.ts +31 -5
  101. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  102. package/dist/Tooltip/useTooltip.js +59 -18
  103. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  104. package/dist/Typeahead/BaseTypeahead.js +19 -9
  105. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  106. package/dist/Typeahead/Typeahead.js +26 -15
  107. package/dist/astryx.css +91 -8
  108. package/dist/gestures/GestureArena.d.ts +21 -2
  109. package/dist/gestures/GestureArena.d.ts.map +1 -1
  110. package/dist/gestures/GestureArena.js +23 -3
  111. package/dist/gestures/GestureHost.d.ts +123 -3
  112. package/dist/gestures/GestureHost.d.ts.map +1 -1
  113. package/dist/gestures/GestureHost.js +797 -15
  114. package/dist/gestures/GestureRecognizer.d.ts +15 -1
  115. package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
  116. package/dist/gestures/GestureRecognizer.js +12 -0
  117. package/dist/gestures/PanGestureRecognizer.d.ts +8 -0
  118. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  119. package/dist/gestures/PinchGestureRecognizer.d.ts +5 -1
  120. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -1
  121. package/dist/gestures/PinchGestureRecognizer.js +10 -0
  122. package/dist/gestures/PressTracker.d.ts +6 -0
  123. package/dist/gestures/PressTracker.d.ts.map +1 -1
  124. package/dist/gestures/PressTracker.js +3 -0
  125. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +3 -3
  126. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
  127. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +7 -5
  128. package/dist/gestures/nativeScroll.d.ts +16 -2
  129. package/dist/gestures/nativeScroll.d.ts.map +1 -1
  130. package/dist/gestures/nativeScroll.js +39 -8
  131. package/dist/gestures/recognizerInternals.d.ts +4 -2
  132. package/dist/gestures/recognizerInternals.d.ts.map +1 -1
  133. package/dist/gestures/types.d.ts +17 -0
  134. package/dist/gestures/types.d.ts.map +1 -1
  135. package/dist/gestures/types.js +7 -0
  136. package/dist/gestures/usePressTracker.d.ts.map +1 -1
  137. package/dist/gestures/usePressTracker.js +1 -0
  138. package/dist/gestures/wheelSegmenter.d.ts +56 -0
  139. package/dist/gestures/wheelSegmenter.d.ts.map +1 -0
  140. package/dist/gestures/wheelSegmenter.js +122 -0
  141. package/dist/hooks/useAttachment.d.ts +16 -8
  142. package/dist/hooks/useAttachment.d.ts.map +1 -1
  143. package/dist/hooks/useAttachment.js +35 -15
  144. package/dist/hooks/useFocusTrap.d.ts +15 -0
  145. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  146. package/dist/hooks/useFocusTrap.js +13 -2
  147. package/dist/hooks/usePager.d.ts +261 -0
  148. package/dist/hooks/usePager.d.ts.map +1 -0
  149. package/dist/hooks/usePager.js +1169 -0
  150. package/dist/motion/animator.d.ts.map +1 -1
  151. package/dist/motion/animator.js +140 -19
  152. package/dist/motion/paging.d.ts +69 -0
  153. package/dist/motion/paging.d.ts.map +1 -0
  154. package/dist/motion/paging.js +148 -0
  155. package/dist/motion/uikit/index.d.ts +8 -4
  156. package/dist/motion/uikit/index.d.ts.map +1 -1
  157. package/dist/motion/uikit/index.js +8 -4
  158. package/dist/motion/uikit/paging.d.ts +171 -0
  159. package/dist/motion/uikit/paging.d.ts.map +1 -0
  160. package/dist/motion/uikit/paging.js +192 -0
  161. package/dist/motion/uikit/progressBar.d.ts +79 -0
  162. package/dist/motion/uikit/progressBar.d.ts.map +1 -0
  163. package/dist/motion/uikit/progressBar.js +102 -0
  164. package/dist/motion/uikit/scroll.d.ts +0 -19
  165. package/dist/motion/uikit/scroll.d.ts.map +1 -1
  166. package/dist/motion/uikit/scroll.js +2 -21
  167. package/dist/motion/uikit/slider.d.ts +97 -0
  168. package/dist/motion/uikit/slider.d.ts.map +1 -0
  169. package/dist/motion/uikit/slider.js +111 -0
  170. package/dist/motion/uikit/springs.d.ts +59 -30
  171. package/dist/motion/uikit/springs.d.ts.map +1 -1
  172. package/dist/motion/uikit/springs.js +64 -34
  173. package/dist/motion/uikit/switch.d.ts +166 -0
  174. package/dist/motion/uikit/switch.d.ts.map +1 -0
  175. package/dist/motion/uikit/switch.js +186 -0
  176. package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts +18 -0
  177. package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts.map +1 -0
  178. package/dist/privateAttributes/pressHoldsDrag.stylex.js +28 -0
  179. package/dist/utils/pressFeedback.d.ts.map +1 -1
  180. package/dist/utils/pressFeedback.js +5 -0
  181. package/dist/utils/selectionLock.d.ts +43 -0
  182. package/dist/utils/selectionLock.d.ts.map +1 -1
  183. package/dist/utils/selectionLock.js +394 -6
  184. package/package.json +5 -5
  185. package/src/AppShell/AppShell.doc.mjs +10 -0
  186. package/src/AppShell/AppShell.tsx +48 -2
  187. package/src/Avatar/Avatar.tsx +3 -1
  188. package/src/BottomSheet/BottomSheetPanel.tsx +16 -9
  189. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  190. package/src/Button/Button.tsx +3 -2
  191. package/src/Chat/Chat.doc.mjs +1 -0
  192. package/src/Chat/ChatComposerInput.doc.mjs +11 -0
  193. package/src/Chat/ChatComposerInput.tsx +11 -0
  194. package/src/Chat/ChatMessage.tsx +2 -2
  195. package/src/Chat/chatMessage.vars.stylex.ts +8 -8
  196. package/src/CommandPalette/CommandPaletteInput.tsx +6 -0
  197. package/src/DateInput/MonthScroller.tsx +63 -5
  198. package/src/DateInput/Wheel.tsx +174 -14
  199. package/src/DateRangeInput/DateRangeInput.doc.mjs +3 -2
  200. package/src/DateRangeInput/DateRangeInput.tsx +117 -71
  201. package/src/Dialog/Dialog.tsx +37 -1
  202. package/src/Drawer/Drawer.tsx +6 -0
  203. package/src/Drawer/useDrawerDialogPresence.ts +4 -0
  204. package/src/DropdownMenu/DropdownMenu.tsx +28 -6
  205. package/src/DropdownMenu/useMenuOverflow.ts +44 -8
  206. package/src/Field/PanelSearchInput.tsx +6 -0
  207. package/src/Field/textEntryTraits.ts +6 -6
  208. package/src/Layer/layerClickBoundary.ts +2 -2
  209. package/src/Layer/usePressPin.ts +516 -0
  210. package/src/Layer/useTouchTrigger.ts +56 -6
  211. package/src/Lightbox/Lightbox.tsx +2 -1
  212. package/src/Link/Link.tsx +3 -0
  213. package/src/Popover/usePopover.tsx +71 -9
  214. package/src/PowerSearch/PowerSearch.tsx +38 -31
  215. package/src/ProgressBar/ProgressBar.doc.mjs +7 -5
  216. package/src/ProgressBar/ProgressBar.tsx +147 -16
  217. package/src/ProgressBar/ProgressBarTouch.ts +109 -0
  218. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -1
  219. package/src/Selector/Selector.tsx +29 -2
  220. package/src/Slider/Slider.doc.mjs +11 -7
  221. package/src/Slider/Slider.tsx +539 -132
  222. package/src/Slider/sliderTouchController.ts +1209 -0
  223. package/src/Slider/useSliderTouch.ts +78 -0
  224. package/src/Switch/Switch.doc.mjs +3 -3
  225. package/src/Switch/Switch.tsx +412 -86
  226. package/src/Switch/switchFill.ts +106 -0
  227. package/src/Switch/switchTouch.ts +194 -0
  228. package/src/Switch/useSwitchTouch.ts +1121 -0
  229. package/src/TabList/Tab.tsx +5 -3
  230. package/src/Tokenizer/Tokenizer.tsx +15 -5
  231. package/src/Tokenizer/TokenizerItemRemovableContext.ts +22 -0
  232. package/src/Tooltip/Tooltip.doc.mjs +21 -6
  233. package/src/Tooltip/Tooltip.tsx +42 -5
  234. package/src/Tooltip/index.ts +1 -0
  235. package/src/Tooltip/useTooltip.doc.mjs +4 -2
  236. package/src/Tooltip/useTooltip.tsx +84 -21
  237. package/src/Typeahead/BaseTypeahead.tsx +16 -8
  238. package/src/Typeahead/Typeahead.tsx +29 -15
  239. package/src/gestures/GestureArena.ts +45 -4
  240. package/src/gestures/GestureHost.ts +958 -15
  241. package/src/gestures/GestureRecognizer.ts +28 -0
  242. package/src/gestures/PanGestureRecognizer.ts +8 -0
  243. package/src/gestures/PinchGestureRecognizer.ts +16 -1
  244. package/src/gestures/PressTracker.ts +10 -0
  245. package/src/gestures/README.md +82 -3
  246. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +7 -5
  247. package/src/gestures/nativeScroll.ts +49 -9
  248. package/src/gestures/recognizerInternals.ts +9 -2
  249. package/src/gestures/types.ts +18 -0
  250. package/src/gestures/usePressTracker.ts +2 -0
  251. package/src/gestures/wheelSegmenter.ts +143 -0
  252. package/src/hooks/useAttachment.ts +56 -14
  253. package/src/hooks/useFocusTrap.ts +26 -2
  254. package/src/hooks/usePager.ts +1446 -0
  255. package/src/motion/MEASUREMENTS.md +36 -27
  256. package/src/motion/README.md +7 -3
  257. package/src/motion/animator.ts +193 -25
  258. package/src/motion/paging.ts +161 -0
  259. package/src/motion/uikit/index.ts +7 -4
  260. package/src/motion/uikit/paging.ts +193 -0
  261. package/src/motion/uikit/progressBar.ts +102 -0
  262. package/src/motion/uikit/scroll.ts +2 -18
  263. package/src/motion/uikit/slider.ts +105 -0
  264. package/src/motion/uikit/springs.ts +53 -32
  265. package/src/motion/uikit/switch.ts +174 -0
  266. package/src/privateAttributes/pressHoldsDrag.stylex.ts +28 -0
  267. package/src/utils/pressFeedback.ts +5 -0
  268. package/src/utils/selectionLock.ts +501 -6
  269. package/dist/motion/selectPage.d.ts +0 -26
  270. package/dist/motion/selectPage.d.ts.map +0 -1
  271. package/dist/motion/selectPage.js +0 -55
  272. package/src/motion/selectPage.ts +0 -55
@@ -36,6 +36,7 @@ import type {
36
36
  GestureTouch,
37
37
  Point,
38
38
  PointerKind,
39
+ ScrollType,
39
40
  } from './types';
40
41
  import {DEFAULT_ENVIRONMENT} from './types';
41
42
 
@@ -81,6 +82,16 @@ export interface GestureRecognizerOptions<R = GestureRecognizer> {
81
82
  delaysTouchesEnded?: boolean;
82
83
  /** Pointer kinds this recognizer listens to (UIKit's `allowedTouchTypes`). @default all */
83
84
  allowedPointerTypes?: ReadonlyArray<PointerKind>;
85
+ /**
86
+ * Scroll input this recognizer takes, as UIKit's `allowedScrollTypesMask`.
87
+ * A pan follows a trackpad's two-finger scroll (`continuous`), never a
88
+ * mouse wheel's notches. It moves as a contact with the content, along its
89
+ * own axis, after the scrollers under the pointer. A pinch takes a
90
+ * ctrl-wheel of either, as a trackpad pinches.
91
+ * @default none; a pinch takes both, the screen-edge pan none whatever a
92
+ * caller passes
93
+ */
94
+ allowedScrollTypes?: ReadonlyArray<ScrollType>;
84
95
  /** Ignore a touch of a different kind than the ones already tracked. @default true */
85
96
  requiresExclusiveTouchType?: boolean;
86
97
  /**
@@ -202,6 +213,8 @@ export abstract class GestureRecognizer {
202
213
  delaysTouchesBegan: boolean;
203
214
  delaysTouchesEnded: boolean;
204
215
  allowedPointerTypes: ReadonlySet<PointerKind> | null;
216
+ /** See GestureRecognizerOptions.allowedScrollTypes. */
217
+ readonly allowedScrollTypes: ReadonlySet<ScrollType>;
205
218
  requiresExclusiveTouchType: boolean;
206
219
  /**
207
220
  * The element (or any object) the recognizer is attached to. The DOM host
@@ -247,6 +260,9 @@ export abstract class GestureRecognizer {
247
260
  options.allowedPointerTypes == null
248
261
  ? null
249
262
  : new Set(options.allowedPointerTypes);
263
+ this.allowedScrollTypes = new Set(
264
+ options.allowedScrollTypes ?? this.defaultScrollTypes,
265
+ );
250
266
  this.requiresExclusiveTouchType =
251
267
  options.requiresExclusiveTouchType ?? true;
252
268
  }
@@ -479,6 +495,11 @@ export abstract class GestureRecognizer {
479
495
  /** Clear per-gesture state before the next attempt. */
480
496
  protected reset(): void {}
481
497
 
498
+ /** The scroll types taken when the options name none (UIKit's: none). */
499
+ protected get defaultScrollTypes(): ReadonlyArray<ScrollType> {
500
+ return [];
501
+ }
502
+
482
503
  /**
483
504
  * Whether a touch may join. The base checks enabled, pointer kind and
484
505
  * exclusivity of kind; subclasses narrow it (a one-finger pan ignores a
@@ -488,6 +509,13 @@ export abstract class GestureRecognizer {
488
509
  if (!this.isEnabled) {
489
510
  return false;
490
511
  }
512
+ if (touch.scrollType != null) {
513
+ // Scroll input is no pointer: only the scroll types allowed count.
514
+ return (
515
+ this.allowedScrollTypes.has(touch.scrollType) &&
516
+ (this.options.shouldReceiveTouch?.(this, touch) ?? true)
517
+ );
518
+ }
491
519
  if (
492
520
  this.allowedPointerTypes != null &&
493
521
  !this.allowedPointerTypes.has(touch.pointerType)
@@ -73,6 +73,14 @@ export interface PanGestureRecognizerOptions extends GestureRecognizerOptions<Pa
73
73
  * at the start only (UIScrollView's directional lock).
74
74
  */
75
75
  lockAxis?: boolean;
76
+ /**
77
+ * A trackpad's two-finger scroll (`continuous`) drives the pan where its
78
+ * owner allows it. It moves as a contact with the content, along its own
79
+ * axis, after the scrollers under the pointer. A mouse wheel's notches
80
+ * never drive a pan; they scroll what is under the pointer.
81
+ * @default [] (none)
82
+ */
83
+ allowedScrollTypes?: ReadonlyArray<'continuous'>;
76
84
  /** Begin only when the native scrollers under the touch are at the edge. */
77
85
  scrollEdge?: boolean;
78
86
  }
@@ -27,7 +27,12 @@ import {
27
27
  type GestureRecognizerOptions,
28
28
  type NativeTouchBehavior,
29
29
  } from './GestureRecognizer';
30
- import type {GestureEventContext, GestureTouch, Point} from './types';
30
+ import type {
31
+ GestureEventContext,
32
+ GestureTouch,
33
+ Point,
34
+ ScrollType,
35
+ } from './types';
31
36
  import {VelocityTracker} from './VelocityTracker';
32
37
 
33
38
  export interface PinchGestureRecognizerOptions extends GestureRecognizerOptions<PinchGestureRecognizer> {
@@ -51,6 +56,16 @@ export class PinchGestureRecognizer extends GestureRecognizer {
51
56
  this.hysteresis = options.hysteresis ?? UIKIT_PINCH.hysteresis.value;
52
57
  }
53
58
 
59
+ /** @internal The host's brand: a ctrl-wheel drives a pinch (recognizerInternals). */
60
+ protected get family(): 'pinch' {
61
+ return 'pinch';
62
+ }
63
+
64
+ /** A ctrl-wheel drives a pinch, from a trackpad or a mouse. */
65
+ protected override get defaultScrollTypes(): ReadonlyArray<ScrollType> {
66
+ return ['continuous', 'discrete'];
67
+ }
68
+
54
69
  /** Current span over the span the fingers started at. */
55
70
  get scale(): number {
56
71
  return this.scaleValue;
@@ -95,6 +95,12 @@ export interface PressTrackerOptions {
95
95
  * does nothing passes none. @default undefined
96
96
  */
97
97
  selectionLock?: SelectionLock;
98
+ /**
99
+ * Whether the press of `touchId` holds the browser's context menu closed
100
+ * now; the host then cancels it. For a control whose hold acts, such as a
101
+ * switch. @default undefined
102
+ */
103
+ holdsContextMenu?: (touchId: number) => boolean;
98
104
  trackingExpansion?: number;
99
105
  /**
100
106
  * When the highlight waits `contentTouchDelay`: inside a scroller (a
@@ -312,6 +318,10 @@ export function createPressTracker(options: PressTrackerOptions): PressTracker {
312
318
  return options.isEnabled?.() !== false;
313
319
  },
314
320
 
321
+ holdsContextMenu(id: number): boolean {
322
+ return options.holdsContextMenu?.(id) === true;
323
+ },
324
+
315
325
  touchBegan(touch: GestureTouch): void {
316
326
  if (
317
327
  touchId != null ||
@@ -304,9 +304,25 @@ presses nothing.
304
304
  controller's surfaces (`usePressFeedback` rows), keep the browser's
305
305
  long-press selection; a tap never selects. A non-text input under the finger inside such an element (a checkbox, a radio, a switch's input) is locked too: WebKit's own stylesheet makes every input selectable, which no ancestor's lock reaches. A text field keeps its selection, since a held finger places its caret. A
306
306
  finger or a pen takes the lock under any primary pointer, a touchscreen
307
- laptop and a desktop pen tablet included; a mouse never does, so mouse
308
- selection is unchanged (`spec:AST-017` FR59); content under a sheet's or a
309
- pager's pan stays selectable.
307
+ laptop and a desktop pen tablet included; a mouse never does
308
+ (`spec:AST-017` FR59).
309
+
310
+ A mouse or pen drag that a recognizer claims takes no new text selection
311
+ and never clears the page's (`spec:AST-094` FR41): from the claim until a
312
+ task after the lift (the press's own `mouseup` and `click` come in its
313
+ task), the claiming elements and the document refuse a selection, and the
314
+ selection the page had when the press began is put back over whatever the
315
+ press or the drag made of it. The hold starts at the claim of whichever
316
+ recognizer makes it, whatever its state: a swipe recognizes as `ended`
317
+ while the button is still down, and a recognizer that waits on another's
318
+ failure claims while still `possible`. A claim given back to the browser
319
+ (that waiting recognizer failing or cancelled, with no other owner) lets
320
+ the hold go, and the drag selects again. A drag that starts on selectable text (a
321
+ character of text that allows a selection, or a text field) is not
322
+ claimed: no recognizer recognizes with it while it is down, so it selects
323
+ text, and content under a sheet's or a pager's pan stays selectable. A
324
+ press a recognizer claims at its own pointerdown, before it moves, is no
325
+ drag, and a tap claims only at its lift.
310
326
 
311
327
  - **Claiming.** When a recognizer recognizes, it owns its touches. Each
312
328
  element with a recognizer that can claim a moving touch (a pan, a swipe, a
@@ -452,6 +468,69 @@ anywhere (often on a plain row no recognizer is on) fails a pan limited to
452
468
  one finger, so the two fingers zoom the page (WCAG 1.4.4) instead of the pan
453
469
  claiming them midway.
454
470
 
471
+ ## Wheel and trackpad
472
+
473
+ A recognizer takes scroll input only where it allows that scroll type
474
+ (`allowedScrollTypes`, UIKit's `allowedScrollTypesMask`): `continuous`
475
+ for a trackpad's two-finger scroll or pinch, `discrete` for a mouse wheel's
476
+ notches. A pan follows a trackpad, never a mouse wheel (`spec:AST-094` FR43,
477
+ DEC-24): it allows only `continuous`, and none by default, so it takes a
478
+ trackpad's scroll only where its owner allows it, its element then getting
479
+ the one wheel listener, and a mouse wheel's notches always scroll what is
480
+ under the pointer. A pinch allows both, for a ctrl-wheel from a trackpad or
481
+ a mouse. The screen-edge pan takes none, as it takes no mouse, trackpad or
482
+ pen (FR43). A control's thumb, a slider's or a switch's, tracks as a control,
483
+ not a pan, and takes no wheel, so a page scroll over it never changes its
484
+ value (DEC-23). The web reports no wheel phases or device, so the deltas
485
+ tell, and what they cannot tell apart reads as a mouse wheel: a line- or
486
+ page-mode delta; a scroll with Shift held (Shift+wheel); a pixel delta a hair
487
+ off a whole pixel (a mouse wheel on Windows); a scroll with no legacy `wheelDelta` (an engine that
488
+ sends none); a delta that is not a third of its legacy `wheelDelta`; or a 120
489
+ tick over a 40 px delta. A trackpad's precise deltas, a third of their legacy
490
+ `wheelDelta` (Chromium and WebKit), read as a trackpad.
491
+
492
+ A wheel gesture drives a pan as a contact moving with the content, along the
493
+ pan's own axis, and only once every scroller between the pointer and the
494
+ pan's element is at its edge that way, decided at the gesture's first wheel:
495
+ a wheel the pan does not take is never prevented. A watcher
496
+ (`claimsTouches: false`) never takes one, and a gesture whose first wheel
497
+ another handler already prevented, or that cannot be cancelled, stays the
498
+ page's. A scroll-edge pan (a pull to
499
+ refresh) takes one only if no scroller under the pointer could scroll for it,
500
+ its own scroll owner and the page included, as its finger's rule reads them
501
+ (`spec:AST-094` FR19); `touch-action` narrows only a finger's pan, so a wheel
502
+ reads every scroller past it. A ctrl-wheel (a trackpad
503
+ pinch, or Control and a mouse wheel) drives a pinch, whose scale follows
504
+ `exp(-deltaY / 100)`; a pinch takes it by default and ends 100 ms after its
505
+ last wheel. While the fingers move, a trackpad scroll ends 180 ms after its
506
+ last wheel vertically and 220 ms horizontally, whoever keeps it (a pan, a
507
+ scroller under the pointer, or the page), as a slow drag pauses between
508
+ wheels (DEC-24). A gesture's release is never stamped
509
+ later than an input that ends it sooner (FR16).
510
+
511
+ The web reports no trackpad contact or phase either, so one splitter reads
512
+ every trackpad stream (`wheelSegmenter.ts`, DEC-24): at the fourth or later
513
+ delta of a shrinking run, once the run has lost a fifth of its size, the
514
+ fingers lifted and momentum began. Until it decides,
515
+ the shrinking deltas wait, and a pan it follows moves on at the fingers' last
516
+ speed, never past what that speed projects. At the lift the pan keeps that
517
+ travel and lets go there, still moving at that speed, and the momentum after
518
+ it never moves it; when the fingers carry on instead, their moves absorb
519
+ what the pan showed past their travel, or short of it, so it never steps
520
+ back and ends on their travel. Fingers that stop release still, once the end
521
+ time passes. In momentum, a reversal, a delta that grows
522
+ against the decay, or a 50 ms pause starts a new gesture. A finger, mouse or
523
+ pen that comes down ends it, and a wheel claim keeps the page's text
524
+ selection. A gesture a
525
+ recognizer took is latched to it, as a browser latches a scroller: while it
526
+ lasts, the host hears every step first, through the shared input stream's
527
+ window-capture listener, wherever it lands, so a pan whose element moves from
528
+ under a still pointer keeps the gesture. A page that loses focus or hides
529
+ cancels a trackpad pan a recognizer still drives, as it cancels every touch;
530
+ the rest of that stream is the page's. A ctrl-wheel pinch keeps its stream,
531
+ so the page never zooms for it, and a pan its lift released keeps its
532
+ momentum latched until the stream ends.
533
+
455
534
  ## Time
456
535
 
457
536
  Every input carries its own timestamp (`event.timeStamp`), and timers are
@@ -71,12 +71,12 @@ interface EdgeLayout {
71
71
  }
72
72
 
73
73
  /**
74
- * A screen-edge pan is a finger's: it takes no `allowedPointerTypes`
75
- * (`spec:AST-094` FR43).
74
+ * A screen-edge pan is a finger's: it takes no `allowedPointerTypes` and no
75
+ * `allowedScrollTypes`, so neither a mouse, a trackpad nor a pen drives it.
76
76
  */
77
77
  export interface ScreenEdgePanGestureRecognizerOptions extends Omit<
78
78
  PanGestureRecognizerOptions,
79
- 'direction' | 'allowedPointerTypes'
79
+ 'direction' | 'allowedPointerTypes' | 'allowedScrollTypes'
80
80
  > {
81
81
  /** The edges a touch may start from. @default ['leading'] */
82
82
  edges?: ReadonlyArray<ScreenEdge>;
@@ -109,9 +109,11 @@ export class ScreenEdgePanGestureRecognizer extends PanGestureRecognizer {
109
109
  maximumNumberOfTouches: 1,
110
110
  hysteresis: UIKIT_SCREEN_EDGE_PAN.hysteresis.value,
111
111
  ...options,
112
- // A finger's only: under a mouse, a trackpad or a pen the screen-edge
113
- // pan stays off (`spec:AST-094` FR43), whatever a caller passes.
112
+ // A finger's only (`spec:AST-094` FR43). Under a mouse, a trackpad or
113
+ // a pen, the screen-edge pan stays off, whatever a caller passes. It
114
+ // takes no wheel or trackpad scrolling.
114
115
  allowedPointerTypes: ['touch'],
116
+ allowedScrollTypes: [],
115
117
  });
116
118
  this.edges = options.edges ?? ['leading'];
117
119
  this.edgeRegion =
@@ -4,7 +4,8 @@
4
4
  * @file nativeScroll.ts
5
5
  * @input An element a touch landed on
6
6
  * @output Exports createNativeScrollProbe (the NativeScrollProbe a host gives
7
- * each touch) and parseTouchAction
7
+ * each touch, and each wheel gesture without the touch-action narrowing)
8
+ * and parseTouchAction
8
9
  * @position DOM half of the scroll coordination in packages/core/src/gestures
9
10
  *
10
11
  * Answers one question for a recognizer without letting it touch the DOM:
@@ -82,7 +83,7 @@ interface Chain {
82
83
  readonly allowsPan: boolean;
83
84
  }
84
85
 
85
- function buildChain(target: Element): Chain {
86
+ function buildChain(target: Element, readsTouchAction: boolean): Chain {
86
87
  const doc = target.ownerDocument;
87
88
  const view = doc.defaultView;
88
89
  if (view == null) {
@@ -103,9 +104,11 @@ function buildChain(target: Element): Chain {
103
104
  continue;
104
105
  }
105
106
  const style = view.getComputedStyle(node);
106
- const pans = parseTouchAction(style.touchAction || 'auto');
107
- allowX &&= pans.x;
108
- allowY &&= pans.y;
107
+ if (readsTouchAction) {
108
+ const pans = parseTouchAction(style.touchAction || 'auto');
109
+ allowX &&= pans.x;
110
+ allowY &&= pans.y;
111
+ }
109
112
  const isRoot = node === root || node === doc.documentElement;
110
113
  if (isRoot) {
111
114
  reachedRoot = true;
@@ -196,7 +199,10 @@ function canMoveY(element: Element, dy: number): boolean {
196
199
  element.scrollHeight - element.clientHeight - EDGE_TOLERANCE + 0.5;
197
200
  }
198
201
 
199
- function canMoveX(entry: ChainEntry, dx: number): boolean {
202
+ function canMoveX(
203
+ entry: Pick<ChainEntry, 'element' | 'isRtl'>,
204
+ dx: number,
205
+ ): boolean {
200
206
  const {element} = entry;
201
207
  const overflow = element.scrollWidth - element.clientWidth;
202
208
  if (overflow <= EDGE_TOLERANCE) {
@@ -210,13 +216,47 @@ function canMoveX(entry: ChainEntry, dx: number): boolean {
210
216
  : element.scrollLeft < max - EDGE_TOLERANCE + 0.5;
211
217
  }
212
218
 
213
- /** A probe of the native scrollers under `target`. */
214
- export function createNativeScrollProbe(target: unknown): NativeScrollProbe {
219
+ /**
220
+ * Whether `element` itself scrolls natively now for a finger moving by
221
+ * (dx, dy). It uses the probe's own test of each scroller: overflow that
222
+ * scrolls on that axis, and room past the edge tolerance. The position must
223
+ * also be short of the end it moves toward, a right-to-left range included.
224
+ * A wheel moves the content the other way, so pass its deltas negated.
225
+ */
226
+ export function canElementScroll(
227
+ element: Element,
228
+ dx: number,
229
+ dy: number,
230
+ ): boolean {
231
+ const view = element.ownerDocument.defaultView;
232
+ if (view == null) {
233
+ return false;
234
+ }
235
+ const style = view.getComputedStyle(element);
236
+ if (dy !== 0 && SCROLLABLE.has(style.overflowY) && canMoveY(element, dy)) {
237
+ return true;
238
+ }
239
+ return (
240
+ dx !== 0 &&
241
+ SCROLLABLE.has(style.overflowX) &&
242
+ canMoveX({element, isRtl: isRtlElement(element as HTMLElement)}, dx)
243
+ );
244
+ }
245
+
246
+ /**
247
+ * A probe of the native scrollers under `target`. A wheel scrolls whatever
248
+ * `touch-action` says, which narrows only a finger's pan: a wheel's probe
249
+ * passes `readsTouchAction: false`.
250
+ */
251
+ export function createNativeScrollProbe(
252
+ target: unknown,
253
+ {readsTouchAction = true}: {readsTouchAction?: boolean} = {},
254
+ ): NativeScrollProbe {
215
255
  let chain: Chain | null = null;
216
256
  const getChain = (): ChainEntry[] => {
217
257
  chain ??=
218
258
  target instanceof Element
219
- ? buildChain(target)
259
+ ? buildChain(target, readsTouchAction)
220
260
  : {entries: [], allowsPan: false};
221
261
  return chain.entries;
222
262
  };
@@ -20,7 +20,12 @@
20
20
 
21
21
  import type {GestureArena} from './GestureArena';
22
22
  import type {GestureRecognizer} from './GestureRecognizer';
23
- import type {GestureEventContext, GestureState, GestureTouch} from './types';
23
+ import type {
24
+ GestureEventContext,
25
+ GestureState,
26
+ GestureTouch,
27
+ Point,
28
+ } from './types';
24
29
 
25
30
  /**
26
31
  * The kinds of recognizer the host and touch-action treat specially. Each of
@@ -28,12 +33,14 @@ import type {GestureEventContext, GestureState, GestureTouch} from './types';
28
33
  * never imports a recognizer class: a page ships only the recognizers it
29
34
  * constructs.
30
35
  */
31
- export type RecognizerFamily = 'tap' | 'longPress' | 'pan';
36
+ export type RecognizerFamily = 'tap' | 'longPress' | 'pan' | 'pinch';
32
37
 
33
38
  /** The members of a recognizer only the gesture system calls. */
34
39
  export interface RecognizerInternals {
35
40
  /** The recognizer's family, when the host treats it specially. */
36
41
  readonly family?: RecognizerFamily;
42
+ /** A pan's direction rule for a translation, `axisRatio` included. */
43
+ classify?(translation: Point): 'begin' | 'fail' | 'wait';
37
44
  arena: GestureArena | null;
38
45
  pending: 'began' | 'ended' | null;
39
46
  readonly seenTouchIds: Set<number>;
@@ -21,6 +21,14 @@ export type GestureState =
21
21
  /** What made the contact. A pen on an iPad is `pen`, not `touch`. */
22
22
  export type PointerKind = 'touch' | 'mouse' | 'pen';
23
23
 
24
+ /**
25
+ * Scroll input a recognizer can take, as UIKit's `UIScrollTypeMask`: a
26
+ * trackpad's two-finger scroll or pinch (`continuous`), or a mouse wheel's
27
+ * notches (`discrete`). A pan takes only a trackpad's; a pinch takes a
28
+ * ctrl-wheel of either.
29
+ */
30
+ export type ScrollType = 'continuous' | 'discrete';
31
+
24
32
  export interface Point {
25
33
  readonly x: number;
26
34
  readonly y: number;
@@ -85,6 +93,8 @@ export interface GestureTouch {
85
93
  readonly target: unknown;
86
94
  /** The native scrollers under the contact, when the host knows them. */
87
95
  readonly scroll: NativeScrollProbe | null;
96
+ /** Set for a wheel's or a trackpad's scroll input, which is no contact. */
97
+ readonly scrollType?: ScrollType;
88
98
  /** The browser took this contact for its own pan or zoom. */
89
99
  takenByBrowser: boolean;
90
100
  }
@@ -104,6 +114,8 @@ export interface TouchBeganInput extends TouchInput {
104
114
  readonly pointerType: PointerKind;
105
115
  readonly target: unknown;
106
116
  readonly scroll?: NativeScrollProbe | null;
117
+ /** Set for a wheel's or a trackpad's scroll input. */
118
+ readonly scrollType?: ScrollType;
107
119
  }
108
120
 
109
121
  /** What a recognizer is told alongside the touches that changed. */
@@ -190,6 +202,12 @@ export interface TouchConsumer {
190
202
  * undefined and keeps the browser's long-press selection.
191
203
  */
192
204
  readonly selectionLock?: SelectionLock;
205
+ /**
206
+ * Whether this touch's press holds the browser's context menu closed now.
207
+ * A control whose hold acts uses it (a switch's hold toggles it). The host
208
+ * then cancels the menu, as for a claimed touch.
209
+ */
210
+ holdsContextMenu?(touchId: number): boolean;
193
211
  /**
194
212
  * Whether the consumer takes touches now (a disabled control does not).
195
213
  * The host listens only while something enabled is attached
@@ -132,6 +132,8 @@ export function usePressTracker(
132
132
  recognizer,
133
133
  ),
134
134
  isEnabled: () => optionsRef.current.isEnabled?.() ?? true,
135
+ holdsContextMenu: id =>
136
+ optionsRef.current.holdsContextMenu?.(id) === true,
135
137
  // The caller's brake, or the host's: a touch that stops a flick
136
138
  // presses nothing, as a UIKit control in a decelerating scroll view.
137
139
  isBraking: touch => {
@@ -0,0 +1,143 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file wheelSegmenter.ts
5
+ * @input A wheel stream's deltas along one axis, with their event times
6
+ * @output Exports WheelSegmenter and isBunched. WheelSegmenter splits the
7
+ * stream into gestures and reads a trackpad's lift (the onset of momentum).
8
+ * isBunched tells one stall's merge from a frame.
9
+ * @position Read by GestureHost.ts's wheel input: the web reports no
10
+ * trackpad contact or phase, so both are inferred.
11
+ */
12
+
13
+ export type WheelBoundary = 'first' | 'idle' | 'reversal' | 'jump' | 'gap';
14
+ export type WheelPhase = 'finger' | 'momentum';
15
+
16
+ export interface WheelSegment {
17
+ /** Why this event starts a new gesture, or null when it continues one. */
18
+ readonly boundary: WheelBoundary | null;
19
+ readonly phase: WheelPhase;
20
+ /** The gesture's number, counting from 1. */
21
+ readonly gesture: number;
22
+ /** This event is where the fingers lifted (momentum begins with it). */
23
+ readonly liftNow: boolean;
24
+ /** This event shrank, so it may be momentum: hold it until known. */
25
+ readonly decaying: boolean;
26
+ /** This event came so soon after the one before that it is a merge. */
27
+ readonly bunched: boolean;
28
+ }
29
+
30
+ /** Momentum arrives at frame rate: a pause this long ends it. */
31
+ const MOMENTUM_GAP_MS = 50;
32
+ /**
33
+ * A wheel this close to the one before, ms, is one stall's merge. Frames come
34
+ * further apart at every display rate up to 500 Hz, which is 2 ms.
35
+ */
36
+ const BUNCHED_MS = 1.5;
37
+ /** Shrinking deltas in a row before the lift is read. */
38
+ const LIFT_RUN = 4;
39
+ /** And how much of its size the run has lost by then. */
40
+ const LIFT_DECAY = 0.8;
41
+
42
+ /**
43
+ * Whether a wheel `intervalMs` after the one before is one stall's merge, not
44
+ * a frame. A stalled page receives the wheels it held back together, within
45
+ * 1.5 ms of each other. The rule reads no history, so slow wheels never make
46
+ * a real frame look like a merge.
47
+ */
48
+ export function isBunched(intervalMs: number): boolean {
49
+ return intervalMs <= BUNCHED_MS;
50
+ }
51
+
52
+ /**
53
+ * A trackpad gesture has a finger phase and, after a fling, a momentum phase.
54
+ * In the finger phase, deltas come while the fingers move, with pauses when
55
+ * they move slowly. In the momentum phase, deltas come at frame rate and
56
+ * shrink smoothly. The lift is the start of a run of shrinking deltas that
57
+ * has lost a fifth of its size. In the finger phase, a pause of `idleMs`
58
+ * ends the gesture. In the momentum phase, a reversal, a jump up against the
59
+ * decay, or a pause of 50 ms starts a new gesture. A bunched wheel is never
60
+ * a jump. The decay reads it by its last step. Momentum never pauses,
61
+ * reverses or grows.
62
+ */
63
+ export class WheelSegmenter {
64
+ gesture = 0;
65
+ phase: WheelPhase = 'finger';
66
+ private last = -Infinity;
67
+ private previous = 0;
68
+ private sign = 0;
69
+ private count = 0;
70
+ private run = 0;
71
+ private runStart = 0;
72
+
73
+ push(time: number, delta: number, idleMs: number): WheelSegment {
74
+ const size = Math.abs(delta);
75
+ const sign = Math.sign(delta);
76
+ const gap = time - this.last;
77
+ const bunched = this.count > 0 && isBunched(gap);
78
+ let boundary: WheelBoundary | null = null;
79
+ if (this.count === 0) {
80
+ boundary = this.gesture === 0 ? 'first' : 'idle';
81
+ } else if (this.phase === 'finger' && gap >= idleMs) {
82
+ boundary = 'idle';
83
+ } else if (this.phase === 'momentum') {
84
+ if (sign !== 0 && this.sign !== 0 && sign !== this.sign) {
85
+ boundary = 'reversal';
86
+ } else if (gap >= MOMENTUM_GAP_MS) {
87
+ boundary = 'gap';
88
+ } else if (!bunched && size >= 2 && size > this.previous * 1.3 + 0.5) {
89
+ boundary = 'jump';
90
+ }
91
+ }
92
+ // A bunched wheel carries the steps of the frames it merged. The decay
93
+ // goes on from its last step, so the steps after it read as they are.
94
+ const steps =
95
+ boundary == null && bunched && this.previous > 0
96
+ ? Math.max(1, Math.round(size / this.previous))
97
+ : 1;
98
+ if (boundary != null) {
99
+ this.gesture += 1;
100
+ this.phase = 'finger';
101
+ this.count = 0;
102
+ this.run = 0;
103
+ }
104
+ let liftNow = false;
105
+ let decaying = false;
106
+ if (this.phase === 'finger' && this.count >= 3 && size > 0) {
107
+ // A bunched wheel is read by its last step, as the decay goes on from it.
108
+ const step = size / steps;
109
+ decaying = step <= this.previous * 0.995 && step >= this.previous * 0.5;
110
+ if (decaying) {
111
+ if (this.run === 0) {
112
+ this.runStart = this.previous;
113
+ }
114
+ this.run += 1;
115
+ } else {
116
+ this.run = 0;
117
+ }
118
+ if (this.run >= LIFT_RUN && step <= this.runStart * LIFT_DECAY) {
119
+ this.phase = 'momentum';
120
+ liftNow = true;
121
+ }
122
+ }
123
+ this.count += 1;
124
+ this.previous = size / steps;
125
+ if (sign !== 0) {
126
+ this.sign = sign;
127
+ }
128
+ this.last = time;
129
+ return {
130
+ boundary,
131
+ phase: this.phase,
132
+ gesture: this.gesture,
133
+ liftNow,
134
+ decaying: decaying && !liftNow,
135
+ bunched: boundary == null && bunched,
136
+ };
137
+ }
138
+
139
+ /** The gesture ended on its own (its timer): the next event starts one. */
140
+ end(): void {
141
+ this.count = 0;
142
+ }
143
+ }