@multiplatform.one/forms 7.65.2 → 7.67.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/FloatingPanel/RegularPanel.cjs +164 -0
  2. package/dist/cjs/FloatingPanel/RegularPanel.native.js +169 -0
  3. package/dist/cjs/FloatingPanel/RegularPanel.native.js.map +1 -0
  4. package/dist/cjs/FloatingPanel/compactFace.cjs +6 -0
  5. package/dist/cjs/FloatingPanel/compactFace.native.js +6 -0
  6. package/dist/cjs/FloatingPanel/compactFace.native.js.map +1 -1
  7. package/dist/cjs/FloatingPanel/containingBlock.cjs +46 -0
  8. package/dist/cjs/FloatingPanel/containingBlock.native.js +51 -0
  9. package/dist/cjs/FloatingPanel/containingBlock.native.js.map +1 -0
  10. package/dist/cjs/FloatingPanel/index.cjs +2 -20
  11. package/dist/cjs/FloatingPanel/index.native.js +79 -45
  12. package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
  13. package/dist/cjs/FloatingPanel/pointAnchor.cjs +25 -0
  14. package/dist/cjs/FloatingPanel/pointAnchor.native.js +26 -0
  15. package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
  16. package/dist/cjs/FloatingPanel/regularPlacement.cjs +61 -0
  17. package/dist/cjs/FloatingPanel/regularPlacement.native.js +65 -0
  18. package/dist/cjs/FloatingPanel/regularPlacement.native.js.map +1 -0
  19. package/dist/cjs/fields/Combobox/index.cjs +8 -10
  20. package/dist/cjs/fields/Combobox/index.native.js +10 -10
  21. package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
  22. package/dist/cjs/fields/DatePicker/parts.cjs +1 -0
  23. package/dist/cjs/fields/DatePicker/parts.native.js +3 -0
  24. package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
  25. package/dist/cjs/fields/Input/index.cjs +5 -0
  26. package/dist/cjs/fields/Input/index.native.js +5 -0
  27. package/dist/cjs/fields/Input/index.native.js.map +1 -1
  28. package/dist/cjs/fields/Select/InlineChoices.cjs +3 -2
  29. package/dist/cjs/fields/Select/InlineChoices.native.js +5 -2
  30. package/dist/cjs/fields/Select/InlineChoices.native.js.map +1 -1
  31. package/dist/cjs/fields/Select/SheetPickerRow.cjs +154 -0
  32. package/dist/cjs/fields/Select/SheetPickerRow.native.js +165 -0
  33. package/dist/cjs/fields/Select/SheetPickerRow.native.js.map +1 -0
  34. package/dist/cjs/fields/Select/index.cjs +64 -5
  35. package/dist/cjs/fields/Select/index.native.js +86 -27
  36. package/dist/cjs/fields/Select/index.native.js.map +1 -1
  37. package/dist/cjs/fields/Select/shouldInlineChoices.cjs +14 -1
  38. package/dist/cjs/fields/Select/shouldInlineChoices.native.js +15 -1
  39. package/dist/cjs/fields/Select/shouldInlineChoices.native.js.map +1 -1
  40. package/dist/cjs/fields/Slider/index.cjs +96 -4
  41. package/dist/cjs/fields/Slider/index.native.js +103 -5
  42. package/dist/cjs/fields/Slider/index.native.js.map +1 -1
  43. package/dist/cjs/fields/Switch/index.cjs +71 -1
  44. package/dist/cjs/fields/Switch/index.native.js +72 -1
  45. package/dist/cjs/fields/Switch/index.native.js.map +1 -1
  46. package/dist/cjs/fields/TimePicker/parts.cjs +1 -0
  47. package/dist/cjs/fields/TimePicker/parts.native.js +1 -0
  48. package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
  49. package/dist/cjs/fields/ToggleGroup/index.cjs +67 -1
  50. package/dist/cjs/fields/ToggleGroup/index.native.js +75 -1
  51. package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
  52. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +34 -19
  53. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +44 -21
  54. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  55. package/dist/cjs/fields/nativeDateTime/sheetKeyboard.cjs +54 -0
  56. package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js +57 -0
  57. package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -0
  58. package/dist/cjs/index.cjs +11 -0
  59. package/dist/cjs/index.native.js +11 -0
  60. package/dist/cjs/index.native.js.map +1 -1
  61. package/dist/cjs/shared/overlaySizeClass.cjs +42 -0
  62. package/dist/cjs/shared/overlaySizeClass.native.js +45 -0
  63. package/dist/cjs/shared/overlaySizeClass.native.js.map +1 -0
  64. package/dist/cjs/shared/sheetNavigation.cjs +76 -0
  65. package/dist/cjs/shared/sheetNavigation.native.js +129 -0
  66. package/dist/cjs/shared/sheetNavigation.native.js.map +1 -0
  67. package/dist/cjs/shared/systemControlModules.cjs +34 -0
  68. package/dist/cjs/shared/systemControlModules.gnome.cjs +34 -0
  69. package/dist/cjs/shared/systemControlModules.gnome.native.js +37 -0
  70. package/dist/cjs/shared/systemControlModules.gnome.native.js.map +1 -0
  71. package/dist/cjs/shared/systemControlModules.native.js +77 -0
  72. package/dist/cjs/shared/systemControlModules.native.js.map +1 -0
  73. package/dist/cjs/shared/systemControls.cjs +47 -0
  74. package/dist/cjs/shared/systemControls.native.js +51 -0
  75. package/dist/cjs/shared/systemControls.native.js.map +1 -0
  76. package/dist/esm/FloatingPanel/RegularPanel.mjs +139 -0
  77. package/dist/esm/FloatingPanel/RegularPanel.mjs.map +1 -0
  78. package/dist/esm/FloatingPanel/RegularPanel.native.js +141 -0
  79. package/dist/esm/FloatingPanel/RegularPanel.native.js.map +1 -0
  80. package/dist/esm/FloatingPanel/compactFace.mjs +5 -1
  81. package/dist/esm/FloatingPanel/compactFace.mjs.map +1 -1
  82. package/dist/esm/FloatingPanel/compactFace.native.js +5 -1
  83. package/dist/esm/FloatingPanel/compactFace.native.js.map +1 -1
  84. package/dist/esm/FloatingPanel/containingBlock.mjs +21 -0
  85. package/dist/esm/FloatingPanel/containingBlock.mjs.map +1 -0
  86. package/dist/esm/FloatingPanel/containingBlock.native.js +23 -0
  87. package/dist/esm/FloatingPanel/containingBlock.native.js.map +1 -0
  88. package/dist/esm/FloatingPanel/index.mjs +1 -19
  89. package/dist/esm/FloatingPanel/index.mjs.map +1 -1
  90. package/dist/esm/FloatingPanel/index.native.js +83 -49
  91. package/dist/esm/FloatingPanel/index.native.js.map +1 -1
  92. package/dist/esm/FloatingPanel/pointAnchor.mjs +25 -1
  93. package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
  94. package/dist/esm/FloatingPanel/pointAnchor.native.js +26 -1
  95. package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
  96. package/dist/esm/FloatingPanel/regularPlacement.mjs +35 -0
  97. package/dist/esm/FloatingPanel/regularPlacement.mjs.map +1 -0
  98. package/dist/esm/FloatingPanel/regularPlacement.native.js +36 -0
  99. package/dist/esm/FloatingPanel/regularPlacement.native.js.map +1 -0
  100. package/dist/esm/fields/Combobox/index.mjs +9 -11
  101. package/dist/esm/fields/Combobox/index.mjs.map +1 -1
  102. package/dist/esm/fields/Combobox/index.native.js +11 -11
  103. package/dist/esm/fields/Combobox/index.native.js.map +1 -1
  104. package/dist/esm/fields/DatePicker/parts.mjs +1 -0
  105. package/dist/esm/fields/DatePicker/parts.mjs.map +1 -1
  106. package/dist/esm/fields/DatePicker/parts.native.js +3 -0
  107. package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
  108. package/dist/esm/fields/Input/index.mjs +6 -1
  109. package/dist/esm/fields/Input/index.mjs.map +1 -1
  110. package/dist/esm/fields/Input/index.native.js +6 -1
  111. package/dist/esm/fields/Input/index.native.js.map +1 -1
  112. package/dist/esm/fields/Select/InlineChoices.mjs +3 -2
  113. package/dist/esm/fields/Select/InlineChoices.mjs.map +1 -1
  114. package/dist/esm/fields/Select/InlineChoices.native.js +5 -2
  115. package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
  116. package/dist/esm/fields/Select/SheetPickerRow.mjs +129 -0
  117. package/dist/esm/fields/Select/SheetPickerRow.mjs.map +1 -0
  118. package/dist/esm/fields/Select/SheetPickerRow.native.js +137 -0
  119. package/dist/esm/fields/Select/SheetPickerRow.native.js.map +1 -0
  120. package/dist/esm/fields/Select/index.mjs +66 -7
  121. package/dist/esm/fields/Select/index.mjs.map +1 -1
  122. package/dist/esm/fields/Select/index.native.js +88 -29
  123. package/dist/esm/fields/Select/index.native.js.map +1 -1
  124. package/dist/esm/fields/Select/shouldInlineChoices.mjs +12 -1
  125. package/dist/esm/fields/Select/shouldInlineChoices.mjs.map +1 -1
  126. package/dist/esm/fields/Select/shouldInlineChoices.native.js +13 -1
  127. package/dist/esm/fields/Select/shouldInlineChoices.native.js.map +1 -1
  128. package/dist/esm/fields/Slider/index.mjs +96 -4
  129. package/dist/esm/fields/Slider/index.mjs.map +1 -1
  130. package/dist/esm/fields/Slider/index.native.js +103 -5
  131. package/dist/esm/fields/Slider/index.native.js.map +1 -1
  132. package/dist/esm/fields/Switch/index.mjs +71 -1
  133. package/dist/esm/fields/Switch/index.mjs.map +1 -1
  134. package/dist/esm/fields/Switch/index.native.js +72 -1
  135. package/dist/esm/fields/Switch/index.native.js.map +1 -1
  136. package/dist/esm/fields/TimePicker/parts.mjs +1 -0
  137. package/dist/esm/fields/TimePicker/parts.mjs.map +1 -1
  138. package/dist/esm/fields/TimePicker/parts.native.js +1 -0
  139. package/dist/esm/fields/TimePicker/parts.native.js.map +1 -1
  140. package/dist/esm/fields/ToggleGroup/index.mjs +67 -1
  141. package/dist/esm/fields/ToggleGroup/index.mjs.map +1 -1
  142. package/dist/esm/fields/ToggleGroup/index.native.js +75 -1
  143. package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
  144. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +36 -21
  145. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  146. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +46 -23
  147. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  148. package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs +21 -0
  149. package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -0
  150. package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js +21 -0
  151. package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -0
  152. package/dist/esm/index.mjs +3 -1
  153. package/dist/esm/index.mjs.map +1 -1
  154. package/dist/esm/index.native.js +3 -1
  155. package/dist/esm/index.native.js.map +1 -1
  156. package/dist/esm/shared/overlaySizeClass.mjs +14 -0
  157. package/dist/esm/shared/overlaySizeClass.mjs.map +1 -0
  158. package/dist/esm/shared/overlaySizeClass.native.js +14 -0
  159. package/dist/esm/shared/overlaySizeClass.native.js.map +1 -0
  160. package/dist/esm/shared/sheetNavigation.mjs +47 -0
  161. package/dist/esm/shared/sheetNavigation.mjs.map +1 -0
  162. package/dist/esm/shared/sheetNavigation.native.js +97 -0
  163. package/dist/esm/shared/sheetNavigation.native.js.map +1 -0
  164. package/dist/esm/shared/systemControlModules.gnome.mjs +8 -0
  165. package/dist/esm/shared/systemControlModules.gnome.mjs.map +1 -0
  166. package/dist/esm/shared/systemControlModules.gnome.native.js +8 -0
  167. package/dist/esm/shared/systemControlModules.gnome.native.js.map +1 -0
  168. package/dist/esm/shared/systemControlModules.mjs +8 -0
  169. package/dist/esm/shared/systemControlModules.mjs.map +1 -0
  170. package/dist/esm/shared/systemControlModules.native.js +47 -0
  171. package/dist/esm/shared/systemControlModules.native.js.map +1 -0
  172. package/dist/esm/shared/systemControls.mjs +19 -0
  173. package/dist/esm/shared/systemControls.mjs.map +1 -0
  174. package/dist/esm/shared/systemControls.native.js +20 -0
  175. package/dist/esm/shared/systemControls.native.js.map +1 -0
  176. package/dist/jsx/FloatingPanel/RegularPanel.mjs +139 -0
  177. package/dist/jsx/FloatingPanel/RegularPanel.mjs.map +1 -0
  178. package/dist/jsx/FloatingPanel/RegularPanel.native.js +169 -0
  179. package/dist/jsx/FloatingPanel/RegularPanel.native.js.map +1 -0
  180. package/dist/jsx/FloatingPanel/compactFace.mjs +5 -1
  181. package/dist/jsx/FloatingPanel/compactFace.mjs.map +1 -1
  182. package/dist/jsx/FloatingPanel/compactFace.native.js +6 -0
  183. package/dist/jsx/FloatingPanel/compactFace.native.js.map +1 -1
  184. package/dist/jsx/FloatingPanel/containingBlock.mjs +21 -0
  185. package/dist/jsx/FloatingPanel/containingBlock.mjs.map +1 -0
  186. package/dist/jsx/FloatingPanel/containingBlock.native.js +51 -0
  187. package/dist/jsx/FloatingPanel/containingBlock.native.js.map +1 -0
  188. package/dist/jsx/FloatingPanel/index.mjs +1 -19
  189. package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
  190. package/dist/jsx/FloatingPanel/index.native.js +79 -45
  191. package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
  192. package/dist/jsx/FloatingPanel/pointAnchor.mjs +25 -1
  193. package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
  194. package/dist/jsx/FloatingPanel/pointAnchor.native.js +26 -0
  195. package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
  196. package/dist/jsx/FloatingPanel/regularPlacement.mjs +35 -0
  197. package/dist/jsx/FloatingPanel/regularPlacement.mjs.map +1 -0
  198. package/dist/jsx/FloatingPanel/regularPlacement.native.js +65 -0
  199. package/dist/jsx/FloatingPanel/regularPlacement.native.js.map +1 -0
  200. package/dist/jsx/fields/Combobox/index.mjs +9 -11
  201. package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
  202. package/dist/jsx/fields/Combobox/index.native.js +10 -10
  203. package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
  204. package/dist/jsx/fields/DatePicker/parts.mjs +1 -0
  205. package/dist/jsx/fields/DatePicker/parts.mjs.map +1 -1
  206. package/dist/jsx/fields/DatePicker/parts.native.js +3 -0
  207. package/dist/jsx/fields/DatePicker/parts.native.js.map +1 -1
  208. package/dist/jsx/fields/Input/index.mjs +6 -1
  209. package/dist/jsx/fields/Input/index.mjs.map +1 -1
  210. package/dist/jsx/fields/Input/index.native.js +5 -0
  211. package/dist/jsx/fields/Input/index.native.js.map +1 -1
  212. package/dist/jsx/fields/Select/InlineChoices.mjs +3 -2
  213. package/dist/jsx/fields/Select/InlineChoices.mjs.map +1 -1
  214. package/dist/jsx/fields/Select/InlineChoices.native.js +5 -2
  215. package/dist/jsx/fields/Select/InlineChoices.native.js.map +1 -1
  216. package/dist/jsx/fields/Select/SheetPickerRow.mjs +129 -0
  217. package/dist/jsx/fields/Select/SheetPickerRow.mjs.map +1 -0
  218. package/dist/jsx/fields/Select/SheetPickerRow.native.js +165 -0
  219. package/dist/jsx/fields/Select/SheetPickerRow.native.js.map +1 -0
  220. package/dist/jsx/fields/Select/index.mjs +66 -7
  221. package/dist/jsx/fields/Select/index.mjs.map +1 -1
  222. package/dist/jsx/fields/Select/index.native.js +86 -27
  223. package/dist/jsx/fields/Select/index.native.js.map +1 -1
  224. package/dist/jsx/fields/Select/shouldInlineChoices.mjs +12 -1
  225. package/dist/jsx/fields/Select/shouldInlineChoices.mjs.map +1 -1
  226. package/dist/jsx/fields/Select/shouldInlineChoices.native.js +15 -1
  227. package/dist/jsx/fields/Select/shouldInlineChoices.native.js.map +1 -1
  228. package/dist/jsx/fields/Slider/index.mjs +96 -4
  229. package/dist/jsx/fields/Slider/index.mjs.map +1 -1
  230. package/dist/jsx/fields/Slider/index.native.js +103 -5
  231. package/dist/jsx/fields/Slider/index.native.js.map +1 -1
  232. package/dist/jsx/fields/Switch/index.mjs +71 -1
  233. package/dist/jsx/fields/Switch/index.mjs.map +1 -1
  234. package/dist/jsx/fields/Switch/index.native.js +72 -1
  235. package/dist/jsx/fields/Switch/index.native.js.map +1 -1
  236. package/dist/jsx/fields/TimePicker/parts.mjs +1 -0
  237. package/dist/jsx/fields/TimePicker/parts.mjs.map +1 -1
  238. package/dist/jsx/fields/TimePicker/parts.native.js +1 -0
  239. package/dist/jsx/fields/TimePicker/parts.native.js.map +1 -1
  240. package/dist/jsx/fields/ToggleGroup/index.mjs +67 -1
  241. package/dist/jsx/fields/ToggleGroup/index.mjs.map +1 -1
  242. package/dist/jsx/fields/ToggleGroup/index.native.js +75 -1
  243. package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
  244. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +36 -21
  245. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  246. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +44 -21
  247. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  248. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs +21 -0
  249. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -0
  250. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js +57 -0
  251. package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -0
  252. package/dist/jsx/index.js +3 -1
  253. package/dist/jsx/index.js.map +1 -1
  254. package/dist/jsx/index.mjs +3 -1
  255. package/dist/jsx/index.mjs.map +1 -1
  256. package/dist/jsx/index.native.js +11 -0
  257. package/dist/jsx/index.native.js.map +1 -1
  258. package/dist/jsx/shared/overlaySizeClass.mjs +14 -0
  259. package/dist/jsx/shared/overlaySizeClass.mjs.map +1 -0
  260. package/dist/jsx/shared/overlaySizeClass.native.js +45 -0
  261. package/dist/jsx/shared/overlaySizeClass.native.js.map +1 -0
  262. package/dist/jsx/shared/sheetNavigation.mjs +47 -0
  263. package/dist/jsx/shared/sheetNavigation.mjs.map +1 -0
  264. package/dist/jsx/shared/sheetNavigation.native.js +129 -0
  265. package/dist/jsx/shared/sheetNavigation.native.js.map +1 -0
  266. package/dist/jsx/shared/systemControlModules.gnome.mjs +8 -0
  267. package/dist/jsx/shared/systemControlModules.gnome.mjs.map +1 -0
  268. package/dist/jsx/shared/systemControlModules.gnome.native.js +37 -0
  269. package/dist/jsx/shared/systemControlModules.gnome.native.js.map +1 -0
  270. package/dist/jsx/shared/systemControlModules.mjs +8 -0
  271. package/dist/jsx/shared/systemControlModules.mjs.map +1 -0
  272. package/dist/jsx/shared/systemControlModules.native.js +77 -0
  273. package/dist/jsx/shared/systemControlModules.native.js.map +1 -0
  274. package/dist/jsx/shared/systemControls.mjs +19 -0
  275. package/dist/jsx/shared/systemControls.mjs.map +1 -0
  276. package/dist/jsx/shared/systemControls.native.js +51 -0
  277. package/dist/jsx/shared/systemControls.native.js.map +1 -0
  278. package/package.json +16 -8
  279. package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +73 -3
  280. package/src/FloatingPanel/FloatingPanel.regularWidth.native.spec.tsx +193 -0
  281. package/src/FloatingPanel/RegularPanel.tsx +159 -0
  282. package/src/FloatingPanel/compactFace.spec.ts +24 -1
  283. package/src/FloatingPanel/compactFace.ts +16 -0
  284. package/src/FloatingPanel/containingBlock.ts +32 -0
  285. package/src/FloatingPanel/index.native.tsx +80 -35
  286. package/src/FloatingPanel/index.tsx +1 -33
  287. package/src/FloatingPanel/pointAnchor.spec.ts +29 -0
  288. package/src/FloatingPanel/pointAnchor.ts +30 -0
  289. package/src/FloatingPanel/regularPlacement.spec.ts +65 -0
  290. package/src/FloatingPanel/regularPlacement.ts +40 -0
  291. package/src/fields/Combobox/Combobox.regularWidth.native.spec.tsx +94 -0
  292. package/src/fields/Combobox/index.tsx +11 -34
  293. package/src/fields/DatePicker/parts.tsx +3 -0
  294. package/src/fields/DatePicker/touchWeb.spec.tsx +13 -0
  295. package/src/fields/Input/Input.formName.spec.tsx +53 -0
  296. package/src/fields/Input/index.tsx +6 -1
  297. package/src/fields/Select/InlineChoices.tsx +6 -3
  298. package/src/fields/Select/SheetPickerRow.tsx +160 -0
  299. package/src/fields/Select/index.tsx +67 -5
  300. package/src/fields/Select/shouldInlineChoices.spec.ts +21 -1
  301. package/src/fields/Select/shouldInlineChoices.ts +26 -0
  302. package/src/fields/Slider/Slider.system.native.spec.tsx +168 -0
  303. package/src/fields/Slider/index.tsx +166 -30
  304. package/src/fields/Switch/Switch.system.native.spec.tsx +162 -0
  305. package/src/fields/Switch/index.tsx +98 -0
  306. package/src/fields/TimePicker/parts.tsx +1 -0
  307. package/src/fields/ToggleGroup/ToggleGroup.system.native.spec.tsx +192 -0
  308. package/src/fields/ToggleGroup/index.tsx +111 -0
  309. package/src/fields/nativeDateTime/OsDateTimeSheet.presentation.spec.tsx +167 -0
  310. package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +36 -18
  311. package/src/fields/nativeDateTime/sheetKeyboard.spec.ts +60 -0
  312. package/src/fields/nativeDateTime/sheetKeyboard.ts +45 -0
  313. package/src/index.ts +15 -0
  314. package/src/shared/overlaySizeClass.spec.ts +27 -0
  315. package/src/shared/overlaySizeClass.ts +36 -0
  316. package/src/shared/sheetNavigation.ts +82 -0
  317. package/src/shared/systemControlModules.gnome.ts +10 -0
  318. package/src/shared/systemControlModules.native.spec.ts +52 -0
  319. package/src/shared/systemControlModules.native.ts +66 -0
  320. package/src/shared/systemControlModules.ts +10 -0
  321. package/src/shared/systemControls.ts +70 -0
  322. package/types/FloatingPanel/RegularPanel.d.ts +29 -0
  323. package/types/FloatingPanel/RegularPanel.d.ts.map +1 -0
  324. package/types/FloatingPanel/compactFace.d.ts +12 -0
  325. package/types/FloatingPanel/compactFace.d.ts.map +1 -1
  326. package/types/FloatingPanel/containingBlock.d.ts +14 -0
  327. package/types/FloatingPanel/containingBlock.d.ts.map +1 -0
  328. package/types/FloatingPanel/index.d.ts.map +1 -1
  329. package/types/FloatingPanel/index.native.d.ts +6 -1
  330. package/types/FloatingPanel/index.native.d.ts.map +1 -1
  331. package/types/FloatingPanel/pointAnchor.d.ts +12 -0
  332. package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
  333. package/types/FloatingPanel/regularPlacement.d.ts +20 -0
  334. package/types/FloatingPanel/regularPlacement.d.ts.map +1 -0
  335. package/types/fields/Combobox/index.d.ts.map +1 -1
  336. package/types/fields/DatePicker/parts.d.ts.map +1 -1
  337. package/types/fields/Input/index.d.ts.map +1 -1
  338. package/types/fields/Select/InlineChoices.d.ts.map +1 -1
  339. package/types/fields/Select/SheetPickerRow.d.ts +23 -0
  340. package/types/fields/Select/SheetPickerRow.d.ts.map +1 -0
  341. package/types/fields/Select/index.d.ts.map +1 -1
  342. package/types/fields/Select/shouldInlineChoices.d.ts +15 -0
  343. package/types/fields/Select/shouldInlineChoices.d.ts.map +1 -1
  344. package/types/fields/Slider/index.d.ts +7 -1
  345. package/types/fields/Slider/index.d.ts.map +1 -1
  346. package/types/fields/Switch/index.d.ts +7 -1
  347. package/types/fields/Switch/index.d.ts.map +1 -1
  348. package/types/fields/TimePicker/parts.d.ts.map +1 -1
  349. package/types/fields/ToggleGroup/index.d.ts +8 -1
  350. package/types/fields/ToggleGroup/index.d.ts.map +1 -1
  351. package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts.map +1 -1
  352. package/types/fields/nativeDateTime/sheetKeyboard.d.ts +20 -0
  353. package/types/fields/nativeDateTime/sheetKeyboard.d.ts.map +1 -0
  354. package/types/index.d.ts +4 -0
  355. package/types/index.d.ts.map +1 -1
  356. package/types/shared/overlaySizeClass.d.ts +24 -0
  357. package/types/shared/overlaySizeClass.d.ts.map +1 -0
  358. package/types/shared/sheetNavigation.d.ts +31 -0
  359. package/types/shared/sheetNavigation.d.ts.map +1 -0
  360. package/types/shared/systemControlModules.d.ts +5 -0
  361. package/types/shared/systemControlModules.d.ts.map +1 -0
  362. package/types/shared/systemControlModules.gnome.d.ts +5 -0
  363. package/types/shared/systemControlModules.gnome.d.ts.map +1 -0
  364. package/types/shared/systemControlModules.native.d.ts +20 -0
  365. package/types/shared/systemControlModules.native.d.ts.map +1 -0
  366. package/types/shared/systemControls.d.ts +53 -0
  367. package/types/shared/systemControls.d.ts.map +1 -0
