@astryxdesign/core 0.7.8 → 0.7.9-canary.0a64fdb

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 (276) hide show
  1. package/dist/AppShell/observeMainOverflow.d.ts.map +1 -1
  2. package/dist/AppShell/observeMainOverflow.js +9 -6
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +5 -0
  5. package/dist/Calendar/styles.d.ts +1 -1
  6. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  7. package/dist/CheckboxInput/CheckboxInput.js +54 -11
  8. package/dist/ClickableCard/ClickableCard.d.ts +1 -1
  9. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  10. package/dist/ClickableCard/ClickableCard.js +5 -1
  11. package/dist/DateInput/PickerWheel.d.ts.map +1 -1
  12. package/dist/DateInput/PickerWheel.js +7 -1
  13. package/dist/DateRangeInput/DateRangeInput.js +1 -1
  14. package/dist/Dialog/Dialog.d.ts +2 -1
  15. package/dist/Dialog/Dialog.d.ts.map +1 -1
  16. package/dist/Dialog/Dialog.js +12 -1
  17. package/dist/Drawer/useDrawerDialogPresence.d.ts +2 -1
  18. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  19. package/dist/Drawer/useDrawerDialogPresence.js +11 -2
  20. package/dist/DropdownMenu/DropdownMenu.d.ts +9 -1
  21. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  22. package/dist/DropdownMenu/DropdownMenu.js +9 -4
  23. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  24. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +3 -2
  25. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  26. package/dist/DropdownMenu/DropdownMenuItem.js +3 -2
  27. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  28. package/dist/DropdownMenu/DropdownMenuRadioItem.js +3 -2
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.js +3 -2
  31. package/dist/Field/inputDisabledFocus.d.ts +4 -4
  32. package/dist/Field/inputStyles.stylex.d.ts +1 -1
  33. package/dist/FileInput/FileInput.d.ts.map +1 -1
  34. package/dist/FileInput/FileInput.js +16 -2
  35. package/dist/Icon/Icon.d.ts +1 -1
  36. package/dist/InfoTip/InfoTip.d.ts +1 -0
  37. package/dist/InfoTip/InfoTip.d.ts.map +1 -1
  38. package/dist/InfoTip/InfoTip.js +1 -0
  39. package/dist/InputGroup/InputGroup.d.ts +4 -3
  40. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  41. package/dist/InputGroup/InputGroup.js +2 -2
  42. package/dist/InputGroup/groupStyles.d.ts +1 -1
  43. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  44. package/dist/InputGroup/groupStyles.js +1 -1
  45. package/dist/Item/Item.d.ts +2 -1
  46. package/dist/Item/Item.d.ts.map +1 -1
  47. package/dist/Item/Item.js +29 -6
  48. package/dist/Layer/holdEntranceInput.d.ts +24 -0
  49. package/dist/Layer/holdEntranceInput.d.ts.map +1 -0
  50. package/dist/Layer/holdEntranceInput.js +65 -0
  51. package/dist/Layer/inputHold.d.ts +49 -0
  52. package/dist/Layer/inputHold.d.ts.map +1 -0
  53. package/dist/Layer/inputHold.js +636 -0
  54. package/dist/Layer/preserveAnimations.d.ts +1 -5
  55. package/dist/Layer/preserveAnimations.d.ts.map +1 -1
  56. package/dist/Layer/preserveAnimations.js +12 -214
  57. package/dist/Layout/LayoutContent.d.ts +3 -1
  58. package/dist/Layout/LayoutContent.d.ts.map +1 -1
  59. package/dist/Layout/LayoutContent.js +12 -4
  60. package/dist/Layout/LayoutPanel.d.ts +4 -1
  61. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  62. package/dist/Layout/LayoutPanel.js +13 -4
  63. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  64. package/dist/Lightbox/Lightbox.js +4 -0
  65. package/dist/MetadataList/MetadataList.js +1 -1
  66. package/dist/MobileNav/MobileNav.d.ts +11 -1
  67. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  68. package/dist/MobileNav/MobileNav.js +39 -1
  69. package/dist/MoreMenu/MoreMenu.d.ts +5 -3
  70. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  71. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  72. package/dist/RadioList/RadioListItem.js +22 -4
  73. package/dist/SelectableCard/SelectableCard.d.ts +1 -1
  74. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  75. package/dist/SelectableCard/SelectableCard.js +5 -1
  76. package/dist/Spinner/Spinner.d.ts.map +1 -1
  77. package/dist/Spinner/Spinner.js +12 -2
  78. package/dist/Stack/StackItem.d.ts +8 -4
  79. package/dist/Stack/StackItem.d.ts.map +1 -1
  80. package/dist/Stack/StackItem.js +17 -4
  81. package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
  82. package/dist/Token/Token.d.ts.map +1 -1
  83. package/dist/Token/Token.js +9 -0
  84. package/dist/Token/TokenLink.d.ts +1 -1
  85. package/dist/Token/TokenLink.d.ts.map +1 -1
  86. package/dist/Token/TokenLink.js +4 -1
  87. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  88. package/dist/Tooltip/useTooltip.js +162 -59
  89. package/dist/TreeList/TreeListItem.d.ts +2 -1
  90. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  91. package/dist/TreeList/TreeListItem.js +27 -3
  92. package/dist/astryx.css +35 -8
  93. package/dist/gestures/GestureHost.d.ts +2 -2
  94. package/dist/gestures/GestureHost.d.ts.map +1 -1
  95. package/dist/gestures/GestureHost.js +21 -6
  96. package/dist/gestures/PanGestureRecognizer.d.ts +11 -1
  97. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  98. package/dist/gestures/PanGestureRecognizer.js +15 -1
  99. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +6 -1
  100. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
  101. package/dist/gestures/nativeScroll.d.ts +3 -6
  102. package/dist/gestures/nativeScroll.d.ts.map +1 -1
  103. package/dist/gestures/nativeScroll.js +7 -52
  104. package/dist/gestures/wheelSegmenter.d.ts +68 -1
  105. package/dist/gestures/wheelSegmenter.d.ts.map +1 -1
  106. package/dist/gestures/wheelSegmenter.js +199 -10
  107. package/dist/hooks/StackItemScrollRegion.d.ts +31 -0
  108. package/dist/hooks/StackItemScrollRegion.d.ts.map +1 -0
  109. package/dist/hooks/StackItemScrollRegion.js +40 -0
  110. package/dist/hooks/menuPressGesture.d.ts +133 -21
  111. package/dist/hooks/menuPressGesture.d.ts.map +1 -1
  112. package/dist/hooks/menuPressGesture.js +155 -30
  113. package/dist/hooks/menuPressValues.d.ts +16 -0
  114. package/dist/hooks/menuPressValues.d.ts.map +1 -0
  115. package/dist/hooks/menuPressValues.js +18 -0
  116. package/dist/hooks/useClickableContainer.d.ts +27 -5
  117. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  118. package/dist/hooks/useClickableContainer.js +41 -5
  119. package/dist/hooks/useFocusTrap.d.ts +10 -0
  120. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  121. package/dist/hooks/useFocusTrap.js +225 -132
  122. package/dist/hooks/useMenuPress.d.ts +34 -1
  123. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  124. package/dist/hooks/useMenuPress.js +93 -5
  125. package/dist/hooks/usePressFeedback.d.ts +6 -6
  126. package/dist/hooks/usePressFeedback.js +6 -6
  127. package/dist/hooks/useScrollRegionProps.d.ts +35 -0
  128. package/dist/hooks/useScrollRegionProps.d.ts.map +1 -0
  129. package/dist/hooks/useScrollRegionProps.js +51 -0
  130. package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts +8 -0
  131. package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.d.ts.map +1 -0
  132. package/dist/i18n/generated-english/hooks/useScrollRegionProps.generated.js +12 -0
  133. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  134. package/dist/i18n/generated-locales/en.generated.js +1 -0
  135. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  136. package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
  137. package/dist/motion/carryAnimations.d.ts +58 -0
  138. package/dist/motion/carryAnimations.d.ts.map +1 -0
  139. package/dist/motion/carryAnimations.js +305 -0
  140. package/dist/motion/transientCopy.d.ts +4 -2
  141. package/dist/motion/transientCopy.d.ts.map +1 -1
  142. package/dist/motion/transientCopy.js +34 -5
  143. package/dist/motion/uikit/gestures.d.ts +41 -0
  144. package/dist/motion/uikit/gestures.d.ts.map +1 -1
  145. package/dist/motion/uikit/gestures.js +25 -0
  146. package/dist/motion/uikit/index.d.ts +1 -1
  147. package/dist/motion/uikit/index.d.ts.map +1 -1
  148. package/dist/motion/uikit/index.js +1 -1
  149. package/dist/privateAttributes/menuPressLit.stylex.d.ts +52 -0
  150. package/dist/privateAttributes/menuPressLit.stylex.d.ts.map +1 -0
  151. package/dist/privateAttributes/menuPressLit.stylex.js +99 -0
  152. package/dist/privateAttributes/press.stylex.d.ts +139 -0
  153. package/dist/privateAttributes/press.stylex.d.ts.map +1 -0
  154. package/dist/privateAttributes/press.stylex.js +98 -0
  155. package/dist/theme/tokens.stylex.d.ts +2 -2
  156. package/dist/theme/tokens.stylex.js +1 -1
  157. package/dist/utils/flatTree.d.ts +6 -3
  158. package/dist/utils/flatTree.d.ts.map +1 -1
  159. package/dist/utils/flatTree.js +13 -3
  160. package/dist/utils/hitArea.d.ts.map +1 -1
  161. package/dist/utils/hitArea.js +82 -5
  162. package/dist/utils/inputStream.d.ts +14 -1
  163. package/dist/utils/inputStream.d.ts.map +1 -1
  164. package/dist/utils/inputStream.js +40 -9
  165. package/dist/utils/interactionOverlay.stylex.d.ts +2 -1
  166. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  167. package/dist/utils/interactionOverlay.stylex.js +7 -2
  168. package/dist/utils/pressFeedback.d.ts +7 -0
  169. package/dist/utils/pressFeedback.d.ts.map +1 -1
  170. package/dist/utils/pressFeedback.js +32 -7
  171. package/dist/utils/pressGesture.d.ts +6 -0
  172. package/dist/utils/pressGesture.d.ts.map +1 -1
  173. package/dist/utils/pressGesture.js +17 -0
  174. package/dist/utils/rowPressLayer.stylex.d.ts +14 -0
  175. package/dist/utils/rowPressLayer.stylex.d.ts.map +1 -0
  176. package/dist/utils/rowPressLayer.stylex.js +25 -0
  177. package/dist/utils/scrollView.d.ts +25 -0
  178. package/dist/utils/scrollView.d.ts.map +1 -0
  179. package/dist/utils/scrollView.js +108 -0
  180. package/dist/utils/selectionLock.d.ts.map +1 -1
  181. package/dist/utils/selectionLock.js +1 -10
  182. package/dist/utils/touchHitArea.stylex.d.ts +35 -0
  183. package/dist/utils/touchHitArea.stylex.d.ts.map +1 -0
  184. package/dist/utils/touchHitArea.stylex.js +32 -0
  185. package/dist/utils/triggerFocus.d.ts +5 -0
  186. package/dist/utils/triggerFocus.d.ts.map +1 -1
  187. package/dist/utils/triggerFocus.js +9 -2
  188. package/locales/en.json +4 -0
  189. package/locales/pseudo.json +3 -0
  190. package/package.json +5 -5
  191. package/src/AppShell/observeMainOverflow.ts +9 -6
  192. package/src/BottomSheet/BottomSheet.tsx +5 -0
  193. package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -2
  194. package/src/CheckboxInput/CheckboxInput.tsx +71 -9
  195. package/src/ClickableCard/ClickableCard.tsx +8 -1
  196. package/src/DateInput/PickerWheel.tsx +7 -2
  197. package/src/DateRangeInput/DateRangeInput.tsx +1 -1
  198. package/src/Dialog/Dialog.doc.mjs +1 -1
  199. package/src/Dialog/Dialog.tsx +12 -1
  200. package/src/Drawer/Drawer.doc.mjs +1 -1
  201. package/src/Drawer/useDrawerDialogPresence.ts +9 -2
  202. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -2
  203. package/src/DropdownMenu/DropdownMenu.tsx +29 -5
  204. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -1
  205. package/src/DropdownMenu/DropdownMenuItem.tsx +8 -4
  206. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -1
  207. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -1
  208. package/src/FileInput/FileInput.tsx +16 -2
  209. package/src/InfoTip/InfoTip.doc.mjs +26 -2
  210. package/src/InfoTip/InfoTip.tsx +1 -0
  211. package/src/InputGroup/InputGroup.doc.mjs +4 -3
  212. package/src/InputGroup/InputGroup.tsx +4 -3
  213. package/src/InputGroup/groupStyles.ts +3 -1
  214. package/src/Item/Item.tsx +51 -9
  215. package/src/Layer/holdEntranceInput.ts +84 -0
  216. package/src/Layer/inputHold.ts +734 -0
  217. package/src/Layer/preserveAnimations.ts +12 -275
  218. package/src/Layout/LayoutContent.doc.mjs +6 -3
  219. package/src/Layout/LayoutContent.tsx +10 -4
  220. package/src/Layout/LayoutPanel.doc.mjs +6 -3
  221. package/src/Layout/LayoutPanel.tsx +11 -4
  222. package/src/Lightbox/Lightbox.tsx +4 -0
  223. package/src/MetadataList/MetadataList.tsx +1 -1
  224. package/src/MobileNav/MobileNav.doc.mjs +3 -3
  225. package/src/MobileNav/MobileNav.tsx +39 -1
  226. package/src/MoreMenu/MoreMenu.doc.mjs +2 -4
  227. package/src/MoreMenu/MoreMenu.tsx +5 -3
  228. package/src/RadioList/RadioListItem.doc.mjs +3 -2
  229. package/src/RadioList/RadioListItem.tsx +25 -7
  230. package/src/SelectableCard/SelectableCard.tsx +8 -1
  231. package/src/Spinner/Spinner.tsx +13 -3
  232. package/src/Stack/Stack.doc.mjs +1 -1
  233. package/src/Stack/StackItem.tsx +27 -13
  234. package/src/Table/plugins/sortable/useTableSortable.tsx +1 -1
  235. package/src/Token/Token.tsx +18 -1
  236. package/src/Token/TokenLink.tsx +7 -1
  237. package/src/Tooltip/useTooltip.tsx +175 -79
  238. package/src/TreeList/TreeListItem.tsx +36 -2
  239. package/src/gestures/GestureHost.ts +21 -5
  240. package/src/gestures/PanGestureRecognizer.ts +19 -2
  241. package/src/gestures/README.md +14 -5
  242. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +6 -1
  243. package/src/gestures/nativeScroll.ts +12 -68
  244. package/src/gestures/wheelSegmenter.ts +243 -10
  245. package/src/hooks/StackItemScrollRegion.tsx +57 -0
  246. package/src/hooks/menuPressGesture.ts +244 -38
  247. package/src/hooks/menuPressValues.ts +18 -0
  248. package/src/hooks/useClickableContainer.doc.mjs +8 -2
  249. package/src/hooks/useClickableContainer.ts +72 -6
  250. package/src/hooks/useFocusTrap.ts +268 -151
  251. package/src/hooks/useMenuPress.ts +118 -11
  252. package/src/hooks/usePressFeedback.doc.mjs +3 -3
  253. package/src/hooks/usePressFeedback.ts +6 -6
  254. package/src/hooks/useScrollRegionProps.ts +76 -0
  255. package/src/i18n/generated-english/hooks/useScrollRegionProps.generated.ts +13 -0
  256. package/src/i18n/generated-locales/en.generated.ts +1 -0
  257. package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
  258. package/src/motion/MEASUREMENTS.md +11 -0
  259. package/src/motion/carryAnimations.ts +389 -0
  260. package/src/motion/transientCopy.ts +45 -5
  261. package/src/motion/uikit/gestures.ts +22 -0
  262. package/src/motion/uikit/index.ts +1 -0
  263. package/src/privateAttributes/menuPressLit.stylex.ts +104 -0
  264. package/src/privateAttributes/press.stylex.ts +244 -0
  265. package/src/theme/tokens.stylex.ts +1 -1
  266. package/src/utils/flatTree.ts +15 -3
  267. package/src/utils/hitArea.ts +107 -5
  268. package/src/utils/inputStream.ts +47 -10
  269. package/src/utils/interactionOverlay.stylex.ts +7 -2
  270. package/src/utils/pressFeedback.ts +31 -6
  271. package/src/utils/pressGesture.ts +24 -1
  272. package/src/utils/rowPressLayer.stylex.ts +27 -0
  273. package/src/utils/scrollView.ts +135 -0
  274. package/src/utils/selectionLock.ts +5 -10
  275. package/src/utils/touchHitArea.stylex.ts +41 -0
  276. package/src/utils/triggerFocus.ts +17 -4
