@multiplatform.one/forms 7.56.0 → 7.58.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 (367) hide show
  1. package/dist/cjs/AdaptivePopup/index.cjs +5 -1
  2. package/dist/cjs/AdaptivePopup/index.native.js +6 -2
  3. package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
  4. package/dist/cjs/Button/index.cjs +2 -1
  5. package/dist/cjs/Button/index.native.js +4 -3
  6. package/dist/cjs/Button/index.native.js.map +1 -1
  7. package/dist/cjs/Chip/index.native.js.map +1 -1
  8. package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js +1 -1
  9. package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js.map +1 -1
  10. package/dist/cjs/ContextualSaveBar/clientKnownIdentity.native.js.map +1 -1
  11. package/dist/cjs/ContextualSaveBar/commitImmediateField.native.js.map +1 -1
  12. package/dist/cjs/ContextualSaveBar/index.native.js +3 -3
  13. package/dist/cjs/ContextualSaveBar/index.native.js.map +1 -1
  14. package/dist/cjs/ContextualSaveBar/useUnsavedChangesGuard.native.js.map +1 -1
  15. package/dist/cjs/ControlGroup/index.native.js +1 -1
  16. package/dist/cjs/ControlGroup/index.native.js.map +1 -1
  17. package/dist/cjs/DescriptionList/index.native.js +2 -2
  18. package/dist/cjs/DescriptionList/index.native.js.map +1 -1
  19. package/dist/cjs/ErrorSummary.native.js.map +1 -1
  20. package/dist/cjs/FieldDisplay/editMode.native.js.map +1 -1
  21. package/dist/cjs/FieldDisplay/formatters.native.js.map +1 -1
  22. package/dist/cjs/FieldDisplay/index.native.js +1 -1
  23. package/dist/cjs/FieldDisplay/index.native.js.map +1 -1
  24. package/dist/cjs/FieldGroup.native.js.map +1 -1
  25. package/dist/cjs/FloatingPanel/compactFace.cjs +38 -0
  26. package/dist/cjs/FloatingPanel/compactFace.native.js +42 -0
  27. package/dist/cjs/FloatingPanel/compactFace.native.js.map +1 -0
  28. package/dist/cjs/FloatingPanel/index.cjs +38 -9
  29. package/dist/cjs/FloatingPanel/index.native.js +154 -6
  30. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  31. package/dist/cjs/FloatingPanel/nestedAction.native.js.map +1 -1
  32. package/dist/cjs/FloatingPanel/pointAnchor.cjs +22 -0
  33. package/dist/cjs/FloatingPanel/pointAnchor.native.js +23 -0
  34. package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
  35. package/dist/cjs/FloatingPanel/trailingAction.native.js +1 -1
  36. package/dist/cjs/FloatingPanel/trailingAction.native.js.map +1 -1
  37. package/dist/cjs/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  38. package/dist/cjs/Form/index.native.js +1 -1
  39. package/dist/cjs/Form/index.native.js.map +1 -1
  40. package/dist/cjs/Form/submissionController.native.js.map +1 -1
  41. package/dist/cjs/Form/validateOn.native.js.map +1 -1
  42. package/dist/cjs/FormGrid.native.js.map +1 -1
  43. package/dist/cjs/FormSection.native.js.map +1 -1
  44. package/dist/cjs/InputParts/PasswordReveal.native.js.map +1 -1
  45. package/dist/cjs/InputParts/index.cjs +13 -7
  46. package/dist/cjs/InputParts/index.native.js +13 -7
  47. package/dist/cjs/InputParts/index.native.js.map +1 -1
  48. package/dist/cjs/Label/index.native.js.map +1 -1
  49. package/dist/cjs/MediaPicker/base64.native.js.map +1 -1
  50. package/dist/cjs/MediaPicker/payload.native.js.map +1 -1
  51. package/dist/cjs/MediaPicker/types.native.js.map +1 -1
  52. package/dist/cjs/MediaPicker/useMediaPicker.native.js.map +1 -1
  53. package/dist/cjs/Skeleton/index.native.js.map +1 -1
  54. package/dist/cjs/Spinner/SpinnerGlyph.native.js.map +1 -1
  55. package/dist/cjs/Spinner/spinnerSize.native.js.map +1 -1
  56. package/dist/cjs/Table/index.native.js.map +1 -1
  57. package/dist/cjs/Wheel/haptic.native.js.map +1 -1
  58. package/dist/cjs/Wheel/index.native.js.map +1 -1
  59. package/dist/cjs/Wheel/wheelShared.native.js.map +1 -1
  60. package/dist/cjs/actionLabel.native.js.map +1 -1
  61. package/dist/cjs/fieldDefaults.native.js.map +1 -1
  62. package/dist/cjs/fieldLayout.native.js +1 -1
  63. package/dist/cjs/fieldLayout.native.js.map +1 -1
  64. package/dist/cjs/fields/Barcode/BarcodePreview.native.js.map +1 -1
  65. package/dist/cjs/fields/Barcode/BarcodeScanner.native.js +1 -1
  66. package/dist/cjs/fields/Barcode/code128.native.js.map +1 -1
  67. package/dist/cjs/fields/Barcode/index.native.js +2 -2
  68. package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
  69. package/dist/cjs/fields/Barcode/qr.native.js.map +1 -1
  70. package/dist/cjs/fields/Barcode/scannerTypes.native.js.map +1 -1
  71. package/dist/cjs/fields/Checkbox/Checkbox.cjs +24 -14
  72. package/dist/cjs/fields/Checkbox/Checkbox.native.js +39 -23
  73. package/dist/cjs/fields/Checkbox/Checkbox.native.js.map +1 -1
  74. package/dist/cjs/fields/Checkbox/CheckboxBox.native.js.map +1 -1
  75. package/dist/cjs/fields/Checkbox/CheckboxCard.native.js.map +1 -1
  76. package/dist/cjs/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
  77. package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js +1 -1
  78. package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js.map +1 -1
  79. package/dist/cjs/fields/Checkbox/checkboxGroupContext.native.js.map +1 -1
  80. package/dist/cjs/fields/Checkbox/index.native.js.map +1 -1
  81. package/dist/cjs/fields/ColorPicker/index.native.js +2 -2
  82. package/dist/cjs/fields/ColorPicker/index.native.js.map +1 -1
  83. package/dist/cjs/fields/ColorPicker/parts.native.js +2 -2
  84. package/dist/cjs/fields/ColorPicker/parts.native.js.map +1 -1
  85. package/dist/cjs/fields/Combobox/authorOptions.native.js.map +1 -1
  86. package/dist/cjs/fields/Combobox/index.native.js +5 -5
  87. package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
  88. package/dist/cjs/fields/DatePicker/Calendar.native.js.map +1 -1
  89. package/dist/cjs/fields/DatePicker/DateRangePicker.native.js +2 -2
  90. package/dist/cjs/fields/DatePicker/DateRangePicker.native.js.map +1 -1
  91. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js +3 -3
  92. package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  93. package/dist/cjs/fields/DatePicker/DatetimePicker.native.js +2 -2
  94. package/dist/cjs/fields/DatePicker/MonthPicker.native.js +2 -2
  95. package/dist/cjs/fields/DatePicker/MonthPicker.native.js.map +1 -1
  96. package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js +2 -2
  97. package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
  98. package/dist/cjs/fields/DatePicker/catalog.native.js +2 -2
  99. package/dist/cjs/fields/DatePicker/catalog.native.js.map +1 -1
  100. package/dist/cjs/fields/DatePicker/fieldValueSync.native.js.map +1 -1
  101. package/dist/cjs/fields/DatePicker/index.native.js +2 -2
  102. package/dist/cjs/fields/DatePicker/parts.native.js +2 -2
  103. package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
  104. package/dist/cjs/fields/DatePicker/utils.native.js.map +1 -1
  105. package/dist/cjs/fields/Duration/durationMath.native.js.map +1 -1
  106. package/dist/cjs/fields/Duration/index.native.js +3 -3
  107. package/dist/cjs/fields/Duration/index.native.js.map +1 -1
  108. package/dist/cjs/fields/FileUpload/filePickerTypes.native.js.map +1 -1
  109. package/dist/cjs/fields/FileUpload/index.native.js +2 -2
  110. package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
  111. package/dist/cjs/fields/FileUpload/nativePickerLoader.native.js.map +1 -1
  112. package/dist/cjs/fields/Geolocation/expoMapsLoader.native.js.map +1 -1
  113. package/dist/cjs/fields/Geolocation/geoValue.native.js.map +1 -1
  114. package/dist/cjs/fields/Geolocation/geocoder.native.js.map +1 -1
  115. package/dist/cjs/fields/Geolocation/geocoderContext.native.js.map +1 -1
  116. package/dist/cjs/fields/Geolocation/index.native.js +5 -5
  117. package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
  118. package/dist/cjs/fields/Geolocation/mapMath.native.js.map +1 -1
  119. package/dist/cjs/fields/Geolocation/mapTypes.native.js.map +1 -1
  120. package/dist/cjs/fields/Geolocation/nativeSearchFocus.native.js.map +1 -1
  121. package/dist/cjs/fields/Geolocation/photonGeocoder.native.js.map +1 -1
  122. package/dist/cjs/fields/Icon/drawnSet.native.js.map +1 -1
  123. package/dist/cjs/fields/Icon/index.native.js +3 -3
  124. package/dist/cjs/fields/Icon/index.native.js.map +1 -1
  125. package/dist/cjs/fields/ImageField/index.native.js +2 -2
  126. package/dist/cjs/fields/ImageField/index.native.js.map +1 -1
  127. package/dist/cjs/fields/Input/index.native.js +3 -3
  128. package/dist/cjs/fields/Input/index.native.js.map +1 -1
  129. package/dist/cjs/fields/MentionInput/index.native.js +3 -3
  130. package/dist/cjs/fields/MentionInput/index.native.js.map +1 -1
  131. package/dist/cjs/fields/OTPInput/index.native.js +3 -3
  132. package/dist/cjs/fields/OTPInput/index.native.js.map +1 -1
  133. package/dist/cjs/fields/OTPInput/otpCellCss.native.js.map +1 -1
  134. package/dist/cjs/fields/PhoneInput/dialCodes.native.js.map +1 -1
  135. package/dist/cjs/fields/PhoneInput/index.native.js +3 -3
  136. package/dist/cjs/fields/PhoneInput/index.native.js.map +1 -1
  137. package/dist/cjs/fields/PhoneInput/phone.native.js.map +1 -1
  138. package/dist/cjs/fields/PhoneInput/regionNames.native.js.map +1 -1
  139. package/dist/cjs/fields/Progress/index.native.js +1 -1
  140. package/dist/cjs/fields/Progress/index.native.js.map +1 -1
  141. package/dist/cjs/fields/RadioGroup/index.cjs +6 -1
  142. package/dist/cjs/fields/RadioGroup/index.native.js +7 -2
  143. package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
  144. package/dist/cjs/fields/Rating/index.native.js +1 -1
  145. package/dist/cjs/fields/Rating/index.native.js.map +1 -1
  146. package/dist/cjs/fields/Rating/ratingMath.native.js.map +1 -1
  147. package/dist/cjs/fields/Rating/types.native.js.map +1 -1
  148. package/dist/cjs/fields/SearchInput/index.native.js +2 -2
  149. package/dist/cjs/fields/SearchInput/index.native.js.map +1 -1
  150. package/dist/cjs/fields/Select/index.cjs +13 -2
  151. package/dist/cjs/fields/Select/index.native.js +26 -7
  152. package/dist/cjs/fields/Select/index.native.js.map +1 -1
  153. package/dist/cjs/fields/Signature/index.native.js +2 -2
  154. package/dist/cjs/fields/Signature/index.native.js.map +1 -1
  155. package/dist/cjs/fields/Slider/index.native.js +1 -1
  156. package/dist/cjs/fields/Slider/index.native.js.map +1 -1
  157. package/dist/cjs/fields/Slider/jumpMotion.native.js.map +1 -1
  158. package/dist/cjs/fields/Stepper/index.native.js +2 -2
  159. package/dist/cjs/fields/Stepper/index.native.js.map +1 -1
  160. package/dist/cjs/fields/Stepper/resolveStepperPlacement.native.js.map +1 -1
  161. package/dist/cjs/fields/Switch/footprint.native.js.map +1 -1
  162. package/dist/cjs/fields/Switch/index.cjs +22 -10
  163. package/dist/cjs/fields/Switch/index.native.js +30 -16
  164. package/dist/cjs/fields/Switch/index.native.js.map +1 -1
  165. package/dist/cjs/fields/TextArea/index.native.js +2 -2
  166. package/dist/cjs/fields/TextArea/index.native.js.map +1 -1
  167. package/dist/cjs/fields/TimePicker/catalog.native.js +3 -3
  168. package/dist/cjs/fields/TimePicker/catalog.native.js.map +1 -1
  169. package/dist/cjs/fields/TimePicker/index.native.js +2 -2
  170. package/dist/cjs/fields/TimePicker/parts.native.js +1 -1
  171. package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
  172. package/dist/cjs/fields/TimeRangeScrubber/geometry.native.js.map +1 -1
  173. package/dist/cjs/fields/TimeRangeScrubber/index.native.js +2 -2
  174. package/dist/cjs/fields/TimeRangeScrubber/index.native.js.map +1 -1
  175. package/dist/cjs/fields/ToggleGroup/index.native.js +1 -1
  176. package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
  177. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  178. package/dist/cjs/fields/nativeDateTime/dateTimePickerLoader.native.js.map +1 -1
  179. package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
  180. package/dist/cjs/fields/nativeSelect.native.js.map +1 -1
  181. package/dist/cjs/fields/selectedOrder.native.js.map +1 -1
  182. package/dist/cjs/formSpacing.native.js.map +1 -1
  183. package/dist/cjs/hooks/useFocusManagement.native.js.map +1 -1
  184. package/dist/cjs/index.native.js +45 -45
  185. package/dist/cjs/index.native.js.map +1 -1
  186. package/dist/cjs/keyboard/KeyboardDoneBar.native.js +1 -1
  187. package/dist/cjs/keyboard/chain.native.js.map +1 -1
  188. package/dist/cjs/keyboard/index.native.js.map +1 -1
  189. package/dist/cjs/keyboard/types.native.js.map +1 -1
  190. package/dist/cjs/requiredMarking.native.js.map +1 -1
  191. package/dist/cjs/shared/TimeColumn.native.js +1 -1
  192. package/dist/cjs/shared/TimeColumn.native.js.map +1 -1
  193. package/dist/cjs/shared/capabilityPreflight.native.js.map +1 -1
  194. package/dist/cjs/shared/choiceRowPress.cjs +46 -0
  195. package/dist/cjs/shared/choiceRowPress.native.js +50 -0
  196. package/dist/cjs/shared/choiceRowPress.native.js.map +1 -0
  197. package/dist/cjs/shared/colorRamps.native.js.map +1 -1
  198. package/dist/cjs/shared/devWarn.native.js.map +1 -1
  199. package/dist/cjs/shared/floatingList.native.js +1 -1
  200. package/dist/cjs/shared/floatingList.native.js.map +1 -1
  201. package/dist/cjs/shared/focusManagement.native.js.map +1 -1
  202. package/dist/cjs/shared/groupContext.native.js.map +1 -1
  203. package/dist/cjs/shared/i18n.native.js.map +1 -1
  204. package/dist/cjs/shared/instantMotion.native.js.map +1 -1
  205. package/dist/cjs/shared/nativeA11y.native.js.map +1 -1
  206. package/dist/cjs/shared/nativeAutoFocus.native.js.map +1 -1
  207. package/dist/cjs/shared/overlayStack.cjs +4 -0
  208. package/dist/cjs/shared/overlayStack.native.js +4 -0
  209. package/dist/cjs/shared/overlayStack.native.js.map +1 -1
  210. package/dist/cjs/shared/panelPortal.native.js.map +1 -1
  211. package/dist/cjs/shared/pickerTriggerContract.native.js.map +1 -1
  212. package/dist/cjs/shared/pressSlopProps.native.js.map +1 -1
  213. package/dist/cjs/shared/restoreRetries.native.js.map +1 -1
  214. package/dist/cjs/shared/t.native.js.map +1 -1
  215. package/dist/cjs/shared/tableCellContext.native.js.map +1 -1
  216. package/dist/cjs/shared/useDragScroll.native.js.map +1 -1
  217. package/dist/cjs/shared/useKeyboardInset.native.js.map +1 -1
  218. package/dist/cjs/shared/useSheetEscapeDismiss.native.js.map +1 -1
  219. package/dist/cjs/shared/useWheelIncrement.native.js.map +1 -1
  220. package/dist/cjs/shared/utils.native.js +1 -1
  221. package/dist/cjs/shared/utils.native.js.map +1 -1
  222. package/dist/cjs/shared/zIndex.native.js.map +1 -1
  223. package/dist/cjs/themedPrimitives.native.js.map +1 -1
  224. package/dist/cjs/types.native.js.map +1 -1
  225. package/dist/cjs/utils/LinkCell.native.js.map +1 -1
  226. package/dist/esm/AdaptivePopup/index.mjs +6 -2
  227. package/dist/esm/AdaptivePopup/index.mjs.map +1 -1
  228. package/dist/esm/AdaptivePopup/index.native.js +6 -2
  229. package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
  230. package/dist/esm/Button/index.mjs +3 -2
  231. package/dist/esm/Button/index.mjs.map +1 -1
  232. package/dist/esm/Button/index.native.js +3 -2
  233. package/dist/esm/Button/index.native.js.map +1 -1
  234. package/dist/esm/FloatingPanel/compactFace.mjs +11 -0
  235. package/dist/esm/FloatingPanel/compactFace.mjs.map +1 -0
  236. package/dist/esm/FloatingPanel/compactFace.native.js +12 -0
  237. package/dist/esm/FloatingPanel/compactFace.native.js.map +1 -0
  238. package/dist/esm/FloatingPanel/index.mjs +40 -11
  239. package/dist/esm/FloatingPanel/index.mjs.map +1 -1
  240. package/dist/esm/FloatingPanel/index.native.js +156 -8
  241. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  242. package/dist/esm/FloatingPanel/pointAnchor.mjs +22 -1
  243. package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
  244. package/dist/esm/FloatingPanel/pointAnchor.native.js +23 -1
  245. package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
  246. package/dist/esm/InputParts/index.mjs +13 -7
  247. package/dist/esm/InputParts/index.mjs.map +1 -1
  248. package/dist/esm/InputParts/index.native.js +13 -7
  249. package/dist/esm/InputParts/index.native.js.map +1 -1
  250. package/dist/esm/fields/Checkbox/Checkbox.mjs +24 -14
  251. package/dist/esm/fields/Checkbox/Checkbox.mjs.map +1 -1
  252. package/dist/esm/fields/Checkbox/Checkbox.native.js +38 -22
  253. package/dist/esm/fields/Checkbox/Checkbox.native.js.map +1 -1
  254. package/dist/esm/fields/RadioGroup/index.mjs +6 -1
  255. package/dist/esm/fields/RadioGroup/index.mjs.map +1 -1
  256. package/dist/esm/fields/RadioGroup/index.native.js +6 -1
  257. package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
  258. package/dist/esm/fields/Select/index.mjs +15 -4
  259. package/dist/esm/fields/Select/index.mjs.map +1 -1
  260. package/dist/esm/fields/Select/index.native.js +24 -5
  261. package/dist/esm/fields/Select/index.native.js.map +1 -1
  262. package/dist/esm/fields/Switch/index.mjs +22 -10
  263. package/dist/esm/fields/Switch/index.mjs.map +1 -1
  264. package/dist/esm/fields/Switch/index.native.js +28 -14
  265. package/dist/esm/fields/Switch/index.native.js.map +1 -1
  266. package/dist/esm/shared/choiceRowPress.mjs +20 -0
  267. package/dist/esm/shared/choiceRowPress.mjs.map +1 -0
  268. package/dist/esm/shared/choiceRowPress.native.js +21 -0
  269. package/dist/esm/shared/choiceRowPress.native.js.map +1 -0
  270. package/dist/esm/shared/overlayStack.mjs +4 -1
  271. package/dist/esm/shared/overlayStack.mjs.map +1 -1
  272. package/dist/esm/shared/overlayStack.native.js +4 -1
  273. package/dist/esm/shared/overlayStack.native.js.map +1 -1
  274. package/dist/jsx/AdaptivePopup/index.mjs +6 -2
  275. package/dist/jsx/AdaptivePopup/index.mjs.map +1 -1
  276. package/dist/jsx/AdaptivePopup/index.native.js +5 -1
  277. package/dist/jsx/AdaptivePopup/index.native.js.map +1 -1
  278. package/dist/jsx/Button/index.mjs +3 -2
  279. package/dist/jsx/Button/index.mjs.map +1 -1
  280. package/dist/jsx/Button/index.native.js +2 -1
  281. package/dist/jsx/Button/index.native.js.map +1 -1
  282. package/dist/jsx/FloatingPanel/compactFace.mjs +11 -0
  283. package/dist/jsx/FloatingPanel/compactFace.mjs.map +1 -0
  284. package/dist/jsx/FloatingPanel/compactFace.native.js +42 -0
  285. package/dist/jsx/FloatingPanel/compactFace.native.js.map +1 -0
  286. package/dist/jsx/FloatingPanel/index.mjs +40 -11
  287. package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
  288. package/dist/jsx/FloatingPanel/index.native.js +154 -6
  289. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  290. package/dist/jsx/FloatingPanel/pointAnchor.mjs +22 -1
  291. package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
  292. package/dist/jsx/FloatingPanel/pointAnchor.native.js +23 -0
  293. package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
  294. package/dist/jsx/InputParts/index.mjs +13 -7
  295. package/dist/jsx/InputParts/index.mjs.map +1 -1
  296. package/dist/jsx/InputParts/index.native.js +13 -7
  297. package/dist/jsx/InputParts/index.native.js.map +1 -1
  298. package/dist/jsx/fields/Checkbox/Checkbox.mjs +24 -14
  299. package/dist/jsx/fields/Checkbox/Checkbox.mjs.map +1 -1
  300. package/dist/jsx/fields/Checkbox/Checkbox.native.js +38 -22
  301. package/dist/jsx/fields/Checkbox/Checkbox.native.js.map +1 -1
  302. package/dist/jsx/fields/RadioGroup/index.mjs +6 -1
  303. package/dist/jsx/fields/RadioGroup/index.mjs.map +1 -1
  304. package/dist/jsx/fields/RadioGroup/index.native.js +6 -1
  305. package/dist/jsx/fields/RadioGroup/index.native.js.map +1 -1
  306. package/dist/jsx/fields/Select/index.mjs +15 -4
  307. package/dist/jsx/fields/Select/index.mjs.map +1 -1
  308. package/dist/jsx/fields/Select/index.native.js +22 -3
  309. package/dist/jsx/fields/Select/index.native.js.map +1 -1
  310. package/dist/jsx/fields/Switch/index.mjs +22 -10
  311. package/dist/jsx/fields/Switch/index.mjs.map +1 -1
  312. package/dist/jsx/fields/Switch/index.native.js +28 -14
  313. package/dist/jsx/fields/Switch/index.native.js.map +1 -1
  314. package/dist/jsx/index.native.js.map +1 -1
  315. package/dist/jsx/shared/choiceRowPress.mjs +20 -0
  316. package/dist/jsx/shared/choiceRowPress.mjs.map +1 -0
  317. package/dist/jsx/shared/choiceRowPress.native.js +50 -0
  318. package/dist/jsx/shared/choiceRowPress.native.js.map +1 -0
  319. package/dist/jsx/shared/overlayStack.mjs +4 -1
  320. package/dist/jsx/shared/overlayStack.mjs.map +1 -1
  321. package/dist/jsx/shared/overlayStack.native.js +4 -0
  322. package/dist/jsx/shared/overlayStack.native.js.map +1 -1
  323. package/package.json +8 -8
  324. package/src/AdaptivePopup/index.tsx +6 -1
  325. package/src/Button/Button.controlTiers.spec.tsx +29 -0
  326. package/src/Button/index.tsx +3 -1
  327. package/src/FloatingPanel/FloatingPanel.compactFace.spec.tsx +187 -0
  328. package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +183 -0
  329. package/src/FloatingPanel/compactFace.spec.ts +40 -0
  330. package/src/FloatingPanel/compactFace.ts +32 -0
  331. package/src/FloatingPanel/index.native.tsx +171 -4
  332. package/src/FloatingPanel/index.tsx +55 -11
  333. package/src/FloatingPanel/pointAnchor.spec.ts +80 -0
  334. package/src/FloatingPanel/pointAnchor.ts +52 -0
  335. package/src/InputParts/index.tsx +18 -7
  336. package/src/fields/Checkbox/Checkbox.native.spec.tsx +44 -0
  337. package/src/fields/Checkbox/Checkbox.tsx +31 -18
  338. package/src/fields/DatePicker/columnFit.spec.tsx +85 -0
  339. package/src/fields/RadioGroup/RadioGroup.native.spec.tsx +65 -0
  340. package/src/fields/RadioGroup/RadioGroup.stories.tsx +49 -0
  341. package/src/fields/RadioGroup/index.tsx +14 -2
  342. package/src/fields/Select/index.tsx +36 -6
  343. package/src/fields/Switch/Switch.rowPress.native.spec.tsx +69 -0
  344. package/src/fields/Switch/index.tsx +29 -11
  345. package/src/fields/pickerRows.spec.tsx +2 -0
  346. package/src/overlayGround.spec.ts +181 -0
  347. package/src/shared/choiceRowPress.ts +40 -0
  348. package/src/shared/overlayStack.tsx +5 -0
  349. package/types/AdaptivePopup/index.d.ts.map +1 -1
  350. package/types/Button/index.d.ts.map +1 -1
  351. package/types/FloatingPanel/compactFace.d.ts +21 -0
  352. package/types/FloatingPanel/compactFace.d.ts.map +1 -0
  353. package/types/FloatingPanel/index.d.ts +10 -0
  354. package/types/FloatingPanel/index.d.ts.map +1 -1
  355. package/types/FloatingPanel/index.native.d.ts +8 -1
  356. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  357. package/types/FloatingPanel/pointAnchor.d.ts +29 -0
  358. package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
  359. package/types/InputParts/index.d.ts.map +1 -1
  360. package/types/fields/Checkbox/Checkbox.d.ts.map +1 -1
  361. package/types/fields/RadioGroup/index.d.ts.map +1 -1
  362. package/types/fields/Select/index.d.ts.map +1 -1
  363. package/types/fields/Switch/index.d.ts.map +1 -1
  364. package/types/shared/choiceRowPress.d.ts +40 -0
  365. package/types/shared/choiceRowPress.d.ts.map +1 -0
  366. package/types/shared/overlayStack.d.ts +2 -0
  367. package/types/shared/overlayStack.d.ts.map +1 -1