@@ -0,0 +1,192 @@
1
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
2
+ import { createDefaultThemeConfig } from "@multiplatform.one/theme";
3
+ import { useForm } from "@tanstack/react-form";
4
+ import { cleanup, fireEvent, render } from "@testing-library/react";
5
+ import type { ReactElement } from "react";
6
+ import { TamaguiProvider } from "tamagui";
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import { Form } from "../../Form";
9
+ import { ToggleGroup } from "./index";
10
+
11
+ const system = vi.hoisted(() => ({
12
+ os: "ios" as "ios" | "android" | null,
13
+ installed: true,
14
+ }));
15
+
16
+ // UISegmentedControl as a probe: it shows the props it got and selects a
17
+ // segment through onChange, the way a tap on a segment does.
18
+ const SegmentedProbe = vi.hoisted(
19
+ () =>
20
+ function SegmentedProbe(props: Record<string, any>) {
21
+ return (
22
+ <div
23
+ data-testid="system-segmented"
24
+ data-selected={props.selectedIndex}
25
+ data-enabled={String(props.enabled)}
26
+ data-tint={props.tintColor}
27
+ data-ink={props.activeFontStyle?.color}
28
+ data-appearance={props.appearance}
29
+ data-label={props.accessibilityLabel ?? ""}
30
+ >
31
+ {props.values.map((value: string, index: number) => (
32
+ <button
33
+ key={value}
34
+ type="button"
35
+ onClick={() => props.onChange?.({ nativeEvent: { selectedSegmentIndex: index } })}
36
+ >
37
+ {value}
38
+ </button>
39
+ ))}
40
+ </div>
41
+ );
42
+ },
43
+ );
44
+
45
+ vi.mock("../../shared/systemControls", async (importOriginal) => ({
46
+ ...(await importOriginal<typeof import("../../shared/systemControls")>()),
47
+ systemControlPlatform: () => system.os,
48
+ }));
49
+
50
+ vi.mock("../../shared/systemControlModules", () => ({
51
+ loadSystemSlider: () => null,
52
+ loadSystemSegmentedControl: () => (system.installed ? SegmentedProbe : null),
53
+ }));
54
+
55
+ const mpoTheme = createDefaultThemeConfig();
56
+ const renderInScheme = (ui: ReactElement, scheme: "light" | "dark") =>
57
+ render(
58
+ <TamaguiProvider config={mpoTheme.tamagui} defaultTheme={scheme} disableInjectCSS>
59
+ {ui}
60
+ </TamaguiProvider>,
61
+ );
62
+
63
+ afterEach(cleanup);
64
+ beforeEach(() => {
65
+ system.os = "ios";
66
+ system.installed = true;
67
+ });
68
+
69
+ const views = [
70
+ { label: "Day", value: "day" },
71
+ { label: "Week", value: "week" },
72
+ { label: "Month", value: "month" },
73
+ ];
74
+
75
+ const literal = (value: string | null) => Boolean(value) && !value!.startsWith("$");
76
+ const segmented = (result: ReturnType<typeof renderWithProviders>) =>
77
+ result.queryByTestId("system-segmented");
78
+
79
+ function FormToggleGroup({ onSubmit }: { onSubmit: (value: { view: string }) => void }) {
80
+ const form = useForm({
81
+ defaultValues: { view: "day" },
82
+ onSubmit: ({ value }) => onSubmit(value),
83
+ });
84
+ return (
85
+ <Form form={form}>
86
+ <ToggleGroup name="view" label="View" options={views} />
87
+ <button type="button" onClick={() => form.handleSubmit()}>
88
+ Save
89
+ </button>
90
+ </Form>
91
+ );
92
+ }
93
+
94
+ // LC-266 PLATFORM-ERGONOMICS (FZ-8119). On iOS a short single choice is
95
+ // UISegmentedControl when the binary carries the module, painted with the
96
+ // LC-131 selected tint and ink. Without it, the MPO segmented control.
97
+ describe("ToggleGroup on iOS", () => {
98
+ it.each(["light", "dark"] as const)(
99
+ "renders UISegmentedControl with the %s theme's selected tint, ink and scheme",
100
+ (scheme) => {
101
+ const result = renderInScheme(
102
+ <ToggleGroup label="View" options={views} defaultValue="week" />,
103
+ scheme,
104
+ );
105
+ const view = result.getByTestId("system-segmented");
106
+ expect(view.dataset).toMatchObject({ selected: "1", enabled: "true", label: "View" });
107
+ expect(literal(view.getAttribute("data-tint"))).toBe(true);
108
+ expect(literal(view.getAttribute("data-ink"))).toBe(true);
109
+ expect(view.dataset.appearance).toBe(scheme);
110
+ },
111
+ );
112
+
113
+ it("selects uncontrolled and reports the option value", () => {
114
+ const onChange = vi.fn();
115
+ const result = renderWithProviders(
116
+ <ToggleGroup options={views} defaultValue="day" onChange={onChange} />,
117
+ );
118
+ fireEvent.click(result.getByText("Month"));
119
+ expect(onChange).toHaveBeenCalledExactlyOnceWith("month");
120
+ expect(segmented(result)!.dataset.selected).toBe("2");
121
+ });
122
+
123
+ it("follows a controlled value", () => {
124
+ const result = renderWithProviders(<ToggleGroup options={views} value="day" />);
125
+ fireEvent.click(result.getByText("Week"));
126
+ expect(segmented(result)!.dataset.selected).toBe("0");
127
+ result.rerender(<ToggleGroup options={views} value="month" />);
128
+ expect(segmented(result)!.dataset.selected).toBe("2");
129
+ });
130
+
131
+ it("writes the form field it is bound to", async () => {
132
+ const onSubmit = vi.fn();
133
+ const result = renderWithProviders(<FormToggleGroup onSubmit={onSubmit} />);
134
+ expect(segmented(result)!.dataset.selected).toBe("0");
135
+ fireEvent.click(result.getByText("Week"));
136
+ fireEvent.click(result.getByText("Save"));
137
+ await vi.waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ view: "week" }));
138
+ });
139
+
140
+ it("disables the control and never changes a disabled or read-only value", () => {
141
+ const onChange = vi.fn();
142
+ const result = renderWithProviders(
143
+ <>
144
+ <ToggleGroup
145
+ options={views}
146
+ value="day"
147
+ onChange={onChange}
148
+ disabled
149
+ disabledReason="Locked"
150
+ />
151
+ <ToggleGroup options={views} value="day" onChange={onChange} readOnly />
152
+ </>,
153
+ );
154
+ const [disabledView, readOnlyView] = result.getAllByTestId("system-segmented");
155
+ expect(disabledView!.dataset.enabled).toBe("false");
156
+ expect(readOnlyView!.dataset.enabled).toBe("true");
157
+ for (const week of result.getAllByText("Week")) fireEvent.click(week);
158
+ expect(onChange).not.toHaveBeenCalled();
159
+ });
160
+
161
+ it("falls back to the MPO control when the binary lacks the module", () => {
162
+ system.installed = false;
163
+ const result = renderWithProviders(<ToggleGroup options={views} defaultValue="day" />);
164
+ expect(segmented(result)).toBeNull();
165
+ expect(result.getByText("Week")).toBeTruthy();
166
+ });
167
+
168
+ it("keeps the MPO control for multi-select, icons, a disabled segment, six segments and native={false}", () => {
169
+ const six = ["a", "b", "c", "d", "e", "f"].map((value) => ({ label: value, value }));
170
+ const result = renderWithProviders(
171
+ <>
172
+ <ToggleGroup type="multiple" options={views} defaultValue={["day"]} />
173
+ <ToggleGroup options={[{ ...views[0]!, icon: <span>i</span> }, views[1]!]} />
174
+ <ToggleGroup
175
+ options={[views[0]!, { ...views[1]!, disabled: true, disabledReason: "Locked" }]}
176
+ />
177
+ <ToggleGroup options={six} />
178
+ <ToggleGroup options={views} native={false} />
179
+ </>,
180
+ );
181
+ expect(segmented(result)).toBeNull();
182
+ });
183
+
184
+ it("keeps the MPO control on Android, web and GNOME", () => {
185
+ for (const os of ["android", null] as const) {
186
+ system.os = os;
187
+ const result = renderWithProviders(<ToggleGroup options={views} defaultValue="day" />);
188
+ expect(segmented(result)).toBeNull();
189
+ result.unmount();
190
+ }
191
+ });
192
+ });
@@ -33,6 +33,13 @@ import {
33
33
  } from "../../shared/colorRamps";
