@astryxdesign/core 0.7.7-canary.bf14580 → 0.7.7-canary.c19ca12
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/AlertDialog/AlertDialog.d.ts +4 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +36 -6
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +76 -6
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +23 -4
- package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
- package/dist/BottomSheet/bottomSheetStack.js +5 -4
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +3 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +47 -3
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +13 -2
- package/dist/ComplexSelector/ComplexSelector.d.ts +4 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +14 -4
- package/dist/ContextMenu/ContextMenu.js +2 -2
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +13 -27
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +22 -41
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +7 -31
- package/dist/Field/Field.js +2 -2
- package/dist/HoverCard/HoverCard.d.ts +4 -3
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +27 -2
- package/dist/HoverCard/useHoverCard.d.ts +5 -4
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +17 -31
- package/dist/Icon/Icon.d.ts +5 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +5 -2
- package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +25 -20
- package/dist/Layer/usePressPin.d.ts +17 -5
- package/dist/Layer/usePressPin.d.ts.map +1 -1
- package/dist/Layer/usePressPin.js +62 -12
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +18 -0
- package/dist/PowerSearch/PowerSearch.d.ts +36 -3
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +522 -269
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +100 -0
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -0
- package/dist/PowerSearch/PowerSearchBottomSheet.js +1913 -0
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts +32 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +17 -6
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts +38 -3
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +170 -61
- package/dist/PowerSearch/formatFilterValue.d.ts +7 -0
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +11 -1
- package/dist/PowerSearch/formatTokenValue.d.ts +28 -0
- package/dist/PowerSearch/formatTokenValue.d.ts.map +1 -0
- package/dist/PowerSearch/formatTokenValue.js +160 -0
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts +65 -0
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts.map +1 -0
- package/dist/PowerSearch/powerSearchSheetUtils.js +168 -0
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +3 -2
- package/dist/RadioList/RadioListItem.d.ts +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -3
- package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -1
- package/dist/SegmentedControl/useSegmentedSelectionMotion.js +3 -3
- package/dist/Slider/sliderTouchController.js +2 -2
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +60 -58
- package/dist/Switch/useSwitchTouch.js +4 -4
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +29 -8
- package/dist/TextInput/TextInput.d.ts +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +10 -7
- package/dist/Toast/Toast.d.ts +11 -1
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +25 -15
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +94 -34
- package/dist/Toast/types.d.ts +9 -3
- package/dist/Toast/types.d.ts.map +1 -1
- package/dist/Token/Token.d.ts +2 -2
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +12 -4
- package/dist/Token/TokenNamingContext.d.ts +14 -0
- package/dist/Token/TokenNamingContext.d.ts.map +1 -0
- package/dist/Token/TokenNamingContext.js +23 -0
- package/dist/Tooltip/useTooltip.d.ts +19 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +34 -53
- package/dist/Typeahead/TypeaheadItem.d.ts +2 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/animator/createAnimator.d.ts +98 -0
- package/dist/animator/createAnimator.d.ts.map +1 -0
- package/dist/animator/createAnimator.js +259 -0
- package/dist/astryx.css +22 -6
- package/dist/gestures/GestureHost.d.ts +27 -1
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +90 -8
- package/dist/gestures/PressTracker.d.ts.map +1 -1
- package/dist/gestures/PressTracker.js +4 -3
- package/dist/hooks/actionGroupTracker.d.ts +4 -1
- package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
- package/dist/hooks/actionGroupTracker.js +13 -3
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/pressGroupContext.d.ts +6 -0
- package/dist/hooks/pressGroupContext.d.ts.map +1 -0
- package/dist/hooks/pressGroupContext.js +20 -0
- package/dist/hooks/useActionGroupTracking.d.ts +53 -0
- package/dist/hooks/useActionGroupTracking.d.ts.map +1 -0
- package/dist/hooks/useActionGroupTracking.js +174 -0
- package/dist/hooks/useAnimator.d.ts +41 -0
- package/dist/hooks/useAnimator.d.ts.map +1 -0
- package/dist/hooks/useAnimator.js +70 -0
- package/dist/hooks/useAttachment.d.ts +1 -1
- package/dist/hooks/useAttachment.js +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +2 -2
- package/dist/hooks/useLongPress.d.ts +6 -2
- package/dist/hooks/useLongPress.d.ts.map +1 -1
- package/dist/hooks/useLongPress.js +95 -15
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +4 -2
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts +8 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +42 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.js +6 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts +8 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.js +14 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +30 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +30 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/internal/indicatorPress.stylex.d.ts +20 -0
- package/dist/internal/indicatorPress.stylex.d.ts.map +1 -0
- package/dist/internal/indicatorPress.stylex.js +26 -0
- package/dist/motion/animator.d.ts +14 -14
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +41 -18
- package/dist/motion/reducedMotion.d.ts +24 -0
- package/dist/motion/reducedMotion.d.ts.map +1 -1
- package/dist/motion/reducedMotion.js +78 -1
- package/dist/motion/uikit/alert.d.ts +41 -0
- package/dist/motion/uikit/alert.d.ts.map +1 -0
- package/dist/motion/uikit/alert.js +43 -0
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts +15 -0
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/pressGroupItem.stylex.js +23 -0
- package/dist/utils/clickGuard.d.ts +8 -1
- package/dist/utils/clickGuard.d.ts.map +1 -1
- package/dist/utils/clickGuard.js +24 -1
- package/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +12 -0
- package/dist/utils/focusReturn.d.ts +15 -0
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +28 -0
- package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +3 -2
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +19 -6
- package/dist/utils/triggerFocus.d.ts +36 -0
- package/dist/utils/triggerFocus.d.ts.map +1 -0
- package/dist/utils/triggerFocus.js +65 -0
- package/locales/en.json +120 -0
- package/locales/pseudo.json +90 -0
- package/package.json +9 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +2 -2
- package/src/AlertDialog/AlertDialog.tsx +37 -5
- package/src/BottomSheet/BottomSheet.tsx +85 -5
- package/src/BottomSheet/BottomSheetPanel.tsx +40 -5
- package/src/BottomSheet/bottomSheetStack.ts +8 -4
- package/src/BottomSheet/useSheetGestures.ts +3 -1
- package/src/Button/Button.tsx +63 -5
- package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
- package/src/CheckboxInput/CheckboxInput.tsx +36 -9
- package/src/ComplexSelector/ComplexSelector.tsx +20 -5
- package/src/ContextMenu/ContextMenu.tsx +2 -2
- package/src/DateInput/MonthScroller.tsx +18 -22
- package/src/DateInput/Wheel.tsx +30 -32
- package/src/Dialog/Dialog.tsx +10 -31
- package/src/Field/Field.tsx +2 -2
- package/src/HoverCard/HoverCard.doc.mjs +4 -2
- package/src/HoverCard/HoverCard.tsx +35 -4
- package/src/HoverCard/useHoverCard.doc.mjs +178 -36
- package/src/HoverCard/useHoverCard.tsx +46 -52
- package/src/Icon/Icon.doc.mjs +49 -8
- package/src/Icon/Icon.tsx +15 -7
- package/src/Layer/layerAnimations.stylex.ts +24 -16
- package/src/Layer/usePressPin.ts +81 -15
- package/src/Lightbox/Lightbox.tsx +18 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +54 -5
- package/src/PowerSearch/PowerSearch.tsx +734 -288
- package/src/PowerSearch/PowerSearchBottomSheet.tsx +2515 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +28 -6
- package/src/PowerSearch/PowerSearchValueEditor.tsx +203 -38
- package/src/PowerSearch/formatFilterValue.ts +18 -1
- package/src/PowerSearch/formatTokenValue.ts +215 -0
- package/src/PowerSearch/powerSearchSheetUtils.ts +224 -0
- package/src/RadioList/RadioList.tsx +3 -2
- package/src/RadioList/RadioListItem.tsx +5 -3
- package/src/SegmentedControl/useSegmentedSelectionMotion.ts +6 -3
- package/src/Slider/sliderTouchController.ts +2 -2
- package/src/Spinner/Spinner.tsx +77 -63
- package/src/Switch/useSwitchTouch.ts +4 -4
- package/src/Table/BaseTable.tsx +46 -12
- package/src/TextInput/TextInput.tsx +11 -10
- package/src/Toast/Toast.doc.mjs +4 -3
- package/src/Toast/Toast.tsx +52 -32
- package/src/Toast/ToastViewport.tsx +121 -35
- package/src/Toast/types.ts +9 -3
- package/src/Token/Token.tsx +13 -4
- package/src/Token/TokenNamingContext.ts +28 -0
- package/src/Tooltip/useTooltip.doc.mjs +24 -0
- package/src/Tooltip/useTooltip.tsx +61 -56
- package/src/Typeahead/TypeaheadItem.doc.mjs +6 -3
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/animator/createAnimator.doc.mjs +153 -0
- package/src/animator/createAnimator.ts +324 -0
- package/src/gestures/GestureHost.ts +105 -10
- package/src/gestures/PressTracker.ts +4 -3
- package/src/gestures/README.md +7 -1
- package/src/hooks/actionGroupTracker.ts +20 -5
- package/src/hooks/index.ts +2 -0
- package/src/hooks/pressGroupContext.ts +21 -0
- package/src/hooks/useActionGroupTracking.ts +224 -0
- package/src/hooks/useAnimator.doc.mjs +109 -0
- package/src/hooks/useAnimator.ts +67 -0
- package/src/hooks/useAttachment.ts +1 -1
- package/src/hooks/useInputStatusIcon.tsx +3 -2
- package/src/hooks/useLongPress.doc.mjs +2 -2
- package/src/hooks/useLongPress.ts +136 -15
- package/src/hooks/useMenuPress.ts +8 -2
- package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +43 -0
- package/src/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.ts +6 -0
- package/src/i18n/generated-english/PowerSearch/formatTokenValue.generated.ts +15 -0
- package/src/i18n/generated-locales/en.generated.ts +41 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +42 -0
- package/src/index.ts +13 -0
- package/src/internal/indicatorPress.stylex.ts +24 -0
- package/src/motion/README.md +8 -5
- package/src/motion/animator.ts +61 -29
- package/src/motion/reducedMotion.ts +79 -1
- package/src/motion/uikit/alert.ts +43 -0
- package/src/privateAttributes/pressGroupItem.stylex.ts +23 -0
- package/src/utils/clickGuard.ts +37 -1
- package/src/utils/dateParser.ts +16 -0
- package/src/utils/focusReturn.ts +30 -0
- package/src/utils/interactionOverlay.stylex.ts +3 -2
- package/src/utils/pressFeedback.ts +19 -6
- package/src/utils/triggerFocus.ts +80 -0
|
@@ -731,6 +731,12 @@ export class GestureHost {
|
|
|
731
731
|
* lift click (`spec:AST-094` FR28: nothing acts).
|
|
732
732
|
*/
|
|
733
733
|
private readonly cancelledFingers = new Set<number>();
|
|
734
|
+
/**
|
|
735
|
+
* The pens and pointer-read fingers that the app's lost focus cancelled,
|
|
736
|
+
* by pointer id. Each is kept until its lift, which must not click either,
|
|
737
|
+
* or until a new press.
|
|
738
|
+
*/
|
|
739
|
+
private readonly cancelledPointers = new Map<number, PointerKind>();
|
|
734
740
|
/** Touch.identifier → the element the finger landed on. */
|
|
735
741
|
private readonly touchTargets = new Map<number, EventTarget>();
|
|
736
742
|
/**
|
|
@@ -755,6 +761,8 @@ export class GestureHost {
|
|
|
755
761
|
private readonly processed = new WeakSet<Event>();
|
|
756
762
|
/** pointerId → arena id, for live mouse and pen contacts. */
|
|
757
763
|
private readonly pointerIds = new Map<number, number>();
|
|
764
|
+
/** Each contact's kind: a blur refuses a pen's or a pointer-read finger's lift, never a mouse's. */
|
|
765
|
+
private readonly pointerKinds = new Map<number, PointerKind>();
|
|
758
766
|
/**
|
|
759
767
|
* The boxes the host watches for screen-edge pans (syncLayoutWatch): each
|
|
760
768
|
* element with an enabled one, and the root for one whose band moves with
|
|
@@ -1820,13 +1828,36 @@ export class GestureHost {
|
|
|
1820
1828
|
}
|
|
1821
1829
|
}
|
|
1822
1830
|
|
|
1823
|
-
/**
|
|
1831
|
+
/**
|
|
1832
|
+
* A new press is a new gesture, as the click guard reads one, so the lifts
|
|
1833
|
+
* the blur cancelled owe nothing after it. A second finger keeps the other
|
|
1834
|
+
* fingers' refusals, since those fingers may still be down.
|
|
1835
|
+
*/
|
|
1836
|
+
private dropCancelledLifts(event: PointerEvent): void {
|
|
1837
|
+
const isSecond = event.pointerType === 'touch' && event.isPrimary === false;
|
|
1838
|
+
for (const [pointerId, kind] of [...this.cancelledPointers]) {
|
|
1839
|
+
if (pointerId === event.pointerId || !(isSecond && kind === 'touch')) {
|
|
1840
|
+
this.cancelledPointers.delete(pointerId);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
/**
|
|
1846
|
+
* After a contact ends: an armed press that is over stops listening. A
|
|
1847
|
+
* contact the lost focus cancelled still owes its lift, so the press is
|
|
1848
|
+
* not over until that lift comes. A lift the page never hears ends it at
|
|
1849
|
+
* the next press, or at a cancel.
|
|
1850
|
+
*/
|
|
1824
1851
|
private releaseArmedPress(): void {
|
|
1825
1852
|
if (!this.isArmedPress) {
|
|
1826
1853
|
return;
|
|
1827
1854
|
}
|
|
1828
1855
|
queueMicrotask(() => {
|
|
1829
|
-
if (
|
|
1856
|
+
if (
|
|
1857
|
+
this.isArmedPress &&
|
|
1858
|
+
this.arena.touches.length === 0 &&
|
|
1859
|
+
this.cancelledPointers.size === 0
|
|
1860
|
+
) {
|
|
1830
1861
|
this.isArmedPress = false;
|
|
1831
1862
|
this.syncKeptPresses();
|
|
1832
1863
|
this.syncListeners();
|
|
@@ -1857,6 +1888,7 @@ export class GestureHost {
|
|
|
1857
1888
|
return;
|
|
1858
1889
|
}
|
|
1859
1890
|
this.cancelledFingers.clear();
|
|
1891
|
+
this.cancelledPointers.clear();
|
|
1860
1892
|
this.clearClaimedLifts();
|
|
1861
1893
|
this.cancelCallerRead?.();
|
|
1862
1894
|
this.cancelCallerRead = null;
|
|
@@ -2934,13 +2966,19 @@ export class GestureHost {
|
|
|
2934
2966
|
if (locksSelectionFor('touch')) {
|
|
2935
2967
|
toLock.push({id, target, consumerElement});
|
|
2936
2968
|
}
|
|
2937
|
-
// The finger's events keep going to what the engine hit.
|
|
2969
|
+
// The finger's events keep going to what the engine hit. A disabled
|
|
2970
|
+
// participant can never claim, so a scroll never waits on it.
|
|
2938
2971
|
this.watchTarget(
|
|
2939
2972
|
touch.identifier,
|
|
2940
2973
|
native,
|
|
2941
|
-
recognizers.some(
|
|
2942
|
-
|
|
2943
|
-
|
|
2974
|
+
recognizers.some(
|
|
2975
|
+
recognizer => recognizer.isEnabled && claimsWhileMoving(recognizer),
|
|
2976
|
+
) ||
|
|
2977
|
+
(consumer?.keepsTouch != null && consumer.isEnabled?.() !== false),
|
|
2978
|
+
(consumer != null && consumer.isEnabled?.() !== false) ||
|
|
2979
|
+
recognizers.some(
|
|
2980
|
+
recognizer => recognizer.isEnabled && recognizer.claimsTouches,
|
|
2981
|
+
),
|
|
2944
2982
|
);
|
|
2945
2983
|
this.lastFed.set(id, {x: touch.clientX, y: touch.clientY, time});
|
|
2946
2984
|
this.pairTouch(id, {x: touch.clientX, y: touch.clientY, target: native});
|
|
@@ -2975,6 +3013,11 @@ export class GestureHost {
|
|
|
2975
3013
|
for (const input of untracked) {
|
|
2976
3014
|
this.arena.untrackedBegan(input);
|
|
2977
3015
|
}
|
|
3016
|
+
// Again once the new fingers are delivered: a pinch's second finger may
|
|
3017
|
+
// have landed on another one-finger pan, the next row.
|
|
3018
|
+
if (this.fingerTargets.length > 1 && !this.isFingerClaimed()) {
|
|
3019
|
+
this.failUnclaimedLoneFingerPans();
|
|
3020
|
+
}
|
|
2978
3021
|
// Stamped after the consumers judged the touch: the brake asks whether
|
|
2979
3022
|
// an EARLIER finger is still driving a scroll.
|
|
2980
3023
|
const landed = landedOn(event);
|
|
@@ -3570,6 +3613,7 @@ export class GestureHost {
|
|
|
3570
3613
|
if (!isPointerKind(event.pointerType)) {
|
|
3571
3614
|
return;
|
|
3572
3615
|
}
|
|
3616
|
+
this.dropCancelledLifts(event);
|
|
3573
3617
|
if (event.pointerType === 'touch' && this.usesTouchEvents) {
|
|
3574
3618
|
this.pairPointer(event.pointerId, {
|
|
3575
3619
|
x: event.clientX,
|
|
@@ -3578,6 +3622,13 @@ export class GestureHost {
|
|
|
3578
3622
|
});
|
|
3579
3623
|
return;
|
|
3580
3624
|
}
|
|
3625
|
+
if (event.pointerType === 'touch' && event.isPrimary === false) {
|
|
3626
|
+
// Fingers from pointer events: a second one, wherever either lands,
|
|
3627
|
+
// leaves the two to the page's pinch, as onTouchStart does. A finger
|
|
3628
|
+
// that is not the primary pointer is a second finger, even where the
|
|
3629
|
+
// host never heard the first.
|
|
3630
|
+
this.failUnclaimedLoneFingerPans();
|
|
3631
|
+
}
|
|
3581
3632
|
// A right click, a pen's barrel button, or a ctrl-click on macOS (its
|
|
3582
3633
|
// context click) starts no press (FR43).
|
|
3583
3634
|
if (event.pointerType !== 'touch' && !isPrimaryClick(event)) {
|
|
@@ -3599,6 +3650,7 @@ export class GestureHost {
|
|
|
3599
3650
|
}
|
|
3600
3651
|
const id = this.nextId++;
|
|
3601
3652
|
this.pointerIds.set(event.pointerId, id);
|
|
3653
|
+
this.pointerKinds.set(event.pointerId, event.pointerType);
|
|
3602
3654
|
if (target !== landed && target != null) {
|
|
3603
3655
|
this.retargeted.set(id, {x: event.clientX, y: event.clientY, target});
|
|
3604
3656
|
}
|
|
@@ -3620,6 +3672,15 @@ export class GestureHost {
|
|
|
3620
3672
|
consumer,
|
|
3621
3673
|
},
|
|
3622
3674
|
]);
|
|
3675
|
+
if (
|
|
3676
|
+
event.pointerType === 'touch' &&
|
|
3677
|
+
event.isPrimary === false &&
|
|
3678
|
+
!this.isFingerClaimed()
|
|
3679
|
+
) {
|
|
3680
|
+
// Again once this finger is delivered: it may have landed on another
|
|
3681
|
+
// one-finger pan.
|
|
3682
|
+
this.failUnclaimedLoneFingerPans();
|
|
3683
|
+
}
|
|
3623
3684
|
if (locksSelectionFor(event.pointerType)) {
|
|
3624
3685
|
// The presented target: what the pen is on, where the engine's own
|
|
3625
3686
|
// hit test may still see the motion's start.
|
|
@@ -3678,9 +3739,14 @@ export class GestureHost {
|
|
|
3678
3739
|
}
|
|
3679
3740
|
const id = this.pointerIds.get(event.pointerId);
|
|
3680
3741
|
if (id == null) {
|
|
3742
|
+
// A contact the lost focus cancelled: its lift clicks nothing.
|
|
3743
|
+
if (this.cancelledPointers.delete(event.pointerId)) {
|
|
3744
|
+
this.refuseCancelledLift(event);
|
|
3745
|
+
}
|
|
3681
3746
|
return;
|
|
3682
3747
|
}
|
|
3683
3748
|
this.pointerIds.delete(event.pointerId);
|
|
3749
|
+
this.pointerKinds.delete(event.pointerId);
|
|
3684
3750
|
this.unlockSelection(id);
|
|
3685
3751
|
const dispositions = this.arena.ended([
|
|
3686
3752
|
{id, x: event.clientX, y: event.clientY, time: eventTime(event)},
|
|
@@ -3784,9 +3850,12 @@ export class GestureHost {
|
|
|
3784
3850
|
}
|
|
3785
3851
|
return;
|
|
3786
3852
|
}
|
|
3853
|
+
// A contact that ends in a cancel owes no refused lift.
|
|
3854
|
+
this.cancelledPointers.delete(event.pointerId);
|
|
3787
3855
|
const id = this.pointerIds.get(event.pointerId);
|
|
3788
3856
|
if (id != null) {
|
|
3789
3857
|
this.pointerIds.delete(event.pointerId);
|
|
3858
|
+
this.pointerKinds.delete(event.pointerId);
|
|
3790
3859
|
this.unlockSelection(id, true);
|
|
3791
3860
|
this.retargeted.delete(id);
|
|
3792
3861
|
this.arena.cancelled([id], eventTime(event));
|
|
@@ -3991,20 +4060,26 @@ export class GestureHost {
|
|
|
3991
4060
|
}
|
|
3992
4061
|
}
|
|
3993
4062
|
}
|
|
4063
|
+
/** Whether a finger's drag is claimed: then two fingers make no pinch. */
|
|
4064
|
+
private isFingerClaimed(): boolean {
|
|
4065
|
+
return this.arena.touches.some(
|
|
4066
|
+
touch => touch.pointerType === 'touch' && this.arena.isClaimed(touch.id),
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
3994
4069
|
|
|
3995
4070
|
private readonly onVisibilityChange = (): void => {
|
|
3996
4071
|
if (this.doc.visibilityState === 'hidden') {
|
|
3997
4072
|
this.cancelEverything(this.arena.now());
|
|
3998
|
-
// As at a blur:
|
|
3999
|
-
//
|
|
4073
|
+
// As at a blur: an armed press's page listeners go once nothing is left
|
|
4074
|
+
// to hear.
|
|
4000
4075
|
this.releaseArmedPress();
|
|
4001
4076
|
}
|
|
4002
4077
|
};
|
|
4003
4078
|
|
|
4004
4079
|
private readonly onBlur = (): void => {
|
|
4005
4080
|
this.cancelEverything(this.arena.now());
|
|
4006
|
-
//
|
|
4007
|
-
//
|
|
4081
|
+
// An armed press's page listeners go once nothing is left to hear. A pen
|
|
4082
|
+
// or a pointer-read finger the blur cancelled still owes its lift.
|
|
4008
4083
|
this.releaseArmedPress();
|
|
4009
4084
|
};
|
|
4010
4085
|
|
|
@@ -4027,8 +4102,27 @@ export class GestureHost {
|
|
|
4027
4102
|
);
|
|
4028
4103
|
}
|
|
4029
4104
|
|
|
4105
|
+
/**
|
|
4106
|
+
* The lift of a contact the lost focus cancelled: its click is refused,
|
|
4107
|
+
* and a finger's touchend after it is cancelled.
|
|
4108
|
+
*/
|
|
4109
|
+
private refuseCancelledLift(event: PointerEvent): void {
|
|
4110
|
+
armClickGuard(this.doc, {takesLateMouse: event.pointerType === 'touch'});
|
|
4111
|
+
if (event.pointerType === 'touch') {
|
|
4112
|
+
// Noted as a claimed lift is: capped, and gone after its time.
|
|
4113
|
+
this.noteClaimedLift(event);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4030
4117
|
/** The app lost focus: UIKit cancels every touch in flight. */
|
|
4031
4118
|
private cancelEverything(time: number): void {
|
|
4119
|
+
// The browser may keep a pen or a pointer-read finger down: its lift must
|
|
4120
|
+
// not click. A mouse button held through the blur keeps its click.
|
|
4121
|
+
for (const [pointerId, kind] of this.pointerKinds) {
|
|
4122
|
+
if (kind !== 'mouse') {
|
|
4123
|
+
this.cancelledPointers.set(pointerId, kind);
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4032
4126
|
const ids = [...this.pointerIds.values()];
|
|
4033
4127
|
for (const id of ids) {
|
|
4034
4128
|
this.unlockSelection(id, true);
|
|
@@ -4042,6 +4136,7 @@ export class GestureHost {
|
|
|
4042
4136
|
}
|
|
4043
4137
|
}
|
|
4044
4138
|
this.pointerIds.clear();
|
|
4139
|
+
this.pointerKinds.clear();
|
|
4045
4140
|
this.retargeted.clear();
|
|
4046
4141
|
this.fingerPointers.clear();
|
|
4047
4142
|
this.pendingPointers.clear();
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import {UIKIT_CONTROL, UIKIT_PAN, UIKIT_PRESS} from '../motion/uikit/gestures';
|
|
50
|
-
|
|
50
|
+
// Importing the whole table bundles its curves into each press-tracking control.
|
|
51
|
+
import {press as pressClocks} from '../motion/uikit/timing';
|
|
51
52
|
import {travel} from './geometry';
|
|
52
53
|
import type {GestureRecognizer, SelectionLock} from './GestureRecognizer';
|
|
53
54
|
import type {TapGestureRecognizer} from './TapGestureRecognizer';
|
|
@@ -154,7 +155,7 @@ export const PRESS_PROFILES = {
|
|
|
154
155
|
// The shared press styles release every surface on the deselect clock
|
|
155
156
|
// until a control runs its own release (UIButton's 250 ms), so a
|
|
156
157
|
// painter's `fading` never outlasts the fade it shows.
|
|
157
|
-
releaseMs:
|
|
158
|
+
releaseMs: pressClocks.highlightOutMs,
|
|
158
159
|
},
|
|
159
160
|
/**
|
|
160
161
|
* A table or collection cell: inside a scroll view it highlights after the
|
|
@@ -165,7 +166,7 @@ export const PRESS_PROFILES = {
|
|
|
165
166
|
highlightDelay: 'inScrollers',
|
|
166
167
|
cancelTravel: UIKIT_PAN.hysteresis.value,
|
|
167
168
|
quickTapFlash: 0,
|
|
168
|
-
releaseMs:
|
|
169
|
+
releaseMs: pressClocks.highlightOutMs,
|
|
169
170
|
},
|
|
170
171
|
} as const satisfies Record<string, PressProfile>;
|
|
171
172
|
|
package/src/gestures/README.md
CHANGED
|
@@ -96,6 +96,10 @@ Once a drag has claimed its finger, a second finger never breaks it.
|
|
|
96
96
|
| `PinchGestureRecognizer` | `UIPinchGestureRecognizer` | continuous | `scale` (settable, to re-base it), `velocity` in scale per second, `focalPoint()` |
|
|
97
97
|
| `ScreenEdgePanGestureRecognizer` | `UIScreenEdgePanGestureRecognizer` | continuous | `edges`: `leading` / `trailing`, resolved against the view's writing direction; attached to an edge zone the view lies within; fails as soon as the touch lands outside every requested edge band; a finger's only |
|
|
98
98
|
|
|
99
|
+
Each recognizer reports what it is in `kind` (`'tap'`, `'longPress'`,
|
|
100
|
+
`'pan'`, `'screenEdgePan'`, `'swipe'`, `'pinch'`): code tells the family apart by it, never by `instanceof`
|
|
101
|
+
(`architecture:react-component-runtime` INV13).
|
|
102
|
+
|
|
99
103
|
A recognizer created with `claimsTouches: false` is a watcher (a tab bar
|
|
100
104
|
that minimizes while the page scrolls): it never claims, so its element gets
|
|
101
105
|
only passive listeners and a scroll never waits for it; it prevents no other
|
|
@@ -174,7 +178,9 @@ innerPan.requireToFail(edgePan);
|
|
|
174
178
|
## Touches in the view: `createPressTracker`
|
|
175
179
|
|
|
176
180
|
A pressable control's view-level touch handling, with `UIControl`
|
|
177
|
-
semantics
|
|
181
|
+
semantics, built by `createPressTracker(options)`: a factory, as
|
|
182
|
+
`architecture:react-component-runtime` INV13 asks of the touch modules outside
|
|
183
|
+
the recognizer family (`VelocityTracker` is still a class, its one listed gap):
|
|
178
184
|
|
|
179
185
|
- `touchDown`, `touchDragInside` / `touchDragOutside`, `touchDragEnter` /
|
|
180
186
|
`touchDragExit`, `touchUpInside` / `touchUpOutside`, `touchCancel`.
|
|
@@ -57,7 +57,8 @@ export interface ActionGroupTrackerOptions {
|
|
|
57
57
|
* target past the group's ends commits the lift there wherever the finger
|
|
58
58
|
* lifts.
|
|
59
59
|
* @default the target whose rect, grown by `highlightExpansion`, holds the
|
|
60
|
-
* point; none between or
|
|
60
|
+
* point, the one the press landed on where several do; none between or
|
|
61
|
+
* outside them
|
|
61
62
|
*/
|
|
62
63
|
resolveTarget?: (
|
|
63
64
|
x: number,
|
|
@@ -83,8 +84,13 @@ export interface ActionGroupTracker {
|
|
|
83
84
|
/**
|
|
84
85
|
* A press began at viewport (x, y): read the actions' rects once and
|
|
85
86
|
* highlight the one under it. An inert press is taken but tracks nothing.
|
|
87
|
+
* `landed` is the node the press landed on.
|
|
86
88
|
*/
|
|
87
|
-
begin(
|
|
89
|
+
begin(
|
|
90
|
+
x: number,
|
|
91
|
+
y: number,
|
|
92
|
+
options?: {inert?: boolean; landed?: Node | null},
|
|
93
|
+
): void;
|
|
88
94
|
/** The finger moved to viewport (x, y). */
|
|
89
95
|
move(x: number, y: number): void;
|
|
90
96
|
/**
|
|
@@ -150,6 +156,7 @@ export function createActionGroupTracker(
|
|
|
150
156
|
let actions: TrackedAction[] = [];
|
|
151
157
|
let highlighted: HTMLElement | null = null;
|
|
152
158
|
let beganOn: HTMLElement | null = null;
|
|
159
|
+
let landedOn: Node | null = null;
|
|
153
160
|
let isTracking = false;
|
|
154
161
|
let isInert = false;
|
|
155
162
|
// The finger's last position: `remeasure` resolves it again.
|
|
@@ -241,6 +248,9 @@ export function createActionGroupTracker(
|
|
|
241
248
|
if (resolveTarget != null) {
|
|
242
249
|
return actions.length === 0 ? null : resolveTarget(x, y, actions);
|
|
243
250
|
}
|
|
251
|
+
// Rects that share an edge, or that have no layout (a test renderer), can
|
|
252
|
+
// both hold the point: the one the press landed on wins.
|
|
253
|
+
let first: HTMLElement | null = null;
|
|
244
254
|
for (const action of actions) {
|
|
245
255
|
if (
|
|
246
256
|
x >= action.left - margin &&
|
|
@@ -248,22 +258,26 @@ export function createActionGroupTracker(
|
|
|
248
258
|
y >= action.top - margin &&
|
|
249
259
|
y <= action.bottom + margin
|
|
250
260
|
) {
|
|
251
|
-
|
|
261
|
+
if (landedOn != null && action.element.contains(landedOn)) {
|
|
262
|
+
return action.element;
|
|
263
|
+
}
|
|
264
|
+
first ??= action.element;
|
|
252
265
|
}
|
|
253
266
|
}
|
|
254
|
-
return
|
|
267
|
+
return first;
|
|
255
268
|
};
|
|
256
269
|
|
|
257
270
|
const reset = () => {
|
|
258
271
|
actions = [];
|
|
259
272
|
highlighted = null;
|
|
260
273
|
beganOn = null;
|
|
274
|
+
landedOn = null;
|
|
261
275
|
isTracking = false;
|
|
262
276
|
isInert = false;
|
|
263
277
|
};
|
|
264
278
|
|
|
265
279
|
return {
|
|
266
|
-
begin(x, y, {inert = false} = {}) {
|
|
280
|
+
begin(x, y, {inert = false, landed = null} = {}) {
|
|
267
281
|
// A press can begin while another is tracked, when a second recognizer
|
|
268
282
|
// takes over the finger (a trigger's held press, then the menu's): the
|
|
269
283
|
// action the first one lit loses its paint.
|
|
@@ -276,6 +290,7 @@ export function createActionGroupTracker(
|
|
|
276
290
|
highlighted = null;
|
|
277
291
|
isTracking = true;
|
|
278
292
|
isInert = inert;
|
|
293
|
+
landedOn = landed;
|
|
279
294
|
lastX = x;
|
|
280
295
|
lastY = y;
|
|
281
296
|
getOptions().onMove?.(x, y);
|
package/src/hooks/index.ts
CHANGED
|
@@ -133,6 +133,8 @@ export type {
|
|
|
133
133
|
UseInteractiveRoleOptions,
|
|
134
134
|
} from './useInteractiveRole';
|
|
135
135
|
|
|
136
|
+
export {useAnimator} from './useAnimator';
|
|
137
|
+
|
|
136
138
|
export {useLongPress} from './useLongPress';
|
|
137
139
|
export type {UseLongPressOptions, UseLongPressHandlers} from './useLongPress';
|
|
138
140
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file pressGroupContext.ts
|
|
7
|
+
* @input Nothing
|
|
8
|
+
* @output PressGroupContext: true inside a group whose own tracker paints
|
|
9
|
+
* its actions' press
|
|
10
|
+
* @position Internal; not exported from the hooks entry. AlertDialog
|
|
11
|
+
* provides it around its tracked actions, and Button reads it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {createContext} from 'react';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* True inside a group whose tracker paints a pointer's press on its actions.
|
|
18
|
+
* Those actions take no `:active` paint, so one press never lights two.
|
|
19
|
+
*/
|
|
20
|
+
export const PressGroupContext = createContext(false);
|
|
21
|
+
PressGroupContext.displayName = 'PressGroupContext';
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useActionGroupTracking.ts
|
|
7
|
+
* @input A ref to a group of sibling actions, the actions' selector, and the
|
|
8
|
+
* component's tracking constants
|
|
9
|
+
* @output Exports useActionGroupTracking and ActionGroupTrackingOptions
|
|
10
|
+
* @position Internal hook, not exported from the hooks entry. A long press
|
|
11
|
+
* drives the shared action-group tracker (hooks/actionGroupTracker.ts) for
|
|
12
|
+
* a group that a held pointer slides across. AlertDialog consumes it.
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When modified, update useActionGroupTracking.test.tsx
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {useEffect, useRef, useState, type RefObject} from 'react';
|
|
18
|
+
import {isActive} from '../gestures/GestureRecognizer';
|
|
19
|
+
import {LongPressGestureRecognizer} from '../gestures/LongPressGestureRecognizer';
|
|
20
|
+
import {lockSelection} from '../utils/selectionLock';
|
|
21
|
+
import type {GestureTouch, PointerKind} from '../gestures/types';
|
|
22
|
+
import {useGestureRecognizer} from '../gestures/useGestureRecognizer';
|
|
23
|
+
import {UIKIT_PRESS} from '../motion/uikit/gestures';
|
|
24
|
+
import {isIosWebKit} from '../utils/touchQueries';
|
|
25
|
+
import {createActionGroupTracker} from './actionGroupTracker';
|
|
26
|
+
import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
|
|
27
|
+
|
|
28
|
+
export interface ActionGroupTrackingOptions {
|
|
29
|
+
/** Selects the actions inside the tracked element. */
|
|
30
|
+
itemSelector: string;
|
|
31
|
+
/** How long after touch-down the touched action highlights, ms. */
|
|
32
|
+
highlightOnsetMs: number;
|
|
33
|
+
/**
|
|
34
|
+
* How far outside an action a finger keeps it highlighted, px. A mouse or a
|
|
35
|
+
* pen hit-tests the actions' exact rects.
|
|
36
|
+
*/
|
|
37
|
+
highlightExpansion: number;
|
|
38
|
+
/** Pointers that are tracked; others keep the browser's click. @default every pointer */
|
|
39
|
+
pointerTypes?: ReadonlyArray<PointerKind>;
|
|
40
|
+
/** A press whose target this rejects is not tracked. */
|
|
41
|
+
shouldTrack?: (target: unknown) => boolean;
|
|
42
|
+
/** Off, nothing is attached and nothing is tracked. @default true */
|
|
43
|
+
isEnabled?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Let the gesture host write the group's `touch-action` and refuse a text
|
|
46
|
+
* selection while a finger or a pen holds it.
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
manageStyle?: boolean;
|
|
50
|
+
/** Runs the action under the finger at the lift. @default its own click */
|
|
51
|
+
onActivate?: (item: HTMLElement) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const PRESS = UIKIT_PRESS;
|
|
55
|
+
|
|
56
|
+
/** The ref handed to the recognizer while tracking is off: no element. */
|
|
57
|
+
const DETACHED: RefObject<Element | null> = {current: null};
|
|
58
|
+
|
|
59
|
+
const EVERY_POINTER: ReadonlyArray<PointerKind> = ['touch', 'pen', 'mouse'];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The group's press: a long press that a second finger on the group ends with
|
|
63
|
+
* nothing acting. A plain long press ignores a second finger.
|
|
64
|
+
*/
|
|
65
|
+
class ActionGroupPress extends LongPressGestureRecognizer {
|
|
66
|
+
protected override acceptsTouch(touch: GestureTouch): boolean {
|
|
67
|
+
if (isActive(this.state)) {
|
|
68
|
+
this.setState('cancelled');
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return super.acceptsTouch(touch);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Track a held pointer on the group behind `ref` across the actions inside
|
|
77
|
+
* it, through the shared action-group tracker.
|
|
78
|
+
*/
|
|
79
|
+
export function useActionGroupTracking(
|
|
80
|
+
ref: RefObject<HTMLElement | null>,
|
|
81
|
+
options: ActionGroupTrackingOptions,
|
|
82
|
+
): ActionGroupPress {
|
|
83
|
+
const optionsRef = useRef(options);
|
|
84
|
+
// Keeps the recognizer's delegate and the tracker, which run outside
|
|
85
|
+
// render, reading the latest options.
|
|
86
|
+
useIsomorphicLayoutEffect(() => {
|
|
87
|
+
optionsRef.current = options;
|
|
88
|
+
});
|
|
89
|
+
// The pointer that started the current press. Only a finger gets the hit
|
|
90
|
+
// expansion; a mouse or a pen hit-tests the actions' exact rects.
|
|
91
|
+
const pointerRef = useRef<PointerKind>('touch');
|
|
92
|
+
const landedRef = useRef<Node | null>(null);
|
|
93
|
+
const [tracker] = useState(() =>
|
|
94
|
+
createActionGroupTracker(() => ({
|
|
95
|
+
roots: () => [ref.current],
|
|
96
|
+
itemSelector: optionsRef.current.itemSelector,
|
|
97
|
+
highlightExpansion:
|
|
98
|
+
pointerRef.current === 'touch'
|
|
99
|
+
? optionsRef.current.highlightExpansion
|
|
100
|
+
: 0,
|
|
101
|
+
onActivate: optionsRef.current.onActivate,
|
|
102
|
+
// The browser's own click for a press the group ran is taken, so the
|
|
103
|
+
// action runs once. The gesture host swallows it for a press it claims.
|
|
104
|
+
// This covers one it does not claim, a mouse under a coarse pointer.
|
|
105
|
+
swallowsNativeClick: true,
|
|
106
|
+
})),
|
|
107
|
+
);
|
|
108
|
+
const isEnabled = options.isEnabled ?? true;
|
|
109
|
+
// Synchronizes the tracker with tracking on and mounted. Turning tracking
|
|
110
|
+
// off, or unmounting, drops a tracked press and ends its release fades.
|
|
111
|
+
// The host reports no cancellation to a disabled recognizer, and the lift
|
|
112
|
+
// that runs an action often closes the owner before its fade ends.
|
|
113
|
+
useEffect(
|
|
114
|
+
() => () => {
|
|
115
|
+
tracker.cancel();
|
|
116
|
+
tracker.dispose();
|
|
117
|
+
},
|
|
118
|
+
[tracker, isEnabled],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return useGestureRecognizer(
|
|
122
|
+
isEnabled ? ref : DETACHED,
|
|
123
|
+
() =>
|
|
124
|
+
new ActionGroupPress({
|
|
125
|
+
name: 'ActionGroupTracking',
|
|
126
|
+
minimumPressDuration: options.highlightOnsetMs,
|
|
127
|
+
allowableMovement: Number.POSITIVE_INFINITY,
|
|
128
|
+
allowedPointerTypes: options.pointerTypes ?? EVERY_POINTER,
|
|
129
|
+
delaysTouchesBegan: true,
|
|
130
|
+
// The hold picks an action, so a held finger or pen starts no text
|
|
131
|
+
// selection and opens no callout while down. A long press brings this
|
|
132
|
+
// lock by default; this hold has no minimum duration, so it names it.
|
|
133
|
+
selectionLock: lockSelection,
|
|
134
|
+
shouldReceiveTouch: (recognizer, touch) => {
|
|
135
|
+
if (recognizer.numberOfTouches === 0) {
|
|
136
|
+
pointerRef.current = touch.pointerType;
|
|
137
|
+
const target = touch.target as Node | null;
|
|
138
|
+
landedRef.current =
|
|
139
|
+
typeof target?.nodeType === 'number' ? target : null;
|
|
140
|
+
}
|
|
141
|
+
return receiveTouch(recognizer, touch, optionsRef.current);
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
recognizer => {
|
|
145
|
+
switch (recognizer.state) {
|
|
146
|
+
case 'began': {
|
|
147
|
+
const {x, y} = recognizer.location();
|
|
148
|
+
tracker.begin(x, y, {landed: landedRef.current});
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'changed': {
|
|
152
|
+
const {x, y} = recognizer.location();
|
|
153
|
+
tracker.move(x, y);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'ended': {
|
|
157
|
+
const doc = ref.current?.ownerDocument;
|
|
158
|
+
const focusedBefore = doc?.activeElement ?? null;
|
|
159
|
+
const beganOn = tracker.beganOn;
|
|
160
|
+
// Whether a click on the lifted action would focus it. A mouse's or
|
|
161
|
+
// a pen's pointerdown already gave the engine's answer, on the action
|
|
162
|
+
// it began on. A tap focuses on a later mousedown, which the claimed
|
|
163
|
+
// touch cancels, so the platform answers for a finger.
|
|
164
|
+
// iOS WebKit focuses no tapped button; Chromium and Firefox do.
|
|
165
|
+
const clickFocuses =
|
|
166
|
+
pointerRef.current === 'touch'
|
|
167
|
+
? !isIosWebKit(doc?.defaultView)
|
|
168
|
+
: beganOn != null && focusedBefore === beganOn;
|
|
169
|
+
const lifted = tracker.end();
|
|
170
|
+
// The lift runs the action's own click(), which moves no focus. So
|
|
171
|
+
// focus moves to the lifted action, unless its click moved focus.
|
|
172
|
+
if (
|
|
173
|
+
clickFocuses &&
|
|
174
|
+
lifted != null &&
|
|
175
|
+
lifted.isConnected &&
|
|
176
|
+
doc?.activeElement === focusedBefore &&
|
|
177
|
+
focusedBefore !== lifted
|
|
178
|
+
) {
|
|
179
|
+
lifted.focus({preventScroll: true, focusVisible: false});
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'cancelled':
|
|
184
|
+
tracker.cancel();
|
|
185
|
+
break;
|
|
186
|
+
case 'possible':
|
|
187
|
+
case 'failed':
|
|
188
|
+
// Listeners are not called for these.
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
isEnabled,
|
|
194
|
+
manageStyle: options.manageStyle ?? true,
|
|
195
|
+
actionGroup: true,
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The recognizer's should-receive delegate. It also picks the press's clock.
|
|
202
|
+
* Inside a scroller the press waits the content-touch delay and fails past
|
|
203
|
+
* the slop, so the scroller takes a flick. Elsewhere it begins at once, with
|
|
204
|
+
* no movement limit.
|
|
205
|
+
*/
|
|
206
|
+
function receiveTouch(
|
|
207
|
+
recognizer: LongPressGestureRecognizer,
|
|
208
|
+
touch: GestureTouch,
|
|
209
|
+
options: ActionGroupTrackingOptions,
|
|
210
|
+
): boolean {
|
|
211
|
+
if (options.shouldTrack?.(touch.target) === false) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
if (recognizer.numberOfTouches === 0) {
|
|
215
|
+
const isInsideScroller = touch.scroll?.isInsideScroller === true;
|
|
216
|
+
recognizer.minimumPressDuration = isInsideScroller
|
|
217
|
+
? PRESS.contentTouchDelay.value
|
|
218
|
+
: options.highlightOnsetMs;
|
|
219
|
+
recognizer.allowableMovement = isInsideScroller
|
|
220
|
+
? PRESS.slop.value
|
|
221
|
+
: Number.POSITIVE_INFINITY;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|