@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.
Files changed (213) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  2. package/dist/BottomSheet/BottomSheet.js +5 -0
  3. package/dist/Calendar/styles.d.ts +1 -1
  4. package/dist/ClickableCard/ClickableCard.d.ts +1 -1
  5. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  6. package/dist/ClickableCard/ClickableCard.js +5 -1
  7. package/dist/DateInput/PickerWheel.d.ts.map +1 -1
  8. package/dist/DateInput/PickerWheel.js +7 -1
  9. package/dist/DateRangeInput/DateRangeInput.js +1 -1
  10. package/dist/Dialog/Dialog.d.ts +2 -1
  11. package/dist/Dialog/Dialog.d.ts.map +1 -1
  12. package/dist/Dialog/Dialog.js +12 -1
  13. package/dist/Drawer/useDrawerDialogPresence.d.ts +2 -1
  14. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  15. package/dist/Drawer/useDrawerDialogPresence.js +11 -2
  16. package/dist/DropdownMenu/DropdownMenu.d.ts +9 -1
  17. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  18. package/dist/DropdownMenu/DropdownMenu.js +9 -4
  19. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  20. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +3 -2
  21. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  22. package/dist/DropdownMenu/DropdownMenuItem.js +3 -2
  23. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  24. package/dist/DropdownMenu/DropdownMenuRadioItem.js +3 -2
  25. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  26. package/dist/DropdownMenu/DropdownMenuSubMenu.js +3 -2
  27. package/dist/Field/inputDisabledFocus.d.ts +4 -4
  28. package/dist/Field/inputStyles.stylex.d.ts +1 -1
  29. package/dist/FileInput/FileInput.d.ts.map +1 -1
  30. package/dist/FileInput/FileInput.js +16 -2
  31. package/dist/Icon/Icon.d.ts +1 -1
  32. package/dist/InfoTip/InfoTip.d.ts +1 -0
  33. package/dist/InfoTip/InfoTip.d.ts.map +1 -1
  34. package/dist/InfoTip/InfoTip.js +1 -0
  35. package/dist/Item/Item.d.ts +2 -1
  36. package/dist/Item/Item.d.ts.map +1 -1
  37. package/dist/Item/Item.js +29 -6
  38. package/dist/Layer/holdEntranceInput.d.ts +24 -0
  39. package/dist/Layer/holdEntranceInput.d.ts.map +1 -0
  40. package/dist/Layer/holdEntranceInput.js +65 -0
  41. package/dist/Layer/inputHold.d.ts +49 -0
  42. package/dist/Layer/inputHold.d.ts.map +1 -0
  43. package/dist/Layer/inputHold.js +636 -0
  44. package/dist/Layer/preserveAnimations.d.ts +1 -5
  45. package/dist/Layer/preserveAnimations.d.ts.map +1 -1
  46. package/dist/Layer/preserveAnimations.js +12 -214
  47. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  48. package/dist/Lightbox/Lightbox.js +4 -0
  49. package/dist/MetadataList/MetadataList.js +1 -1
  50. package/dist/MobileNav/MobileNav.d.ts +11 -1
  51. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  52. package/dist/MobileNav/MobileNav.js +34 -1
  53. package/dist/MoreMenu/MoreMenu.d.ts +5 -3
  54. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  55. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  56. package/dist/RadioList/RadioListItem.js +4 -1
  57. package/dist/SelectableCard/SelectableCard.d.ts +1 -1
  58. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  59. package/dist/SelectableCard/SelectableCard.js +5 -1
  60. package/dist/Spinner/Spinner.d.ts.map +1 -1
  61. package/dist/Spinner/Spinner.js +12 -2
  62. package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
  63. package/dist/Token/Token.d.ts.map +1 -1
  64. package/dist/Token/Token.js +9 -0
  65. package/dist/Token/TokenLink.d.ts +1 -1
  66. package/dist/Token/TokenLink.d.ts.map +1 -1
  67. package/dist/Token/TokenLink.js +4 -1
  68. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  69. package/dist/Tooltip/useTooltip.js +162 -59
  70. package/dist/TreeList/TreeListItem.d.ts +2 -1
  71. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  72. package/dist/TreeList/TreeListItem.js +27 -3
  73. package/dist/astryx.css +30 -7
  74. package/dist/gestures/GestureHost.d.ts +2 -2
  75. package/dist/gestures/GestureHost.d.ts.map +1 -1
  76. package/dist/gestures/GestureHost.js +21 -6
  77. package/dist/gestures/nativeScroll.d.ts +3 -6
  78. package/dist/gestures/nativeScroll.d.ts.map +1 -1
  79. package/dist/gestures/nativeScroll.js +7 -52
  80. package/dist/gestures/wheelSegmenter.d.ts +68 -1
  81. package/dist/gestures/wheelSegmenter.d.ts.map +1 -1
  82. package/dist/gestures/wheelSegmenter.js +199 -10
  83. package/dist/hooks/menuPressGesture.d.ts +133 -21
  84. package/dist/hooks/menuPressGesture.d.ts.map +1 -1
  85. package/dist/hooks/menuPressGesture.js +155 -30
  86. package/dist/hooks/menuPressValues.d.ts +16 -0
  87. package/dist/hooks/menuPressValues.d.ts.map +1 -0
  88. package/dist/hooks/menuPressValues.js +18 -0
  89. package/dist/hooks/useClickableContainer.d.ts +27 -5
  90. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  91. package/dist/hooks/useClickableContainer.js +41 -5
  92. package/dist/hooks/useFocusTrap.d.ts +10 -0
  93. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  94. package/dist/hooks/useFocusTrap.js +225 -132
  95. package/dist/hooks/useMenuPress.d.ts +34 -1
  96. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  97. package/dist/hooks/useMenuPress.js +93 -5
  98. package/dist/hooks/usePressFeedback.d.ts +6 -6
  99. package/dist/hooks/usePressFeedback.js +6 -6
  100. package/dist/motion/carryAnimations.d.ts +58 -0
  101. package/dist/motion/carryAnimations.d.ts.map +1 -0
  102. package/dist/motion/carryAnimations.js +305 -0
  103. package/dist/motion/transientCopy.d.ts +4 -2
  104. package/dist/motion/transientCopy.d.ts.map +1 -1
  105. package/dist/motion/transientCopy.js +34 -5
  106. package/dist/motion/uikit/gestures.d.ts +41 -0
  107. package/dist/motion/uikit/gestures.d.ts.map +1 -1
  108. package/dist/motion/uikit/gestures.js +25 -0
  109. package/dist/motion/uikit/index.d.ts +1 -1
  110. package/dist/motion/uikit/index.d.ts.map +1 -1
  111. package/dist/motion/uikit/index.js +1 -1
  112. package/dist/privateAttributes/menuPressLit.stylex.d.ts +52 -0
  113. package/dist/privateAttributes/menuPressLit.stylex.d.ts.map +1 -0
  114. package/dist/privateAttributes/menuPressLit.stylex.js +99 -0
  115. package/dist/privateAttributes/press.stylex.d.ts +139 -0
  116. package/dist/privateAttributes/press.stylex.d.ts.map +1 -0
  117. package/dist/privateAttributes/press.stylex.js +98 -0
  118. package/dist/theme/tokens.stylex.d.ts +2 -2
  119. package/dist/theme/tokens.stylex.js +1 -1
  120. package/dist/utils/flatTree.d.ts +6 -3
  121. package/dist/utils/flatTree.d.ts.map +1 -1
  122. package/dist/utils/flatTree.js +13 -3
  123. package/dist/utils/hitArea.d.ts.map +1 -1
  124. package/dist/utils/hitArea.js +82 -5
  125. package/dist/utils/inputStream.d.ts +14 -1
  126. package/dist/utils/inputStream.d.ts.map +1 -1
  127. package/dist/utils/inputStream.js +40 -9
  128. package/dist/utils/interactionOverlay.stylex.d.ts +2 -1
  129. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  130. package/dist/utils/interactionOverlay.stylex.js +7 -2
  131. package/dist/utils/pressFeedback.d.ts +7 -0
  132. package/dist/utils/pressFeedback.d.ts.map +1 -1
  133. package/dist/utils/pressFeedback.js +32 -7
  134. package/dist/utils/pressGesture.d.ts +6 -0
  135. package/dist/utils/pressGesture.d.ts.map +1 -1
  136. package/dist/utils/pressGesture.js +17 -0
  137. package/dist/utils/rowPressLayer.stylex.d.ts +14 -0
  138. package/dist/utils/rowPressLayer.stylex.d.ts.map +1 -0
  139. package/dist/utils/rowPressLayer.stylex.js +25 -0
  140. package/dist/utils/scrollView.d.ts +25 -0
  141. package/dist/utils/scrollView.d.ts.map +1 -0
  142. package/dist/utils/scrollView.js +108 -0
  143. package/dist/utils/selectionLock.d.ts.map +1 -1
  144. package/dist/utils/selectionLock.js +1 -10
  145. package/dist/utils/triggerFocus.d.ts +5 -0
  146. package/dist/utils/triggerFocus.d.ts.map +1 -1
  147. package/dist/utils/triggerFocus.js +9 -2
  148. package/package.json +5 -5
  149. package/src/BottomSheet/BottomSheet.tsx +5 -0
  150. package/src/ClickableCard/ClickableCard.tsx +8 -1
  151. package/src/DateInput/PickerWheel.tsx +7 -2
  152. package/src/DateRangeInput/DateRangeInput.tsx +1 -1
  153. package/src/Dialog/Dialog.doc.mjs +1 -1
  154. package/src/Dialog/Dialog.tsx +12 -1
  155. package/src/Drawer/Drawer.doc.mjs +1 -1
  156. package/src/Drawer/useDrawerDialogPresence.ts +9 -2
  157. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -2
  158. package/src/DropdownMenu/DropdownMenu.tsx +29 -5
  159. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -1
  160. package/src/DropdownMenu/DropdownMenuItem.tsx +8 -4
  161. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -1
  162. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -1
  163. package/src/FileInput/FileInput.tsx +16 -2
  164. package/src/InfoTip/InfoTip.doc.mjs +26 -2
  165. package/src/InfoTip/InfoTip.tsx +1 -0
  166. package/src/Item/Item.tsx +51 -9
  167. package/src/Layer/holdEntranceInput.ts +84 -0
  168. package/src/Layer/inputHold.ts +734 -0
  169. package/src/Layer/preserveAnimations.ts +12 -275
  170. package/src/Lightbox/Lightbox.tsx +4 -0
  171. package/src/MetadataList/MetadataList.tsx +1 -1
  172. package/src/MobileNav/MobileNav.doc.mjs +3 -3
  173. package/src/MobileNav/MobileNav.tsx +34 -1
  174. package/src/MoreMenu/MoreMenu.doc.mjs +2 -4
  175. package/src/MoreMenu/MoreMenu.tsx +5 -3
  176. package/src/RadioList/RadioListItem.tsx +4 -5
  177. package/src/SelectableCard/SelectableCard.tsx +8 -1
  178. package/src/Spinner/Spinner.tsx +13 -3
  179. package/src/Table/plugins/sortable/useTableSortable.tsx +1 -1
  180. package/src/Token/Token.tsx +18 -1
  181. package/src/Token/TokenLink.tsx +7 -1
  182. package/src/Tooltip/useTooltip.tsx +175 -79
  183. package/src/TreeList/TreeListItem.tsx +36 -2
  184. package/src/gestures/GestureHost.ts +21 -5
  185. package/src/gestures/README.md +12 -4
  186. package/src/gestures/nativeScroll.ts +12 -68
  187. package/src/gestures/wheelSegmenter.ts +243 -10
  188. package/src/hooks/menuPressGesture.ts +244 -38
  189. package/src/hooks/menuPressValues.ts +18 -0
  190. package/src/hooks/useClickableContainer.doc.mjs +8 -2
  191. package/src/hooks/useClickableContainer.ts +72 -6
  192. package/src/hooks/useFocusTrap.ts +268 -151
  193. package/src/hooks/useMenuPress.ts +118 -11
  194. package/src/hooks/usePressFeedback.doc.mjs +3 -3
  195. package/src/hooks/usePressFeedback.ts +6 -6
  196. package/src/motion/MEASUREMENTS.md +11 -0
  197. package/src/motion/carryAnimations.ts +389 -0
  198. package/src/motion/transientCopy.ts +45 -5
  199. package/src/motion/uikit/gestures.ts +22 -0
  200. package/src/motion/uikit/index.ts +1 -0
  201. package/src/privateAttributes/menuPressLit.stylex.ts +104 -0
  202. package/src/privateAttributes/press.stylex.ts +244 -0
  203. package/src/theme/tokens.stylex.ts +1 -1
  204. package/src/utils/flatTree.ts +15 -3
  205. package/src/utils/hitArea.ts +107 -5
  206. package/src/utils/inputStream.ts +47 -10
  207. package/src/utils/interactionOverlay.stylex.ts +7 -2
  208. package/src/utils/pressFeedback.ts +31 -6
  209. package/src/utils/pressGesture.ts +24 -1
  210. package/src/utils/rowPressLayer.stylex.ts +27 -0
  211. package/src/utils/scrollView.ts +135 -0
  212. package/src/utils/selectionLock.ts +5 -10
  213. 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, #2694FE)',
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':
@@ -3,11 +3,12 @@
3
3
  /**
4
4
  * @file flatTree.ts
5
5
  * @input A DOM node
6
- * @output Exports parentAcrossShadow, and containsAlongFlatTree, which walks it
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 and the
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
+ }
@@ -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: clampedHitInsets(target, neighbours, watch.minSize),
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
  }
@@ -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. One that throws is reported, and
26
- // the rest still run.
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) => void;
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]) => void,
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
- channel.subscribers.push(subscriber);
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