@multiplatform.one/components 7.81.2 → 7.82.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 (309) hide show
  1. package/dist/cjs/appShell/AppShell.cjs +1 -9
  2. package/dist/cjs/appShell/AppShell.native.js +1 -9
  3. package/dist/cjs/appShell/AppShell.native.js.map +1 -1
  4. package/dist/cjs/appShell/BottomChromeClearance.cjs +41 -0
  5. package/dist/cjs/appShell/BottomChromeClearance.native.js +45 -0
  6. package/dist/cjs/appShell/BottomChromeClearance.native.js.map +1 -0
  7. package/dist/cjs/appShell/bottomChrome.cjs +1 -12
  8. package/dist/cjs/appShell/bottomChrome.native.js +1 -13
  9. package/dist/cjs/appShell/bottomChrome.native.js.map +1 -1
  10. package/dist/cjs/appShell/index.cjs +3 -1
  11. package/dist/cjs/appShell/index.native.js +2 -0
  12. package/dist/cjs/appShell/index.native.js.map +1 -1
  13. package/dist/cjs/code/CodeBlock/index.cjs +38 -31
  14. package/dist/cjs/code/CodeBlock/index.native.js +38 -31
  15. package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
  16. package/dist/cjs/layouts/EmptyState.cjs +9 -43
  17. package/dist/cjs/layouts/EmptyState.native.js +9 -45
  18. package/dist/cjs/layouts/EmptyState.native.js.map +1 -1
  19. package/dist/cjs/layouts/barTitleSpan.cjs +54 -0
  20. package/dist/cjs/layouts/barTitleSpan.native.js +78 -0
  21. package/dist/cjs/layouts/barTitleSpan.native.js.map +1 -0
  22. package/dist/cjs/layouts/page.cjs +134 -49
  23. package/dist/cjs/layouts/page.native.js +147 -53
  24. package/dist/cjs/layouts/page.native.js.map +1 -1
  25. package/dist/esm/Accordion/index.native.js.map +1 -1
  26. package/dist/esm/AnimateHeight/index.native.js.map +1 -1
  27. package/dist/esm/Avatar/getInitials.native.js.map +1 -1
  28. package/dist/esm/Avatar/index.native.js.map +1 -1
  29. package/dist/esm/Badge/index.native.js.map +1 -1
  30. package/dist/esm/Button.native.js.map +1 -1
  31. package/dist/esm/Carousel/index.native.js.map +1 -1
  32. package/dist/esm/CheckGlyph/MenuCheckColumn.native.js.map +1 -1
  33. package/dist/esm/CheckGlyph/index.native.js.map +1 -1
  34. package/dist/esm/Chip/index.native.js.map +1 -1
  35. package/dist/esm/Collapsible.native.js.map +1 -1
  36. package/dist/esm/ContextMenu/index.native.js.map +1 -1
  37. package/dist/esm/CopyField/CopyMenuButton.native.js.map +1 -1
  38. package/dist/esm/CopyField/index.native.js.map +1 -1
  39. package/dist/esm/Dialog.native.js.map +1 -1
  40. package/dist/esm/Diff/diff.native.js.map +1 -1
  41. package/dist/esm/Diff/index.native.js.map +1 -1
  42. package/dist/esm/DotIndicator/index.native.js.map +1 -1
  43. package/dist/esm/DropdownMenu/index.native.js.map +1 -1
  44. package/dist/esm/DropdownMenu/menuSettle.native.js.map +1 -1
  45. package/dist/esm/ElapsedProgress/index.native.js.map +1 -1
  46. package/dist/esm/ErrorBoundary/index.native.js.map +1 -1
  47. package/dist/esm/Heading.native.js.map +1 -1
  48. package/dist/esm/Kbd/index.native.js.map +1 -1
  49. package/dist/esm/Kbd/keys.native.js.map +1 -1
  50. package/dist/esm/Lightbox/MediaControl.native.js.map +1 -1
  51. package/dist/esm/Lightbox/index.gnome.native.js.map +1 -1
  52. package/dist/esm/Lightbox/shared.native.js.map +1 -1
  53. package/dist/esm/Lightbox/useLightbox.native.js.map +1 -1
  54. package/dist/esm/MachineLiteral/index.native.js.map +1 -1
  55. package/dist/esm/Material/index.native.js.map +1 -1
  56. package/dist/esm/Meter/index.native.js.map +1 -1
  57. package/dist/esm/Meter/meter.native.js.map +1 -1
  58. package/dist/esm/OverflowRow/index.native.js.map +1 -1
  59. package/dist/esm/Pagination.native.js.map +1 -1
  60. package/dist/esm/Popover.native.js.map +1 -1
  61. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  62. package/dist/esm/Quote/index.native.js.map +1 -1
  63. package/dist/esm/SchemeToggle/index.native.js.map +1 -1
  64. package/dist/esm/ScrollView/revealFocusedField.native.js.map +1 -1
  65. package/dist/esm/Separator/index.native.js.map +1 -1
  66. package/dist/esm/Sheet.native.js.map +1 -1
  67. package/dist/esm/SheetModal/SheetHeader.native.js.map +1 -1
  68. package/dist/esm/SheetModal/SheetNavigation.native.js.map +1 -1
  69. package/dist/esm/SheetModal/detents.native.js.map +1 -1
  70. package/dist/esm/SheetModal/useSheetFrameFocus.native.js.map +1 -1
  71. package/dist/esm/Skeleton/index.native.js.map +1 -1
  72. package/dist/esm/SkipLink/index.native.js.map +1 -1
  73. package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
  74. package/dist/esm/SortableList/index.native.js.map +1 -1
  75. package/dist/esm/SortableList/useSortableRows.native.js.map +1 -1
  76. package/dist/esm/SortableList/useUserOrder.native.js.map +1 -1
  77. package/dist/esm/Tabs/index.native.js.map +1 -1
  78. package/dist/esm/Text.native.js.map +1 -1
  79. package/dist/esm/ThemePresetPicker/index.native.js.map +1 -1
  80. package/dist/esm/Toast/index.native.js.map +1 -1
  81. package/dist/esm/Toast/store.native.js.map +1 -1
  82. package/dist/esm/Tooltip/nativePress.native.js.map +1 -1
  83. package/dist/esm/Tooltip/tooltipShared.native.js.map +1 -1
  84. package/dist/esm/TreeSelect/index.native.js.map +1 -1
  85. package/dist/esm/TreeSelect/treeSelectModel.native.js.map +1 -1
  86. package/dist/esm/Video/PlayerChrome.native.js.map +1 -1
  87. package/dist/esm/Video/VideoFrame.native.js.map +1 -1
  88. package/dist/esm/Video/types.native.js.map +1 -1
  89. package/dist/esm/Wheel/index.native.js.map +1 -1
  90. package/dist/esm/Wizard/index.native.js.map +1 -1
  91. package/dist/esm/actions/ActionBar.native.js.map +1 -1
  92. package/dist/esm/actions/DangerSection.native.js.map +1 -1
  93. package/dist/esm/actions/DestructiveAction.native.js.map +1 -1
  94. package/dist/esm/actions/HeaderActions.native.js.map +1 -1
  95. package/dist/esm/actions/StandardAction.native.js.map +1 -1
  96. package/dist/esm/actions/devWarn.native.js.map +1 -1
  97. package/dist/esm/actions/index.native.js.map +1 -1
  98. package/dist/esm/actions/severity.native.js.map +1 -1
  99. package/dist/esm/appShell/AppShell.mjs +2 -10
  100. package/dist/esm/appShell/AppShell.mjs.map +1 -1
  101. package/dist/esm/appShell/AppShell.native.js +2 -10
  102. package/dist/esm/appShell/AppShell.native.js.map +1 -1
  103. package/dist/esm/appShell/BottomChromeClearance.mjs +16 -0
  104. package/dist/esm/appShell/BottomChromeClearance.mjs.map +1 -0
  105. package/dist/esm/appShell/BottomChromeClearance.native.js +17 -0
  106. package/dist/esm/appShell/BottomChromeClearance.native.js.map +1 -0
  107. package/dist/esm/appShell/Dock.native.js.map +1 -1
  108. package/dist/esm/appShell/MpoTabHost.native.js.map +1 -1
  109. package/dist/esm/appShell/ScopeMenu.native.js.map +1 -1
  110. package/dist/esm/appShell/SettingsSplit.native.js.map +1 -1
  111. package/dist/esm/appShell/Springboard.native.js.map +1 -1
  112. package/dist/esm/appShell/SystemBar.native.js.map +1 -1
  113. package/dist/esm/appShell/TabBar.native.js.map +1 -1
  114. package/dist/esm/appShell/appTile.native.js.map +1 -1
  115. package/dist/esm/appShell/bottomChrome.mjs +3 -13
  116. package/dist/esm/appShell/bottomChrome.mjs.map +1 -1
  117. package/dist/esm/appShell/bottomChrome.native.js +3 -14
  118. package/dist/esm/appShell/bottomChrome.native.js.map +1 -1
  119. package/dist/esm/appShell/index.mjs +2 -1
  120. package/dist/esm/appShell/index.mjs.map +1 -1
  121. package/dist/esm/appShell/index.native.js +2 -1
  122. package/dist/esm/appShell/index.native.js.map +1 -1
  123. package/dist/esm/charts/Axis.native.js.map +1 -1
  124. package/dist/esm/charts/BarChart.native.js.map +1 -1
  125. package/dist/esm/charts/ChartDataView.native.js.map +1 -1
  126. package/dist/esm/charts/ChartSurface.native.js.map +1 -1
  127. package/dist/esm/charts/LineChart.native.js.map +1 -1
  128. package/dist/esm/charts/PieChart.native.js.map +1 -1
  129. package/dist/esm/charts/Sparkline.native.js.map +1 -1
  130. package/dist/esm/charts/accessibility.native.js.map +1 -1
  131. package/dist/esm/charts/chartHeight.native.js.map +1 -1
  132. package/dist/esm/charts/composeChart.native.js.map +1 -1
  133. package/dist/esm/charts/index.native.js.map +1 -1
  134. package/dist/esm/charts/math.native.js.map +1 -1
  135. package/dist/esm/charts/series.native.js.map +1 -1
  136. package/dist/esm/charts/svg.native.js.map +1 -1
  137. package/dist/esm/charts/types.native.js.map +1 -1
  138. package/dist/esm/charts/useChartTheme.native.js.map +1 -1
  139. package/dist/esm/code/Code/index.native.js.map +1 -1
  140. package/dist/esm/code/CodeBlock/index.mjs +39 -32
  141. package/dist/esm/code/CodeBlock/index.mjs.map +1 -1
  142. package/dist/esm/code/CodeBlock/index.native.js +39 -32
  143. package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
  144. package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
  145. package/dist/esm/code/Highlighter/houseSyntaxTheme.native.js.map +1 -1
  146. package/dist/esm/code/Highlighter/index.native.js.map +1 -1
  147. package/dist/esm/code/Highlighter/namedGroupMatchAll.native.js.map +1 -1
  148. package/dist/esm/code/LogView.native.js.map +1 -1
  149. package/dist/esm/code/Pre/index.native.js.map +1 -1
  150. package/dist/esm/code/index.native.js.map +1 -1
  151. package/dist/esm/componentColors.native.js.map +1 -1
  152. package/dist/esm/feedback/ConfirmDialog.native.js.map +1 -1
  153. package/dist/esm/feedback/NotifyHost.native.js.map +1 -1
  154. package/dist/esm/feedback/copy.native.js.map +1 -1
  155. package/dist/esm/feedback/index.native.js.map +1 -1
  156. package/dist/esm/feedback/notify.native.js.map +1 -1
  157. package/dist/esm/feedback/route.native.js.map +1 -1
  158. package/dist/esm/feedback/store.native.js.map +1 -1
  159. package/dist/esm/formLayouts/FormLayouts.native.js.map +1 -1
  160. package/dist/esm/formLayouts/PreferencePanel.native.js.map +1 -1
  161. package/dist/esm/formLayouts/index.native.js.map +1 -1
  162. package/dist/esm/hooks/index.native.js.map +1 -1
  163. package/dist/esm/hooks/useClipboard.native.js.map +1 -1
  164. package/dist/esm/hooks/useDocumentTitle.native.js.map +1 -1
  165. package/dist/esm/hooks/useHoldCue.native.js.map +1 -1
  166. package/dist/esm/icons/CheckRegular.native.js.map +1 -1
  167. package/dist/esm/icons/MinusRegular.native.js.map +1 -1
  168. package/dist/esm/icons/index.native.js.map +1 -1
  169. package/dist/esm/icons/lucide.native.js.map +1 -1
  170. package/dist/esm/icons/themed.native.js.map +1 -1
  171. package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
  172. package/dist/esm/images/Image/framing.native.js.map +1 -1
  173. package/dist/esm/images/Image/mediaRadius.native.js.map +1 -1
  174. package/dist/esm/images/Image/shared.native.js.map +1 -1
  175. package/dist/esm/images/Image/types.native.js.map +1 -1
  176. package/dist/esm/images/index.native.js.map +1 -1
  177. package/dist/esm/index.native.js.map +1 -1
  178. package/dist/esm/knobImmuneExports.native.js.map +1 -1
  179. package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
  180. package/dist/esm/layouts/Debug/index.native.js.map +1 -1
  181. package/dist/esm/layouts/EmptyState.mjs +9 -43
  182. package/dist/esm/layouts/EmptyState.mjs.map +1 -1
  183. package/dist/esm/layouts/EmptyState.native.js +9 -45
  184. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  185. package/dist/esm/layouts/IdentityPlate.native.js.map +1 -1
  186. package/dist/esm/layouts/KeyboardAvoidingWrapper/behavior.native.js.map +1 -1
  187. package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
  188. package/dist/esm/layouts/PaneScaffold.native.js.map +1 -1
  189. package/dist/esm/layouts/SafeAreaWrapper/insets.native.js.map +1 -1
  190. package/dist/esm/layouts/StatusDot.native.js.map +1 -1
  191. package/dist/esm/layouts/StatusGlyphStrip.native.js.map +1 -1
  192. package/dist/esm/layouts/ViewSwitcher.native.js.map +1 -1
  193. package/dist/esm/layouts/attachments.native.js.map +1 -1
  194. package/dist/esm/layouts/barTitleSpan.mjs +27 -0
  195. package/dist/esm/layouts/barTitleSpan.mjs.map +1 -0
  196. package/dist/esm/layouts/barTitleSpan.native.js +48 -0
  197. package/dist/esm/layouts/barTitleSpan.native.js.map +1 -0
  198. package/dist/esm/layouts/bottomAccessory.native.js.map +1 -1
  199. package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
  200. package/dist/esm/layouts/index.native.js.map +1 -1
  201. package/dist/esm/layouts/page.mjs +136 -51
  202. package/dist/esm/layouts/page.mjs.map +1 -1
  203. package/dist/esm/layouts/page.native.js +149 -55
  204. package/dist/esm/layouts/page.native.js.map +1 -1
  205. package/dist/esm/layouts/pageHeadingScope.native.js.map +1 -1
  206. package/dist/esm/layouts/screenScroll.native.js.map +1 -1
  207. package/dist/esm/layouts/shellSidebarSlot.native.js.map +1 -1
  208. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  209. package/dist/esm/layouts/tags.native.js.map +1 -1
  210. package/dist/esm/layouts/timeline.native.js.map +1 -1
  211. package/dist/esm/layouts/toolbar.native.js.map +1 -1
  212. package/dist/esm/shared/actionScan.native.js.map +1 -1
  213. package/dist/esm/shared/i18n.native.js.map +1 -1
  214. package/dist/esm/shared/restoreRetries.native.js.map +1 -1
  215. package/dist/esm/shared/t.native.js.map +1 -1
  216. package/dist/esm/shared/textRidesText.native.js.map +1 -1
  217. package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
  218. package/dist/esm/surfaces.native.js.map +1 -1
  219. package/dist/esm/tamagui.native.js.map +1 -1
  220. package/dist/esm/testSetup.native.js.map +1 -1
  221. package/dist/esm/utils/setTinted.native.js.map +1 -1
  222. package/dist/esm/utils/unwrapText.native.js.map +1 -1
  223. package/dist/esm/views/Calendar.native.js.map +1 -1
  224. package/dist/esm/views/Dashboard.native.js.map +1 -1
  225. package/dist/esm/views/Gantt.native.js.map +1 -1
  226. package/dist/esm/views/GanttDateGrip.native.js.map +1 -1
  227. package/dist/esm/views/ImageGrid.native.js.map +1 -1
  228. package/dist/esm/views/List.native.js.map +1 -1
  229. package/dist/esm/views/ListGroup.native.js.map +1 -1
  230. package/dist/esm/views/ListRow.native.js.map +1 -1
  231. package/dist/esm/views/Matrix.native.js.map +1 -1
  232. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  233. package/dist/esm/views/ShortcutCard.native.js.map +1 -1
  234. package/dist/esm/views/TreeView.native.js.map +1 -1
  235. package/dist/esm/views/calendarMath.native.js.map +1 -1
  236. package/dist/esm/views/ganttIntervals.native.js.map +1 -1
  237. package/dist/esm/views/ganttMath.native.js.map +1 -1
  238. package/dist/esm/views/index.native.js.map +1 -1
  239. package/dist/esm/views/kanbanDnd.native.js.map +1 -1
  240. package/dist/esm/views/matrixMath.native.js.map +1 -1
  241. package/dist/esm/views/sameNameScope.native.js.map +1 -1
  242. package/dist/esm/views/treeViewDnd.native.js.map +1 -1
  243. package/dist/esm/views/treeViewLayout.native.js.map +1 -1
  244. package/dist/esm/views/treeViewMotion.native.js.map +1 -1
  245. package/dist/esm/views/treeViewReveal.native.js.map +1 -1
  246. package/dist/esm/views/usePageFlowLoadMore.native.js.map +1 -1
  247. package/dist/esm/views/widgetChrome.native.js.map +1 -1
  248. package/dist/jsx/appShell/AppShell.mjs +2 -10
  249. package/dist/jsx/appShell/AppShell.mjs.map +1 -1
  250. package/dist/jsx/appShell/AppShell.native.js +1 -9
  251. package/dist/jsx/appShell/AppShell.native.js.map +1 -1
  252. package/dist/jsx/appShell/BottomChromeClearance.mjs +16 -0
  253. package/dist/jsx/appShell/BottomChromeClearance.mjs.map +1 -0
  254. package/dist/jsx/appShell/BottomChromeClearance.native.js +45 -0
  255. package/dist/jsx/appShell/BottomChromeClearance.native.js.map +1 -0
  256. package/dist/jsx/appShell/bottomChrome.mjs +3 -13
  257. package/dist/jsx/appShell/bottomChrome.mjs.map +1 -1
  258. package/dist/jsx/appShell/bottomChrome.native.js +1 -13
  259. package/dist/jsx/appShell/bottomChrome.native.js.map +1 -1
  260. package/dist/jsx/appShell/index.mjs +2 -1
  261. package/dist/jsx/appShell/index.mjs.map +1 -1
  262. package/dist/jsx/appShell/index.native.js +2 -0
  263. package/dist/jsx/appShell/index.native.js.map +1 -1
  264. package/dist/jsx/code/CodeBlock/index.mjs +39 -32
  265. package/dist/jsx/code/CodeBlock/index.mjs.map +1 -1
  266. package/dist/jsx/code/CodeBlock/index.native.js +38 -31
  267. package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
  268. package/dist/jsx/layouts/EmptyState.mjs +9 -43
  269. package/dist/jsx/layouts/EmptyState.mjs.map +1 -1
  270. package/dist/jsx/layouts/EmptyState.native.js +9 -45
  271. package/dist/jsx/layouts/EmptyState.native.js.map +1 -1
  272. package/dist/jsx/layouts/barTitleSpan.mjs +27 -0
  273. package/dist/jsx/layouts/barTitleSpan.mjs.map +1 -0
  274. package/dist/jsx/layouts/barTitleSpan.native.js +78 -0
  275. package/dist/jsx/layouts/barTitleSpan.native.js.map +1 -0
  276. package/dist/jsx/layouts/page.mjs +136 -51
  277. package/dist/jsx/layouts/page.mjs.map +1 -1
  278. package/dist/jsx/layouts/page.native.js +147 -53
  279. package/dist/jsx/layouts/page.native.js.map +1 -1
  280. package/package.json +9 -9
  281. package/src/appShell/AppShell.spec.tsx +11 -7
  282. package/src/appShell/AppShell.tsx +2 -9
  283. package/src/appShell/BottomChromeClearance.spec.tsx +42 -0
  284. package/src/appShell/BottomChromeClearance.tsx +15 -0
  285. package/src/appShell/bottomChrome.ts +3 -24
  286. package/src/appShell/index.ts +1 -0
  287. package/src/code/CodeBlock/CodeBlock.copy.spec.tsx +13 -0
  288. package/src/code/CodeBlock/index.tsx +46 -30
  289. package/src/layouts/EmptyState.spec.tsx +2 -0
  290. package/src/layouts/EmptyState.tsx +6 -39
  291. package/src/layouts/PageHeader.stories.tsx +238 -38
  292. package/src/layouts/barTitleSpan.spec.ts +35 -0
  293. package/src/layouts/barTitleSpan.ts +44 -0
  294. package/src/layouts/glyphCentre.spec.tsx +218 -0
  295. package/src/layouts/navBar.spec.tsx +58 -2
  296. package/src/layouts/page.tsx +128 -45
  297. package/types/appShell/AppShell.d.ts.map +1 -1
  298. package/types/appShell/BottomChromeClearance.d.ts +10 -0
  299. package/types/appShell/BottomChromeClearance.d.ts.map +1 -0
  300. package/types/appShell/bottomChrome.d.ts +1 -11
  301. package/types/appShell/bottomChrome.d.ts.map +1 -1
  302. package/types/appShell/index.d.ts +1 -0
  303. package/types/appShell/index.d.ts.map +1 -1
  304. package/types/code/CodeBlock/index.d.ts.map +1 -1
  305. package/types/layouts/EmptyState.d.ts.map +1 -1
  306. package/types/layouts/barTitleSpan.d.ts +29 -0
  307. package/types/layouts/barTitleSpan.d.ts.map +1 -0
  308. package/types/layouts/page.d.ts +6 -1
  309. package/types/layouts/page.d.ts.map +1 -1
