@astryxdesign/core 0.7.8 → 0.7.9-canary.048476f
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/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +5 -0
- package/dist/Calendar/styles.d.ts +1 -1
- package/dist/ClickableCard/ClickableCard.d.ts +1 -1
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +5 -1
- package/dist/DateInput/PickerWheel.d.ts.map +1 -1
- package/dist/DateInput/PickerWheel.js +7 -1
- package/dist/DateRangeInput/DateRangeInput.js +1 -1
- package/dist/Dialog/Dialog.d.ts +2 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +12 -1
- package/dist/Drawer/useDrawerDialogPresence.d.ts +2 -1
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
- package/dist/Drawer/useDrawerDialogPresence.js +11 -2
- package/dist/DropdownMenu/DropdownMenu.d.ts +9 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +9 -4
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +3 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +3 -2
- package/dist/Field/inputDisabledFocus.d.ts +4 -4
- package/dist/Field/inputStyles.stylex.d.ts +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -2
- package/dist/Icon/Icon.d.ts +1 -1
- package/dist/InfoTip/InfoTip.d.ts +1 -0
- package/dist/InfoTip/InfoTip.d.ts.map +1 -1
- package/dist/InfoTip/InfoTip.js +1 -0
- package/dist/Item/Item.d.ts +2 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +29 -6
- package/dist/Layer/holdEntranceInput.d.ts +24 -0
- package/dist/Layer/holdEntranceInput.d.ts.map +1 -0
- package/dist/Layer/holdEntranceInput.js +65 -0
- package/dist/Layer/inputHold.d.ts +49 -0
- package/dist/Layer/inputHold.d.ts.map +1 -0
- package/dist/Layer/inputHold.js +636 -0
- package/dist/Layer/preserveAnimations.d.ts +1 -5
- package/dist/Layer/preserveAnimations.d.ts.map +1 -1
- package/dist/Layer/preserveAnimations.js +12 -214
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +4 -0
- package/dist/MetadataList/MetadataList.js +1 -1
- package/dist/MobileNav/MobileNav.d.ts +11 -1
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +34 -1
- package/dist/MoreMenu/MoreMenu.d.ts +5 -3
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +4 -1
- package/dist/SelectableCard/SelectableCard.d.ts +1 -1
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +5 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +12 -2
- package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +9 -0
- package/dist/Token/TokenLink.d.ts +1 -1
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +4 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +162 -59
- package/dist/TreeList/TreeListItem.d.ts +2 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +27 -3
- package/dist/astryx.css +30 -7
- package/dist/gestures/GestureHost.d.ts +2 -2
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +21 -6
- package/dist/gestures/nativeScroll.d.ts +3 -6
- package/dist/gestures/nativeScroll.d.ts.map +1 -1
- package/dist/gestures/nativeScroll.js +7 -52
- package/dist/gestures/wheelSegmenter.d.ts +68 -1
- package/dist/gestures/wheelSegmenter.d.ts.map +1 -1
- package/dist/gestures/wheelSegmenter.js +199 -10
- package/dist/hooks/menuPressGesture.d.ts +133 -21
- package/dist/hooks/menuPressGesture.d.ts.map +1 -1
- package/dist/hooks/menuPressGesture.js +155 -30
- package/dist/hooks/menuPressValues.d.ts +16 -0
- package/dist/hooks/menuPressValues.d.ts.map +1 -0
- package/dist/hooks/menuPressValues.js +18 -0
- package/dist/hooks/useClickableContainer.d.ts +27 -5
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +41 -5
- package/dist/hooks/useFocusTrap.d.ts +10 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +225 -132
- package/dist/hooks/useMenuPress.d.ts +34 -1
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +93 -5
- package/dist/hooks/usePressFeedback.d.ts +6 -6
- package/dist/hooks/usePressFeedback.js +6 -6
- package/dist/motion/carryAnimations.d.ts +58 -0
- package/dist/motion/carryAnimations.d.ts.map +1 -0
- package/dist/motion/carryAnimations.js +305 -0
- package/dist/motion/transientCopy.d.ts +4 -2
- package/dist/motion/transientCopy.d.ts.map +1 -1
- package/dist/motion/transientCopy.js +34 -5
- package/dist/motion/uikit/gestures.d.ts +41 -0
- package/dist/motion/uikit/gestures.d.ts.map +1 -1
- package/dist/motion/uikit/gestures.js +25 -0
- package/dist/motion/uikit/index.d.ts +1 -1
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +1 -1
- package/dist/privateAttributes/menuPressLit.stylex.d.ts +52 -0
- package/dist/privateAttributes/menuPressLit.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/menuPressLit.stylex.js +99 -0
- package/dist/privateAttributes/press.stylex.d.ts +139 -0
- package/dist/privateAttributes/press.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/press.stylex.js +98 -0
- package/dist/theme/tokens.stylex.d.ts +2 -2
- package/dist/theme/tokens.stylex.js +1 -1
- package/dist/utils/flatTree.d.ts +6 -3
- package/dist/utils/flatTree.d.ts.map +1 -1
- package/dist/utils/flatTree.js +13 -3
- package/dist/utils/hitArea.d.ts.map +1 -1
- package/dist/utils/hitArea.js +82 -5
- package/dist/utils/inputStream.d.ts +14 -1
- package/dist/utils/inputStream.d.ts.map +1 -1
- package/dist/utils/inputStream.js +40 -9
- package/dist/utils/interactionOverlay.stylex.d.ts +2 -1
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +7 -2
- package/dist/utils/pressFeedback.d.ts +7 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +32 -7
- package/dist/utils/pressGesture.d.ts +6 -0
- package/dist/utils/pressGesture.d.ts.map +1 -1
- package/dist/utils/pressGesture.js +17 -0
- package/dist/utils/rowPressLayer.stylex.d.ts +14 -0
- package/dist/utils/rowPressLayer.stylex.d.ts.map +1 -0
- package/dist/utils/rowPressLayer.stylex.js +25 -0
- package/dist/utils/scrollView.d.ts +25 -0
- package/dist/utils/scrollView.d.ts.map +1 -0
- package/dist/utils/scrollView.js +108 -0
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +1 -10
- package/dist/utils/triggerFocus.d.ts +5 -0
- package/dist/utils/triggerFocus.d.ts.map +1 -1
- package/dist/utils/triggerFocus.js +9 -2
- package/package.json +5 -5
- package/src/BottomSheet/BottomSheet.tsx +5 -0
- package/src/ClickableCard/ClickableCard.tsx +8 -1
- package/src/DateInput/PickerWheel.tsx +7 -2
- package/src/DateRangeInput/DateRangeInput.tsx +1 -1
- package/src/Dialog/Dialog.doc.mjs +1 -1
- package/src/Dialog/Dialog.tsx +12 -1
- package/src/Drawer/Drawer.doc.mjs +1 -1
- package/src/Drawer/useDrawerDialogPresence.ts +9 -2
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -2
- package/src/DropdownMenu/DropdownMenu.tsx +29 -5
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +8 -4
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -1
- package/src/FileInput/FileInput.tsx +16 -2
- package/src/InfoTip/InfoTip.doc.mjs +26 -2
- package/src/InfoTip/InfoTip.tsx +1 -0
- package/src/Item/Item.tsx +51 -9
- package/src/Layer/holdEntranceInput.ts +84 -0
- package/src/Layer/inputHold.ts +734 -0
- package/src/Layer/preserveAnimations.ts +12 -275
- package/src/Lightbox/Lightbox.tsx +4 -0
- package/src/MetadataList/MetadataList.tsx +1 -1
- package/src/MobileNav/MobileNav.doc.mjs +3 -3
- package/src/MobileNav/MobileNav.tsx +34 -1
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -4
- package/src/MoreMenu/MoreMenu.tsx +5 -3
- package/src/RadioList/RadioListItem.tsx +4 -5
- package/src/SelectableCard/SelectableCard.tsx +8 -1
- package/src/Spinner/Spinner.tsx +13 -3
- package/src/Table/plugins/sortable/useTableSortable.tsx +1 -1
- package/src/Token/Token.tsx +18 -1
- package/src/Token/TokenLink.tsx +7 -1
- package/src/Tooltip/useTooltip.tsx +175 -79
- package/src/TreeList/TreeListItem.tsx +36 -2
- package/src/gestures/GestureHost.ts +21 -5
- package/src/gestures/README.md +12 -4
- package/src/gestures/nativeScroll.ts +12 -68
- package/src/gestures/wheelSegmenter.ts +243 -10
- package/src/hooks/menuPressGesture.ts +244 -38
- package/src/hooks/menuPressValues.ts +18 -0
- package/src/hooks/useClickableContainer.doc.mjs +8 -2
- package/src/hooks/useClickableContainer.ts +72 -6
- package/src/hooks/useFocusTrap.ts +268 -151
- package/src/hooks/useMenuPress.ts +118 -11
- package/src/hooks/usePressFeedback.doc.mjs +3 -3
- package/src/hooks/usePressFeedback.ts +6 -6
- package/src/motion/MEASUREMENTS.md +11 -0
- package/src/motion/carryAnimations.ts +389 -0
- package/src/motion/transientCopy.ts +45 -5
- package/src/motion/uikit/gestures.ts +22 -0
- package/src/motion/uikit/index.ts +1 -0
- package/src/privateAttributes/menuPressLit.stylex.ts +104 -0
- package/src/privateAttributes/press.stylex.ts +244 -0
- package/src/theme/tokens.stylex.ts +1 -1
- package/src/utils/flatTree.ts +15 -3
- package/src/utils/hitArea.ts +107 -5
- package/src/utils/inputStream.ts +47 -10
- package/src/utils/interactionOverlay.stylex.ts +7 -2
- package/src/utils/pressFeedback.ts +31 -6
- package/src/utils/pressGesture.ts +24 -1
- package/src/utils/rowPressLayer.stylex.ts +27 -0
- package/src/utils/scrollView.ts +135 -0
- package/src/utils/selectionLock.ts +5 -10
- package/src/utils/triggerFocus.ts +17 -4
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
/**
|
|
3
|
+
* @file privateAttributes/menuPressLit.stylex.ts
|
|
4
|
+
* @input The row that a held finger or pen lights in a menu
|
|
5
|
+
* @output Exports markMenuPressLit and clearMenuPressLit, which mark that row,
|
|
6
|
+
* and isMenuPressLit, which reads the mark. It also exports
|
|
7
|
+
* menuPressLitRowStyle, which paints the marked row. The hold pair,
|
|
8
|
+
* holdMenuPressPaint and releaseMenuPressPaint, keeps the other rows off
|
|
9
|
+
* their pressed paint, and isMenuPressPaintHeld reads that hold.
|
|
10
|
+
* @position Owner module of the private menu-press-lit attribute: the only
|
|
11
|
+
* file that writes its name. The menu press model (hooks/useMenuPress.ts)
|
|
12
|
+
* marks the row; DropdownMenu's rows compose the styles. No package entry
|
|
13
|
+
* exports it.
|
|
14
|
+
*
|
|
15
|
+
* SYNC: Each row that composes menuPressLitRowStyle reads
|
|
16
|
+
* `--_menu-row-focus-paint` in its `:focus` background, and a row with an
|
|
17
|
+
* `:active` background reads `--_menu-row-active-paint` there:
|
|
18
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuItem.tsx
|
|
19
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuCheckboxItem.tsx
|
|
20
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuRadioItem.tsx
|
|
21
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuSubMenu.tsx
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import * as stylex from '@stylexjs/stylex';
|
|
25
|
+
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
26
|
+
import {colorVars} from '../theme/tokens.stylex';
|
|
27
|
+
|
|
28
|
+
const MENU_PRESS_LIT = 'data-astryx-menu-press-lit';
|
|
29
|
+
const ACTIVE_PAINT = '--_menu-row-active-paint';
|
|
30
|
+
|
|
31
|
+
/** Marks `row` as the row that a held finger or pen lights. */
|
|
32
|
+
export function markMenuPressLit(row: Element): void {
|
|
33
|
+
row.setAttribute(MENU_PRESS_LIT, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Removes the mark from `row`. */
|
|
37
|
+
export function clearMenuPressLit(row: Element): void {
|
|
38
|
+
row.removeAttribute(MENU_PRESS_LIT);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Whether a held finger or pen lights `row`. */
|
|
42
|
+
export function isMenuPressLit(row: Element): boolean {
|
|
43
|
+
return row.hasAttribute(MENU_PRESS_LIT);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Keeps `menu`'s rows off their `:active` paint while a held finger or pen
|
|
48
|
+
* presses in it. The row the press began on stays `:active` as the press
|
|
49
|
+
* moves on. The lit row keeps the pressed overlay through its own mark.
|
|
50
|
+
*/
|
|
51
|
+
export function holdMenuPressPaint(menu: HTMLElement): void {
|
|
52
|
+
menu.style.setProperty(ACTIVE_PAINT, 'transparent');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Gives `menu`'s rows their `:active` paint back. */
|
|
56
|
+
export function releaseMenuPressPaint(menu: HTMLElement): void {
|
|
57
|
+
menu.style.removeProperty(ACTIVE_PAINT);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Whether a held finger or pen keeps `menu`'s rows off their `:active` paint. */
|
|
61
|
+
export function isMenuPressPaintHeld(menu: HTMLElement): boolean {
|
|
62
|
+
return menu.style.getPropertyValue(ACTIVE_PAINT) !== '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A value for each attribute state. The index signature gives StyleX the value
|
|
67
|
+
* type, which it cannot read from a literal attribute key.
|
|
68
|
+
*/
|
|
69
|
+
type ByAttribute<Value> = Readonly<{
|
|
70
|
+
default: null;
|
|
71
|
+
[attribute: string]: Value | null;
|
|
72
|
+
}>;
|
|
73
|
+
|
|
74
|
+
const litRowPaint: Readonly<{
|
|
75
|
+
'--_menu-row-focus-paint': ByAttribute<string>;
|
|
76
|
+
'--_menu-row-active-paint': ByAttribute<string>;
|
|
77
|
+
}> = {
|
|
78
|
+
'--_menu-row-focus-paint': {
|
|
79
|
+
default: null,
|
|
80
|
+
'[data-astryx-menu-press-lit]': colorVars['--color-overlay-pressed'],
|
|
81
|
+
},
|
|
82
|
+
'--_menu-row-active-paint': {
|
|
83
|
+
default: null,
|
|
84
|
+
'[data-astryx-menu-press-lit]': colorVars['--color-overlay-pressed'],
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const styles = stylex.create({
|
|
89
|
+
row: {...litRowPaint},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Compose on a menu row whose `:focus` background is
|
|
94
|
+
* `var(--_menu-row-focus-paint, <hover overlay>)`, and whose `:active`
|
|
95
|
+
* background, if any, is `var(--_menu-row-active-paint, <pressed overlay>)`.
|
|
96
|
+
* The row paints the hover overlay on focus, and the pressed overlay while a
|
|
97
|
+
* held finger or pen lights it.
|
|
98
|
+
* Under a finger, the hover overlay is too faint to follow. The variable has
|
|
99
|
+
* no default: a layered stylesheet emits a default unlayered, and it would
|
|
100
|
+
* beat the attribute arm. The value resolves on the row, so a theme set on any
|
|
101
|
+
* ancestor applies. The style sets only a custom property, which shares no
|
|
102
|
+
* key with StyleX's CSS property type, so TypeScript needs the wider type.
|
|
103
|
+
*/
|
|
104
|
+
export const menuPressLitRowStyle = styles.row as StyleXStyles;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/* eslint-disable @astryx/no-hover-on-disabled -- Every hover branch is
|
|
4
|
+
* nested beneath ENABLED below, as in interactionOverlay.stylex.ts. */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @file privateAttributes/press.stylex.ts
|
|
8
|
+
* @input StyleX, the semantic overlay color tokens, the touch press strength
|
|
9
|
+
* (`pressVars`, utils/interactionOverlay.stylex.ts) and the row layer's
|
|
10
|
+
* clocks (utils/rowPressLayer.stylex.ts)
|
|
11
|
+
* @output `rowPressLayerStyles`: the compositor press layer for list rows
|
|
12
|
+
* (`cell`) and its own-image arms for a row on the `backgroundImage`
|
|
13
|
+
* variant (`cellOnImage`)
|
|
14
|
+
* @position Owner module of the private press attribute for the row layer:
|
|
15
|
+
* the press controller (utils/pressFeedback.ts) writes the attribute, and
|
|
16
|
+
* this file reads it. No package entry exports it. Item and TreeList
|
|
17
|
+
* compose these styles after their overlay variant;
|
|
18
|
+
* utils/interactionOverlay.cell.test.ts tests them.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as stylex from '@stylexjs/stylex';
|
|
22
|
+
import {colorVars} from '../theme/tokens.stylex';
|
|
23
|
+
import {pressVars} from '../utils/interactionOverlay.stylex';
|
|
24
|
+
import {cellPressConsts} from '../utils/rowPressLayer.stylex';
|
|
25
|
+
|
|
26
|
+
// The overlay's arm selectors (interactionOverlay.stylex.ts), restated here:
|
|
27
|
+
// StyleX resolves a style's keys within its own file.
|
|
28
|
+
const ENABLED = ':where(:not(:disabled,[aria-disabled="true"]))';
|
|
29
|
+
const HOVER_HOVER = '@media (hover: hover)';
|
|
30
|
+
const COARSE = '@media (pointer: coarse)';
|
|
31
|
+
const PRESSED_ON = '[data-astryx-press="on"]';
|
|
32
|
+
const PRESSED_FADING = '[data-astryx-press="fading"]';
|
|
33
|
+
/**
|
|
34
|
+
* A press in any of its states: the row layer exists only then, so at rest a
|
|
35
|
+
* row's own `::before` and its stacking are untouched.
|
|
36
|
+
*/
|
|
37
|
+
const PRESSED_ANY =
|
|
38
|
+
':is([data-astryx-press="on"],[data-astryx-press="fading"])';
|
|
39
|
+
|
|
40
|
+
const PRESS_ALPHA = pressVars['--astryx-press-alpha'];
|
|
41
|
+
|
|
42
|
+
const hoverImage = `linear-gradient(${colorVars['--color-overlay-hover']}, ${colorVars['--color-overlay-hover']})`;
|
|
43
|
+
const pressedImage = `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`;
|
|
44
|
+
/** The overlay's paint, read back off the element (its gradient layer). */
|
|
45
|
+
const pressedOverlayImage = 'var(--_press-paint-image)';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The overlay's strength release (interactionOverlay.stylex.ts), restated so
|
|
49
|
+
* a fine pointer keeps it: the same keyframes, so the same animation.
|
|
50
|
+
*/
|
|
51
|
+
const pressRelease = stylex.keyframes({
|
|
52
|
+
from: {[PRESS_ALPHA]: 1},
|
|
53
|
+
to: {[PRESS_ALPHA]: 0},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The row layer's clock as CSS. It is the strength release's clock, scaled
|
|
58
|
+
* by the theme's `--_astryx-motion-tempo` up to 1.5, so the layer and the
|
|
59
|
+
* strength land together.
|
|
60
|
+
*/
|
|
61
|
+
const CELL_RELEASE = `calc(${cellPressConsts.cellReleaseMs}ms * min(var(--_astryx-motion-tempo, 1), 1.5))`;
|
|
62
|
+
/** The same clock under Reduce Motion: a tempo may shorten it, never lengthen it. */
|
|
63
|
+
const CELL_RELEASE_REDUCED = `min(${cellPressConsts.cellReleaseMs}ms, calc(${cellPressConsts.cellReleaseMs}ms * var(--_astryx-motion-tempo, 1)))`;
|
|
64
|
+
const REDUCED_MOTION = '@media (prefers-reduced-motion: reduce)';
|
|
65
|
+
|
|
66
|
+
/** Opacity 1 → 0: the row layer leaves; its fading arm runs it. */
|
|
67
|
+
const cellRelease = stylex.keyframes({
|
|
68
|
+
from: {opacity: 1},
|
|
69
|
+
to: {opacity: 0},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/** The release's curve: the table's gentle ease-out. */
|
|
73
|
+
const RELEASE_EASE = cellPressConsts.cellReleaseEase;
|
|
74
|
+
|
|
75
|
+
export const rowPressLayerStyles = stylex.create({
|
|
76
|
+
/**
|
|
77
|
+
* A list row's touch press on a compositor layer (Item, and TreeList's row
|
|
78
|
+
* box), composed AFTER the row's overlay variant (StyleX is last-wins per
|
|
79
|
+
* property). The variant's own touch paint, a gradient whose strength is a
|
|
80
|
+
* registered custom property, animates on the main thread: while script
|
|
81
|
+
* runs, a pressed row would stay at full strength and every frame of the
|
|
82
|
+
* fade would paint. Here the press is an element-sized `::before` under the
|
|
83
|
+
* row's content whose OPACITY is the strength: `on` shows it at once and
|
|
84
|
+
* `fading` runs a keyframe animation of its opacity, 1 → 0, on the cell's
|
|
85
|
+
* release clock (`timing.press`), which engines run on the compositor:
|
|
86
|
+
* it keeps advancing while the main thread is blocked and lays out and
|
|
87
|
+
* paints nothing. Same pixels as the variant's paint at that strength. The
|
|
88
|
+
* variant keeps the hover and a mouse's `:active`. The row must be
|
|
89
|
+
* positioned (the layer is placed absolutely); `isolation: isolate` keeps
|
|
90
|
+
* the layer above the row's background (a selected fill) and below its
|
|
91
|
+
* content. The layer exists only while a press is on or releasing: at rest
|
|
92
|
+
* a row's own `::before` and its stacking are as they were; during a press
|
|
93
|
+
* the layer is the row's `::before`, so a caller's decoration there merges
|
|
94
|
+
* into the layer for that time (decorate with `::after` or a child).
|
|
95
|
+
*
|
|
96
|
+
* Under a finger only (a coarse primary pointer): a fine pointer keeps the
|
|
97
|
+
* variant exactly, with no layer, no stacking context and its own touch
|
|
98
|
+
* paint, so a desktop row is as it was.
|
|
99
|
+
*/
|
|
100
|
+
cell: {
|
|
101
|
+
// The `backgroundColor` variant's own image arms (Item's), its touch
|
|
102
|
+
// paint yielding to the layer under a finger. Only the row's own image
|
|
103
|
+
// turns off: the press paint (`--_press-paint`) stays the variant's, so
|
|
104
|
+
// what a row's descendants paint from it still paints. A fine pointer
|
|
105
|
+
// keeps the arms exactly. `cellOnImage` is the same for the
|
|
106
|
+
// `backgroundImage` variant (TreeList's row box).
|
|
107
|
+
backgroundImage: {
|
|
108
|
+
default: null,
|
|
109
|
+
[ENABLED]: {
|
|
110
|
+
default: null,
|
|
111
|
+
[PRESSED_ON]: {default: pressedOverlayImage, [COARSE]: 'none'},
|
|
112
|
+
[PRESSED_FADING]: {default: pressedOverlayImage, [COARSE]: 'none'},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
// Under a finger the strength's own release (a main-thread animation)
|
|
116
|
+
// is dropped: the layer fades instead, on the compositor. A row whose
|
|
117
|
+
// descendant fades at the row's strength (a radio row's circle) brings
|
|
118
|
+
// it back with `pressedAlpha`. A fine pointer keeps it as the variant
|
|
119
|
+
// declares it (its clock and curve are untouched).
|
|
120
|
+
animationName: {
|
|
121
|
+
default: null,
|
|
122
|
+
[PRESSED_FADING]: {default: pressRelease, [COARSE]: 'none'},
|
|
123
|
+
},
|
|
124
|
+
// The stacking context the layer needs, for a press only.
|
|
125
|
+
isolation: {
|
|
126
|
+
default: null,
|
|
127
|
+
[COARSE]: {default: null, [PRESSED_ANY]: 'isolate'},
|
|
128
|
+
},
|
|
129
|
+
// The whole layer is under a finger only, and only while a press is on
|
|
130
|
+
// or releasing: with a fine pointer none of it applies, and at rest a
|
|
131
|
+
// row's own `::before` is exactly as it was.
|
|
132
|
+
'::before': {
|
|
133
|
+
content: {
|
|
134
|
+
default: null,
|
|
135
|
+
[COARSE]: {
|
|
136
|
+
default: null,
|
|
137
|
+
[ENABLED]: {default: null, [PRESSED_ANY]: '""'},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
position: {
|
|
141
|
+
default: null,
|
|
142
|
+
[COARSE]: {default: null, [PRESSED_ANY]: 'absolute'},
|
|
143
|
+
},
|
|
144
|
+
inset: {default: null, [COARSE]: {default: null, [PRESSED_ANY]: 0}},
|
|
145
|
+
zIndex: {default: null, [COARSE]: {default: null, [PRESSED_ANY]: -1}},
|
|
146
|
+
borderRadius: {
|
|
147
|
+
default: null,
|
|
148
|
+
[COARSE]: {default: null, [PRESSED_ANY]: 'inherit'},
|
|
149
|
+
},
|
|
150
|
+
pointerEvents: {
|
|
151
|
+
default: null,
|
|
152
|
+
[COARSE]: {default: null, [PRESSED_ANY]: 'none'},
|
|
153
|
+
},
|
|
154
|
+
backgroundColor: {
|
|
155
|
+
default: null,
|
|
156
|
+
[COARSE]: {
|
|
157
|
+
default: null,
|
|
158
|
+
[PRESSED_ANY]: colorVars['--color-overlay-pressed'],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
// On at once while the press is believed; nothing transitions it.
|
|
162
|
+
opacity: {
|
|
163
|
+
default: null,
|
|
164
|
+
[COARSE]: {default: null, [PRESSED_ON]: 1, [PRESSED_FADING]: 0},
|
|
165
|
+
},
|
|
166
|
+
animationName: {
|
|
167
|
+
default: null,
|
|
168
|
+
[COARSE]: {
|
|
169
|
+
default: null,
|
|
170
|
+
[ENABLED]: {default: null, [PRESSED_FADING]: cellRelease},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
animationDuration: {
|
|
174
|
+
default: null,
|
|
175
|
+
[COARSE]: {
|
|
176
|
+
default: null,
|
|
177
|
+
[PRESSED_FADING]: {
|
|
178
|
+
default: CELL_RELEASE,
|
|
179
|
+
[REDUCED_MOTION]: CELL_RELEASE_REDUCED,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
animationTimingFunction: {
|
|
184
|
+
default: null,
|
|
185
|
+
[COARSE]: {
|
|
186
|
+
default: null,
|
|
187
|
+
[PRESSED_FADING]: RELEASE_EASE,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
animationFillMode: {
|
|
191
|
+
default: null,
|
|
192
|
+
[COARSE]: {default: null, [PRESSED_FADING]: 'both'},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
/**
|
|
197
|
+
* A row that keeps no press paint of its own, composed after `cell`: a
|
|
198
|
+
* radio row, whose circle paints the press. The row paints no overlay
|
|
199
|
+
* image, keeps the strength's own release (the circle's layer reads the
|
|
200
|
+
* strength), and under a finger has no `::before` box and is no stacking
|
|
201
|
+
* context during a press.
|
|
202
|
+
*/
|
|
203
|
+
cellWithoutLayer: {
|
|
204
|
+
backgroundImage: 'none',
|
|
205
|
+
animationName: {default: null, [PRESSED_FADING]: pressRelease},
|
|
206
|
+
isolation: {
|
|
207
|
+
default: null,
|
|
208
|
+
[COARSE]: {default: null, [PRESSED_ANY]: 'auto'},
|
|
209
|
+
},
|
|
210
|
+
'::before': {
|
|
211
|
+
content: {
|
|
212
|
+
default: null,
|
|
213
|
+
[COARSE]: {
|
|
214
|
+
default: null,
|
|
215
|
+
[ENABLED]: {default: null, [PRESSED_ANY]: 'none'},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
/**
|
|
221
|
+
* `cell`'s own-image arms for a row on the `backgroundImage` variant
|
|
222
|
+
* (TreeList's row box), composed after `cell`: the variant's arms, its
|
|
223
|
+
* touch press paint yielding to the layer under a coarse primary pointer.
|
|
224
|
+
*/
|
|
225
|
+
cellOnImage: {
|
|
226
|
+
backgroundImage: {
|
|
227
|
+
default: null,
|
|
228
|
+
[ENABLED]: {
|
|
229
|
+
default: null,
|
|
230
|
+
':active': {
|
|
231
|
+
default: pressedImage,
|
|
232
|
+
[COARSE]: 'none',
|
|
233
|
+
},
|
|
234
|
+
[HOVER_HOVER]: {
|
|
235
|
+
default: null,
|
|
236
|
+
':hover': hoverImage,
|
|
237
|
+
':active': pressedImage,
|
|
238
|
+
},
|
|
239
|
+
[PRESSED_ON]: {default: pressedOverlayImage, [COARSE]: 'none'},
|
|
240
|
+
[PRESSED_FADING]: {default: pressedOverlayImage, [COARSE]: 'none'},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
});
|
|
@@ -24,7 +24,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
24
24
|
|
|
25
25
|
export const colorDefaults = {
|
|
26
26
|
// Core semantic
|
|
27
|
-
'--color-accent': 'light-dark(#0064E0, #
|
|
27
|
+
'--color-accent': 'light-dark(#0064E0, #0171E3)',
|
|
28
28
|
'--color-accent-muted': 'light-dark(#0082FB33, #0082FB3F)',
|
|
29
29
|
'--color-on-accent': 'light-dark(#FFFFFF, #FFFFFF)',
|
|
30
30
|
'--color-neutral':
|
package/src/utils/flatTree.ts
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file flatTree.ts
|
|
5
5
|
* @input A DOM node
|
|
6
|
-
* @output Exports parentAcrossShadow,
|
|
6
|
+
* @output Exports parentAcrossShadow, containsAlongFlatTree, which walks it,
|
|
7
|
+
* and deepActiveElement
|
|
7
8
|
* @position The flat-tree parent the browser scrolls and dispatches
|
|
8
|
-
* through, shared by the gesture host's native-scroll probe
|
|
9
|
+
* through, shared by the gesture host's native-scroll probe, the
|
|
9
10
|
* selection lock (which the press feedback controller uses without the
|
|
10
|
-
* gesture system)
|
|
11
|
+
* gesture system) and the Layer module's input hold
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
/** The node's parent, crossing out of shadow roots. */
|
|
@@ -38,3 +39,14 @@ export function containsAlongFlatTree(ancestor: Node, node: Node): boolean {
|
|
|
38
39
|
}
|
|
39
40
|
return false;
|
|
40
41
|
}
|
|
42
|
+
|
|
43
|
+
/** The focused element of `doc`, inside open shadow roots too. */
|
|
44
|
+
export function deepActiveElement(
|
|
45
|
+
doc: Document | null | undefined,
|
|
46
|
+
): Element | null {
|
|
47
|
+
let active = doc?.activeElement ?? null;
|
|
48
|
+
while (active?.shadowRoot?.activeElement != null) {
|
|
49
|
+
active = active.shadowRoot.activeElement;
|
|
50
|
+
}
|
|
51
|
+
return active;
|
|
52
|
+
}
|
package/src/utils/hitArea.ts
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
// live control measures: only under a coarse primary pointer (AST-094), only on
|
|
25
25
|
// screen, and in one read pass per frame within a 4 ms budget. Until the first
|
|
26
26
|
// pass the insets are 0, so first paint never overlaps anything.
|
|
27
|
+
// The insets are lengths in the control's own box, so a pass measures in its
|
|
28
|
+
// layout px. A layer that opens from a smaller scale then keeps the insets of
|
|
29
|
+
// the size it settles at.
|
|
27
30
|
|
|
28
31
|
import {observeContent} from './sharedMutationObserver';
|
|
29
32
|
|
|
@@ -330,6 +333,95 @@ function plainRect(element: Element): HitRect {
|
|
|
330
333
|
return {left, top, right, bottom};
|
|
331
334
|
}
|
|
332
335
|
|
|
336
|
+
/** A box that the transforms around it leave at its layout size. */
|
|
337
|
+
const UNSCALED = {x: 1, y: 1} as const;
|
|
338
|
+
|
|
339
|
+
/** One axis of a box's scale on screen: its client size over its layout size. */
|
|
340
|
+
function axisScale(onScreen: number, layout: number): number {
|
|
341
|
+
if (!(layout > 0) || !(onScreen > 0)) {
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
344
|
+
const scale = onScreen / layout;
|
|
345
|
+
// Within rounding of 1 the box is at its layout size: no change to the rects.
|
|
346
|
+
return Math.abs(scale - 1) < 1e-3 ? 1 : scale;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* How far the transforms around `element` scale its box on screen, per axis.
|
|
351
|
+
* A layer that opens from a smaller scale scales every rect inside it, while
|
|
352
|
+
* the layout box keeps the size the layer settles at. Only a scale is
|
|
353
|
+
* expected: a rotated or skewed box would read as a larger scale.
|
|
354
|
+
*/
|
|
355
|
+
function screenScale(
|
|
356
|
+
element: Element,
|
|
357
|
+
onScreen: HitRect,
|
|
358
|
+
): {x: number; y: number} {
|
|
359
|
+
// The offset size is the layout size in whole pixels, unaffected by
|
|
360
|
+
// transforms, and costs no style resolution. A box with none, such as an SVG
|
|
361
|
+
// element or a jsdom box, reads as unscaled.
|
|
362
|
+
const box = element as Partial<HTMLElement>;
|
|
363
|
+
const offsetWidth = box.offsetWidth ?? 0;
|
|
364
|
+
const offsetHeight = box.offsetHeight ?? 0;
|
|
365
|
+
if (offsetWidth === 0 || offsetHeight === 0) {
|
|
366
|
+
return UNSCALED;
|
|
367
|
+
}
|
|
368
|
+
// Within a pixel of its layout size the box reads as unscaled, which bounds
|
|
369
|
+
// the inset error at about a pixel. Only a box that a transform shrinks or
|
|
370
|
+
// grows reads its exact size from the computed style.
|
|
371
|
+
if (
|
|
372
|
+
Math.abs(onScreen.right - onScreen.left - offsetWidth) <= 1 &&
|
|
373
|
+
Math.abs(onScreen.bottom - onScreen.top - offsetHeight) <= 1
|
|
374
|
+
) {
|
|
375
|
+
return UNSCALED;
|
|
376
|
+
}
|
|
377
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
378
|
+
if (style == null || !style.width.endsWith('px')) {
|
|
379
|
+
return UNSCALED;
|
|
380
|
+
}
|
|
381
|
+
const px = (value: string) => Number.parseFloat(value) || 0;
|
|
382
|
+
const isContentBox = style.boxSizing !== 'border-box';
|
|
383
|
+
const width =
|
|
384
|
+
px(style.width) +
|
|
385
|
+
(isContentBox
|
|
386
|
+
? px(style.paddingLeft) +
|
|
387
|
+
px(style.paddingRight) +
|
|
388
|
+
px(style.borderLeftWidth) +
|
|
389
|
+
px(style.borderRightWidth)
|
|
390
|
+
: 0);
|
|
391
|
+
const height =
|
|
392
|
+
px(style.height) +
|
|
393
|
+
(isContentBox
|
|
394
|
+
? px(style.paddingTop) +
|
|
395
|
+
px(style.paddingBottom) +
|
|
396
|
+
px(style.borderTopWidth) +
|
|
397
|
+
px(style.borderBottomWidth)
|
|
398
|
+
: 0);
|
|
399
|
+
return {
|
|
400
|
+
x: axisScale(onScreen.right - onScreen.left, width),
|
|
401
|
+
y: axisScale(onScreen.bottom - onScreen.top, height),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* `rect` in the layout px of the box at `origin`: its offset from `origin`,
|
|
407
|
+
* divided by that box's scale on screen.
|
|
408
|
+
*/
|
|
409
|
+
function inLayout(
|
|
410
|
+
rect: HitRect,
|
|
411
|
+
origin: HitRect,
|
|
412
|
+
scale: {x: number; y: number},
|
|
413
|
+
): HitRect {
|
|
414
|
+
if (scale.x === 1 && scale.y === 1) {
|
|
415
|
+
return rect;
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
left: origin.left + (rect.left - origin.left) / scale.x,
|
|
419
|
+
top: origin.top + (rect.top - origin.top) / scale.y,
|
|
420
|
+
right: origin.left + (rect.right - origin.left) / scale.x,
|
|
421
|
+
bottom: origin.top + (rect.bottom - origin.top) / scale.y,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
333
425
|
interface Candidate {
|
|
334
426
|
readonly element: Element;
|
|
335
427
|
readonly rect: HitRect;
|
|
@@ -373,14 +465,15 @@ function measureOne(
|
|
|
373
465
|
candidates: ReadonlyArray<Candidate>,
|
|
374
466
|
): {target: HitRect; insets: HitInsets; neighbours: HitRect[]} {
|
|
375
467
|
const target = plainRect(watch.element);
|
|
468
|
+
const scale = screenScale(watch.element, target);
|
|
376
469
|
// Two targets each growing up to half the minimum reach each other from a
|
|
377
470
|
// minimum apart.
|
|
378
471
|
const reach = Math.max(0, watch.minSize);
|
|
379
472
|
const band = grown(target, {
|
|
380
|
-
top: reach,
|
|
381
|
-
right: reach,
|
|
382
|
-
bottom: reach,
|
|
383
|
-
left: reach,
|
|
473
|
+
top: reach * scale.y,
|
|
474
|
+
right: reach * scale.x,
|
|
475
|
+
bottom: reach * scale.y,
|
|
476
|
+
left: reach * scale.x,
|
|
384
477
|
});
|
|
385
478
|
const neighbours = candidates
|
|
386
479
|
.filter(
|
|
@@ -393,7 +486,16 @@ function measureOne(
|
|
|
393
486
|
.map(({rect}) => rect);
|
|
394
487
|
return {
|
|
395
488
|
target,
|
|
396
|
-
insets
|
|
489
|
+
// The insets are in the control's layout px. The returned rects stay in
|
|
490
|
+
// screen px for a touch's distance checks. A neighbour maps by the
|
|
491
|
+
// control's scale, as if it is in the same layer. A neighbour outside that
|
|
492
|
+
// layer then reads farther away. A touch compares screen distances, so it
|
|
493
|
+
// still counts that neighbour.
|
|
494
|
+
insets: clampedHitInsets(
|
|
495
|
+
inLayout(target, target, scale),
|
|
496
|
+
neighbours.map(rect => inLayout(rect, target, scale)),
|
|
497
|
+
watch.minSize,
|
|
498
|
+
),
|
|
397
499
|
neighbours,
|
|
398
500
|
};
|
|
399
501
|
}
|
package/src/utils/inputStream.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @file inputStream.ts
|
|
5
5
|
* @input A window, and subscribers to its input events (the press feedback
|
|
6
6
|
* controller, the gesture host)
|
|
7
|
-
* @output subscribeInput
|
|
7
|
+
* @output subscribeInput, and getLastPointerType: the type of the latest
|
|
8
|
+
* pointer event the stream delivered
|
|
8
9
|
* @position The one entry for document-wide input listeners in core: one
|
|
9
10
|
* native listener per event type on the window's capture phase, shared by
|
|
10
11
|
* every subscriber
|
|
@@ -22,18 +23,21 @@
|
|
|
22
23
|
// `blur` or `focus`. A subscriber for the window's own event checks
|
|
23
24
|
// `event.target`.
|
|
24
25
|
//
|
|
25
|
-
// Subscribers run in subscription order
|
|
26
|
-
// the
|
|
26
|
+
// Subscribers run in subscription order, after the ones subscribed `first`.
|
|
27
|
+
// A first subscriber that returns `true` ends the dispatch: no later
|
|
28
|
+
// subscriber hears the event. One that throws is reported, and the rest still
|
|
29
|
+
// run.
|
|
27
30
|
//
|
|
28
31
|
// A type's native listener is passive, so it blocks no scroll. It is
|
|
29
32
|
// non-passive only while a subscriber that cancels the event
|
|
30
33
|
// (`{cancels: true}`) is subscribed. With no subscriber left, it is removed.
|
|
31
34
|
|
|
32
|
-
type InputListener = (event: Event) =>
|
|
35
|
+
type InputListener = (event: Event) => unknown;
|
|
33
36
|
|
|
34
37
|
interface Subscriber {
|
|
35
38
|
readonly listener: InputListener;
|
|
36
39
|
readonly cancels: boolean;
|
|
40
|
+
readonly isFirst: boolean;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
interface Channel {
|
|
@@ -50,12 +54,30 @@ export interface InputSubscriptionOptions {
|
|
|
50
54
|
* @default false
|
|
51
55
|
*/
|
|
52
56
|
readonly cancels?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The subscriber runs before every subscriber that is not first. When it
|
|
59
|
+
* returns `true`, no later subscriber hears the event.
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
readonly first?: boolean;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/** The events a window's capture phase sees: its own, and the document's. */
|
|
56
66
|
export type InputEventMap = WindowEventMap & DocumentEventMap;
|
|
57
67
|
|
|
58
68
|
const streams = new WeakMap<Window, Map<string, Channel>>();
|
|
69
|
+
/** The type of the latest pointer event each window's stream delivered. */
|
|
70
|
+
const lastPointerTypes = new WeakMap<Window, string>();
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The type of the latest pointer event `view`'s stream delivered: `mouse`,
|
|
74
|
+
* `pen` or `touch`, or `null` before any. The stream hears a pointer event
|
|
75
|
+
* type only while a subscriber listens to it, as press feedback does on any
|
|
76
|
+
* page with a pressable. Reading it adds no listener.
|
|
77
|
+
*/
|
|
78
|
+
export function getLastPointerType(view: Window): string | null {
|
|
79
|
+
return lastPointerTypes.get(view) ?? null;
|
|
80
|
+
}
|
|
59
81
|
|
|
60
82
|
/**
|
|
61
83
|
* Listen to `type` on `view`'s capture phase through its shared native
|
|
@@ -64,8 +86,8 @@ const streams = new WeakMap<Window, Map<string, Channel>>();
|
|
|
64
86
|
export function subscribeInput<K extends keyof InputEventMap>(
|
|
65
87
|
view: Window,
|
|
66
88
|
type: K,
|
|
67
|
-
listener: (event: InputEventMap[K]) =>
|
|
68
|
-
{cancels = false}: InputSubscriptionOptions = {},
|
|
89
|
+
listener: (event: InputEventMap[K]) => unknown,
|
|
90
|
+
{cancels = false, first = false}: InputSubscriptionOptions = {},
|
|
69
91
|
): () => void {
|
|
70
92
|
let channels = streams.get(view);
|
|
71
93
|
if (channels == null) {
|
|
@@ -80,8 +102,16 @@ export function subscribeInput<K extends keyof InputEventMap>(
|
|
|
80
102
|
const subscriber: Subscriber = {
|
|
81
103
|
listener: listener as InputListener,
|
|
82
104
|
cancels,
|
|
105
|
+
isFirst: first,
|
|
83
106
|
};
|
|
84
|
-
|
|
107
|
+
const firstLater = first
|
|
108
|
+
? channel.subscribers.findIndex(other => !other.isFirst)
|
|
109
|
+
: -1;
|
|
110
|
+
if (firstLater === -1) {
|
|
111
|
+
channel.subscribers.push(subscriber);
|
|
112
|
+
} else {
|
|
113
|
+
channel.subscribers.splice(firstLater, 0, subscriber);
|
|
114
|
+
}
|
|
85
115
|
register(view, type, channel);
|
|
86
116
|
let isSubscribed = true;
|
|
87
117
|
return () => {
|
|
@@ -124,12 +154,17 @@ function register(view: Window, type: string, channel: Channel): void {
|
|
|
124
154
|
if (wanted == null) {
|
|
125
155
|
return;
|
|
126
156
|
}
|
|
127
|
-
const listener: InputListener = event => dispatch(channel, event);
|
|
157
|
+
const listener: InputListener = event => dispatch(view, channel, event);
|
|
128
158
|
view.addEventListener(type, listener, {capture: true, passive: !wanted});
|
|
129
159
|
channel.native = {listener, cancels: wanted};
|
|
130
160
|
}
|
|
131
161
|
|
|
132
|
-
function dispatch(channel: Channel, event: Event): void {
|
|
162
|
+
function dispatch(view: Window, channel: Channel, event: Event): void {
|
|
163
|
+
// Recorded before any subscriber runs, so each reads the pointer at hand.
|
|
164
|
+
const {pointerType} = event as Partial<PointerEvent>;
|
|
165
|
+
if (typeof pointerType === 'string' && pointerType !== '') {
|
|
166
|
+
lastPointerTypes.set(view, pointerType);
|
|
167
|
+
}
|
|
133
168
|
// A subscriber may subscribe or unsubscribe while the event runs; the
|
|
134
169
|
// event goes to those subscribed when it arrived.
|
|
135
170
|
for (const subscriber of [...channel.subscribers]) {
|
|
@@ -137,7 +172,9 @@ function dispatch(channel: Channel, event: Event): void {
|
|
|
137
172
|
continue;
|
|
138
173
|
}
|
|
139
174
|
try {
|
|
140
|
-
subscriber.listener(event)
|
|
175
|
+
if (subscriber.listener(event) === true && subscriber.isFirst) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
141
178
|
} catch (error) {
|
|
142
179
|
report(error);
|
|
143
180
|
}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* @input StyleX and the semantic interaction-overlay color tokens
|
|
10
10
|
* @output Reusable background-color and background-image state styles, the
|
|
11
11
|
* touch press's registered strength variable (`pressVars`) and its clocks
|
|
12
|
-
* (`pressConsts`)
|
|
12
|
+
* (`pressConsts`). The list row's compositor press layer and its clocks
|
|
13
|
+
* are internal (privateAttributes/press.stylex.ts, utils/rowPressLayer.stylex.ts)
|
|
13
14
|
* @position Internal styling utility for interactive core surfaces
|
|
14
15
|
*
|
|
15
16
|
* SYNC: When modified, update pressFeedback.ts, pressGesture.ts (the fade
|
|
@@ -148,7 +149,11 @@ const pressRelease = stylex.keyframes({
|
|
|
148
149
|
* (strength ≈ 0.2 at the midpoint), which is what a deselect crossfade reads
|
|
149
150
|
* as. Not the system's `--ease-standard`: that curve is for things that move,
|
|
150
151
|
* and is so front-loaded (strength 0.05 at the midpoint) that the release
|
|
151
|
-
* would read as half its clock.
|
|
152
|
+
* would read as half its clock. It is the constants table's
|
|
153
|
+
* `timing.press.highlightOutCurve`, which the list row's layer mirrors too
|
|
154
|
+
* (rowPressLayer.stylex.ts); interactionOverlay.cell.test.ts fails if either
|
|
155
|
+
* drifts. A copy, not an import: this module reaches nearly every component,
|
|
156
|
+
* and the row layer's constants stay with the rows.
|
|
152
157
|
*/
|
|
153
158
|
const RELEASE_EASE = 'cubic-bezier(0, 0, 0.2, 1)';
|
|
154
159
|
|