@multiplatform.one/forms 7.77.1 → 7.78.0

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 (296) hide show
  1. package/dist/cjs/Button/IconButtonGroup.cjs +58 -27
  2. package/dist/cjs/Button/IconButtonGroup.native.js +64 -29
  3. package/dist/cjs/Button/IconButtonGroup.native.js.map +1 -1
  4. package/dist/cjs/Button/circleGlyph.cjs +12 -2
  5. package/dist/cjs/Button/circleGlyph.native.js +12 -1
  6. package/dist/cjs/Button/circleGlyph.native.js.map +1 -1
  7. package/dist/cjs/Button/glass.cjs +2 -1
  8. package/dist/cjs/Button/glass.native.js +44 -19
  9. package/dist/cjs/Button/glass.native.js.map +1 -1
  10. package/dist/cjs/Button/iconButtonGroupContext.cjs +7 -1
  11. package/dist/cjs/Button/iconButtonGroupContext.native.js +7 -1
  12. package/dist/cjs/Button/iconButtonGroupContext.native.js.map +1 -1
  13. package/dist/cjs/Button/index.cjs +15 -23
  14. package/dist/cjs/Button/index.native.js +15 -24
  15. package/dist/cjs/Button/index.native.js.map +1 -1
  16. package/dist/cjs/Button/webGlass.cjs +9 -5
  17. package/dist/cjs/Button/webGlass.native.js +9 -5
  18. package/dist/cjs/Button/webGlass.native.js.map +1 -1
  19. package/dist/cjs/FloatingPanel/coverRect.cjs +50 -0
  20. package/dist/cjs/FloatingPanel/coverRect.native.js +54 -0
  21. package/dist/cjs/FloatingPanel/coverRect.native.js.map +1 -0
  22. package/dist/cjs/FloatingPanel/iconEcho.cjs +3 -1
  23. package/dist/cjs/FloatingPanel/iconEcho.native.js +3 -1
  24. package/dist/cjs/FloatingPanel/iconEcho.native.js.map +1 -1
  25. package/dist/cjs/FloatingPanel/index.cjs +23 -13
  26. package/dist/cjs/FloatingPanel/index.native.js +41 -15
  27. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  28. package/dist/cjs/InputParts/index.cjs +5 -0
  29. package/dist/cjs/InputParts/index.native.js +7 -0
  30. package/dist/cjs/InputParts/index.native.js.map +1 -1
  31. package/dist/cjs/{Button/mediaPlate.cjs → InputParts/keyboardAppearance.cjs} +7 -8
  32. package/dist/{jsx/Button/mediaPlate.native.js → cjs/InputParts/keyboardAppearance.native.js} +8 -9
  33. package/dist/cjs/InputParts/keyboardAppearance.native.js.map +1 -0
  34. package/dist/cjs/{Button/mediaPlate.native.js → fields/Duration/DurationWheel.cjs} +11 -11
  35. package/dist/cjs/fields/Duration/DurationWheel.native.js +132 -0
  36. package/dist/cjs/fields/Duration/DurationWheel.native.js.map +1 -0
  37. package/dist/cjs/fields/Duration/durationMath.cjs +19 -1
  38. package/dist/cjs/fields/Duration/durationMath.native.js +19 -1
  39. package/dist/cjs/fields/Duration/durationMath.native.js.map +1 -1
  40. package/dist/cjs/fields/Duration/durationWheelTypes.cjs +18 -0
  41. package/dist/cjs/fields/Duration/durationWheelTypes.native.js +21 -0
  42. package/dist/cjs/fields/Duration/durationWheelTypes.native.js.map +1 -0
  43. package/dist/cjs/fields/Duration/index.cjs +30 -10
  44. package/dist/cjs/fields/Duration/index.native.js +32 -12
  45. package/dist/cjs/fields/Duration/index.native.js.map +1 -1
  46. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +8 -7
  47. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +8 -7
  48. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  49. package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.cjs +1 -1
  50. package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js +1 -1
  51. package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
  52. package/dist/cjs/fields/nativeDateTime/sheetKeyboard.cjs +1 -1
  53. package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js +1 -1
  54. package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
  55. package/dist/cjs/index.cjs +0 -1
  56. package/dist/cjs/index.native.js +0 -1
  57. package/dist/cjs/index.native.js.map +1 -1
  58. package/dist/esm/Button/IconButtonGroup.mjs +65 -33
  59. package/dist/esm/Button/IconButtonGroup.mjs.map +1 -1
  60. package/dist/esm/Button/IconButtonGroup.native.js +71 -35
  61. package/dist/esm/Button/IconButtonGroup.native.js.map +1 -1
  62. package/dist/esm/Button/circleGlyph.mjs +9 -1
  63. package/dist/esm/Button/circleGlyph.mjs.map +1 -1
  64. package/dist/esm/Button/circleGlyph.native.js +10 -1
  65. package/dist/esm/Button/circleGlyph.native.js.map +1 -1
  66. package/dist/esm/Button/glass.mjs +2 -1
  67. package/dist/esm/Button/glass.mjs.map +1 -1
  68. package/dist/esm/Button/glass.native.js +43 -19
  69. package/dist/esm/Button/glass.native.js.map +1 -1
  70. package/dist/esm/Button/iconButtonGroupContext.mjs +5 -1
  71. package/dist/esm/Button/iconButtonGroupContext.mjs.map +1 -1
  72. package/dist/esm/Button/iconButtonGroupContext.native.js +5 -1
  73. package/dist/esm/Button/iconButtonGroupContext.native.js.map +1 -1
  74. package/dist/esm/Button/index.mjs +15 -22
  75. package/dist/esm/Button/index.mjs.map +1 -1
  76. package/dist/esm/Button/index.native.js +15 -23
  77. package/dist/esm/Button/index.native.js.map +1 -1
  78. package/dist/esm/Button/webGlass.mjs +10 -6
  79. package/dist/esm/Button/webGlass.mjs.map +1 -1
  80. package/dist/esm/Button/webGlass.native.js +10 -6
  81. package/dist/esm/Button/webGlass.native.js.map +1 -1
  82. package/dist/esm/FloatingPanel/coverRect.mjs +23 -0
  83. package/dist/esm/FloatingPanel/coverRect.mjs.map +1 -0
  84. package/dist/esm/FloatingPanel/coverRect.native.js +24 -0
  85. package/dist/esm/FloatingPanel/coverRect.native.js.map +1 -0
  86. package/dist/esm/FloatingPanel/iconEcho.mjs +4 -2
  87. package/dist/esm/FloatingPanel/iconEcho.mjs.map +1 -1
  88. package/dist/esm/FloatingPanel/iconEcho.native.js +4 -2
  89. package/dist/esm/FloatingPanel/iconEcho.native.js.map +1 -1
  90. package/dist/esm/FloatingPanel/index.mjs +23 -13
  91. package/dist/esm/FloatingPanel/index.mjs.map +1 -1
  92. package/dist/esm/FloatingPanel/index.native.js +41 -15
  93. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  94. package/dist/esm/InputParts/index.mjs +6 -1
  95. package/dist/esm/InputParts/index.mjs.map +1 -1
  96. package/dist/esm/InputParts/index.native.js +8 -1
  97. package/dist/esm/InputParts/index.native.js.map +1 -1
  98. package/dist/esm/InputParts/keyboardAppearance.mjs +5 -0
  99. package/dist/esm/InputParts/keyboardAppearance.mjs.map +1 -0
  100. package/dist/esm/InputParts/keyboardAppearance.native.js +5 -0
  101. package/dist/esm/InputParts/keyboardAppearance.native.js.map +1 -0
  102. package/dist/esm/fields/Duration/DurationWheel.mjs +8 -0
  103. package/dist/esm/fields/Duration/DurationWheel.mjs.map +1 -0
  104. package/dist/esm/fields/Duration/DurationWheel.native.js +103 -0
  105. package/dist/esm/fields/Duration/DurationWheel.native.js.map +1 -0
  106. package/dist/esm/fields/Duration/durationMath.mjs +16 -1
  107. package/dist/esm/fields/Duration/durationMath.mjs.map +1 -1
  108. package/dist/esm/fields/Duration/durationMath.native.js +16 -1
  109. package/dist/esm/fields/Duration/durationMath.native.js.map +1 -1
  110. package/dist/esm/fields/Duration/durationWheelTypes.mjs +2 -0
  111. package/dist/esm/fields/Duration/durationWheelTypes.mjs.map +1 -0
  112. package/dist/esm/fields/Duration/durationWheelTypes.native.js +2 -0
  113. package/dist/esm/fields/Duration/durationWheelTypes.native.js.map +1 -0
  114. package/dist/esm/fields/Duration/index.mjs +28 -11
  115. package/dist/esm/fields/Duration/index.mjs.map +1 -1
  116. package/dist/esm/fields/Duration/index.native.js +30 -13
  117. package/dist/esm/fields/Duration/index.native.js.map +1 -1
  118. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +8 -7
  119. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  120. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +8 -7
  121. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  122. package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.mjs +1 -1
  123. package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.mjs.map +1 -1
  124. package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.native.js +1 -1
  125. package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
  126. package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs +1 -1
  127. package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -1
  128. package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js +1 -1
  129. package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
  130. package/dist/esm/index.mjs +2 -2
  131. package/dist/esm/index.mjs.map +1 -1
  132. package/dist/esm/index.native.js +2 -2
  133. package/dist/esm/index.native.js.map +1 -1
  134. package/dist/jsx/Button/IconButtonGroup.mjs +65 -33
  135. package/dist/jsx/Button/IconButtonGroup.mjs.map +1 -1
  136. package/dist/jsx/Button/IconButtonGroup.native.js +64 -29
  137. package/dist/jsx/Button/IconButtonGroup.native.js.map +1 -1
  138. package/dist/jsx/Button/circleGlyph.mjs +9 -1
  139. package/dist/jsx/Button/circleGlyph.mjs.map +1 -1
  140. package/dist/jsx/Button/circleGlyph.native.js +12 -1
  141. package/dist/jsx/Button/circleGlyph.native.js.map +1 -1
  142. package/dist/jsx/Button/glass.mjs +2 -1
  143. package/dist/jsx/Button/glass.mjs.map +1 -1
  144. package/dist/jsx/Button/glass.native.js +44 -19
  145. package/dist/jsx/Button/glass.native.js.map +1 -1
  146. package/dist/jsx/Button/iconButtonGroupContext.mjs +5 -1
  147. package/dist/jsx/Button/iconButtonGroupContext.mjs.map +1 -1
  148. package/dist/jsx/Button/iconButtonGroupContext.native.js +7 -1
  149. package/dist/jsx/Button/iconButtonGroupContext.native.js.map +1 -1
  150. package/dist/jsx/Button/index.mjs +15 -22
  151. package/dist/jsx/Button/index.mjs.map +1 -1
  152. package/dist/jsx/Button/index.native.js +15 -24
  153. package/dist/jsx/Button/index.native.js.map +1 -1
  154. package/dist/jsx/Button/webGlass.mjs +10 -6
  155. package/dist/jsx/Button/webGlass.mjs.map +1 -1
  156. package/dist/jsx/Button/webGlass.native.js +9 -5
  157. package/dist/jsx/Button/webGlass.native.js.map +1 -1
  158. package/dist/jsx/FloatingPanel/coverRect.mjs +23 -0
  159. package/dist/jsx/FloatingPanel/coverRect.mjs.map +1 -0
  160. package/dist/jsx/FloatingPanel/coverRect.native.js +54 -0
  161. package/dist/jsx/FloatingPanel/coverRect.native.js.map +1 -0
  162. package/dist/jsx/FloatingPanel/iconEcho.mjs +4 -2
  163. package/dist/jsx/FloatingPanel/iconEcho.mjs.map +1 -1
  164. package/dist/jsx/FloatingPanel/iconEcho.native.js +3 -1
  165. package/dist/jsx/FloatingPanel/iconEcho.native.js.map +1 -1
  166. package/dist/jsx/FloatingPanel/index.mjs +23 -13
  167. package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
  168. package/dist/jsx/FloatingPanel/index.native.js +41 -15
  169. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  170. package/dist/jsx/InputParts/index.mjs +6 -1
  171. package/dist/jsx/InputParts/index.mjs.map +1 -1
  172. package/dist/jsx/InputParts/index.native.js +7 -0
  173. package/dist/jsx/InputParts/index.native.js.map +1 -1
  174. package/dist/jsx/InputParts/keyboardAppearance.mjs +5 -0
  175. package/dist/jsx/InputParts/keyboardAppearance.mjs.map +1 -0
  176. package/dist/jsx/InputParts/keyboardAppearance.native.js +33 -0
  177. package/dist/jsx/InputParts/keyboardAppearance.native.js.map +1 -0
  178. package/dist/jsx/fields/Duration/DurationWheel.mjs +8 -0
  179. package/dist/jsx/fields/Duration/DurationWheel.mjs.map +1 -0
  180. package/dist/jsx/fields/Duration/DurationWheel.native.js +132 -0
  181. package/dist/jsx/fields/Duration/DurationWheel.native.js.map +1 -0
  182. package/dist/jsx/fields/Duration/durationMath.mjs +16 -1
  183. package/dist/jsx/fields/Duration/durationMath.mjs.map +1 -1
  184. package/dist/jsx/fields/Duration/durationMath.native.js +19 -1
  185. package/dist/jsx/fields/Duration/durationMath.native.js.map +1 -1
  186. package/dist/jsx/fields/Duration/durationWheelTypes.mjs +2 -0
  187. package/dist/jsx/fields/Duration/durationWheelTypes.mjs.map +1 -0
  188. package/dist/jsx/fields/Duration/durationWheelTypes.native.js +21 -0
  189. package/dist/jsx/fields/Duration/durationWheelTypes.native.js.map +1 -0
  190. package/dist/jsx/fields/Duration/index.mjs +28 -11
  191. package/dist/jsx/fields/Duration/index.mjs.map +1 -1
  192. package/dist/jsx/fields/Duration/index.native.js +32 -12
  193. package/dist/jsx/fields/Duration/index.native.js.map +1 -1
  194. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +8 -7
  195. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  196. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +8 -7
  197. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  198. package/dist/jsx/fields/nativeDateTime/osDateTimeIosDisplay.mjs +1 -1
  199. package/dist/jsx/fields/nativeDateTime/osDateTimeIosDisplay.mjs.map +1 -1
  200. package/dist/jsx/fields/nativeDateTime/osDateTimeIosDisplay.native.js +1 -1
  201. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs +1 -1
  202. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -1
  203. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js +1 -1
  204. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
  205. package/dist/jsx/index.js +2 -2
  206. package/dist/jsx/index.js.map +1 -1
  207. package/dist/jsx/index.mjs +2 -2
  208. package/dist/jsx/index.mjs.map +1 -1
  209. package/dist/jsx/index.native.js +0 -1
  210. package/dist/jsx/index.native.js.map +1 -1
  211. package/package.json +8 -8
  212. package/src/Button/Button.iconButton.spec.tsx +47 -7
  213. package/src/Button/IconButtonGroup.spec.tsx +103 -19
  214. package/src/Button/IconButtonGroup.tsx +84 -46
  215. package/src/Button/circleGlyph.ts +18 -0
  216. package/src/Button/glass.native.spec.ts +62 -0
  217. package/src/Button/glass.native.ts +58 -37
  218. package/src/Button/glass.spec.ts +64 -16
  219. package/src/Button/glass.ts +16 -12
  220. package/src/Button/iconButtonGroupContext.ts +27 -3
  221. package/src/Button/index.tsx +21 -33
  222. package/src/Button/webGlass.ts +25 -18
  223. package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +61 -1
  224. package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +104 -0
  225. package/src/FloatingPanel/coverRect.ts +42 -0
  226. package/src/FloatingPanel/iconEcho.tsx +9 -3
  227. package/src/FloatingPanel/index.native.tsx +63 -18
  228. package/src/FloatingPanel/index.tsx +35 -15
  229. package/src/InputParts/index.tsx +6 -0
  230. package/src/InputParts/keyboardAppearance.spec.ts +12 -0
  231. package/src/InputParts/keyboardAppearance.ts +4 -0
  232. package/src/fields/Duration/Duration.spec.tsx +19 -10
  233. package/src/fields/Duration/Duration.wheel.native.spec.tsx +98 -0
  234. package/src/fields/Duration/DurationWheel.native.tsx +101 -0
  235. package/src/fields/Duration/DurationWheel.tsx +11 -0
  236. package/src/fields/Duration/durationMath.spec.ts +43 -0
  237. package/src/fields/Duration/durationMath.ts +31 -0
  238. package/src/fields/Duration/durationWheelTypes.ts +18 -0
  239. package/src/fields/Duration/index.tsx +58 -27
  240. package/src/fields/nativeDateTime/OsDateTimeSheet.countdown.native.spec.tsx +121 -0
  241. package/src/fields/nativeDateTime/OsDateTimeSheet.spec.ts +4 -0
  242. package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +14 -9
  243. package/src/fields/nativeDateTime/osDateTimeIosDisplay.ts +3 -3
  244. package/src/fields/nativeDateTime/sheetKeyboard.spec.ts +6 -0
  245. package/src/fields/nativeDateTime/sheetKeyboard.ts +2 -2
  246. package/src/index.ts +0 -1
  247. package/types/Button/IconButtonGroup.d.ts +7 -12
  248. package/types/Button/IconButtonGroup.d.ts.map +1 -1
  249. package/types/Button/circleGlyph.d.ts +12 -0
  250. package/types/Button/circleGlyph.d.ts.map +1 -1
  251. package/types/Button/glass.d.ts +7 -11
  252. package/types/Button/glass.d.ts.map +1 -1
  253. package/types/Button/glass.native.d.ts +22 -10
  254. package/types/Button/glass.native.d.ts.map +1 -1
  255. package/types/Button/iconButtonGroupContext.d.ts +21 -3
  256. package/types/Button/iconButtonGroupContext.d.ts.map +1 -1
  257. package/types/Button/index.d.ts +3 -10
  258. package/types/Button/index.d.ts.map +1 -1
  259. package/types/Button/webGlass.d.ts +8 -16
  260. package/types/Button/webGlass.d.ts.map +1 -1
  261. package/types/FloatingPanel/coverRect.d.ts +25 -0
  262. package/types/FloatingPanel/coverRect.d.ts.map +1 -0
  263. package/types/FloatingPanel/index.d.ts.map +1 -1
  264. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  265. package/types/InputParts/index.d.ts.map +1 -1
  266. package/types/InputParts/keyboardAppearance.d.ts +3 -0
  267. package/types/InputParts/keyboardAppearance.d.ts.map +1 -0
  268. package/types/fields/Duration/DurationWheel.d.ts +5 -0
  269. package/types/fields/Duration/DurationWheel.d.ts.map +1 -0
  270. package/types/fields/Duration/DurationWheel.native.d.ts +5 -0
  271. package/types/fields/Duration/DurationWheel.native.d.ts.map +1 -0
  272. package/types/fields/Duration/durationMath.d.ts +13 -0
  273. package/types/fields/Duration/durationMath.d.ts.map +1 -1
  274. package/types/fields/Duration/durationWheelTypes.d.ts +18 -0
  275. package/types/fields/Duration/durationWheelTypes.d.ts.map +1 -0
  276. package/types/fields/Duration/index.d.ts +8 -3
  277. package/types/fields/Duration/index.d.ts.map +1 -1
  278. package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts +1 -1
  279. package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts.map +1 -1
  280. package/types/fields/nativeDateTime/osDateTimeIosDisplay.d.ts +2 -2
  281. package/types/fields/nativeDateTime/osDateTimeIosDisplay.d.ts.map +1 -1
  282. package/types/fields/nativeDateTime/sheetKeyboard.d.ts +1 -1
  283. package/types/fields/nativeDateTime/sheetKeyboard.d.ts.map +1 -1
  284. package/types/index.d.ts +1 -1
  285. package/types/index.d.ts.map +1 -1
  286. package/dist/cjs/Button/mediaPlate.native.js.map +0 -1
  287. package/dist/esm/Button/mediaPlate.mjs +0 -6
  288. package/dist/esm/Button/mediaPlate.mjs.map +0 -1
  289. package/dist/esm/Button/mediaPlate.native.js +0 -6
  290. package/dist/esm/Button/mediaPlate.native.js.map +0 -1
  291. package/dist/jsx/Button/mediaPlate.mjs +0 -6
  292. package/dist/jsx/Button/mediaPlate.mjs.map +0 -1
  293. package/dist/jsx/Button/mediaPlate.native.js.map +0 -1
  294. package/src/Button/mediaPlate.ts +0 -9
  295. package/types/Button/mediaPlate.d.ts +0 -10
  296. package/types/Button/mediaPlate.d.ts.map +0 -1