34
34
  import { warnBareDisabled } from "../../shared/devWarn";
35
35
  import { choiceItemA11y, composeA11yName } from "../../shared/nativeA11y";
36
+ import { loadSystemSegmentedControl } from "../../shared/systemControlModules";
37
+ import {
38
+ type SystemSegmentedControlComponent,
39
+ systemControlPlatform,
40
+ useSystemControlScheme,
41
+ useThemeLiteral,
42
+ } from "../../shared/systemControls";
36
43
  import {
37
44
  getElevationWrapperProps,
38
45
  getFieldError,
@@ -100,6 +107,69 @@ function ToggleItemWithElevation({
100
107
  return <ToggleItemFrame {...props} {...groupedRadius} />;
101
108
  }
102
109
 
110
+ /** HIG: a segmented control holds about five segments at most. */
111
+ const SYSTEM_SEGMENT_LIMIT = 5;
112
+
113
+ /**
114
+ * LC-266: UISegmentedControl, so iOS draws its own segmented control (iOS 26
115
+ * glass included) with the LC-131 selected paint: the soft accent tint behind
116
+ * the chosen segment and the accent ink on its label. It follows the app's
117
+ * scheme, not the OS's. It spans the row, as segmented controls in iOS forms do.
118
+ */
119
+ function SystemSegmentedControl({
120
+ Component,
121
+ id,
122
+ options,
123
+ value,
124
+ controlled,
125
+ disabled,
126
+ readOnly,
127
+ accessibilityLabel,
128
+ accessibilityHint,
129
+ onSelect,
130
+ }: {
131
+ Component: SystemSegmentedControlComponent;
132
+ id: string;
133
+ options: ToggleGroupOption[];
134
+ value: string | undefined;
135
+ controlled: boolean;
136
+ disabled?: boolean;
137
+ readOnly?: boolean;
138
+ accessibilityLabel?: string;
139
+ accessibilityHint?: string;
140
+ onSelect: (value: string) => void;
141
+ }) {
142
+ const literal = useThemeLiteral();
143
+ const appearance = useSystemControlScheme();
144
+ const [uncontrolledValue, setUncontrolledValue] = useState(value);
145
+ const current = controlled ? value : uncontrolledValue;
146
+ return (
147
+ <View
148
+ alignSelf="stretch"
149
+ {...(readOnly ? { pointerEvents: "none" as const } : undefined)}
150
+ data-mp-system-control="segmented"
151
+ >
152
+ <Component
153
+ id={id}
154
+ values={options.map((option) => option.label)}
155
+ selectedIndex={options.findIndex((option) => option.value === current)}
156
+ enabled={!disabled}
157
+ appearance={appearance}
158
+ tintColor={literal("$selectedBackground")}
159
+ activeFontStyle={{ color: literal("$selectedColor") }}
160
+ accessibilityLabel={accessibilityLabel}
161
+ accessibilityHint={accessibilityHint}
162
+ onChange={(event) => {
163
+ const next = options[event.nativeEvent.selectedSegmentIndex]?.value;
164
+ if (next === undefined || disabled || readOnly) return;
165
+ if (!controlled) setUncontrolledValue(next);
166
+ onSelect(next);
167
+ }}
168
+ />
169
+ </View>
170
+ );
171
+ }
172
+
103
173
  export interface ToggleGroupOption {
104
174
  label: string;
105
175
  value: string;
@@ -177,6 +247,13 @@ export interface ToggleGroupProps {
177
247
  "aria-labelledby"?: string;
178
248
  /** Extra spoken context for the group (native `accessibilityHint`). */
179
249
  accessibilityHint?: string;
250
+ /**
251
+ * LC-266: on iOS a single-choice group of two to five text segments, none
252
+ * disabled, renders UISegmentedControl when the binary carries
253
+ * `@react-native-segmented-control/segmented-control`. `false` keeps the MPO
254
+ * control there. Android keeps the MPO control, which is Material 3's form.
255
+ */
256
+ native?: boolean;
180
257
  }
181
258
 
182
259
  export function ToggleGroup({
@@ -207,6 +284,7 @@ export function ToggleGroup({
207
284
  accessibilityHint,
208
285
  "aria-label": ariaLabel,
209
286
  "aria-labelledby": ariaLabelledBy,
287
+ native = true,
210
288
  }: ToggleGroupProps) {
211
289
  // Canonical `onChange` and the alias `onValueChange` both fire from the
212
290
  // single emit site below (same reconciliation as Switch).
@@ -289,6 +367,18 @@ export function ToggleGroup({
289
367
  const index = uniqueItemReasons.indexOf(reason.trim());
290
368
  return index >= 0 ? `${id}-item-reason-${index}` : undefined;
291
369
  };
370
+ const SystemSegmented =
371
+ native &&
372
+ type === "single" &&
373
+ orientation === "horizontal" &&
374
+ !children &&
375
+ options &&
376
+ options.length >= 2 &&
377
+ options.length <= SYSTEM_SEGMENT_LIMIT &&
378
+ options.every((option) => !option.icon && !option.disabled) &&
379
+ systemControlPlatform() === "ios"
380
+ ? loadSystemSegmentedControl()
381
+ : null;
292
382
  const optionDisabledByValue = (val: string) =>
293
383
  Boolean(options?.find((o) => o.value === val)?.disabled);
294
384
 
@@ -527,6 +617,27 @@ export function ToggleGroup({
527
617
  ) => {
528
618
  const emptyValue = type === "multiple" ? [] : "";
529
619
 
620
+ if (SystemSegmented && options) {
621
+ return (
622
+ <SystemSegmentedControl
623
+ Component={SystemSegmented}
624
+ id={id}
625
+ options={options}
626
+ value={typeof resolvedValue === "string" ? resolvedValue : undefined}
627
+ controlled={isControlled}
628
+ disabled={disabled}
629
+ readOnly={readOnly}
630
+ accessibilityLabel={groupA11yName ?? (typeof label === "string" ? label : undefined)}
631
+ accessibilityHint={accessibilityHint}
632
+ onSelect={(val) => {
633
+ handleChange?.(val);
634
+ handleValueChange(val);
635
+ handleBlur?.();
636
+ }}
637
+ />
638
+ );
639
+ }
640
+
530
641
  const toggleProps: any = {
531
642
  // LC-71: the ring rides the focused segment, never the group. A
532
643
  // container ring is square over the rounded frame and paints on mouse
@@ -0,0 +1,167 @@
1
+ /**
2
+ * What the OS date/time sheet asks the native picker for. Every MPO date,
3
+ * time and datetime field on iOS and Android opens through this sheet, the
4
+ * Frappe `Date` field included (FZ-8000 retired its private copy).
5
+ *
6
+ * - iOS `date` opens the iOS 14+ calendar (`display="inline"`), not the legacy
7
+ * wheel; `time` keeps the wheel.
8
+ * - iOS `datetime` runs calendar → wheel across the sheet's Next button and
9
+ * commits one moment built from both halves.
10
+ * - The iOS sheet ends above the keyboard and the calendar shrinks to fit
11
+ * (MPO-64).
12
+ * - Android keeps `display="default"`, the Material dialog, and runs its own
13
+ * date-then-time sequence.
14
+ *
15
+ * The suite runs on web, so `react-native` resolves to `react-native-web`;
16
+ * the picker is recorded rather than rendered and the platform flags are mocked.
17
+ */
18
+
19
+ import { describe, it, expect, vi, beforeEach } from "vitest";
20
+
21
+ const mockState = vi.hoisted(() => ({
22
+ os: "ios" as "ios" | "android",
23
+ /** Props of every mounted picker, newest last. */
24
+ picks: [] as Record<string, unknown>[],
25
+ keyboardInset: 0,
26
+ }));
27
+
28
+ vi.mock("../../shared/useKeyboardInset", () => ({
29
+ useKeyboardInset: () => mockState.keyboardInset,
30
+ }));
31
+
32
+ // jsdom lays nothing out, so react-native-web reports a 0pt window and the
33
+ // keyboard cap would floor the picker on both sides of the comparison.
34
+ vi.mock("react-native-web", async (importOriginal) => ({
35
+ ...(await importOriginal<typeof import("react-native")>()),
36
+ useWindowDimensions: () => ({ width: 390, height: 844, scale: 3, fontScale: 1 }),
37
+ }));
38
+
39
+ vi.mock("./dateTimePickerLoader", () => ({
40
+ loadDateTimePicker: () => (props: Record<string, unknown>) => {
41
+ mockState.picks.push(props);
42
+ return null;
43
+ },
44
+ }));
45
+
46
+ vi.mock("@multiplatform.one/platform", async (importOriginal) => {
47
+ const actual = (await importOriginal()) as Record<string, unknown>;
48
+ const real = actual.platform as Record<string, unknown>;
49
+ return {
50
+ ...actual,
51
+ platform: new Proxy(
52
+ {},
53
+ {
54
+ get(_target, prop) {
55
+ if (prop === "isIos") return mockState.os === "ios";
56
+ if (prop === "isAndroid") return mockState.os === "android";
57
+ return real[prop as string];
58
+ },
59
+ },
60
+ ),
61
+ };
62
+ });
63
+
64
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
65
+ import { act, cleanup, fireEvent, waitFor } from "@testing-library/react";
66
+ import { OsDateTimeSheet, type OsDateTimeMode } from "./OsDateTimeSheet";
67
+
68
+ const norm = (value: string) => value.replace(/\s+/gu, " ").trim();
69
+
70
+ async function press(text: string) {
71
+ let target: Element | null = null;
72
+ await waitFor(() => {
73
+ const node = Array.from(document.body.querySelectorAll("*")).find(
74
+ (el) => el.children.length === 0 && norm(el.textContent ?? "") === norm(text),
75
+ );
76
+ if (!node) throw new Error(`no element with text "${text}"`);
77
+ target = node.closest('button, [role="button"]') ?? node;
78
+ });
79
+ fireEvent.click(target!);
80
+ }
81
+
82
+ const lastPick = () => mockState.picks[mockState.picks.length - 1];
83
+
84
+ function open(mode: OsDateTimeMode, value: Date, onConfirm = vi.fn()) {
85
+ renderWithProviders(
86
+ <OsDateTimeSheet open mode={mode} value={value} onCancel={vi.fn()} onConfirm={onConfirm} />,
87
+ );
88
+ return onConfirm;
89
+ }
90
+
91
+ beforeEach(() => {
92
+ cleanup();
93
+ mockState.picks.length = 0;
94
+ mockState.os = "ios";
95
+ mockState.keyboardInset = 0;
96
+ });
97
+
98
+ describe("OS date/time sheet — iOS presentation", () => {
99
+ it("a date opens the calendar, not the legacy wheel", () => {
100
+ open("date", new Date(2026, 2, 15));
101
+ expect(lastPick().mode).toBe("date");
102
+ expect(lastPick().display).toBe("inline");
103
+ expect(["light", "dark"]).toContain(lastPick().themeVariant);
104
+ });
105
+
106
+ it("shrinks the calendar when the keyboard is up so the sheet can sit above it", () => {
107
+ open("date", new Date(2026, 2, 15));
108
+ const resting = (lastPick().style as { height: number }).height;
109
+ expect(resting).toBe(360);
110
+ cleanup();
111
+ mockState.picks.length = 0;
112
+ mockState.keyboardInset = 420;
113
+ open("date", new Date(2026, 2, 15));
114
+ const raised = (lastPick().style as { height: number }).height;
115
+ expect(raised).toBeLessThan(resting);
116
+ });
117
+
118
+ it("a time keeps the wheel", () => {
119
+ open("time", new Date(2026, 2, 15, 9, 0));
120
+ expect(lastPick().mode).toBe("time");
121
+ expect(lastPick().display).toBe("spinner");
122
+ });
123
+
124
+ it("a datetime runs calendar then wheel, and commits both halves", async () => {
125
+ const onConfirm = open("datetime", new Date(2026, 2, 15, 14, 30));
126
+ expect(lastPick().mode).toBe("date");
127
+ expect(lastPick().display).toBe("inline");
128
+
129
+ await press("Next");
130
+ await waitFor(() => expect(lastPick().mode).toBe("time"));
131
+ expect(lastPick().display).toBe("spinner");
132
+
133
+ await press("Done");
134
+ expect(onConfirm).toHaveBeenCalledTimes(1);
135
+ expect(onConfirm).toHaveBeenCalledWith(new Date(2026, 2, 15, 14, 30));
136
+ });
137
+ });
138
+
139
+ describe("OS date/time sheet — Android presentation", () => {
140
+ it("keeps the Material dialog and mounts inline, without the iOS sheet", () => {
141
+ mockState.os = "android";
142
+ open("date", new Date(2026, 2, 15));
143
+ expect(lastPick().mode).toBe("date");
144
+ expect(lastPick().display).toBe("default");
145
+ expect(document.body.textContent).not.toContain("Done");
146
+ });
147
+
148
+ it("a datetime advances from date to time after the first commit", async () => {
149
+ mockState.os = "android";
150
+ const onConfirm = open("datetime", new Date(2026, 2, 15, 14, 30));
151
+ expect(lastPick().mode).toBe("date");
152
+
153
+ const onPickerChange = lastPick().onChange as (event: unknown, date?: Date) => void;
154
+ await act(async () => {
155
+ onPickerChange({ type: "set" }, new Date(2026, 2, 20, 0, 0));
156
+ });
157
+ await waitFor(() => expect(lastPick().mode).toBe("time"));
158
+ expect(onConfirm).not.toHaveBeenCalled();
159
+
160
+ const onTimeChange = lastPick().onChange as (event: unknown, date?: Date) => void;
161
+ await act(async () => {
162
+ onTimeChange({ type: "set" }, new Date(2026, 0, 1, 9, 45));
163
+ });
164
+ expect(onConfirm).toHaveBeenCalledTimes(1);
165
+ expect(onConfirm).toHaveBeenCalledWith(new Date(2026, 2, 20, 9, 45, 0));
166
+ });
167
+ });
@@ -10,14 +10,25 @@
10
10
  * does a missing `@react-native-community/datetimepicker` (an optional peer,
11
11
  * loaded through `loadDateTimePicker`).
12
12
  */
13
- import { useCallback, useState } from "react";
14
- import { Modal, Pressable, StyleSheet, useColorScheme, Platform } from "react-native";
13
+ import { useCallback, useContext, useState } from "react";
14
+ import {
15
+ Modal,
16
+ Pressable,
17
+ StyleSheet,
18
+ useColorScheme,
19
+ useWindowDimensions,
20
+ View as RNView,
21
+ } from "react-native";
22
+ import { platform } from "@multiplatform.one/platform";
23
+ import { SafeAreaInsetsContext, initialWindowMetrics } from "react-native-safe-area-context";
15
24
  import { useResolvedKnobs, Text } from "@multiplatform.one/theme";
16
25
  import { View, XStack, YStack, useTheme, useThemeName } from "tamagui";
17
26
  import { formCommonColors } from "../../shared/colorRamps";
18
27
  import { t } from "../../shared/t";
19
28
  import { type DateTimePickerEvent, loadDateTimePicker } from "./dateTimePickerLoader";
20
29
  import { osDateTimeIosDisplay } from "./osDateTimeIosDisplay";
30
+ import { datePickerHeight, dateSheetBottomPadding, dateSheetMaxHeight } from "./sheetKeyboard";
31
+ import { useKeyboardInset } from "../../shared/useKeyboardInset";
21
32
 
22
33
  export type OsDateTimeMode = "date" | "time" | "datetime";
23
34
 
@@ -52,7 +63,12 @@ export function OsDateTimeSheet({
52
63
  const themeVariant = useNativePickerScheme();
53
64
  const theme = useTheme();
54
65
  const accentColor = theme.accentColor?.val;
55
- const isIOS = Platform.OS === "ios";
66
+ const isIOS = platform.isIos;
67
+ // MPO-64: the sheet ends ABOVE the keyboard and the picker shrinks to fit.
68
+ const keyboardInset = useKeyboardInset();
69
+ const { height: windowHeight } = useWindowDimensions();
70
+ const topInset = useContext(SafeAreaInsetsContext)?.top ?? initialWindowMetrics?.insets.top ?? 0;
71
+ const maxHeight = dateSheetMaxHeight(windowHeight, keyboardInset, topInset);
56
72
  const [showTimePicker, setShowTimePicker] = useState(false);
57
73
  const [temp, setTemp] = useState(value);
58
74
  const pickerMode: "date" | "time" =
@@ -172,21 +188,25 @@ export function OsDateTimeSheet({
172
188
 
173
189
  return (
174
190
  <Modal transparent animationType="fade" visible={open} onRequestClose={onCancel}>
175
- <Pressable style={styles.modalOverlay} onPress={onCancel}>
176
- <View
177
- pointerEvents="none"
178
- position="absolute"
179
- top={0}
180
- right={0}
181
- bottom={0}
182
- left={0}
183
- backgroundColor="$shadow6"
184
- />
191
+ {/*
192
+ A plain padded column, not KeyboardAvoidingView: inside a Modal the
193
+ latter pads the container while the sheet keeps its own height, so the
194
+ sheet overflows off the top instead of shrinking (MPO-64).
195
+ */}
196
+ <RNView style={[styles.modalOverlay, { paddingBottom: keyboardInset }]}>
197
+ <Pressable style={StyleSheet.absoluteFill} onPress={onCancel}>
198
+ <View pointerEvents="none" flex={1} backgroundColor="$shadow6" />
199
+ </Pressable>
185
200
  <Pressable onPress={(e) => e.stopPropagation()}>
186
201
  <YStack
202
+ testID="os-date-time-sheet"
187
203
  {...knobProps.elevatedSurface}
188
204
  borderBottomLeftRadius={0}
189
205
  borderBottomRightRadius={0}
206
+ maxHeight={maxHeight}
207
+ flexShrink={1}
208
+ minHeight={0}
209
+ paddingBottom={dateSheetBottomPadding(keyboardInset, isIOS)}
190
210
  >
191
211
  <XStack justifyContent="space-between" {...knobProps.panelPadding}>
192
212
  <Pressable onPress={onCancel} accessibilityRole="button">
@@ -202,7 +222,7 @@ export function OsDateTimeSheet({
202
222
  </Pressable>
203
223
  )}
204
224
  </XStack>
205
- <YStack {...knobProps.panelPadding}>
225
+ <YStack {...knobProps.panelPadding} flexShrink={1} minHeight={0}>
206
226
  <DateTimePicker
207
227
  value={temp}
208
228
  mode={pickerMode}
@@ -212,12 +232,12 @@ export function OsDateTimeSheet({
212
232
  onChange={handleIOSChange}
213
233
  minimumDate={mode !== "time" ? minDate : undefined}
214
234
  maximumDate={mode !== "time" ? maxDate : undefined}
215
- style={pickerMode === "time" ? styles.wheel : styles.calendar}
235
+ style={{ width: "100%", height: datePickerHeight(pickerMode, maxHeight) }}
216
236
  />
217
237
  </YStack>
218
238
  </YStack>
219
239
  </Pressable>
220
- </Pressable>
240
+ </RNView>
221
241
  </Modal>
222
242
  );
223
243
  }
@@ -227,6 +247,4 @@ const styles = StyleSheet.create({
227
247
  flex: 1,
228
248
  justifyContent: "flex-end",
229
249
  },
230
- wheel: { height: 216 },
231
- calendar: { height: 360 },
232
250
  });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * MPO-64 leftover: the OS date/time iOS sheet must use the same keyboard
3
+ * cap as FloatingPanel / SheetModal. KeyboardAvoidingView cannot shrink a
4
+ * fixed-height calendar, so the cap is the only thing that keeps the sheet
5
+ * above the suggestion strip.
6
+ */
7
+ import { describe, expect, it } from "vitest";
8
+ import {
9
+ datePickerHeight,
10
+ dateSheetBottomPadding,
11
+ dateSheetMaxHeight,
12
+ DATE_PICKER_CALENDAR_HEIGHT,
13
+ DATE_SHEET_BACKDROP_STRIP,
14
+ DATE_SHEET_MIN_HEIGHT,
15
+ } from "./sheetKeyboard";
16
+
17
+ describe("dateSheetMaxHeight", () => {
18
+ it("uses the 85% detent when the keyboard is down", () => {
19
+ expect(dateSheetMaxHeight(800, 0, 47)).toBe(Math.round(800 * 0.85));
20
+ });
21
+
22
+ it("caps to the space above the keyboard, less the 48pt backdrop strip", () => {
23
+ expect(dateSheetMaxHeight(800, 400, 0)).toBe(800 - 400 - DATE_SHEET_BACKDROP_STRIP);
24
+ expect(dateSheetMaxHeight(800, 400, 0)).toBeLessThan(dateSheetMaxHeight(800, 0, 0));
25
+ });
26
+
27
+ it("keeps the backdrop strip below the status bar, not inside it", () => {
28
+ const windowHeight = 874;
29
+ const keyboardInset = 335;
30
+ const topInset = 62;
31
+ const sheetTop =
32
+ windowHeight - keyboardInset - dateSheetMaxHeight(windowHeight, keyboardInset, topInset);
33
+ expect(sheetTop - topInset).toBe(DATE_SHEET_BACKDROP_STRIP);
34
+ });
35
+
36
+ it("never collapses below the minimum sheet height", () => {
37
+ expect(dateSheetMaxHeight(800, 700, 47)).toBe(DATE_SHEET_MIN_HEIGHT);
38
+ });
39
+ });
40
+
41
+ describe("datePickerHeight", () => {
42
+ it("keeps the calendar grid when the sheet is tall enough", () => {
43
+ expect(datePickerHeight("date", 700)).toBe(DATE_PICKER_CALENDAR_HEIGHT);
44
+ });
45
+
46
+ it("shrinks the calendar so the sheet can sit above the keyboard", () => {
47
+ const maxHeight = dateSheetMaxHeight(800, 400, 47);
48
+ const height = datePickerHeight("date", maxHeight);
49
+ expect(height).toBeLessThan(DATE_PICKER_CALENDAR_HEIGHT);
50
+ expect(height).toBeLessThanOrEqual(maxHeight);
51
+ });
52
+ });
53
+
54
+ describe("dateSheetBottomPadding", () => {
55
+ it("drops the home-indicator inset while the keyboard covers it", () => {
56
+ expect(dateSheetBottomPadding(0, true)).toBe(24);
57
+ expect(dateSheetBottomPadding(336, true)).toBe(8);
58
+ expect(dateSheetBottomPadding(0, false)).toBe(16);
59
+ });
60
+ });