@multiplatform.one/forms 7.72.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 (534) hide show
  1. package/dist/cjs/AdaptivePopup/index.native.js +2 -2
  2. package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
  3. package/dist/cjs/Button/IconButtonGroup.native.js.map +1 -1
  4. package/dist/cjs/Button/circleGlyph.cjs +28 -0
  5. package/dist/cjs/Button/circleGlyph.native.js +31 -0
  6. package/dist/cjs/Button/circleGlyph.native.js.map +1 -0
  7. package/dist/cjs/Button/iconButtonGroupContext.native.js.map +1 -1
  8. package/dist/cjs/Button/index.cjs +3 -3
  9. package/dist/cjs/Button/index.native.js +5 -5
  10. package/dist/cjs/Button/index.native.js.map +1 -1
  11. package/dist/cjs/Button/mediaPlate.native.js.map +1 -1
  12. package/dist/cjs/Chip/index.native.js.map +1 -1
  13. package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js +1 -1
  14. package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js.map +1 -1
  15. package/dist/cjs/ContextualSaveBar/clientKnownIdentity.native.js.map +1 -1
  16. package/dist/cjs/ContextualSaveBar/commitImmediateField.native.js.map +1 -1
  17. package/dist/cjs/ContextualSaveBar/index.native.js +3 -3
  18. package/dist/cjs/ContextualSaveBar/index.native.js.map +1 -1
  19. package/dist/cjs/ContextualSaveBar/useUnsavedChangesGuard.native.js.map +1 -1
  20. package/dist/cjs/ControlGroup/index.native.js +1 -1
  21. package/dist/cjs/ControlGroup/index.native.js.map +1 -1
  22. package/dist/cjs/DescriptionList/index.native.js +2 -2
  23. package/dist/cjs/DescriptionList/index.native.js.map +1 -1
  24. package/dist/cjs/ErrorSummary.native.js.map +1 -1
  25. package/dist/cjs/FieldDisplay/editMode.native.js.map +1 -1
  26. package/dist/cjs/FieldDisplay/formatters.native.js.map +1 -1
  27. package/dist/cjs/FieldDisplay/index.native.js +1 -1
  28. package/dist/cjs/FieldDisplay/index.native.js.map +1 -1
  29. package/dist/cjs/FieldGroup.native.js.map +1 -1
  30. package/dist/cjs/FloatingPanel/PanelHeader.cjs +49 -0
  31. package/dist/cjs/FloatingPanel/PanelHeader.native.js +53 -0
  32. package/dist/cjs/FloatingPanel/PanelHeader.native.js.map +1 -0
  33. package/dist/cjs/FloatingPanel/RegularPanel.native.js.map +1 -1
  34. package/dist/cjs/FloatingPanel/compactFace.native.js.map +1 -1
  35. package/dist/cjs/FloatingPanel/containingBlock.native.js.map +1 -1
  36. package/dist/cjs/FloatingPanel/echoContext.cjs +28 -0
  37. package/dist/cjs/FloatingPanel/echoContext.native.js +29 -0
  38. package/dist/cjs/FloatingPanel/echoContext.native.js.map +1 -1
  39. package/dist/cjs/FloatingPanel/iconEcho.cjs +104 -0
  40. package/dist/cjs/FloatingPanel/iconEcho.native.js +114 -0
  41. package/dist/cjs/FloatingPanel/iconEcho.native.js.map +1 -0
  42. package/dist/cjs/FloatingPanel/index.cjs +168 -15
  43. package/dist/cjs/FloatingPanel/index.native.js +77 -8
  44. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  45. package/dist/cjs/FloatingPanel/nestedAction.native.js.map +1 -1
  46. package/dist/cjs/FloatingPanel/paintedBox.cjs +80 -0
  47. package/dist/cjs/FloatingPanel/paintedBox.native.js +85 -0
  48. package/dist/cjs/FloatingPanel/paintedBox.native.js.map +1 -0
  49. package/dist/cjs/FloatingPanel/panelWidth.native.js.map +1 -1
  50. package/dist/cjs/FloatingPanel/pointAnchor.cjs +22 -3
  51. package/dist/cjs/FloatingPanel/pointAnchor.native.js +26 -7
  52. package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
  53. package/dist/cjs/FloatingPanel/regularPlacement.native.js.map +1 -1
  54. package/dist/cjs/FloatingPanel/trailingAction.native.js +1 -1
  55. package/dist/cjs/FloatingPanel/trailingAction.native.js.map +1 -1
  56. package/dist/cjs/FloatingPanel/useFloatingPanel.cjs +2 -1
  57. package/dist/cjs/FloatingPanel/useFloatingPanel.native.js +3 -1
  58. package/dist/cjs/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  59. package/dist/cjs/Form/index.native.js +1 -1
  60. package/dist/cjs/Form/index.native.js.map +1 -1
  61. package/dist/cjs/Form/submissionController.native.js.map +1 -1
  62. package/dist/cjs/Form/validateOn.native.js.map +1 -1
  63. package/dist/cjs/FormGrid.native.js.map +1 -1
  64. package/dist/cjs/FormSection.native.js.map +1 -1
  65. package/dist/cjs/InputParts/PasswordReveal.native.js.map +1 -1
  66. package/dist/cjs/InputParts/index.native.js.map +1 -1
  67. package/dist/cjs/Label/index.native.js.map +1 -1
  68. package/dist/cjs/MediaPicker/base64.native.js.map +1 -1
  69. package/dist/cjs/MediaPicker/payload.native.js.map +1 -1
  70. package/dist/cjs/MediaPicker/types.native.js.map +1 -1
  71. package/dist/cjs/MediaPicker/useMediaPicker.native.js.map +1 -1
  72. package/dist/cjs/Skeleton/index.native.js.map +1 -1
  73. package/dist/cjs/Spinner/SpinnerGlyph.native.js.map +1 -1
  74. package/dist/cjs/Spinner/spinnerSize.native.js.map +1 -1
  75. package/dist/cjs/Table/index.native.js.map +1 -1
  76. package/dist/cjs/Wheel/haptic.native.js.map +1 -1
  77. package/dist/cjs/Wheel/index.native.js.map +1 -1
  78. package/dist/cjs/Wheel/wheelShared.native.js.map +1 -1
  79. package/dist/cjs/actionLabel.native.js.map +1 -1
  80. package/dist/cjs/fieldDefaults.native.js.map +1 -1
  81. package/dist/cjs/fieldLayout.native.js +1 -1
  82. package/dist/cjs/fieldLayout.native.js.map +1 -1
  83. package/dist/cjs/fields/Barcode/BarcodePreview.native.js.map +1 -1
  84. package/dist/cjs/fields/Barcode/BarcodeScanner.native.js +1 -1
  85. package/dist/cjs/fields/Barcode/code128.native.js.map +1 -1
  86. package/dist/cjs/fields/Barcode/index.native.js +2 -2
  87. package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
  88. package/dist/cjs/fields/Barcode/qr.native.js.map +1 -1
  89. package/dist/cjs/fields/Barcode/scannerTypes.native.js.map +1 -1
  90. package/dist/cjs/fields/Checkbox/Checkbox.native.js +1 -1
  91. package/dist/cjs/fields/Checkbox/Checkbox.native.js.map +1 -1
  92. package/dist/cjs/fields/Checkbox/CheckboxBox.native.js.map +1 -1
  93. package/dist/cjs/fields/Checkbox/CheckboxCard.native.js.map +1 -1
  94. package/dist/cjs/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
  95. package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js +1 -1
  96. package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js.map +1 -1
  97. package/dist/cjs/fields/Checkbox/checkboxGroupContext.native.js.map +1 -1
  98. package/dist/cjs/fields/Checkbox/index.native.js.map +1 -1
  99. package/dist/cjs/fields/ColorPicker/SystemColorField.native.js.map +1 -1
  100. package/dist/cjs/fields/ColorPicker/index.native.js +2 -2
  101. package/dist/cjs/fields/ColorPicker/index.native.js.map +1 -1
  102. package/dist/cjs/fields/ColorPicker/parts.native.js +2 -2
  103. package/dist/cjs/fields/ColorPicker/parts.native.js.map +1 -1
  104. package/dist/cjs/fields/ColorPicker/systemColorWell.gnome.native.js.map +1 -1
  105. package/dist/cjs/fields/Combobox/authorOptions.native.js.map +1 -1
  106. package/dist/cjs/fields/Combobox/index.native.js +5 -5
  107. package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
  108. package/dist/cjs/fields/DatePicker/Calendar.native.js.map +1 -1
  109. package/dist/cjs/fields/DatePicker/DateRangePicker.native.js +2 -2
  110. package/dist/cjs/fields/DatePicker/DateRangePicker.native.js.map +1 -1
  111. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js +3 -3
  112. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  113. package/dist/cjs/fields/DatePicker/DatetimePicker.native.js +2 -2
  114. package/dist/cjs/fields/DatePicker/MonthPicker.native.js +2 -2
  115. package/dist/cjs/fields/DatePicker/MonthPicker.native.js.map +1 -1
  116. package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js +2 -2
  117. package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
  118. package/dist/cjs/fields/DatePicker/catalog.native.js +2 -2
  119. package/dist/cjs/fields/DatePicker/catalog.native.js.map +1 -1
  120. package/dist/cjs/fields/DatePicker/fieldValueSync.native.js.map +1 -1
  121. package/dist/cjs/fields/DatePicker/index.native.js +2 -2
  122. package/dist/cjs/fields/DatePicker/parts.native.js +2 -2
  123. package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
  124. package/dist/cjs/fields/DatePicker/utils.native.js.map +1 -1
  125. package/dist/cjs/fields/Duration/durationMath.native.js.map +1 -1
  126. package/dist/cjs/fields/Duration/index.native.js +3 -3
  127. package/dist/cjs/fields/Duration/index.native.js.map +1 -1
  128. package/dist/cjs/fields/FileUpload/filePickerTypes.native.js.map +1 -1
  129. package/dist/cjs/fields/FileUpload/index.native.js +2 -2
  130. package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
  131. package/dist/cjs/fields/FileUpload/nativePickerLoader.native.js.map +1 -1
  132. package/dist/cjs/fields/Geolocation/expoMapsLoader.native.js.map +1 -1
  133. package/dist/cjs/fields/Geolocation/geoValue.native.js.map +1 -1
  134. package/dist/cjs/fields/Geolocation/geocoder.native.js.map +1 -1
  135. package/dist/cjs/fields/Geolocation/geocoderContext.native.js.map +1 -1
  136. package/dist/cjs/fields/Geolocation/index.native.js +5 -5
  137. package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
  138. package/dist/cjs/fields/Geolocation/mapMath.native.js.map +1 -1
  139. package/dist/cjs/fields/Geolocation/mapTypes.native.js.map +1 -1
  140. package/dist/cjs/fields/Geolocation/nativeSearchFocus.native.js.map +1 -1
  141. package/dist/cjs/fields/Geolocation/photonGeocoder.native.js.map +1 -1
  142. package/dist/cjs/fields/Icon/drawnSet.native.js.map +1 -1
  143. package/dist/cjs/fields/Icon/index.native.js +3 -3
  144. package/dist/cjs/fields/Icon/index.native.js.map +1 -1
  145. package/dist/cjs/fields/ImageField/index.native.js +2 -2
  146. package/dist/cjs/fields/ImageField/index.native.js.map +1 -1
  147. package/dist/cjs/fields/Input/index.native.js +3 -3
  148. package/dist/cjs/fields/Input/index.native.js.map +1 -1
  149. package/dist/cjs/fields/MentionInput/index.native.js +3 -3
  150. package/dist/cjs/fields/MentionInput/index.native.js.map +1 -1
  151. package/dist/cjs/fields/OTPInput/index.native.js +3 -3
  152. package/dist/cjs/fields/OTPInput/index.native.js.map +1 -1
  153. package/dist/cjs/fields/OTPInput/otpCellCss.native.js.map +1 -1
  154. package/dist/cjs/fields/PhoneInput/dialCodes.native.js.map +1 -1
  155. package/dist/cjs/fields/PhoneInput/index.native.js +3 -3
  156. package/dist/cjs/fields/PhoneInput/index.native.js.map +1 -1
  157. package/dist/cjs/fields/PhoneInput/phone.native.js.map +1 -1
  158. package/dist/cjs/fields/PhoneInput/regionNames.native.js.map +1 -1
  159. package/dist/cjs/fields/Progress/index.native.js +1 -1
  160. package/dist/cjs/fields/Progress/index.native.js.map +1 -1
  161. package/dist/cjs/fields/RadioGroup/index.native.js +1 -1
  162. package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
  163. package/dist/cjs/fields/Rating/index.native.js +1 -1
  164. package/dist/cjs/fields/Rating/index.native.js.map +1 -1
  165. package/dist/cjs/fields/Rating/ratingMath.native.js.map +1 -1
  166. package/dist/cjs/fields/Rating/types.native.js.map +1 -1
  167. package/dist/cjs/fields/SearchInput/index.native.js +2 -2
  168. package/dist/cjs/fields/SearchInput/index.native.js.map +1 -1
  169. package/dist/cjs/fields/Select/InlineChoices.native.js.map +1 -1
  170. package/dist/cjs/fields/Select/SheetPickerRow.native.js.map +1 -1
  171. package/dist/cjs/fields/Select/index.native.js +4 -4
  172. package/dist/cjs/fields/Select/index.native.js.map +1 -1
  173. package/dist/cjs/fields/Select/shouldInlineChoices.native.js.map +1 -1
  174. package/dist/cjs/fields/Signature/index.native.js +2 -2
  175. package/dist/cjs/fields/Signature/index.native.js.map +1 -1
  176. package/dist/cjs/fields/Slider/index.native.js +1 -1
  177. package/dist/cjs/fields/Slider/index.native.js.map +1 -1
  178. package/dist/cjs/fields/Slider/jumpMotion.native.js.map +1 -1
  179. package/dist/cjs/fields/Stepper/index.native.js +2 -2
  180. package/dist/cjs/fields/Stepper/index.native.js.map +1 -1
  181. package/dist/cjs/fields/Stepper/resolveStepperPlacement.native.js.map +1 -1
  182. package/dist/cjs/fields/Switch/footprint.native.js.map +1 -1
  183. package/dist/cjs/fields/Switch/index.native.js +2 -2
  184. package/dist/cjs/fields/Switch/index.native.js.map +1 -1
  185. package/dist/cjs/fields/TextArea/index.native.js +2 -2
  186. package/dist/cjs/fields/TextArea/index.native.js.map +1 -1
  187. package/dist/cjs/fields/TimePicker/catalog.native.js +3 -3
  188. package/dist/cjs/fields/TimePicker/catalog.native.js.map +1 -1
  189. package/dist/cjs/fields/TimePicker/index.native.js +2 -2
  190. package/dist/cjs/fields/TimePicker/parts.native.js +1 -1
  191. package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
  192. package/dist/cjs/fields/TimeRangeScrubber/geometry.native.js.map +1 -1
  193. package/dist/cjs/fields/TimeRangeScrubber/index.native.js +2 -2
  194. package/dist/cjs/fields/TimeRangeScrubber/index.native.js.map +1 -1
  195. package/dist/cjs/fields/ToggleGroup/index.native.js +1 -1
  196. package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
  197. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +1 -1
  198. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  199. package/dist/cjs/fields/nativeDateTime/dateTimePickerLoader.native.js.map +1 -1
  200. package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
  201. package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
  202. package/dist/cjs/fields/nativeSelect.native.js.map +1 -1
  203. package/dist/cjs/fields/selectedOrder.native.js.map +1 -1
  204. package/dist/cjs/formSpacing.native.js.map +1 -1
  205. package/dist/cjs/hooks/useFocusManagement.native.js.map +1 -1
  206. package/dist/cjs/index.cjs +2 -0
  207. package/dist/cjs/index.native.js +47 -45
  208. package/dist/cjs/index.native.js.map +1 -1
  209. package/dist/cjs/keyboard/KeyboardDoneBar.native.js +1 -1
  210. package/dist/cjs/keyboard/chain.native.js.map +1 -1
  211. package/dist/cjs/keyboard/index.native.js.map +1 -1
  212. package/dist/cjs/keyboard/types.native.js.map +1 -1
  213. package/dist/cjs/requiredMarking.native.js.map +1 -1
  214. package/dist/cjs/shared/TimeColumn.native.js +1 -1
  215. package/dist/cjs/shared/TimeColumn.native.js.map +1 -1
  216. package/dist/cjs/shared/capabilityPreflight.native.js.map +1 -1
  217. package/dist/cjs/shared/choiceRowPress.native.js.map +1 -1
  218. package/dist/cjs/shared/colorRamps.native.js.map +1 -1
  219. package/dist/cjs/shared/devWarn.native.js.map +1 -1
  220. package/dist/cjs/shared/floatingList.native.js +1 -1
  221. package/dist/cjs/shared/floatingList.native.js.map +1 -1
  222. package/dist/cjs/shared/focusManagement.native.js.map +1 -1
  223. package/dist/cjs/shared/groupContext.native.js.map +1 -1
  224. package/dist/cjs/shared/i18n.native.js.map +1 -1
  225. package/dist/cjs/shared/instantMotion.native.js.map +1 -1
  226. package/dist/cjs/shared/nativeA11y.native.js.map +1 -1
  227. package/dist/cjs/shared/nativeAutoFocus.native.js.map +1 -1
  228. package/dist/cjs/shared/overlaySizeClass.native.js.map +1 -1
  229. package/dist/cjs/shared/overlayStack.native.js.map +1 -1
  230. package/dist/cjs/shared/panelPortal.native.js.map +1 -1
  231. package/dist/cjs/shared/pickerTriggerContract.native.js.map +1 -1
  232. package/dist/cjs/shared/pressSlopProps.native.js.map +1 -1
  233. package/dist/cjs/shared/restoreRetries.native.js.map +1 -1
  234. package/dist/cjs/shared/sheetNavigation.native.js.map +1 -1
  235. package/dist/cjs/shared/systemControlModules.gnome.native.js.map +1 -1
  236. package/dist/cjs/shared/systemControls.native.js.map +1 -1
  237. package/dist/cjs/shared/t.native.js.map +1 -1
  238. package/dist/cjs/shared/tableCellContext.native.js.map +1 -1
  239. package/dist/cjs/shared/useDragScroll.native.js.map +1 -1
  240. package/dist/cjs/shared/useKeyboardInset.native.js.map +1 -1
  241. package/dist/cjs/shared/useSheetEscapeDismiss.native.js.map +1 -1
  242. package/dist/cjs/shared/useWheelIncrement.native.js.map +1 -1
  243. package/dist/cjs/shared/utils.native.js +1 -1
  244. package/dist/cjs/shared/utils.native.js.map +1 -1
  245. package/dist/cjs/shared/zIndex.native.js.map +1 -1
  246. package/dist/cjs/standardActions.native.js.map +1 -1
  247. package/dist/cjs/themedPrimitives.native.js.map +1 -1
  248. package/dist/cjs/types.native.js.map +1 -1
  249. package/dist/cjs/utils/LinkCell.native.js.map +1 -1
  250. package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
  251. package/dist/esm/Button/IconButtonGroup.native.js.map +1 -1
  252. package/dist/esm/Button/circleGlyph.mjs +3 -0
  253. package/dist/esm/Button/circleGlyph.mjs.map +1 -0
  254. package/dist/esm/Button/circleGlyph.native.js +3 -0
  255. package/dist/esm/Button/circleGlyph.native.js.map +1 -0
  256. package/dist/esm/Button/iconButtonGroupContext.native.js.map +1 -1
  257. package/dist/esm/Button/index.mjs +1 -1
  258. package/dist/esm/Button/index.mjs.map +1 -1
  259. package/dist/esm/Button/index.native.js +1 -1
  260. package/dist/esm/Button/index.native.js.map +1 -1
  261. package/dist/esm/Button/mediaPlate.native.js.map +1 -1
  262. package/dist/esm/Chip/index.native.js.map +1 -1
  263. package/dist/esm/ContextualSaveBar/UnsavedChangesDialog.native.js.map +1 -1
  264. package/dist/esm/ContextualSaveBar/clientKnownIdentity.native.js.map +1 -1
  265. package/dist/esm/ContextualSaveBar/commitImmediateField.native.js.map +1 -1
  266. package/dist/esm/ContextualSaveBar/index.native.js.map +1 -1
  267. package/dist/esm/ContextualSaveBar/useUnsavedChangesGuard.native.js.map +1 -1
  268. package/dist/esm/ControlGroup/index.native.js.map +1 -1
  269. package/dist/esm/DescriptionList/index.native.js.map +1 -1
  270. package/dist/esm/ErrorSummary.native.js.map +1 -1
  271. package/dist/esm/FieldDisplay/editMode.native.js.map +1 -1
  272. package/dist/esm/FieldDisplay/formatters.native.js.map +1 -1
  273. package/dist/esm/FieldDisplay/index.native.js.map +1 -1
  274. package/dist/esm/FieldGroup.native.js.map +1 -1
  275. package/dist/esm/FloatingPanel/PanelHeader.mjs +24 -0
  276. package/dist/esm/FloatingPanel/PanelHeader.mjs.map +1 -0
  277. package/dist/esm/FloatingPanel/PanelHeader.native.js +25 -0
  278. package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -0
  279. package/dist/esm/FloatingPanel/RegularPanel.native.js.map +1 -1
  280. package/dist/esm/FloatingPanel/compactFace.native.js.map +1 -1
  281. package/dist/esm/FloatingPanel/containingBlock.native.js.map +1 -1
  282. package/dist/esm/FloatingPanel/echoContext.mjs +25 -1
  283. package/dist/esm/FloatingPanel/echoContext.mjs.map +1 -1
  284. package/dist/esm/FloatingPanel/echoContext.native.js +26 -1
  285. package/dist/esm/FloatingPanel/echoContext.native.js.map +1 -1
  286. package/dist/esm/FloatingPanel/iconEcho.mjs +77 -0
  287. package/dist/esm/FloatingPanel/iconEcho.mjs.map +1 -0
  288. package/dist/esm/FloatingPanel/iconEcho.native.js +84 -0
  289. package/dist/esm/FloatingPanel/iconEcho.native.js.map +1 -0
  290. package/dist/esm/FloatingPanel/index.mjs +169 -16
  291. package/dist/esm/FloatingPanel/index.mjs.map +1 -1
  292. package/dist/esm/FloatingPanel/index.native.js +79 -10
  293. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  294. package/dist/esm/FloatingPanel/nestedAction.native.js.map +1 -1
  295. package/dist/esm/FloatingPanel/paintedBox.mjs +52 -0
  296. package/dist/esm/FloatingPanel/paintedBox.mjs.map +1 -0
  297. package/dist/esm/FloatingPanel/paintedBox.native.js +54 -0
  298. package/dist/esm/FloatingPanel/paintedBox.native.js.map +1 -0
  299. package/dist/esm/FloatingPanel/panelWidth.native.js.map +1 -1
  300. package/dist/esm/FloatingPanel/pointAnchor.mjs +22 -3
  301. package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
  302. package/dist/esm/FloatingPanel/pointAnchor.native.js +26 -7
  303. package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
  304. package/dist/esm/FloatingPanel/regularPlacement.native.js.map +1 -1
  305. package/dist/esm/FloatingPanel/trailingAction.native.js.map +1 -1
  306. package/dist/esm/FloatingPanel/useFloatingPanel.mjs +2 -1
  307. package/dist/esm/FloatingPanel/useFloatingPanel.mjs.map +1 -1
  308. package/dist/esm/FloatingPanel/useFloatingPanel.native.js +3 -1
  309. package/dist/esm/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  310. package/dist/esm/Form/index.native.js.map +1 -1
  311. package/dist/esm/Form/submissionController.native.js.map +1 -1
  312. package/dist/esm/Form/validateOn.native.js.map +1 -1
  313. package/dist/esm/FormGrid.native.js.map +1 -1
  314. package/dist/esm/FormSection.native.js.map +1 -1
  315. package/dist/esm/InputParts/PasswordReveal.native.js.map +1 -1
  316. package/dist/esm/InputParts/index.native.js.map +1 -1
  317. package/dist/esm/Label/index.native.js.map +1 -1
  318. package/dist/esm/MediaPicker/base64.native.js.map +1 -1
  319. package/dist/esm/MediaPicker/payload.native.js.map +1 -1
  320. package/dist/esm/MediaPicker/types.native.js.map +1 -1
  321. package/dist/esm/MediaPicker/useMediaPicker.native.js.map +1 -1
  322. package/dist/esm/Skeleton/index.native.js.map +1 -1
  323. package/dist/esm/Spinner/SpinnerGlyph.native.js.map +1 -1
  324. package/dist/esm/Spinner/spinnerSize.native.js.map +1 -1
  325. package/dist/esm/Table/index.native.js.map +1 -1
  326. package/dist/esm/Wheel/haptic.native.js.map +1 -1
  327. package/dist/esm/Wheel/index.native.js.map +1 -1
  328. package/dist/esm/Wheel/wheelShared.native.js.map +1 -1
  329. package/dist/esm/actionLabel.native.js.map +1 -1
  330. package/dist/esm/fieldDefaults.native.js.map +1 -1
  331. package/dist/esm/fieldLayout.native.js.map +1 -1
  332. package/dist/esm/fields/Barcode/BarcodePreview.native.js.map +1 -1
  333. package/dist/esm/fields/Barcode/code128.native.js.map +1 -1
  334. package/dist/esm/fields/Barcode/index.native.js.map +1 -1
  335. package/dist/esm/fields/Barcode/qr.native.js.map +1 -1
  336. package/dist/esm/fields/Barcode/scannerTypes.native.js.map +1 -1
  337. package/dist/esm/fields/Checkbox/Checkbox.native.js.map +1 -1
  338. package/dist/esm/fields/Checkbox/CheckboxBox.native.js.map +1 -1
  339. package/dist/esm/fields/Checkbox/CheckboxCard.native.js.map +1 -1
  340. package/dist/esm/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
  341. package/dist/esm/fields/Checkbox/CheckboxGroup.native.js.map +1 -1
  342. package/dist/esm/fields/Checkbox/checkboxGroupContext.native.js.map +1 -1
  343. package/dist/esm/fields/Checkbox/index.native.js.map +1 -1
  344. package/dist/esm/fields/ColorPicker/SystemColorField.native.js.map +1 -1
  345. package/dist/esm/fields/ColorPicker/index.native.js.map +1 -1
  346. package/dist/esm/fields/ColorPicker/parts.native.js.map +1 -1
  347. package/dist/esm/fields/ColorPicker/systemColorWell.gnome.native.js.map +1 -1
  348. package/dist/esm/fields/Combobox/authorOptions.native.js.map +1 -1
  349. package/dist/esm/fields/Combobox/index.native.js.map +1 -1
  350. package/dist/esm/fields/DatePicker/Calendar.native.js.map +1 -1
  351. package/dist/esm/fields/DatePicker/DateRangePicker.native.js.map +1 -1
  352. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  353. package/dist/esm/fields/DatePicker/MonthPicker.native.js.map +1 -1
  354. package/dist/esm/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
  355. package/dist/esm/fields/DatePicker/catalog.native.js.map +1 -1
  356. package/dist/esm/fields/DatePicker/fieldValueSync.native.js.map +1 -1
  357. package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
  358. package/dist/esm/fields/DatePicker/utils.native.js.map +1 -1
  359. package/dist/esm/fields/Duration/durationMath.native.js.map +1 -1
  360. package/dist/esm/fields/Duration/index.native.js.map +1 -1
  361. package/dist/esm/fields/FileUpload/filePickerTypes.native.js.map +1 -1
  362. package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
  363. package/dist/esm/fields/FileUpload/nativePickerLoader.native.js.map +1 -1
  364. package/dist/esm/fields/Geolocation/expoMapsLoader.native.js.map +1 -1
  365. package/dist/esm/fields/Geolocation/geoValue.native.js.map +1 -1
  366. package/dist/esm/fields/Geolocation/geocoder.native.js.map +1 -1
  367. package/dist/esm/fields/Geolocation/geocoderContext.native.js.map +1 -1
  368. package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
  369. package/dist/esm/fields/Geolocation/mapMath.native.js.map +1 -1
  370. package/dist/esm/fields/Geolocation/mapTypes.native.js.map +1 -1
  371. package/dist/esm/fields/Geolocation/nativeSearchFocus.native.js.map +1 -1
  372. package/dist/esm/fields/Geolocation/photonGeocoder.native.js.map +1 -1
  373. package/dist/esm/fields/Icon/drawnSet.native.js.map +1 -1
  374. package/dist/esm/fields/Icon/index.native.js.map +1 -1
  375. package/dist/esm/fields/ImageField/index.native.js.map +1 -1
  376. package/dist/esm/fields/Input/index.native.js.map +1 -1
  377. package/dist/esm/fields/MentionInput/index.native.js.map +1 -1
  378. package/dist/esm/fields/OTPInput/index.native.js.map +1 -1
  379. package/dist/esm/fields/OTPInput/otpCellCss.native.js.map +1 -1
  380. package/dist/esm/fields/PhoneInput/dialCodes.native.js.map +1 -1
  381. package/dist/esm/fields/PhoneInput/index.native.js.map +1 -1
  382. package/dist/esm/fields/PhoneInput/phone.native.js.map +1 -1
  383. package/dist/esm/fields/PhoneInput/regionNames.native.js.map +1 -1
  384. package/dist/esm/fields/Progress/index.native.js.map +1 -1
  385. package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
  386. package/dist/esm/fields/Rating/index.native.js.map +1 -1
  387. package/dist/esm/fields/Rating/ratingMath.native.js.map +1 -1
  388. package/dist/esm/fields/Rating/types.native.js.map +1 -1
  389. package/dist/esm/fields/SearchInput/index.native.js.map +1 -1
  390. package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
  391. package/dist/esm/fields/Select/SheetPickerRow.native.js.map +1 -1
  392. package/dist/esm/fields/Select/index.native.js.map +1 -1
  393. package/dist/esm/fields/Select/shouldInlineChoices.native.js.map +1 -1
  394. package/dist/esm/fields/Signature/index.native.js.map +1 -1
  395. package/dist/esm/fields/Slider/index.native.js.map +1 -1
  396. package/dist/esm/fields/Slider/jumpMotion.native.js.map +1 -1
  397. package/dist/esm/fields/Stepper/index.native.js.map +1 -1
  398. package/dist/esm/fields/Stepper/resolveStepperPlacement.native.js.map +1 -1
  399. package/dist/esm/fields/Switch/footprint.native.js.map +1 -1
  400. package/dist/esm/fields/Switch/index.native.js.map +1 -1
  401. package/dist/esm/fields/TextArea/index.native.js.map +1 -1
  402. package/dist/esm/fields/TimePicker/catalog.native.js.map +1 -1
  403. package/dist/esm/fields/TimePicker/parts.native.js.map +1 -1
  404. package/dist/esm/fields/TimeRangeScrubber/geometry.native.js.map +1 -1
  405. package/dist/esm/fields/TimeRangeScrubber/index.native.js.map +1 -1
  406. package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
  407. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  408. package/dist/esm/fields/nativeDateTime/dateTimePickerLoader.native.js.map +1 -1
  409. package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
  410. package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
  411. package/dist/esm/fields/nativeSelect.native.js.map +1 -1
  412. package/dist/esm/fields/selectedOrder.native.js.map +1 -1
  413. package/dist/esm/formSpacing.native.js.map +1 -1
  414. package/dist/esm/hooks/useFocusManagement.native.js.map +1 -1
  415. package/dist/esm/index.mjs +2 -1
  416. package/dist/esm/index.mjs.map +1 -1
  417. package/dist/esm/index.native.js +2 -1
  418. package/dist/esm/index.native.js.map +1 -1
  419. package/dist/esm/keyboard/chain.native.js.map +1 -1
  420. package/dist/esm/keyboard/index.native.js.map +1 -1
  421. package/dist/esm/keyboard/types.native.js.map +1 -1
  422. package/dist/esm/requiredMarking.native.js.map +1 -1
  423. package/dist/esm/shared/TimeColumn.native.js.map +1 -1
  424. package/dist/esm/shared/capabilityPreflight.native.js.map +1 -1
  425. package/dist/esm/shared/choiceRowPress.native.js.map +1 -1
  426. package/dist/esm/shared/colorRamps.native.js.map +1 -1
  427. package/dist/esm/shared/devWarn.native.js.map +1 -1
  428. package/dist/esm/shared/floatingList.native.js.map +1 -1
  429. package/dist/esm/shared/focusManagement.native.js.map +1 -1
  430. package/dist/esm/shared/groupContext.native.js.map +1 -1
  431. package/dist/esm/shared/i18n.native.js.map +1 -1
  432. package/dist/esm/shared/instantMotion.native.js.map +1 -1
  433. package/dist/esm/shared/nativeA11y.native.js.map +1 -1
  434. package/dist/esm/shared/nativeAutoFocus.native.js.map +1 -1
  435. package/dist/esm/shared/overlaySizeClass.native.js.map +1 -1
  436. package/dist/esm/shared/overlayStack.native.js.map +1 -1
  437. package/dist/esm/shared/panelPortal.native.js.map +1 -1
  438. package/dist/esm/shared/pickerTriggerContract.native.js.map +1 -1
  439. package/dist/esm/shared/pressSlopProps.native.js.map +1 -1
  440. package/dist/esm/shared/restoreRetries.native.js.map +1 -1
  441. package/dist/esm/shared/sheetNavigation.native.js.map +1 -1
  442. package/dist/esm/shared/systemControlModules.gnome.native.js.map +1 -1
  443. package/dist/esm/shared/systemControls.native.js.map +1 -1
  444. package/dist/esm/shared/t.native.js.map +1 -1
  445. package/dist/esm/shared/tableCellContext.native.js.map +1 -1
  446. package/dist/esm/shared/useDragScroll.native.js.map +1 -1
  447. package/dist/esm/shared/useKeyboardInset.native.js.map +1 -1
  448. package/dist/esm/shared/useSheetEscapeDismiss.native.js.map +1 -1
  449. package/dist/esm/shared/useWheelIncrement.native.js.map +1 -1
  450. package/dist/esm/shared/utils.native.js.map +1 -1
  451. package/dist/esm/shared/zIndex.native.js.map +1 -1
  452. package/dist/esm/standardActions.native.js.map +1 -1
  453. package/dist/esm/themedPrimitives.native.js.map +1 -1
  454. package/dist/esm/types.native.js.map +1 -1
  455. package/dist/esm/utils/LinkCell.native.js.map +1 -1
  456. package/dist/jsx/Button/circleGlyph.mjs +3 -0
  457. package/dist/jsx/Button/circleGlyph.mjs.map +1 -0
  458. package/dist/jsx/Button/circleGlyph.native.js +31 -0
  459. package/dist/jsx/Button/circleGlyph.native.js.map +1 -0
  460. package/dist/jsx/Button/index.mjs +1 -1
  461. package/dist/jsx/Button/index.mjs.map +1 -1
  462. package/dist/jsx/Button/index.native.js +3 -3
  463. package/dist/jsx/Button/index.native.js.map +1 -1
  464. package/dist/jsx/FloatingPanel/PanelHeader.mjs +24 -0
  465. package/dist/jsx/FloatingPanel/PanelHeader.mjs.map +1 -0
  466. package/dist/jsx/FloatingPanel/PanelHeader.native.js +53 -0
  467. package/dist/jsx/FloatingPanel/PanelHeader.native.js.map +1 -0
  468. package/dist/jsx/FloatingPanel/echoContext.mjs +25 -1
  469. package/dist/jsx/FloatingPanel/echoContext.mjs.map +1 -1
  470. package/dist/jsx/FloatingPanel/echoContext.native.js +29 -0
  471. package/dist/jsx/FloatingPanel/echoContext.native.js.map +1 -1
  472. package/dist/jsx/FloatingPanel/iconEcho.mjs +77 -0
  473. package/dist/jsx/FloatingPanel/iconEcho.mjs.map +1 -0
  474. package/dist/jsx/FloatingPanel/iconEcho.native.js +114 -0
  475. package/dist/jsx/FloatingPanel/iconEcho.native.js.map +1 -0
  476. package/dist/jsx/FloatingPanel/index.mjs +169 -16
  477. package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
  478. package/dist/jsx/FloatingPanel/index.native.js +77 -8
  479. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  480. package/dist/jsx/FloatingPanel/paintedBox.mjs +52 -0
  481. package/dist/jsx/FloatingPanel/paintedBox.mjs.map +1 -0
  482. package/dist/jsx/FloatingPanel/paintedBox.native.js +85 -0
  483. package/dist/jsx/FloatingPanel/paintedBox.native.js.map +1 -0
  484. package/dist/jsx/FloatingPanel/pointAnchor.mjs +22 -3
  485. package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
  486. package/dist/jsx/FloatingPanel/pointAnchor.native.js +26 -7
  487. package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
  488. package/dist/jsx/FloatingPanel/useFloatingPanel.mjs +2 -1
  489. package/dist/jsx/FloatingPanel/useFloatingPanel.mjs.map +1 -1
  490. package/dist/jsx/FloatingPanel/useFloatingPanel.native.js +3 -1
  491. package/dist/jsx/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  492. package/dist/jsx/index.js +2 -1
  493. package/dist/jsx/index.js.map +1 -1
  494. package/dist/jsx/index.mjs +2 -1
  495. package/dist/jsx/index.mjs.map +1 -1
  496. package/dist/jsx/index.native.js +2 -0
  497. package/dist/jsx/index.native.js.map +1 -1
  498. package/package.json +8 -8
  499. package/src/Button/circleGlyph.ts +6 -0
  500. package/src/Button/index.tsx +3 -7
  501. package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +297 -0
  502. package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +87 -1
  503. package/src/FloatingPanel/PanelHeader.tsx +26 -0
  504. package/src/FloatingPanel/echoContext.ts +45 -0
  505. package/src/FloatingPanel/iconEcho.tsx +118 -0
  506. package/src/FloatingPanel/index.native.tsx +115 -4
  507. package/src/FloatingPanel/index.tsx +238 -16
  508. package/src/FloatingPanel/paintedBox.spec.ts +95 -0
  509. package/src/FloatingPanel/paintedBox.ts +75 -0
  510. package/src/FloatingPanel/pointAnchor.spec.ts +47 -0
  511. package/src/FloatingPanel/pointAnchor.ts +28 -0
  512. package/src/FloatingPanel/useFloatingPanel.ts +6 -1
  513. package/src/index.ts +2 -0
  514. package/types/Button/circleGlyph.d.ts +7 -0
  515. package/types/Button/circleGlyph.d.ts.map +1 -0
  516. package/types/Button/index.d.ts +2 -6
  517. package/types/Button/index.d.ts.map +1 -1
  518. package/types/FloatingPanel/PanelHeader.d.ts +9 -0
  519. package/types/FloatingPanel/PanelHeader.d.ts.map +1 -0
  520. package/types/FloatingPanel/echoContext.d.ts +24 -0
  521. package/types/FloatingPanel/echoContext.d.ts.map +1 -1
  522. package/types/FloatingPanel/iconEcho.d.ts +29 -0
  523. package/types/FloatingPanel/iconEcho.d.ts.map +1 -0
  524. package/types/FloatingPanel/index.d.ts +10 -0
  525. package/types/FloatingPanel/index.d.ts.map +1 -1
  526. package/types/FloatingPanel/index.native.d.ts +1 -1
  527. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  528. package/types/FloatingPanel/paintedBox.d.ts +29 -0
  529. package/types/FloatingPanel/paintedBox.d.ts.map +1 -0
  530. package/types/FloatingPanel/pointAnchor.d.ts +10 -1
  531. package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
  532. package/types/FloatingPanel/useFloatingPanel.d.ts.map +1 -1
  533. package/types/index.d.ts +2 -0
  534. package/types/index.d.ts.map +1 -1