@@ -1,40 +1,64 @@
1
- import { radiusClassProps, useBarSurface } from "@multiplatform.one/theme";
2
- import { useCallback, useState, type ReactNode } from "react";
3
- import { isWeb, Theme, View, XStack, type XStackProps } from "tamagui";
4
- import { loadGlassView } from "./glass";
5
- import { MEDIA_PLATE_WEB_STYLE } from "./mediaPlate";
6
- import { isAppleTouchWeb, WebGlassFace } from "./webGlass";
7
- import { IconButtonGroupContext, IconButtonGroupRegistry } from "./iconButtonGroupContext";
1
+ import { isDarkThemeName, radiusClassProps, useBarSurface } from "@multiplatform.one/theme";
2
+ import { Children, useCallback, useMemo, useRef, useState, type ReactNode } from "react";
3
+ import { isWeb, Theme, useThemeName, View, XStack, type XStackProps } from "tamagui";
4
+ import { loadGlassContainer, loadGlassView } from "./glass";
5
+ import { isAppleTouchWeb } from "./webGlass";
6
+ import {
7
+ IconButtonGroupContext,
8
+ IconButtonGroupRegistry,
9
+ IconButtonGroupSurfaceContext,
10
+ type IconButtonGroupSurface,
11
+ } from "./iconButtonGroupContext";
8
12
 
