@multiplatform.one/forms 7.71.0 → 7.73.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 (381) hide show
  1. package/dist/cjs/Button/circleGlyph.cjs +28 -0
  2. package/dist/cjs/Button/circleGlyph.native.js +31 -0
  3. package/dist/cjs/Button/circleGlyph.native.js.map +1 -0
  4. package/dist/cjs/Button/index.cjs +19 -3
  5. package/dist/cjs/Button/index.native.js +19 -3
  6. package/dist/cjs/Button/index.native.js.map +1 -1
  7. package/dist/cjs/FloatingPanel/PanelHeader.cjs +49 -0
  8. package/dist/cjs/FloatingPanel/PanelHeader.native.js +53 -0
  9. package/dist/cjs/FloatingPanel/PanelHeader.native.js.map +1 -0
  10. package/dist/cjs/FloatingPanel/echoContext.cjs +67 -0
  11. package/dist/cjs/FloatingPanel/echoContext.native.js +71 -0
  12. package/dist/cjs/FloatingPanel/echoContext.native.js.map +1 -0
  13. package/dist/cjs/FloatingPanel/iconEcho.cjs +104 -0
  14. package/dist/cjs/FloatingPanel/iconEcho.native.js +114 -0
  15. package/dist/cjs/FloatingPanel/iconEcho.native.js.map +1 -0
  16. package/dist/cjs/FloatingPanel/index.cjs +371 -86
  17. package/dist/cjs/FloatingPanel/index.native.js +164 -20
  18. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  19. package/dist/cjs/FloatingPanel/paintedBox.cjs +80 -0
  20. package/dist/cjs/FloatingPanel/paintedBox.native.js +85 -0
  21. package/dist/cjs/FloatingPanel/paintedBox.native.js.map +1 -0
  22. package/dist/cjs/FloatingPanel/panelWidth.cjs +92 -0
  23. package/dist/cjs/FloatingPanel/panelWidth.native.js +99 -0
  24. package/dist/cjs/FloatingPanel/panelWidth.native.js.map +1 -0
  25. package/dist/cjs/FloatingPanel/pointAnchor.cjs +34 -5
  26. package/dist/cjs/FloatingPanel/pointAnchor.native.js +37 -8
  27. package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
  28. package/dist/cjs/FloatingPanel/useFloatingPanel.cjs +60 -5
  29. package/dist/cjs/FloatingPanel/useFloatingPanel.native.js +72 -5
  30. package/dist/cjs/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  31. package/dist/cjs/InputParts/index.cjs +18 -0
  32. package/dist/cjs/InputParts/index.native.js +19 -1
  33. package/dist/cjs/InputParts/index.native.js.map +1 -1
  34. package/dist/cjs/Wheel/NativeWheel.native.js +1 -1
  35. package/dist/cjs/Wheel/NativeWheel.native.js.map +1 -1
  36. package/dist/cjs/Wheel/index.cjs +2 -1
  37. package/dist/cjs/Wheel/index.native.js +4 -1
  38. package/dist/cjs/Wheel/index.native.js.map +1 -1
  39. package/dist/cjs/Wheel/wheelShared.cjs +6 -1
  40. package/dist/cjs/Wheel/wheelShared.native.js +7 -1
  41. package/dist/cjs/Wheel/wheelShared.native.js.map +1 -1
  42. package/dist/cjs/fields/Combobox/index.cjs +1 -0
  43. package/dist/cjs/fields/Combobox/index.native.js +1 -0
  44. package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
  45. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.cjs +6 -30
  46. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js +11 -34
  47. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  48. package/dist/cjs/fields/DatePicker/DatetimePicker.native.js.map +1 -1
  49. package/dist/cjs/fields/DatePicker/catalog.cjs +2 -28
  50. package/dist/cjs/fields/DatePicker/catalog.native.js +2 -30
  51. package/dist/cjs/fields/DatePicker/catalog.native.js.map +1 -1
  52. package/dist/cjs/fields/DatePicker/index.native.js.map +1 -1
  53. package/dist/cjs/fields/DatePicker/parts.cjs +11 -20
  54. package/dist/cjs/fields/DatePicker/parts.native.js +13 -22
  55. package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
  56. package/dist/cjs/fields/Select/index.cjs +65 -44
  57. package/dist/cjs/fields/Select/index.native.js +69 -47
  58. package/dist/cjs/fields/Select/index.native.js.map +1 -1
  59. package/dist/cjs/fields/Select/shouldInlineChoices.cjs +3 -1
  60. package/dist/cjs/fields/Select/shouldInlineChoices.native.js +3 -1
  61. package/dist/cjs/fields/Select/shouldInlineChoices.native.js.map +1 -1
  62. package/dist/cjs/fields/TimePicker/catalog.cjs +4 -25
  63. package/dist/cjs/fields/TimePicker/catalog.native.js +7 -29
  64. package/dist/cjs/fields/TimePicker/catalog.native.js.map +1 -1
  65. package/dist/cjs/fields/TimePicker/index.native.js.map +1 -1
  66. package/dist/cjs/fields/TimePicker/parts.cjs +8 -21
  67. package/dist/cjs/fields/TimePicker/parts.native.js +8 -21
  68. package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
  69. package/dist/cjs/index.cjs +2 -0
  70. package/dist/cjs/index.native.js +2 -0
  71. package/dist/cjs/index.native.js.map +1 -1
  72. package/dist/cjs/shared/floatingList.cjs +13 -4
  73. package/dist/cjs/shared/floatingList.native.js +15 -4
  74. package/dist/cjs/shared/floatingList.native.js.map +1 -1
  75. package/dist/esm/Button/circleGlyph.mjs +3 -0
  76. package/dist/esm/Button/circleGlyph.mjs.map +1 -0
  77. package/dist/esm/Button/circleGlyph.native.js +3 -0
  78. package/dist/esm/Button/circleGlyph.native.js.map +1 -0
  79. package/dist/esm/Button/index.mjs +18 -2
  80. package/dist/esm/Button/index.mjs.map +1 -1
  81. package/dist/esm/Button/index.native.js +18 -2
  82. package/dist/esm/Button/index.native.js.map +1 -1
  83. package/dist/esm/FloatingPanel/PanelHeader.mjs +24 -0
  84. package/dist/esm/FloatingPanel/PanelHeader.mjs.map +1 -0
  85. package/dist/esm/FloatingPanel/PanelHeader.native.js +25 -0
  86. package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -0
  87. package/dist/esm/FloatingPanel/echoContext.mjs +35 -0
  88. package/dist/esm/FloatingPanel/echoContext.mjs.map +1 -0
  89. package/dist/esm/FloatingPanel/echoContext.native.js +36 -0
  90. package/dist/esm/FloatingPanel/echoContext.native.js.map +1 -0
  91. package/dist/esm/FloatingPanel/iconEcho.mjs +77 -0
  92. package/dist/esm/FloatingPanel/iconEcho.mjs.map +1 -0
  93. package/dist/esm/FloatingPanel/iconEcho.native.js +84 -0
  94. package/dist/esm/FloatingPanel/iconEcho.native.js.map +1 -0
  95. package/dist/esm/FloatingPanel/index.mjs +373 -90
  96. package/dist/esm/FloatingPanel/index.mjs.map +1 -1
  97. package/dist/esm/FloatingPanel/index.native.js +165 -23
  98. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  99. package/dist/esm/FloatingPanel/paintedBox.mjs +52 -0
  100. package/dist/esm/FloatingPanel/paintedBox.mjs.map +1 -0
  101. package/dist/esm/FloatingPanel/paintedBox.native.js +54 -0
  102. package/dist/esm/FloatingPanel/paintedBox.native.js.map +1 -0
  103. package/dist/esm/FloatingPanel/panelWidth.mjs +63 -0
  104. package/dist/esm/FloatingPanel/panelWidth.mjs.map +1 -0
  105. package/dist/esm/FloatingPanel/panelWidth.native.js +67 -0
  106. package/dist/esm/FloatingPanel/panelWidth.native.js.map +1 -0
  107. package/dist/esm/FloatingPanel/pointAnchor.mjs +34 -5
  108. package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
  109. package/dist/esm/FloatingPanel/pointAnchor.native.js +37 -8
  110. package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
  111. package/dist/esm/FloatingPanel/useFloatingPanel.mjs +60 -6
  112. package/dist/esm/FloatingPanel/useFloatingPanel.mjs.map +1 -1
  113. package/dist/esm/FloatingPanel/useFloatingPanel.native.js +72 -6
  114. package/dist/esm/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  115. package/dist/esm/InputParts/index.mjs +19 -1
  116. package/dist/esm/InputParts/index.mjs.map +1 -1
  117. package/dist/esm/InputParts/index.native.js +20 -2
  118. package/dist/esm/InputParts/index.native.js.map +1 -1
  119. package/dist/esm/Wheel/NativeWheel.native.js +1 -1
  120. package/dist/esm/Wheel/NativeWheel.native.js.map +1 -1
  121. package/dist/esm/Wheel/index.mjs +2 -1
  122. package/dist/esm/Wheel/index.mjs.map +1 -1
  123. package/dist/esm/Wheel/index.native.js +4 -1
  124. package/dist/esm/Wheel/index.native.js.map +1 -1
  125. package/dist/esm/Wheel/wheelShared.mjs +6 -1
  126. package/dist/esm/Wheel/wheelShared.mjs.map +1 -1
  127. package/dist/esm/Wheel/wheelShared.native.js +7 -1
  128. package/dist/esm/Wheel/wheelShared.native.js.map +1 -1
  129. package/dist/esm/fields/Combobox/index.mjs +1 -0
  130. package/dist/esm/fields/Combobox/index.mjs.map +1 -1
  131. package/dist/esm/fields/Combobox/index.native.js +1 -0
  132. package/dist/esm/fields/Combobox/index.native.js.map +1 -1
  133. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.mjs +6 -30
  134. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.mjs.map +1 -1
  135. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.native.js +11 -34
  136. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  137. package/dist/esm/fields/DatePicker/DatetimePicker.native.js.map +1 -1
  138. package/dist/esm/fields/DatePicker/catalog.mjs +2 -28
  139. package/dist/esm/fields/DatePicker/catalog.mjs.map +1 -1
  140. package/dist/esm/fields/DatePicker/catalog.native.js +2 -30
  141. package/dist/esm/fields/DatePicker/catalog.native.js.map +1 -1
  142. package/dist/esm/fields/DatePicker/index.native.js.map +1 -1
  143. package/dist/esm/fields/DatePicker/parts.mjs +11 -20
  144. package/dist/esm/fields/DatePicker/parts.mjs.map +1 -1
  145. package/dist/esm/fields/DatePicker/parts.native.js +13 -22
  146. package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
  147. package/dist/esm/fields/Select/index.mjs +66 -45
  148. package/dist/esm/fields/Select/index.mjs.map +1 -1
  149. package/dist/esm/fields/Select/index.native.js +70 -48
  150. package/dist/esm/fields/Select/index.native.js.map +1 -1
  151. package/dist/esm/fields/Select/shouldInlineChoices.mjs +3 -1
  152. package/dist/esm/fields/Select/shouldInlineChoices.mjs.map +1 -1
  153. package/dist/esm/fields/Select/shouldInlineChoices.native.js +3 -1
  154. package/dist/esm/fields/Select/shouldInlineChoices.native.js.map +1 -1
  155. package/dist/esm/fields/TimePicker/catalog.mjs +4 -25
  156. package/dist/esm/fields/TimePicker/catalog.mjs.map +1 -1
  157. package/dist/esm/fields/TimePicker/catalog.native.js +7 -29
  158. package/dist/esm/fields/TimePicker/catalog.native.js.map +1 -1
  159. package/dist/esm/fields/TimePicker/index.native.js.map +1 -1
  160. package/dist/esm/fields/TimePicker/parts.mjs +8 -21
  161. package/dist/esm/fields/TimePicker/parts.mjs.map +1 -1
  162. package/dist/esm/fields/TimePicker/parts.native.js +8 -21
  163. package/dist/esm/fields/TimePicker/parts.native.js.map +1 -1
  164. package/dist/esm/index.mjs +2 -1
  165. package/dist/esm/index.mjs.map +1 -1
  166. package/dist/esm/index.native.js +2 -1
  167. package/dist/esm/index.native.js.map +1 -1
  168. package/dist/esm/shared/floatingList.mjs +14 -5
  169. package/dist/esm/shared/floatingList.mjs.map +1 -1
  170. package/dist/esm/shared/floatingList.native.js +16 -5
  171. package/dist/esm/shared/floatingList.native.js.map +1 -1
  172. package/dist/jsx/Button/circleGlyph.mjs +3 -0
  173. package/dist/jsx/Button/circleGlyph.mjs.map +1 -0
  174. package/dist/jsx/Button/circleGlyph.native.js +31 -0
  175. package/dist/jsx/Button/circleGlyph.native.js.map +1 -0
  176. package/dist/jsx/Button/index.mjs +18 -2
  177. package/dist/jsx/Button/index.mjs.map +1 -1
  178. package/dist/jsx/Button/index.native.js +19 -3
  179. package/dist/jsx/Button/index.native.js.map +1 -1
  180. package/dist/jsx/FloatingPanel/PanelHeader.mjs +24 -0
  181. package/dist/jsx/FloatingPanel/PanelHeader.mjs.map +1 -0
  182. package/dist/jsx/FloatingPanel/PanelHeader.native.js +53 -0
  183. package/dist/jsx/FloatingPanel/PanelHeader.native.js.map +1 -0
  184. package/dist/jsx/FloatingPanel/echoContext.mjs +35 -0
  185. package/dist/jsx/FloatingPanel/echoContext.mjs.map +1 -0
  186. package/dist/jsx/FloatingPanel/echoContext.native.js +71 -0
  187. package/dist/jsx/FloatingPanel/echoContext.native.js.map +1 -0
  188. package/dist/jsx/FloatingPanel/iconEcho.mjs +77 -0
  189. package/dist/jsx/FloatingPanel/iconEcho.mjs.map +1 -0
  190. package/dist/jsx/FloatingPanel/iconEcho.native.js +114 -0
  191. package/dist/jsx/FloatingPanel/iconEcho.native.js.map +1 -0
  192. package/dist/jsx/FloatingPanel/index.mjs +373 -90
  193. package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
  194. package/dist/jsx/FloatingPanel/index.native.js +164 -20
  195. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  196. package/dist/jsx/FloatingPanel/paintedBox.mjs +52 -0
  197. package/dist/jsx/FloatingPanel/paintedBox.mjs.map +1 -0
  198. package/dist/jsx/FloatingPanel/paintedBox.native.js +85 -0
  199. package/dist/jsx/FloatingPanel/paintedBox.native.js.map +1 -0
  200. package/dist/jsx/FloatingPanel/panelWidth.mjs +63 -0
  201. package/dist/jsx/FloatingPanel/panelWidth.mjs.map +1 -0
  202. package/dist/jsx/FloatingPanel/panelWidth.native.js +99 -0
  203. package/dist/jsx/FloatingPanel/panelWidth.native.js.map +1 -0
  204. package/dist/jsx/FloatingPanel/pointAnchor.mjs +34 -5
  205. package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
  206. package/dist/jsx/FloatingPanel/pointAnchor.native.js +37 -8
  207. package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
  208. package/dist/jsx/FloatingPanel/useFloatingPanel.mjs +60 -6
  209. package/dist/jsx/FloatingPanel/useFloatingPanel.mjs.map +1 -1
  210. package/dist/jsx/FloatingPanel/useFloatingPanel.native.js +72 -5
  211. package/dist/jsx/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  212. package/dist/jsx/InputParts/index.mjs +19 -1
  213. package/dist/jsx/InputParts/index.mjs.map +1 -1
  214. package/dist/jsx/InputParts/index.native.js +19 -1
  215. package/dist/jsx/InputParts/index.native.js.map +1 -1
  216. package/dist/jsx/Wheel/NativeWheel.native.js +1 -1
  217. package/dist/jsx/Wheel/NativeWheel.native.js.map +1 -1
  218. package/dist/jsx/Wheel/index.mjs +2 -1
  219. package/dist/jsx/Wheel/index.mjs.map +1 -1
  220. package/dist/jsx/Wheel/index.native.js +4 -1
  221. package/dist/jsx/Wheel/index.native.js.map +1 -1
  222. package/dist/jsx/Wheel/wheelShared.mjs +6 -1
  223. package/dist/jsx/Wheel/wheelShared.mjs.map +1 -1
  224. package/dist/jsx/Wheel/wheelShared.native.js +7 -1
  225. package/dist/jsx/Wheel/wheelShared.native.js.map +1 -1
  226. package/dist/jsx/fields/Combobox/index.mjs +1 -0
  227. package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
  228. package/dist/jsx/fields/Combobox/index.native.js +1 -0
  229. package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
  230. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.mjs +6 -30
  231. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.mjs.map +1 -1
  232. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.native.js +11 -34
  233. package/dist/jsx/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  234. package/dist/jsx/fields/DatePicker/DatetimePicker.native.js.map +1 -1
  235. package/dist/jsx/fields/DatePicker/catalog.mjs +2 -28
  236. package/dist/jsx/fields/DatePicker/catalog.mjs.map +1 -1
  237. package/dist/jsx/fields/DatePicker/catalog.native.js +2 -30
  238. package/dist/jsx/fields/DatePicker/catalog.native.js.map +1 -1
  239. package/dist/jsx/fields/DatePicker/index.native.js.map +1 -1
  240. package/dist/jsx/fields/DatePicker/parts.mjs +11 -20
  241. package/dist/jsx/fields/DatePicker/parts.mjs.map +1 -1
  242. package/dist/jsx/fields/DatePicker/parts.native.js +13 -22
  243. package/dist/jsx/fields/DatePicker/parts.native.js.map +1 -1
  244. package/dist/jsx/fields/Select/index.mjs +66 -45
  245. package/dist/jsx/fields/Select/index.mjs.map +1 -1
  246. package/dist/jsx/fields/Select/index.native.js +69 -47
  247. package/dist/jsx/fields/Select/index.native.js.map +1 -1
  248. package/dist/jsx/fields/Select/shouldInlineChoices.mjs +3 -1
  249. package/dist/jsx/fields/Select/shouldInlineChoices.mjs.map +1 -1
  250. package/dist/jsx/fields/Select/shouldInlineChoices.native.js +3 -1
  251. package/dist/jsx/fields/Select/shouldInlineChoices.native.js.map +1 -1
  252. package/dist/jsx/fields/TimePicker/catalog.mjs +4 -25
  253. package/dist/jsx/fields/TimePicker/catalog.mjs.map +1 -1
  254. package/dist/jsx/fields/TimePicker/catalog.native.js +7 -29
  255. package/dist/jsx/fields/TimePicker/catalog.native.js.map +1 -1
  256. package/dist/jsx/fields/TimePicker/index.native.js.map +1 -1
  257. package/dist/jsx/fields/TimePicker/parts.mjs +8 -21
  258. package/dist/jsx/fields/TimePicker/parts.mjs.map +1 -1
  259. package/dist/jsx/fields/TimePicker/parts.native.js +8 -21
  260. package/dist/jsx/fields/TimePicker/parts.native.js.map +1 -1
  261. package/dist/jsx/index.js +2 -1
  262. package/dist/jsx/index.js.map +1 -1
  263. package/dist/jsx/index.mjs +2 -1
  264. package/dist/jsx/index.mjs.map +1 -1
  265. package/dist/jsx/index.native.js +2 -0
  266. package/dist/jsx/index.native.js.map +1 -1
  267. package/dist/jsx/shared/floatingList.mjs +14 -5
  268. package/dist/jsx/shared/floatingList.mjs.map +1 -1
  269. package/dist/jsx/shared/floatingList.native.js +15 -4
  270. package/dist/jsx/shared/floatingList.native.js.map +1 -1
  271. package/package.json +8 -8
  272. package/src/Button/circleGlyph.ts +6 -0
  273. package/src/Button/index.tsx +25 -7
  274. package/src/FloatingPanel/FloatingPanel.echoStretch.spec.tsx +67 -0
  275. package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +297 -0
  276. package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +280 -3
  277. package/src/FloatingPanel/FloatingPanel.seamlessCover.spec.tsx +237 -0
  278. package/src/FloatingPanel/PanelHeader.tsx +26 -0
  279. package/src/FloatingPanel/echoContext.ts +67 -0
  280. package/src/FloatingPanel/iconEcho.tsx +118 -0
  281. package/src/FloatingPanel/index.native.tsx +220 -12
  282. package/src/FloatingPanel/index.tsx +539 -109
  283. package/src/FloatingPanel/paintedBox.spec.ts +95 -0
  284. package/src/FloatingPanel/paintedBox.ts +75 -0
  285. package/src/FloatingPanel/panelWidth.spec.ts +158 -0
  286. package/src/FloatingPanel/panelWidth.ts +122 -0
  287. package/src/FloatingPanel/panelWidthContract.spec.ts +10 -10
  288. package/src/FloatingPanel/pointAnchor.spec.ts +111 -2
  289. package/src/FloatingPanel/pointAnchor.ts +56 -3
  290. package/src/FloatingPanel/useFloatingPanel.spec.tsx +124 -4
  291. package/src/FloatingPanel/useFloatingPanel.ts +112 -34
  292. package/src/InputParts/index.tsx +26 -0
  293. package/src/Wheel/NativeWheel.native.tsx +1 -1
  294. package/src/Wheel/Wheel.spec.tsx +9 -0
  295. package/src/Wheel/index.tsx +12 -3
  296. package/src/Wheel/wheelShared.ts +6 -2
  297. package/src/fields/Combobox/index.tsx +1 -0
  298. package/src/fields/DatePicker/DatePicker.stories.tsx +3 -3
  299. package/src/fields/DatePicker/DatetimePicker.catalog.tsx +17 -45
  300. package/src/fields/DatePicker/DatetimePicker.native.tsx +3 -0
  301. package/src/fields/DatePicker/catalog.tsx +8 -39
  302. package/src/fields/DatePicker/index.native.tsx +3 -0
  303. package/src/fields/DatePicker/parts.tsx +13 -23
  304. package/src/fields/DatePicker/seamlessCover.spec.tsx +180 -0
  305. package/src/fields/DatePicker/touchWeb.spec.tsx +109 -167
  306. package/src/fields/Select/Select.containingBlock.spec.tsx +191 -0
  307. package/src/fields/Select/Select.stories.tsx +43 -0
  308. package/src/fields/Select/index.tsx +109 -45
  309. package/src/fields/Select/shouldInlineChoices.spec.ts +13 -0
  310. package/src/fields/Select/shouldInlineChoices.ts +6 -0
  311. package/src/fields/TimePicker/catalog.tsx +12 -33
  312. package/src/fields/TimePicker/index.native.tsx +2 -0
  313. package/src/fields/TimePicker/parts.tsx +6 -20
  314. package/src/index.ts +3 -0
  315. package/src/shared/floatingList.tsx +21 -5
  316. package/types/Button/circleGlyph.d.ts +7 -0
  317. package/types/Button/circleGlyph.d.ts.map +1 -0
  318. package/types/Button/index.d.ts +2 -6
  319. package/types/Button/index.d.ts.map +1 -1
  320. package/types/FloatingPanel/PanelHeader.d.ts +9 -0
  321. package/types/FloatingPanel/PanelHeader.d.ts.map +1 -0
  322. package/types/FloatingPanel/echoContext.d.ts +38 -0
  323. package/types/FloatingPanel/echoContext.d.ts.map +1 -0
  324. package/types/FloatingPanel/iconEcho.d.ts +29 -0
  325. package/types/FloatingPanel/iconEcho.d.ts.map +1 -0
  326. package/types/FloatingPanel/index.d.ts +69 -0
  327. package/types/FloatingPanel/index.d.ts.map +1 -1
  328. package/types/FloatingPanel/index.native.d.ts +9 -1
  329. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  330. package/types/FloatingPanel/paintedBox.d.ts +29 -0
  331. package/types/FloatingPanel/paintedBox.d.ts.map +1 -0
  332. package/types/FloatingPanel/panelWidth.d.ts +71 -0
  333. package/types/FloatingPanel/panelWidth.d.ts.map +1 -0
  334. package/types/FloatingPanel/pointAnchor.d.ts +17 -1
  335. package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
  336. package/types/FloatingPanel/useFloatingPanel.d.ts +33 -2
  337. package/types/FloatingPanel/useFloatingPanel.d.ts.map +1 -1
  338. package/types/InputParts/index.d.ts.map +1 -1
  339. package/types/Wheel/index.d.ts.map +1 -1
  340. package/types/Wheel/wheelShared.d.ts +2 -1
  341. package/types/Wheel/wheelShared.d.ts.map +1 -1
  342. package/types/fields/Combobox/index.d.ts.map +1 -1
  343. package/types/fields/DatePicker/DatetimePicker.catalog.d.ts +6 -0
  344. package/types/fields/DatePicker/DatetimePicker.catalog.d.ts.map +1 -1
  345. package/types/fields/DatePicker/DatetimePicker.native.d.ts +3 -0
  346. package/types/fields/DatePicker/DatetimePicker.native.d.ts.map +1 -1
  347. package/types/fields/DatePicker/catalog.d.ts +6 -0
  348. package/types/fields/DatePicker/catalog.d.ts.map +1 -1
  349. package/types/fields/DatePicker/index.native.d.ts +3 -0
  350. package/types/fields/DatePicker/index.native.d.ts.map +1 -1
  351. package/types/fields/DatePicker/parts.d.ts +5 -7
  352. package/types/fields/DatePicker/parts.d.ts.map +1 -1
  353. package/types/fields/Select/index.d.ts +8 -1
  354. package/types/fields/Select/index.d.ts.map +1 -1
  355. package/types/fields/Select/shouldInlineChoices.d.ts +5 -1
  356. package/types/fields/Select/shouldInlineChoices.d.ts.map +1 -1
  357. package/types/fields/TimePicker/catalog.d.ts +7 -1
  358. package/types/fields/TimePicker/catalog.d.ts.map +1 -1
  359. package/types/fields/TimePicker/index.native.d.ts +2 -0
  360. package/types/fields/TimePicker/index.native.d.ts.map +1 -1
  361. package/types/fields/TimePicker/parts.d.ts +1 -3
  362. package/types/fields/TimePicker/parts.d.ts.map +1 -1
  363. package/types/index.d.ts +3 -0
  364. package/types/index.d.ts.map +1 -1
  365. package/types/shared/floatingList.d.ts +5 -1
  366. package/types/shared/floatingList.d.ts.map +1 -1
  367. package/dist/cjs/shared/touchWebPicker.cjs +0 -136
  368. package/dist/cjs/shared/touchWebPicker.native.js +0 -154
  369. package/dist/cjs/shared/touchWebPicker.native.js.map +0 -1
  370. package/dist/esm/shared/touchWebPicker.mjs +0 -105
  371. package/dist/esm/shared/touchWebPicker.mjs.map +0 -1
  372. package/dist/esm/shared/touchWebPicker.native.js +0 -120
  373. package/dist/esm/shared/touchWebPicker.native.js.map +0 -1
  374. package/dist/jsx/shared/touchWebPicker.mjs +0 -105
  375. package/dist/jsx/shared/touchWebPicker.mjs.map +0 -1
  376. package/dist/jsx/shared/touchWebPicker.native.js +0 -154
  377. package/dist/jsx/shared/touchWebPicker.native.js.map +0 -1
  378. package/src/shared/touchWebPicker.spec.ts +0 -98
  379. package/src/shared/touchWebPicker.tsx +0 -158
  380. package/types/shared/touchWebPicker.d.ts +0 -40
  381. package/types/shared/touchWebPicker.d.ts.map +0 -1
