@multiplatform.one/frappe-ui 6.6.0 → 7.0.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 (516) hide show
  1. package/dist/cjs/FrappeAssignment.cjs +2 -0
  2. package/dist/cjs/FrappeAssignment.native.js +2 -0
  3. package/dist/cjs/FrappeAssignment.native.js.map +1 -1
  4. package/dist/cjs/FrappeCalendar.cjs +5 -7
  5. package/dist/cjs/FrappeCalendar.native.js +5 -9
  6. package/dist/cjs/FrappeCalendar.native.js.map +1 -1
  7. package/dist/cjs/FrappeChartCard.cjs +2 -0
  8. package/dist/cjs/FrappeChartCard.native.js +2 -0
  9. package/dist/cjs/FrappeChartCard.native.js.map +1 -1
  10. package/dist/cjs/FrappeDashboard.cjs +11 -22
  11. package/dist/cjs/FrappeDashboard.native.js +20 -22
  12. package/dist/cjs/FrappeDashboard.native.js.map +1 -1
  13. package/dist/cjs/FrappeForm.cjs +3 -1
  14. package/dist/cjs/FrappeForm.native.js +3 -1
  15. package/dist/cjs/FrappeForm.native.js.map +1 -1
  16. package/dist/cjs/FrappeGantt.cjs +5 -7
  17. package/dist/cjs/FrappeGantt.native.js +5 -9
  18. package/dist/cjs/FrappeGantt.native.js.map +1 -1
  19. package/dist/cjs/FrappeImageView.cjs +3 -1
  20. package/dist/cjs/FrappeImageView.native.js +3 -1
  21. package/dist/cjs/FrappeImageView.native.js.map +1 -1
  22. package/dist/cjs/FrappeKPICard.cjs +2 -0
  23. package/dist/cjs/FrappeKPICard.native.js +2 -0
  24. package/dist/cjs/FrappeKPICard.native.js.map +1 -1
  25. package/dist/cjs/FrappeKanban.cjs +3 -1
  26. package/dist/cjs/FrappeKanban.native.js +3 -1
  27. package/dist/cjs/FrappeKanban.native.js.map +1 -1
  28. package/dist/cjs/FrappeLikedBy.cjs +2 -0
  29. package/dist/cjs/FrappeLikedBy.native.js +2 -0
  30. package/dist/cjs/FrappeLikedBy.native.js.map +1 -1
  31. package/dist/cjs/FrappeList.cjs +3 -1
  32. package/dist/cjs/FrappeList.native.js +3 -1
  33. package/dist/cjs/FrappeList.native.js.map +1 -1
  34. package/dist/cjs/FrappeListView.cjs +6 -12
  35. package/dist/cjs/FrappeListView.native.js +13 -12
  36. package/dist/cjs/FrappeListView.native.js.map +1 -1
  37. package/dist/cjs/FrappeProvider.cjs +5 -1
  38. package/dist/cjs/FrappeProvider.native.js +5 -1
  39. package/dist/cjs/FrappeProvider.native.js.map +1 -1
  40. package/dist/cjs/FrappeReportBuilder.cjs +2 -0
  41. package/dist/cjs/FrappeReportBuilder.native.js +2 -0
  42. package/dist/cjs/FrappeReportBuilder.native.js.map +1 -1
  43. package/dist/cjs/FrappeReportView.cjs +23 -45
  44. package/dist/cjs/FrappeReportView.native.js +35 -50
  45. package/dist/cjs/FrappeReportView.native.js.map +1 -1
  46. package/dist/cjs/FrappeTable.cjs +105 -31
  47. package/dist/cjs/FrappeTable.native.js +140 -42
  48. package/dist/cjs/FrappeTable.native.js.map +1 -1
  49. package/dist/cjs/FrappeTreeView.cjs +3 -1
  50. package/dist/cjs/FrappeTreeView.native.js +3 -1
  51. package/dist/cjs/FrappeTreeView.native.js.map +1 -1
  52. package/dist/cjs/SyncErrorNotice.cjs +76 -0
  53. package/dist/cjs/SyncErrorNotice.native.js +92 -0
  54. package/dist/cjs/SyncErrorNotice.native.js.map +1 -0
  55. package/dist/cjs/commentActions.cjs +120 -0
  56. package/dist/cjs/commentActions.native.js +129 -0
  57. package/dist/cjs/commentActions.native.js.map +1 -0
  58. package/dist/cjs/components/attachments.cjs +2 -0
  59. package/dist/cjs/components/attachments.native.js +2 -0
  60. package/dist/cjs/components/attachments.native.js.map +1 -1
  61. package/dist/cjs/components/tags.cjs +5 -0
  62. package/dist/cjs/components/tags.native.js +5 -0
  63. package/dist/cjs/components/tags.native.js.map +1 -1
  64. package/dist/cjs/components/timeline.cjs +29 -2
  65. package/dist/cjs/components/timeline.native.js +33 -2
  66. package/dist/cjs/components/timeline.native.js.map +1 -1
  67. package/dist/cjs/desk/DeskFormSidebar.cjs +2 -0
  68. package/dist/cjs/desk/DeskFormSidebar.native.js +2 -0
  69. package/dist/cjs/desk/DeskFormSidebar.native.js.map +1 -1
  70. package/dist/cjs/fieldRegistry.cjs +2 -0
  71. package/dist/cjs/fieldRegistry.native.js +2 -0
  72. package/dist/cjs/fieldRegistry.native.js.map +1 -1
  73. package/dist/cjs/fields/Date/index.cjs +71 -7
  74. package/dist/cjs/fields/Date/index.native.js +325 -37
  75. package/dist/cjs/fields/Date/index.native.js.map +1 -1
  76. package/dist/cjs/fields/Date/types.cjs +18 -0
  77. package/dist/cjs/fields/Date/types.native.js +21 -0
  78. package/dist/cjs/fields/Date/types.native.js.map +1 -0
  79. package/dist/cjs/fields/Date/wire.cjs +4 -8
  80. package/dist/cjs/fields/Date/wire.native.js +4 -11
  81. package/dist/cjs/fields/Date/wire.native.js.map +1 -1
  82. package/dist/cjs/fields/DynamicLink.cjs +2 -0
  83. package/dist/cjs/fields/DynamicLink.native.js +2 -0
  84. package/dist/cjs/fields/DynamicLink.native.js.map +1 -1
  85. package/dist/cjs/fields/FileUpload.cjs +2 -0
  86. package/dist/cjs/fields/FileUpload.native.js +2 -0
  87. package/dist/cjs/fields/FileUpload.native.js.map +1 -1
  88. package/dist/cjs/fields/TableMultiSelect.cjs +6 -1
  89. package/dist/cjs/fields/TableMultiSelect.native.js +6 -1
  90. package/dist/cjs/fields/TableMultiSelect.native.js.map +1 -1
  91. package/dist/cjs/frappeStoryTarget.cjs +88 -0
  92. package/dist/cjs/frappeStoryTarget.native.js +100 -0
  93. package/dist/cjs/frappeStoryTarget.native.js.map +1 -1
  94. package/dist/cjs/frappeUsers.cjs +2 -0
  95. package/dist/cjs/frappeUsers.native.js +2 -0
  96. package/dist/cjs/frappeUsers.native.js.map +1 -1
  97. package/dist/cjs/index.cjs +17 -4
  98. package/dist/cjs/index.native.js +17 -4
  99. package/dist/cjs/index.native.js.map +1 -1
  100. package/dist/cjs/liveErrors.cjs +5 -1
  101. package/dist/cjs/liveErrors.native.js +5 -1
  102. package/dist/cjs/liveErrors.native.js.map +1 -1
  103. package/dist/cjs/useFrappeAggregate.cjs +13 -1
  104. package/dist/cjs/useFrappeAggregate.native.js +14 -1
  105. package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
  106. package/dist/cjs/useFrappeFacets.cjs +329 -0
  107. package/dist/cjs/useFrappeFacets.native.js +632 -0
  108. package/dist/cjs/useFrappeFacets.native.js.map +1 -0
  109. package/dist/esm/FrappeAssignment.mjs +2 -0
  110. package/dist/esm/FrappeAssignment.mjs.map +1 -1
  111. package/dist/esm/FrappeAssignment.native.js +2 -0
  112. package/dist/esm/FrappeAssignment.native.js.map +1 -1
  113. package/dist/esm/FrappeCalendar.mjs +6 -8
  114. package/dist/esm/FrappeCalendar.mjs.map +1 -1
  115. package/dist/esm/FrappeCalendar.native.js +6 -10
  116. package/dist/esm/FrappeCalendar.native.js.map +1 -1
  117. package/dist/esm/FrappeChartCard.mjs +2 -0
  118. package/dist/esm/FrappeChartCard.mjs.map +1 -1
  119. package/dist/esm/FrappeChartCard.native.js +2 -0
  120. package/dist/esm/FrappeChartCard.native.js.map +1 -1
  121. package/dist/esm/FrappeDashboard.mjs +12 -19
  122. package/dist/esm/FrappeDashboard.mjs.map +1 -1
  123. package/dist/esm/FrappeDashboard.native.js +23 -21
  124. package/dist/esm/FrappeDashboard.native.js.map +1 -1
  125. package/dist/esm/FrappeForm.mjs +3 -1
  126. package/dist/esm/FrappeForm.mjs.map +1 -1
  127. package/dist/esm/FrappeForm.native.js +3 -1
  128. package/dist/esm/FrappeForm.native.js.map +1 -1
  129. package/dist/esm/FrappeGantt.mjs +6 -8
  130. package/dist/esm/FrappeGantt.mjs.map +1 -1
  131. package/dist/esm/FrappeGantt.native.js +6 -10
  132. package/dist/esm/FrappeGantt.native.js.map +1 -1
  133. package/dist/esm/FrappeImageView.mjs +3 -1
  134. package/dist/esm/FrappeImageView.mjs.map +1 -1
  135. package/dist/esm/FrappeImageView.native.js +3 -1
  136. package/dist/esm/FrappeImageView.native.js.map +1 -1
  137. package/dist/esm/FrappeKPICard.mjs +2 -0
  138. package/dist/esm/FrappeKPICard.mjs.map +1 -1
  139. package/dist/esm/FrappeKPICard.native.js +2 -0
  140. package/dist/esm/FrappeKPICard.native.js.map +1 -1
  141. package/dist/esm/FrappeKanban.mjs +3 -1
  142. package/dist/esm/FrappeKanban.mjs.map +1 -1
  143. package/dist/esm/FrappeKanban.native.js +3 -1
  144. package/dist/esm/FrappeKanban.native.js.map +1 -1
  145. package/dist/esm/FrappeLikedBy.mjs +2 -0
  146. package/dist/esm/FrappeLikedBy.mjs.map +1 -1
  147. package/dist/esm/FrappeLikedBy.native.js +2 -0
  148. package/dist/esm/FrappeLikedBy.native.js.map +1 -1
  149. package/dist/esm/FrappeList.mjs +3 -1
  150. package/dist/esm/FrappeList.mjs.map +1 -1
  151. package/dist/esm/FrappeList.native.js +3 -1
  152. package/dist/esm/FrappeList.native.js.map +1 -1
  153. package/dist/esm/FrappeListView.mjs +7 -13
  154. package/dist/esm/FrappeListView.mjs.map +1 -1
  155. package/dist/esm/FrappeListView.native.js +15 -14
  156. package/dist/esm/FrappeListView.native.js.map +1 -1
  157. package/dist/esm/FrappeProvider.mjs +5 -1
  158. package/dist/esm/FrappeProvider.mjs.map +1 -1
  159. package/dist/esm/FrappeProvider.native.js +5 -1
  160. package/dist/esm/FrappeProvider.native.js.map +1 -1
  161. package/dist/esm/FrappeReportBuilder.mjs +2 -0
  162. package/dist/esm/FrappeReportBuilder.mjs.map +1 -1
  163. package/dist/esm/FrappeReportBuilder.native.js +2 -0
  164. package/dist/esm/FrappeReportBuilder.native.js.map +1 -1
  165. package/dist/esm/FrappeReportView.mjs +26 -49
  166. package/dist/esm/FrappeReportView.mjs.map +1 -1
  167. package/dist/esm/FrappeReportView.native.js +38 -54
  168. package/dist/esm/FrappeReportView.native.js.map +1 -1
  169. package/dist/esm/FrappeTable.mjs +107 -33
  170. package/dist/esm/FrappeTable.mjs.map +1 -1
  171. package/dist/esm/FrappeTable.native.js +142 -44
  172. package/dist/esm/FrappeTable.native.js.map +1 -1
  173. package/dist/esm/FrappeTreeView.mjs +3 -1
  174. package/dist/esm/FrappeTreeView.mjs.map +1 -1
  175. package/dist/esm/FrappeTreeView.native.js +3 -1
  176. package/dist/esm/FrappeTreeView.native.js.map +1 -1
  177. package/dist/esm/SyncErrorNotice.mjs +51 -0
  178. package/dist/esm/SyncErrorNotice.mjs.map +1 -0
  179. package/dist/esm/SyncErrorNotice.native.js +53 -0
  180. package/dist/esm/SyncErrorNotice.native.js.map +1 -0
  181. package/dist/esm/commentActions.mjs +95 -0
  182. package/dist/esm/commentActions.mjs.map +1 -0
  183. package/dist/esm/commentActions.native.js +101 -0
  184. package/dist/esm/commentActions.native.js.map +1 -0
  185. package/dist/esm/components/attachments.mjs +2 -0
  186. package/dist/esm/components/attachments.mjs.map +1 -1
  187. package/dist/esm/components/attachments.native.js +2 -0
  188. package/dist/esm/components/attachments.native.js.map +1 -1
  189. package/dist/esm/components/tags.mjs +5 -0
  190. package/dist/esm/components/tags.mjs.map +1 -1
  191. package/dist/esm/components/tags.native.js +5 -0
  192. package/dist/esm/components/tags.native.js.map +1 -1
  193. package/dist/esm/components/timeline.mjs +31 -4
  194. package/dist/esm/components/timeline.mjs.map +1 -1
  195. package/dist/esm/components/timeline.native.js +35 -4
  196. package/dist/esm/components/timeline.native.js.map +1 -1
  197. package/dist/esm/desk/DeskFormSidebar.mjs +2 -0
  198. package/dist/esm/desk/DeskFormSidebar.mjs.map +1 -1
  199. package/dist/esm/desk/DeskFormSidebar.native.js +2 -0
  200. package/dist/esm/desk/DeskFormSidebar.native.js.map +1 -1
  201. package/dist/esm/fieldRegistry.mjs +2 -0
  202. package/dist/esm/fieldRegistry.mjs.map +1 -1
  203. package/dist/esm/fieldRegistry.native.js +2 -0
  204. package/dist/esm/fieldRegistry.native.js.map +1 -1
  205. package/dist/esm/fields/Date/index.mjs +73 -9
  206. package/dist/esm/fields/Date/index.mjs.map +1 -1
  207. package/dist/esm/fields/Date/index.native.js +315 -38
  208. package/dist/esm/fields/Date/index.native.js.map +1 -1
  209. package/dist/esm/fields/Date/types.mjs +2 -0
  210. package/dist/esm/fields/Date/types.mjs.map +1 -0
  211. package/dist/esm/fields/Date/types.native.js +2 -0
  212. package/dist/esm/fields/Date/types.native.js.map +1 -0
  213. package/dist/esm/fields/Date/wire.mjs +4 -8
  214. package/dist/esm/fields/Date/wire.mjs.map +1 -1
  215. package/dist/esm/fields/Date/wire.native.js +4 -11
  216. package/dist/esm/fields/Date/wire.native.js.map +1 -1
  217. package/dist/esm/fields/DynamicLink.mjs +2 -0
  218. package/dist/esm/fields/DynamicLink.mjs.map +1 -1
  219. package/dist/esm/fields/DynamicLink.native.js +2 -0
  220. package/dist/esm/fields/DynamicLink.native.js.map +1 -1
  221. package/dist/esm/fields/FileUpload.mjs +2 -0
  222. package/dist/esm/fields/FileUpload.mjs.map +1 -1
  223. package/dist/esm/fields/FileUpload.native.js +2 -0
  224. package/dist/esm/fields/FileUpload.native.js.map +1 -1
  225. package/dist/esm/fields/TableMultiSelect.mjs +7 -2
  226. package/dist/esm/fields/TableMultiSelect.mjs.map +1 -1
  227. package/dist/esm/fields/TableMultiSelect.native.js +7 -2
  228. package/dist/esm/fields/TableMultiSelect.native.js.map +1 -1
  229. package/dist/esm/frappeStoryTarget.mjs +88 -2
  230. package/dist/esm/frappeStoryTarget.mjs.map +1 -1
  231. package/dist/esm/frappeStoryTarget.native.js +100 -2
  232. package/dist/esm/frappeStoryTarget.native.js.map +1 -1
  233. package/dist/esm/frappeUsers.mjs +2 -0
  234. package/dist/esm/frappeUsers.mjs.map +1 -1
  235. package/dist/esm/frappeUsers.native.js +2 -0
  236. package/dist/esm/frappeUsers.native.js.map +1 -1
  237. package/dist/esm/index.mjs +7 -3
  238. package/dist/esm/index.mjs.map +1 -1
  239. package/dist/esm/index.native.js +7 -3
  240. package/dist/esm/index.native.js.map +1 -1
  241. package/dist/esm/liveErrors.mjs +5 -1
  242. package/dist/esm/liveErrors.mjs.map +1 -1
  243. package/dist/esm/liveErrors.native.js +5 -1
  244. package/dist/esm/liveErrors.native.js.map +1 -1
  245. package/dist/esm/useFrappeAggregate.mjs +13 -1
  246. package/dist/esm/useFrappeAggregate.mjs.map +1 -1
  247. package/dist/esm/useFrappeAggregate.native.js +14 -1
  248. package/dist/esm/useFrappeAggregate.native.js.map +1 -1
  249. package/dist/esm/useFrappeFacets.mjs +299 -0
  250. package/dist/esm/useFrappeFacets.mjs.map +1 -0
  251. package/dist/esm/useFrappeFacets.native.js +599 -0
  252. package/dist/esm/useFrappeFacets.native.js.map +1 -0
  253. package/dist/jsx/FrappeAssignment.mjs +2 -0
  254. package/dist/jsx/FrappeAssignment.mjs.map +1 -1
  255. package/dist/jsx/FrappeAssignment.native.js +2 -0
  256. package/dist/jsx/FrappeAssignment.native.js.map +1 -1
  257. package/dist/jsx/FrappeCalendar.mjs +6 -8
  258. package/dist/jsx/FrappeCalendar.mjs.map +1 -1
  259. package/dist/jsx/FrappeCalendar.native.js +5 -9
  260. package/dist/jsx/FrappeCalendar.native.js.map +1 -1
  261. package/dist/jsx/FrappeChartCard.mjs +2 -0
  262. package/dist/jsx/FrappeChartCard.mjs.map +1 -1
  263. package/dist/jsx/FrappeChartCard.native.js +2 -0
  264. package/dist/jsx/FrappeChartCard.native.js.map +1 -1
  265. package/dist/jsx/FrappeDashboard.mjs +12 -19
  266. package/dist/jsx/FrappeDashboard.mjs.map +1 -1
  267. package/dist/jsx/FrappeDashboard.native.js +20 -22
  268. package/dist/jsx/FrappeDashboard.native.js.map +1 -1
  269. package/dist/jsx/FrappeForm.mjs +3 -1
  270. package/dist/jsx/FrappeForm.mjs.map +1 -1
  271. package/dist/jsx/FrappeForm.native.js +3 -1
  272. package/dist/jsx/FrappeForm.native.js.map +1 -1
  273. package/dist/jsx/FrappeGantt.mjs +6 -8
  274. package/dist/jsx/FrappeGantt.mjs.map +1 -1
  275. package/dist/jsx/FrappeGantt.native.js +5 -9
  276. package/dist/jsx/FrappeGantt.native.js.map +1 -1
  277. package/dist/jsx/FrappeImageView.mjs +3 -1
  278. package/dist/jsx/FrappeImageView.mjs.map +1 -1
  279. package/dist/jsx/FrappeImageView.native.js +3 -1
  280. package/dist/jsx/FrappeImageView.native.js.map +1 -1
  281. package/dist/jsx/FrappeKPICard.mjs +2 -0
  282. package/dist/jsx/FrappeKPICard.mjs.map +1 -1
  283. package/dist/jsx/FrappeKPICard.native.js +2 -0
  284. package/dist/jsx/FrappeKPICard.native.js.map +1 -1
  285. package/dist/jsx/FrappeKanban.mjs +3 -1
  286. package/dist/jsx/FrappeKanban.mjs.map +1 -1
  287. package/dist/jsx/FrappeKanban.native.js +3 -1
  288. package/dist/jsx/FrappeKanban.native.js.map +1 -1
  289. package/dist/jsx/FrappeLikedBy.mjs +2 -0
  290. package/dist/jsx/FrappeLikedBy.mjs.map +1 -1
  291. package/dist/jsx/FrappeLikedBy.native.js +2 -0
  292. package/dist/jsx/FrappeLikedBy.native.js.map +1 -1
  293. package/dist/jsx/FrappeList.mjs +3 -1
  294. package/dist/jsx/FrappeList.mjs.map +1 -1
  295. package/dist/jsx/FrappeList.native.js +3 -1
  296. package/dist/jsx/FrappeList.native.js.map +1 -1
  297. package/dist/jsx/FrappeListView.mjs +7 -13
  298. package/dist/jsx/FrappeListView.mjs.map +1 -1
  299. package/dist/jsx/FrappeListView.native.js +13 -12
  300. package/dist/jsx/FrappeListView.native.js.map +1 -1
  301. package/dist/jsx/FrappeProvider.mjs +5 -1
  302. package/dist/jsx/FrappeProvider.mjs.map +1 -1
  303. package/dist/jsx/FrappeProvider.native.js +5 -1
  304. package/dist/jsx/FrappeProvider.native.js.map +1 -1
  305. package/dist/jsx/FrappeReportBuilder.mjs +2 -0
  306. package/dist/jsx/FrappeReportBuilder.mjs.map +1 -1
  307. package/dist/jsx/FrappeReportBuilder.native.js +2 -0
  308. package/dist/jsx/FrappeReportBuilder.native.js.map +1 -1
  309. package/dist/jsx/FrappeReportView.mjs +26 -49
  310. package/dist/jsx/FrappeReportView.mjs.map +1 -1
  311. package/dist/jsx/FrappeReportView.native.js +35 -50
  312. package/dist/jsx/FrappeReportView.native.js.map +1 -1
  313. package/dist/jsx/FrappeTable.mjs +107 -33
  314. package/dist/jsx/FrappeTable.mjs.map +1 -1
  315. package/dist/jsx/FrappeTable.native.js +140 -42
  316. package/dist/jsx/FrappeTable.native.js.map +1 -1
  317. package/dist/jsx/FrappeTreeView.mjs +3 -1
  318. package/dist/jsx/FrappeTreeView.mjs.map +1 -1
  319. package/dist/jsx/FrappeTreeView.native.js +3 -1
  320. package/dist/jsx/FrappeTreeView.native.js.map +1 -1
  321. package/dist/jsx/SyncErrorNotice.mjs +51 -0
  322. package/dist/jsx/SyncErrorNotice.mjs.map +1 -0
  323. package/dist/jsx/SyncErrorNotice.native.js +92 -0
  324. package/dist/jsx/SyncErrorNotice.native.js.map +1 -0
  325. package/dist/jsx/commentActions.mjs +95 -0
  326. package/dist/jsx/commentActions.mjs.map +1 -0
  327. package/dist/jsx/commentActions.native.js +129 -0
  328. package/dist/jsx/commentActions.native.js.map +1 -0
  329. package/dist/jsx/components/attachments.mjs +2 -0
  330. package/dist/jsx/components/attachments.mjs.map +1 -1
  331. package/dist/jsx/components/attachments.native.js +2 -0
  332. package/dist/jsx/components/attachments.native.js.map +1 -1
  333. package/dist/jsx/components/tags.mjs +5 -0
  334. package/dist/jsx/components/tags.mjs.map +1 -1
  335. package/dist/jsx/components/tags.native.js +5 -0
  336. package/dist/jsx/components/tags.native.js.map +1 -1
  337. package/dist/jsx/components/timeline.mjs +31 -4
  338. package/dist/jsx/components/timeline.mjs.map +1 -1
  339. package/dist/jsx/components/timeline.native.js +33 -2
  340. package/dist/jsx/components/timeline.native.js.map +1 -1
  341. package/dist/jsx/desk/DeskFormSidebar.mjs +2 -0
  342. package/dist/jsx/desk/DeskFormSidebar.mjs.map +1 -1
  343. package/dist/jsx/desk/DeskFormSidebar.native.js +2 -0
  344. package/dist/jsx/desk/DeskFormSidebar.native.js.map +1 -1
  345. package/dist/jsx/fieldRegistry.mjs +2 -0
  346. package/dist/jsx/fieldRegistry.mjs.map +1 -1
  347. package/dist/jsx/fieldRegistry.native.js +2 -0
  348. package/dist/jsx/fieldRegistry.native.js.map +1 -1
  349. package/dist/jsx/fields/Date/index.mjs +73 -9
  350. package/dist/jsx/fields/Date/index.mjs.map +1 -1
  351. package/dist/jsx/fields/Date/index.native.js +325 -37
  352. package/dist/jsx/fields/Date/index.native.js.map +1 -1
  353. package/dist/jsx/fields/Date/types.mjs +2 -0
  354. package/dist/jsx/fields/Date/types.mjs.map +1 -0
  355. package/dist/jsx/fields/Date/types.native.js +21 -0
  356. package/dist/jsx/fields/Date/types.native.js.map +1 -0
  357. package/dist/jsx/fields/Date/wire.mjs +4 -8
  358. package/dist/jsx/fields/Date/wire.mjs.map +1 -1
  359. package/dist/jsx/fields/Date/wire.native.js +4 -11
  360. package/dist/jsx/fields/Date/wire.native.js.map +1 -1
  361. package/dist/jsx/fields/DynamicLink.mjs +2 -0
  362. package/dist/jsx/fields/DynamicLink.mjs.map +1 -1
  363. package/dist/jsx/fields/DynamicLink.native.js +2 -0
  364. package/dist/jsx/fields/DynamicLink.native.js.map +1 -1
  365. package/dist/jsx/fields/FileUpload.mjs +2 -0
  366. package/dist/jsx/fields/FileUpload.mjs.map +1 -1
  367. package/dist/jsx/fields/FileUpload.native.js +2 -0
  368. package/dist/jsx/fields/FileUpload.native.js.map +1 -1
  369. package/dist/jsx/fields/TableMultiSelect.mjs +7 -2
  370. package/dist/jsx/fields/TableMultiSelect.mjs.map +1 -1
  371. package/dist/jsx/fields/TableMultiSelect.native.js +6 -1
  372. package/dist/jsx/fields/TableMultiSelect.native.js.map +1 -1
  373. package/dist/jsx/frappeStoryTarget.mjs +88 -2
  374. package/dist/jsx/frappeStoryTarget.mjs.map +1 -1
  375. package/dist/jsx/frappeStoryTarget.native.js +100 -0
  376. package/dist/jsx/frappeStoryTarget.native.js.map +1 -1
  377. package/dist/jsx/frappeUsers.mjs +2 -0
  378. package/dist/jsx/frappeUsers.mjs.map +1 -1
  379. package/dist/jsx/frappeUsers.native.js +2 -0
  380. package/dist/jsx/frappeUsers.native.js.map +1 -1
  381. package/dist/jsx/index.js +7 -3
  382. package/dist/jsx/index.js.map +1 -1
  383. package/dist/jsx/index.mjs +7 -3
  384. package/dist/jsx/index.mjs.map +1 -1
  385. package/dist/jsx/index.native.js +17 -4
  386. package/dist/jsx/index.native.js.map +1 -1
  387. package/dist/jsx/liveErrors.mjs +5 -1
  388. package/dist/jsx/liveErrors.mjs.map +1 -1
  389. package/dist/jsx/liveErrors.native.js +5 -1
  390. package/dist/jsx/liveErrors.native.js.map +1 -1
  391. package/dist/jsx/useFrappeAggregate.mjs +13 -1
  392. package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
  393. package/dist/jsx/useFrappeAggregate.native.js +14 -1
  394. package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
  395. package/dist/jsx/useFrappeFacets.mjs +299 -0
  396. package/dist/jsx/useFrappeFacets.mjs.map +1 -0
  397. package/dist/jsx/useFrappeFacets.native.js +632 -0
  398. package/dist/jsx/useFrappeFacets.native.js.map +1 -0
  399. package/package.json +21 -21
  400. package/src/FormSidebar.stories.tsx +88 -10
  401. package/src/FrappeAssignment.tsx +2 -0
  402. package/src/FrappeCalendar.tsx +12 -7
  403. package/src/FrappeChartCard.tsx +9 -2
  404. package/src/FrappeDashboard.spec.tsx +95 -0
  405. package/src/FrappeDashboard.tsx +20 -41
  406. package/src/FrappeForm.stories.tsx +53 -46
  407. package/src/FrappeForm.tsx +11 -1
  408. package/src/FrappeGantt.tsx +12 -7
  409. package/src/FrappeImageView.tsx +11 -1
  410. package/src/FrappeKPICard.stories.tsx +4 -1
  411. package/src/FrappeKPICard.tsx +2 -0
  412. package/src/FrappeKanban.tsx +4 -0
  413. package/src/FrappeLikedBy.stories.tsx +26 -10
  414. package/src/FrappeLikedBy.tsx +2 -0
  415. package/src/FrappeList.tsx +11 -1
  416. package/src/FrappeListView.spec.tsx +144 -0
  417. package/src/FrappeListView.tsx +11 -23
  418. package/src/FrappeProvider.tsx +30 -2
  419. package/src/FrappeReportBuilder.tsx +2 -0
  420. package/src/FrappeReportView.spec.tsx +140 -16
  421. package/src/FrappeReportView.stories.tsx +12 -6
  422. package/src/FrappeReportView.tsx +62 -61
  423. package/src/FrappeTable.keepRows.spec.tsx +200 -0
  424. package/src/FrappeTable.retryStorm.spec.tsx +140 -0
  425. package/src/FrappeTable.spec.tsx +222 -0
  426. package/src/FrappeTable.tsx +211 -54
  427. package/src/FrappeTreeView.tsx +11 -1
  428. package/src/SyncErrorNotice.spec.tsx +60 -0
  429. package/src/SyncErrorNotice.tsx +81 -0
  430. package/src/commentActions.ts +157 -0
  431. package/src/components/attachments.tsx +2 -0
  432. package/src/components/tags.tsx +14 -1
  433. package/src/components/timeline.optimistic.spec.tsx +106 -0
  434. package/src/components/timeline.tsx +36 -4
  435. package/src/desk/DeskFormSidebar.tsx +2 -0
  436. package/src/fieldRegistry.ts +2 -0
  437. package/src/fields/Date/index.native.tsx +410 -0
  438. package/src/fields/Date/index.tsx +138 -57
  439. package/src/fields/Date/nativePresentation.spec.tsx +251 -0
  440. package/src/fields/Date/typeParity.spec.tsx +480 -0
  441. package/src/fields/Date/types.ts +53 -0
  442. package/src/fields/Date/wire.spec.ts +63 -1
  443. package/src/fields/Date/wire.ts +17 -17
  444. package/src/fields/DynamicLink.tsx +2 -0
  445. package/src/fields/FileUpload.tsx +2 -0
  446. package/src/fields/TableMultiSelect.tsx +6 -3
  447. package/src/frappeStoryTarget.tsx +104 -1
  448. package/src/frappeUsers.ts +2 -0
  449. package/src/index.spec.ts +62 -0
  450. package/src/index.ts +36 -2
  451. package/src/liveErrors.spec.ts +50 -0
  452. package/src/liveErrors.ts +11 -1
  453. package/src/useFrappeAggregate.ts +20 -1
  454. package/src/useFrappeFacets.spec.tsx +436 -0
  455. package/src/useFrappeFacets.ts +568 -0
  456. package/types/FrappeAssignment.d.ts.map +1 -1
  457. package/types/FrappeCalendar.d.ts.map +1 -1
  458. package/types/FrappeChartCard.d.ts +1 -2
  459. package/types/FrappeChartCard.d.ts.map +1 -1
  460. package/types/FrappeDashboard.d.ts +0 -1
  461. package/types/FrappeDashboard.d.ts.map +1 -1
  462. package/types/FrappeForm.d.ts.map +1 -1
  463. package/types/FrappeGantt.d.ts.map +1 -1
  464. package/types/FrappeKPICard.d.ts.map +1 -1
  465. package/types/FrappeKanban.d.ts.map +1 -1
  466. package/types/FrappeLikedBy.d.ts.map +1 -1
  467. package/types/FrappeList.d.ts.map +1 -1
  468. package/types/FrappeListView.d.ts.map +1 -1
  469. package/types/FrappeProvider.d.ts +18 -1
  470. package/types/FrappeProvider.d.ts.map +1 -1
  471. package/types/FrappeReportBuilder.d.ts.map +1 -1
  472. package/types/FrappeReportView.d.ts +22 -15
  473. package/types/FrappeReportView.d.ts.map +1 -1
  474. package/types/FrappeTable.d.ts +21 -2
  475. package/types/FrappeTable.d.ts.map +1 -1
  476. package/types/FrappeTreeView.d.ts.map +1 -1
  477. package/types/SyncErrorNotice.d.ts +38 -0
  478. package/types/SyncErrorNotice.d.ts.map +1 -0
  479. package/types/commentActions.d.ts +46 -0
  480. package/types/commentActions.d.ts.map +1 -0
  481. package/types/components/attachments.d.ts.map +1 -1
  482. package/types/components/tags.d.ts.map +1 -1
  483. package/types/components/timeline.d.ts.map +1 -1
  484. package/types/desk/DeskFormSidebar.d.ts.map +1 -1
  485. package/types/fieldRegistry.d.ts.map +1 -1
  486. package/types/fields/Date/index.d.ts +22 -12
  487. package/types/fields/Date/index.d.ts.map +1 -1
  488. package/types/fields/Date/index.native.d.ts +14 -0
  489. package/types/fields/Date/index.native.d.ts.map +1 -0
  490. package/types/fields/Date/types.d.ts +27 -0
  491. package/types/fields/Date/types.d.ts.map +1 -0
  492. package/types/fields/Date/wire.d.ts +13 -9
  493. package/types/fields/Date/wire.d.ts.map +1 -1
  494. package/types/fields/DynamicLink.d.ts.map +1 -1
  495. package/types/fields/FileUpload.d.ts.map +1 -1
  496. package/types/fields/TableMultiSelect.d.ts.map +1 -1
  497. package/types/frappeStoryTarget.d.ts +19 -1
  498. package/types/frappeStoryTarget.d.ts.map +1 -1
  499. package/types/frappeUsers.d.ts.map +1 -1
  500. package/types/index.d.ts +6 -2
  501. package/types/index.d.ts.map +1 -1
  502. package/types/liveErrors.d.ts +4 -0
  503. package/types/liveErrors.d.ts.map +1 -1
  504. package/types/useFrappeAggregate.d.ts +4 -0
  505. package/types/useFrappeAggregate.d.ts.map +1 -1
  506. package/types/useFrappeFacets.d.ts +143 -0
  507. package/types/useFrappeFacets.d.ts.map +1 -0
  508. package/dist/cjs/fields/Date/date.native.js +0 -277
  509. package/dist/cjs/fields/Date/date.native.js.map +0 -1
  510. package/dist/esm/fields/Date/date.native.js +0 -238
  511. package/dist/esm/fields/Date/date.native.js.map +0 -1
  512. package/dist/jsx/fields/Date/date.native.js +0 -277
  513. package/dist/jsx/fields/Date/date.native.js.map +0 -1
  514. package/src/fields/Date/date.native.tsx +0 -299
  515. package/types/fields/Date/date.native.d.ts +0 -19
  516. package/types/fields/Date/date.native.d.ts.map +0 -1