9
- /**
10
- * LC-176: how far apart two Liquid Glass circles may sit and still merge. Half
11
- * the 44pt target, so neighbours on the bar's gap always join.
12
- */
13
- export const ICON_BUTTON_GLASS_MERGE_PX = 22;
13
+ /** UIGlassContainerEffect's own default: the system decides when glass merges. */
14
+ const SYSTEM_GLASS_SPACING = 0;
15
+
16
+ type MeasurableView = {
17
+ measureInWindow?: (
18
+ callback: (x: number, y: number, width: number, height: number) => void,
19
+ ) => void;
20
+ };
14
21
 
15
22
  export interface IconButtonGroupProps extends XStackProps {
16
- /** Over imagery or video: the group shares one material capsule. */
23
+ /** Over imagery or video: the group shares one dark capsule. */
17
24
  plate?: "media";
18
25
  children?: ReactNode;
19
26
  }
20
27
 
21
28
  /**
22
29
  * LC-176: icon buttons that sit side by side. Grounds merge, they never stand
23
- * as a row of separate circles: on iOS 26 the bar's group is one Liquid Glass
24
- * capsule, as the system draws a bar's item group (merging each circle through
25
- * the glass container left a waist between them, FZ-8158); over media the
26
- * group shares one material capsule; a bare row stays bare. A lone button
27
- * keeps its own circle, and a bar group with no icon button in it paints no
28
- * capsule (FZ-8494).
30
+ * as a row of separate circles: on iOS 26 the bar's group sits in one system
31
+ * Liquid Glass capsule, as the system draws a bar's item group (merging each
32
+ * circle through the glass container left a waist between them, FZ-8158), so
33
+ * press and merge are the system's; over media the group shares one dark
34
+ * capsule; a bare row stays bare. A lone button keeps its own circle, and a
35
+ * bar group with no icon button in it paints no capsule (FZ-8494).
29
36
  */