@@ -15,6 +15,7 @@ import {
15
15
  OVERLAY_ATTACH_GAP,
16
16
  OVERLAY_BREAKPOINT,
17
17
  Surface,
18
+ SurfaceContext,
18
19
  useResolvedKnobs,
19
20
  wasKeyboardFocus,
20
21
  ensureKeyboardModalityTracking,
@@ -40,6 +41,14 @@ import { OverlayStackProvider, useInsideOverlay, useOverlayZIndex } from "../sha
40
41
  import { resolveCompactFace, type CompactFace } from "./compactFace";
41
42
  import { isNestedPanelAction, type PanelActionEvent } from "./nestedAction";
42
43
  import { PanelPortal } from "./PanelPortal";
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";
43
52
  import {
44
53
  contextMenuPoint,
45
54
  placeOverTrigger,
@@ -92,6 +101,15 @@ export interface FloatingPanelProps {
92
101
  * instead of handing it to a sheet, the way an iOS menu grows out of it.
93
102
  */
94
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;
95
113
  /**
96
114
  * Optional control rendered beside the trigger but OUTSIDE the native press
97
115
  * capture (e.g. DatePicker/ColorPicker clear). On native the trigger subtree
@@ -519,6 +537,103 @@ export function placeEchoOverTrigger(
519
537
  }
520
538
  }
521
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
+
522
637
  // ── FloatingPanel ─────────────────────────────────────────────
523
638
 
524
639
  // Canonical float↔sheet pivot (theme `layoutBreakpoints.medium` = Tamagui
@@ -767,6 +882,7 @@ function FloatingPanelWeb({
767
882
  cover = true,
768
883
  triggerEcho = true,
769
884
  coverAlign = "start",
885
+ iconEcho,
770
886
  triggerRow,
771
887
  anchorRect,
772
888
  clampCollisions = false,
@@ -776,6 +892,13 @@ function FloatingPanelWeb({
776
892
  const [collisionSheet, setCollisionSheet] = useState(false);
777
893
  const isFill = sizing === "fill";
778
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);
779
902
  const overlayStyleRef = useRef<{ top: number; left?: number; maxHeight: number } | null>(null);
780
903
  const pinnedTopRef = useRef(0);
781
904
  const pinnedHeightRef = useRef(0);
@@ -810,10 +933,10 @@ function FloatingPanelWeb({
810
933
  wheelGrowRef: isFill ? undefined : grownRef,
811
934
  widthMode: widthMode ?? (isContextMenu && fitContent === undefined ? "fit-content" : undefined),
812
935
  fitContent,
813
- freeOverlay: isContextMenu,
936
+ freeOverlay: pointMenu,
814
937
  cover,
815
938
  panelWidth,
816
- echo: cover && triggerEcho && !isContextMenu,
939
+ echo: cover && triggerEcho && !pointMenu,
817
940
  onBeforeOpen: resetPosition,
818
941
  });
819
942
 
@@ -854,7 +977,7 @@ function FloatingPanelWeb({
854
977
  dropdownElement.scrollTop = 0;
855
978
  }
856
979
 
857
- const point = isContextMenu
980
+ const point = pointMenu
858
981
  ? (anchorPointRef.current ?? referenceCornerPoint(trig, isRtl()))
859
982
  : null;
860
983
  const measured = anchorRect
@@ -887,6 +1010,12 @@ function FloatingPanelWeb({
887
1010
  (viewport?.width ?? (document.documentElement.clientWidth || window.innerWidth));
888
1011
  const viewportBottom = viewportTop + (viewport?.height ?? window.innerHeight);
889
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` : "";
890
1019
  const layoutStyle = getComputedStyle(dropdownElement);
891
1020
  const scale =
892
1021
  layoutStyle.transform &&
@@ -955,7 +1084,38 @@ function FloatingPanelWeb({
955
1084
  : totalContentHeight;
956
1085
  let placedTop = measured.top;
957
1086
  let placedLeft = left;
958
- 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 (
959
1119
  measured.width > 0 &&
960
1120
  measured.height > 0 &&
961
1121
  ((!slides &&
@@ -996,6 +1156,36 @@ function FloatingPanelWeb({
996
1156
  alignEnd = placed.end;
997
1157
  }
998
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
+ });
999
1189
  placeEchoOverTrigger(
1000
1190
  dropdownElement,
1001
1191
  {
@@ -1009,12 +1199,13 @@ function FloatingPanelWeb({
1009
1199
  );
1010
1200
  const top = placedTop - containingBlock.top;
1011
1201
  const cssLeft = placedLeft - containingBlock.left;
1012
- const maxHeight = Math.max(0, viewportBottom - panelViewportPadding - placedTop);
1202
+ const maxHeight =
1203
+ placedMaxHeight ?? Math.max(0, viewportBottom - panelViewportPadding - placedTop);
1013
1204
  dropdownElement.style.top = `${top}px`;
1014
1205
  dropdownElement.style.left = `${cssLeft}px`;
1015
1206
  dropdownElement.style.maxHeight = `${maxHeight}px`;
1016
1207
  dropdownElement.dir = rtl ? "rtl" : "ltr";
1017
- dropdownElement.style.transformOrigin = rtl !== alignEnd ? "top right" : "top left";
1208
+ dropdownElement.style.transformOrigin = `${iconEchoUpRef.current ? "bottom" : "top"} ${rtl !== alignEnd ? "right" : "left"}`;
1018
1209
  dropdownElement.style.visibility = "visible";
1019
1210
  overlayStyleRef.current = { top, left: cssLeft, maxHeight };
1020
1211
  panel.initializingRef.current = false;
@@ -1174,6 +1365,8 @@ function FloatingPanelWeb({
1174
1365
  }, [
1175
1366
  isFill,
1176
1367
  isContextMenu,
1368
+ pointMenu,
1369
+ showIconEcho,
1177
1370
  cover,
1178
1371
  anchorRect,
1179
1372
  clampCollisions,
@@ -1243,7 +1436,7 @@ function FloatingPanelWeb({
1243
1436
  (event: UIEvent) => {
1244
1437
  const el = event.target as HTMLElement;
1245
1438
  panel.updateArrows(el);
1246
- if (isFill || (cover && !isContextMenu)) return;
1439
+ if (isFill || (cover && !pointMenu)) return;
1247
1440
  if (panel.initializingRef.current) {
1248
1441
  lastScrollTopRef.current = el.scrollTop;
1249
1442
  return;
@@ -1267,7 +1460,7 @@ function FloatingPanelWeb({
1267
1460
  repositionFnRef.current?.();
1268
1461
  }
1269
1462
  },
1270
- [isFill, cover, isContextMenu, panel.updateArrows],
1463
+ [isFill, cover, pointMenu, panel.updateArrows],
1271
1464
  );
1272
1465
 
1273
1466
  const webTransition = panel.panelTransition;
@@ -1354,6 +1547,24 @@ function FloatingPanelWeb({
1354
1547
  onClickCapture: openFromSynthesizedClick,
1355
1548
  ...(isContextMenu ? { onContextMenu: openFromContextMenu } : {}),
1356
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
+
1357
1568
  // Guard the composed floating-ui openers, after hook handlers are merged.
1358
1569
  // Declining leaves each action's own events and focus defaults intact.
1359
1570
  for (const name of ["onMouseDown", "onClick", "onKeyDown", "onKeyUp"] as const) {
@@ -1371,7 +1582,7 @@ function FloatingPanelWeb({
1371
1582
  <>
1372
1583
  <View
1373
1584
  {...referenceProps}
1374
- alignSelf={sheetProps.triggerSizing === "content" ? "flex-start" : undefined}
1585
+ alignSelf={hugSelf}
1375
1586
  cursor={disabled ? "not-allowed" : "pointer"}
1376
1587
  opacity={disabled ? 0.5 : 1}
1377
1588
  // The cover contract is stated as a relation between the panel's top
@@ -1417,7 +1628,7 @@ function FloatingPanelWeb({
1417
1628
  scrollbarWidth: "none" as any,
1418
1629
  zIndex: panelZIndex,
1419
1630
  overflow: "auto",
1420
- ...(cover && !isContextMenu ? { overflowX: "hidden" as const } : {}),
1631
+ ...(cover && !pointMenu ? { overflowX: "hidden" as const } : {}),
1421
1632
  ...panel.floatingStyleRef.current,
1422
1633
  ...(overlayStyleRef.current
1423
1634
  ? { maxHeight: overlayStyleRef.current.maxHeight }
@@ -1429,10 +1640,12 @@ function FloatingPanelWeb({
1429
1640
  transform: panel.visible ? "none" : "scale(0.96)",
1430
1641
  transition: webTransition,
1431
1642
  transformOrigin:
1432
- cover && !isContextMenu
1433
- ? isRtl(panel.triggerRef.current) !== coverAlignEndRef.current
1434
- ? "top right"
1435
- : "top left"
1643
+ cover && !pointMenu
1644
+ ? `${iconEchoUpRef.current ? "bottom" : "top"} ${
1645
+ isRtl(panel.triggerRef.current) !== coverAlignEndRef.current
1646
+ ? "right"
1647
+ : "left"
1648
+ }`
1436
1649
  : panelTransformOrigin(
1437
1650
  pointPlacementRef.current
1438
1651
  ? `${pointPlacementRef.current.dropup ? "top" : "bottom"}-${pointPlacementRef.current.alignEnd ? "end" : "start"}`
@@ -1464,7 +1677,7 @@ function FloatingPanelWeb({
1464
1677
  paddingVertical={0}
1465
1678
  paddingBottom={panel.coverCornerInset || 0}
1466
1679
  >
1467
- {isContextMenu || !cover || !triggerEcho ? null : (
1680
+ {pointMenu || !cover || !triggerEcho || showIconEcho ? null : (
1468
1681
  <TriggerEchoRow
1469
1682
  sourceRef={panel.triggerRef}
1470
1683
  source={trigger}
@@ -1485,7 +1698,11 @@ function FloatingPanelWeb({
1485
1698
  {...(contentPadding !== "none" ? panel.knobProps.panelPadding : undefined)}
1486
1699
  {...(contentPadding !== "none" ? panel.knobProps.gapLg : undefined)}
1487
1700
  >
1488
- <OverlayStackProvider zIndex={panelZIndex}>{children}</OverlayStackProvider>
1701
+ <OverlayStackProvider zIndex={panelZIndex}>
1702
+ <PanelIconEchoBoxContext.Provider value={echoBox}>
1703
+ {children}
1704
+ </PanelIconEchoBoxContext.Provider>
1705
+ </OverlayStackProvider>
1489
1706
  </View>
1490
1707
  <ScrollArrow
1491
1708
  direction="down"
@@ -1493,6 +1710,11 @@ function FloatingPanelWeb({
1493
1710
  visible={panel.arrowDown}
1494
1711
  arrowScrollDirRef={panel.arrowScrollDirRef}
1495
1712
  />
1713
+ {showIconEcho && iconEcho ? (
1714
+ <IconEchoRow>
1715
+ {iconEchoElement(trigger, iconEcho, () => onOpenChange(false), triggerSurface)}
1716
+ </IconEchoRow>
1717
+ ) : null}
1496
1718
  </YStack>
1497
1719
  </Theme>
1498
1720
  </PanelPortal>
@@ -0,0 +1,95 @@
1
+ /**
2
+ * LC-112 (FZ-8480): a panel covering an icon trigger covers the button as it
3
+ * is drawn. The 1440 + hung 6px past its layout slot on its press slop, and
4
+ * the 390 + sat in a lone glass capsule 6px wider on each side; both left a
5
+ * sliver of the button showing beside the panel.
6
+ */
7
+ import { afterEach, describe, expect, it } from "vitest";
8
+ import { PAINTED_BOX_REACH, paintedTriggerBox } from "./paintedBox";
9
+
10
+ type Rect = [left: number, top: number, width: number, height: number];
11
+
12
+ function el(rect: Rect | null, children: HTMLElement[] = [], style = "") {
13
+ const node = document.createElement("div");
14
+ if (style) node.setAttribute("style", style);
15
+ for (const child of children) node.appendChild(child);
16
+ const [left, top, width, height] = rect ?? [0, 0, 0, 0];
17
+ node.getBoundingClientRect = () =>
18
+ ({
19
+ left,
20
+ top,
21
+ width,
22
+ height,
23
+ x: left,
24
+ y: top,
25
+ right: left + width,
26
+ bottom: top + height,
27
+ toJSON: () => ({}),
28
+ }) as DOMRect;
29
+ return node;
30
+ }
31
+
32
+ const box = ([left, top, width, height]: Rect) => ({
33
+ left,
34
+ top,
35
+ right: left + width,
36
+ bottom: top + height,
37
+ });
38
+
39
+ afterEach(() => {
40
+ document.body.replaceChildren();
41
+ });
42
+
43
+ describe("paintedTriggerBox (FZ-8480)", () => {
44
+ it("takes in a bare glyph's press box that hangs past its layout slot", () => {
45
+ const button = el([1302.5, 61, 40, 40]);
46
+ const reference = el([1302.5, 61, 34, 40], [el(null, [button], "display: contents")]);
47
+ document.body.appendChild(el([1302.5, 61, 34, 40], [reference]));
48
+ expect(paintedTriggerBox(reference, box([1302.5, 61, 34, 40]))).toEqual(
49
+ box([1302.5, 61, 40, 40]),
50
+ );
51
+ });
52
+
53
+ it("takes in a lone button's plate, ignoring the plate's absolute face", () => {
54
+ const reference = el([324, 61, 44, 44]);
55
+ const face = el([318, 61, 56, 44], [], "position: absolute");
56
+ const capsule = el([318, 61, 56, 44], [face, reference]);
57
+ const row = el([16, 61, 358, 44], [el([16, 61, 200, 44]), capsule]);
58
+ document.body.appendChild(row);
59
+ expect(paintedTriggerBox(reference, box([324, 61, 44, 44]))).toEqual(box([318, 61, 56, 44]));
60
+ });
61
+
62
+ it("stops at a wrapper shared with other content", () => {
63
+ const reference = el([290, 0, 44, 44]);
64
+ const pill = el([214, 0, 160, 44], [el([220, 0, 44, 44]), reference]);
65
+ document.body.appendChild(pill);
66
+ expect(paintedTriggerBox(reference, box([290, 0, 44, 44]))).toEqual(box([290, 0, 44, 44]));
67
+ });
68
+
69
+ it("reads a DOMRect layout box, whose sides are prototype getters", () => {
70
+ const reference = el([324, 61, 44, 44]);
71
+ document.body.appendChild(el([16, 61, 358, 44], [el([16, 61, 200, 44]), reference]));
72
+ class Getters {
73
+ get left() {
74
+ return 324;
75
+ }
76
+ get top() {
77
+ return 61;
78
+ }
79
+ get right() {
80
+ return 368;
81
+ }
82
+ get bottom() {
83
+ return 105;
84
+ }
85
+ }
86
+ const layout = new Getters();
87
+ expect(paintedTriggerBox(reference, layout)).toEqual(box([324, 61, 44, 44]));
88
+ });
89
+
90
+ it(`never reaches more than ${PAINTED_BOX_REACH}px past the trigger`, () => {
91
+ const reference = el([324, 61, 44, 44]);
92
+ document.body.appendChild(el([0, 61, 390, 44], [reference]));
93
+ expect(paintedTriggerBox(reference, box([324, 61, 44, 44]))).toEqual(box([324, 61, 44, 44]));
94
+ });
95
+ });
@@ -0,0 +1,75 @@
1
+ export type Box = { left: number; top: number; right: number; bottom: number };
2
+
3
+ /** How far a trigger's plate or press box may reach past its layout box. */
4
+ export const PAINTED_BOX_REACH = 12;
5
+
6
+ function inFlowChildren(element: Element): Element[] {
7
+ return Array.from(element.children).filter((child) => {
8
+ const style = getComputedStyle(child);
9
+ return style.display !== "none" && style.position !== "absolute" && style.position !== "fixed";
10
+ });
11
+ }
12
+
13
+ /**
14
+ * The box an icon trigger is seen in, which a panel covering it has to cover
15
+ * (LC-112, FZ-8480). A bare glyph's press box can hang past its layout slot
16
+ * (the trailing press slop), and a lone button's plate can be a wrapper that
17
+ * holds nothing else (an iOS glass capsule). Both count, up to
18
+ * {@link PAINTED_BOX_REACH} on any side; a wrapper shared with other content
19
+ * ends the walk.
20
+ */
21
+ export function paintedTriggerBox(reference: Element, layout: Box): Box {
22
+ // A DOMRect's sides are prototype getters, so spreading it copies nothing.
23
+ const box = { left: layout.left, top: layout.top, right: layout.right, bottom: layout.bottom };
24
+ const take = (rect: DOMRect) => {
25
+ if (rect.width <= 0 || rect.height <= 0) return true;
26
+ const next = {
27
+ left: Math.min(box.left, rect.left),
28
+ top: Math.min(box.top, rect.top),
29
+ right: Math.max(box.right, rect.right),
30
+ bottom: Math.max(box.bottom, rect.bottom),
31
+ };
32
+ if (
33
+ layout.left - next.left > PAINTED_BOX_REACH ||
34
+ layout.top - next.top > PAINTED_BOX_REACH ||
35
+ next.right - layout.right > PAINTED_BOX_REACH ||
36
+ next.bottom - layout.bottom > PAINTED_BOX_REACH
37
+ ) {
38
+ return false;
39
+ }
40
+ Object.assign(box, next);
41
+ return true;
42
+ };
43
+ let inner: Element | null = reference;
44
+ while (inner) {
45
+ const children = inFlowChildren(inner);
46
+ if (children.length !== 1) break;
47
+ inner = children[0]!;
48
+ if (!take(inner.getBoundingClientRect())) break;
49
+ }
50
+ let outer: Element | null = reference.parentElement;
51
+ let child: Element = reference;
52
+ while (outer && outer.tagName !== "BODY") {
53
+ const children = inFlowChildren(outer);
54
+ if (children.length !== 1 || children[0] !== child) break;
55
+ if (!take(outer.getBoundingClientRect())) break;
56
+ child = outer;
57
+ outer = outer.parentElement;
58
+ }
59
+ return box;
60
+ }
61
+
62
+ /**
63
+ * A plate several buttons share (an iOS glass capsule around a bar's group).
64
+ * `data-fp-cover-surface` marks one explicitly.
65
+ */
66
+ export const GROUP_SURFACE_SELECTOR = "[data-fp-cover-surface], [data-mp-glass-capsule]";
67
+
68
+ /**
69
+ * The shared plate an icon trigger sits on, when it sits in a group (LC-112,
70
+ * FZ-8480 r37): the trigger's surface is the plate under it, so a panel
71
+ * opening from a grouped button covers the whole group.
72
+ */
73
+ export function groupSurfaceOf(reference: Element): HTMLElement | null {
74
+ return reference.parentElement?.closest<HTMLElement>(GROUP_SURFACE_SELECTOR) ?? null;
75
+ }
@@ -161,6 +161,7 @@ describe("placeOverTrigger (FZ-7830, LC-112 where no sheet may take it)", () =>
161
161
  maxHeight: 600,
162
162
  shifted: false,
163
163
  end: false,
164
+ up: false,
164
165
  });
165
166
  });
