@multiplatform.one/components 7.28.1 → 7.29.1

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 (328) hide show
  1. package/dist/cjs/Accordion/index.cjs +26 -17
  2. package/dist/cjs/Accordion/index.native.js +36 -27
  3. package/dist/cjs/Accordion/index.native.js.map +1 -1
  4. package/dist/cjs/Heading.cjs +74 -30
  5. package/dist/cjs/Heading.native.js +81 -30
  6. package/dist/cjs/Heading.native.js.map +1 -1
  7. package/dist/cjs/ProgressSteps/index.native.js.map +1 -1
  8. package/dist/cjs/ScrollView/index.native.js +4 -0
  9. package/dist/cjs/ScrollView/index.native.js.map +1 -1
  10. package/dist/cjs/SortableList/SortableHandle.cjs +1 -1
  11. package/dist/cjs/SortableList/SortableHandle.native.js +1 -1
  12. package/dist/cjs/Text.cjs +58 -21
  13. package/dist/cjs/Text.native.js +62 -21
  14. package/dist/cjs/Text.native.js.map +1 -1
  15. package/dist/cjs/Toast/index.native.js.map +1 -1
  16. package/dist/cjs/charts/ChartDataView.cjs +2 -1
  17. package/dist/cjs/charts/ChartDataView.native.js +2 -1
  18. package/dist/cjs/charts/ChartDataView.native.js.map +1 -1
  19. package/dist/cjs/code/CodeBlock/index.cjs +8 -8
  20. package/dist/cjs/code/CodeBlock/index.native.js +8 -8
  21. package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
  22. package/dist/cjs/index.cjs +1 -0
  23. package/dist/cjs/index.native.js +1 -0
  24. package/dist/cjs/index.native.js.map +1 -1
  25. package/dist/cjs/layouts/EmptyState.cjs +4 -3
  26. package/dist/cjs/layouts/EmptyState.native.js +14 -9
  27. package/dist/cjs/layouts/EmptyState.native.js.map +1 -1
  28. package/dist/cjs/layouts/page.cjs +2 -1
  29. package/dist/cjs/layouts/page.native.js +2 -1
  30. package/dist/cjs/layouts/page.native.js.map +1 -1
  31. package/dist/cjs/layouts/sidebar.cjs +2 -1
  32. package/dist/cjs/layouts/sidebar.native.js +2 -1
  33. package/dist/cjs/layouts/sidebar.native.js.map +1 -1
  34. package/dist/cjs/layouts/tags.cjs +2 -1
  35. package/dist/cjs/layouts/tags.native.js +2 -1
  36. package/dist/cjs/layouts/tags.native.js.map +1 -1
  37. package/dist/cjs/layouts/toolbar.cjs +2 -1
  38. package/dist/cjs/layouts/toolbar.native.js +2 -1
  39. package/dist/cjs/layouts/toolbar.native.js.map +1 -1
  40. package/dist/cjs/shared/useReturnFocusOnClose.cjs +1 -0
  41. package/dist/cjs/shared/useReturnFocusOnClose.native.js +1 -0
  42. package/dist/cjs/shared/useReturnFocusOnClose.native.js.map +1 -1
  43. package/dist/cjs/surfaces.cjs +61 -0
  44. package/dist/cjs/surfaces.native.js +73 -0
  45. package/dist/cjs/surfaces.native.js.map +1 -1
  46. package/dist/cjs/views/Calendar.cjs +13 -10
  47. package/dist/cjs/views/Calendar.native.js +13 -10
  48. package/dist/cjs/views/Calendar.native.js.map +1 -1
  49. package/dist/cjs/views/Dashboard.cjs +2 -1
  50. package/dist/cjs/views/Dashboard.native.js +2 -1
  51. package/dist/cjs/views/Dashboard.native.js.map +1 -1
  52. package/dist/cjs/views/Gantt.cjs +39 -27
  53. package/dist/cjs/views/Gantt.native.js +38 -23
  54. package/dist/cjs/views/Gantt.native.js.map +1 -1
  55. package/dist/cjs/views/Kanban.cjs +2 -1
  56. package/dist/cjs/views/Kanban.native.js +1 -0
  57. package/dist/cjs/views/Kanban.native.js.map +1 -1
  58. package/dist/cjs/views/List.cjs +1 -0
  59. package/dist/cjs/views/List.native.js +1 -0
  60. package/dist/cjs/views/List.native.js.map +1 -1
  61. package/dist/cjs/views/Matrix.cjs +5 -3
  62. package/dist/cjs/views/Matrix.native.js +5 -3
  63. package/dist/cjs/views/Matrix.native.js.map +1 -1
  64. package/dist/cjs/views/ReportBuilder.cjs +2 -1
  65. package/dist/cjs/views/ReportBuilder.native.js +2 -1
  66. package/dist/cjs/views/ReportBuilder.native.js.map +1 -1
  67. package/dist/cjs/views/TreeView.cjs +2 -1
  68. package/dist/cjs/views/TreeView.native.js +2 -1
  69. package/dist/cjs/views/TreeView.native.js.map +1 -1
  70. package/dist/esm/Accordion/index.mjs +26 -18
  71. package/dist/esm/Accordion/index.mjs.map +1 -1
  72. package/dist/esm/Accordion/index.native.js +36 -28
  73. package/dist/esm/Accordion/index.native.js.map +1 -1
  74. package/dist/esm/Heading.mjs +74 -30
  75. package/dist/esm/Heading.mjs.map +1 -1
  76. package/dist/esm/Heading.native.js +81 -30
  77. package/dist/esm/Heading.native.js.map +1 -1
  78. package/dist/esm/ProgressSteps/index.mjs.map +1 -1
  79. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  80. package/dist/esm/ScrollView/index.native.js +4 -0
  81. package/dist/esm/ScrollView/index.native.js.map +1 -1
  82. package/dist/esm/SortableList/SortableHandle.mjs +1 -1
  83. package/dist/esm/SortableList/SortableHandle.native.js +1 -1
  84. package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
  85. package/dist/esm/Text.mjs +58 -21
  86. package/dist/esm/Text.mjs.map +1 -1
  87. package/dist/esm/Text.native.js +62 -21
  88. package/dist/esm/Text.native.js.map +1 -1
  89. package/dist/esm/Toast/index.mjs.map +1 -1
  90. package/dist/esm/Toast/index.native.js.map +1 -1
  91. package/dist/esm/charts/ChartDataView.mjs +2 -1
  92. package/dist/esm/charts/ChartDataView.mjs.map +1 -1
  93. package/dist/esm/charts/ChartDataView.native.js +2 -1
  94. package/dist/esm/charts/ChartDataView.native.js.map +1 -1
  95. package/dist/esm/code/CodeBlock/index.mjs +1 -1
  96. package/dist/esm/code/CodeBlock/index.native.js +1 -1
  97. package/dist/esm/index.mjs +2 -2
  98. package/dist/esm/index.mjs.map +1 -1
  99. package/dist/esm/index.native.js +2 -2
  100. package/dist/esm/index.native.js.map +1 -1
  101. package/dist/esm/layouts/EmptyState.mjs +4 -3
  102. package/dist/esm/layouts/EmptyState.mjs.map +1 -1
  103. package/dist/esm/layouts/EmptyState.native.js +14 -9
  104. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  105. package/dist/esm/layouts/page.mjs +2 -1
  106. package/dist/esm/layouts/page.mjs.map +1 -1
  107. package/dist/esm/layouts/page.native.js +2 -1
  108. package/dist/esm/layouts/page.native.js.map +1 -1
  109. package/dist/esm/layouts/sidebar.mjs +2 -1
  110. package/dist/esm/layouts/sidebar.mjs.map +1 -1
  111. package/dist/esm/layouts/sidebar.native.js +2 -1
  112. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  113. package/dist/esm/layouts/tags.mjs +2 -1
  114. package/dist/esm/layouts/tags.mjs.map +1 -1
  115. package/dist/esm/layouts/tags.native.js +2 -1
  116. package/dist/esm/layouts/tags.native.js.map +1 -1
  117. package/dist/esm/layouts/toolbar.mjs +2 -1
  118. package/dist/esm/layouts/toolbar.mjs.map +1 -1
  119. package/dist/esm/layouts/toolbar.native.js +2 -1
  120. package/dist/esm/layouts/toolbar.native.js.map +1 -1
  121. package/dist/esm/shared/useReturnFocusOnClose.mjs +1 -0
  122. package/dist/esm/shared/useReturnFocusOnClose.mjs.map +1 -1
  123. package/dist/esm/shared/useReturnFocusOnClose.native.js +1 -0
  124. package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
  125. package/dist/esm/surfaces.mjs +63 -3
  126. package/dist/esm/surfaces.mjs.map +1 -1
  127. package/dist/esm/surfaces.native.js +75 -3
  128. package/dist/esm/surfaces.native.js.map +1 -1
  129. package/dist/esm/views/Calendar.mjs +12 -9
  130. package/dist/esm/views/Calendar.mjs.map +1 -1
  131. package/dist/esm/views/Calendar.native.js +12 -9
  132. package/dist/esm/views/Calendar.native.js.map +1 -1
  133. package/dist/esm/views/Dashboard.mjs +2 -1
  134. package/dist/esm/views/Dashboard.mjs.map +1 -1
  135. package/dist/esm/views/Dashboard.native.js +2 -1
  136. package/dist/esm/views/Dashboard.native.js.map +1 -1
  137. package/dist/esm/views/Gantt.mjs +37 -25
  138. package/dist/esm/views/Gantt.mjs.map +1 -1
  139. package/dist/esm/views/Gantt.native.js +36 -21
  140. package/dist/esm/views/Gantt.native.js.map +1 -1
  141. package/dist/esm/views/Kanban.mjs +2 -1
  142. package/dist/esm/views/Kanban.mjs.map +1 -1
  143. package/dist/esm/views/Kanban.native.js +1 -0
  144. package/dist/esm/views/Kanban.native.js.map +1 -1
  145. package/dist/esm/views/List.mjs +1 -0
  146. package/dist/esm/views/List.mjs.map +1 -1
  147. package/dist/esm/views/List.native.js +1 -0
  148. package/dist/esm/views/List.native.js.map +1 -1
  149. package/dist/esm/views/Matrix.mjs +3 -1
  150. package/dist/esm/views/Matrix.mjs.map +1 -1
  151. package/dist/esm/views/Matrix.native.js +3 -1
  152. package/dist/esm/views/Matrix.native.js.map +1 -1
  153. package/dist/esm/views/ReportBuilder.mjs +2 -1
  154. package/dist/esm/views/ReportBuilder.mjs.map +1 -1
  155. package/dist/esm/views/ReportBuilder.native.js +2 -1
  156. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  157. package/dist/esm/views/TreeView.mjs +2 -1
  158. package/dist/esm/views/TreeView.mjs.map +1 -1
  159. package/dist/esm/views/TreeView.native.js +2 -1
  160. package/dist/esm/views/TreeView.native.js.map +1 -1
  161. package/dist/jsx/Accordion/index.mjs +26 -18
  162. package/dist/jsx/Accordion/index.mjs.map +1 -1
  163. package/dist/jsx/Accordion/index.native.js +36 -27
  164. package/dist/jsx/Accordion/index.native.js.map +1 -1
  165. package/dist/jsx/Heading.mjs +74 -30
  166. package/dist/jsx/Heading.mjs.map +1 -1
  167. package/dist/jsx/Heading.native.js +81 -30
  168. package/dist/jsx/Heading.native.js.map +1 -1
  169. package/dist/jsx/ProgressSteps/index.mjs.map +1 -1
  170. package/dist/jsx/ProgressSteps/index.native.js.map +1 -1
  171. package/dist/jsx/ScrollView/index.native.js +4 -0
  172. package/dist/jsx/ScrollView/index.native.js.map +1 -1
  173. package/dist/jsx/SortableList/SortableHandle.mjs +1 -1
  174. package/dist/jsx/SortableList/SortableHandle.native.js +1 -1
  175. package/dist/jsx/Text.mjs +58 -21
  176. package/dist/jsx/Text.mjs.map +1 -1
  177. package/dist/jsx/Text.native.js +62 -21
  178. package/dist/jsx/Text.native.js.map +1 -1
  179. package/dist/jsx/Toast/index.mjs.map +1 -1
  180. package/dist/jsx/Toast/index.native.js.map +1 -1
  181. package/dist/jsx/charts/ChartDataView.mjs +2 -1
  182. package/dist/jsx/charts/ChartDataView.mjs.map +1 -1
  183. package/dist/jsx/charts/ChartDataView.native.js +2 -1
  184. package/dist/jsx/charts/ChartDataView.native.js.map +1 -1
  185. package/dist/jsx/code/CodeBlock/index.mjs +1 -1
  186. package/dist/jsx/code/CodeBlock/index.native.js +8 -8
  187. package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
  188. package/dist/jsx/index.js +2 -2
  189. package/dist/jsx/index.js.map +1 -1
  190. package/dist/jsx/index.mjs +2 -2
  191. package/dist/jsx/index.mjs.map +1 -1
  192. package/dist/jsx/index.native.js +1 -0
  193. package/dist/jsx/index.native.js.map +1 -1
  194. package/dist/jsx/layouts/EmptyState.mjs +4 -3
  195. package/dist/jsx/layouts/EmptyState.mjs.map +1 -1
  196. package/dist/jsx/layouts/EmptyState.native.js +14 -9
  197. package/dist/jsx/layouts/EmptyState.native.js.map +1 -1
  198. package/dist/jsx/layouts/page.mjs +2 -1
  199. package/dist/jsx/layouts/page.mjs.map +1 -1
  200. package/dist/jsx/layouts/page.native.js +2 -1
  201. package/dist/jsx/layouts/page.native.js.map +1 -1
  202. package/dist/jsx/layouts/sidebar.mjs +2 -1
  203. package/dist/jsx/layouts/sidebar.mjs.map +1 -1
  204. package/dist/jsx/layouts/sidebar.native.js +2 -1
  205. package/dist/jsx/layouts/sidebar.native.js.map +1 -1
  206. package/dist/jsx/layouts/tags.mjs +2 -1
  207. package/dist/jsx/layouts/tags.mjs.map +1 -1
  208. package/dist/jsx/layouts/tags.native.js +2 -1
  209. package/dist/jsx/layouts/tags.native.js.map +1 -1
  210. package/dist/jsx/layouts/toolbar.mjs +2 -1
  211. package/dist/jsx/layouts/toolbar.mjs.map +1 -1
  212. package/dist/jsx/layouts/toolbar.native.js +2 -1
  213. package/dist/jsx/layouts/toolbar.native.js.map +1 -1
  214. package/dist/jsx/shared/useReturnFocusOnClose.mjs +1 -0
  215. package/dist/jsx/shared/useReturnFocusOnClose.mjs.map +1 -1
  216. package/dist/jsx/shared/useReturnFocusOnClose.native.js +1 -0
  217. package/dist/jsx/shared/useReturnFocusOnClose.native.js.map +1 -1
  218. package/dist/jsx/surfaces.mjs +63 -3
  219. package/dist/jsx/surfaces.mjs.map +1 -1
  220. package/dist/jsx/surfaces.native.js +73 -0
  221. package/dist/jsx/surfaces.native.js.map +1 -1
  222. package/dist/jsx/views/Calendar.mjs +12 -9
  223. package/dist/jsx/views/Calendar.mjs.map +1 -1
  224. package/dist/jsx/views/Calendar.native.js +13 -10
  225. package/dist/jsx/views/Calendar.native.js.map +1 -1
  226. package/dist/jsx/views/Dashboard.mjs +2 -1
  227. package/dist/jsx/views/Dashboard.mjs.map +1 -1
  228. package/dist/jsx/views/Dashboard.native.js +2 -1
  229. package/dist/jsx/views/Dashboard.native.js.map +1 -1
  230. package/dist/jsx/views/Gantt.mjs +37 -25
  231. package/dist/jsx/views/Gantt.mjs.map +1 -1
  232. package/dist/jsx/views/Gantt.native.js +38 -23
  233. package/dist/jsx/views/Gantt.native.js.map +1 -1
  234. package/dist/jsx/views/Kanban.mjs +2 -1
  235. package/dist/jsx/views/Kanban.mjs.map +1 -1
  236. package/dist/jsx/views/Kanban.native.js +1 -0
  237. package/dist/jsx/views/Kanban.native.js.map +1 -1
  238. package/dist/jsx/views/List.mjs +1 -0
  239. package/dist/jsx/views/List.mjs.map +1 -1
  240. package/dist/jsx/views/List.native.js +1 -0
  241. package/dist/jsx/views/List.native.js.map +1 -1
  242. package/dist/jsx/views/Matrix.mjs +3 -1
  243. package/dist/jsx/views/Matrix.mjs.map +1 -1
  244. package/dist/jsx/views/Matrix.native.js +5 -3
  245. package/dist/jsx/views/Matrix.native.js.map +1 -1
  246. package/dist/jsx/views/ReportBuilder.mjs +2 -1
  247. package/dist/jsx/views/ReportBuilder.mjs.map +1 -1
  248. package/dist/jsx/views/ReportBuilder.native.js +2 -1
  249. package/dist/jsx/views/ReportBuilder.native.js.map +1 -1
  250. package/dist/jsx/views/TreeView.mjs +2 -1
  251. package/dist/jsx/views/TreeView.mjs.map +1 -1
  252. package/dist/jsx/views/TreeView.native.js +2 -1
  253. package/dist/jsx/views/TreeView.native.js.map +1 -1
  254. package/package.json +9 -9
  255. package/src/Accordion/index.tsx +34 -15
  256. package/src/Heading.stories.tsx +13 -0
  257. package/src/Heading.tsx +62 -21
  258. package/src/ProgressSteps/ProgressSteps.spec.tsx +2 -3
  259. package/src/ProgressSteps/index.tsx +3 -4
  260. package/src/ScrollView/ScrollView.native.spec.tsx +97 -1
  261. package/src/ScrollView/index.native.tsx +4 -0
  262. package/src/SortableList/SortableHandle.tsx +2 -2
  263. package/src/Tabs/Tabs.lifecycle.spec.tsx +3 -1
  264. package/src/Text.tsx +32 -20
  265. package/src/Toast/index.tsx +4 -3
  266. package/src/charts/ChartDataView.tsx +2 -1
  267. package/src/code/CodeBlock/index.tsx +1 -1
  268. package/src/feedback/ConfirmDialog.spec.tsx +2 -2
  269. package/src/icons/phosphorTokenColor.spec.tsx +86 -0
  270. package/src/index.ts +1 -1
  271. package/src/layouts/EmptyState.spec.tsx +17 -0
  272. package/src/layouts/EmptyState.tsx +10 -3
  273. package/src/layouts/Screen.keyboardTaps.spec.tsx +57 -0
  274. package/src/layouts/Screen.stories.tsx +55 -2
  275. package/src/layouts/page.tsx +2 -14
  276. package/src/layouts/sidebar.tsx +2 -1
  277. package/src/layouts/tags.tsx +1 -1
  278. package/src/layouts/toolbar.tsx +2 -1
  279. package/src/shared/useReturnFocusOnClose.spec.tsx +39 -0
  280. package/src/shared/useReturnFocusOnClose.ts +4 -0
  281. package/src/surfaces.spec.tsx +272 -3
  282. package/src/surfaces.tsx +97 -4
  283. package/src/textWeight.houseFonts.spec.tsx +127 -0
  284. package/src/textWeight.native.spec.tsx +27 -0
  285. package/src/textWeight.spec.tsx +15 -0
  286. package/src/views/Calendar.tsx +37 -29
  287. package/src/views/Dashboard.tsx +2 -13
  288. package/src/views/Gantt.scroll.spec.tsx +4 -1
  289. package/src/views/Gantt.tsx +40 -25
  290. package/src/views/Kanban.native.tsx +1 -0
  291. package/src/views/Kanban.tsx +2 -10
  292. package/src/views/List.tsx +1 -0
  293. package/src/views/Matrix.tsx +3 -1
  294. package/src/views/ReportBuilder.tsx +2 -1
  295. package/src/views/TreeView.tsx +2 -1
  296. package/src/views/views.spec.tsx +14 -2
  297. package/types/Accordion/index.d.ts +8 -0
  298. package/types/Accordion/index.d.ts.map +1 -1
  299. package/types/Chip/index.d.ts +1 -1
  300. package/types/Heading.d.ts +7 -7
  301. package/types/Heading.d.ts.map +1 -1
  302. package/types/ProgressSteps/index.d.ts.map +1 -1
  303. package/types/ScrollView/index.native.d.ts.map +1 -1
  304. package/types/SortableList/SortableHandle.d.ts +1 -1
  305. package/types/Tabs/index.d.ts +1 -1
  306. package/types/Text.d.ts +4 -4
  307. package/types/Text.d.ts.map +1 -1
  308. package/types/Toast/index.d.ts.map +1 -1
  309. package/types/charts/ChartDataView.d.ts.map +1 -1
  310. package/types/index.d.ts +1 -1
  311. package/types/index.d.ts.map +1 -1
  312. package/types/layouts/EmptyState.d.ts.map +1 -1
  313. package/types/layouts/page.d.ts.map +1 -1
  314. package/types/layouts/sidebar.d.ts.map +1 -1
  315. package/types/layouts/tags.d.ts.map +1 -1
  316. package/types/layouts/toolbar.d.ts.map +1 -1
  317. package/types/shared/useReturnFocusOnClose.d.ts.map +1 -1
  318. package/types/surfaces.d.ts +14 -0
  319. package/types/surfaces.d.ts.map +1 -1
  320. package/types/views/Calendar.d.ts +18 -1
  321. package/types/views/Calendar.d.ts.map +1 -1
  322. package/types/views/Dashboard.d.ts.map +1 -1
  323. package/types/views/Gantt.d.ts.map +1 -1
  324. package/types/views/Kanban.d.ts.map +1 -1
  325. package/types/views/List.d.ts.map +1 -1
  326. package/types/views/Matrix.d.ts.map +1 -1
  327. package/types/views/ReportBuilder.d.ts.map +1 -1
  328. package/types/views/TreeView.d.ts.map +1 -1