30
37
  export function IconButtonGroup({ plate, children, ...rest }: IconButtonGroupProps) {
31
38
  const bar = useBarSurface();
39
+ const dark = isDarkThemeName(useThemeName());
32
40
  const [capsuleHeight, setCapsuleHeight] = useState(0);
33
41
  const [members, setMembers] = useState(0);
34
42
  const register = useCallback(() => {
35
43
  setMembers((count) => count + 1);
36
44
  return () => setMembers((count) => count - 1);
37
45
  }, []);
46
+ const capsuleRef = useRef<MeasurableView | null>(null);
47
+ const surface = useMemo<IconButtonGroupSurface>(
48
+ () => ({
49
+ measure(callback) {
50
+ const node = capsuleRef.current;
51
+ if (typeof node?.measureInWindow !== "function") {
52
+ callback(null);
53
+ return;
54
+ }
55
+ node.measureInWindow((x, y, width, height) =>
56
+ callback(width > 0 && height > 0 ? { x, y, width, height, radius: height / 2 } : null),
57
+ );
58
+ },
59
+ }),
60
+ [],
61
+ );
38
62
  if (plate === "media") {
39
63
  return (
40
64
  <IconButtonGroupContext.Provider value="media">
@@ -47,7 +71,6 @@ export function IconButtonGroup({ plate, children, ...rest }: IconButtonGroupPro
47
71
  overflow="hidden"
48
72
  data-mp-icon-button-group="media"
49
73
  {...radiusClassProps("R-IDENTITY", "icon button plate")}
50
- {...(isWeb ? { style: MEDIA_PLATE_WEB_STYLE } : undefined)}
51
74
  {...rest}
52
75
  >
53
76
  {children}
@@ -56,7 +79,8 @@ export function IconButtonGroup({ plate, children, ...rest }: IconButtonGroupPro
56
79
  </IconButtonGroupContext.Provider>
57
80
  );
58
81
  }
59
- if (bar && isWeb && isAppleTouchWeb()) {
82
+ const GlassView = bar ? loadGlassView() : null;
83
+ if (GlassView && isWeb && isAppleTouchWeb()) {
60
84
  // The caller's layout props stay on the outer row; the capsule hugs the
61
85
  // buttons inside it, so a stretched slot never draws a wide empty pill.
62
86
  return (
@@ -76,7 +100,7 @@ export function IconButtonGroup({ plate, children, ...rest }: IconButtonGroupPro
76
100
  data-mp-glass-capsule=""
77
101
  >
78
102
  {members > 0 ? (
79
- <WebGlassFace
103
+ <GlassView
80
104
  style={{
81
105
  position: "absolute",
82
106
  top: 0,
@@ -94,42 +118,56 @@ export function IconButtonGroup({ plate, children, ...rest }: IconButtonGroupPro
94
118
  </IconButtonGroupRegistry.Provider>
95
119
  );
96
120
  }
97
- const GlassView = bar ? loadGlassView() : null;
121
+ const capsule = GlassView != null && members > 0;
122
+ // The capsule hugs its buttons with the web twin's $2 end inset and $1 gap,
123
+ // so its first and last items never sit on its curve (FZ-8550). A group of
124
+ // one button is a lone button, and keeps its circle.
125
+ const lone = members === 1 && Children.toArray(children).length === 1;
98
126
  const row = (
99
127
  <XStack
100
128
  alignItems="center"
101
- data-mp-icon-button-group={GlassView && members > 0 ? "glass" : bar ? "bar" : "row"}
129
+ data-mp-icon-button-group={capsule ? "glass" : bar ? "bar" : "row"}
102
130
  {...rest}
131
+ {...(capsule && !lone ? { paddingHorizontal: "$2" as const, gap: "$1" as const } : null)}
103
132
  >
104
133
  {children}
105
134
  </XStack>
106
135
  );
107
136
  if (!GlassView) return row;
137
+ // The buttons sit inside the glass, so touches reach it and the system
138
+ // answers the press. It stays mounted with no effect until a button
139
+ // registers, or the swap would remount the buttons that are registering.
140
+ const glass = (
141
+ <GlassView
142
+ glassEffectStyle={capsule ? "regular" : "none"}
143
+ isInteractive={capsule}
144
+ colorScheme={dark ? "dark" : "light"}
145
+ style={{ borderRadius: capsuleHeight / 2, flexShrink: 1, minWidth: 0 }}
146
+ >
147
+ {row}
148
+ </GlassView>
149
+ );
150
+ const GlassContainer = loadGlassContainer();
108
151
  return (
109
152
  <IconButtonGroupRegistry.Provider value={register}>
110
153
  <IconButtonGroupContext.Provider value="glass">
111
- <View
112
- position="relative"
113
- flexShrink={1}
114
- minWidth={0}
115
- onLayout={(event) => setCapsuleHeight(event.nativeEvent.layout.height)}
116
- >
117
- {members > 0 ? (
118
- <GlassView
119
- glassEffectStyle="regular"
120
- pointerEvents="none"
121
- style={{
122
- position: "absolute",
123
- top: 0,
124
- right: 0,
125
- bottom: 0,
126
- left: 0,
127
- borderRadius: capsuleHeight / 2,
128
- }}
129
- />
130
- ) : null}
131
- {row}
132
- </View>
154
+ <IconButtonGroupSurfaceContext.Provider value={members > 0 ? surface : null}>
155
+ <View
156
+ ref={capsuleRef as never}
157
+ collapsable={false}
158
+ flexShrink={1}
159
+ minWidth={0}
160
+ onLayout={(event) => setCapsuleHeight(event.nativeEvent.layout.height)}
161
+ >
162
+ {GlassContainer ? (
163
+ <GlassContainer spacing={SYSTEM_GLASS_SPACING} style={{ flexShrink: 1, minWidth: 0 }}>
164
+ {glass}
165
+ </GlassContainer>
166
+ ) : (
167
+ glass
168
+ )}
169
+ </View>
170
+ </IconButtonGroupSurfaceContext.Provider>
133
171
  </IconButtonGroupContext.Provider>
134
172
  </IconButtonGroupRegistry.Provider>
135
173
  );
@@ -4,3 +4,21 @@
4
4
  * ratio, never a small glyph lost in a big plate.
5
5
  */
6
6
  export const ICON_BUTTON_CIRCLE_GLYPH_RATIO = 0.58;
7
+
8
+ /**
9
+ * LC-176: Phosphor bold's stem is 0.12 of its ink, an SF Symbols bold plus's
10
+ * 0.18 (measured at 17 and 24pt). An outline stroke this wide, in the 256-unit
11
+ * grid, brings a bold glyph to SF semibold-bold (0.154); a wider one closes
12
+ * Phosphor bold's 16-unit counters (the trash lid, copy's overlap).
13
+ */
14
+ export const GLYPH_BOLD_STROKE_UNITS = 8;
15
+
16
+ export function boldStroke(weight: "regular" | "bold", color = "currentColor") {
17
+ return weight === "bold"
18
+ ? {
19
+ stroke: color,
20
+ strokeWidth: GLYPH_BOLD_STROKE_UNITS,
21
+ strokeLinejoin: "round" as const,
22
+ }
23
+ : undefined;
24
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * LC-176 (FZ-8666): real Liquid Glass or none. An Android binary, a binary
3
+ * without the module and an iOS before 26 get no glass at all, so the icon
4
+ * button is its plain glyph.
5
+ */
6
+ import { describe, expect, it, vi } from "vitest";
7
+ import type { GlassContainerComponent, GlassViewComponent } from "./glass";
8
+ import { resolveSystemGlass, type GlassSources } from "./glass.native";
9
+
10
+ const view = (() => null) as unknown as GlassViewComponent;
11
+ const container = (() => null) as unknown as GlassContainerComponent;
12
+
13
+ function sources(
14
+ nativeModule: unknown,
15
+ overrides: Partial<ReturnType<GlassSources["glass"]>> = {},
16
+ ): GlassSources {
17
+ return {
18
+ core: () => ({ requireOptionalNativeModule: () => nativeModule }),
19
+ glass: () => ({
20
+ GlassView: view,
21
+ GlassContainer: container,
22
+ isLiquidGlassAvailable: () => true,
23
+ isGlassEffectAPIAvailable: () => true,
24
+ ...overrides,
25
+ }),
26
+ };
27
+ }
28
+
29
+ describe("resolveSystemGlass", () => {
30
+ it("iOS 26 with the module gets the system's views", () => {
31
+ expect(resolveSystemGlass(sources({}))).toEqual({ view, container });
32
+ });
33
+
34
+ it("no native module (Android, a binary built without it) never requires the package", () => {
35
+ const glass = vi.fn();
36
+ const source = { ...sources(null), glass };
37
+ expect(resolveSystemGlass(source as unknown as GlassSources)).toBeNull();
38
+ expect(glass).not.toHaveBeenCalled();
39
+ });
40
+
41
+ it("an iOS before 26 and a beta without the API get no glass", () => {
42
+ expect(resolveSystemGlass(sources({}, { isLiquidGlassAvailable: () => false }))).toBeNull();
43
+ expect(resolveSystemGlass(sources({}, { isGlassEffectAPIAvailable: () => false }))).toBeNull();
44
+ });
45
+
46
+ it("a package that throws on load gets no glass", () => {
47
+ const source = {
48
+ ...sources({}),
49
+ glass: () => {
50
+ throw new Error("no module");
51
+ },
52
+ };
53
+ expect(resolveSystemGlass(source)).toBeNull();
54
+ });
55
+
56
+ it("a module with no container keeps the view", () => {
57
+ expect(resolveSystemGlass(sources({}, { GlassContainer: undefined }))).toEqual({
58
+ view,
59
+ container: null,
60
+ });
61
+ });
62
+ });
@@ -2,50 +2,71 @@ import type { GlassContainerComponent, GlassViewComponent } from "./glass";
2
2
 
3
3
  export type { GlassContainerComponent, GlassViewComponent } from "./glass";
4
4
 
5
- let cachedGlassView: GlassViewComponent | null | undefined;
6
- let cachedGlassContainer: GlassContainerComponent | null | undefined;
5
+ export interface GlassSources {
6
+ core: () => { requireOptionalNativeModule?: (moduleName: string) => unknown };
7
+ glass: () => {
8
+ GlassView?: GlassViewComponent;
9
+ GlassContainer?: GlassContainerComponent;
10
+ isLiquidGlassAvailable?: () => boolean;
11
+ isGlassEffectAPIAvailable?: () => boolean;
12
+ };
13
+ }
14
+
15
+ // Each require sits in its own try: Metro only treats a require as optional
16
+ // inside one, and an app without the package must still bundle.
17
+ const liveSources: GlassSources = {
18
+ core: () => {
19
+ try {
20
+ return require("expo-modules-core");
21
+ } catch {
22
+ return {};
23
+ }
24
+ },
25
+ glass: () => {
26
+ try {
27
+ return require("expo-glass-effect");
28
+ } catch {
29
+ return {};
30
+ }
31
+ },
32
+ };
33
+
34
+ export interface SystemGlass {
35
+ view: GlassViewComponent;
36
+ container: GlassContainerComponent | null;
37
+ }
7
38
 
8
39
  /**
9
- * LC-176: expo-glass-effect's `GlassView`, when this binary links its native
10
- * module and the OS draws Liquid Glass (iOS 26+); Android never links it. The module is probed through
11
- * `requireOptionalNativeModule` BEFORE the guarded `require`, so an OTA on a
12
- * binary built without it, or an older iOS, falls back to the bare tinted
13
- * glyph instead of throwing mid-render (the `nativePickerLoader.ts` contract).
40
+ * LC-176: expo-glass-effect's views, when this binary links the native module
41
+ * and the OS draws Liquid Glass (iOS 26+); Android never links it. The module
42
+ * is probed through `requireOptionalNativeModule` BEFORE the guarded `require`,
43
+ * so an OTA on a binary built without it, or an older iOS, falls back to the
44
+ * bare tinted glyph instead of throwing mid-render (the `nativePickerLoader.ts`
45
+ * contract). A binary whose module predates the container keeps its capsule.
14
46
  */
15
- export function loadGlassView(): GlassViewComponent | null {
16
- if (cachedGlassView !== undefined) return cachedGlassView;
17
- cachedGlassView = null;
47
+ export function resolveSystemGlass(sources: GlassSources = liveSources): SystemGlass | null {
18
48
  try {
19
- const core = require("expo-modules-core") as {
20
- requireOptionalNativeModule?: (moduleName: string) => unknown;
21
- };
22
- if (core.requireOptionalNativeModule?.("ExpoGlassEffect") == null) return cachedGlassView;
23
- const glass = require("expo-glass-effect") as {
24
- GlassView?: GlassViewComponent;
25
- GlassContainer?: GlassContainerComponent;
26
- isLiquidGlassAvailable?: () => boolean;
27
- isGlassEffectAPIAvailable?: () => boolean;
28
- };
29
- if (
30
- glass.GlassView &&
31
- glass.isLiquidGlassAvailable?.() &&
32
- glass.isGlassEffectAPIAvailable?.()
33
- ) {
34
- cachedGlassView = glass.GlassView;
35
- cachedGlassContainer = glass.GlassContainer ?? null;
36
- }
49
+ if (sources.core().requireOptionalNativeModule?.("ExpoGlassEffect") == null) return null;
50
+ const glass = sources.glass();
51
+ if (!glass.GlassView || !glass.isLiquidGlassAvailable?.()) return null;
52
+ if (!glass.isGlassEffectAPIAvailable?.()) return null;
53
+ return { view: glass.GlassView, container: glass.GlassContainer ?? null };
37
54
  } catch {
38
- cachedGlassView = null;
55
+ return null;
39
56
  }
40
- return cachedGlassView;
41
57
  }
42
58
 
43
- /**
44
- * LC-176: expo-glass-effect's `GlassContainer`, present exactly when
45
- * `loadGlassView` found Liquid Glass. A binary whose module predates the
46
- * container keeps separate circles rather than throwing.
47
- */
59
+ let cached: SystemGlass | null | undefined;
60
+
61
+ function systemGlass(): SystemGlass | null {
62
+ if (cached === undefined) cached = resolveSystemGlass();
63
+ return cached;
64
+ }
65
+
66
+ export function loadGlassView(): GlassViewComponent | null {
67
+ return systemGlass()?.view ?? null;
68
+ }
69
+
48
70
  export function loadGlassContainer(): GlassContainerComponent | null {
49
- if (cachedGlassView === undefined) loadGlassView();
50
- return cachedGlassContainer ?? null;
71
+ return systemGlass()?.container ?? null;
51
72
  }
@@ -1,16 +1,19 @@
1
1
  /**
2
2
  * @vitest-environment jsdom
3
3
  *
4
- * LC-176 / LC-266: on the web only iOS Safari takes the glass face (Clay on
5
- * rv_3bb3f1f65d6b981f).
4
+ * LC-176 / LC-266 (FZ-8666): no browser can draw Liquid Glass, so no browser
5
+ * draws glass, iOS Safari included. The drawn face stays behind one switch in
6
+ * `glass.ts` until Clay answers the compare.
6
7
  */
7
8
 
8
- import { createDefaultThemeConfig } from "@multiplatform.one/theme";
9
- import { cleanup, render } from "@testing-library/react";
9
+ import { createDefaultThemeConfig, Surface } from "@multiplatform.one/theme";
10
+ import { cleanup, render, screen } from "@testing-library/react";
10
11
  import { createElement } from "react";
11
12
  import { TamaguiProvider } from "tamagui";
12
13
  import { afterEach, describe, expect, it, vi } from "vitest";
13
- import { isAppleTouchWeb, loadGlassView } from "./glass";
14
+ import { isAppleTouchWeb, loadGlassContainer, loadGlassView } from "./glass";
15
+ import { IconButton, IconButtonGroup } from "./index";
16
+ import { WebGlassFace } from "./webGlass";
14
17
 
15
18
  const IPHONE =
16
19
  "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";
@@ -44,30 +47,75 @@ describe("isAppleTouchWeb", () => {
44
47
  });
45
48
  });
46
49
 
47
- describe("loadGlassView on the web", () => {
50
+ function Glyph({ size }: { size?: number }) {
51
+ return createElement("svg", { "data-glyph": "", width: size, height: size });
52
+ }
53
+
54
+ function renderBar(ui: ReturnType<typeof createElement>) {
55
+ return render(
56
+ createElement(
57
+ TamaguiProvider,
58
+ { config: houseConfig.tamagui, defaultTheme: "light", disableInjectCSS: true },
59
+ createElement(Surface, { bar: true }, ui),
60
+ ),
61
+ );
62
+ }
63
+
64
+ describe("no glass on the web (LC-176)", () => {
48
65
  afterEach(() => {
49
66
  cleanup();
50
67
  vi.unstubAllGlobals();
51
68
  });
52
69
 
53
- it("iOS Safari's glass circle sits under the glyph it backs", () => {
54
- vi.stubGlobal("navigator", { userAgent: IPHONE, platform: "iPhone", maxTouchPoints: 5 });
55
- const Glass = loadGlassView();
56
- expect(Glass).not.toBeNull();
70
+ it.each([
71
+ ["iOS Safari", { userAgent: IPHONE, platform: "iPhone", maxTouchPoints: 5 }],
72
+ ["iPadOS Safari", { userAgent: MAC_SAFARI, platform: "MacIntel", maxTouchPoints: 5 }],
73
+ ["desktop Safari", { userAgent: MAC_SAFARI, platform: "MacIntel", maxTouchPoints: 0 }],
74
+ ["Android Chrome", { userAgent: PIXEL, platform: "Linux armv8l", maxTouchPoints: 5 }],
75
+ ])("%s loads no glass and draws the plain glyph", (_browser, nav) => {
76
+ vi.stubGlobal("navigator", nav);
77
+ expect(loadGlassView()).toBeNull();
78
+ expect(loadGlassContainer()).toBeNull();
79
+ renderBar(
80
+ createElement(
81
+ "div",
82
+ null,
83
+ createElement(
84
+ IconButtonGroup,
85
+ null,
86
+ createElement(IconButton, { icon: Glyph, "aria-label": "Search" }),
87
+ createElement(IconButton, { icon: Glyph, "aria-label": "More" }),
88
+ ),
89
+ createElement(IconButton, { icon: Glyph, "aria-label": "Back" }),
90
+ ),
91
+ );
92
+ expect(document.querySelector("[data-mp-glass]")).toBeNull();
93
+ expect(document.querySelector("[data-mp-glass-capsule]")).toBeNull();
94
+ expect(document.querySelector('[data-mp-icon-button-group="bar"]')).not.toBeNull();
95
+ for (const name of ["Search", "More", "Back"])
96
+ expect(screen.getByLabelText(name).getAttribute("data-mp-icon-button")).toBe("glyph");
97
+ });
98
+ });
99
+
100
+ describe("the drawn web face behind the switch", () => {
101
+ afterEach(cleanup);
102
+
103
+ it("sits under the glyph it backs, which stays its sibling", () => {
57
104
  const { container } = render(
58
105
  createElement(
59
106
  TamaguiProvider,
60
107
  { config: houseConfig.tamagui, defaultTheme: "light", disableInjectCSS: true },
61
- createElement(Glass!, { style: { position: "absolute" } }),
108
+ createElement(
109
+ WebGlassFace,
110
+ { style: { position: "absolute" } },
111
+ createElement("svg", { "data-glyph": "" }),
112
+ ),
62
113
  ),
63
114
  );
64
115
  const face = container.querySelector('[data-mp-glass="web"]') as HTMLElement | null;
65
116
  expect(face).not.toBeNull();
66
117
  expect(face!.style.zIndex).toBe("-1");
67
- });
68
-
69
- it("every other browser draws the bare glyph", () => {
70
- vi.stubGlobal("navigator", { userAgent: PIXEL, platform: "Linux armv8l", maxTouchPoints: 5 });
71
- expect(loadGlassView()).toBeNull();
118
+ expect(face!.querySelector("[data-glyph]")).toBeNull();
119
+ expect(face!.nextElementSibling?.hasAttribute("data-glyph")).toBe(true);
72
120
  });
73
121
  });
@@ -4,9 +4,10 @@ import { isAppleTouchWeb, WebGlassFace } from "./webGlass";
4
4
  /** The slice of expo-glass-effect's `GlassView` an icon button draws. */
5
5
  export type GlassViewComponent = ComponentType<{
6
6
  style?: unknown;
7
- glassEffectStyle?: "regular" | "clear";
7
+ glassEffectStyle?: "regular" | "clear" | "none";
8
8
  isInteractive?: boolean;
9
- pointerEvents?: "none" | "auto" | "box-none" | "box-only";
9
+ colorScheme?: "auto" | "light" | "dark";
10
+ children?: ReactNode;
10
11
  }>;
11
12
 
12
13
  /** The slice of expo-glass-effect's `GlassContainer` an icon button group draws. */
@@ -19,20 +20,23 @@ export type GlassContainerComponent = ComponentType<{
19
20
  export { isAppleTouchWeb } from "./webGlass";
20
21
 
21
22
  /**
22
- * LC-176: the iOS 26 Liquid Glass material an icon button floats on in a bar.
23
- * Native iOS draws it from expo-glass-effect (`glass.native.ts`; Android
24
- * probes null). On the web only iOS Safari takes the glass face (Clay on
25
- * rv_3bb3f1f65d6b981f: "Is there a way to have the buttons at the bottom be
26
- * liquid glass if it's an iPhone"); every other browser draws the bare glyph.
23
+ * LC-176 (FZ-8666): iOS Safari cannot draw Liquid Glass, so it draws no glass.
24
+ * This is the one switch for the drawn face Clay asked for on
25
+ * rv_3bb3f1f65d6b981f, off since his note on rv_d481d34557e702ef, until he
26
+ * answers rv_c6369c2bab2ffea0.
27
27
  */
28
- export function loadGlassView(): GlassViewComponent | null {
29
- return isAppleTouchWeb() ? (WebGlassFace as GlassViewComponent) : null;
30
- }
28
+ const iosSafariGlassFace = false;
31
29
 
32
30
  /**
33
- * LC-176: the container that merges neighbouring Liquid Glass circles into one
34
- * shape, as iOS 26 groups bar buttons. Only iOS has it.
31
+ * LC-176: the system Liquid Glass an icon button sits in on a bar. Only native
32
+ * iOS 26 has it, from expo-glass-effect (`glass.native.ts`; Android probes
33
+ * null), so the web draws the plain glyph.
35
34
  */
35
+ export function loadGlassView(): GlassViewComponent | null {
36
+ return iosSafariGlassFace && isAppleTouchWeb() ? (WebGlassFace as GlassViewComponent) : null;
37
+ }
38
+
39
+ /** LC-176: the system's Liquid Glass container. Only iOS has it. */
36
40
  export function loadGlassContainer(): GlassContainerComponent | null {
37
41
  return null;
38
42
  }
@@ -4,9 +4,8 @@ const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : us
4
4
 
5
5
  /**
6
6
  * LC-176: the ground an `IconButtonGroup` paints under its buttons. `media`
7
- * is one shared material capsule and `glass` one shared glass capsule (Liquid
8
- * Glass on iOS 26, its web twin in iOS Safari),
9
- * so a button inside draws its bare glyph instead of its own circle.
7
+ * is one shared dark capsule and `glass` one shared Liquid Glass capsule, so
8
+ * a button inside draws its bare glyph instead of its own circle.
10
9
  */
11
10
  export type IconButtonGroupGround = "media" | "glass" | null;
12
11
 
@@ -30,3 +29,28 @@ export function useIconButtonGroupMember(iconOnly: boolean): void {
30
29
  [iconOnly, register],
31
30
  );
32
31
  }
32
+
33
+ /**
34
+ * The window frame and corner radius of the plate an `IconButtonGroup` paints
35
+ * (a native Liquid Glass capsule). Native has no DOM to walk, so a panel
36
+ * opening from a grouped button reads the plate it covers here (LC-112,
37
+ * FZ-8550).
38
+ */
39
+ export interface IconButtonGroupPlate {
40
+ x: number;
41
+ y: number;
42
+ width: number;
43
+ height: number;
44
+ radius: number;
45
+ }
46
+
47
+ export interface IconButtonGroupSurface {
48
+ /** Answers once: the plate, or null when it cannot be measured. */
49
+ measure(callback: (plate: IconButtonGroupPlate | null) => void): void;
50
+ }
51
+
52
+ export const IconButtonGroupSurfaceContext = createContext<IconButtonGroupSurface | null>(null);
53
+
54
+ export function useIconButtonGroupSurface(): IconButtonGroupSurface | null {
55
+ return useContext(IconButtonGroupSurfaceContext);
56
+ }