@@ -0,0 +1,218 @@
1
+ /**
2
+ * LC-176 (FZ-8442): a glyph on a glass circle sits at the circle's centre.
3
+ * Clay on the Kitchen Sink SHC Nodes story: "the icons inside of these are
4
+ * aligned wrong". The back caret was pulled toward the bar's edge and the "+"
5
+ * pushed past its circle's centre.
6
+ *
7
+ * jsdom has no layout, so the ink centre is computed: the offset each box
8
+ * between the glyph and its circle adds (a margin moves a centred child by
9
+ * half of it, a translate by all of it), plus where the glyph's ink sits in
10
+ * its 256-unit Phosphor grid, read from the rendered path.
11
+ */
12
+ import { IconButton } from "@multiplatform.one/forms";
13
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
14
+ import { Surface } from "@multiplatform.one/theme";
15
+ import { PlusIcon, XIcon } from "@phosphor-icons/react";
16
+ import { cleanup, screen } from "@testing-library/react";
17
+ import { afterEach, describe, expect, it, vi } from "vitest";
18
+ import { PageBackButton, ScreenToolbar } from "./page";
19
+
20
+ const IPHONE =
21
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1";
22
+ const GRID = 256;
23
+ const TOLERANCE_PT = 0.5;
24
+
25
+ type Box = { minX: number; maxX: number; minY: number; maxY: number };
26
+
27
+ function arcExtremes(
28
+ box: Box,
29
+ from: [number, number],
30
+ r: number,
31
+ large: number,
32
+ sweep: number,
33
+ to: [number, number],
34
+ ) {
35
+ const [x1, y1] = from;
36
+ const [x2, y2] = to;
37
+ const mx = (x1 - x2) / 2;
38
+ const my = (y1 - y2) / 2;
39
+ const d2 = mx * mx + my * my;
40
+ const radius = Math.max(r, Math.sqrt(d2));
41
+ const k = (large === sweep ? -1 : 1) * Math.sqrt(Math.max(0, radius ** 2 / d2 - 1));
42
+ const cx = k * my + (x1 + x2) / 2;
43
+ const cy = -k * mx + (y1 + y2) / 2;
44
+ const start = Math.atan2(y1 - cy, x1 - cx);
45
+ let delta = Math.atan2(y2 - cy, x2 - cx) - start;
46
+ if (sweep && delta < 0) delta += 2 * Math.PI;
47
+ if (!sweep && delta > 0) delta -= 2 * Math.PI;
48
+ for (const angle of [0, Math.PI / 2, Math.PI, -Math.PI / 2]) {
49
+ let t = angle - start;
50
+ while (t < 0) t += 2 * Math.PI;
51
+ while (t >= 2 * Math.PI) t -= 2 * Math.PI;
52
+ const within = delta >= 0 ? t <= delta : t >= 2 * Math.PI + delta || t === 0;
53
+ if (within) include(box, cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
54
+ }
55
+ }
56
+
57
+ function include(box: Box, x: number, y: number) {
58
+ box.minX = Math.min(box.minX, x);
59
+ box.maxX = Math.max(box.maxX, x);
60
+ box.minY = Math.min(box.minY, y);
61
+ box.maxY = Math.max(box.maxY, y);
62
+ }
63
+
64
+ /** The ink box of an SVG path made of the commands Phosphor uses. */
65
+ function inkBox(d: string): Box {
66
+ const box: Box = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity };
67
+ const tokens = d.match(/[a-zA-Z]|-?(?:\d*\.\d+|\d+)(?:e-?\d+)?/g) ?? [];
68
+ let i = 0;
69
+ let command = "";
70
+ let x = 0;
71
+ let y = 0;
72
+ let startX = 0;
73
+ let startY = 0;
74
+ const num = () => Number(tokens[i++]);
75
+ while (i < tokens.length) {
76
+ if (/[a-zA-Z]/.test(tokens[i]!)) command = tokens[i++]!;
77
+ const rel = command === command.toLowerCase();
78
+ const ox = rel ? x : 0;
79
+ const oy = rel ? y : 0;
80
+ switch (command.toUpperCase()) {
81
+ case "M":
82
+ x = ox + num();
83
+ y = oy + num();
84
+ startX = x;
85
+ startY = y;
86
+ include(box, x, y);
87
+ command = rel ? "l" : "L";
88
+ break;
89
+ case "L":
90
+ x = ox + num();
91
+ y = oy + num();
92
+ include(box, x, y);
93
+ break;
94
+ case "H":
95
+ x = ox + num();
96
+ include(box, x, y);
97
+ break;
98
+ case "V":
99
+ y = oy + num();
100
+ include(box, x, y);
101
+ break;
102
+ case "C": {
103
+ for (let n = 0; n < 2; n++) include(box, ox + num(), oy + num());
104
+ x = ox + num();
105
+ y = oy + num();
106
+ include(box, x, y);
107
+ break;
108
+ }
109
+ case "A": {
110
+ const r = num();
111
+ num();
112
+ num();
113
+ const large = num();
114
+ const sweep = num();
115
+ const to: [number, number] = [ox + num(), oy + num()];
116
+ arcExtremes(box, [x, y], r, large, sweep, to);
117
+ [x, y] = to;
118
+ include(box, x, y);
119
+ break;
120
+ }
121
+ case "Z":
122
+ x = startX;
123
+ y = startY;
124
+ break;
125
+ default:
126
+ throw new Error(`inkBox: unhandled path command ${command}`);
127
+ }
128
+ }
129
+ return box;
130
+ }
131
+
132
+ const px = (value: string | undefined) => Number.parseFloat(value || "0") || 0;
133
+
134
+ function translate(transform: string, axis: "X" | "Y") {
135
+ const one = transform.match(new RegExp(`translate${axis}\\((-?[\\d.]+)px\\)`));
136
+ if (one) return Number(one[1]);
137
+ const both = transform.match(/translate\((-?[\d.]+)px(?:,\s*(-?[\d.]+)px)?\)/);
138
+ if (!both) return 0;
139
+ return axis === "X" ? Number(both[1]) : Number(both[2] ?? 0);
140
+ }
141
+
142
+ /** How far the glyph's ink centre sits from its circle's centre, in pt. */
143
+ function inkOffset(button: HTMLElement) {
144
+ const svg = button.querySelector("svg");
145
+ if (!svg) throw new Error("no glyph");
146
+ const size = Number(svg.getAttribute("width"));
147
+ const scale = size / GRID;
148
+ const box = inkBox(
149
+ Array.from(svg.querySelectorAll("path"))
150
+ .map((path) => path.getAttribute("d") ?? "")
151
+ .join(" "),
152
+ );
153
+ let dx = ((box.minX + box.maxX) / 2 - GRID / 2) * scale;
154
+ let dy = ((box.minY + box.maxY) / 2 - GRID / 2) * scale;
155
+ const frame = getComputedStyle(button);
156
+ dx += (px(frame.paddingLeft) - px(frame.paddingRight)) / 2;
157
+ dy += (px(frame.paddingTop) - px(frame.paddingBottom)) / 2;
158
+ for (let el: Element | null = svg; el && el !== button; el = el.parentElement) {
159
+ const style = getComputedStyle(el);
160
+ dx +=
161
+ (px(style.marginLeft) +
162
+ px(style.marginInlineStart) -
163
+ px(style.marginRight) -
164
+ px(style.marginInlineEnd)) /
165
+ 2 +
166
+ translate(style.transform, "X");
167
+ dy += (px(style.marginTop) - px(style.marginBottom)) / 2 + translate(style.transform, "Y");
168
+ }
169
+ return { dx, dy, size };
170
+ }
171
+
172
+ afterEach(() => {
173
+ vi.unstubAllGlobals();
174
+ cleanup();
175
+ });
176
+
177
+ describe("glyph centre == glass circle centre (LC-176, FZ-8442)", () => {
178
+ it("the ink box reads Phosphor's bold glyphs where they draw", () => {
179
+ const plus = inkBox(
180
+ "M228,128a12,12,0,0,1-12,12H140v76a12,12,0,0,1-24,0V140H40a12,12,0,0,1,0-24h76V40a12,12,0,0,1,24,0v76h76A12,12,0,0,1,228,128Z",
181
+ );
182
+ expect([plus.minX, plus.maxX, plus.minY, plus.maxY].map(Math.round)).toEqual([
183
+ 28, 228, 28, 228,
184
+ ]);
185
+ const caret = inkBox(
186
+ "M168.49,199.51a12,12,0,0,1-17,17l-80-80a12,12,0,0,1,0-17l80-80a12,12,0,0,1,17,17L97,128Z",
187
+ );
188
+ expect(Math.round((caret.minX + caret.maxX) / 2)).toBe(120);
189
+ });
190
+
191
+ it("off glass the back caret keeps its optical pull, so the measure sees margins", () => {
192
+ renderWithProviders(<ScreenToolbar leading={<PageBackButton />} />);
193
+ const back = screen.getByLabelText("Back");
194
+ expect(back.getAttribute("data-mp-icon-button")).not.toBe("glass");
195
+ const { dx, size } = inkOffset(back);
196
+ expect(dx).toBeLessThan(-size * 0.05);
197
+ });
198
+
199
+ it("back caret, + and ✕ sit at their circle's centre within 0.5pt on iPhone", () => {
200
+ vi.stubGlobal("navigator", { userAgent: IPHONE, platform: "iPhone", maxTouchPoints: 5 });
201
+ renderWithProviders(
202
+ <Surface bar>
203
+ <ScreenToolbar
204
+ leading={<PageBackButton />}
205
+ trailing={<IconButton icon={PlusIcon} aria-label="Add node" edge="end" />}
206
+ />
207
+ <IconButton icon={XIcon} aria-label="Close" edge="start" />
208
+ </Surface>,
209
+ );
210
+ for (const name of ["Back", "Add node", "Close"]) {
211
+ const button = screen.getByLabelText(name);
212
+ expect(button.getAttribute("data-mp-icon-button"), name).toBe("glass");
213
+ const { dx, dy } = inkOffset(button);
214
+ expect(Math.abs(dx), `${name} dx`).toBeLessThanOrEqual(TOLERANCE_PT);
215
+ expect(Math.abs(dy), `${name} dy`).toBeLessThanOrEqual(TOLERANCE_PT);
216
+ }
217
+ });
218
+ });
@@ -8,10 +8,10 @@
8
8
  * jsdom has no layout: the large title's geometry is stubbed through