@@ -4,12 +4,16 @@
4
4
  * so only inside another overlay, so a sheet never stacks on a sheet.
5
5
  */
6
6
  import { renderWithProviders } from "@multiplatform.one/test-utils";
7
- import { act } from "@testing-library/react";
7
+ import { act, render } from "@testing-library/react";
8
+ import { PlusIcon, XIcon } from "@phosphor-icons/react";
8
9
  import { useState, type ReactNode } from "react";
9
10
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
10
- import { Text, XStack, YStack } from "tamagui";
11
+ import { Text, XStack, YStack, getTokens, getVariableValue } from "tamagui";
12
+ import { Button } from "../Button";
13
+ import { Input as InputParts } from "../InputParts";
11
14
  import { OverlayStackProvider } from "../shared/overlayStack";
12
15
  import { MENU_MIN_WIDTH, type CompactFace } from "./compactFace";
16
+ import type { PanelWidth } from "./panelWidth";
13
17
  import { FloatingPanel } from "./index.native";
14
18
  import { panelViewportPadding } from "./useFloatingPanel";
15
19
 
@@ -49,13 +53,22 @@ afterEach(() => {
49
53
  vi.useRealTimers();
50
54
  });
51
55
 
52
- function Menu({ face, attach = false }: { face?: CompactFace; attach?: boolean }) {
56
+ function Menu({
57
+ face,
58
+ attach = false,
59
+ width,
60
+ }: {
61
+ face?: CompactFace;
62
+ attach?: boolean;
63
+ width?: PanelWidth;
64
+ }) {
53
65
  const [open, setOpen] = useState(false);
54
66
  return (
55
67
  <FloatingPanel
56
68
  open={open}
57
69
  onOpenChange={setOpen}
58
70
  compactFace={face}
71
+ {...(width === undefined ? null : { panelWidth: width })}
59
72
  {...(attach ? { anchorMode: "attach" as const, triggerEcho: false } : null)}
60
73
  triggerSizing="content"
61
74
  contentPadding="none"
@@ -207,6 +220,134 @@ describe("FloatingPanel native menu face (FZ-7830)", () => {
207
220
  });
208
221
  });
209
222
 
223
+ function IconMenu() {
224
+ const [open, setOpen] = useState(false);
225
+ return (
226
+ <FloatingPanel
227
+ open={open}
228
+ onOpenChange={setOpen}
229
+ compactFace="menu"
230
+ anchorMode="cover-trigger"
231
+ triggerEcho={false}
232
+ coverAlign="auto"
233
+ triggerSizing="content"
234
+ contentPadding="none"
235
+ trigger={<Text>◐</Text>}
236
+ triggerA11y={{ label: "Appearance" }}
237
+ >
238
+ <Text>System</Text>
239
+ </FloatingPanel>
240
+ );
241
+ }
242
+
243
+ function ButtonMenu() {
244
+ const [open, setOpen] = useState(false);
245
+ return (
246
+ <FloatingPanel
247
+ open={open}
248
+ onOpenChange={setOpen}
249
+ compactFace="menu"
250
+ contentPadding="none"
251
+ trigger={<Button data-testid="pill">Status: All</Button>}
252
+ triggerA11y={{ label: "Filter status" }}
253
+ >
254
+ <Text>Open</Text>
255
+ </FloatingPanel>
256
+ );
257
+ }
258
+
259
+ function FieldMenu() {
260
+ const [open, setOpen] = useState(false);
261
+ return (
262
+ <FloatingPanel
263
+ open={open}
264
+ onOpenChange={setOpen}
265
+ compactFace="menu"
266
+ contentPadding="none"
267
+ trigger={
268
+ <InputParts>
269
+ <InputParts.Box data-testid="field-box">
270
+ <Text>United States</Text>
271
+ </InputParts.Box>
272
+ </InputParts>
273
+ }
274
+ triggerA11y={{ label: "Country" }}
275
+ >
276
+ <Text>Canada</Text>
277
+ </FloatingPanel>
278
+ );
279
+ }
280
+
281
+ const frame = () =>
282
+ document.querySelector<HTMLElement>('[data-testid="floating-panel-menu-surface"]');
283
+ const corners = (el: Element) =>
284
+ Array.from(el.classList)
285
+ .filter((c) => /^_b[tb][lr]r-/.test(c))
286
+ .sort();
287
+ const radiusAtoms = (px: number) =>
288
+ [`_bblr-${px}px`, `_bbrr-${px}px`, `_btlr-${px}px`, `_btrr-${px}px`].sort();
289
+
290
+ describe("LC-112 seamless cover on native (FZ-8438)", () => {
291
+ it("opens an icon trigger's menu ON the button from its top trailing corner (rv_d86cc201997dffd4)", () => {
292
+ trigger = { x: 330, y: 60, width: 44, height: 44 };
293
+ const result = renderWithProviders(<IconMenu />);
294
+ tap(result.getByRole("button", { name: "Appearance" }));
295
+ const panel = menu() as HTMLElement;
296
+ layout(panel, MENU_MIN_WIDTH, 180);
297
+ expect(panel.style.top).toBe(`${trigger.y}px`);
298
+ expect(panel.style.left).toBe(`${trigger.x + trigger.width - MENU_MIN_WIDTH}px`);
299
+ expect(echo()).toBeNull();
300
+ expect(sheet()).toBeNull();
301
+ expect(corners(frame() as HTMLElement)).toEqual(radiusAtoms(22));
302
+ });
303
+
304
+ it("keeps the shadow on an unclipped frame and clips inside it (FZ-8414)", () => {
305
+ const result = renderWithProviders(<IconMenu />);
306
+ tap(result.getByRole("button", { name: "Appearance" }));
307
+ const outer = frame() as HTMLElement;
308
+ expect(outer.className).not.toMatch(/(?:^| )_ox-hidden|_ov-hidden|_o-hidden/);
309
+ expect(getComputedStyle(outer).overflow).not.toBe("hidden");
310
+ const inner = outer.firstElementChild as HTMLElement;
311
+ expect(inner.className).toMatch(/hidden/);
312
+ });
313
+
314
+ it("echoes a field with no border or fill of its own and repeats the field's arc", () => {
315
+ trigger = { x: 20, y: 240, width: 300, height: 44 };
316
+ const result = renderWithProviders(<FieldMenu />);
317
+ const live = result.getByTestId("field-box");
318
+ const atom = corners(live).find((c) => c.startsWith("_btlr-")) as string;
319
+ expect(atom).toBeTruthy();
320
+ const token = atom.match(/^_btlr-t-radius-(.+)$/);
321
+ const drawn = token
322
+ ? Number(getVariableValue((getTokens().radius as Record<string, unknown>)[`$${token[1]}`]))
323
+ : Number(atom.match(/^_btlr-([\d.]+)px$/)?.[1]);
324
+ tap(result.getByRole("button", { name: "Country" }));
325
+ const row = echo() as HTMLElement;
326
+ expect(row).not.toBeNull();
327
+ const box = row.querySelector('[data-testid="field-box"]') as HTMLElement;
328
+ expect(box.className).toMatch(/_bbc-transparent/);
329
+ expect(box.className).toMatch(/_bg-transparent/);
330
+ expect(live.className).not.toMatch(/_bbc-transparent/);
331
+ expect(corners(frame() as HTMLElement)).toEqual(radiusAtoms(Math.min(drawn, 22)));
332
+ });
333
+
334
+ it("echoes a labelled Button trigger with no border or fill of its own", () => {
335
+ trigger = { x: 20, y: 240, width: 140, height: 44 };
336
+ const result = renderWithProviders(<ButtonMenu />);
337
+ const live = result.getByTestId("pill");
338
+ tap(result.getByRole("button", { name: "Filter status" }));
339
+ const row = echo() as HTMLElement;
340
+ expect(row).not.toBeNull();
341
+ // The echo is the trigger's own box at the start edge, not the menu's width.
342
+ expect(row.style.width).toBe("140px");
343
+ expect(row.style.alignSelf).toBe("flex-start");
344
+ const pill = row.querySelector('[data-testid="pill"]') as HTMLElement;
345
+ expect(pill.className).toMatch(/_bbc-transparent|_btc-transparent/);
346
+ expect(pill.className).toMatch(/_bg-transparent/);
347
+ expect(live.className).not.toBe(pill.className);
348
+ });
349
+ });
350
+
210
351
  /** The SHC account menu's shape: an avatar-sized trigger over rows that fill their width. */
211
352
  function AccountMenu() {
212
353
  const [open, setOpen] = useState(false);
@@ -263,3 +404,139 @@ describe("FloatingPanel native menu face width (FZ-8220)", () => {
263
404
  expect(menu()?.style.minWidth).toBe("320px");
264
405
  });
265
406
  });