@@ -251,6 +251,27 @@ export const UIKIT_PRESS = {
251
251
  momentumMemory: {value: 3000, unit: 'ms'},
252
252
  } as const satisfies Record<string, GestureConstant>;
253
253
 
254
+ /**
255
+ * UIMenu presented by a button whose menu is its primary action: how far the
256
+ * finger that opened it travels before it picks. DropdownMenu's press model
257
+ * reads a copy in `hooks/menuPressValues.ts`.
258
+ */
259
+ export const UIKIT_MENU_PRESS = {
260
+ /**
261
+ * After the menu opens under a still-held finger, the finger travels this
262
+ * far before it starts picking. The distance is a straight line from the
263
+ * first sample the menu receives after it opened. Until then nothing is
264
+ * lit, and lifting acts on nothing.
265
+ * @source measured: UIKit lab (injected host app), iOS 26.2 simulator,
266
+ * iPhone 17 Pro, gesture-state hook, steps 150 ms apart, Oct 23 2026
267
+ * (M-MN2): with the menu open under a held finger and a 1 pt first step,
268
+ * moves of (0, 9) and (7, 7) picked nothing; (0, 10), (0, 11), (10, 0),
269
+ * (9, 9) and (10, 10) picked; a 9 pt step followed by 3 pt more picked
270
+ * nothing
271
+ */
272
+ handoff: {value: 10, unit: 'px'},
273
+ } as const satisfies Record<string, GestureConstant>;
274
+
254
275
  export const UIKIT_VELOCITY = {
255
276
  /**
256
277
  * UIPanGestureRecognizer.velocity(in:) is this times the newest event's
@@ -318,6 +339,7 @@ export const UIKIT_GESTURES = {
318
339
  screenEdgePan: UIKIT_SCREEN_EDGE_PAN,
319
340
  control: UIKIT_CONTROL,
320
341
  press: UIKIT_PRESS,
342
+ menuPress: UIKIT_MENU_PRESS,
321
343
  velocity: UIKIT_VELOCITY,
322
344
  } as const;
323
345
 
@@ -27,6 +27,7 @@ export {
27
27
  UIKIT_CONTROL,
28
28
  UIKIT_GESTURES,
29
29
  UIKIT_LONG_PRESS,
30
+ UIKIT_MENU_PRESS,
30
31
  UIKIT_PAN,
31
32
  UIKIT_PINCH,
32
33
  UIKIT_PRESS,
@@ -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
  }