package/src/surfaces.tsx CHANGED
@@ -1,11 +1,13 @@
1
1
  import { Preset, Surface, useResolvedKnobs } from "@multiplatform.one/theme";
2
2
  import type { ReactNode } from "react";
3
- import { createContext, forwardRef, useContext } from "react";
3
+ import { createContext, forwardRef, useContext, useEffect } from "react";
4
4
  import {
5
5
  AlertDialog,
6
6
  Dialog,
7
+ isWeb,
7
8
  Popover,
8
9
  Sheet,
10
+ useDialogContext,
9
11
  View,
10
12
  type DialogContentProps as TamaguiDialogContentProps,
11
13
  type DialogOverlayProps as TamaguiDialogOverlayProps,
@@ -56,6 +58,86 @@ const overlayFrameFocusStyle = {
56
58
  outlineStyle: "none" as any,
57
59
  } as const;
58
60
 
61
+ // ── Overlay open focus (MPO-452, MPO-466) ────────────────────────────────
62
+ // Tamagui 2.7's FocusScope decides "focus is not inside yet", waits for
63
+ // idle (up to 200 ms), then focuses the first tabbable that does not
64
+ // already hold focus: a click into a field during that wait had focus
65
+ // moved to another control, so the typing that followed went there. The
66
+ // house handler checks and focuses in one step. With no tabbable yet,
67
+ // tamagui's deferred path still runs. Dialog content sits in a native
68
+ // <dialog> whose show() puts focus inside before the check in browsers,
69
+ // so there FocusScope never calls the handler; useDialogShowFocus does
70
+ // when show() left focus on something that is not a tab stop (MPO-470).
71
+
72
+ function isTabbableWithin(node: HTMLElement, container: HTMLElement): boolean {
73
+ if (node.tabIndex < 0 || node.hidden || node.tagName === "A") return false;
74
+ if ((node as HTMLInputElement).disabled) return false;
75
+ if (node instanceof HTMLInputElement && node.type === "hidden") return false;
76
+ if (getComputedStyle(node).visibility === "hidden") return false;
77
+ for (let el: HTMLElement | null = node; el && el !== container; el = el.parentElement) {
78
+ if (getComputedStyle(el).display === "none") return false;
79
+ }
80
+ return true;
81
+ }
82
+
83
+ function focusFirstTabbableOnOpen(event: Event): void {
84
+ const container = event.currentTarget;
85
+ if (typeof HTMLElement === "undefined" || !(container instanceof HTMLElement)) return;
86
+ const first = Array.from(container.querySelectorAll<HTMLElement>("*")).find((node) =>
87
+ isTabbableWithin(node, container),
88
+ );
89
+ if (!first) return;
90
+ event.preventDefault();
91
+ first.focus({ preventScroll: true });
92
+ if (first instanceof HTMLInputElement) first.select();
93
+ }
94
+
95
+ type OpenAutoFocus = (event: Event) => void;
96
+
97
+ /** The caller's handler runs first; its `preventDefault()` skips the house open focus. */
98
+ function withHouseOpenFocus(onOpenAutoFocus?: OpenAutoFocus): OpenAutoFocus {
99
+ return (event) => {
100
+ onOpenAutoFocus?.(event);
101
+ if (!event.defaultPrevented) focusFirstTabbableOnOpen(event);
102
+ };
103
+ }
104
+
105
+ const DIALOG_SHOW_FOCUS_EVENT = "mp.dialogShowFocus";
106
+
107
+ /**
108
+ * Chromium and WebKit's `<dialog>.show()` focuses the first focusable
109
+ * descendant, so a tabIndex -1 frame (the house Input's group frame, a
110
+ * tabIndex -1 panel) takes focus; Firefox and the HTML spec take the first
111
+ * tab stop. When focus sits on something that is not a tab stop, run
112
+ * `onOpenAutoFocus` against the content the way FocusScope would. The
113
+ * content is found by its id because FocusScope clones it with its own ref,
114
+ * so a ref passed to `Dialog.Content` never attaches on web. Passive effects
115
+ * run after the portal's layout-effect `show()`. Mirror of the forms hook.
116
+ */
117
+ function useDialogShowFocus(onOpenAutoFocus: OpenAutoFocus): void {
118
+ const { open, contentId } = useDialogContext();
119
+ useEffect(() => {
120
+ if (!open || !contentId || !isWeb || typeof document === "undefined") return;
121
+ const content = document.getElementById(contentId);
122
+ const focused = document.activeElement;
123
+ if (!content || !(focused instanceof HTMLElement)) return;
124
+ if (!content.contains(focused) || focused.tabIndex >= 0) return;
125
+ content.addEventListener(DIALOG_SHOW_FOCUS_EVENT, onOpenAutoFocus, { once: true });
126
+ content.dispatchEvent(new CustomEvent(DIALOG_SHOW_FOCUS_EVENT, { cancelable: true }));
127
+ }, [open, contentId]);
128
+ }
129
+
130
+ /**
131
+ * The house open focus for a raw `Dialog.Content`, which `DialogContent` and
132
+ * `AlertDialogContent` already run: render it inside the content. When
133
+ * `<dialog>.show()` left focus off a tab stop, the caller's `onOpenAutoFocus`
134
+ * runs, then the first tabbable takes focus (MPO-470).
135
+ */
136
+ export function DialogShowFocus({ onOpenAutoFocus }: { onOpenAutoFocus?: OpenAutoFocus }): null {
137
+ useDialogShowFocus(withHouseOpenFocus(onOpenAutoFocus));
138
+ return null;
139
+ }
140
+
59
141
  // Card frame lives in ./Card (NestedScale only; no Tint — R12); the parts
60
142
  // live in ./CardHeader and ./CardFooter and are re-exported through ./Card.
61
143
 
@@ -105,8 +187,10 @@ export const DialogContent = forwardRef<
105
187
  const DialogContentInner = forwardRef<
106
188
  TamaguiElement,
107
189
  TamaguiDialogContentProps & { children?: ReactNode }
108
- >(function DialogContentInner({ children, ...props }, ref) {
190
+ >(function DialogContentInner({ children, onOpenAutoFocus, ...props }, ref) {
109
191
  const { knobProps } = useResolvedKnobs({ component: "DialogContent" });
192
+ const openFocus = withHouseOpenFocus(onOpenAutoFocus);
193
+ useDialogShowFocus(openFocus);
110
194
  // ThemeableStack unstyled:false defaults `elevate: true`, which paints
111
195
  // getElevation(size) — `$true` is a control stop. `elevate={false}` leaves
112
196
  // the overlay ladder in `elevatedSurface` as the only shadow (MPO-99).
@@ -126,6 +210,7 @@ const DialogContentInner = forwardRef<
126
210
  {...overlayFrameFocus}
127
211
  focusStyle={overlayFrameFocusStyle}
128
212
  focusVisibleStyle={overlayFrameFocusStyle}
213
+ onOpenAutoFocus={openFocus}
129
214
  {...props}
130
215
  >
131
216
  <DialogRegionContext.Provider value={IN_DIALOG_REGION}>
@@ -190,6 +275,7 @@ const AlertDialogContentInner = forwardRef<
190
275
  TamaguiDialogContentProps & { children?: ReactNode }
191
276
  >(function AlertDialogContentInner({ children, ...props }, ref) {
192
277
  const { knobProps } = useResolvedKnobs({ component: "AlertDialogContent" });
278
+ useDialogShowFocus(withHouseOpenFocus(props.onOpenAutoFocus));
193
279
  return (
194
280
  <AlertDialog.Content
195
281
  ref={ref}
@@ -242,8 +328,14 @@ export const AlertDialogOverlay = forwardRef<
242
328
  // containerRadius cap spread over it; children must not draw their own
243
329
  // outer border.
244
330
 
245
- export const PopoverContent = forwardRef<TamaguiElement, ViewProps & { children?: ReactNode }>(
246
- function PopoverContent({ children, ...props }, ref) {
331
+ type PopoverContentProps = ViewProps & {
332
+ children?: ReactNode;
333
+ /** Runs first; `preventDefault()` skips the house open focus. */
334
+ onOpenAutoFocus?: OpenAutoFocus;
335
+ };
336
+
337
+ export const PopoverContent = forwardRef<TamaguiElement, PopoverContentProps>(
338
+ function PopoverContent({ children, onOpenAutoFocus, ...props }, ref) {
247
339
  const { knobProps } = useResolvedKnobs({ component: "PopoverContent" });
248
340
  return (
249
341
  <Popover.Content
@@ -256,6 +348,7 @@ export const PopoverContent = forwardRef<TamaguiElement, ViewProps & { children?
256
348
  {...overlayFrameFocus}
257
349
  focusStyle={overlayFrameFocusStyle}
258
350
  focusVisibleStyle={overlayFrameFocusStyle}
351
+ onOpenAutoFocus={withHouseOpenFocus(onOpenAutoFocus)}
259
352
  {...props}
260
353
  >
261
354
  <NestedScale density={knobProps.density}>{children}</NestedScale>
@@ -0,0 +1,127 @@
1
+ /**
2
+ * MPO-446 on the HOUSE font tables, read as computed weight on the text node.
3
+ *
4
+ * The class-identity matrix (../tests/textWeightMatrix.tsx) runs on the stock
5
+ * test config, which registers no `$serif`, so a family swap to serif there
6
+ * resolves no font at all and cannot show the defect the portal did: a Band
7
+ * H2 at Inter 600 falling to Georgia 400 under `headingFont: serif`. Here
8
+ * the provider carries `createDefaultThemeConfig()`, the tables the live
9
+ * apps ship (Inter heading ramp 600/700/800, category fonts 400 at every
10
+ * step), and every assertion is a `getComputedStyle` value.
11
+ */
12
+ import { cleanup, render } from "@testing-library/react";
13
+ import { createDefaultThemeConfig, Preset } from "@multiplatform.one/theme";
14
+ import type { ReactElement } from "react";
15
+ import { TamaguiProvider } from "tamagui";
16
+ import { afterEach, describe, expect, it } from "vitest";
17
+ import { H1, H2, Heading } from "./Heading";
18
+ import { Paragraph, SizableText, Text } from "./Text";
19
+
20
+ const themeConfig = createDefaultThemeConfig();
21
+ const LABEL = "Sign in to see your clusters";
22
+
23
+ afterEach(cleanup);
24
+
25
+ function textNodeHost(root: HTMLElement): HTMLElement {
26
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
27
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
28
+ if (node.textContent?.trim() === LABEL && node.parentElement) return node.parentElement;
29
+ }
30
+ throw new Error(`no text node "${LABEL}"`);
31
+ }
32
+
33
+ function measure(node: ReactElement, scheme: "light" | "dark") {
34
+ const { container } = render(
35
+ <TamaguiProvider config={themeConfig.tamagui} defaultTheme={scheme}>
36
+ {node}
37
+ </TamaguiProvider>,
38
+ );
39
+ const cs = getComputedStyle(textNodeHost(container));
40
+ const weight = cs.fontWeight.trim();
41
+ const family = cs.fontFamily.split(",")[0]?.replace(/['"]/g, "").trim() ?? "";
42
+ cleanup();
43
+ if (!weight) throw new Error("no computed font-weight on the text node");
44
+ if (!family) throw new Error("no computed font-family on the text node");
45
+ return { weight, family };
46
+ }
47
+
48
+ const schemes = ["light", "dark"] as const;
49
+ const headingSizes = ["$1", "$4", "$6", "$9"] as const;
50
+ const knobWeights = [
51
+ ["regular", "400"],
52
+ ["bold", "700"],
53
+ ] as const;
54
+
55
+ describe("house headings keep their weight across headingFont (MPO-446, LC-11)", () => {
56
+ for (const scheme of schemes) {
57
+ for (const [name, Voice] of [
58
+ ["Heading", Heading],
59
+ ["H1", H1],
60
+ ["H2", H2],
61
+ ] as const) {
62
+ it(`${scheme}: ${name} weight is the knob's at every size, sans-serif and serif alike`, () => {
63
+ for (const [fontWeight, expected] of knobWeights) {
64
+ for (const size of headingSizes) {
65
+ const at = (headingFont: "sans-serif" | "serif") =>
66
+ measure(
67
+ <Preset overrides={{ fontWeight, headingFont }}>
68
+ <Voice size={size}>{LABEL}</Voice>
69
+ </Preset>,
70
+ scheme,
71
+ );
72
+ const sans = at("sans-serif");
73
+ const serif = at("serif");
74
+ expect(sans.family).toBe("Inter");
75
+ expect(serif.family).toBe("Georgia");
76
+ expect({ size, fontWeight, sans: sans.weight, serif: serif.weight }).toEqual({
77
+ size,
78
+ fontWeight,
79
+ sans: expected,
80
+ serif: expected,
81
+ });
82
+ }
83
+ }
84
+ });
85
+ }
86
+
87
+ it(`${scheme}: the portal Band heading keeps its explicit 700 under serif`, () => {
88
+ for (const [fontWeight] of knobWeights) {
89
+ for (const headingFont of ["sans-serif", "serif"] as const) {
90
+ const { weight } = measure(
91
+ <Preset overrides={{ fontWeight, headingFont }}>
92
+ <H2 margin={0} size="$1" fontWeight="700" color="$color12">
93
+ {LABEL}
94
+ </H2>
95
+ </Preset>,
96
+ scheme,
97
+ );
98
+ expect({ fontWeight, headingFont, weight }).toEqual({
99
+ fontWeight,
100
+ headingFont,
101
+ weight: "700",
102
+ });
103
+ }
104
+ }
105
+ });
106
+ }
107
+ });
108
+
109
+ describe("sized body voices take fontWeight and bodyFont together (MPO-446)", () => {
110
+ for (const scheme of schemes) {
111
+ for (const [name, Voice] of [
112
+ ["Text", Text],
113
+ ["SizableText", SizableText],
114
+ ["Paragraph", Paragraph],
115
+ ] as const) {
116
+ it(`${scheme}: ${name} size $3 is 700 Georgia under bold + serif`, () => {
117
+ const m = measure(
118
+ <Preset overrides={{ fontWeight: "bold", bodyFont: "serif" }}>
119
+ <Voice size="$3">{LABEL}</Voice>
120
+ </Preset>,
121
+ scheme,
122
+ );
123
+ expect(m).toEqual({ weight: "700", family: "Georgia" });
124
+ });
125
+ }
126
+ }
127
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * MPO-446 native arm: the same matrix as ./textWeight.spec.tsx under the
3
+ * native platform flags. A file-scoped `vi.mock` is one module graph (see
4
+ * ./Heading.native.spec.tsx for why that matters); the props channel is the
5
+ * only channel this arm can carry the knob on, which is the one native has.
6
+ */
7
+ import { cleanup } from "@testing-library/react";
8
+ import { afterEach, describe, expect, it, vi } from "vitest";
9
+ import { registerWeightMatrix } from "../tests/textWeightMatrix";
10
+
11
+ vi.mock("@multiplatform.one/platform", async (importOriginal) => ({
12
+ ...(await importOriginal<typeof import("@multiplatform.one/platform")>()),
13
+ isWeb: false,
14
+ isNative: true,
15
+ }));
16
+
17
+ afterEach(cleanup);
18
+
19
+ describe("house typography — fontWeight x size x family (native, MPO-446)", () => {
20
+ it("really is running with the native platform flags", async () => {
21
+ const platform = await import("@multiplatform.one/platform");
22
+ expect(platform.isWeb).toBe(false);
23
+ expect(platform.isNative).toBe(true);
24
+ });
25
+
26
+ registerWeightMatrix();
27
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * MPO-446 web arm: the fontWeight knob reaches sized house text. The matrix
3
+ * and the reason a size prop used to block the knob live in
4
+ * ../tests/textWeightMatrix.tsx; ./textWeight.native.spec.tsx runs the same table
5
+ * with the native platform flags.
6
+ */
7
+ import { afterEach, describe } from "vitest";
8
+ import { cleanup } from "@testing-library/react";
9
+ import { registerWeightMatrix } from "../tests/textWeightMatrix";
10
+
11
+ afterEach(cleanup);
12
+
13
+ describe("house typography — fontWeight x size x family (web, MPO-446)", () => {
14
+ registerWeightMatrix();
15
+ });
@@ -39,16 +39,8 @@ import {
39
39
  import React, { useState, useMemo, useCallback, useEffect, useId, useRef } from "react";
40
40
  import { useTranslation } from "../shared/i18n";
41
41
  import { Button, FloatingPanel, Input, zIndex } from "@multiplatform.one/forms";
42
- import {
43
- AnimatePresence,
44
- ScrollView,
45
- Text,
46
- XStack,
47
- YStack,
48
- isWeb,
49
- styled,
50
- type YStackProps,
51
- } from "tamagui";
42
+ import { AnimatePresence, Text, XStack, YStack, isWeb, styled, type YStackProps } from "tamagui";
43
+ import { ScrollView } from "../ScrollView";
52
44
  import { useDirection } from "../hooks/useDirection";
53
45
 
54
46
  /**
@@ -89,18 +81,6 @@ export interface CalendarEventDraft {
89
81
  end: Date;
90
82
  }
91
83
 
92
- /**
93
- * Readable chip text for an arbitrary consumer-supplied event color.
94
- * White text on mid-tone brand hexes (#10b981, #f59e0b) measures 2-3.7:1 —
95
- * pick black/white by WCAG relative luminance instead. Theme tokens fall
96
- * back to $accentColor, the guaranteed pair for $accentBackground.
97
- *
98
- * The dark candidate is pure black: the old #1a1a1a candidate was scored
99
- * against ideal black (L=0) but rendered at L=0.0103, overstating its own
100
- * contrast — measured 4.11:1 on #8B5CF6 while claiming 4.96 (Axiom 15:
101
- * foreground on fills is luminance-COMPUTED, and the computation must match
102
- * the rendered pixels). Black keeps every sanctioned step-9 solid ≥ 4.5:1.
103
- */
104
84
  /**
105
85
  * Inline-END hairline rule (P-16 RTL mirroring): grid column separators sit
106
86
  * on the logical end edge so the grid mirrors with the writing direction.
@@ -132,8 +112,26 @@ function gridCellEdges(end: boolean, bottom: boolean) {
132
112
  };
133
113
  }
134
114
 
135
- export function eventChipTextColor(bg?: string): string {
136
- if (!bg || !bg.startsWith("#")) return "$accentColor";
115
+ /**
116
+ * Readable chip text for an event's fill.
117
+ * White text on mid-tone brand hexes (#10b981, #f59e0b) measures 2-3.7:1 —
118
+ * pick black/white by WCAG relative luminance instead.
119
+ *
120
+ * An event with no color paints `$accentBackground`, and its text is
121
+ * `onAccent`: the fill's readable anchor, which the caller resolves with
122
+ * `useReadableTextOn("$accentBackground")`. `$accentColor` is accent ink for
123
+ * the page, not for the fill; on it the fallback chip measured 1.76:1 in light
124
+ * and 3.84:1 in dark (MPO-468). A token color keeps `$accentColor`.
125
+ *
126
+ * The dark candidate is pure black: the old #1a1a1a candidate was scored
127
+ * against ideal black (L=0) but rendered at L=0.0103, overstating its own
128
+ * contrast — measured 4.11:1 on #8B5CF6 while claiming 4.96 (Axiom 15:
129
+ * foreground on fills is luminance-COMPUTED, and the computation must match
130
+ * the rendered pixels). Black keeps every sanctioned step-9 solid ≥ 4.5:1.
131
+ */
132
+ export function eventChipTextColor(bg?: string, onAccent?: string): string {
133
+ if (!bg) return onAccent ?? "$accentColor";
134
+ if (!bg.startsWith("#")) return "$accentColor";
137
135
  const hex = bg.slice(1);
138
136
  const full =
139
137
  hex.length === 3
@@ -652,6 +650,7 @@ function SettleGhost({
652
650
  borderRadius: Record<string, unknown>;
653
651
  onDone: () => void;
654
652
  }) {
653
+ const onAccent = useReadableTextOn("$accentBackground");
655
654
  const [moved, setMoved] = useState(false);
656
655
  useEffect(() => {
657
656
  // Double rAF so the ghost paints at `from` before the tween retargets it
@@ -690,7 +689,7 @@ function SettleGhost({
690
689
  },
691
690
  } as any)}
692
691
  >
693
- <Text fontSize="$2" numberOfLines={1} color={eventChipTextColor(settle.color)}>
692
+ <Text fontSize="$2" numberOfLines={1} color={eventChipTextColor(settle.color, onAccent)}>
694
693
  {settle.title}
695
694
  </Text>
696
695
  </YStack>
@@ -1662,7 +1661,7 @@ export function Calendar({
1662
1661
  {...knobProps.body}
1663
1662
  fontSize={compact ? "$1" : "$2"}
1664
1663
  numberOfLines={compact ? 1 : 2}
1665
- color={eventChipTextColor(event.color)}
1664
+ color={eventChipTextColor(event.color, onAccent)}
1666
1665
  >
1667
1666
  {event.title}
1668
1667
  </Text>
@@ -1679,6 +1678,7 @@ export function Calendar({
1679
1678
  getEventDragProps,
1680
1679
  eventA11yProps,
1681
1680
  draggingEventId,
1681
+ onAccent,
1682
1682
  ],
1683
1683
  );
1684
1684
 
@@ -1958,12 +1958,16 @@ export function Calendar({
1958
1958
  fontSize="$1"
1959
1959
  fontWeight="600"
1960
1960
  numberOfLines={1}
1961
- color={eventChipTextColor(event.color)}
1961
+ color={eventChipTextColor(event.color, onAccent)}
1962
1962
  >
1963
1963
  {event.title}
1964
1964
  </Text>
1965
1965
  {blockHeight >= 34 && (
1966
- <Text fontSize="$1" numberOfLines={1} color={eventChipTextColor(event.color)}>
1966
+ <Text
1967
+ fontSize="$1"
1968
+ numberOfLines={1}
1969
+ color={eventChipTextColor(event.color, onAccent)}
1970
+ >
1967
1971
  {formatTimeRangeLabel(start, end)}
1968
1972
  </Text>
1969
1973
  )}
@@ -2517,7 +2521,11 @@ export function Calendar({
2517
2521
  }),
2518
2522
  } as any)}
2519
2523
  >
2520
- <Text fontSize="$2" numberOfLines={1} color={eventChipTextColor(dragGhost.event.color)}>
2524
+ <Text
2525
+ fontSize="$2"
2526
+ numberOfLines={1}
2527
+ color={eventChipTextColor(dragGhost.event.color, onAccent)}
2528
+ >
2521
2529
  {dragGhost.event.title}
2522
2530
  </Text>
2523
2531
  </YStack>
@@ -15,19 +15,8 @@ import {
15
15
  } from "@multiplatform.one/theme";
16
16
  import React, { useCallback, useMemo, useState, type ReactNode } from "react";
17
17
  import { useTranslation } from "../shared/i18n";
18
- import {
19
- ScrollView,
20
- Text,
21
- XStack,
22
- YStack,
23
- isWeb,
24
- type YStackProps,
25
- H2,
26
- H3,
27
- H4,
28
- H5,
29
- H6,
30
- } from "tamagui";
18
+ import { Text, XStack, YStack, isWeb, type YStackProps, H2, H3, H4, H5, H6 } from "tamagui";
19
+ import { ScrollView } from "../ScrollView";
31
20
 
32
21
  /**
33
22
  * LC-17 SECTION-HEADING-SCALE + DG-A11Y-02: card sub-titles read at the ONE
@@ -11,7 +11,10 @@ const resizeCallbacks = new Map<Element, () => void>();
11
11
  vi.stubGlobal(
12
12
  "ResizeObserver",
13
13
  class {
14
- constructor(private callback: ResizeObserverCallback) {}
14
+ private callback: ResizeObserverCallback;
15
+ constructor(callback: ResizeObserverCallback) {
16
+ this.callback = callback;
17
+ }
15
18
  observe(target: Element) {
16
19
  const notify = () =>
17
20
  this.callback([{ target } as ResizeObserverEntry], this as unknown as ResizeObserver);
@@ -22,7 +22,8 @@ import { Button } from "@multiplatform.one/forms";
22
22
  import React, { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
23
23
  import { useTranslation } from "../shared/i18n";
24
24
  import { ScrollView as RNScrollView } from "react-native";
25
- import { ScrollView, Text, XStack, YStack, isWeb, type YStackProps } from "tamagui";
25
+ import { Text, XStack, YStack, isWeb, type YStackProps } from "tamagui";
26
+ import { ScrollView } from "../ScrollView";
26
27
  import {
27
28
  addDays,
28
29
  barGeometry,
@@ -424,6 +425,36 @@ function GanttBarLabel({ title, fill }: { title: string; fill: string }) {
424
425
  );
425
426
  }
426
427
 
428
+ /** Open-end mark: the bar label's ink, so it reads on every fill. */
429
+ function GanttOpenEdgeMark({
430
+ fill,
431
+ glyph,
432
+ side,
433
+ edge,
434
+ }: {
435
+ fill: string;
436
+ glyph: string;
437
+ side: { left: number } | { right: number };
438
+ edge: "start" | "end";
439
+ }) {
440
+ const { knobProps } = useResolvedKnobs();
441
+ const ink = useReadableTextOn(fill);
442
+ return (
443
+ <Text
444
+ position="absolute"
445
+ {...side}
446
+ {...knobProps.label}
447
+ color={ink ?? "$color12"}
448
+ opacity={0.75}
449
+ pointerEvents="none"
450
+ aria-hidden
451
+ {...{ [`data-gantt-open-${edge}`]: "true" }}
452
+ >
453
+ {glyph}
454
+ </Text>
455
+ );
456
+ }
457
+
427
458
  /** Row-identity label: selected fill gets readable on-accent ink (LC-71). */
428
459
  function GanttTaskName({
429
460
  title,
@@ -1386,32 +1417,15 @@ export function GanttView({
1386
1417
 
1387
1418
  {/* "This end is not an end" — a mark, never a cap. */}
1388
1419
  {geom.openStart && (
1389
- <Text
1390
- position="absolute"
1391
- {...openStartSide}
1392
- {...knobProps.label}
1393
- color="$color12"
1394
- opacity={0.75}
1395
- pointerEvents="none"
1396
- aria-hidden
1397
- {...{ "data-gantt-open-start": "true" }}
1398
- >
1399
- {rtl ? "»" : "«"}
1400
- </Text>
1420
+ <GanttOpenEdgeMark
1421
+ fill={fill}
1422
+ glyph={rtl ? "»" : "«"}
1423
+ side={openStartSide}
1424
+ edge="start"
1425
+ />
1401
1426
  )}
1402
1427
  {geom.openEnd && (
1403
- <Text
1404
- position="absolute"
1405
- {...openEndSide}
1406
- {...knobProps.label}
1407
- color="$color12"
1408
- opacity={0.75}
1409
- pointerEvents="none"
1410
- aria-hidden
1411
- {...{ "data-gantt-open-end": "true" }}
1412
- >
1413
- {rtl ? "«" : "»"}
1414
- </Text>
1428
+ <GanttOpenEdgeMark fill={fill} glyph={rtl ? "«" : "»"} side={openEndSide} edge="end" />
1415
1429
  )}
1416
1430
 
1417
1431
  {/* Live date label while dragging (honest preview of the commit). */}
@@ -1780,6 +1794,7 @@ export function GanttView({
1780
1794
  Tamagui's styled ScrollView does not deliver onScroll there, and
1781
1795
  the pinned axis follower depends on it. */}
1782
1796
  <CanvasScroller
1797
+ keyboardShouldPersistTaps="handled"
1783
1798
  ref={hScrollRef}
1784
1799
  horizontal
1785
1800
  {...(isWeb ? { flex: 1 } : null)}
@@ -892,6 +892,7 @@ function KanbanColumnView<T>({
892
892
  !isDropTarget && <EmptyState compact title={emptyColumnMessage} opacity={0.5} flex={1} />
893
893
  ) : (
894
894
  <FlashList
895
+ keyboardShouldPersistTaps="handled"
895
896
  ref={(list: any) => {
896
897
  handle.list = list;
897
898
  }}
@@ -20,16 +20,8 @@ import React, {
20
20
  useState,
21
21
  type ReactNode,
22
22
  } from "react";
23
- import {
24
- AnimatePresence,
25
- ListItem,
26
- ScrollView,
27
- Text,
28
- View,
29
- XStack,
30
- YStack,
31
- type YStackProps,
32
- } from "tamagui";
23
+ import { AnimatePresence, ListItem, Text, View, XStack, YStack, type YStackProps } from "tamagui";
24
+ import { ScrollView } from "../ScrollView";
33
25
  import { componentColors } from "../componentColors";
34
26
  import { AsyncBoundary, useAsyncCount } from "../layouts/AsyncBoundary";
35
27
  import { EmptyState } from "../layouts/page";
@@ -451,6 +451,7 @@ export function List<T>({
451
451
  }}
452
452
  >
453
453
  <FlashList
454
+ keyboardShouldPersistTaps="handled"
454
455
  ref={flashListRef}
455
456
  data={items}
456
457
  extraData={{ selected: effectiveSelected, ring: showKeyboardRing }}
@@ -18,7 +18,8 @@ import {
18
18
  import React, { useCallback, useMemo, useRef, useState, type ReactNode } from "react";
19
19
  import { useTranslation } from "../shared/i18n";
20
20
  import { ScrollView as RNScrollView } from "react-native";
21
- import { ScrollView, Text, XStack, YStack, isWeb, type YStackProps } from "tamagui";
21
+ import { Text, XStack, YStack, isWeb, type YStackProps } from "tamagui";
22
+ import { ScrollView } from "../ScrollView";
22
23
  import { formatRangeLabel } from "./ganttMath";
23
24
  import {
24
25
  alignMatrixRange,
@@ -634,6 +635,7 @@ export function MatrixView({
634
635
  </YStack>
635
636
 
636
637
  <CanvasScroller
638
+ keyboardShouldPersistTaps="handled"
637
639
  horizontal
638
640
  {...(isWeb ? { flex: 1 } : null)}
639
641
  showsHorizontalScrollIndicator
@@ -30,7 +30,8 @@ import {
30
30
  import { useHotkey } from "@tanstack/react-hotkeys";
31
31
  import React, { useState, useMemo, useCallback, useId, useRef } from "react";
32
32
  import { useTranslation } from "../shared/i18n";
33
- import { ScrollView, Text, XStack, YStack, type YStackProps } from "tamagui";
33
+ import { Text, XStack, YStack, type YStackProps } from "tamagui";
34
+ import { ScrollView } from "../ScrollView";
34
35
 
35
36
  const AGGREGATIONS = ["count", "sum", "avg", "min", "max"] as const;
36
37
 
@@ -1468,6 +1468,7 @@ export function TreeView<T = unknown>({
1468
1468
  }}
1469
1469
  >
1470
1470
  <FlashList
1471
+ keyboardShouldPersistTaps="handled"
1471
1472
  ref={attachList}
1472
1473
  data={flatNodes}
1473
1474
  drawDistance={overscan * estimateSize}
@@ -1702,7 +1703,7 @@ export function TreeView<T = unknown>({
1702
1703
  marginVertical={-handleOutset}
1703
1704
  hitSlop={pressTargetHitSlop(caretSize)}
1704
1705
  cursor="grab"
1705
- opacity={0.45}
1706
+ opacity={0.6}
1706
1707
  transition={transition as any}
1707
1708
  hoverStyle={{ opacity: 1 }}
1708
1709
  onPress={(e: any) => e?.stopPropagation?.()}