@@ -0,0 +1,480 @@
1
+ /**
2
+ * The web half honours all three Frappe date fieldtypes, and both halves
3
+ * persist the same bytes for the same pick.
4
+ *
5
+ * The web field used to render ONE date-only calendar whatever the type
6
+ * (SB-M-617): a `Datetime` dropped its time component from the picker, and a
7
+ * `Time` rendered EMPTY, because the calendar adopts its value through
8
+ * `coerceToDate` and `new Date("09:00")` is an Invalid Date. Native, meanwhile,
9
+ * had just been activated with all three implemented. These cases pin the
10
+ * three web controls — display, edit, and the exact wire string — and then
11
+ * drive the SAME pick through both platform halves and assert the emitted
12
+ * strings are byte-identical, which is the only reason one `wire.ts` body is
13
+ * worth sharing.
14
+ *
15
+ * The suite runs on web, so the native half's two native-only edges are mocked
16
+ * exactly as `nativePresentation.spec.tsx` mocks them: the OS picker (recorded
17
+ * rather than rendered) and the platform flags. The web half imports neither.
18
+ */
19
+
20
+ import { describe, it, expect, vi, beforeEach } from "vitest";
21
+
22
+ // Same cold-fork budget as the sibling Date specs: the first field-module
23
+ // import transforms the whole fields graph.
24
+ vi.setConfig({ testTimeout: 240_000 });
25
+
26
+ const mockState = vi.hoisted(() => ({
27
+ isIos: true,
28
+ isAndroid: false,
29
+ /** Props of every mounted OS picker, newest last. */
30
+ picks: [] as Record<string, unknown>[],
31
+ }));
32
+
33
+ vi.mock("@react-native-community/datetimepicker", () => ({
34
+ default: (props: Record<string, unknown>) => {
35
+ mockState.picks.push(props);
36
+ return null;
37
+ },
38
+ }));
39
+
40
+ vi.mock("@multiplatform.one/platform", async (importOriginal) => {
41
+ const actual = (await importOriginal()) as Record<string, unknown>;
42
+ const real = actual.platform as Record<string, unknown>;
43
+ return {
44
+ ...actual,
45
+ platform: new Proxy(
46
+ {},
47
+ {
48
+ get(_target, prop) {
49
+ if (prop === "isIos") return mockState.isIos;
50
+ if (prop === "isAndroid") return mockState.isAndroid;
51
+ return real[prop as string];
52
+ },
53
+ },
54
+ ),
55
+ };
56
+ });
57
+
58
+ // Mock react-native-reanimated to prevent ESM module resolution failures
59
+ // (same shim as tests/fieldRenderers.spec.tsx).
60
+ vi.mock("react-native-reanimated", () => ({
61
+ default: {
62
+ useSharedValue: (init: any) => ({ value: init }),
63
+ useAnimatedStyle: () => ({}),
64
+ withTiming: (val: any) => val,
65
+ withSpring: (val: any) => val,
66
+ createAnimatedComponent: (component: any) => component,
67
+ runOnJS: (fn: any) => fn,
68
+ runOnUI: (fn: any) => fn,
69
+ useDerivedValue: (fn: () => any) => ({ value: fn() }),
70
+ useAnimatedRef: () => ({ current: null }),
71
+ Easing: { linear: (v: any) => v, ease: (v: any) => v, bezier: () => (v: any) => v },
72
+ FadeIn: { duration: () => ({}) },
73
+ FadeOut: { duration: () => ({}) },
74
+ Layout: { duration: () => ({}) },
75
+ Extrapolation: { CLAMP: "clamp" },
76
+ measure: () => null,
77
+ scrollTo: () => {},
78
+ makeMutable: (init: any) => ({ value: init }),
79
+ },
80
+ useSharedValue: (init: any) => ({ value: init }),
81
+ useAnimatedStyle: () => ({}),
82
+ withTiming: (val: any) => val,
83
+ withSpring: (val: any) => val,
84
+ createAnimatedComponent: (component: any) => component,
85
+ runOnJS: (fn: any) => fn,
86
+ runOnUI: (fn: any) => fn,
87
+ useDerivedValue: (fn: () => any) => ({ value: fn() }),
88
+ useAnimatedRef: () => ({ current: null }),
89
+ Easing: { linear: (v: any) => v, ease: (v: any) => v, bezier: () => (v: any) => v },
90
+ FadeIn: { duration: () => ({}) },
91
+ FadeOut: { duration: () => ({}) },
92
+ Layout: { duration: () => ({}) },
93
+ }));
94
+
95
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
96
+ import { act, cleanup, fireEvent, waitFor } from "@testing-library/react";
97
+ import React from "react";
98
+
99
+ const loadWebDateField = async () => (await import(".")).Date;
100
+ const loadNativeDateField = async () => (await import("./index.native")).Date;
101
+
102
+ /** Intl puts a narrow no-break space before the meridiem; no test should care. */
103
+ const norm = (value: string) => value.replace(/\s+/gu, " ").trim();
104
+
105
+ const triggerInput = (root: ParentNode) => root.querySelector("input") as HTMLInputElement | null;
106
+
107
+ /**
108
+ * Open the panel the way a browser does. FloatingPanel's own opener is
109
+ * `useClick(..., { event: "mousedown" })` while the DatePicker/DatetimePicker
110
+ * triggers carry their own `onPress` — TimePicker's trigger carries none, so
111
+ * a `click`-only test would never open it.
112
+ */
113
+ async function openPanel(input: Element) {
114
+ await act(async () => {
115
+ fireEvent.mouseDown(input);
116
+ fireEvent.click(input);
117
+ });
118
+ await waitFor(
119
+ () => {
120
+ if (!document.querySelector('[data-testid="floating-panel-viewport"]')) {
121
+ throw new Error("panel never opened");
122
+ }
123
+ },
124
+ { timeout: 30_000 },
125
+ );
126
+ }
127
+
128
+ const panelText = () =>
129
+ norm(
130
+ document.querySelector('[data-testid="floating-panel-viewport"]')?.textContent ??
131
+ document.body.textContent ??
132
+ "",
133
+ );
134
+
135
+ /** An enabled day cell inside the (portaled) panel. */
136
+ function dayCell(day: number): HTMLButtonElement | undefined {
137
+ return (
138
+ Array.from(document.body.querySelectorAll('button, [role="button"]')) as HTMLButtonElement[]
139
+ ).find((btn) => {
140
+ const disabled =
141
+ btn.hasAttribute("disabled") ||
142
+ btn.getAttribute("aria-disabled") === "true" ||
143
+ btn.hasAttribute("data-disabled");
144
+ return btn.textContent?.trim() === String(day) && !disabled;
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Click whatever leaf carries this text, once it exists. The wait is the
150
+ * point: first paint of this graph is slow on a loaded machine, and a bare
151
+ * query races it. Clicking the leaf is enough — React bubbles the event to
152
+ * whichever ancestor owns the handler.
153
+ */
154
+ async function pressText(root: ParentNode, text: string) {
155
+ let target: Element | null = null;
156
+ await waitFor(
157
+ () => {
158
+ const node = Array.from(root.querySelectorAll("*")).find(
159
+ (el) => el.children.length === 0 && norm(el.textContent ?? "") === norm(text),
160
+ );
161
+ if (!node) throw new Error(`no element with text "${text}"`);
162
+ target = node;
163
+ },
164
+ { timeout: 30_000 },
165
+ );
166
+ await act(async () => {
167
+ fireEvent.click(target!);
168
+ });
169
+ }
170
+
171
+ beforeEach(() => {
172
+ // Both halves portal their surfaces, so a previous case's chrome would
173
+ // otherwise still be in the document when the next one queries it.
174
+ cleanup();
175
+ mockState.picks.length = 0;
176
+ mockState.isIos = true;
177
+ mockState.isAndroid = false;
178
+ });
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // The web half, one control per type
182
+ // ---------------------------------------------------------------------------
183
+
184
+ describe("web Date field — type=date keeps the calendar", () => {
185
+ it("displays its value, opens a month grid, and writes YYYY-MM-DD", async () => {
186
+ const DateField = await loadWebDateField();
187
+ const onChange = vi.fn();
188
+ const result = renderWithProviders(
189
+ <DateField label="Due Date" value="2026-03-05" onChange={onChange} type="date" />,
190
+ );
191
+
192
+ expect(triggerInput(result.container)?.value).toBe("Mar 5, 2026");
193
+
194
+ await openPanel(triggerInput(result.container)!);
195
+ expect(panelText()).toContain("Su");
196
+ // A date-only control offers no clock.
197
+ expect(panelText()).not.toMatch(/\bHour\b/);
198
+
199
+ await waitFor(() => expect(dayCell(15)).toBeTruthy(), { timeout: 30_000 });
200
+ await act(async () => {
201
+ fireEvent.click(dayCell(15)!);
202
+ });
203
+
204
+ expect(onChange).toHaveBeenCalledTimes(1);
205
+ expect(onChange).toHaveBeenCalledWith("2026-03-15");
206
+ });
207
+ });
208
+
209
+ describe("web Date field — type=time renders a time control", () => {
210
+ it("DISPLAYS an existing HH:mm:ss value (it used to render empty)", async () => {
211
+ const DateField = await loadWebDateField();
212
+ const result = renderWithProviders(
213
+ <DateField label="Reminder" value="09:00:00" onChange={vi.fn()} type="time" />,
214
+ );
215
+ // The defect: `coerceToDate("09:00:00")` is null, so the calendar showed
216
+ // nothing at all for a Time field that had a value.
217
+ await waitFor(() => expect(norm(triggerInput(result.container)?.value ?? "")).toBe("9:00 AM"), {
218
+ timeout: 30_000,
219
+ });
220
+ });
221
+
222
+ it("opens hour/minute columns rather than a month grid", async () => {
223
+ const DateField = await loadWebDateField();
224
+ const result = renderWithProviders(
225
+ <DateField label="Reminder" value="09:00:00" onChange={vi.fn()} type="time" />,
226
+ );
227
+ await openPanel(triggerInput(result.container)!);
228
+ const text = panelText();
229
+ expect(text).toContain("Hour");
230
+ expect(text).toContain("Min");
231
+ expect(text).not.toContain("Su");
232
+ });
233
+
234
+ it("an edit writes the Frappe HH:mm:ss wire shape, seconds included", async () => {
235
+ const DateField = await loadWebDateField();
236
+ const onChange = vi.fn();
237
+ const result = renderWithProviders(
238
+ <DateField label="Reminder" value="09:00:00" onChange={onChange} type="time" />,
239
+ );
240
+ await openPanel(triggerInput(result.container)!);
241
+ // 9:00 AM → PM is a deterministic edit with no wheel animation in it.
242
+ await pressText(document.body, "PM");
243
+
244
+ // The control speaks `HH:mm`; the field re-serializes through the shared
245
+ // wire body, so what persists carries seconds like every other writer.
246
+ expect(onChange).toHaveBeenCalledWith("21:00:00");
247
+ });
248
+
249
+ it("adopts a seconds-less HH:mm value too (what Frappe hands a fresh field)", async () => {
250
+ const DateField = await loadWebDateField();
251
+ const result = renderWithProviders(
252
+ <DateField label="Reminder" value="14:30" onChange={vi.fn()} type="time" />,
253
+ );
254
+ await waitFor(() => expect(norm(triggerInput(result.container)?.value ?? "")).toBe("2:30 PM"), {
255
+ timeout: 30_000,
256
+ });
257
+ });
258
+ });
259
+
260
+ describe("web Date field — type=datetime renders date AND time", () => {
261
+ it("displays both halves of its value", async () => {
262
+ const DateField = await loadWebDateField();
263
+ const result = renderWithProviders(
264
+ <DateField
265
+ label="Starts On"
266
+ value="2026-03-05 14:30:00"
267
+ onChange={vi.fn()}
268
+ type="datetime"
269
+ />,
270
+ );
271
+ // Before the fix this read "Mar 5, 2026" — the time was simply gone.
272
+ await waitFor(
273
+ () => {
274
+ const value = norm(triggerInput(result.container)?.value ?? "");
275
+ expect(value).toContain("Mar 5, 2026");
276
+ expect(value).toContain("2:30 PM");
277
+ },
278
+ { timeout: 30_000 },
279
+ );
280
+ });
281
+
282
+ it("offers a time surface as well as the grid", async () => {
283
+ const DateField = await loadWebDateField();
284
+ const result = renderWithProviders(
285
+ <DateField
286
+ label="Starts On"
287
+ value="2026-03-05 14:30:00"
288
+ onChange={vi.fn()}
289
+ type="datetime"
290
+ />,
291
+ );
292
+ await openPanel(triggerInput(result.container)!);
293
+ expect(panelText()).toContain("Su");
294
+ await pressText(document.body, "Time");
295
+ await waitFor(() => expect(panelText()).toContain("Hour"), { timeout: 30_000 });
296
+ });
297
+
298
+ it("a day change keeps the existing time — and the picker, not the serializer, keeps it", async () => {
299
+ const DateField = await loadWebDateField();
300
+ const onChange = vi.fn();
301
+ const result = renderWithProviders(
302
+ <DateField
303
+ label="Starts On"
304
+ value="2026-03-05 14:30:00"
305
+ onChange={onChange}
306
+ type="datetime"
307
+ />,
308
+ );
309
+ await openPanel(triggerInput(result.container)!);
310
+ await waitFor(() => expect(dayCell(20)).toBeTruthy(), { timeout: 30_000 });
311
+ await act(async () => {
312
+ fireEvent.click(dayCell(20)!);
313
+ });
314
+ expect(onChange).toHaveBeenCalledTimes(1);
315
+ expect(onChange).toHaveBeenCalledWith("2026-03-20 14:30:00");
316
+ });
317
+
318
+ it("a time change writes the new clock (the branch passes no previousValue)", async () => {
319
+ const DateField = await loadWebDateField();
320
+ const onChange = vi.fn();
321
+ const result = renderWithProviders(
322
+ <DateField
323
+ label="Starts On"
324
+ value="2026-03-05 14:30:00"
325
+ onChange={onChange}
326
+ type="datetime"
327
+ />,
328
+ );
329
+ await openPanel(triggerInput(result.container)!);
330
+ await pressText(document.body, "Time");
331
+ // 2:30 PM → AM. Carrying `previousValue` here would have pinned the old
332
+ // 14:30 back over the edit the user just made.
333
+ await pressText(document.body, "AM");
334
+ expect(onChange).toHaveBeenLastCalledWith("2026-03-05 02:30:00");
335
+ });
336
+ });
337
+
338
+ // ---------------------------------------------------------------------------
339
+ // Cross-half parity — the reason one `wire.ts` body is shared
340
+ // ---------------------------------------------------------------------------
341
+
342
+ /** Drive the native half through a pick and return the single emitted string. */
343
+ async function nativePick(
344
+ type: "date" | "datetime" | "time",
345
+ value: string,
346
+ triggerText: string,
347
+ steps: Date[],
348
+ ): Promise<string> {
349
+ const DateField = await loadNativeDateField();
350
+ const onChange = vi.fn();
351
+ const result = renderWithProviders(
352
+ <DateField label="Field" value={value} onChange={onChange} type={type} />,
353
+ );
354
+ await pressText(result.container, triggerText);
355
+ await waitFor(() => expect(mockState.picks.length).toBeGreaterThan(0), { timeout: 30_000 });
356
+
357
+ for (const [index, step] of steps.entries()) {
358
+ const onPickerChange = mockState.picks[mockState.picks.length - 1].onChange as (
359
+ event: unknown,
360
+ date?: Date,
361
+ ) => void;
362
+ await act(async () => {
363
+ onPickerChange({ type: "set" }, step);
364
+ });
365
+ // A datetime runs calendar → Next → wheel; the sheet's Next is the seam.
366
+ if (index === 0 && steps.length > 1) {
367
+ await pressText(document.body, "Next");
368
+ await waitFor(() => expect(mockState.picks[mockState.picks.length - 1].mode).toBe("time"), {
369
+ timeout: 30_000,
370
+ });
371
+ }
372
+ }
373
+ await pressText(document.body, "Done");
374
+
375
+ expect(onChange).toHaveBeenCalledTimes(1);
376
+ return onChange.mock.calls[0][0] as string;
377
+ }
378
+
379
+ /** Drive the web half through the equivalent pick and return the last string. */
380
+ async function webPick(
381
+ type: "date" | "datetime" | "time",
382
+ value: string,
383
+ drive: () => Promise<void>,
384
+ ): Promise<string> {
385
+ const DateField = await loadWebDateField();
386
+ const onChange = vi.fn();
387
+ const result = renderWithProviders(
388
+ <DateField label="Field" value={value} onChange={onChange} type={type} />,
389
+ );
390
+ await openPanel(triggerInput(result.container)!);
391
+ await drive();
392
+ await waitFor(() => expect(onChange).toHaveBeenCalled(), { timeout: 30_000 });
393
+ const calls = onChange.mock.calls;
394
+ return calls[calls.length - 1][0] as string;
395
+ }
396
+
397
+ describe("web and native persist byte-identical wire strings", () => {
398
+ it("date — picking Mar 15 writes the same string on both halves", async () => {
399
+ const web = await webPick("date", "2026-03-05", async () => {
400
+ await waitFor(() => expect(dayCell(15)).toBeTruthy(), { timeout: 30_000 });
401
+ await act(async () => {
402
+ fireEvent.click(dayCell(15)!);
403
+ });
404
+ });
405
+ cleanup();
406
+ mockState.picks.length = 0;
407
+ const native = await nativePick("date", "2026-03-05", "Mar 5, 2026", [new Date(2026, 2, 15)]);
408
+
409
+ expect({ web, native }).toEqual({ web: "2026-03-15", native: "2026-03-15" });
410
+ expect(web).toBe(native);
411
+ });
412
+
413
+ it("time — moving 9:00 AM to 9:00 PM writes the same string on both halves", async () => {
414
+ const web = await webPick("time", "09:00:00", async () => {
415
+ await pressText(document.body, "PM");
416
+ });
417
+ cleanup();
418
+ mockState.picks.length = 0;
419
+ const native = await nativePick("time", "09:00:00", "9:00 AM", [
420
+ new Date(2026, 2, 5, 21, 0, 0),
421
+ ]);
422
+
423
+ expect({ web, native }).toEqual({ web: "21:00:00", native: "21:00:00" });
424
+ expect(web).toBe(native);
425
+ });
426
+
427
+ it("datetime — moving the day to Mar 20 writes the same string on both halves", async () => {
428
+ const web = await webPick("datetime", "2026-03-05 14:30:00", async () => {
429
+ await waitFor(() => expect(dayCell(20)).toBeTruthy(), { timeout: 30_000 });
430
+ await act(async () => {
431
+ fireEvent.click(dayCell(20)!);
432
+ });
433
+ });
434
+ cleanup();
435
+ mockState.picks.length = 0;
436
+ // The native two-step: the calendar commits the day (dropping the clock,
437
+ // which is why the wheel step re-supplies it), then the wheel commits it.
438
+ const native = await nativePick("datetime", "2026-03-05 14:30:00", "Mar 5, 2026, 2:30 PM", [
439
+ new Date(2026, 2, 20),
440
+ new Date(2026, 0, 1, 14, 30),
441
+ ]);
442
+
443
+ expect({ web, native }).toEqual({
444
+ web: "2026-03-20 14:30:00",
445
+ native: "2026-03-20 14:30:00",
446
+ });
447
+ expect(web).toBe(native);
448
+ });
449
+ });
450
+
451
+ // ---------------------------------------------------------------------------
452
+ // SB-M-614 — disabled must gate every type's opener
453
+ // ---------------------------------------------------------------------------
454
+
455
+ describe("web Date field — a disabled field opens nothing, whatever the type", () => {
456
+ for (const [type, value] of [
457
+ ["date", "2026-03-05"],
458
+ ["datetime", "2026-03-05 14:30:00"],
459
+ ["time", "09:00:00"],
460
+ ] as const) {
461
+ it(`type=${type}`, async () => {
462
+ const DateField = await loadWebDateField();
463
+ const result = renderWithProviders(
464
+ <DateField label="Field" value={value} onChange={vi.fn()} type={type} disabled />,
465
+ );
466
+ const input = triggerInput(result.container)!;
467
+ expect(input.getAttribute("aria-disabled")).toBe("true");
468
+
469
+ // Both halves of a real click: the trigger's own handler is gated, but
470
+ // FloatingPanel's opener listens on mousedown and the disabled trigger
471
+ // is pointerEvents-none, so the press falls through to it.
472
+ await act(async () => {
473
+ fireEvent.mouseDown(input);
474
+ fireEvent.click(input);
475
+ });
476
+ await new Promise((resolve) => setTimeout(resolve, 250));
477
+ expect(document.querySelector('[data-testid="floating-panel-viewport"]')).toBeNull();
478
+ });
479
+ }
480
+ });
@@ -0,0 +1,53 @@
1
+ import type { FormFieldProps, FieldComponentProps } from "@multiplatform.one/forms";
2
+ import type { DeepKeys } from "@tanstack/form-core";
3
+
4
+ /**
5
+ * The public contract for the frappe-ui `Date` field.
6
+ *
7
+ * The package exports one `Date`, and the two platform halves
8
+ * (`index.tsx` / `index.native.tsx`) both satisfy this type, so a consumer
9
+ * never sees a different prop surface depending on where the code runs. The
10
+ * type lives in its own module for the same reason `TextEditor` keeps one:
11
+ * neither half can import it from the other without resolving to itself.
12
+ */
13
+ export type DateProps<
14
+ TData extends Record<string, unknown> = Record<string, unknown>,
15
+ TName extends string = string,
16
+ > = Omit<
17
+ FormFieldProps<
18
+ TData,
19
+ TName extends DeepKeys<TData> ? TName : DeepKeys<TData>,
20
+ unknown,
21
+ unknown,
22
+ string
23
+ >,
24
+ // "onChange": FormFieldProps carries the DOM ChangeEventHandler from
25
+ // YStackProps; this field exposes a canonical value callback instead.
26
+ "children" | "field" | "onChange"
27
+ > &
28
+ Partial<
29
+ Omit<
30
+ FieldComponentProps<
31
+ TData,
32
+ TName extends DeepKeys<TData> ? TName : DeepKeys<TData>,
33
+ unknown,
34
+ unknown,
35
+ string
36
+ >,
37
+ "children"
38
+ >
39
+ > & {
40
+ /** Frappe wire value: `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or `HH:mm:ss`. */
41
+ value?: string;
42
+ onChange?: (value: string) => void;
43
+ type?: "date" | "datetime" | "time";
44
+ disabled?: boolean;
45
+ readOnly?: boolean;
46
+ skeleton?: boolean;
47
+ compact?: boolean;
48
+ /** Overrides the type-derived default ("Select date" / "…time"). */
49
+ placeholder?: string;
50
+ minDate?: Date;
51
+ maxDate?: Date;
52
+ inputProps?: Record<string, unknown>;
53
+ };
@@ -131,7 +131,10 @@ describe("toFrappeDateValue — a datetime pick preserves the instant", () => {
131
131
  expect(shifted).toEqual({ wire: "2026-08-15 22:00", instantPreserved: false, driftHours: 7 });
132
132
  });
133
133
 
134
- it("keeps an existing time part when only the day was edited (web date-only picker)", () => {
134
+ // `previousValue` has no shipped caller since the web datetime branch moved
135
+ // onto a picker that owns the whole value — kept because it is an exported
136
+ // capability for any date-only writer, and untested capabilities rot.
137
+ it("keeps an existing time part when a caller says only the day was edited", () => {
135
138
  for (const separator of [" ", "T"]) {
136
139
  expect(
137
140
  inZone("Pacific/Kiritimati", () =>
@@ -153,6 +156,65 @@ describe("toFrappeDateValue — a time pick is a local clock", () => {
153
156
  });
154
157
  });
155
158
 
159
+ describe("toFrappeDateValue delegates without changing a byte", () => {
160
+ /**
161
+ * The body this function carried before it moved onto `formatCalendarDate`
162
+ * in `@multiplatform.one/theme`, pasted verbatim. Consolidating the repo's
163
+ * date serializers was a de-duplication, not a behavior change, and this is
164
+ * what makes that claim checkable rather than asserted.
165
+ */
166
+ function previousBody(
167
+ date: Date,
168
+ type: "date" | "datetime" | "time",
169
+ previousValue?: string,
170
+ ): string {
171
+ const pad2 = (n: number) => String(n).padStart(2, "0");
172
+ const datePart = `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
173
+ const timePart = `${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
174
+ if (type === "time") return timePart;
175
+ if (type === "datetime") {
176
+ return `${datePart} ${previousValue?.trim().split(/[ T]/)[1] ?? timePart}`;
177
+ }
178
+ return datePart;
179
+ }
180
+
181
+ // Every `previousValue` the web half can hand the datetime branch, including
182
+ // the shapes with no time part at all (where the pick's own clock is used).
183
+ const CARRIED = [
184
+ undefined,
185
+ "",
186
+ "2026-03-05",
187
+ "2026-03-05 14:30:00",
188
+ "2026-03-05T14:30:00",
189
+ "2026-03-05 14:30:00.123456",
190
+ " 2026-03-05 09:00:00 ",
191
+ ];
192
+
193
+ it("emits the identical string for every pick, shape and carried value", () => {
194
+ const disagreements: unknown[] = [];
195
+ for (const timeZone of ZONES) {
196
+ inZone(timeZone, () => {
197
+ for (const make of [
198
+ () => new Date(2026, 7, 15),
199
+ () => new Date(2026, 7, 15, 18, 30),
200
+ () => new Date(2026, 7, 15, 15, 0, 0),
201
+ () => new Date(2026, 0, 5, 9, 8, 7),
202
+ () => new Date(2026, 11, 31, 23, 59, 59),
203
+ ]) {
204
+ for (const type of ["date", "datetime", "time"] as const) {
205
+ for (const carried of CARRIED) {
206
+ const was = previousBody(make(), type, carried);
207
+ const now = toFrappeDateValue(make(), type, carried);
208
+ if (was !== now) disagreements.push({ timeZone, type, carried, was, now });
209
+ }
210
+ }
211
+ }
212
+ });
213
+ }
214
+ expect(disagreements).toEqual([]);
215
+ });
216
+ });
217
+
156
218
  describe("fromFrappeDateValue — the read side is local too", () => {
157
219
  it("opens a bare YYYY-MM-DD on local midnight in every timezone", () => {
158
220
  for (const timeZone of ZONES) {
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * The Date field's Frappe wire contract, in both directions — one body shared
3
- * by the web half (`index.tsx`) and the native half (`date.native.tsx`), so
3
+ * by the web half (`index.tsx`) and the native half (`index.native.tsx`), so
4
4
  * the same pick persists the same string on either platform.
5
5
  *
6
6
  * Frappe date fields are timezone-NAIVE: `Date` is a bare calendar date
7
7
  * (`YYYY-MM-DD`), `Datetime` is a bare local wall clock
8
8
  * (`YYYY-MM-DD HH:mm:ss`), `Time` is a bare clock (`HH:mm:ss`). None carries an
9
- * offset, and every other writer in the client already builds them from LOCAL
10
- * parts — `FrappeCalendar` / `FrappeGantt` `toFrappeFieldValue`, backoffice
11
- * `CalendarView.toFrappeDateValue`. So does the read side here, and so does
12
- * `parseCalendarDate` in `@multiplatform.one/theme`.
9
+ * offset, so both directions build from LOCAL parts: the write half through
10
+ * `formatCalendarDate` and the read half through the local-midnight branch
11
+ * below, matching `parseCalendarDate` all three in
12
+ * `@multiplatform.one/theme`, which is where the repo's one body for this
13
+ * lives.
13
14
  *
14
15
  * Serializing through `toISOString()` instead renders the UTC instant, which
15
16
  * breaks both halves of that contract: it names the wrong calendar day whenever
@@ -18,30 +19,29 @@
18
19
  * whole UTC offset, because the value is then read back as local wall clock.
19
20
  */
20
21
 
21
- const pad2 = (n: number) => String(n).padStart(2, "0");
22
+ import { formatCalendarDate } from "@multiplatform.one/theme";
22
23
 
23
24
  export type FrappeDateFieldType = "date" | "datetime" | "time";
24
25
 
25
26
  /**
26
27
  * Serialize a picked `Date` into the wire shape for the fieldtype.
27
28
  *
28
- * `previousValue` is for pickers that edit only the day: the web half wraps a
29
- * date-only calendar, so an existing time part survives the edit. The native
30
- * half picks the time too and passes nothing, in which case the datetime
31
- * branch uses the picked Date's own local time.
29
+ * `previousValue` is for a caller whose picker edits only the day, so that an
30
+ * existing time part survives the edit. NEITHER half passes it any more, and
31
+ * neither should: both now mount a picker that owns the whole value (the web
32
+ * datetime branch uses the forms `DatetimePicker`, which preserves an existing
33
+ * clock across a day change itself), so carrying the old time part here would
34
+ * overwrite a time the user had just set. It stays on the contract because the
35
+ * capability is real for a date-only writer, and it stays covered below.
32
36
  */
33
37
  export function toFrappeDateValue(
34
38
  date: Date,
35
39
  type: FrappeDateFieldType,
36
40
  previousValue?: string,
37
41
  ): string {
38
- const datePart = `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
39
- const timePart = `${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())}`;
40
- if (type === "time") return timePart;
41
- if (type === "datetime") {
42
- return `${datePart} ${previousValue?.trim().split(/[ T]/)[1] ?? timePart}`;
43
- }
44
- return datePart;
42
+ if (type !== "datetime") return formatCalendarDate(date, type);
43
+ const carriedTime = previousValue?.trim().split(/[ T]/)[1];
44
+ return `${formatCalendarDate(date, "date")} ${carriedTime ?? formatCalendarDate(date, "time")}`;
45
45
  }
46
46
 
47
47
  /**
@@ -52,6 +52,8 @@ export function DynamicLink({
52
52
  ? {
53
53
  baseURL: finalBaseURL,
54
54
  socketPort: config?.socketPort,
55
+ siteName: config?.siteName,
56
+ socketBaseURL: config?.socketBaseURL,
55
57
  auth: auth ?? config?.auth,
56
58
  syncOptions: config?.syncOptions,
57
59
  fixtures: config?.fixtures,