166
167
 
@@ -276,6 +277,51 @@ describe("placeOverTrigger (FZ-7830, LC-112 where no sheet may take it)", () =>
276
277
  });
277
278
  });
278
279
 
280
+ describe("placeOverTrigger pinned: an echoed icon trigger stays in its spot (LC-112, FZ-8480)", () => {
281
+ const phone: PanelBounds = { top: 57, left: 10, right: 380, bottom: 800 };
282
+
283
+ it("opens down from the button's top edge where the menu fits", () => {
284
+ const button = { x: 326, y: 60, width: 44, height: 44 };
285
+ const placed = placeOverTrigger({
286
+ trigger: button,
287
+ width: 250,
288
+ height: 300,
289
+ bounds: phone,
290
+ align: "auto",
291
+ pinned: true,
292
+ });
293
+ expect(placed).toMatchObject({ top: 60, left: 120, end: true, up: false, maxHeight: 740 });
294
+ });
295
+
296
+ it("opens up from the button's bottom edge near the bottom, never sliding off the button", () => {
297
+ const button = { x: 20, y: 740, width: 44, height: 44 };
298
+ const placed = placeOverTrigger({
299
+ trigger: button,
300
+ width: 250,
301
+ height: 300,
302
+ bounds: phone,
303
+ align: "auto",
304
+ pinned: true,
305
+ });
306
+ expect(placed.up).toBe(true);
307
+ expect(placed.top + 300).toBe(button.y + button.height);
308
+ expect(placed.maxHeight).toBe(button.y + button.height - phone.top);
309
+ });
310
+
311
+ it("scrolls from the button rather than covering past the margin when neither side fits", () => {
312
+ const button = { x: 20, y: 400, width: 44, height: 44 };
313
+ const placed = placeOverTrigger({
314
+ trigger: button,
315
+ width: 250,
316
+ height: 2000,
317
+ bounds: phone,
318
+ align: "auto",
319
+ pinned: true,
320
+ });
321
+ expect(placed).toMatchObject({ top: 400, up: false, maxHeight: 400 });
322
+ });
323
+ });
324
+
279
325
  describe("placeBesideTrigger (FZ-7830 round 3, LC-262: beside the trigger, never over it)", () => {
280
326
  const phone: PanelBounds = { top: 57, left: 10, right: 380, bottom: 800 };
281
327
  const field = { x: 16, y: 300, width: 358, height: 44 };
@@ -289,6 +335,7 @@ describe("placeBesideTrigger (FZ-7830 round 3, LC-262: beside the trigger, never
289
335
  maxHeight: 180,
290
336
  shifted: false,
291
337
  end: false,
338
+ up: false,
292
339
  });
293
340
  });
294
341