@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
@@ -15,24 +15,40 @@ import {
15
15
  OVERLAY_ATTACH_GAP,
16
16
  OVERLAY_BREAKPOINT,
17
17
  Surface,
18
+ SurfaceContext,
18
19
  useResolvedKnobs,
19
20
  wasKeyboardFocus,
20
21
  ensureKeyboardModalityTracking,
21
22
  motionDurationMs,
22
23
  } from "@multiplatform.one/theme";
23
- import { isWeb, Sheet, View, YStack } from "tamagui";
24
+ import { isWeb, Sheet, Theme, View, YStack } from "tamagui";
24
25
  import { useClick, useDismiss, useInteractions } from "@floating-ui/react";
25
26
  import { ScrollArrow } from "../shared/floatingList";
26
27
  import { TrailingActionOverlay, TriggerWithTrailingAction } from "./trailingAction";
27
28
  import { useReturnFocusOnClose, useSheetModalFocus } from "../shared/focusManagement";
28
29
  import { useSheetEscapeDismiss } from "../shared/useSheetEscapeDismiss";
29
- import { useFloatingPanel, panelViewportPadding } from "./useFloatingPanel";
30
+ import { useFloatingPanel, panelViewportPadding, paintedCornerBox } from "./useFloatingPanel";
31
+ import { fixedContainingBlockOrigin } from "./containingBlock";
30
32
  import type { PanelWidthMode } from "./useFloatingPanel";
33
+ import {
34
+ echoOffset,
35
+ type PanelWidth,
36
+ panelLeftForWidth,
37
+ screenBounds,
38
+ slidesAlongScreen,
39
+ } from "./panelWidth";
31
40
  import { OverlayStackProvider, useInsideOverlay, useOverlayZIndex } from "../shared/overlayStack";
32
41
  import { resolveCompactFace, type CompactFace } from "./compactFace";
33
42
  import { isNestedPanelAction, type PanelActionEvent } from "./nestedAction";
34
43
  import { PanelPortal } from "./PanelPortal";