@@ -0,0 +1,187 @@
1
+ /**
2
+ * FZ-7830: on a phone-width web a menu opened inside another overlay opens
3
+ * over its trigger, never as a second bottom sheet; at page level it keeps the
4
+ * sheet. Inside an overlay a collision slides the menu back on screen instead
5
+ * of taking LC-139's collision sheet.
6
+ */
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import { fireEvent, waitFor } from "@testing-library/react";
9
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
10
+ import { useState } from "react";
11
+ import { Button } from "../Button";
12
+ import { OverlayStackProvider } from "../shared/overlayStack";
13
+ import type { CompactFace } from "./compactFace";
14
+ import { FloatingPanel } from "./index";
15
+ import { panelViewportPadding } from "./useFloatingPanel";
16
+
17
+ const phone = { width: 390, height: 844 };
18
+ const triggerBounds = {
19
+ x: 20,
20
+ y: 240,
21
+ top: 240,
22
+ bottom: 284,
23
+ left: 20,
24
+ right: 140,
25
+ width: 120,
26
+ height: 44,
27
+ };
28
+
29
+ let originalWidth = 0;
30
+ let originalHeight = 0;
31
+ beforeEach(() => {
32
+ originalWidth = window.innerWidth;
33
+ originalHeight = window.innerHeight;
34
+ Object.defineProperty(window, "innerWidth", {
35
+ configurable: true,
36
+ writable: true,
37
+ value: phone.width,
38
+ });
39
+ Object.defineProperty(window, "innerHeight", {
40
+ configurable: true,
41
+ writable: true,
42
+ value: phone.height,
43
+ });
44
+ });
45
+ afterEach(() => {
46
+ vi.restoreAllMocks();
47
+ Object.defineProperty(window, "innerWidth", {
48
+ configurable: true,
49
+ writable: true,
50
+ value: originalWidth,
51
+ });
52
+ Object.defineProperty(window, "innerHeight", {
53
+ configurable: true,
54
+ writable: true,
55
+ value: originalHeight,
56
+ });
57
+ });
58
+
59
+ function measure(bounds = triggerBounds, panelWidth = 200, panelHeight = 180) {
60
+ const original = HTMLElement.prototype.getBoundingClientRect;
61
+ vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
62
+ function (this: HTMLElement) {
63
+ if (this.hasAttribute("data-fp-reference")) return { ...bounds, toJSON: () => ({}) };
64
+ return original.call(this);
65
+ },
66
+ );
67
+ vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockImplementation(
68
+ function (this: HTMLElement) {
69
+ return this.hasAttribute("data-fp-panel") ? panelWidth : 0;
70
+ },
71
+ );
72
+ vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockImplementation(
73
+ function (this: HTMLElement) {
74
+ return this.hasAttribute("data-fp-panel") ? panelHeight : 0;
75
+ },
76
+ );
77
+ }
78
+
79
+ function Menu({ face }: { face?: CompactFace }) {
80
+ const [open, setOpen] = useState(false);
81
+ return (
82
+ <FloatingPanel
83
+ open={open}
84
+ onOpenChange={setOpen}
85
+ compactFace={face}
86
+ triggerSizing="content"
87
+ trigger={<Button aria-label="Stack actions">⋯</Button>}
88
+ >
89
+ <Button>Stop</Button>
90
+ <Button>Restart</Button>
91
+ </FloatingPanel>
92
+ );
93
+ }
94
+
95
+ /** What a SheetModal, Dialog or AdaptivePopup puts around its content. */
96
+ function InSheet({ children }: { children: React.ReactNode }) {
97
+ return <OverlayStackProvider zIndex={100_000}>{children}</OverlayStackProvider>;
98
+ }
99
+
100
+ function open(result: ReturnType<typeof renderWithProviders>) {
101
+ fireEvent.click(result.getByRole("button", { name: "Stack actions" }), { detail: 0 });
102
+ }
103
+
104
+ describe("FloatingPanel compactFace on a 390pt web viewport (FZ-7830)", () => {
105
+ it("keeps the bottom sheet at page level for the auto face", async () => {
106
+ measure();
107
+ const result = renderWithProviders(<Menu face="auto" />);
108
+ open(result);
109
+ await result.findByTestId("floating-panel-sheet");
110
+ expect(result.queryByTestId("floating-panel-viewport")).toBeNull();
111
+ });
112
+
113
+ it("opens over the trigger inside an overlay for the auto face, never a second sheet", async () => {
114
+ measure();
115
+ const result = renderWithProviders(
116
+ <InSheet>
117
+ <Menu face="auto" />
118
+ </InSheet>,
119
+ );
120
+ open(result);
121
+ const panel = await result.findByTestId("floating-panel-viewport");
122
+ expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
123
+ await waitFor(() => expect(panel.style.top).toBe("240px"));
124
+ expect(panel.style.left).toBe("20px");
125
+ });
126
+
127
+ it("opens over the trigger at page level for the menu face", async () => {
128
+ measure();
129
+ const result = renderWithProviders(<Menu face="menu" />);
130
+ open(result);
131
+ const panel = await result.findByTestId("floating-panel-viewport");
132
+ expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
133
+ await waitFor(() => expect(panel.style.top).toBe("240px"));
134
+ });
135
+
136
+ it("leaves a panel without a face as a sheet, even inside an overlay", async () => {
137
+ measure();
138
+ const result = renderWithProviders(
139
+ <InSheet>
140
+ <Menu />
141
+ </InSheet>,
142
+ );
143
+ open(result);
144
+ await result.findByTestId("floating-panel-sheet");
145
+ expect(result.queryByTestId("floating-panel-viewport")).toBeNull();
146
+ });
147
+
148
+ it("slides a trailing ⋯ menu back inside the end edge instead of taking a collision sheet", async () => {
149
+ measure({ ...triggerBounds, x: 330, left: 330, right: 374, width: 44 });
150
+ const result = renderWithProviders(
151
+ <InSheet>
152
+ <Menu face="auto" />
153
+ </InSheet>,
154
+ );
155
+ open(result);
156
+ const panel = await result.findByTestId("floating-panel-viewport");
157
+ expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
158
+ await waitFor(() => expect(parseFloat(panel.style.left)).toBeLessThan(330));
159
+ expect(parseFloat(panel.style.left) + 200).toBeLessThanOrEqual(
160
+ phone.width - panelViewportPadding,
161
+ );
162
+ expect(panel.style.top).toBe("240px");
163
+ });
164
+
165
+ it("slides up from the bottom edge instead of taking a collision sheet", async () => {
166
+ measure({ ...triggerBounds, y: 800, top: 800, bottom: 844 });
167
+ const result = renderWithProviders(
168
+ <InSheet>
169
+ <Menu face="auto" />
170
+ </InSheet>,
171
+ );
172
+ open(result);
173
+ const panel = await result.findByTestId("floating-panel-viewport");
174
+ expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
175
+ await waitFor(() => expect(parseFloat(panel.style.top)).toBeLessThan(800));
176
+ expect(parseFloat(panel.style.top) + 180).toBeLessThanOrEqual(
177
+ phone.height - panelViewportPadding,
178
+ );
179
+ });
180
+
181
+ it("still takes LC-139's collision sheet at page level", async () => {
182
+ measure({ ...triggerBounds, y: 800, top: 800, bottom: 844 });
183
+ const result = renderWithProviders(<Menu face="menu" />);
184
+ open(result);
185
+ await result.findByTestId("floating-panel-sheet");
186
+ });
187
+ });
@@ -0,0 +1,183 @@
1
+ /**
2
+ * FZ-7830: on native a menu-faced FloatingPanel opens over its trigger (LC-112,
3
+ * the UIMenu pull-down) instead of as a bottom sheet, and an `auto` panel does
4
+ * so only inside another overlay, so a sheet never stacks on a sheet.
5
+ */
6
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
7
+ import { act } from "@testing-library/react";
8
+ import { useState, type ReactNode } from "react";
9
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
10
+ import { Text } from "tamagui";
11
+ import { OverlayStackProvider } from "../shared/overlayStack";
12
+ import type { CompactFace } from "./compactFace";
13
+ import { FloatingPanel } from "./index.native";
14
+ import { panelViewportPadding } from "./useFloatingPanel";
15
+
16
+ vi.mock("react-native-web", async (importOriginal) => {
17
+ const actual = await importOriginal<typeof import("react-native")>();
18
+ return {
19
+ ...actual,
20
+ ActionSheetIOS: undefined,
21
+ Modal: ({ children, visible }: { children: ReactNode; visible: boolean }) =>
22
+ visible ? children : null,
23
+ useWindowDimensions: () => ({ width: 390, height: 844, scale: 3, fontScale: 1 }),
24
+ };
25
+ });
26
+
27
+ let trigger = { x: 20, y: 240, width: 120, height: 44 };
28
+ beforeEach(() => {
29
+ vi.useFakeTimers();
30
+ trigger = { x: 20, y: 240, width: 120, height: 44 };
31
+ // RN-web's measureInWindow reads the node's client rect on a timer.
32
+ vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(() => ({
33
+ x: trigger.x,
34
+ y: trigger.y,
35
+ left: trigger.x,
36
+ top: trigger.y,
37
+ width: trigger.width,
38
+ height: trigger.height,
39
+ right: trigger.x + trigger.width,
40
+ bottom: trigger.y + trigger.height,
41
+ toJSON: () => ({}),
42
+ }));
43
+ });
44
+ afterEach(() => {
45
+ vi.restoreAllMocks();
46
+ vi.useRealTimers();
47
+ });
48
+
49
+ function Menu({ face }: { face?: CompactFace }) {
50
+ const [open, setOpen] = useState(false);
51
+ return (
52
+ <FloatingPanel
53
+ open={open}
54
+ onOpenChange={setOpen}
55
+ compactFace={face}
56
+ triggerSizing="content"
57
+ contentPadding="none"
58
+ trigger={<Text>Stack actions</Text>}
59
+ triggerA11y={{ label: "Stack actions" }}
60
+ >
61
+ <Text>Stop</Text>
62
+ <Text>Restart</Text>
63
+ </FloatingPanel>
64
+ );
65
+ }
66
+
67
+ /** What a native SheetModal's OverlayWindow puts around its content. */
68
+ function InSheet({ children }: { children: ReactNode }) {
69
+ return <OverlayStackProvider zIndex={100_000}>{children}</OverlayStackProvider>;
70
+ }
71
+
72
+ function tap(target: Element) {
73
+ act(() => {
74
+ target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
75
+ target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, button: 0 }));
76
+ target.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0 }));
77
+ });
78
+ // measureInWindow answers on the next tick.
79
+ act(() => {
80
+ vi.advanceTimersByTime(1);
81
+ });
82
+ }
83
+
84
+ /** RN-web parks onLayout on the node for a ResizeObserver happy-dom never fires. */
85
+ function layout(node: Element, width: number, height: number) {
86
+ const onLayout = (node as unknown as { __reactLayoutHandler?: (event: unknown) => void })
87
+ .__reactLayoutHandler;
88
+ expect(onLayout).toBeTypeOf("function");
89
+ act(() => {
90
+ onLayout?.({ nativeEvent: { layout: { x: 0, y: 0, width, height } } });
91
+ });
92
+ }
93
+
94
+ const menu = () => document.querySelector<HTMLElement>('[data-testid="floating-panel-menu"]');
95
+ const sheet = () => document.querySelector('[data-testid="floating-panel-sheet"]');
96
+ const echo = () => document.querySelector('[data-testid="floating-panel-menu-echo"]');
97
+
98
+ describe("FloatingPanel native menu face (FZ-7830)", () => {
99
+ it("opens the menu face over the trigger at page level, never a sheet", () => {
100
+ const result = renderWithProviders(<Menu face="menu" />);
101
+ tap(result.getByRole("button", { name: "Stack actions" }));
102
+ const panel = menu();
103
+ expect(panel).not.toBeNull();
104
+ expect(sheet()).toBeNull();
105
+ expect(panel?.style.opacity).toBe("0");
106
+ layout(panel as HTMLElement, 200, 180);
107
+ expect(panel?.style.left).toBe("20px");
108
+ expect(panel?.style.top).toBe("240px");
109
+ expect(panel?.style.opacity).toBe("1");
110
+ });
111
+
112
+ it("repeats the trigger as the first row, in the trigger's own box", () => {
113
+ const result = renderWithProviders(<Menu face="menu" />);
114
+ tap(result.getByRole("button", { name: "Stack actions" }));
115
+ const row = echo() as HTMLElement;
116
+ expect(row).not.toBeNull();
117
+ expect(row.textContent).toBe("Stack actions");
118
+ expect(row.style.height).toBe("44px");
119
+ expect(row.getAttribute("aria-expanded")).toBe("true");
120
+ });
121
+
122
+ it("closes when the repeated trigger row is pressed", () => {
123
+ const result = renderWithProviders(<Menu face="menu" />);
124
+ tap(result.getByRole("button", { name: "Stack actions" }));
125
+ act(() => {
126
+ vi.advanceTimersByTime(1000);
127
+ });
128
+ tap(echo() as HTMLElement);
129
+ expect(menu()).toBeNull();
130
+ });
131
+
132
+ it("slides a trailing ⋯ menu back inside the padded window", () => {
133
+ trigger = { x: 330, y: 240, width: 44, height: 44 };
134
+ const result = renderWithProviders(<Menu face="menu" />);
135
+ tap(result.getByRole("button", { name: "Stack actions" }));
136
+ const panel = menu() as HTMLElement;
137
+ layout(panel, 200, 180);
138
+ expect(panel.style.left).toBe(`${390 - panelViewportPadding - 200}px`);
139
+ expect(panel.style.top).toBe("240px");
140
+ });
141
+
142
+ it("slides up from the bottom edge instead of running under it", () => {
143
+ trigger = { x: 20, y: 800, width: 120, height: 44 };
144
+ const result = renderWithProviders(<Menu face="menu" />);
145
+ tap(result.getByRole("button", { name: "Stack actions" }));
146
+ const panel = menu() as HTMLElement;
147
+ layout(panel, 200, 180);
148
+ const top = parseFloat(panel.style.top);
149
+ expect(top + 180).toBeLessThanOrEqual(844 - panelViewportPadding);
150
+ expect(top).toBeLessThan(800);
151
+ });
152
+
153
+ it("keeps the sheet at page level for the auto face", () => {
154
+ const result = renderWithProviders(<Menu face="auto" />);
155
+ tap(result.getByRole("button", { name: "Stack actions" }));
156
+ expect(sheet()).not.toBeNull();
157
+ expect(menu()).toBeNull();
158
+ });
159
+
160
+ it("opens the auto face over the trigger inside another overlay, never a second sheet", () => {
161
+ const result = renderWithProviders(
162
+ <InSheet>
163
+ <Menu face="auto" />
164
+ </InSheet>,
165
+ );
166
+ tap(result.getByRole("button", { name: "Stack actions" }));
167
+ expect(sheet()).toBeNull();
168
+ const panel = menu() as HTMLElement;
169
+ layout(panel, 200, 180);
170
+ expect(panel.style.top).toBe("240px");
171
+ });
172
+
173
+ it("leaves a panel without a face as a sheet, even inside an overlay", () => {
174
+ const result = renderWithProviders(
175
+ <InSheet>
176
+ <Menu />
177
+ </InSheet>,
178
+ );
179
+ tap(result.getByRole("button", { name: "Stack actions" }));
180
+ expect(sheet()).not.toBeNull();
181
+ expect(menu()).toBeNull();
182
+ });
183
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { pickerCompactFace, resolveCompactFace, SHORT_LIST_MAX } from "./compactFace";
3
+
4
+ describe("resolveCompactFace (FZ-7830)", () => {
5
+ it("keeps the sheet by default, at page level and inside an overlay", () => {
6
+ expect(resolveCompactFace(undefined, false)).toBe("sheet");
7
+ expect(resolveCompactFace(undefined, true)).toBe("sheet");
8
+ expect(resolveCompactFace("sheet", true)).toBe("sheet");
9
+ });
10
+
11
+ it("opens a menu everywhere for the menu face", () => {
12
+ expect(resolveCompactFace("menu", false)).toBe("menu");
13
+ expect(resolveCompactFace("menu", true)).toBe("menu");
14
+ });
15
+
16
+ it("opens a menu only inside another overlay for the auto face", () => {
17
+ expect(resolveCompactFace("auto", false)).toBe("sheet");
18
+ expect(resolveCompactFace("auto", true)).toBe("menu");
19
+ });
20
+ });
21
+
22
+ describe("pickerCompactFace (FZ-7830)", () => {
23
+ it("opens a short list as a menu at every level", () => {
24
+ expect(SHORT_LIST_MAX).toBe(7);
25
+ expect(pickerCompactFace(1)).toBe("menu");
26
+ expect(pickerCompactFace(7)).toBe("menu");
27
+ expect(resolveCompactFace(pickerCompactFace(7), false)).toBe("menu");
28
+ });
29
+
30
+ it("keeps a long list's sheet at page level and opens it as a menu inside an overlay", () => {
31
+ expect(pickerCompactFace(8)).toBe("auto");
32
+ expect(resolveCompactFace(pickerCompactFace(8), false)).toBe("sheet");
33
+ expect(resolveCompactFace(pickerCompactFace(8), true)).toBe("menu");
34
+ });
35
+
36
+ it("keeps a searchable list's sheet at page level, however short", () => {
37
+ expect(pickerCompactFace(3, true)).toBe("auto");
38
+ expect(resolveCompactFace(pickerCompactFace(3, true), false)).toBe("sheet");
39
+ });
40
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * What a FloatingPanel becomes where it would otherwise be a bottom sheet:
3
+ * on native, and on the web at or below OVERLAY_BREAKPOINT.
4
+ *
5
+ * - `"sheet"`: the bottom sheet, wherever it opens.
6
+ * - `"menu"`: a menu over its trigger (LC-112) wherever it opens.
7
+ * - `"auto"`: the sheet at page level, the menu inside another overlay. A
8
+ * sheet over a sheet is the one thing a phone must never show (FZ-7830).
9
+ */
10
+ export type CompactFace = "sheet" | "menu" | "auto";
11
+
12
+ export function resolveCompactFace(
13
+ face: CompactFace | undefined,
14
+ insideOverlay: boolean,
15
+ ): "sheet" | "menu" {
16
+ if (face === "menu") return "menu";
17
+ if (face === "auto") return insideOverlay ? "menu" : "sheet";
18
+ return "sheet";
19
+ }
20
+
21
+ /** The longest list a picker opens as a menu over its trigger (FZ-7830). */
22
+ export const SHORT_LIST_MAX = 7;
23
+
24
+ /**
25
+ * A picker's face (FZ-7830, LC-112): a short list with no search opens as a
26
+ * menu over its trigger at every level, the iOS 14+ pull-down picker. A long or
27
+ * searchable list keeps the sheet at page level and opens as a scrolling menu
28
+ * inside another overlay.
29
+ */
30
+ export function pickerCompactFace(optionCount: number, searchable = false): CompactFace {
31
+ return !searchable && optionCount <= SHORT_LIST_MAX ? "menu" : "auto";
32
+ }
@@ -44,6 +44,13 @@
44
44
  * - Opening a Modal under the same finger can synthesize a backdrop press;
45
45
  * backdrop dismiss is armed a short delay after the present begins.
46
46
  *
47
+ * Menu face (`compactFace`, FZ-7830): instead of the sheet, a menu opens
48
+ * over the trigger (LC-112), its top start corner on the trigger's, the
49
+ * trigger echoed as its first row, sliding back inside the safe area where it
50
+ * would run off screen. DropdownMenu always opens this way (a UIMenu
51
+ * pull-down); Select opens this way for a short list, and for any list inside
52
+ * another overlay, so a sheet never stacks on a sheet.
53
+ *
47
54
  * ContextMenu (`openOn="contextmenu"`): long-press opens it. On a window wider
48
55
  * than OVERLAY_BREAKPOINT (a tablet) it opens at the long-press point, the
49
56
  * same free overlay the web draws at the pointer; a phone keeps the sheet,
@@ -83,12 +90,19 @@ import {
83
90
  motionDurationMs,
84
91
  } from "@multiplatform.one/theme";
85
92
  import { View, YStack } from "tamagui";
86
- import { useOverlayZIndex } from "../shared/overlayStack";
93
+ import { useInsideOverlay, useOverlayZIndex } from "../shared/overlayStack";
87
94
  import { OverlayWindow } from "../shared/OverlayWindow";
88
95
  import { t } from "../shared/t";
89
96
  import { useKeyboardInset } from "../shared/useKeyboardInset";
90
97
  import type { FloatingPanelProps } from "./index";
91
- import { placeAtPoint, type PanelBounds, type PanelPoint } from "./pointAnchor";
98
+ import { resolveCompactFace } from "./compactFace";
99
+ import {
100
+ placeAtPoint,
101
+ placeOverTrigger,
102
+ type PanelBounds,
103
+ type PanelPoint,
104
+ type TriggerRect,
105
+ } from "./pointAnchor";
92
106
  import { panelViewportPadding } from "./useFloatingPanel";
93
107
  import { TrailingActionOverlay, TriggerWithTrailingAction } from "./trailingAction";
94
108
 
@@ -142,11 +156,18 @@ export function FloatingPanel({
142
156
  triggerA11y,
143
157
  sheetFill,
144
158
  openOn = "press",
159
+ compactFace,
160
+ triggerRow,
161
+ triggerEcho = true,
145
162
  }: FloatingPanelProps) {
146
163
  // The panel floats over the page: it never inherits a painted ancestor.
147
164
  const { knobProps } = useResolvedKnobs({ painted: false });
148
165
  const windowZIndex = useOverlayZIndex("sheet");
149
- const children = <Surface painted={false}>{panelChildren}</Surface>;
166
+ const children = (
167
+ <Surface painted={false} elevated>
168
+ {panelChildren}
169
+ </Surface>
170
+ );
150
171
  const slideTransition = knobProps.motion.slide.transition;
151
172
  const enterDuration = motionDurationMs(slideTransition);
152
173
  const scrimDuration = motionDurationMs(knobProps.motion.scrim.transition);
@@ -181,6 +202,34 @@ export function FloatingPanel({
181
202
  );
182
203
  const isContextMenu = openOn === "contextmenu";
183
204
  const atPoint = isContextMenu && pressPoint !== null && windowWidth > OVERLAY_BREAKPOINT;
205
+ const insideOverlay = useInsideOverlay();
206
+ const asMenu = !isContextMenu && resolveCompactFace(compactFace, insideOverlay) === "menu";
207
+ const triggerRef = useRef<RNView | null>(null);
208
+ const [anchor, setAnchor] = useState<TriggerRect | null>(null);
209
+ // The trigger's window rect, read each time the menu opens: it may have
210
+ // moved (a scrolled sheet) since the last open.
211
+ useLayoutEffect(() => {
212
+ if (!asMenu || !open) {
213
+ setAnchor(null);
214
+ return;
215
+ }
216
+ const node = triggerRef.current as
217
+ | (RNView & {
218
+ measureInWindow?: (cb: (x: number, y: number, w: number, h: number) => void) => void;
219
+ })
220
+ | null;
221
+ let live = true;
222
+ if (typeof node?.measureInWindow === "function") {
223
+ node.measureInWindow((x, y, width, height) => {
224
+ if (live) setAnchor({ x, y, width, height });
225
+ });
226
+ } else {
227
+ setAnchor({ x: 0, y: 0, width: 0, height: 0 });
228
+ }
229
+ return () => {
230
+ live = false;
231
+ };
232
+ }, [asMenu, open]);
184
233
 
185
234
  const handleBackdropPress = useCallback(() => {
186
235
  if (backdropArmed) close();
@@ -473,6 +522,8 @@ export function FloatingPanel({
473
522
  return (
474
523
  <>
475
524
  <RNView
525
+ ref={triggerRef}
526
+ collapsable={false}
476
527
  style={{
477
528
  flexDirection: "row",
478
529
  alignItems: "center",
@@ -526,7 +577,29 @@ export function FloatingPanel({
526
577
  }}
527
578
  >
528
579
  <OverlayWindow zIndex={windowZIndex}>
529
- {atPoint && pressPoint ? (
580
+ {asMenu ? (
581
+ <TriggerMenuPanel
582
+ anchor={anchor}
583
+ bounds={{
584
+ top: topInset + panelViewportPadding,
585
+ left: (safeArea?.left ?? 0) + panelViewportPadding,
586
+ right: windowWidth - (safeArea?.right ?? 0) - panelViewportPadding,
587
+ bottom:
588
+ windowHeight -
589
+ Math.max(safeArea?.bottom ?? 0, keyboardInset) -
590
+ panelViewportPadding,
591
+ }}
592
+ echo={triggerEcho ? (triggerRow ?? trigger) : null}
593
+ echoLabel={triggerA11y?.label}
594
+ framePadding={framePadding}
595
+ frameGap={frameGap}
596
+ onBackdropPress={handleBackdropPress}
597
+ onClose={close}
598
+ >
599
+ {header}
600
+ {children}
601
+ </TriggerMenuPanel>
602
+ ) : atPoint && pressPoint ? (
530
603
  <PointPanel
531
604
  key={`${pressPoint.x},${pressPoint.y}`}
532
605
  point={pressPoint}
@@ -644,6 +717,100 @@ export function FloatingPanel({
644
717
  );
645
718
  }
646
719
 
720
+ /**
721
+ * The menu over its trigger (LC-112): measured once per open at its natural
722
+ * size, then placed on the trigger's window rect. The first row repeats the
723
+ * trigger in the trigger's own box, and pressing it closes the menu, as the
724
+ * web echo row does.
725
+ */
726
+ function TriggerMenuPanel({
727
+ anchor,
728
+ bounds,
729
+ echo,
730
+ echoLabel,
731
+ framePadding,
732
+ frameGap,
733
+ onBackdropPress,
734
+ onClose,
735
+ children,
736
+ }: {
737
+ anchor: TriggerRect | null;
738
+ bounds: PanelBounds;
739
+ echo: ReactNode;
740
+ echoLabel?: string;
741
+ framePadding: string;
742
+ frameGap: string | undefined;
743
+ onBackdropPress: () => void;
744
+ onClose: () => void;
745
+ children: ReactNode;
746
+ }) {
747
+ const { knobProps } = useResolvedKnobs({ painted: false });
748
+ const [size, setSize] = useState<{ width: number; height: number } | null>(null);
749
+ const placement =
750
+ anchor && size
751
+ ? placeOverTrigger({
752
+ trigger: anchor,
753
+ width: size.width,
754
+ height: size.height,
755
+ bounds,
756
+ rtl: I18nManager.isRTL,
757
+ })
758
+ : null;
759
+ return (
760
+ <RNView style={StyleSheet.absoluteFill}>
761
+ <Pressable
762
+ style={StyleSheet.absoluteFill}
763
+ onPress={onBackdropPress}
764
+ accessibilityRole="button"
765
+ accessibilityLabel={t("Close")}
766
+ />
767
+ <RNView
768
+ testID="floating-panel-menu"
769
+ onLayout={(e) => {
770
+ if (size) return;
771
+ const { width, height } = e.nativeEvent.layout;
772
+ if (width > 0 && height > 0) setSize({ width, height });
773
+ }}
774
+ style={{
775
+ position: "absolute",
776
+ left: placement?.left ?? bounds.left,
777
+ top: placement?.top ?? bounds.top,
778
+ minWidth: anchor?.width ?? 0,
779
+ maxWidth: bounds.right - bounds.left,
780
+ maxHeight: placement?.maxHeight ?? bounds.bottom - bounds.top,
781
+ opacity: placement ? 1 : 0,
782
+ }}
783
+ >
784
+ <YStack
785
+ {...knobProps.elevatedSurface}
786
+ flexShrink={1}
787
+ minHeight={0}
788
+ overflow="hidden"
789
+ onAccessibilityEscape={onClose}
790
+ >
791
+ {echo != null && anchor && anchor.height > 0 ? (
792
+ <Pressable
793
+ testID="floating-panel-menu-echo"
794
+ onPress={onClose}
795
+ accessibilityRole="button"
796
+ {...(echoLabel ? { accessibilityLabel: echoLabel } : undefined)}
797
+ aria-expanded
798
+ style={{ height: anchor.height, minWidth: anchor.width, justifyContent: "center" }}
799
+ >
800
+ <RNView pointerEvents="none">{echo}</RNView>
801
+ </Pressable>
802
+ ) : null}
803
+ <RNScrollView style={{ flexGrow: 0, flexShrink: 1 }} keyboardShouldPersistTaps="handled">
804
+ <YStack padding={framePadding} gap={frameGap}>
805
+ {children}
806
+ </YStack>
807
+ </RNScrollView>
808
+ </YStack>
809
+ </RNView>
810
+ </RNView>
811
+ );
812
+ }
813
+
647
814
  /**
648
815
  * The menu at the long-press point: measured once per open at its natural
649
816
  * size, then placed by the same rule as the web pointer menu. Later layouts