407
+
408
+ describe("FloatingPanel native menu face panelWidth (LC-112, FZ-8438 r3)", () => {
409
+ const room = 390 - 2 * panelViewportPadding;
410
+ const open = (width?: PanelWidth) => {
411
+ const result = renderWithProviders(<Menu face="menu" width={width} />);
412
+ tap(result.getByRole("button", { name: "Stack actions" }));
413
+ return menu() as HTMLElement;
414
+ };
415
+
416
+ it('"content" is the default: the 250pt pull-down floor, up to the room', () => {
417
+ const panel = open();
418
+ expect(panel.style.minWidth).toBe(`${MENU_MIN_WIDTH}px`);
419
+ expect(panel.style.maxWidth).toBe(`${room}px`);
420
+ expect(panel.style.width).toBe("");
421
+ });
422
+
423
+ it('"trigger" is exactly the trigger\'s width', () => {
424
+ const panel = open("trigger");
425
+ expect([panel.style.minWidth, panel.style.width, panel.style.maxWidth]).toEqual([
426
+ "120px",
427
+ "120px",
428
+ "120px",
429
+ ]);
430
+ });
431
+
432
+ it('"screen" from a trigger in the gutter spans the gutters, start edge on the trigger\'s', () => {
433
+ const panel = open("screen");
434
+ const gutters = 390 - 2 * trigger.x;
435
+ expect(panel.style.width).toBe(`${gutters}px`);
436
+ layout(panel, gutters, 180);
437
+ expect(panel.style.left).toBe(`${trigger.x}px`);
438
+ expect(panel.style.top).toBe(`${trigger.y}px`);
439
+ });
440
+
441
+ it('"screen" from a trigger deep in the row fills the room, top edge on the trigger\'s', () => {
442
+ trigger = { x: 186, y: 240, width: 120, height: 44 };
443
+ const panel = open("screen");
444
+ expect(panel.style.width).toBe(`${room}px`);
445
+ layout(panel, room, 180);
446
+ expect(panel.style.left).toBe(`${panelViewportPadding}px`);
447
+ expect(panel.style.top).toBe(`${trigger.y}px`);
448
+ });
449
+
450
+ it("a number is a fixed width, capped at the room", () => {
451
+ expect(open(300).style.width).toBe("300px");
452
+ });
453
+
454
+ it("a number wider than the room stops at the margins", () => {
455
+ expect(open(900).style.width).toBe(`${room}px`);
456
+ });
457
+ });
458
+
459
+ function EchoMenu({ kind }: { kind: "close" | "menu" }) {
460
+ const [open, setOpen] = useState(false);
461
+ return (
462
+ <FloatingPanel
463
+ open={open}
464
+ onOpenChange={setOpen}
465
+ compactFace="menu"
466
+ anchorMode="cover-trigger"
467
+ triggerEcho={false}
468
+ iconEcho={kind}
469
+ coverAlign="auto"
470
+ triggerSizing="content"
471
+ contentPadding="none"
472
+ trigger={<Button icon={PlusIcon} aria-label="Add node" />}
473
+ triggerA11y={{ label: "Add node" }}
474
+ >
475
+ <Text>Add a node</Text>
476
+ </FloatingPanel>
477
+ );
478
+ }
479
+
480
+ const iconEcho = () =>
481
+ document.querySelector<HTMLElement>('[data-testid="floating-panel-icon-echo"]');
482
+ const iconBand = () =>
483
+ document.querySelector<HTMLElement>('[data-testid="floating-panel-icon-echo-band"]');
484
+ const glyphPath = (node: Element | null) => node?.querySelector("svg path")?.getAttribute("d");
485
+
486
+ describe("LC-112 icon trigger echo on native (FZ-8480, rv_2607879d3bdaf9bf)", () => {
487
+ it("draws the + again as an X in the button's own box, at the menu's top trailing corner", () => {
488
+ trigger = { x: 330, y: 60, width: 44, height: 44 };
489
+ const result = renderWithProviders(<EchoMenu kind="close" />);
490
+ tap(result.getAllByRole("button", { name: "Add node" })[0]!);
491
+ const panel = menu() as HTMLElement;
492
+ layout(panel, MENU_MIN_WIDTH, 180);
493
+ expect(panel.style.top).toBe(`${trigger.y}px`);
494
+ expect(panel.style.left).toBe(`${trigger.x + trigger.width - MENU_MIN_WIDTH}px`);
495
+ const band = iconBand() as HTMLElement;
496
+ expect(band.style.height).toBe("44px");
497
+ const box = band.firstElementChild as HTMLElement;
498
+ expect(box.style.width).toBe("44px");
499
+ expect(box.style.height).toBe("44px");
500
+ // The box sits where the trigger is: its start offset is the trigger's x
501
+ // less the menu's left edge.
502
+ expect(box.style.marginInlineStart || box.style.marginLeft).toBe(
503
+ `${trigger.x - (trigger.x + trigger.width - MENU_MIN_WIDTH)}px`,
504
+ );
505
+ const echoButton = iconEcho() as HTMLElement;
506
+ expect(echoButton.getAttribute("aria-label")).toBe("Close");
507
+ const x = render(<XIcon weight="bold" />);
508
+ expect(glyphPath(echoButton)).toBe(glyphPath(x.container));
509
+ x.unmount();
510
+ expect(echo()).toBeNull();
511
+ });
512
+
513
+ it("closes the menu when the echo is pressed", () => {
514
+ trigger = { x: 330, y: 60, width: 44, height: 44 };
515
+ const result = renderWithProviders(<EchoMenu kind="close" />);
516
+ tap(result.getAllByRole("button", { name: "Add node" })[0]!);
517
+ layout(menu() as HTMLElement, MENU_MIN_WIDTH, 180);
518
+ act(() => {
519
+ vi.advanceTimersByTime(1000);
520
+ });
521
+ tap(iconEcho() as HTMLElement);
522
+ expect(menu()).toBeNull();
523
+ });
524
+
525
+ it("turns a menu trigger into an X too, and opens up from the button's bottom edge near the bottom", () => {
526
+ trigger = { x: 20, y: 760, width: 44, height: 44 };
527
+ const result = renderWithProviders(<EchoMenu kind="menu" />);
528
+ const live = result.getAllByRole("button", { name: "Add node" })[0]!;
529
+ tap(live);
530
+ const panel = menu() as HTMLElement;
531
+ layout(panel, MENU_MIN_WIDTH, 300);
532
+ expect(parseFloat(panel.style.top) + 300).toBe(trigger.y + trigger.height);
533
+ const surface = frame() as HTMLElement;
534
+ const band = iconBand() as HTMLElement;
535
+ const clip = surface.querySelector('[data-testid="floating-panel-menu-clip"]') as HTMLElement;
536
+ expect(clip.lastElementChild).toBe(band);
537
+ const x = render(<XIcon weight="bold" />);
538
+ expect(glyphPath(iconEcho())).toBe(glyphPath(x.container));
539
+ x.unmount();
540
+ expect(glyphPath(iconEcho())).not.toBe(glyphPath(live));
541
+ });
542
+ });
@@ -0,0 +1,237 @@
1
+ /**
2
+ * LC-112 seamless cover (FZ-8438, Clay's rv_a07d4308a6c445b6 and
3
+ * rv_0ea65e640a0ef261 notes; rv_d86cc201997dffd4 for icon triggers). An open
4
+ * panel is ONE surface laid exactly over its trigger: same top edge, left
5
+ * edge and corner arc, the trigger's content echoed in place with no border,
6
+ * fill or divider of its own, drawn above the trigger.
7
+ */
8
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
9
+ import { Preset } from "@multiplatform.one/theme";
10
+ import { fireEvent, waitFor, within } from "@testing-library/react";
11
+ import { useState } from "react";
12
+ import { afterEach, describe, expect, it, vi } from "vitest";
13
+ import { Text } from "tamagui";
14
+ import { Button } from "../Button";
15
+ import { FloatingPanel } from "./index";
16
+
17
+ type Rect = { x: number; y: number; width: number; height: number };
18
+
19
+ function measure(trigger: Rect, panelWidth = 240) {
20
+ const original = HTMLElement.prototype.getBoundingClientRect;
21
+ const rect = {
22
+ ...trigger,
23
+ top: trigger.y,
24
+ left: trigger.x,
25
+ right: trigger.x + trigger.width,
26
+ bottom: trigger.y + trigger.height,
27
+ toJSON: () => ({}),
28
+ };
29
+ vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
30
+ function (this: HTMLElement) {
31
+ if (this.hasAttribute("data-fp-reference") || this.dataset.testid === "field") return rect;
32
+ return original.call(this);
33
+ },
34
+ );
35
+ vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockImplementation(
36
+ function (this: HTMLElement) {
37
+ return this.hasAttribute("data-fp-panel") ? panelWidth : 0;
38
+ },
39
+ );
40
+ vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockImplementation(
41
+ function (this: HTMLElement) {
42
+ return this.hasAttribute("data-fp-panel") ? 160 : 0;
43
+ },
44
+ );
45
+ }
46
+
47
+ /** A field the way a browser paints it: border, fill and a 10px arc. */
48
+ function Field({ radius }: { radius: number }) {
49
+ return (
50
+ <div
51
+ data-testid="field"
52
+ role="combobox"
53
+ aria-expanded={false}
54
+ tabIndex={0}
55
+ style={{
56
+ borderTopLeftRadius: `${radius}px`,
57
+ borderRadius: `${radius}px`,
58
+ border: "1px solid rgb(200, 200, 200)",
59
+ borderBottom: "1px solid rgb(200, 200, 200)",
60
+ backgroundColor: "rgb(250, 250, 250)",
61
+ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.1)",
62
+ }}
63
+ >
64
+ Mar 5, 2026
65
+ </div>
66
+ );
67
+ }
68
+
69
+ function FieldPanel({ radius = 10 }: { radius?: number }) {
70
+ const [open, setOpen] = useState(false);
71
+ return (
72
+ <FloatingPanel
73
+ open={open}
74
+ onOpenChange={setOpen}
75
+ contentPadding="none"
76
+ trigger={<Field radius={radius} />}
77
+ >
78
+ <Text>March 2026</Text>
79
+ </FloatingPanel>
80
+ );
81
+ }
82
+
83
+ function IconPanel({ align = "auto" }: { align?: "start" | "auto" }) {
84
+ const [open, setOpen] = useState(false);
85
+ return (
86
+ <FloatingPanel
87
+ open={open}
88
+ onOpenChange={setOpen}
89
+ anchorMode="cover-trigger"
90
+ triggerEcho={false}
91
+ coverAlign={align}
92
+ widthMode="fit-content"
93
+ triggerSizing="content"
94
+ trigger={<Button aria-label="Appearance">◐</Button>}
95
+ >
96
+ <Text>System</Text>
97
+ </FloatingPanel>
98
+ );
99
+ }
100
+
101
+ const corners = (el: Element) =>
102
+ Array.from(el.classList)
103
+ .filter((c) => /^_b[tb][lr]r-/.test(c))
104
+ .sort();
105
+
106
+ const radiusAtoms = (px: number) =>
107
+ [`_bblr-${px}px`, `_bbrr-${px}px`, `_btlr-${px}px`, `_btrr-${px}px`].sort();
108
+
109
+ async function openField(result: ReturnType<typeof renderWithProviders>) {
110
+ fireEvent.click(result.getByTestId("field"), { detail: 0 });
111
+ const panel = (await result.findByTestId("floating-panel-viewport")) as HTMLElement;
112
+ return panel;
113
+ }
114
+
115
+ afterEach(() => {
116
+ vi.restoreAllMocks();
117
+ });
118
+
119
+ describe("LC-112 seamless cover: one surface over the field (FZ-8438)", () => {
120
+ const field = { x: 16, y: 42, width: 400, height: 40 };
121
+
122
+ it("lays the panel's top and start edges on the field's", async () => {
123
+ measure(field);
124
+ const result = renderWithProviders(<FieldPanel />);
125
+ const panel = await openField(result);
126
+ await waitFor(() => expect(panel.style.top).toBe(`${field.y}px`));
127
+ expect(panel.style.left).toBe(`${field.x}px`);
128
+ const echo = panel.querySelector<HTMLElement>("[data-fp-trigger-row]");
129
+ expect(echo?.style.width).toBe(`${field.width}px`);
130
+ expect(echo?.style.height).toBe(`${field.height}px`);
131
+ });
132
+
133
+ it("draws the field's own arc at all four corners", async () => {
134
+ measure(field);
135
+ const result = renderWithProviders(<FieldPanel radius={10} />);
136
+ const panel = await openField(result);
137
+ await waitFor(() => expect(corners(panel)).toEqual(radiusAtoms(10)));
138
+ });
139
+
140
+ it("keeps the field's arc past the container cap (full preset, 20px on a 40px field)", async () => {
141
+ measure(field);
142
+ const result = renderWithProviders(
143
+ <Preset overrides={{ borderRadius: "full" }}>
144
+ <FieldPanel radius={50} />
145
+ </Preset>,
146
+ );
147
+ const panel = await openField(result);
148
+ await waitFor(() => expect(corners(panel)).toEqual(radiusAtoms(20)));
149
+ });
150
+
151
+ it("echoes the field with no border, fill, shadow or divider of its own", async () => {
152
+ measure(field);
153
+ const result = renderWithProviders(<FieldPanel />);
154
+ const panel = await openField(result);
155
+ const frame = await waitFor(() => {
156
+ const found = panel.querySelector<HTMLElement>("[data-fp-echo-frame]");
157
+ expect(found).not.toBeNull();
158
+ return found as HTMLElement;
159
+ });
160
+ expect(frame.textContent).toBe("Mar 5, 2026");
161
+ expect(frame.style.getPropertyValue("border-color")).toBe("transparent");
162
+ expect(frame.style.getPropertyPriority("border-color")).toBe("important");
163
+ expect(frame.style.getPropertyValue("background-color")).toBe("transparent");
164
+ expect(frame.style.getPropertyValue("box-shadow")).toBe("none");
165
+ const bottom = getComputedStyle(frame).borderBottomColor;
166
+ expect(["transparent", "rgba(0, 0, 0, 0)"]).toContain(bottom);
167
+ // Layout keeps the field's widths, so the echoed text sits where it did.
168
+ expect(getComputedStyle(frame).borderBottomWidth).toBe("1px");
169
+ const row = panel.querySelector<HTMLElement>("[data-fp-trigger-row]") as HTMLElement;
170
+ expect(row.style.backgroundColor).toBe(getComputedStyle(panel).backgroundColor);
171
+ // The live field keeps its chrome; only the echo drops it.
172
+ expect(result.getByTestId("field").style.backgroundColor).toBe("rgb(250, 250, 250)");
173
+ });
174
+
175
+ it("draws above the field: a fixed, stacked layer later in the document", async () => {
176
+ measure(field);
177
+ const result = renderWithProviders(<FieldPanel />);
178
+ const panel = await openField(result);
179
+ await waitFor(() => expect(panel.style.top).toBe(`${field.y}px`));
180
+ expect(panel.style.position).toBe("fixed");
181
+ expect(Number(panel.style.zIndex)).toBeGreaterThan(0);
182
+ const live = result.getByTestId("field");
183
+ expect(live.compareDocumentPosition(panel) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
184
+ expect(live.contains(panel)).toBe(false);
185
+ });
186
+ });
187
+
188
+ describe("LC-112 icon trigger: the menu opens on its button (rv_d86cc201997dffd4)", () => {
189
+ it("opens from the button's top trailing corner at the end edge, with no echo row", async () => {
190
+ const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
191
+ const button = { x: viewportWidth - 16 - 44, y: 12, width: 44, height: 44 };
192
+ measure(button, 200);
193
+ const result = renderWithProviders(<IconPanel />);
194
+ fireEvent.click(result.getByRole("button", { name: "Appearance" }), { detail: 0 });
195
+ const panel = (await result.findByTestId("floating-panel-viewport")) as HTMLElement;
196
+ await waitFor(() => expect(panel.style.top).toBe(`${button.y}px`));
197
+ expect(panel.style.left).toBe(`${button.x + button.width - 200}px`);
198
+ expect(panel.style.transformOrigin).toBe("top right");
199
+ expect(panel.querySelector("[data-fp-trigger-row]")).toBeNull();
200
+ expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
201
+ });
202
+
203
+ it("opens from the button's top leading corner where that fits", async () => {
204
+ const button = { x: 16, y: 12, width: 44, height: 44 };
205
+ measure(button, 200);
206
+ const result = renderWithProviders(<IconPanel />);
207
+ fireEvent.click(result.getByRole("button", { name: "Appearance" }), { detail: 0 });
208
+ const panel = (await result.findByTestId("floating-panel-viewport")) as HTMLElement;
209
+ await waitFor(() => expect(panel.style.top).toBe(`${button.y}px`));
210
+ expect(panel.style.left).toBe(`${button.x}px`);
211
+ expect(panel.style.transformOrigin).toBe("top left");
212
+ expect(within(panel).queryByRole("button", { name: "Appearance" })).toBeNull();
213
+ });
214
+
215
+ it("slides up over its button near the bottom edge instead of opening a sheet", async () => {
216
+ const button = { x: 16, y: window.innerHeight - 60, width: 44, height: 44 };
217
+ measure(button, 200);
218
+ const result = renderWithProviders(<IconPanel />);
219
+ fireEvent.click(result.getByRole("button", { name: "Appearance" }), { detail: 0 });
220
+ const panel = (await result.findByTestId("floating-panel-viewport")) as HTMLElement;
221
+ await waitFor(() => expect(panel.style.visibility).toBe("visible"));
222
+ const top = parseFloat(panel.style.top);
223
+ expect(top).toBeLessThan(button.y);
224
+ expect(top + 160).toBeGreaterThanOrEqual(button.y + button.height);
225
+ expect(panel.style.left).toBe(`${button.x}px`);
226
+ expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
227
+ });
228
+
229
+ it("never end-aligns a custom first row (triggerEcho off, coverAlign start): it collides to the sheet", async () => {
230
+ const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
231
+ const button = { x: viewportWidth - 16 - 44, y: 12, width: 44, height: 44 };
232
+ measure(button, 200);
233
+ const result = renderWithProviders(<IconPanel align="start" />);
234
+ fireEvent.click(result.getByRole("button", { name: "Appearance" }), { detail: 0 });
235
+ expect(await result.findByTestId("floating-panel-sheet")).toBeTruthy();
236
+ });
237
+ });
@@ -0,0 +1,26 @@
1
+ import { XStack, type XStackProps } from "tamagui";
2
+ import { usePanelIconEchoBox } from "./echoContext";
3
+
4
+ /**
5
+ * A floating panel's header row (LC-112, FZ-8480 Round 37). Over an
6
+ * icon-trigger panel whose X shares the header's row, it takes the X's
7
+ * height and centres its content on the X's line, keeping clear of the X;
8
+ * anywhere else it is the panel's padded top row.
9
+ */
10
+ export function PanelHeader({ children, ...rest }: XStackProps) {
11
+ const echo = usePanelIconEchoBox();
12
+ return (
13
+ <XStack
14
+ alignItems="center"
15
+ gap="$2"
16
+ paddingLeft={echo?.left || "$4"}
17
+ paddingRight={echo?.right || "$4"}
18
+ paddingTop={echo ? 0 : "$4"}
19
+ {...(echo ? { minHeight: echo.height } : undefined)}
20
+ data-fp-panel-header=""
21
+ {...rest}
22
+ >
23
+ {children}
24
+ </XStack>
25
+ );
26
+ }
@@ -0,0 +1,67 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ /**
4
+ * LC-112 seamless cover (FZ-8438): true while a field is drawn as the echo row
5
+ * of the panel covering it. The field keeps its geometry and drops its own
6
+ * border, fill and shadow, so the panel's edge and surface are the only ones.
7
+ */
8
+ export const PanelEchoContext = createContext(false);
9
+
10
+ export function useInPanelEcho(): boolean {
11
+ return useContext(PanelEchoContext);
12
+ }
13
+
14
+ /**
15
+ * Native has no computed style to read, so a trigger's frame reports the
16
+ * radius it draws and a covering panel repeats it at all four corners.
17
+ */
18
+ export const PanelTriggerRadiusContext = createContext<((radius: number) => void) | null>(null);
19
+
20
+ export function usePanelTriggerRadiusReporter(): ((radius: number) => void) | null {
21
+ return useContext(PanelTriggerRadiusContext);
22
+ }
23
+
24
+ /**
25
+ * The X a panel lays over its content's header row (LC-112, FZ-8480 Round
26
+ * 37): its height, and how far the header keeps clear of it on each side.
27
+ * A panel header sits on the X's line and never runs under it.
28
+ */
29
+ export interface PanelIconEchoBox {
30
+ height: number;
31
+ left: number;
32
+ right: number;
33
+ }
34
+
35
+ export const PanelIconEchoBoxContext = createContext<PanelIconEchoBox | null>(null);
36
+
37
+ export function usePanelIconEchoBox(): PanelIconEchoBox | null {
38
+ return useContext(PanelIconEchoBoxContext);
39
+ }
40
+
41
+ /**
42
+ * The header's clearance around an X `offset` in from the panel's start edge:
43
+ * the X's side keeps clear of it, the other side keeps none of its own.
44
+ */
45
+ export function echoHeaderBox({
46
+ offset,
47
+ width,
48
+ height,
49
+ panelWidth,
50
+ rtl = false,
51
+ }: {
52
+ offset: number;
53
+ width: number;
54
+ height: number;
55
+ panelWidth: number;
56
+ rtl?: boolean;
57
+ }): PanelIconEchoBox {
58
+ const atStart = offset + width / 2 < panelWidth / 2;
59
+ const start = atStart ? Math.max(0, offset + width) : 0;
60
+ const end = atStart ? 0 : Math.max(0, panelWidth - offset);
61
+ return { height, left: rtl ? end : start, right: rtl ? start : end };
62
+ }
63
+
64
+ export function sameEchoBox(a: PanelIconEchoBox | null, b: PanelIconEchoBox | null): boolean {
65
+ if (a == null || b == null) return a === b;
66
+ return a.height === b.height && a.left === b.left && a.right === b.right;
67
+ }