@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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file indicatorPress.stylex.ts
|
|
5
|
+
* @input Uses StyleX
|
|
6
|
+
* @output Exports `indicatorPressScope`, the marker of a first-party
|
|
7
|
+
* indicator that paints its own press
|
|
8
|
+
* @position Internal entry for Astryx's own replacement indicators;
|
|
9
|
+
* CheckboxInput reads it
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as stylex from '@stylexjs/stylex';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Marks a first-party replacement indicator that paints its own press, for
|
|
16
|
+
* a shape that CheckboxInput's rounded-square press layer would tint past,
|
|
17
|
+
* such as a circle. CheckboxInput leaves that layer off over an indicator
|
|
18
|
+
* with this marker.
|
|
19
|
+
*
|
|
20
|
+
* @internal Not public API: the touch press reaches the indicator through a
|
|
21
|
+
* private property.
|
|
22
|
+
*/
|
|
23
|
+
export const indicatorPressScope: ReturnType<typeof stylex.defineMarker> =
|
|
24
|
+
stylex.defineMarker();
|
package/src/motion/README.md
CHANGED
|
@@ -19,6 +19,7 @@ roles through `resolveMotion`.
|
|
|
19
19
|
| `uikit/scroll.ts` | UIScrollView: deceleration rates, rubber band, bounce-back spring, `delaysContentTouches`, scroll to top. |
|
|
20
20
|
| `uikit/timing.ts` | Non-spring clocks and curves: press highlight, Core Animation's named curves, keyboard, Reduced Motion cross-fade. |
|
|
21
21
|
| `uikit/paging.ts` | A paging UIScrollView: the integrator that settles a release on its page, the page rule, the catch, `setContentOffset(animated:)`. |
|
|
22
|
+
| `uikit/alert.ts` | UIAlertController: how a finger is tracked across the actions (highlight onset and bounds). |
|
|
22
23
|
| `uikit/navigation.ts` | UINavigationController: push velocity, parallax, dimming, transition clock, interactive pop finish rule. |
|
|
23
24
|
| `uikit/progressBar.ts` | UIProgressView's fill: the whole-track duration and curve, its minimum length and empty alpha; the reduced-motion pulse (three roles). |
|
|
24
25
|
| `uikit/gestures.ts` | Gesture recognizer thresholds, the velocity filter and haptic stand-ins, as `{value, unit}` entries (sources in JSDoc). |
|
|
@@ -64,7 +65,7 @@ the timings that decide who gets a touch are not roles.
|
|
|
64
65
|
## Animator
|
|
65
66
|
|
|
66
67
|
```
|
|
67
|
-
import {
|
|
68
|
+
import {createMotionAnimator} from '../motion/animator';
|
|
68
69
|
import {coast} from '../motion/coast';
|
|
69
70
|
import {solveSpringTowardRamp, toSpring} from '../motion/spring';
|
|
70
71
|
import {resolveMotion} from '../motion/resolveMotion';
|
|
@@ -73,7 +74,7 @@ import {registerSpringsTable, springs} from '../motion/uikit';
|
|
|
73
74
|
// Once, so the springs are registered as their roles:
|
|
74
75
|
registerSpringsTable();
|
|
75
76
|
|
|
76
|
-
const animator =
|
|
77
|
+
const animator = createMotionAnimator(sheet);
|
|
77
78
|
// The animator plays a motion role (spec:AST-094 FR4), its table value, as the
|
|
78
79
|
// per-role tables stay internal (DEC-15); math on a spring reads the role with
|
|
79
80
|
// resolveMotion.
|
|
@@ -138,8 +139,10 @@ await handle.finished; // true if it ran to the end, false if interrupted
|
|
|
138
139
|
presented call's second frame) runs on over two frames, the start moves to
|
|
139
140
|
the call's first read after it, with every call of that task. Everything
|
|
140
141
|
the call starts shares that start
|
|
141
|
-
(`handle.started`); a retarget
|
|
142
|
-
|
|
142
|
+
(`handle.started`); a retarget of a curve that still moves keeps an
|
|
143
|
+
explicit one, while one on a tail that already shows as at rest (within two
|
|
144
|
+
sample tolerances of its target, and slower than one a frame) counts as a
|
|
145
|
+
call from rest. `{at}` puts several animators on one clock. `{start: 'commit'}` lets the browser date
|
|
143
146
|
a retarget too; `{start: 'presented'}` waits two frames, until the
|
|
144
147
|
element at rest has been presented. `handle.curve(property)` is the curve
|
|
145
148
|
a call plays, for a companion to follow. `{additive: true}` retargets the
|
|
@@ -168,7 +171,7 @@ await handle.finished; // true if it ran to the end, false if interrupted
|
|
|
168
171
|
- `animatorFor(element)` returns the one animator for an element, so code
|
|
169
172
|
that animates it from several places (a swipe on x, a removal on y)
|
|
170
173
|
composes in one `translate` track.
|
|
171
|
-
- `
|
|
174
|
+
- `createMotionAnimator(dialog, {pseudoElement: '::backdrop'})` drives a
|
|
172
175
|
pseudo-element; with no inline style to hold it, a static filling animation
|
|
173
176
|
holds its end value until the next animation replaces it, while the
|
|
174
177
|
pseudo-element exists. A dialog's `::backdrop` is made again each time the
|
package/src/motion/animator.ts
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* @file animator.ts
|
|
5
5
|
* @input An element, target values, and a spring, momentum curves (coast.ts)
|
|
6
6
|
* or any closed-form curve
|
|
7
|
-
* @output Exports `
|
|
8
|
-
* rotate, scale and opacity,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* @output Exports `createMotionAnimator`, a compositor animator for
|
|
8
|
+
* translate, rotate, scale and opacity, and its option, handle and
|
|
9
|
+
* transition types. It reads presentation values and velocities from the
|
|
10
|
+
* analytic curves. It retargets with the velocity kept, freezes in place on
|
|
11
|
+
* `stop`, and gives completion promises, a shared clock, and UIKit's
|
|
12
|
+
* Reduced Motion substitution.
|
|
12
13
|
* @position Motion foundation (DOM). Internal to core: components use it for
|
|
13
14
|
* every touch-presentation animation; not exported from the package entry
|
|
14
15
|
*
|
|
@@ -244,18 +245,18 @@ export interface AnimateOptions {
|
|
|
244
245
|
crossfadeAs?: 'entrance' | 'exit';
|
|
245
246
|
/**
|
|
246
247
|
* When the call's animations start. By default a call from rest starts
|
|
247
|
-
* when the browser commits it
|
|
248
|
-
*
|
|
248
|
+
* when the browser commits it. A call that continues motion on screen (a
|
|
249
|
+
* retarget) starts now, from the value on screen. A curve whose tail
|
|
250
|
+
* already shows as at rest counts as rest. Ignored with `at`.
|
|
249
251
|
* - `'commit'`: a call that continues motion also starts when the browser
|
|
250
|
-
* commits it
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* continues motion on screen (a retarget) starts now, as by default.
|
|
252
|
+
* commits it. The browser dates it, as it dates a call from rest.
|
|
253
|
+
* - `'presented'`: a call from rest leaves its element at rest. Its
|
|
254
|
+
* animations are created two frames later, once the frame with the
|
|
255
|
+
* element at rest has been presented. They then start at their commit,
|
|
256
|
+
* so a busy UI process delays the motion instead of skipping into it.
|
|
257
|
+
* Any other change to the animator before then creates the animations
|
|
258
|
+
* first, in call order. A call that continues motion on screen starts
|
|
259
|
+
* now, as by default.
|
|
259
260
|
*/
|
|
260
261
|
start?: 'commit' | 'presented';
|
|
261
262
|
/**
|
|
@@ -297,7 +298,7 @@ export interface MotionCurve {
|
|
|
297
298
|
}
|
|
298
299
|
|
|
299
300
|
/**
|
|
300
|
-
* Options of `
|
|
301
|
+
* Options of `createMotionAnimator`.
|
|
301
302
|
*/
|
|
302
303
|
/** How `set` puts values in place. */
|
|
303
304
|
export interface SetOptions {
|
|
@@ -652,6 +653,18 @@ const SAMPLE_TOLERANCE: Record<MotionProperty, number> = {
|
|
|
652
653
|
/** Keyframes start on this grid (ms) and are split where the curve needs it. */
|
|
653
654
|
const SAMPLE_GRID_MS = 1000 / 30;
|
|
654
655
|
|
|
656
|
+
/**
|
|
657
|
+
* A curve within this many sample tolerances of its target can show as at
|
|
658
|
+
* rest. A call on it then starts as one from rest does, at the browser's start.
|
|
659
|
+
*/
|
|
660
|
+
const REST_DISTANCE_TOLERANCES = 2;
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* A curve shows as at rest only while it moves at most one `SAMPLE_TOLERANCE`
|
|
664
|
+
* a frame at this rate.
|
|
665
|
+
*/
|
|
666
|
+
const REST_FRAMES_PER_SECOND = 60;
|
|
667
|
+
|
|
655
668
|
/** A curve that has not settled by then (an undamped spring) snaps. */
|
|
656
669
|
const MAX_DURATION_MS = 30_000;
|
|
657
670
|
|
|
@@ -803,7 +816,7 @@ export function animatorFor(
|
|
|
803
816
|
const key = options.pseudoElement ?? '';
|
|
804
817
|
let animator = byPseudo.get(key);
|
|
805
818
|
if (animator == null) {
|
|
806
|
-
animator =
|
|
819
|
+
animator = createMotionAnimator(element, options);
|
|
807
820
|
byPseudo.set(key, animator);
|
|
808
821
|
}
|
|
809
822
|
return animator;
|
|
@@ -920,7 +933,7 @@ const REDUCED_TRANSITION: SpringTransition = {
|
|
|
920
933
|
*
|
|
921
934
|
* @example
|
|
922
935
|
* ```
|
|
923
|
-
* const animator =
|
|
936
|
+
* const animator = createMotionAnimator(sheet);
|
|
924
937
|
* // finger lifts at 1200 px/s heading for the detent at 0 (a component
|
|
925
938
|
* // passes the spring of its motion role):
|
|
926
939
|
* animator.animate({y: 0}, {spring: {response: 0.45, dampingRatio: 0.9}, velocity: {y: 1200}});
|
|
@@ -929,7 +942,7 @@ const REDUCED_TRANSITION: SpringTransition = {
|
|
|
929
942
|
* animator.stop(['y']);
|
|
930
943
|
* ```
|
|
931
944
|
*/
|
|
932
|
-
export function
|
|
945
|
+
export function createMotionAnimator(
|
|
933
946
|
element: HTMLElement | null,
|
|
934
947
|
options: AnimatorOptions = {},
|
|
935
948
|
): Animator {
|
|
@@ -1165,6 +1178,22 @@ export function createAnimator(
|
|
|
1165
1178
|
return curveAt(property, time);
|
|
1166
1179
|
};
|
|
1167
1180
|
|
|
1181
|
+
/**
|
|
1182
|
+
* Whether a curve's tail already shows as at rest at `time`. Under a
|
|
1183
|
+
* starved main thread the browser commits a call late. A call that starts
|
|
1184
|
+
* at its own time then skips that far into its curve. One that starts at
|
|
1185
|
+
* the commit holds an element that hardly moves.
|
|
1186
|
+
*/
|
|
1187
|
+
const showsAtRest = (
|
|
1188
|
+
property: MotionProperty,
|
|
1189
|
+
segment: Segment,
|
|
1190
|
+
time: number,
|
|
1191
|
+
): boolean =>
|
|
1192
|
+
Math.abs(valueAt(property, time) - segment.target) <=
|
|
1193
|
+
REST_DISTANCE_TOLERANCES * SAMPLE_TOLERANCE[property] &&
|
|
1194
|
+
Math.abs(velocityAt(property, time)) <=
|
|
1195
|
+
SAMPLE_TOLERANCE[property] * REST_FRAMES_PER_SECOND;
|
|
1196
|
+
|
|
1168
1197
|
const velocityAt = (property: MotionProperty, time: number): number => {
|
|
1169
1198
|
const held = segments.get(property);
|
|
1170
1199
|
if (held != null && isHeld(held)) {
|
|
@@ -2033,10 +2062,10 @@ export function createAnimator(
|
|
|
2033
2062
|
// -- starting from rest ---------------------------------------------------
|
|
2034
2063
|
|
|
2035
2064
|
/**
|
|
2036
|
-
* A call starts from rest when nothing on the channels it drives
|
|
2037
|
-
*
|
|
2038
|
-
*
|
|
2039
|
-
* start.
|
|
2065
|
+
* A call starts from rest when nothing on the channels it drives moves on
|
|
2066
|
+
* screen and nothing pins its clock (`at`). A curve still waiting for its
|
|
2067
|
+
* first frame counts as rest, as does one whose tail already shows as at
|
|
2068
|
+
* rest. Returns its start group, or null for an explicit start.
|
|
2040
2069
|
*/
|
|
2041
2070
|
const beginGroup = (
|
|
2042
2071
|
time: number,
|
|
@@ -2070,10 +2099,12 @@ export function createAnimator(
|
|
|
2070
2099
|
}
|
|
2071
2100
|
return null;
|
|
2072
2101
|
}
|
|
2073
|
-
// A call that continues motion starts now unless it asks for the
|
|
2074
|
-
// browser's start (`commit`). `presented` asks only for a call from rest
|
|
2075
|
-
//
|
|
2076
|
-
// the value and velocity on screen
|
|
2102
|
+
// A call that continues motion starts now, unless it asks for the
|
|
2103
|
+
// browser's start (`commit`). `presented` asks only for a call from rest.
|
|
2104
|
+
// A curve that still moves is not at rest, so a call on it starts now,
|
|
2105
|
+
// from the value and velocity on screen. A tail that shows as at rest
|
|
2106
|
+
// counts as rest, so a call on it waits, under `presented` for its two
|
|
2107
|
+
// frames.
|
|
2077
2108
|
for (const channel of start === 'commit' ? [] : channels) {
|
|
2078
2109
|
for (const property of CHANNELS[channel]) {
|
|
2079
2110
|
const segment = segments.get(property);
|
|
@@ -2084,7 +2115,8 @@ export function createAnimator(
|
|
|
2084
2115
|
time < endOf(property, segment) &&
|
|
2085
2116
|
(segment.group == null ||
|
|
2086
2117
|
segment.group.resolved ||
|
|
2087
|
-
shownSince(property, segment) != null)
|
|
2118
|
+
shownSince(property, segment) != null) &&
|
|
2119
|
+
!showsAtRest(property, segment, time)
|
|
2088
2120
|
) {
|
|
2089
2121
|
return null;
|
|
2090
2122
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file reducedMotion.ts
|
|
5
5
|
* @input The Reduced Motion preference, the primary pointer, the motion tables
|
|
6
6
|
* @output reducedMotionFade (the fade the animator would play, for motion it
|
|
7
|
-
* does not drive) and the
|
|
7
|
+
* does not drive), playReducedMotionDip, and the queries a transition spells
|
|
8
8
|
* @position The one Reduced Motion policy (design:reduced-motion) for code
|
|
9
9
|
* beside the animator: a raw Web Animations fade, a CSS transition
|
|
10
10
|
*
|
|
@@ -102,3 +102,81 @@ export function reducedMotionFade(
|
|
|
102
102
|
easing: `cubic-bezier(${curve.join(', ')})`,
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/** A running Reduced Motion dip. */
|
|
107
|
+
export interface ReducedMotionDip {
|
|
108
|
+
/** Stops the dip. A change that has not landed yet never lands. */
|
|
109
|
+
cancel(): void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Plays `fade` on `element` as a dip, and runs `land` at its middle: the
|
|
114
|
+
* fade-out, the change, then the fade-in. The fade-out holds at 0 until `land`
|
|
115
|
+
* has run, and the fade-in starts only after it. So the change never shows at
|
|
116
|
+
* full opacity, however late the main thread runs. A timer at the middle can
|
|
117
|
+
* fire after the fade-in has begun.
|
|
118
|
+
*
|
|
119
|
+
* `land` must apply the change before it returns: the fade-in starts right
|
|
120
|
+
* after it. `from` is the opacity the fade-out starts at: a leaving element
|
|
121
|
+
* fades out from where it is.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```
|
|
125
|
+
* const dip = playReducedMotionDip(scroller, fade, () => scroller.scrollTo({top}));
|
|
126
|
+
* // A newer change replaces it:
|
|
127
|
+
* dip.cancel();
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function playReducedMotionDip(
|
|
131
|
+
element: Element,
|
|
132
|
+
fade: ReducedMotionFade,
|
|
133
|
+
land: () => void,
|
|
134
|
+
from = 1,
|
|
135
|
+
): ReducedMotionDip {
|
|
136
|
+
const half = fade.duration / 2;
|
|
137
|
+
let isCancelled = false;
|
|
138
|
+
let fadeIn: Animation | null = null;
|
|
139
|
+
const fadeOut = element.animate(
|
|
140
|
+
[{opacity: from, easing: fade.easing}, {opacity: 0}],
|
|
141
|
+
{duration: half, fill: 'forwards'},
|
|
142
|
+
);
|
|
143
|
+
fadeOut.finished.then(
|
|
144
|
+
() => {
|
|
145
|
+
if (isCancelled) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
land();
|
|
149
|
+
// The fade-in holds 0 until it starts: after a long stall, its start can
|
|
150
|
+
// come after the frame that runs this.
|
|
151
|
+
fadeIn = element.animate(
|
|
152
|
+
[{opacity: 0, easing: fade.easing}, {opacity: 1}],
|
|
153
|
+
{duration: half, fill: 'backwards'},
|
|
154
|
+
);
|
|
155
|
+
// The fade-in keeps the dip's own schedule: it starts where the fade-out
|
|
156
|
+
// ended, so the dip keeps its length. The clock is read after the
|
|
157
|
+
// landing, because a frame's timestamp does not count the landing's own
|
|
158
|
+
// work. After a long stall, half of the fade-in still plays.
|
|
159
|
+
const fadeOutEnd = endTime(fadeOut, half);
|
|
160
|
+
if (fadeOutEnd != null) {
|
|
161
|
+
fadeIn.startTime = Math.max(fadeOutEnd, performance.now() - half / 2);
|
|
162
|
+
}
|
|
163
|
+
// The fade-in now holds the opacity, so the held 0 can go.
|
|
164
|
+
fadeOut.cancel();
|
|
165
|
+
},
|
|
166
|
+
// A cancelled fade-out rejects `finished`: nothing lands.
|
|
167
|
+
() => {},
|
|
168
|
+
);
|
|
169
|
+
return {
|
|
170
|
+
cancel() {
|
|
171
|
+
isCancelled = true;
|
|
172
|
+
fadeOut.cancel();
|
|
173
|
+
fadeIn?.cancel();
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** When `animation` was due to end, on its timeline (ms), if it has started. */
|
|
179
|
+
function endTime(animation: Animation, duration: number): number | null {
|
|
180
|
+
const start = animation.startTime;
|
|
181
|
+
return typeof start === 'number' ? start + duration : null;
|
|
182
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file alert.ts
|
|
5
|
+
* @input Nothing; data only
|
|
6
|
+
* @output Exports `alertController`: how UIAlertController (`.alert`) tracks
|
|
7
|
+
* a finger across its actions
|
|
8
|
+
* @position The alert domain of the UIKit constants table (motion/uikit);
|
|
9
|
+
* its springs are `springs.alertPresent` and `springs.alertDismiss`
|
|
10
|
+
*
|
|
11
|
+
* Every value was read from UIKit on the iOS 26.2 simulator. A host app
|
|
12
|
+
* presented `UIAlertController(.alert)` with two and with three actions.
|
|
13
|
+
* Real HID touches drove it, and the action views' highlight was sampled
|
|
14
|
+
* every frame. Lengths are in pt (CSS px), durations in ms.
|
|
15
|
+
*
|
|
16
|
+
* Every leaf carries one or more `@source`; GUESS values also carry the
|
|
17
|
+
* `@measure` task that will confirm or replace them. uikit.test.ts enforces
|
|
18
|
+
* both.
|
|
19
|
+
*
|
|
20
|
+
* SYNC: When modified, update /packages/core/src/motion/MEASUREMENTS.md
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const alertController = {
|
|
24
|
+
actions: {
|
|
25
|
+
/**
|
|
26
|
+
* How long after touch-down the touched action highlights: none, it is
|
|
27
|
+
* highlighted in the touch-down frame (a tap and a 300 ms hold alike).
|
|
28
|
+
* @source measured: UIKit lab (injected host app), iOS 26.2 simulator,
|
|
29
|
+
* iPhone 17 Pro, per-frame highlight of the action views under real HID
|
|
30
|
+
* touches, Oct 23 2026
|
|
31
|
+
*/
|
|
32
|
+
highlightOnsetMs: 0,
|
|
33
|
+
/**
|
|
34
|
+
* How far outside an action the finger may go and keep it highlighted:
|
|
35
|
+
* none. The highlight cleared 1 pt below the action, and a lift there ran
|
|
36
|
+
* nothing. UIButton keeps 70 pt; an alert action does not.
|
|
37
|
+
* @source measured: UIKit lab (injected host app), iOS 26.2 simulator,
|
|
38
|
+
* iPhone 17 Pro, per-frame highlight of the action views under real HID
|
|
39
|
+
* touches, Oct 23 2026
|
|
40
|
+
*/
|
|
41
|
+
highlightExpansion: 0,
|
|
42
|
+
},
|
|
43
|
+
} as const;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
/**
|
|
3
|
+
* @file privateAttributes/pressGroupItem.stylex.ts
|
|
4
|
+
* @input Nothing: the private press-group-item attribute
|
|
5
|
+
* @output pressGroupItemProps, which marks an action a group's tracker
|
|
6
|
+
* follows, and isTrackedByGroup, which reads the mark
|
|
7
|
+
* @position Owner module of the private press-group-item attribute: the only
|
|
8
|
+
* file that writes its name. AlertDialog marks its actions, and the press
|
|
9
|
+
* controller (utils/pressFeedback.ts) leaves their press to the group's
|
|
10
|
+
* tracker. No package entry exports it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const PRESS_GROUP_ITEM = 'data-astryx-press-group-item';
|
|
14
|
+
|
|
15
|
+
/** Spread on each action that a group's tracker follows. */
|
|
16
|
+
export const pressGroupItemProps: Readonly<Record<string, string>> = {
|
|
17
|
+
[PRESS_GROUP_ITEM]: '',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Whether a group's tracker paints the press on `element` or an ancestor. */
|
|
21
|
+
export function isTrackedByGroup(element: Element): boolean {
|
|
22
|
+
return element.closest(`[${PRESS_GROUP_ITEM}]`) != null;
|
|
23
|
+
}
|
package/src/utils/clickGuard.ts
CHANGED
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
// `touchend`, aimed at whatever is under the finger by then. They go with
|
|
27
27
|
// the click, the next `pointerdown` or the window, as the guard does.
|
|
28
28
|
//
|
|
29
|
+
// The menu press model's arm at a finger's release also cancels that touch's
|
|
30
|
+
// `touchend`, so the browser creates no click and no compatibility mouse
|
|
31
|
+
// events. A busy main thread can otherwise hold that click past the window,
|
|
32
|
+
// when a drill-in has put another row under the finger.
|
|
33
|
+
//
|
|
29
34
|
// Some clicks always pass:
|
|
30
35
|
// - a click with `detail` 0: a keyboard's, a screen reader's, or one that the
|
|
31
36
|
// page dispatches;
|
|
@@ -56,6 +61,13 @@ export interface ClickGuardOptions {
|
|
|
56
61
|
* @default false
|
|
57
62
|
*/
|
|
58
63
|
readonly takesLateMouse?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The menu press model's arm at a finger's release: the guard also cancels
|
|
66
|
+
* that touch's `touchend`, so the browser creates no click for it. A
|
|
67
|
+
* cancelled `touchend` disarms the guard.
|
|
68
|
+
* @default false
|
|
69
|
+
*/
|
|
70
|
+
readonly cancelsTouchEnd?: boolean;
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
interface Guard {
|
|
@@ -89,7 +101,11 @@ function listenForClicks(view: Window): () => void {
|
|
|
89
101
|
/** Swallow the browser's next click on `doc` (see the file header). */
|
|
90
102
|
export function armClickGuard(
|
|
91
103
|
doc: Document,
|
|
92
|
-
{
|
|
104
|
+
{
|
|
105
|
+
staysInstalled = false,
|
|
106
|
+
takesLateMouse = false,
|
|
107
|
+
cancelsTouchEnd = false,
|
|
108
|
+
}: ClickGuardOptions = {},
|
|
93
109
|
): void {
|
|
94
110
|
const view = doc.defaultView;
|
|
95
111
|
if (view == null) {
|
|
@@ -116,6 +132,26 @@ export function armClickGuard(
|
|
|
116
132
|
);
|
|
117
133
|
}
|
|
118
134
|
}
|
|
135
|
+
if (cancelsTouchEnd) {
|
|
136
|
+
// A touch sends its pointerup before its touchend, so the next touchend is
|
|
137
|
+
// that touch's own. The next pointerdown disarms the guard first.
|
|
138
|
+
const stopTouchEnd = subscribeInput(
|
|
139
|
+
view,
|
|
140
|
+
'touchend',
|
|
141
|
+
event => {
|
|
142
|
+
stopTouchEnd();
|
|
143
|
+
// A touchend that the browser took for a scroll is not cancelable.
|
|
144
|
+
// The guard stays armed for a click that may follow it.
|
|
145
|
+
if (event.cancelable) {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
// No click follows a cancelled touchend.
|
|
148
|
+
disarmClickGuard(doc);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{cancels: true},
|
|
152
|
+
);
|
|
153
|
+
guard.unsubscribers.push(stopTouchEnd);
|
|
154
|
+
}
|
|
119
155
|
guard.timer = setTimeout(() => disarmClickGuard(doc), CLICK_GUARD_WINDOW_MS);
|
|
120
156
|
}
|
|
121
157
|
|
package/src/utils/dateParser.ts
CHANGED
|
@@ -32,6 +32,11 @@ export function isLocaleDayFirst(locale: Locale = 'en'): boolean {
|
|
|
32
32
|
return dayIndex < monthIndex;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Built at run time: a Unicode property escape in a literal fails a source
|
|
36
|
+
// build whose Babel lacks the Unicode data.
|
|
37
|
+
const LETTER_FREE = new RegExp('^[^\\p{L}]+$', 'u');
|
|
38
|
+
const YEAR_FIRST = new RegExp('^\\d{4}[^\\p{L}\\d]', 'u');
|
|
39
|
+
|
|
35
40
|
/**
|
|
36
41
|
* Parses user input into a PlainDate.
|
|
37
42
|
*
|
|
@@ -138,6 +143,17 @@ export function parseDateInput(
|
|
|
138
143
|
if (/^\d+$/.test(trimmed)) {
|
|
139
144
|
return null;
|
|
140
145
|
}
|
|
146
|
+
// In a day-first locale, letter-free text that no form above matched is not
|
|
147
|
+
// a date. Native `Date` reads `a/b/c` month first. It also takes a year of
|
|
148
|
+
// one to three digits. So "12/01/2" gives a wrong date. A four-digit year
|
|
149
|
+
// written first is not ambiguous.
|
|
150
|
+
if (
|
|
151
|
+
isLocaleDayFirst(locale) &&
|
|
152
|
+
LETTER_FREE.test(trimmed) &&
|
|
153
|
+
!YEAR_FIRST.test(trimmed)
|
|
154
|
+
) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
141
157
|
|
|
142
158
|
const parsed = new Date(trimmed);
|
|
143
159
|
if (!isNaN(parsed.getTime())) {
|
package/src/utils/focusReturn.ts
CHANGED
|
@@ -151,3 +151,33 @@ export function focusForLastInput(
|
|
|
151
151
|
}
|
|
152
152
|
element.focus({...options, focusVisible: false});
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Prepares the focus return for an open surface that may be removed while
|
|
157
|
+
* open (rendered only while open, or removed with its view). Removing a
|
|
158
|
+
* `<dialog>` runs no close and no browser focus return. Call this while the
|
|
159
|
+
* surface is mounted: it reads the frame that hosts the surface now, because a
|
|
160
|
+
* removed frame's document has no window. Call the returned function from the
|
|
161
|
+
* effect cleanup with the opener.
|
|
162
|
+
*
|
|
163
|
+
* Once the commit has finished, focus moves to the opener only when the
|
|
164
|
+
* surface really left the document (StrictMode's simulated unmount keeps it),
|
|
165
|
+
* the opener is still on the page, and the removal lost focus, both in the
|
|
166
|
+
* surface's document and in the document that hosts its frame. A control the
|
|
167
|
+
* user pressed outside the surface, or outside its frame, keeps its focus.
|
|
168
|
+
*/
|
|
169
|
+
export function prepareFocusReturnOnRemoval(
|
|
170
|
+
surface: Element,
|
|
171
|
+
): (opener: Element | null) => void {
|
|
172
|
+
const host = surface.ownerDocument.defaultView?.frameElement ?? null;
|
|
173
|
+
return opener => {
|
|
174
|
+
queueMicrotask(() => {
|
|
175
|
+
if (surface.isConnected || opener == null || !opener.isConnected) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (isFocusLost(surface) && (host == null || isFocusLost(host))) {
|
|
179
|
+
focusForLastInput(opener as HTMLElement);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* SYNC: When modified, update pressFeedback.ts, pressGesture.ts (the fade
|
|
16
16
|
* clock), hooks/usePressFeedback.doc.mjs, the components that paint the touch
|
|
17
17
|
* press off an ancestor scope or a pseudo-element (Switch, Tab,
|
|
18
|
-
* CheckboxInput, RadioListItem, ClickableCard, SelectableCard, Thumbnail)
|
|
19
|
-
* and
|
|
18
|
+
* CheckboxInput, RadioListItem, ClickableCard, SelectableCard, Thumbnail),
|
|
19
|
+
* Button (a tracked action sizes its `:active` image to zero) and
|
|
20
|
+
* scripts/build-css.test.mjs.
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
// Every surface that paints a press uses one of these styles, so a change to
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file pressFeedback.ts
|
|
5
|
-
* @input The press state machine (pressGesture.ts), the menu-press
|
|
6
|
-
*
|
|
5
|
+
* @input The press state machine (pressGesture.ts), the menu-press and
|
|
6
|
+
* press-group markers and the document's pointer, keyboard, scroll and
|
|
7
7
|
* visibility events
|
|
8
8
|
* @output PRESSABLE_ATTRIBUTE, PRESSED_ATTRIBUTE, pressableProps and
|
|
9
9
|
* installPressFeedback
|
|
@@ -46,6 +46,7 @@ import {isCoarsePointer, watchCoarsePointer} from './coarsePointer';
|
|
|
46
46
|
import {subscribeInput} from './inputStream';
|
|
47
47
|
import {TOUCH_QUERY} from './touchPresentation';
|
|
48
48
|
import {holdsPressThroughDrag} from '../privateAttributes/pressHoldsDrag.stylex';
|
|
49
|
+
import {isTrackedByGroup} from '../privateAttributes/pressGroupItem.stylex';
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* The marker the controller finds with `closest()`. Every Astryx component
|
|
@@ -325,8 +326,9 @@ function stylesheetDroppedActive(): boolean {
|
|
|
325
326
|
|
|
326
327
|
/**
|
|
327
328
|
* The surface a mouse button's or the Space key's press paints, if any: the
|
|
328
|
-
* innermost marked surface, outside a menu. A key's
|
|
329
|
-
* focused element that is itself the surface, as
|
|
329
|
+
* innermost marked surface, outside a menu and off a tracked action. A key's
|
|
330
|
+
* press paints only a focused element that is itself the surface, as
|
|
331
|
+
* `:active` does.
|
|
330
332
|
*/
|
|
331
333
|
function buttonSurfaceAt(
|
|
332
334
|
target: EventTarget | null,
|
|
@@ -336,6 +338,10 @@ function buttonSurfaceAt(
|
|
|
336
338
|
if (element == null || element.closest(MENU_PRESS_ROOT) != null) {
|
|
337
339
|
return null;
|
|
338
340
|
}
|
|
341
|
+
// A group's tracker follows pointers only, so a key's press stays here.
|
|
342
|
+
if (pointerId != null && isTrackedByGroup(element)) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
339
345
|
if (pointerId == null && element !== target) {
|
|
340
346
|
return null;
|
|
341
347
|
}
|
|
@@ -414,7 +420,9 @@ function onPointerDown(event: PointerEvent): void {
|
|
|
414
420
|
const braking = element != null && scrollerIsBraking(element);
|
|
415
421
|
lastTouchAt = Date.now();
|
|
416
422
|
lastTouchTarget = event.target instanceof Node ? event.target : null;
|
|
417
|
-
|
|
423
|
+
// A group's tracker paints a tracked action, so two writers would erase each
|
|
424
|
+
// other's paint. The touch still counts for the brake above.
|
|
425
|
+
if (element == null || braking || isTrackedByGroup(element)) {
|
|
418
426
|
return;
|
|
419
427
|
}
|
|
420
428
|
takeOver(button, element);
|
|
@@ -560,6 +568,11 @@ function activatesOnSpace(target: EventTarget | null): boolean {
|
|
|
560
568
|
);
|
|
561
569
|
}
|
|
562
570
|
|
|
571
|
+
/** A tracked action takes no `:active` paint, so a key's press paints here. */
|
|
572
|
+
function isTrackedAction(target: EventTarget | null): boolean {
|
|
573
|
+
return target instanceof Element && isTrackedByGroup(target);
|
|
574
|
+
}
|
|
575
|
+
|
|
563
576
|
/**
|
|
564
577
|
* Space went down on a focused button that is itself a pressable surface.
|
|
565
578
|
* Heard in the BUBBLE phase, after the page's own handlers, so a press the
|
|
@@ -573,7 +586,7 @@ function onKeyDown(event: KeyboardEvent): void {
|
|
|
573
586
|
event.isComposing ||
|
|
574
587
|
event.defaultPrevented ||
|
|
575
588
|
!activatesOnSpace(event.target) ||
|
|
576
|
-
!stylesheetDroppedActive()
|
|
589
|
+
!(stylesheetDroppedActive() || isTrackedAction(event.target))
|
|
577
590
|
) {
|
|
578
591
|
return;
|
|
579
592
|
}
|