35
- import { fixedContainingBlockOrigin } from "./containingBlock";
44
+ import {
45
+ echoHeaderBox,
46
+ PanelIconEchoBoxContext,
47
+ sameEchoBox,
48
+ type PanelIconEchoBox,
49
+ } from "./echoContext";
50
+ import { iconEchoElement, iconEchoOverlays, type IconEcho } from "./iconEcho";
51
+ import { groupSurfaceOf, paintedTriggerBox } from "./paintedBox";
36
52
  import {
37
53
  contextMenuPoint,
38
54
  placeOverTrigger,
@@ -77,6 +93,23 @@ export interface FloatingPanelProps {
77
93
  triggerRow?: ReactNode;
78
94
  anchorRect?: { x: number; y: number; width: number; height: number };
79
95
  triggerEcho?: boolean;
96
+ /**
97
+ * Which top corner of the trigger a covering panel opens from. `"start"`
98
+ * (default) keeps the echo row in place. `"auto"` is for an icon trigger
99
+ * (LC-112, FZ-8438): the trailing corner when the leading one would run off
100
+ * the end edge, and a vertical collision slides the menu up over its button
101
+ * instead of handing it to a sheet, the way an iOS menu grows out of it.
102
+ */
103
+ coverAlign?: "start" | "auto";
104
+ /**
105
+ * An icon-only trigger's covering panel draws the pressed button again in
106
+ * its own spot as an X, same size and plate, the way iOS morphs a + into an
107
+ * X (LC-112, FZ-8480 Round 37). `"close"` lays the X over the content's
108
+ * header row; `"menu"` gives it its own band above the rows. It closes the
109
+ * panel and reads "Close". The panel never slides off the button: it opens
110
+ * down from the button's top edge, or up from its bottom edge.
111
+ */
112
+ iconEcho?: IconEcho;
80
113
  /**
81
114
  * Optional control rendered beside the trigger but OUTSIDE the native press
82
115
  * capture (e.g. DatePicker/ColorPicker clear). On native the trigger subtree
@@ -127,6 +160,14 @@ export interface FloatingPanelProps {
127
160
  * anything opened from a field-shaped trigger takes the floor.
128
161
  */
129
162
  widthMode?: PanelWidthMode;
163
+ /**
164
+ * How wide the panel opens over its trigger (LC-112, FZ-8438 r3):
165
+ * `"content"` hugs the content (never narrower than an echoed trigger),
166
+ * `"trigger"` is the trigger's width, `"screen"` fills the room between the
167
+ * screen's safe margins with its top edge still on the trigger's, and a
168
+ * number is a fixed width. Unset keeps each component's own default.
169
+ */
170
+ panelWidth?: PanelWidth;
130
171
  /**
131
172
  * @deprecated Pass `widthMode` instead — the boolean cannot express
132
173
  * `"match-trigger"`. `true` → `"fit-content"`, `false`/undefined →
@@ -277,6 +318,41 @@ const ECHO_STRIPPED_ATTRIBUTES = [
277
318
  "aria-activedescendant",
278
319
  ];
279
320
 
321
+ function childPath(root: Element, node: Element): number[] | null {
322
+ const path: number[] = [];
323
+ for (let at: Element | null = node; at && at !== root; at = at.parentElement) {
324
+ const parent: Element | null = at.parentElement;
325
+ if (!parent) return null;
326
+ path.unshift(Array.prototype.indexOf.call(parent.children, at));
327
+ }
328
+ return path;
329
+ }
330
+
331
+ function followPath(root: Element, path: number[]): HTMLElement | null {
332
+ let at: Element | undefined = root;
333
+ for (const index of path) at = at?.children[index];
334
+ return (at as HTMLElement | undefined) ?? null;
335
+ }
336
+
337
+ /**
338
+ * LC-112 seamless cover (FZ-8438): the echo keeps the trigger's geometry but
339
+ * not its chrome. Its frame's border, fill, shadow and outline go, so the
340
+ * panel's own edge and surface are the only ones drawn and nothing separates
341
+ * the echo from the body. Widths stay, so the content sits where it did.
342
+ */
343
+ function dropEchoChrome(source: HTMLElement, snapshot: HTMLElement) {
344
+ const painted = paintedCornerBox(source);
345
+ const path = painted ? childPath(source, painted) : null;
346
+ const frame = path ? followPath(snapshot, path) : null;
347
+ if (!frame) return;
348
+ frame.style.setProperty("border-color", "transparent", "important");
349
+ frame.style.setProperty("background-color", "transparent", "important");
350
+ frame.style.setProperty("background-image", "none", "important");
351
+ frame.style.setProperty("box-shadow", "none", "important");
352
+ frame.style.setProperty("outline", "none", "important");
353
+ frame.setAttribute("data-fp-echo-frame", "");
354
+ }
355
+
280
356
  /**
281
357
  * The panel's first row when it opens over its trigger: the trigger's own
282
358
  * label/value, drawn where the trigger sits, so the panel reads as the
@@ -287,18 +363,30 @@ const ECHO_STRIPPED_ATTRIBUTES = [
287
363
  * stops stripped so labels, forms and queries still resolve to the one real
288
364
  * trigger. Pressing it closes the panel, like pressing the trigger again.
289
365
  */
290
- function TriggerEchoRow({
366
+ export function TriggerEchoRow({
291
367
  sourceRef,
292
368
  onPress,
293
369
  children,
294
370
  anchorRect,
295
371
  source,
372
+ presentational = false,
373
+ edge = "top",
296
374
  }: {
297
375
  sourceRef: RefObject<HTMLElement | null>;
298
376
  onPress: () => void;
299
377
  children?: ReactNode;
300
378
  anchorRect?: FloatingPanelProps["anchorRect"];
301
379
  source: ReactNode;
380
+ /**
381
+ * Inside a listbox (Select) the echo is pointer-only and hidden from
382
+ * assistive tech: a button child would break the listbox's role contract.
383
+ */
384
+ presentational?: boolean;
385
+ /**
386
+ * `"bottom"` for a drop-up (FZ-8438 r3): the panel's bottom edge is the
387
+ * trigger's, so the echo is its last row, sticky at the bottom.
388
+ */
389
+ edge?: "top" | "bottom";
302
390
  }) {
303
391
  const rowRef = useRef<HTMLElement | null>(null);
304
392
  const visualRef = useRef<HTMLElement | null>(null);
@@ -320,17 +408,33 @@ function TriggerEchoRow({
320
408
  control?.textContent ||
321
409
  source.textContent ||
322
410
  "";
323
- row.setAttribute("aria-label", label);
411
+ if (!presentational) row.setAttribute("aria-label", label);
324
412
  const rect = anchorRect ?? source.getBoundingClientRect();
325
- row.style.width = `${rect.width}px`;
413
+ // A stretched echo (placeEchoOverTrigger) keeps its grown width.
414
+ const grown = Number(row.getAttribute("data-fp-echo-stretched")) || 0;
415
+ const stretched = grown > rect.width;
416
+ row.style.width = `${stretched ? grown : rect.width}px`;
326
417
  row.style.height = `${rect.height}px`;
327
418
  const rtl = isRtl(source);
328
419
  row.style.alignSelf = "flex-start";
329
420
  const frameStyle = row.parentElement ? getComputedStyle(row.parentElement) : null;
330
- row.style.marginTop = `${-(parseFloat(frameStyle?.borderTopWidth || "0") || 0)}px`;
331
421
  row.style.position = "sticky";
332
- row.style.top = row.style.marginTop;
422
+ if (edge === "bottom") {
423
+ row.style.order = "1";
424
+ row.style.marginTop = "";
425
+ row.style.top = "";
426
+ row.style.marginBottom = `${-(parseFloat(frameStyle?.borderBottomWidth || "0") || 0)}px`;
427
+ row.style.bottom = row.style.marginBottom;
428
+ } else {
429
+ row.style.order = "";
430
+ row.style.marginBottom = "";
431
+ row.style.bottom = "";
432
+ row.style.marginTop = `${-(parseFloat(frameStyle?.borderTopWidth || "0") || 0)}px`;
433
+ row.style.top = row.style.marginTop;
434
+ }
333
435
  row.style.zIndex = "1";
436
+ // Sticky over a scrolled body, so it carries the panel's own fill.
437
+ row.style.backgroundColor = frameStyle?.backgroundColor ?? "";
334
438
  row.style.marginLeft = rtl
335
439
  ? ""
336
440
  : `${-(parseFloat(frameStyle?.borderLeftWidth || "0") || 0)}px`;
@@ -352,8 +456,11 @@ function TriggerEchoRow({
352
456
  node.setAttribute("tabindex", "-1");
353
457
  }
354
458
  }
459
+ if (children == null) dropEchoChrome(source, snapshot);
355
460
  snapshot.setAttribute("inert", "");
356
461
  snapshot.setAttribute("aria-hidden", "true");
462
+ snapshot.style.width = stretched ? "100%" : "";
463
+ snapshot.style.maxWidth = stretched ? "none" : "";
357
464
  if (children == null) visualRef.current?.replaceChildren(snapshot);
358
465
  };
359
466
  sync();
@@ -361,13 +468,14 @@ function TriggerEchoRow({
361
468
  const observer = new ResizeObserver(sync);
362
469
  observer.observe(source);
363
470
  return () => observer.disconnect();
364
- }, [sourceRef, source, children, anchorRect]);
471
+ }, [sourceRef, source, children, anchorRect, presentational, edge]);
365
472
  return (
366
473
  <View
367
474
  ref={rowRef as any}
368
475
  data-fp-trigger-row=""
369
- role="button"
370
- aria-expanded={true}
476
+ {...(presentational
477
+ ? { "aria-hidden": true }
478
+ : { role: "button" as const, "aria-expanded": true })}
371
479
  tabIndex={-1}
372
480
  cursor="pointer"
373
481
  onPress={onPress}
@@ -381,6 +489,151 @@ function TriggerEchoRow({
381
489
  );
382
490
  }
383
491
 
492
+ const ECHO_FIELD = 'input, textarea, [role="combobox"], [aria-haspopup="listbox"]';
493
+
494
+ /**
495
+ * Keep the echo row on its trigger when the panel's start edge is not the
496
+ * trigger's (a `"screen"` or fixed `panelWidth`, or a panel slid back on
497
+ * screen). `translate` leaves the row's own margins and the panel's scale
498
+ * transform alone.
499
+ *
500
+ * With `stretch` (a consumer set `panelWidth`), a field's echo grows from the
501
+ * field's start edge to the panel's end edge: the value stays where it was,
502
+ * the chevron rides the panel's end, and the value is no longer cut short
503
+ * above rows that show it whole (LC-112 r3). A button echo keeps its box, so
504
+ * a centred label never moves.
505
+ */
506
+ export function placeEchoOverTrigger(
507
+ panelElement: HTMLElement,
508
+ geometry: Parameters<typeof echoOffset>[0],
509
+ { stretch = false }: { stretch?: boolean } = {},
510
+ ) {
511
+ const row = panelElement.querySelector<HTMLElement>("[data-fp-trigger-row]");
512
+ if (!row) return;
513
+ const offset = echoOffset(geometry);
514
+ const x = geometry.rtl ? -offset : offset;
515
+ row.style.translate = Math.abs(x) < 0.5 ? "" : `${x}px 0`;
516
+ const triggerWidth = geometry.triggerRight - geometry.triggerLeft;
517
+ const grown = geometry.panelWidth - offset;
518
+ const field = stretch && row.querySelector(ECHO_FIELD) != null;
519
+ const visual = row.firstElementChild as HTMLElement | null;
520
+ const snapshot = visual?.firstElementChild as HTMLElement | null;
521
+ if (field && grown > triggerWidth + 0.5) {
522
+ row.style.width = `${grown}px`;
523
+ row.setAttribute("data-fp-echo-stretched", String(grown));
524
+ for (const node of [visual, snapshot]) {
525
+ if (!node) continue;
526
+ node.style.width = "100%";
527
+ node.style.maxWidth = "none";
528
+ }
529
+ } else if (row.hasAttribute("data-fp-echo-stretched")) {
530
+ row.removeAttribute("data-fp-echo-stretched");
531
+ row.style.width = `${triggerWidth}px`;
532
+ for (const node of [visual, snapshot]) {
533
+ if (!node) continue;
534
+ node.style.width = "";
535
+ node.style.maxWidth = "";
536
+ }
537
+ }
538
+ }
539
+
540
+ /**
541
+ * The band an icon trigger's echo rides (LC-112, FZ-8480): as tall as the
542
+ * trigger, sticky on the panel's top edge (or its bottom edge when the panel
543
+ * opened upward), filled with the panel's surface so scrolled rows pass under
544
+ * it. It comes last in the document, so a keyboard open lands on the panel's
545
+ * content and the echo is the last tab stop; CSS order draws it first.
546
+ */
547
+ function IconEchoRow({ children }: { children: ReactNode }) {
548
+ return (
549
+ <View
550
+ data-fp-icon-echo=""
551
+ flexShrink={0}
552
+ alignSelf="stretch"
553
+ pointerEvents="box-none"
554
+ style={{ order: -1, position: "sticky", zIndex: 1 }}
555
+ >
556
+ <View data-fp-icon-echo-box="" alignItems="flex-start" justifyContent="flex-start">
557
+ {children}
558
+ </View>
559
+ </View>
560
+ );
561
+ }
562
+
563
+ /**
564
+ * Lay the icon echo on the trigger's measured box: the band is the trigger's
565
+ * height on the edge the panel shares with it, and the box sits `offset` in
566
+ * from the panel's start edge, pulled back over the frame's border so the
567
+ * button lands exactly where the trigger is. An overlaid echo (an X) takes no
568
+ * height of its own and lets the content show beside it.
569
+ */
570
+ function placeIconEcho(
571
+ panelElement: HTMLElement,
572
+ {
573
+ offset,
574
+ width,
575
+ height,
576
+ up,
577
+ overlay,
578
+ group = null,
579
+ }: {
580
+ offset: number;
581
+ width: number;
582
+ height: number;
583
+ up: boolean;
584
+ overlay: boolean;
585
+ /**
586
+ * The shared plate a grouped trigger sits on: the band covers all of it,
587
+ * and only the pressed trigger shows, at its own spot in the plate.
588
+ */
589
+ group?: { top: number; height: number } | null;
590
+ },
591
+ ) {
592
+ const row = panelElement.querySelector<HTMLElement>("[data-fp-icon-echo]");
593
+ const box = row?.querySelector<HTMLElement>("[data-fp-icon-echo-box]");
594
+ if (!row || !box) return;
595
+ const frame = getComputedStyle(panelElement);
596
+ const border = (side: "Top" | "Bottom" | "Left" | "Right") =>
597
+ parseFloat(frame[`border${side}Width`] || "0") || 0;
598
+ const rtl = panelElement.dir === "rtl";
599
+ row.style.order = up ? "1" : "-1";
600
+ row.style.height = `${height}px`;
601
+ row.style.backgroundColor = overlay ? "transparent" : frame.backgroundColor;
602
+ if (up) {
603
+ row.style.top = "";
604
+ row.style.marginTop = "";
605
+ row.style.marginBottom = `${-border("Bottom")}px`;
606
+ row.style.bottom = row.style.marginBottom;
607
+ } else {
608
+ row.style.bottom = "";
609
+ row.style.marginTop = `${-border("Top")}px`;
610
+ row.style.marginBottom = overlay ? `${-(height - border("Top"))}px` : "";
611
+ row.style.top = row.style.marginTop;
612
+ }
613
+ row.setAttribute("data-fp-icon-echo", overlay ? "overlay" : "band");
614
+ if (group) {
615
+ row.style.height = `${group.height}px`;
616
+ if (!up && overlay) row.style.marginBottom = `${-(group.height - border("Top"))}px`;
617
+ }
618
+ row.style.marginLeft = `${-border("Left")}px`;
619
+ row.style.marginRight = `${-border("Right")}px`;
620
+ box.style.width = `${width}px`;
621
+ box.style.height = `${height}px`;
622
+ box.style.marginLeft = rtl ? "" : `${offset}px`;
623
+ box.style.marginRight = rtl ? `${offset}px` : "";
624
+ box.style.alignSelf = "flex-start";
625
+ box.style.marginTop = group ? `${group.top}px` : "";
626
+ box.style.position = "relative";
627
+ box.style.zIndex = "1";
628
+ }
629
+
630
+ /** The box that lays an element out, past any `display: contents` wrappers. */
631
+ function layoutParentOf(element: HTMLElement | null): HTMLElement | null {
632
+ let parent = element?.parentElement ?? null;
633
+ while (parent && getComputedStyle(parent).display === "contents") parent = parent.parentElement;
634
+ return parent;
635
+ }
636
+
384
637
  // ── FloatingPanel ─────────────────────────────────────────────
385
638
 
386
639
  // Canonical float↔sheet pivot (theme `layoutBreakpoints.medium` = Tamagui
@@ -623,10 +876,13 @@ function FloatingPanelWeb({
623
876
  sizing = "grow",
624
877
  widthMode,
625
878
  fitContent,
879
+ panelWidth,
626
880
  contentPadding = "default",
627
881
  openOn = "press",
628
882
  cover = true,
629
883
  triggerEcho = true,
884
+ coverAlign = "start",
885
+ iconEcho,
630
886
  triggerRow,
631
887
  anchorRect,
632
888
  clampCollisions = false,
@@ -636,6 +892,13 @@ function FloatingPanelWeb({
636
892
  const [collisionSheet, setCollisionSheet] = useState(false);
637
893
  const isFill = sizing === "fill";
638
894
  const isContextMenu = openOn === "contextmenu";
895
+ // A context menu anchored on its trigger (a bar button's long-press menu)
896
+ // covers the trigger like any other menu; only a free one opens at the point.
897
+ const pointMenu = isContextMenu && sheetProps.anchorMode !== "cover-trigger";
898
+ const showIconEcho = cover && !pointMenu && iconEcho != null;
899
+ const triggerSurface = SurfaceContext.useStyledContext();
900
+ const iconEchoUpRef = useRef(false);
901
+ const [echoBox, setEchoBox] = useState<PanelIconEchoBox | null>(null);
639
902
  const overlayStyleRef = useRef<{ top: number; left?: number; maxHeight: number } | null>(null);
640
903
  const pinnedTopRef = useRef(0);
641
904
  const pinnedHeightRef = useRef(0);
@@ -647,6 +910,7 @@ function FloatingPanelWeb({
647
910
  const contentRef = useRef<HTMLElement | null>(null);
648
911
  const anchorPointRef = useRef<PanelPoint | null>(null);
649
912
  const pointPlacementRef = useRef<{ dropup: boolean; alignEnd: boolean } | null>(null);
913
+ const coverAlignEndRef = useRef(false);
650
914
 
651
915
  const repositionFnRef = useRef<(() => void) | null>(null);
652
916
 
@@ -669,8 +933,10 @@ function FloatingPanelWeb({
669
933
  wheelGrowRef: isFill ? undefined : grownRef,
670
934
  widthMode: widthMode ?? (isContextMenu && fitContent === undefined ? "fit-content" : undefined),
671
935
  fitContent,
672
- freeOverlay: isContextMenu,
936
+ freeOverlay: pointMenu,
673
937
  cover,
938
+ panelWidth,
939
+ echo: cover && triggerEcho && !pointMenu,
674
940
  onBeforeOpen: resetPosition,
675
941
  });
676
942
 
@@ -711,7 +977,7 @@ function FloatingPanelWeb({
711
977
  dropdownElement.scrollTop = 0;
712
978
  }
713
979
 
714
- const point = isContextMenu
980
+ const point = pointMenu
715
981
  ? (anchorPointRef.current ?? referenceCornerPoint(trig, isRtl()))
716
982
  : null;
717
983
  const measured = anchorRect
@@ -744,6 +1010,12 @@ function FloatingPanelWeb({
744
1010
  (viewport?.width ?? (document.documentElement.clientWidth || window.innerWidth));
745
1011
  const viewportBottom = viewportTop + (viewport?.height ?? window.innerHeight);
746
1012
  dropdownElement.style.left = `${viewportLeft - containingBlock.left}px`;
1013
+ // LC-112 (FZ-8480 Round 37): a grouped icon trigger's surface is the
1014
+ // group's plate, so the panel covers the whole plate; only the pressed
1015
+ // trigger shows, as its X.
1016
+ const groupSurface = showIconEcho ? groupSurfaceOf(trig) : null;
1017
+ const groupBox = groupSurface?.getBoundingClientRect() ?? null;
1018
+ if (showIconEcho) dropdownElement.style.minWidth = groupBox ? `${groupBox.width}px` : "";
747
1019
  const layoutStyle = getComputedStyle(dropdownElement);
748
1020
  const scale =
749
1021
  layoutStyle.transform &&
@@ -753,7 +1025,48 @@ function FloatingPanelWeb({
753
1025
  : 1;
754
1026
  const layoutWidth = dropdownElement.getBoundingClientRect().width / (scale || 1);
755
1027
  const width = Math.max(layoutWidth || dropdownElement.offsetWidth, measured.width);
756
- const left = rtl ? measured.right - width : measured.left;
1028
+ const startLeft = rtl ? measured.right - width : measured.left;
1029
+ const endLeft = rtl ? measured.left : measured.right - width;
1030
+ // An icon trigger has no echo to keep in place, so its menu opens from
1031
+ // whichever top corner keeps it on screen: the trailing one for a bar
1032
+ // button at the end edge, the way an iOS menu grows out of its button.
1033
+ // Only the free edge needs the padding; the aligned edge is the trigger's.
1034
+ const iconCover = coverAlign === "auto";
1035
+ const startFree = rtl
1036
+ ? startLeft >= viewportLeft + panelViewportPadding
1037
+ : startLeft + width <= viewportRight - panelViewportPadding;
1038
+ const endFree = rtl
1039
+ ? endLeft + width <= viewportRight - panelViewportPadding
1040
+ : endLeft >= viewportLeft + panelViewportPadding;
1041
+ let alignEnd = iconCover && !startFree && endFree;
1042
+ // A screen-wide or fixed-width panel is not tied to the trigger's start
1043
+ // edge: it slides inside the margins, and only its echo stays put.
1044
+ const slides = slidesAlongScreen(panelWidth);
1045
+ const margins = {
1046
+ boundsLeft: viewportLeft + panelViewportPadding,
1047
+ boundsRight: viewportRight - panelViewportPadding,
1048
+ };
1049
+ const room =
1050
+ panelWidth === "screen"
1051
+ ? screenBounds({
1052
+ triggerLeft: measured.left,
1053
+ triggerRight: measured.right,
1054
+ ...margins,
1055
+ rtl,
1056
+ })
1057
+ : { left: margins.boundsLeft, right: margins.boundsRight };
1058
+ const left = slides
1059
+ ? panelLeftForWidth({
1060
+ triggerLeft: measured.left,
1061
+ triggerRight: measured.right,
1062
+ width,
1063
+ boundsLeft: room.left,
1064
+ boundsRight: room.right,
1065
+ rtl,
1066
+ })
1067
+ : alignEnd
1068
+ ? endLeft
1069
+ : startLeft;
757
1070
  const firstActionHeight =
758
1071
  contentRef.current?.querySelector<HTMLElement>(PANEL_FOCUSABLE)?.getBoundingClientRect()
759
1072
  .height ||
@@ -771,17 +1084,49 @@ function FloatingPanelWeb({
771
1084
  : totalContentHeight;
772
1085
  let placedTop = measured.top;
773
1086
  let placedLeft = left;
774
- if (
1087
+ let placedMaxHeight: number | null = null;
1088
+ iconEchoUpRef.current = false;
1089
+ if (showIconEcho && measured.width > 0 && measured.height > 0) {
1090
+ // LC-112 (FZ-8480): the button is drawn again in its own spot, so the
1091
+ // panel stays on it: down from its top edge, or up from its bottom.
1092
+ // It covers the button's plate and press box where they reach past it.
1093
+ const painted = groupBox ?? paintedTriggerBox(trig, measured);
1094
+ const placed = placeOverTrigger({
1095
+ trigger: {
1096
+ x: painted.left,
1097
+ y: groupBox ? groupBox.top : measured.top,
1098
+ width: painted.right - painted.left,
1099
+ height: groupBox ? groupBox.height : measured.height,
1100
+ },
1101
+ width,
1102
+ height: totalContentHeight,
1103
+ bounds: {
1104
+ top: viewportTop + panelViewportPadding,
1105
+ left: viewportLeft + panelViewportPadding,
1106
+ right: viewportRight - panelViewportPadding,
1107
+ bottom: viewportBottom - panelViewportPadding,
1108
+ },
1109
+ rtl,
1110
+ align: coverAlign,
1111
+ pinned: true,
1112
+ });
1113
+ placedTop = placed.top;
1114
+ if (!slides) placedLeft = placed.left;
1115
+ alignEnd = placed.end;
1116
+ placedMaxHeight = placed.maxHeight;
1117
+ iconEchoUpRef.current = placed.up;
1118
+ } else if (
775
1119
  measured.width > 0 &&
776
1120
  measured.height > 0 &&
777
- (left < viewportLeft ||
778
- left + width > Math.max(measured.right, viewportRight - panelViewportPadding) ||
1121
+ ((!slides &&
1122
+ (left < viewportLeft ||
1123
+ left + width > Math.max(measured.right, viewportRight - panelViewportPadding))) ||
779
1124
  measured.right > viewportRight ||
780
1125
  measured.top < viewportTop ||
781
1126
  measured.top + Math.max(neededHeight, measured.height) >
782
1127
  viewportBottom - panelViewportPadding)
783
1128
  ) {
784
- if (!clampCollisions) {
1129
+ if (!clampCollisions && !iconCover) {
785
1130
  dropdownElement.style.visibility = "hidden";
786
1131
  setCollisionSheet(true);
787
1132
  return;
@@ -804,18 +1149,63 @@ function FloatingPanelWeb({
804
1149
  bottom: viewportBottom - panelViewportPadding,
805
1150
  },
806
1151
  rtl,
1152
+ align: coverAlign,
807
1153
  });
808
1154
  placedTop = placed.top;
809
- placedLeft = placed.left;
1155
+ if (!slides) placedLeft = placed.left;
1156
+ alignEnd = placed.end;
810
1157
  }
1158
+ coverAlignEndRef.current = alignEnd;
1159
+ const echoStart = echoOffset({
1160
+ triggerLeft: measured.left,
1161
+ triggerRight: measured.right,
1162
+ panelLeft: placedLeft,
1163
+ panelWidth: width,
1164
+ rtl,
1165
+ });
1166
+ const echoOverlays = iconEcho != null && iconEchoOverlays(iconEcho, iconEchoUpRef.current);
1167
+ const nextEchoBox =
1168
+ showIconEcho && echoOverlays
1169
+ ? echoHeaderBox({
1170
+ offset: echoStart,
1171
+ width: measured.width,
1172
+ height: measured.height,
1173
+ panelWidth: width,
1174
+ rtl,
1175
+ })
1176
+ : null;
1177
+ setEchoBox((previous) => (sameEchoBox(previous, nextEchoBox) ? previous : nextEchoBox));
1178
+ placeIconEcho(dropdownElement, {
1179
+ offset: echoStart,
1180
+ width: measured.width,
1181
+ height: measured.height,
1182
+ up: iconEchoUpRef.current,
1183
+ overlay: echoOverlays,
1184
+ group:
1185
+ groupSurface && groupBox
1186
+ ? { top: measured.top - groupBox.top, height: groupBox.height }
1187
+ : null,
1188
+ });
1189
+ placeEchoOverTrigger(
1190
+ dropdownElement,
1191
+ {
1192
+ triggerLeft: measured.left,
1193
+ triggerRight: measured.right,
1194
+ panelLeft: placedLeft,
1195
+ panelWidth: width,
1196
+ rtl,
1197
+ },
1198
+ { stretch: panelWidth !== undefined },
1199
+ );
811
1200
  const top = placedTop - containingBlock.top;
812
1201
  const cssLeft = placedLeft - containingBlock.left;
813
- const maxHeight = Math.max(0, viewportBottom - panelViewportPadding - placedTop);
1202
+ const maxHeight =
1203
+ placedMaxHeight ?? Math.max(0, viewportBottom - panelViewportPadding - placedTop);
814
1204
  dropdownElement.style.top = `${top}px`;
815
1205
  dropdownElement.style.left = `${cssLeft}px`;
816
1206
  dropdownElement.style.maxHeight = `${maxHeight}px`;
817
1207
  dropdownElement.dir = rtl ? "rtl" : "ltr";
818
- dropdownElement.style.transformOrigin = rtl ? "top right" : "top left";
1208
+ dropdownElement.style.transformOrigin = `${iconEchoUpRef.current ? "bottom" : "top"} ${rtl !== alignEnd ? "right" : "left"}`;
819
1209
  dropdownElement.style.visibility = "visible";
820
1210
  overlayStyleRef.current = { top, left: cssLeft, maxHeight };
821
1211
  panel.initializingRef.current = false;
@@ -975,10 +1365,14 @@ function FloatingPanelWeb({
975
1365
  }, [
976
1366
  isFill,
977
1367
  isContextMenu,
1368
+ pointMenu,
1369
+ showIconEcho,
978
1370
  cover,
979
1371
  anchorRect,
980
1372
  clampCollisions,
981
1373
  sheetProps.scrollable,
1374
+ triggerEcho,
1375
+ coverAlign,
982
1376
  panel.placement,
983
1377
  panel.refs,
984
1378
  panel.triggerRef,
@@ -1042,7 +1436,7 @@ function FloatingPanelWeb({
1042
1436
  (event: UIEvent) => {
1043
1437
  const el = event.target as HTMLElement;
1044
1438
  panel.updateArrows(el);
1045
- if (isFill || (cover && !isContextMenu)) return;
1439
+ if (isFill || (cover && !pointMenu)) return;
1046
1440
  if (panel.initializingRef.current) {
1047
1441
  lastScrollTopRef.current = el.scrollTop;
1048
1442
  return;
@@ -1066,7 +1460,7 @@ function FloatingPanelWeb({
1066
1460
  repositionFnRef.current?.();
1067
1461
  }
1068
1462
  },
1069
- [isFill, cover, isContextMenu, panel.updateArrows],
1463
+ [isFill, cover, pointMenu, panel.updateArrows],
1070
1464
  );
1071
1465
 
1072
1466
  const webTransition = panel.panelTransition;
@@ -1153,6 +1547,24 @@ function FloatingPanelWeb({
1153
1547
  onClickCapture: openFromSynthesizedClick,
1154
1548
  ...(isContextMenu ? { onContextMenu: openFromContextMenu } : {}),
1155
1549
  });
1550
+ // A content-sized trigger hugs its button instead of stretching across its
1551
+ // parent's cross axis. In a row that centres its items (a bar's capsule) it
1552
+ // keeps that centring, so an avatar sits on the row's line with its
1553
+ // neighbours (FZ-8480 Round 37).
1554
+ const contentSized = sheetProps.triggerSizing === "content";
1555
+ const [hugSelf, setHugSelf] = useState<"flex-start" | undefined>(
1556
+ contentSized ? "flex-start" : undefined,
1557
+ );
1558
+ useLayoutEffect(() => {
1559
+ if (!contentSized) {
1560
+ setHugSelf(undefined);
1561
+ return;
1562
+ }
1563
+ const parent = layoutParentOf(panel.triggerRef.current);
1564
+ const align = parent ? getComputedStyle(parent).alignItems : "stretch";
1565
+ setHugSelf(align === "stretch" || align === "normal" ? "flex-start" : undefined);
1566
+ }, [contentSized, panel.triggerRef]);
1567
+
1156
1568
  // Guard the composed floating-ui openers, after hook handlers are merged.
1157
1569
  // Declining leaves each action's own events and focus defaults intact.
1158
1570
  for (const name of ["onMouseDown", "onClick", "onKeyDown", "onKeyUp"] as const) {
@@ -1170,7 +1582,7 @@ function FloatingPanelWeb({
1170
1582
  <>
1171
1583
  <View
1172
1584
  {...referenceProps}
1173
- alignSelf={sheetProps.triggerSizing === "content" ? "flex-start" : undefined}
1585
+ alignSelf={hugSelf}
1174
1586
  cursor={disabled ? "not-allowed" : "pointer"}
1175
1587
  opacity={disabled ? 0.5 : 1}
1176
1588
  // The cover contract is stated as a relation between the panel's top
@@ -1201,92 +1613,110 @@ function FloatingPanelWeb({
1201
1613
  )}
1202
1614
  {panel.mounted && !collisionSheet && (
1203
1615
  <PanelPortal anchor={panel.triggerRef.current}>
1204
- <YStack
1205
- {...getFloatingProps({
1206
- ref: panel.refs.setFloating,
1207
- style: {
1208
- ...elevatedSurfaceStyle,
1209
- position: panel.strategy,
1210
- top: overlayStyleRef.current?.top ?? panel.y ?? 0,
1211
- left: overlayStyleRef.current?.left ?? panel.x ?? 0,
1212
- scrollbarWidth: "none" as any,
1213
- zIndex: panelZIndex,
1214
- overflow: "auto",
1215
- ...(cover && !isContextMenu ? { overflowX: "hidden" as const } : {}),
1216
- ...panel.floatingStyleRef.current,
1217
- ...(overlayStyleRef.current
1218
- ? { maxHeight: overlayStyleRef.current.maxHeight }
1219
- : {}),
1220
- opacity: panel.visible ? 1 : 0,
1221
- // `none`, not scale(1): any transform makes this panel the
1222
- // containing block of a nested panel's `position: fixed`, which
1223
- // then positions against this box and is clipped by its scroll.
1224
- transform: panel.visible ? "none" : "scale(0.96)",
1225
- transition: webTransition,
1226
- transformOrigin:
1227
- cover && !isContextMenu
1228
- ? isRtl(panel.triggerRef.current)
1229
- ? "top right"
1230
- : "top left"
1231
- : panelTransformOrigin(
1232
- pointPlacementRef.current
1233
- ? `${pointPlacementRef.current.dropup ? "top" : "bottom"}-${pointPlacementRef.current.alignEnd ? "end" : "start"}`
1234
- : panel.placement,
1235
- ),
1236
- },
1237
- onScroll: handleScroll,
1238
- onTransitionEnd: (event) => {
1239
- if (
1240
- event.target === event.currentTarget &&
1241
- (event.propertyName === "height" || event.propertyName === "max-height")
1242
- ) {
1243
- panel.updateArrows(event.currentTarget);
1244
- }
1245
- },
1246
- })}
1247
- data-testid="floating-panel-viewport"
1248
- data-fp-panel=""
1249
- {...panelChromeFocus}
1250
- pointerEvents={panel.visible ? "auto" : "none"}
1251
- {...elevatedSurfaceProps}
1252
- // Complete elevatedSurface fragment above is the ONLY surface source
1253
- // (SF-OVERLAY "including border and elevation", Round 2 docket).
1254
- // The one layered exception is anatomy, not surface: the dropdown
1255
- // radius clamp (same shape as Card's containerRadius over
1256
- // elevatedSurface) keeps list rows unclipped at large radius knobs.
1257
- borderRadius={panel.dropdownRadius}
1258
- paddingVertical={0}
1259
- >
1260
- {isContextMenu || !cover || !triggerEcho ? null : (
1261
- <TriggerEchoRow
1262
- sourceRef={panel.triggerRef}
1263
- source={trigger}
1264
- anchorRect={anchorRect}
1265
- onPress={() => onOpenChange(false)}
1266
- >
1267
- {triggerRow}
1268
- </TriggerEchoRow>
1269
- )}
1270
- <ScrollArrow
1271
- direction="up"
1272
- scrollRef={panel.refs.floating}
1273
- visible={panel.arrowUp}
1274
- arrowScrollDirRef={panel.arrowScrollDirRef}
1275
- />
1276
- <View
1277
- ref={contentRef as any}
1278
- {...(contentPadding !== "none" ? panel.knobProps.panelPadding : undefined)}
1279
- {...(contentPadding !== "none" ? panel.knobProps.gapLg : undefined)}
1616
+ {/* The grouped surface step (alt1), the one a field's own list already
1617
+ opens on: a light menu reads white over the page instead of the
1618
+ page's own grey, and a dark menu's hairlines clear its fill. */}
1619
+ <Theme name="alt1">
1620
+ <YStack
1621
+ {...getFloatingProps({
1622
+ ref: panel.refs.setFloating,
1623
+ style: {
1624
+ ...elevatedSurfaceStyle,
1625
+ position: panel.strategy,
1626
+ top: overlayStyleRef.current?.top ?? panel.y ?? 0,
1627
+ left: overlayStyleRef.current?.left ?? panel.x ?? 0,
1628
+ scrollbarWidth: "none" as any,
1629
+ zIndex: panelZIndex,
1630
+ overflow: "auto",
1631
+ ...(cover && !pointMenu ? { overflowX: "hidden" as const } : {}),
1632
+ ...panel.floatingStyleRef.current,
1633
+ ...(overlayStyleRef.current
1634
+ ? { maxHeight: overlayStyleRef.current.maxHeight }
1635
+ : {}),
1636
+ opacity: panel.visible ? 1 : 0,
1637
+ // `none`, not scale(1): any transform makes this panel the
1638
+ // containing block of a nested panel's `position: fixed`, which
1639
+ // then positions against this box and is clipped by its scroll.
1640
+ transform: panel.visible ? "none" : "scale(0.96)",
1641
+ transition: webTransition,
1642
+ transformOrigin:
1643
+ cover && !pointMenu
1644
+ ? `${iconEchoUpRef.current ? "bottom" : "top"} ${
1645
+ isRtl(panel.triggerRef.current) !== coverAlignEndRef.current
1646
+ ? "right"
1647
+ : "left"
1648
+ }`
1649
+ : panelTransformOrigin(
1650
+ pointPlacementRef.current
1651
+ ? `${pointPlacementRef.current.dropup ? "top" : "bottom"}-${pointPlacementRef.current.alignEnd ? "end" : "start"}`
1652
+ : panel.placement,
1653
+ ),
1654
+ },
1655
+ onScroll: handleScroll,
1656
+ onTransitionEnd: (event) => {
1657
+ if (
1658
+ event.target === event.currentTarget &&
1659
+ (event.propertyName === "height" || event.propertyName === "max-height")
1660
+ ) {
1661
+ panel.updateArrows(event.currentTarget);
1662
+ }
1663
+ },
1664
+ })}
1665
+ data-testid="floating-panel-viewport"
1666
+ data-fp-panel=""
1667
+ {...panelChromeFocus}
1668
+ pointerEvents={panel.visible ? "auto" : "none"}
1669
+ {...elevatedSurfaceProps}
1670
+ // Complete elevatedSurface fragment above is the ONLY surface source
1671
+ // (SF-OVERLAY "including border and elevation", Round 2 docket).
1672
+ // The one layered exception is anatomy, not surface: the dropdown
1673
+ // radius (Card's containerRadius shape over elevatedSurface). A
1674
+ // covering panel takes its trigger's whole arc (LC-112 seamless
1675
+ // cover); past the cap it insets its bottom so rows stay unclipped.
1676
+ borderRadius={panel.dropdownRadius}
1677
+ paddingVertical={0}
1678
+ paddingBottom={panel.coverCornerInset || 0}
1280
1679
  >
1281
- <OverlayStackProvider zIndex={panelZIndex}>{children}</OverlayStackProvider>
1282
- </View>
1283
- <ScrollArrow
1284
- direction="down"
1285
- scrollRef={panel.refs.floating}
1286
- visible={panel.arrowDown}
1287
- arrowScrollDirRef={panel.arrowScrollDirRef}
1288
- />
1289
- </YStack>
1680
+ {pointMenu || !cover || !triggerEcho || showIconEcho ? null : (
1681
+ <TriggerEchoRow
1682
+ sourceRef={panel.triggerRef}
1683
+ source={trigger}
1684
+ anchorRect={anchorRect}
1685
+ onPress={() => onOpenChange(false)}
1686
+ >
1687
+ {triggerRow}
1688
+ </TriggerEchoRow>
1689
+ )}
1690
+ <ScrollArrow
1691
+ direction="up"
1692
+ scrollRef={panel.refs.floating}
1693
+ visible={panel.arrowUp}
1694
+ arrowScrollDirRef={panel.arrowScrollDirRef}
1695
+ />
1696
+ <View
1697
+ ref={contentRef as any}
1698
+ {...(contentPadding !== "none" ? panel.knobProps.panelPadding : undefined)}
1699
+ {...(contentPadding !== "none" ? panel.knobProps.gapLg : undefined)}
1700
+ >
1701
+ <OverlayStackProvider zIndex={panelZIndex}>
1702
+ <PanelIconEchoBoxContext.Provider value={echoBox}>
1703
+ {children}
1704
+ </PanelIconEchoBoxContext.Provider>
1705
+ </OverlayStackProvider>
1706
+ </View>
1707
+ <ScrollArrow
1708
+ direction="down"
1709
+ scrollRef={panel.refs.floating}
1710
+ visible={panel.arrowDown}
1711
+ arrowScrollDirRef={panel.arrowScrollDirRef}
1712
+ />
1713
+ {showIconEcho && iconEcho ? (
1714
+ <IconEchoRow>
1715
+ {iconEchoElement(trigger, iconEcho, () => onOpenChange(false), triggerSurface)}
1716
+ </IconEchoRow>
1717
+ ) : null}
1718
+ </YStack>
1719
+ </Theme>
1290
1720
  </PanelPortal>
1291
1721
  )}
1292
1722
  </>