9
9
  * getBoundingClientRect, and scrolling sets scrollTop on the scroll element.
10
10
  */
11
- import { ICON_BUTTON_GLYPH_PX } from "@multiplatform.one/forms";
11
+ import { ICON_BUTTON_GLYPH_PX, IconButton } from "@multiplatform.one/forms";
12
12
  import { renderWithProviders } from "@multiplatform.one/test-utils";
13
13
  import { Preset, useGlyphTintOnSurface } from "@multiplatform.one/theme";
14
- import { SlidersHorizontalIcon } from "@phosphor-icons/react";
14
+ import { PlusIcon, SlidersHorizontalIcon } from "@phosphor-icons/react";
15
15
  import { act, cleanup, fireEvent, screen } from "@testing-library/react";
16
16
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
17
17
  import {
@@ -269,3 +269,59 @@ describe("bar glyphs carry their weight (LC-176)", () => {
269
269
  expect(screen.getByLabelText("Back").closest("[data-mp-icon-button-group]")).toBeNull();
270
270
  });
271
271
  });
272
+
273
+ const IPHONE =
274
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1";
275
+
276
+ describe("inline bar title and glyphs on glass (FZ-8442)", () => {
277
+ afterEach(() => vi.unstubAllGlobals());
278
+
279
+ it("a title is centred in the flow toolbar and shown at rest, clear of the back control", () => {
280
+ renderWithProviders(
281
+ <ScreenToolbar
282
+ leading={<PageBackButton />}
283
+ title="Inbox"
284
+ trailing={<PageBarButton icon={SlidersHorizontalIcon} aria-label="Filter" />}
285
+ />,
286
+ );
287
+ const title = document.querySelector("[data-bar-title]") as HTMLElement;
288
+ expect(title.textContent).toBe("Inbox");
289
+ expect(title.getAttribute("aria-hidden")).not.toBe("true");
290
+ const back = screen.getByLabelText("Back");
291
+ expect(title.contains(back)).toBe(false);
292
+ expect(back.parentElement?.contains(title)).toBe(false);
293
+ });
294
+
295
+ it("the pinned bar shows a given title at rest", () => {
296
+ renderWithProviders(
297
+ <Screen topBar={<ScreenToolbar leading={<PageBackButton />} title="Inbox" />}>
298
+ <div style={{ height: 2000 }} />
299
+ </Screen>,
300
+ );
301
+ const title = document.querySelector("[data-top-bar] [data-bar-title]") as HTMLElement;
302
+ expect(title.textContent).toBe("Inbox");
303
+ expect(title.getAttribute("aria-hidden")).not.toBe("true");
304
+ });
305
+
306
+ it("on glass the back caret drops its pull and a lone + keeps its own circle", () => {
307
+ vi.stubGlobal("navigator", { userAgent: IPHONE, platform: "iPhone", maxTouchPoints: 5 });
308
+ renderWithProviders(
309
+ <Screen topBar={<ScreenToolbar leading={<PageBackButton />} />}>
310
+ <PageHeader
311
+ title="Nodes"
312
+ actions={<IconButton icon={PlusIcon} aria-label="Add node" edge="end" />}
313
+ />
314
+ </Screen>,
315
+ );
316
+ const back = screen.getByLabelText("Back");
317
+ expect(back.getAttribute("data-mp-icon-button")).toBe("glass");
318
+ const caret = back.querySelector("svg")!;
319
+ const pull = Math.round(Number(caret.getAttribute("width")) * (72 / 256));
320
+ expect(Array.from(caret.parentElement!.classList).some((c) => c.includes(`-${pull}px`))).toBe(
321
+ false,
322
+ );
323
+ const add = screen.getByLabelText("Add node");
324
+ expect(add.getAttribute("data-mp-icon-button")).toBe("glass");
325
+ expect(add.closest("[data-mp-glass-capsule]")).toBeNull();
326
+ });
327
+ });
@@ -33,9 +33,15 @@ import {
33
33
  type PageTitleScale,
34
34
  type ParentScreen,
35
35
  } from "@multiplatform.one/theme";
36
- import { Button, IconButtonGroup, type ButtonProps } from "@multiplatform.one/forms";
36
+ import {
37
+ Button,
38
+ IconButtonGroup,
39
+ useGlyphOnPlate,
40
+ type ButtonProps,
41
+ } from "@multiplatform.one/forms";
37
42
  import { CaretLeftIcon, CaretRightIcon, DotsThreeIcon } from "@phosphor-icons/react";
38
43
  import { useState, type ReactNode } from "react";
44
+ import { barTitleSpan, sideExtents } from "./barTitleSpan";
39
45
  import type { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent } from "react-native";
40
46
  import type { ScrollViewProps as TamaguiScrollViewProps, XStackProps, YStackProps } from "tamagui";
41
47
  import {
@@ -52,7 +58,7 @@ import {
52
58
  YStack,
53
59
  isWeb,
54
60
  } from "tamagui";
55
- import { useBottomChromeInset } from "../appShell/bottomChrome";
61
+ import { BottomChromeClearance } from "../appShell/BottomChromeClearance";
56
62
  import { componentColors, sectionHeading } from "../componentColors";
57
63
  import { useDirection } from "../hooks/useDirection";
58
64
  import { useTranslation } from "../shared/i18n";
@@ -280,7 +286,6 @@ function PageShell({
280
286
  const scrollState = useScreenScrollController();
281
287
  useReportScreenScroll(scrollState.state, Boolean(topBar));
282
288
  const [accessories] = useState(createBottomAccessoryStore);
283
- const chromeInset = useBottomChromeInset();
284
289
  const inset = noPadding ? null : (knobProps.panelPadding.padding as string);
285
290
  const cap = maxWidth && !bleed ? maxWidth : null;
286
291
 
@@ -330,15 +335,7 @@ function PageShell({
330
335
 
331
336
  // The floating tab bar covers the bottom of the screen; content scrolls
332
337
  // under it, and this clears its last row.
333
- const chromeClearance =
334
- chromeInset > 0 ? (
335
- <YStack
336
- height={chromeInset}
337
- flexShrink={0}
338
- aria-hidden
339
- data-testid="screen-chrome-clearance"
340
- />
341
- ) : null;
338
+ const chromeClearance = <BottomChromeClearance testID="screen-chrome-clearance" />;
342
339
 
343
340
  if (!scroll) {
344
341
  return shell(
@@ -655,11 +652,94 @@ export { EmptyState, type EmptyStateProps } from "./EmptyState";
655
652
  // ScreenToolbar — minimal back/edit-style screen toolbar
656
653
  // ---------------------------------------------------------------------------
657
654
 
658
- export interface ScreenToolbarProps extends XStackProps {
655
+ export interface ScreenToolbarProps extends Omit<XStackProps, "title"> {
659
656
  /** Leading content (typically a back button). */
660
657
  leading?: ReactNode;
661
658
  /** Trailing content (typically primary/secondary actions). */
662
659
  trailing?: ReactNode;
660
+ /**
661
+ * The bar's inline title (the iOS inline navigation title): centred in the
662
+ * bar, clear of the wider side group, never set against the back control.
663
+ */
664
+ title?: string;
665
+ }
666
+
667
+ function useBarTitleSpan() {
668
+ const none = { x: 0, width: 0 };
669
+ const [box, setBox] = useState({ row: 0, leading: none, trailing: none, title: 0 });
670
+ const set = <K extends keyof typeof box>(key: K, value: (typeof box)[K]) =>
671
+ setBox((prev) =>
672
+ JSON.stringify(prev[key]) === JSON.stringify(value) ? prev : { ...prev, [key]: value },
673
+ );
674
+ const extent = (event: LayoutChangeEvent) => {
675
+ const { x, width } = event.nativeEvent.layout;
676
+ return { x, width };
677
+ };
678
+ return {
679
+ span: barTitleSpan({
680
+ row: box.row,
681
+ title: box.title,
682
+ ...sideExtents(box.row, box.leading, box.trailing),
683
+ }),
684
+ measureRow: (event: LayoutChangeEvent) => set("row", Math.floor(extent(event).width)),
685
+ measureLeading: (event: LayoutChangeEvent) => set("leading", extent(event)),
686
+ measureTrailing: (event: LayoutChangeEvent) => set("trailing", extent(event)),
687
+ measureTitle: (event: LayoutChangeEvent) => set("title", Math.ceil(extent(event).width)),
688
+ };
689
+ }
690
+
691
+ function BarTitle({
692
+ title,
693
+ span,
694
+ onTitleWidth,
695
+ shown = true,
696
+ ...rest
697
+ }: {
698
+ title: string;
699
+ span: { left: number; right: number };
700
+ onTitleWidth: (event: LayoutChangeEvent) => void;
701
+ shown?: boolean;
702
+ } & XStackProps) {
703
+ const { knobProps } = useResolvedKnobs({ component: "ScreenToolbar" });
704
+ const text = {
705
+ ...knobProps.body,
706
+ fontWeight: "600",
707
+ color: "$color12",
708
+ numberOfLines: 1,
709
+ } as const;
710
+ return (
711
+ <>
712
+ {shown ? (
713
+ <XStack
714
+ position="absolute"
715
+ top={0}
716
+ left={0}
717
+ opacity={0}
718
+ pointerEvents="none"
719
+ aria-hidden
720
+ onLayout={onTitleWidth}
721
+ >
722
+ <SizableText {...text}>{title}</SizableText>
723
+ </XStack>
724
+ ) : null}
725
+ <XStack
726
+ position="absolute"
727
+ top={0}
728
+ bottom={0}
729
+ left={span.left}
730
+ right={span.right}
731
+ alignItems="center"
732
+ justifyContent="center"
733
+ pointerEvents="none"
734
+ aria-hidden={!shown}
735
+ data-bar-title=""
736
+ opacity={shown ? 1 : 0}
737
+ {...rest}
738
+ >
739
+ <SizableText {...text}>{title}</SizableText>
740
+ </XStack>
741
+ </>
742
+ );
663
743
  }
664
744
 
665
745
  /**
@@ -679,8 +759,9 @@ export function ScreenToolbar(props: ScreenToolbarProps) {
679
759
  return slot ? <PinnedTopBar {...props} slot={slot} /> : <InlineToolbar {...props} />;
680
760
  }
681
761
 
682
- function InlineToolbar({ leading, trailing, children, ...rest }: ScreenToolbarProps) {
762
+ function InlineToolbar({ leading, trailing, title, children, ...rest }: ScreenToolbarProps) {
683
763
  const { knobProps } = useResolvedKnobs({ component: "ScreenToolbar" });
764
+ const titleSpan = useBarTitleSpan();
684
765
  const insetToken = usePageChromeInset();
685
766
  const bleed = insetToken ? { marginHorizontal: negateSpaceToken(insetToken) as "$4" } : null;
686
767
  // LC-145: a phone top bar is one row; the leading group shrinks first.
@@ -692,8 +773,9 @@ function InlineToolbar({ leading, trailing, children, ...rest }: ScreenToolbarPr
692
773
  data-testid="screen-toolbar"
693
774
  justifyContent="space-between"
694
775
  alignItems="center"
695
- flexWrap={oneRow ? "nowrap" : "wrap"}
776
+ flexWrap={oneRow || title ? "nowrap" : "wrap"}
696
777
  minHeight={MIN_PRESS_TARGET}
778
+ {...(title ? { position: "relative" as const, onLayout: titleSpan.measureRow } : null)}
697
779
  {...bleed}
698
780
  {...(insetToken ? { paddingHorizontal: insetToken as "$4" } : knobProps.panelPadding)}
699
781
  {...knobProps.gap}
@@ -706,6 +788,7 @@ function InlineToolbar({ leading, trailing, children, ...rest }: ScreenToolbarPr
706
788
  // Shrink weight, not a size: the back label gives way before the actions wrap.
707
789
  flexShrink={oneRow ? 1000 : 1}
708
790
  minWidth={0}
791
+ {...(title ? { onLayout: titleSpan.measureLeading } : null)}
709
792
  >
710
793
  {leading}
711
794
  </XStack>
@@ -716,9 +799,13 @@ function InlineToolbar({ leading, trailing, children, ...rest }: ScreenToolbarPr
716
799
  flexShrink={1}
717
800
  minWidth={0}
718
801
  justifyContent="flex-end"
802
+ {...(title ? { onLayout: titleSpan.measureTrailing } : null)}
719
803
  >
720
804
  {trailing}
721
805
  </IconButtonGroup>
806
+ {title ? (
807
+ <BarTitle title={title} span={titleSpan.span} onTitleWidth={titleSpan.measureTitle} />
808
+ ) : null}
722
809
  </XStack>
723
810
  </Surface>
724
811
  );
@@ -737,19 +824,16 @@ function InlineToolbar({ leading, trailing, children, ...rest }: ScreenToolbarPr
737
824
  function PinnedTopBar({
738
825
  leading,
739
826
  trailing,
827
+ title: inlineTitle,
740
828
  children,
741
829
  slot,
742
830
  ...rest
743
831
  }: ScreenToolbarProps & { slot: { inset: string | null; maxWidth: number | null } }) {
744
832
  const { knobProps } = useResolvedKnobs({ component: "ScreenToolbar" });
745
- const { scrolled, collapsed, title } = useScreenScroll();
746
- const [sides, setSides] = useState({ leading: 0, trailing: 0 });
747
- const measureSide = (side: "leading" | "trailing") => (event: LayoutChangeEvent) => {
748
- const width = Math.ceil(event.nativeEvent.layout.width);
749
- setSides((prev) => (prev[side] === width ? prev : { ...prev, [side]: width }));
750
- };
833
+ const { scrolled, collapsed, title: largeTitle } = useScreenScroll();
834
+ const titleSpan = useBarTitleSpan();
751
835
  const motion = transitionProps(knobProps.motion.control);
752
- const clearance = Math.max(sides.leading, sides.trailing);
836
+ const title = inlineTitle ?? largeTitle;
753
837
  // LC-153: the pinned bar is a bar, so its neutral buttons paint plain.
754
838
  return (
755
839
  <Surface bar>
@@ -772,6 +856,7 @@ function PinnedTopBar({
772
856
  flexWrap="nowrap"
773
857
  // The collapsed title is placed against this row, not the page.
774
858
  position="relative"
859
+ onLayout={titleSpan.measureRow}
775
860
  paddingHorizontal={(slot.inset ?? knobProps.panelPadding.padding) as "$4"}
776
861
  {...knobProps.gap}
777
862
  {...rest}
@@ -781,7 +866,7 @@ function PinnedTopBar({
781
866
  alignItems="center"
782
867
  flexShrink={1000}
783
868
  minWidth={0}
784
- onLayout={measureSide("leading")}
869
+ onLayout={titleSpan.measureLeading}
785
870
  >
786
871
  {leading}
787
872
  </XStack>
@@ -794,29 +879,18 @@ function PinnedTopBar({
794
879
  flexShrink={1}
795
880
  minWidth={0}
796
881
  justifyContent="flex-end"
797
- onLayout={measureSide("trailing")}
882
+ onLayout={titleSpan.measureTrailing}
798
883
  >
799
884
  {trailing}
800
885
  </IconButtonGroup>
801
886
  {title ? (
802
- <XStack
803
- position="absolute"
804
- top={0}
805
- bottom={0}
806
- left={clearance}
807
- right={clearance}
808
- alignItems="center"
809
- justifyContent="center"
810
- pointerEvents="none"
811
- aria-hidden={!collapsed}
812
- data-bar-title=""
813
- opacity={collapsed ? 1 : 0}
887
+ <BarTitle
888
+ title={title}
889
+ span={titleSpan.span}
890
+ onTitleWidth={titleSpan.measureTitle}
891
+ shown={Boolean(inlineTitle) || collapsed}
814
892
  {...motion}
815
- >
816
- <SizableText {...knobProps.body} fontWeight="600" color="$color12" numberOfLines={1}>
817
- {title}
818
- </SizableText>
819
- </XStack>
893
+ />
820
894
  ) : null}
821
895
  </XStack>
822
896
  </YStack>
@@ -841,8 +915,11 @@ function useTopBarControl() {
841
915
  // Phosphor's caret path starts 72 units into its 256 grid; back pulls by that
842
916
  // so the caret's point, not its box, lands on the title's leading edge.
843
917
  const CARET_OPTICAL_INSET = 72 / 256;
918
+ // Its ink is centred 8 units toward the point, so on a glass circle it moves
919
+ // back by that to sit at the circle's centre (FZ-8442).
920
+ const CARET_INK_OFFSET = 8 / 256;
844
921
 
845
- function backCaret(Caret: typeof CaretLeftIcon) {
922
+ function backCaret(Caret: typeof CaretLeftIcon, inkSign: 1 | -1) {
846
923
  return function BackCaret({
847
924
  size = 22,
848
925
  color,
@@ -856,16 +933,22 @@ function backCaret(Caret: typeof CaretLeftIcon) {
856
933
  strokeWidth?: number;
857
934
  strokeLinejoin?: "round";
858
935
  }) {
936
+ const plated = useGlyphOnPlate();
859
937
  return (
860
- <XStack data-back-caret="" marginInlineStart={-Math.round(size * CARET_OPTICAL_INSET)}>
938
+ <XStack
939
+ data-back-caret=""
940
+ {...(plated
941
+ ? { x: inkSign * size * CARET_INK_OFFSET }
942
+ : { marginInlineStart: -Math.round(size * CARET_OPTICAL_INSET) })}
943
+ >
861
944
  <Caret size={size} color={color} weight={weight} {...stroke} />
862
945
  </XStack>
863
946
  );
864
947
  };
865
948
  }
866
949
 
867
- const BackCaretLeft = backCaret(CaretLeftIcon);
868
- const BackCaretRight = backCaret(CaretRightIcon);
950
+ const BackCaretLeft = backCaret(CaretLeftIcon, 1);
951
+ const BackCaretRight = backCaret(CaretRightIcon, -1);
869
952
 
870
953
  export interface PageBackButtonProps extends Omit<ButtonProps, "children"> {
871
954
  /** Where back goes, shown beside the caret (the list's name). */
@@ -1 +1 @@
1
- {"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../src/appShell/AppShell.tsx"],"names":[],"mappings":"AAkCA,OAAO,EAA+C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAepF,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAIvD,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;IACtC;;;OAGG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC7C,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iGAAiG;IACjG,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,wEAAwE;AACxE,wBAAgB,aAAa,CAC3B,YAAY,EAAE,SAAS,mBAAmB,EAAE,EAC5C,IAAI,EAAE,MAAM,GACX;IAAE,WAAW,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAQnE;AAiKD,wBAAgB,QAAQ,CAAC,EACvB,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,MAAM,EACN,SAAS,EACT,SAAc,EACd,QAAQ,EACR,MAAoB,GACrB,EAAE,aAAa,2CAiQf"}
1
+ {"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../src/appShell/AppShell.tsx"],"names":[],"mappings":"AAkCA,OAAO,EAA+C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAepF,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAIvD,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;IACtC;;;OAGG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC7C,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iGAAiG;IACjG,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,wEAAwE;AACxE,wBAAgB,aAAa,CAC3B,YAAY,EAAE,SAAS,mBAAmB,EAAE,EAC5C,IAAI,EAAE,MAAM,GACX;IAAE,WAAW,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAQnE;AAiKD,wBAAgB,QAAQ,CAAC,EACvB,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,KAAK,EACL,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,MAAM,EACN,SAAS,EACT,SAAc,EACd,QAAQ,EACR,MAAoB,GACrB,EAAE,aAAa,2CA0Pf"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Ends a scroll view's content with the height the bottom chrome covers, so
3
+ * content scrolls under a translucent tab bar and its last row still clears it.
4
+ * Renders nothing when no bar is mounted. One per scroll view: `Screen` ends
5
+ * its own content with one.
6
+ */
7
+ export declare function BottomChromeClearance({ testID }: {
8
+ testID?: string;
9
+ }): import("react/jsx-runtime").JSX.Element | null;
10
+ //# sourceMappingURL=BottomChromeClearance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomChromeClearance.d.ts","sourceRoot":"","sources":["../../src/appShell/BottomChromeClearance.tsx"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,MAA2B,EAAE,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,kDAKzF"}
@@ -9,17 +9,7 @@
9
9
  import { type ReactNode } from "react";
10
10
  /** Publish (or with null, withdraw) one floating bar's covered height. */
11
11
  export declare function setBottomChromeInset(id: symbol, next: number | null): void;
12
- /**
13
- * A region whose frame already ends above the bottom chrome, so nothing in it
14
- * pads by the inset again.
15
- */
16
- export declare function BottomChromeCleared({ children }: {
17
- children?: ReactNode;
18
- }): import("react").FunctionComponentElement<import("react").ProviderProps<boolean>>;
19
- /**
20
- * Points of screen the bottom chrome covers; 0 when no floating tab bar is
21
- * mounted, or inside a `BottomChromeCleared` region.
22
- */
12
+ /** Points of screen the bottom chrome covers; 0 when no floating tab bar is mounted. */
23
13
  export declare function useBottomChromeInset(): number;
24
14
  /** Publish (or with null, withdraw) a page's bottom accessory. The latest wins. */
25
15
  export declare function setBottomAccessory(id: symbol, node: ReactNode | null): void;
@@ -1 +1 @@
1
- {"version":3,"file":"bottomChrome.d.ts","sourceRoot":"","sources":["../../src/appShell/bottomChrome.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAGL,KAAK,SAAS,EAGf,MAAM,OAAO,CAAC;AA4Bf,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,QAEnE;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,oFAEzE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAKD,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,QAEpE;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,SAAS,CAE9C;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,QAEjE;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C"}
1
+ {"version":3,"file":"bottomChrome.d.ts","sourceRoot":"","sources":["../../src/appShell/bottomChrome.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AA4B7D,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,QAEnE;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAKD,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,QAEpE;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,IAAI,SAAS,CAE9C;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,QAEjE;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C"}
@@ -8,4 +8,5 @@ export type { AppEntry } from "./appTile";
8
8
  export * from "./SettingsSplit";
9
9
  export * from "./AppShell";
10
10
  export { useBottomChromeInset } from "./bottomChrome";
11
+ export { BottomChromeClearance } from "./BottomChromeClearance";
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/appShell/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/appShell/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/code/CodeBlock/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA2CH,MAAM,WAAW,cAAc;IAC7B,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,SAAS,CAAC,EACxB,QAAa,EACb,QAAgB,EAChB,KAAK,EACL,eAAe,EAAE,mBAAmB,EACpC,cAAmB,EACnB,WAAW,EACX,IAAI,GACL,EAAE,cAAc,2CAiJhB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/code/CodeBlock/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4CH,MAAM,WAAW,cAAc;IAC7B,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,SAAS,CAAC,EACxB,QAAa,EACb,QAAgB,EAChB,KAAK,EACL,eAAe,EAAE,mBAAmB,EACpC,cAAmB,EACnB,WAAW,EACX,IAAI,GACL,EAAE,cAAc,2CAgKhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../src/layouts/EmptyState.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAGL,KAAK,UAAU,EAMf,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAKjB,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;IAChE,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AA6ED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAe,EACf,MAAkB,EAClB,IAAI,EACJ,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,eAAe,2CA2GjB"}
1
+ {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../src/layouts/EmptyState.tsx"],"names":[],"mappings":"AAsBA,OAAO,EAAS,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAGL,KAAK,UAAU,EAKf,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAIjB,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;IAChE,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AA+CD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,IAAI,EACJ,MAAM,EACN,OAAO,EACP,OAAe,EACf,MAAkB,EAClB,IAAI,EACJ,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,eAAe,2CA2GjB"}