@wordpress/dataviews 10.2.0 → 10.3.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 (529) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +108 -1
  3. package/build/components/dataform/index.js +6 -2
  4. package/build/components/dataform/index.js.map +3 -3
  5. package/build/components/dataform-context/index.js +4 -2
  6. package/build/components/dataform-context/index.js.map +1 -1
  7. package/build/components/dataviews/index.js +8 -6
  8. package/build/components/dataviews/index.js.map +1 -1
  9. package/build/components/dataviews-bulk-actions/index.js +4 -2
  10. package/build/components/dataviews-bulk-actions/index.js.map +1 -1
  11. package/build/components/dataviews-context/index.js +3 -1
  12. package/build/components/dataviews-context/index.js.map +1 -1
  13. package/build/components/dataviews-filters/add-filter.js +4 -2
  14. package/build/components/dataviews-filters/add-filter.js.map +1 -1
  15. package/build/components/dataviews-filters/filter.js +20 -5
  16. package/build/components/dataviews-filters/filter.js.map +3 -3
  17. package/build/components/dataviews-filters/filters-toggled.js +3 -1
  18. package/build/components/dataviews-filters/filters-toggled.js.map +1 -1
  19. package/build/components/dataviews-filters/filters.js +3 -1
  20. package/build/components/dataviews-filters/filters.js.map +1 -1
  21. package/build/components/dataviews-filters/index.js +2 -0
  22. package/build/components/dataviews-filters/index.js.map +1 -1
  23. package/build/components/dataviews-filters/input-widget.js +3 -1
  24. package/build/components/dataviews-filters/input-widget.js.map +1 -1
  25. package/build/components/dataviews-filters/reset-filters.js +3 -1
  26. package/build/components/dataviews-filters/reset-filters.js.map +1 -1
  27. package/build/components/dataviews-filters/search-widget.js +8 -6
  28. package/build/components/dataviews-filters/search-widget.js.map +2 -2
  29. package/build/components/dataviews-filters/toggle.js +3 -1
  30. package/build/components/dataviews-filters/toggle.js.map +1 -1
  31. package/build/components/dataviews-filters/use-filters.js +2 -0
  32. package/build/components/dataviews-filters/use-filters.js.map +1 -1
  33. package/build/components/dataviews-filters/utils.js +4 -2
  34. package/build/components/dataviews-filters/utils.js.map +1 -1
  35. package/build/components/dataviews-footer/index.js +4 -2
  36. package/build/components/dataviews-footer/index.js.map +1 -1
  37. package/build/components/dataviews-item-actions/index.js +23 -4
  38. package/build/components/dataviews-item-actions/index.js.map +2 -2
  39. package/build/components/dataviews-layout/index.js +3 -1
  40. package/build/components/dataviews-layout/index.js.map +1 -1
  41. package/build/components/dataviews-pagination/index.js +3 -1
  42. package/build/components/dataviews-pagination/index.js.map +1 -1
  43. package/build/components/dataviews-picker/footer.js +4 -2
  44. package/build/components/dataviews-picker/footer.js.map +1 -1
  45. package/build/components/dataviews-picker/index.js +8 -6
  46. package/build/components/dataviews-picker/index.js.map +1 -1
  47. package/build/components/dataviews-search/index.js +4 -2
  48. package/build/components/dataviews-search/index.js.map +1 -1
  49. package/build/components/dataviews-selection-checkbox/index.js +3 -1
  50. package/build/components/dataviews-selection-checkbox/index.js.map +1 -1
  51. package/build/components/dataviews-view-config/index.js +17 -400
  52. package/build/components/dataviews-view-config/index.js.map +3 -3
  53. package/build/components/dataviews-view-config/infinite-scroll-toggle.js +3 -1
  54. package/build/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -1
  55. package/build/components/dataviews-view-config/properties-section.js +177 -0
  56. package/build/components/dataviews-view-config/properties-section.js.map +7 -0
  57. package/build/constants.js +39 -34
  58. package/build/constants.js.map +2 -2
  59. package/build/dataform-controls/array.js +4 -2
  60. package/build/dataform-controls/array.js.map +1 -1
  61. package/build/dataform-controls/checkbox.js +4 -2
  62. package/build/dataform-controls/checkbox.js.map +1 -1
  63. package/build/dataform-controls/color.js +5 -3
  64. package/build/dataform-controls/color.js.map +1 -1
  65. package/build/dataform-controls/date.js +8 -6
  66. package/build/dataform-controls/date.js.map +1 -1
  67. package/build/dataform-controls/datetime.js +11 -15
  68. package/build/dataform-controls/datetime.js.map +3 -3
  69. package/build/dataform-controls/email.js +3 -1
  70. package/build/dataform-controls/email.js.map +1 -1
  71. package/build/dataform-controls/index.js +4 -2
  72. package/build/dataform-controls/index.js.map +1 -1
  73. package/build/dataform-controls/integer.js +3 -1
  74. package/build/dataform-controls/integer.js.map +1 -1
  75. package/build/dataform-controls/number.js +3 -1
  76. package/build/dataform-controls/number.js.map +1 -1
  77. package/build/dataform-controls/password.js +3 -1
  78. package/build/dataform-controls/password.js.map +1 -1
  79. package/build/dataform-controls/radio.js +4 -2
  80. package/build/dataform-controls/radio.js.map +1 -1
  81. package/build/dataform-controls/select.js +4 -2
  82. package/build/dataform-controls/select.js.map +1 -1
  83. package/build/dataform-controls/telephone.js +3 -1
  84. package/build/dataform-controls/telephone.js.map +1 -1
  85. package/build/dataform-controls/text.js +3 -1
  86. package/build/dataform-controls/text.js.map +1 -1
  87. package/build/dataform-controls/textarea.js +4 -2
  88. package/build/dataform-controls/textarea.js.map +1 -1
  89. package/build/dataform-controls/toggle-group.js +4 -2
  90. package/build/dataform-controls/toggle-group.js.map +1 -1
  91. package/build/dataform-controls/toggle.js +4 -2
  92. package/build/dataform-controls/toggle.js.map +1 -1
  93. package/build/dataform-controls/url.js +3 -1
  94. package/build/dataform-controls/url.js.map +1 -1
  95. package/build/dataform-controls/utils/get-custom-validity.js +2 -0
  96. package/build/dataform-controls/utils/get-custom-validity.js.map +1 -1
  97. package/build/dataform-controls/utils/relative-date-control.js +4 -2
  98. package/build/dataform-controls/utils/relative-date-control.js.map +1 -1
  99. package/build/dataform-controls/utils/validated-input.js +4 -2
  100. package/build/dataform-controls/utils/validated-input.js.map +1 -1
  101. package/build/dataform-controls/utils/validated-number.js +4 -2
  102. package/build/dataform-controls/utils/validated-number.js.map +1 -1
  103. package/build/dataform-layouts/card/index.js +65 -34
  104. package/build/dataform-layouts/card/index.js.map +3 -3
  105. package/build/dataform-layouts/data-form-layout.js +8 -14
  106. package/build/dataform-layouts/data-form-layout.js.map +3 -3
  107. package/build/dataform-layouts/details/index.js +78 -0
  108. package/build/dataform-layouts/details/index.js.map +7 -0
  109. package/build/dataform-layouts/get-summary-fields.js +3 -1
  110. package/build/dataform-layouts/get-summary-fields.js.map +1 -1
  111. package/build/dataform-layouts/index.js +9 -2
  112. package/build/dataform-layouts/index.js.map +3 -3
  113. package/build/dataform-layouts/{normalize-form-fields.js → normalize-form.js} +44 -22
  114. package/build/dataform-layouts/normalize-form.js.map +7 -0
  115. package/build/dataform-layouts/panel/dropdown.js +9 -8
  116. package/build/dataform-layouts/panel/dropdown.js.map +2 -2
  117. package/build/dataform-layouts/panel/index.js +10 -14
  118. package/build/dataform-layouts/panel/index.js.map +2 -2
  119. package/build/dataform-layouts/panel/modal.js +9 -8
  120. package/build/dataform-layouts/panel/modal.js.map +2 -2
  121. package/build/dataform-layouts/panel/summary-button.js +3 -1
  122. package/build/dataform-layouts/panel/summary-button.js.map +1 -1
  123. package/build/dataform-layouts/regular/index.js +8 -10
  124. package/build/dataform-layouts/regular/index.js.map +2 -2
  125. package/build/dataform-layouts/row/index.js +10 -33
  126. package/build/dataform-layouts/row/index.js.map +3 -3
  127. package/build/dataviews-layouts/grid/index.js +4 -2
  128. package/build/dataviews-layouts/grid/index.js.map +1 -1
  129. package/build/dataviews-layouts/grid/preview-size-picker.js +4 -2
  130. package/build/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  131. package/build/dataviews-layouts/index.js +12 -1
  132. package/build/dataviews-layouts/index.js.map +3 -3
  133. package/build/dataviews-layouts/list/index.js +6 -6
  134. package/build/dataviews-layouts/list/index.js.map +2 -2
  135. package/build/dataviews-layouts/picker-grid/index.js +4 -2
  136. package/build/dataviews-layouts/picker-grid/index.js.map +1 -1
  137. package/build/dataviews-layouts/picker-table/index.js +422 -0
  138. package/build/dataviews-layouts/picker-table/index.js.map +7 -0
  139. package/build/dataviews-layouts/table/column-header-menu.js +6 -4
  140. package/build/dataviews-layouts/table/column-header-menu.js.map +2 -2
  141. package/build/dataviews-layouts/table/column-primary.js +4 -7
  142. package/build/dataviews-layouts/table/column-primary.js.map +2 -2
  143. package/build/dataviews-layouts/table/density-picker.js +3 -1
  144. package/build/dataviews-layouts/table/density-picker.js.map +1 -1
  145. package/build/dataviews-layouts/table/index.js +48 -2
  146. package/build/dataviews-layouts/table/index.js.map +2 -2
  147. package/build/dataviews-layouts/table/use-is-horizontal-scroll-end.js +3 -1
  148. package/build/dataviews-layouts/table/use-is-horizontal-scroll-end.js.map +1 -1
  149. package/build/dataviews-layouts/utils/get-data-by-group.js +2 -0
  150. package/build/dataviews-layouts/utils/get-data-by-group.js.map +1 -1
  151. package/build/dataviews-layouts/utils/grid-items.js +4 -2
  152. package/build/dataviews-layouts/utils/grid-items.js.map +1 -1
  153. package/build/dataviews-layouts/utils/item-click-wrapper.js +3 -1
  154. package/build/dataviews-layouts/utils/item-click-wrapper.js.map +1 -1
  155. package/build/dataviews-layouts/utils/preview-size-picker.js +4 -2
  156. package/build/dataviews-layouts/utils/preview-size-picker.js.map +1 -1
  157. package/build/field-types/array.js +3 -1
  158. package/build/field-types/array.js.map +1 -1
  159. package/build/field-types/boolean.js +3 -1
  160. package/build/field-types/boolean.js.map +1 -1
  161. package/build/field-types/color.js +3 -1
  162. package/build/field-types/color.js.map +1 -1
  163. package/build/field-types/date.js +4 -2
  164. package/build/field-types/date.js.map +1 -1
  165. package/build/field-types/datetime.js +17 -2
  166. package/build/field-types/datetime.js.map +3 -3
  167. package/build/field-types/email.js +4 -2
  168. package/build/field-types/email.js.map +1 -1
  169. package/build/field-types/index.js +3 -1
  170. package/build/field-types/index.js.map +1 -1
  171. package/build/field-types/integer.js +3 -1
  172. package/build/field-types/integer.js.map +1 -1
  173. package/build/field-types/media.js +2 -0
  174. package/build/field-types/media.js.map +1 -1
  175. package/build/field-types/number.js +3 -1
  176. package/build/field-types/number.js.map +1 -1
  177. package/build/field-types/password.js +3 -1
  178. package/build/field-types/password.js.map +1 -1
  179. package/build/field-types/telephone.js +3 -1
  180. package/build/field-types/telephone.js.map +1 -1
  181. package/build/field-types/text.js +3 -1
  182. package/build/field-types/text.js.map +1 -1
  183. package/build/field-types/url.js +3 -1
  184. package/build/field-types/url.js.map +1 -1
  185. package/build/{dataform-layouts/is-combined-field.js → field-types/utils/parse-date-time.js} +15 -11
  186. package/build/field-types/utils/parse-date-time.js.map +7 -0
  187. package/build/field-types/utils/render-from-elements.js +2 -0
  188. package/build/field-types/utils/render-from-elements.js.map +1 -1
  189. package/build/hooks/index.js +2 -0
  190. package/build/hooks/index.js.map +1 -1
  191. package/build/hooks/use-elements.js +3 -1
  192. package/build/hooks/use-elements.js.map +1 -1
  193. package/build/hooks/use-form-validity.js +425 -321
  194. package/build/hooks/use-form-validity.js.map +3 -3
  195. package/build/index.js +2 -0
  196. package/build/index.js.map +1 -1
  197. package/build/lock-unlock.js +3 -1
  198. package/build/lock-unlock.js.map +1 -1
  199. package/build/types/dataform.js +2 -0
  200. package/build/types/dataform.js.map +2 -2
  201. package/build/types/dataviews.js +2 -0
  202. package/build/types/dataviews.js.map +2 -2
  203. package/build/types/field-api.js +2 -0
  204. package/build/types/field-api.js.map +1 -1
  205. package/build/types/index.js +2 -0
  206. package/build/types/index.js.map +1 -1
  207. package/build/types/private.js +2 -0
  208. package/build/types/private.js.map +1 -1
  209. package/build/utils/filter-sort-and-paginate.js +3 -1
  210. package/build/utils/filter-sort-and-paginate.js.map +1 -1
  211. package/build/utils/has-elements.js +2 -0
  212. package/build/utils/has-elements.js.map +1 -1
  213. package/build/utils/normalize-fields.js +4 -2
  214. package/build/utils/normalize-fields.js.map +1 -1
  215. package/build-module/components/dataform/index.js +5 -2
  216. package/build-module/components/dataform/index.js.map +2 -2
  217. package/build-module/components/dataform-context/index.js +3 -2
  218. package/build-module/components/dataform-context/index.js.map +1 -1
  219. package/build-module/components/dataviews/index.js +7 -6
  220. package/build-module/components/dataviews/index.js.map +1 -1
  221. package/build-module/components/dataviews-bulk-actions/index.js +3 -2
  222. package/build-module/components/dataviews-bulk-actions/index.js.map +1 -1
  223. package/build-module/components/dataviews-context/index.js +2 -1
  224. package/build-module/components/dataviews-context/index.js.map +1 -1
  225. package/build-module/components/dataviews-filters/add-filter.js +3 -2
  226. package/build-module/components/dataviews-filters/add-filter.js.map +1 -1
  227. package/build-module/components/dataviews-filters/filter.js +19 -5
  228. package/build-module/components/dataviews-filters/filter.js.map +2 -2
  229. package/build-module/components/dataviews-filters/filters-toggled.js +2 -1
  230. package/build-module/components/dataviews-filters/filters-toggled.js.map +1 -1
  231. package/build-module/components/dataviews-filters/filters.js +2 -1
  232. package/build-module/components/dataviews-filters/filters.js.map +1 -1
  233. package/build-module/components/dataviews-filters/index.js +1 -0
  234. package/build-module/components/dataviews-filters/index.js.map +1 -1
  235. package/build-module/components/dataviews-filters/input-widget.js +2 -1
  236. package/build-module/components/dataviews-filters/input-widget.js.map +1 -1
  237. package/build-module/components/dataviews-filters/reset-filters.js +2 -1
  238. package/build-module/components/dataviews-filters/reset-filters.js.map +1 -1
  239. package/build-module/components/dataviews-filters/search-widget.js +7 -6
  240. package/build-module/components/dataviews-filters/search-widget.js.map +2 -2
  241. package/build-module/components/dataviews-filters/toggle.js +2 -1
  242. package/build-module/components/dataviews-filters/toggle.js.map +1 -1
  243. package/build-module/components/dataviews-filters/use-filters.js +1 -0
  244. package/build-module/components/dataviews-filters/use-filters.js.map +1 -1
  245. package/build-module/components/dataviews-filters/utils.js +3 -2
  246. package/build-module/components/dataviews-filters/utils.js.map +1 -1
  247. package/build-module/components/dataviews-footer/index.js +3 -2
  248. package/build-module/components/dataviews-footer/index.js.map +1 -1
  249. package/build-module/components/dataviews-item-actions/index.js +22 -4
  250. package/build-module/components/dataviews-item-actions/index.js.map +2 -2
  251. package/build-module/components/dataviews-layout/index.js +2 -1
  252. package/build-module/components/dataviews-layout/index.js.map +1 -1
  253. package/build-module/components/dataviews-pagination/index.js +2 -1
  254. package/build-module/components/dataviews-pagination/index.js.map +1 -1
  255. package/build-module/components/dataviews-picker/footer.js +3 -2
  256. package/build-module/components/dataviews-picker/footer.js.map +1 -1
  257. package/build-module/components/dataviews-picker/index.js +7 -6
  258. package/build-module/components/dataviews-picker/index.js.map +1 -1
  259. package/build-module/components/dataviews-search/index.js +3 -2
  260. package/build-module/components/dataviews-search/index.js.map +1 -1
  261. package/build-module/components/dataviews-selection-checkbox/index.js +2 -1
  262. package/build-module/components/dataviews-selection-checkbox/index.js.map +1 -1
  263. package/build-module/components/dataviews-view-config/index.js +20 -416
  264. package/build-module/components/dataviews-view-config/index.js.map +2 -2
  265. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js +2 -1
  266. package/build-module/components/dataviews-view-config/infinite-scroll-toggle.js.map +1 -1
  267. package/build-module/components/dataviews-view-config/properties-section.js +149 -0
  268. package/build-module/components/dataviews-view-config/properties-section.js.map +7 -0
  269. package/build-module/constants.js +37 -34
  270. package/build-module/constants.js.map +2 -2
  271. package/build-module/dataform-controls/array.js +3 -2
  272. package/build-module/dataform-controls/array.js.map +1 -1
  273. package/build-module/dataform-controls/checkbox.js +3 -2
  274. package/build-module/dataform-controls/checkbox.js.map +1 -1
  275. package/build-module/dataform-controls/color.js +4 -3
  276. package/build-module/dataform-controls/color.js.map +1 -1
  277. package/build-module/dataform-controls/date.js +7 -6
  278. package/build-module/dataform-controls/date.js.map +1 -1
  279. package/build-module/dataform-controls/datetime.js +7 -12
  280. package/build-module/dataform-controls/datetime.js.map +2 -2
  281. package/build-module/dataform-controls/email.js +2 -1
  282. package/build-module/dataform-controls/email.js.map +1 -1
  283. package/build-module/dataform-controls/index.js +3 -2
  284. package/build-module/dataform-controls/index.js.map +1 -1
  285. package/build-module/dataform-controls/integer.js +2 -1
  286. package/build-module/dataform-controls/integer.js.map +1 -1
  287. package/build-module/dataform-controls/number.js +2 -1
  288. package/build-module/dataform-controls/number.js.map +1 -1
  289. package/build-module/dataform-controls/password.js +2 -1
  290. package/build-module/dataform-controls/password.js.map +1 -1
  291. package/build-module/dataform-controls/radio.js +3 -2
  292. package/build-module/dataform-controls/radio.js.map +1 -1
  293. package/build-module/dataform-controls/select.js +3 -2
  294. package/build-module/dataform-controls/select.js.map +1 -1
  295. package/build-module/dataform-controls/telephone.js +2 -1
  296. package/build-module/dataform-controls/telephone.js.map +1 -1
  297. package/build-module/dataform-controls/text.js +2 -1
  298. package/build-module/dataform-controls/text.js.map +1 -1
  299. package/build-module/dataform-controls/textarea.js +3 -2
  300. package/build-module/dataform-controls/textarea.js.map +1 -1
  301. package/build-module/dataform-controls/toggle-group.js +3 -2
  302. package/build-module/dataform-controls/toggle-group.js.map +1 -1
  303. package/build-module/dataform-controls/toggle.js +3 -2
  304. package/build-module/dataform-controls/toggle.js.map +1 -1
  305. package/build-module/dataform-controls/url.js +2 -1
  306. package/build-module/dataform-controls/url.js.map +1 -1
  307. package/build-module/dataform-controls/utils/get-custom-validity.js +1 -0
  308. package/build-module/dataform-controls/utils/get-custom-validity.js.map +1 -1
  309. package/build-module/dataform-controls/utils/relative-date-control.js +3 -2
  310. package/build-module/dataform-controls/utils/relative-date-control.js.map +1 -1
  311. package/build-module/dataform-controls/utils/validated-input.js +3 -2
  312. package/build-module/dataform-controls/utils/validated-input.js.map +1 -1
  313. package/build-module/dataform-controls/utils/validated-number.js +3 -2
  314. package/build-module/dataform-controls/utils/validated-number.js.map +1 -1
  315. package/build-module/dataform-layouts/card/index.js +63 -33
  316. package/build-module/dataform-layouts/card/index.js.map +3 -3
  317. package/build-module/dataform-layouts/data-form-layout.js +8 -15
  318. package/build-module/dataform-layouts/data-form-layout.js.map +2 -2
  319. package/build-module/dataform-layouts/details/index.js +47 -0
  320. package/build-module/dataform-layouts/details/index.js.map +7 -0
  321. package/build-module/dataform-layouts/get-summary-fields.js +2 -1
  322. package/build-module/dataform-layouts/get-summary-fields.js.map +1 -1
  323. package/build-module/dataform-layouts/index.js +8 -2
  324. package/build-module/dataform-layouts/index.js.map +2 -2
  325. package/build-module/dataform-layouts/{normalize-form-fields.js → normalize-form.js} +39 -17
  326. package/build-module/dataform-layouts/normalize-form.js.map +7 -0
  327. package/build-module/dataform-layouts/panel/dropdown.js +7 -7
  328. package/build-module/dataform-layouts/panel/dropdown.js.map +2 -2
  329. package/build-module/dataform-layouts/panel/index.js +9 -14
  330. package/build-module/dataform-layouts/panel/index.js.map +2 -2
  331. package/build-module/dataform-layouts/panel/modal.js +7 -7
  332. package/build-module/dataform-layouts/panel/modal.js.map +2 -2
  333. package/build-module/dataform-layouts/panel/summary-button.js +2 -1
  334. package/build-module/dataform-layouts/panel/summary-button.js.map +1 -1
  335. package/build-module/dataform-layouts/regular/index.js +6 -9
  336. package/build-module/dataform-layouts/regular/index.js.map +2 -2
  337. package/build-module/dataform-layouts/row/index.js +9 -23
  338. package/build-module/dataform-layouts/row/index.js.map +2 -2
  339. package/build-module/dataviews-layouts/grid/index.js +3 -2
  340. package/build-module/dataviews-layouts/grid/index.js.map +1 -1
  341. package/build-module/dataviews-layouts/grid/preview-size-picker.js +3 -2
  342. package/build-module/dataviews-layouts/grid/preview-size-picker.js.map +1 -1
  343. package/build-module/dataviews-layouts/index.js +13 -2
  344. package/build-module/dataviews-layouts/index.js.map +2 -2
  345. package/build-module/dataviews-layouts/list/index.js +5 -6
  346. package/build-module/dataviews-layouts/list/index.js.map +2 -2
  347. package/build-module/dataviews-layouts/picker-grid/index.js +3 -2
  348. package/build-module/dataviews-layouts/picker-grid/index.js.map +1 -1
  349. package/build-module/dataviews-layouts/picker-table/index.js +397 -0
  350. package/build-module/dataviews-layouts/picker-table/index.js.map +7 -0
  351. package/build-module/dataviews-layouts/table/column-header-menu.js +5 -4
  352. package/build-module/dataviews-layouts/table/column-header-menu.js.map +2 -2
  353. package/build-module/dataviews-layouts/table/column-primary.js +3 -7
  354. package/build-module/dataviews-layouts/table/column-primary.js.map +2 -2
  355. package/build-module/dataviews-layouts/table/density-picker.js +2 -1
  356. package/build-module/dataviews-layouts/table/density-picker.js.map +1 -1
  357. package/build-module/dataviews-layouts/table/index.js +48 -3
  358. package/build-module/dataviews-layouts/table/index.js.map +2 -2
  359. package/build-module/dataviews-layouts/table/use-is-horizontal-scroll-end.js +2 -1
  360. package/build-module/dataviews-layouts/table/use-is-horizontal-scroll-end.js.map +1 -1
  361. package/build-module/dataviews-layouts/utils/get-data-by-group.js +1 -0
  362. package/build-module/dataviews-layouts/utils/get-data-by-group.js.map +1 -1
  363. package/build-module/dataviews-layouts/utils/grid-items.js +3 -2
  364. package/build-module/dataviews-layouts/utils/grid-items.js.map +1 -1
  365. package/build-module/dataviews-layouts/utils/item-click-wrapper.js +2 -1
  366. package/build-module/dataviews-layouts/utils/item-click-wrapper.js.map +1 -1
  367. package/build-module/dataviews-layouts/utils/preview-size-picker.js +3 -2
  368. package/build-module/dataviews-layouts/utils/preview-size-picker.js.map +1 -1
  369. package/build-module/field-types/array.js +2 -1
  370. package/build-module/field-types/array.js.map +1 -1
  371. package/build-module/field-types/boolean.js +2 -1
  372. package/build-module/field-types/boolean.js.map +1 -1
  373. package/build-module/field-types/color.js +2 -1
  374. package/build-module/field-types/color.js.map +1 -1
  375. package/build-module/field-types/date.js +3 -2
  376. package/build-module/field-types/date.js.map +1 -1
  377. package/build-module/field-types/datetime.js +16 -2
  378. package/build-module/field-types/datetime.js.map +2 -2
  379. package/build-module/field-types/email.js +3 -2
  380. package/build-module/field-types/email.js.map +1 -1
  381. package/build-module/field-types/index.js +2 -1
  382. package/build-module/field-types/index.js.map +1 -1
  383. package/build-module/field-types/integer.js +2 -1
  384. package/build-module/field-types/integer.js.map +1 -1
  385. package/build-module/field-types/media.js +1 -0
  386. package/build-module/field-types/media.js.map +1 -1
  387. package/build-module/field-types/number.js +2 -1
  388. package/build-module/field-types/number.js.map +1 -1
  389. package/build-module/field-types/password.js +2 -1
  390. package/build-module/field-types/password.js.map +1 -1
  391. package/build-module/field-types/telephone.js +2 -1
  392. package/build-module/field-types/telephone.js.map +1 -1
  393. package/build-module/field-types/text.js +2 -1
  394. package/build-module/field-types/text.js.map +1 -1
  395. package/build-module/field-types/url.js +2 -1
  396. package/build-module/field-types/url.js.map +1 -1
  397. package/build-module/field-types/utils/parse-date-time.js +14 -0
  398. package/build-module/field-types/utils/parse-date-time.js.map +7 -0
  399. package/build-module/field-types/utils/render-from-elements.js +1 -0
  400. package/build-module/field-types/utils/render-from-elements.js.map +1 -1
  401. package/build-module/hooks/index.js +1 -0
  402. package/build-module/hooks/index.js.map +1 -1
  403. package/build-module/hooks/use-elements.js +2 -1
  404. package/build-module/hooks/use-elements.js.map +1 -1
  405. package/build-module/hooks/use-form-validity.js +424 -321
  406. package/build-module/hooks/use-form-validity.js.map +3 -3
  407. package/build-module/index.js +1 -0
  408. package/build-module/index.js.map +1 -1
  409. package/build-module/lock-unlock.js +2 -1
  410. package/build-module/lock-unlock.js.map +1 -1
  411. package/build-module/utils/filter-sort-and-paginate.js +2 -1
  412. package/build-module/utils/filter-sort-and-paginate.js.map +1 -1
  413. package/build-module/utils/has-elements.js +1 -0
  414. package/build-module/utils/has-elements.js.map +1 -1
  415. package/build-module/utils/normalize-fields.js +3 -2
  416. package/build-module/utils/normalize-fields.js.map +1 -1
  417. package/build-style/style-rtl.css +35 -40
  418. package/build-style/style.css +35 -40
  419. package/build-types/components/dataform/index.d.ts.map +1 -1
  420. package/build-types/components/dataviews-filters/filter.d.ts.map +1 -1
  421. package/build-types/components/dataviews-filters/utils.d.ts.map +1 -1
  422. package/build-types/components/dataviews-item-actions/index.d.ts.map +1 -1
  423. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  424. package/build-types/components/dataviews-view-config/properties-section.d.ts +4 -0
  425. package/build-types/components/dataviews-view-config/properties-section.d.ts.map +1 -0
  426. package/build-types/constants.d.ts +1 -0
  427. package/build-types/constants.d.ts.map +1 -1
  428. package/build-types/dataform-controls/datetime.d.ts.map +1 -1
  429. package/build-types/dataform-layouts/card/index.d.ts +1 -1
  430. package/build-types/dataform-layouts/card/index.d.ts.map +1 -1
  431. package/build-types/dataform-layouts/data-form-layout.d.ts +4 -4
  432. package/build-types/dataform-layouts/data-form-layout.d.ts.map +1 -1
  433. package/build-types/dataform-layouts/details/index.d.ts +6 -0
  434. package/build-types/dataform-layouts/details/index.d.ts.map +1 -0
  435. package/build-types/dataform-layouts/get-summary-fields.d.ts.map +1 -1
  436. package/build-types/dataform-layouts/index.d.ts +7 -2
  437. package/build-types/dataform-layouts/index.d.ts.map +1 -1
  438. package/build-types/dataform-layouts/normalize-form.d.ts +8 -0
  439. package/build-types/dataform-layouts/normalize-form.d.ts.map +1 -0
  440. package/build-types/dataform-layouts/panel/dropdown.d.ts +2 -2
  441. package/build-types/dataform-layouts/panel/dropdown.d.ts.map +1 -1
  442. package/build-types/dataform-layouts/panel/index.d.ts.map +1 -1
  443. package/build-types/dataform-layouts/panel/modal.d.ts +2 -2
  444. package/build-types/dataform-layouts/panel/modal.d.ts.map +1 -1
  445. package/build-types/dataform-layouts/regular/index.d.ts.map +1 -1
  446. package/build-types/dataform-layouts/row/index.d.ts.map +1 -1
  447. package/build-types/dataviews-layouts/index.d.ts +8 -0
  448. package/build-types/dataviews-layouts/index.d.ts.map +1 -1
  449. package/build-types/dataviews-layouts/list/index.d.ts.map +1 -1
  450. package/build-types/dataviews-layouts/picker-table/index.d.ts +4 -0
  451. package/build-types/dataviews-layouts/picker-table/index.d.ts.map +1 -0
  452. package/build-types/dataviews-layouts/table/column-header-menu.d.ts +3 -3
  453. package/build-types/dataviews-layouts/table/column-header-menu.d.ts.map +1 -1
  454. package/build-types/dataviews-layouts/table/column-primary.d.ts.map +1 -1
  455. package/build-types/dataviews-layouts/table/index.d.ts.map +1 -1
  456. package/build-types/field-types/datetime.d.ts +1 -1
  457. package/build-types/field-types/datetime.d.ts.map +1 -1
  458. package/build-types/field-types/utils/parse-date-time.d.ts +2 -0
  459. package/build-types/field-types/utils/parse-date-time.d.ts.map +1 -0
  460. package/build-types/hooks/use-form-validity.d.ts.map +1 -1
  461. package/build-types/stories/dataform.story.d.ts +4 -10
  462. package/build-types/stories/dataform.story.d.ts.map +1 -1
  463. package/build-types/stories/dataviews-picker.story.d.ts +33 -0
  464. package/build-types/stories/dataviews-picker.story.d.ts.map +1 -1
  465. package/build-types/stories/dataviews.fixtures.d.ts.map +1 -1
  466. package/build-types/stories/dataviews.story.d.ts +7 -1
  467. package/build-types/stories/dataviews.story.d.ts.map +1 -1
  468. package/build-types/test/normalize-form.d.ts +2 -0
  469. package/build-types/test/normalize-form.d.ts.map +1 -0
  470. package/build-types/types/dataform.d.ts +23 -9
  471. package/build-types/types/dataform.d.ts.map +1 -1
  472. package/build-types/types/dataviews.d.ts +23 -2
  473. package/build-types/types/dataviews.d.ts.map +1 -1
  474. package/build-wp/index.js +2101 -1673
  475. package/package.json +15 -15
  476. package/src/components/dataform/index.tsx +3 -1
  477. package/src/components/dataviews-filters/filter.tsx +16 -1
  478. package/src/components/dataviews-item-actions/index.tsx +37 -14
  479. package/src/components/dataviews-view-config/index.tsx +8 -504
  480. package/src/components/dataviews-view-config/properties-section.tsx +201 -0
  481. package/src/components/dataviews-view-config/style.scss +2 -39
  482. package/src/constants.ts +1 -0
  483. package/src/dataform-controls/datetime.tsx +3 -10
  484. package/src/dataform-layouts/card/index.tsx +45 -21
  485. package/src/dataform-layouts/data-form-layout.tsx +12 -23
  486. package/src/dataform-layouts/details/index.tsx +71 -0
  487. package/src/dataform-layouts/details/style.scss +5 -0
  488. package/src/dataform-layouts/index.tsx +10 -3
  489. package/src/dataform-layouts/{normalize-form-fields.ts → normalize-form.ts} +45 -23
  490. package/src/dataform-layouts/panel/dropdown.tsx +10 -13
  491. package/src/dataform-layouts/panel/index.tsx +9 -24
  492. package/src/dataform-layouts/panel/modal.tsx +15 -15
  493. package/src/dataform-layouts/regular/index.tsx +7 -12
  494. package/src/dataform-layouts/row/index.tsx +13 -26
  495. package/src/dataviews-layouts/index.ts +10 -0
  496. package/src/dataviews-layouts/list/index.tsx +2 -5
  497. package/src/dataviews-layouts/picker-table/index.tsx +487 -0
  498. package/src/dataviews-layouts/picker-table/style.scss +45 -0
  499. package/src/dataviews-layouts/table/column-header-menu.tsx +3 -2
  500. package/src/dataviews-layouts/table/column-primary.tsx +4 -7
  501. package/src/dataviews-layouts/table/index.tsx +54 -2
  502. package/src/dataviews-layouts/table/style.scss +6 -1
  503. package/src/field-types/datetime.tsx +16 -5
  504. package/src/field-types/utils/parse-date-time.ts +17 -0
  505. package/src/hooks/use-form-validity.ts +572 -422
  506. package/src/stories/dataform.story.tsx +586 -454
  507. package/src/stories/dataviews-picker.story.tsx +166 -38
  508. package/src/stories/dataviews.fixtures.tsx +4 -1
  509. package/src/stories/dataviews.story.tsx +10 -2
  510. package/src/stories/field-types.story.tsx +7 -7
  511. package/src/style.scss +2 -0
  512. package/src/test/normalize-form.ts +568 -0
  513. package/src/test/use-form-validity.ts +318 -33
  514. package/src/types/dataform.ts +30 -11
  515. package/src/types/dataviews.ts +36 -2
  516. package/tsconfig.tsbuildinfo +1 -1
  517. package/build/dataform-layouts/is-combined-field.js.map +0 -7
  518. package/build/dataform-layouts/normalize-form-fields.js.map +0 -7
  519. package/build-module/dataform-layouts/is-combined-field.js +0 -7
  520. package/build-module/dataform-layouts/is-combined-field.js.map +0 -7
  521. package/build-module/dataform-layouts/normalize-form-fields.js.map +0 -7
  522. package/build-types/dataform-layouts/is-combined-field.d.ts +0 -6
  523. package/build-types/dataform-layouts/is-combined-field.d.ts.map +0 -1
  524. package/build-types/dataform-layouts/normalize-form-fields.d.ts +0 -19
  525. package/build-types/dataform-layouts/normalize-form-fields.d.ts.map +0 -1
  526. package/build-types/test/normalize-form-fields.d.ts +0 -2
  527. package/build-types/test/normalize-form-fields.d.ts.map +0 -1
  528. package/src/dataform-layouts/is-combined-field.ts +0 -10
  529. package/src/test/normalize-form-fields.ts +0 -324
@@ -2,6 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import deepMerge from 'deepmerge';
5
+ import fastDeepEqual from 'fast-deep-equal/es6';
5
6
 
6
7
  /**
7
8
  * WordPress dependencies
@@ -13,14 +14,14 @@ import { __ } from '@wordpress/i18n';
13
14
  * Internal dependencies
14
15
  */
15
16
  import normalizeFields from '../utils/normalize-fields';
16
- import normalizeFormFields from '../dataform-layouts/normalize-form-fields';
17
+ import normalizeForm from '../dataform-layouts/normalize-form';
17
18
  import type {
18
- CombinedFormField,
19
19
  Field,
20
20
  FieldValidity,
21
21
  Form,
22
22
  FormValidity,
23
23
  NormalizedField,
24
+ NormalizedFormField,
24
25
  } from '../types';
25
26
  const isEmptyNullOrUndefined = ( value: any ) =>
26
27
  [ undefined, '', null ].includes( value );
@@ -75,486 +76,635 @@ function isFormValid( formValidity: FormValidity | undefined ): boolean {
75
76
  } );
76
77
  }
77
78
 
78
- function updateFieldValidity(
79
- setFormValidity: React.Dispatch< React.SetStateAction< FormValidity > >,
80
- parentFieldId: string | undefined,
81
- fieldId: string,
82
- newValidity: FieldValidity
83
- ) {
84
- if ( parentFieldId ) {
85
- setFormValidity( ( prev ) => ( {
86
- ...prev,
87
- [ parentFieldId ]: {
88
- ...prev?.[ parentFieldId ],
89
- children: {
90
- ...prev?.[ parentFieldId ]?.children,
91
- [ fieldId ]: {
92
- ...newValidity,
93
- },
94
- },
95
- },
96
- } ) );
97
- } else {
98
- setFormValidity( ( prev ) => ( {
99
- ...prev,
100
- [ fieldId ]: {
101
- ...newValidity,
102
- },
103
- } ) );
104
- }
105
- }
79
+ type FormFieldToValidate< Item > = {
80
+ id: string;
81
+ children: FormFieldToValidate< Item >[];
82
+ field?: NormalizedField< Item >;
83
+ };
106
84
 
107
- function getFieldsToValidate< Item >(
108
- fields: Field< Item >[],
109
- form: Form
110
- ): {
111
- fields: NormalizedField< Item >[];
112
- fieldToParent: Map< string, string >;
113
- } {
114
- const formFields = normalizeFormFields( form );
115
- if ( formFields.length === 0 ) {
116
- return { fields: [], fieldToParent: new Map() };
85
+ function getFormFieldsToValidate< Item >(
86
+ form: Form,
87
+ fields: Field< Item >[]
88
+ ): FormFieldToValidate< Item >[] {
89
+ const normalizedForm = normalizeForm( form );
90
+ if ( normalizedForm.fields.length === 0 ) {
91
+ return [];
117
92
  }
118
93
 
119
- const fieldToParent = new Map< string, string >();
120
- const fieldIdsToValidate: string[] = [];
121
- formFields.forEach( ( formField ) => {
122
- if ( !! ( formField as CombinedFormField ).children ) {
123
- ( formField as CombinedFormField ).children.forEach( ( child ) => {
124
- const childId = typeof child === 'string' ? child : child.id;
125
- fieldIdsToValidate.push( childId );
126
- fieldToParent.set( childId, formField.id );
127
- } );
128
- } else {
129
- fieldIdsToValidate.push( formField.id );
130
- }
94
+ // Create a map of field IDs to Field definitions for fast lookup
95
+ const fieldsMap = new Map< string, Field< Item > >();
96
+ fields.forEach( ( field ) => {
97
+ fieldsMap.set( field.id, field );
131
98
  } );
132
99
 
133
- return {
134
- fields: normalizeFields(
135
- fields.filter( ( field ) =>
136
- fieldIdsToValidate.includes( field.id )
137
- )
138
- ),
139
- fieldToParent,
140
- };
100
+ // Recursive function to process form fields and their children
101
+ function processFormField(
102
+ formField: NormalizedFormField
103
+ ): FormFieldToValidate< Item > | null {
104
+ // Handle combined fields (fields with children)
105
+ if ( 'children' in formField && Array.isArray( formField.children ) ) {
106
+ const processedChildren = formField.children
107
+ .map( processFormField )
108
+ .filter( ( child ) => child !== null );
109
+
110
+ if ( processedChildren.length === 0 ) {
111
+ return null;
112
+ }
113
+
114
+ const fieldDef = fieldsMap.get( formField.id );
115
+ if ( fieldDef ) {
116
+ const [ normalizedField ] = normalizeFields< Item >( [
117
+ fieldDef,
118
+ ] );
119
+
120
+ return {
121
+ id: formField.id,
122
+ children: processedChildren,
123
+ field: normalizedField,
124
+ } satisfies FormFieldToValidate< Item >;
125
+ }
126
+
127
+ return {
128
+ id: formField.id,
129
+ children: processedChildren,
130
+ } satisfies FormFieldToValidate< Item >;
131
+ }
132
+
133
+ // Handle leaf fields (fields without children)
134
+ const fieldDef = fieldsMap.get( formField.id );
135
+ if ( ! fieldDef ) {
136
+ return null;
137
+ }
138
+
139
+ const [ normalizedField ] = normalizeFields< Item >( [ fieldDef ] );
140
+ return {
141
+ id: formField.id,
142
+ children: [],
143
+ field: normalizedField,
144
+ } as FormFieldToValidate< Item >;
145
+ }
146
+
147
+ const toValidate = normalizedForm.fields
148
+ .map( processFormField )
149
+ .filter( ( field ) => field !== null );
150
+
151
+ return toValidate;
141
152
  }
142
153
 
143
- /**
144
- * Hook that validates a form item and returns an object with error messages for each field.
145
- *
146
- * @param item The item to validate.
147
- * @param fields Fields config.
148
- * @param form Form config.
149
- *
150
- * @return Record of field IDs to error messages (undefined means no error).
151
- */
152
- export function useFormValidity< Item >(
153
- item: Item,
154
- fields: Field< Item >[],
155
- form: Form
156
- ): { validity: FormValidity; isValid: boolean } {
157
- const [ formValidity, setFormValidity ] = useState< FormValidity >();
158
- const previousValidatedValuesRef = useRef< Record< string, any > >( {} );
159
-
160
- // The following counters are used to track the validation promises triggered
161
- // by executing isValid.custom and the elements validation. When the promise resolves,
162
- // it will update the form validity state ONLY if its counter matches the current one.
163
- const customValidationCounterRef = useRef< Record< string, number > >( {} );
164
- const elementsValidationCounterRef = useRef< Record< string, number > >(
165
- {}
166
- );
154
+ function setValidityAtPath(
155
+ formValidity: FormValidity | undefined,
156
+ fieldValidity: FieldValidity,
157
+ path: string[]
158
+ ): FormValidity {
159
+ // Handle empty validity or empty path
160
+ if ( ! formValidity ) {
161
+ formValidity = {};
162
+ }
167
163
 
168
- const validate = useCallback( () => {
169
- const { fields: fieldsToValidate, fieldToParent } = getFieldsToValidate(
170
- fields,
171
- form
172
- );
173
- if ( fieldsToValidate.length === 0 ) {
174
- setFormValidity( undefined );
175
- return;
164
+ if ( path.length === 0 ) {
165
+ return formValidity;
166
+ }
167
+
168
+ // Clone the root to avoid mutations
169
+ const result = { ...formValidity };
170
+
171
+ // Navigate through the result tree,
172
+ // setting up empty paths if they don't exist.
173
+ let current: any = result;
174
+ for ( let i = 0; i < path.length - 1; i++ ) {
175
+ const segment = path[ i ];
176
+ if ( ! current[ segment ] ) {
177
+ current[ segment ] = {};
176
178
  }
177
179
 
178
- fieldsToValidate.forEach( ( field ) => {
179
- const value = field.getValue( { item } );
180
- if (
181
- previousValidatedValuesRef.current.hasOwnProperty( field.id ) &&
182
- value === previousValidatedValuesRef.current[ field.id ]
183
- ) {
180
+ current = current[ segment ];
181
+ }
182
+
183
+ // At the final destination, merge the new validity with the existing.
184
+ const finalKey = path[ path.length - 1 ];
185
+ current[ finalKey ] = {
186
+ ...( current[ finalKey ] || {} ),
187
+ ...fieldValidity,
188
+ };
189
+
190
+ return result;
191
+ }
192
+
193
+ function handleElementsValidationAsync< Item >(
194
+ promise: Promise< any >,
195
+ formField: FormFieldToValidate< Item >,
196
+ promiseHandler: PromiseHandler< Item >
197
+ ) {
198
+ const { elementsCounterRef, setFormValidity, path, item } = promiseHandler;
199
+ const currentToken =
200
+ ( elementsCounterRef.current[ formField.id ] || 0 ) + 1;
201
+ elementsCounterRef.current[ formField.id ] = currentToken;
202
+
203
+ promise
204
+ .then( ( result ) => {
205
+ if ( currentToken !== elementsCounterRef.current[ formField.id ] ) {
184
206
  return;
185
207
  }
186
- previousValidatedValuesRef.current[ field.id ] = value;
187
-
188
- const parentFieldId = fieldToParent.get( field.id );
189
208
 
190
- // isValid.required
191
- if (
192
- field.isValid.required &&
193
- isInvalidForRequired( field.type, value )
194
- ) {
195
- updateFieldValidity( setFormValidity, parentFieldId, field.id, {
196
- required: { type: 'invalid' },
209
+ if ( ! Array.isArray( result ) ) {
210
+ setFormValidity( ( prev ) => {
211
+ const newFormValidity = setValidityAtPath(
212
+ prev,
213
+ {
214
+ elements: {
215
+ type: 'invalid',
216
+ message: __( 'Could not validate elements.' ),
217
+ },
218
+ },
219
+ [ ...path, formField.id ]
220
+ );
221
+ return newFormValidity;
197
222
  } );
198
223
  return;
199
224
  }
200
225
 
201
- // isValid.elements (static elements)
226
+ const validValues = result.map( ( el ) => el.value );
202
227
  if (
203
- field.isValid.elements &&
204
- field.hasElements &&
205
- ! field.getElements &&
206
- Array.isArray( field.elements )
228
+ !! formField.field &&
229
+ formField.field.type !== 'array' &&
230
+ ! validValues.includes( formField.field.getValue( { item } ) )
207
231
  ) {
208
- const validValues = field.elements.map( ( el ) => el.value );
209
-
210
- if (
211
- field.type !== 'array' &&
212
- ! validValues.includes( value )
213
- ) {
214
- updateFieldValidity(
215
- setFormValidity,
216
- parentFieldId,
217
- field.id,
232
+ setFormValidity( ( prev ) => {
233
+ const newFormValidity = setValidityAtPath(
234
+ prev,
218
235
  {
219
236
  elements: {
220
237
  type: 'invalid',
221
- message: 'Value must be one of the elements.',
238
+ message: __(
239
+ 'Value must be one of the elements.'
240
+ ),
222
241
  },
223
- }
242
+ },
243
+ [ ...path, formField.id ]
224
244
  );
225
- return;
226
- }
245
+ return newFormValidity;
246
+ } );
247
+ return;
248
+ }
227
249
 
228
- if ( field.type === 'array' && ! Array.isArray( value ) ) {
229
- updateFieldValidity(
230
- setFormValidity,
231
- parentFieldId,
232
- field.id,
250
+ if (
251
+ !! formField.field &&
252
+ formField.field.type === 'array' &&
253
+ ! Array.isArray( formField.field.getValue( { item } ) )
254
+ ) {
255
+ setFormValidity( ( prev ) => {
256
+ const newFormValidity = setValidityAtPath(
257
+ prev,
233
258
  {
234
259
  elements: {
235
260
  type: 'invalid',
236
- message: 'Value must be an array.',
261
+ message: __( 'Value must be an array.' ),
237
262
  },
238
- }
263
+ },
264
+ [ ...path, formField.id ]
239
265
  );
240
- return;
241
- }
242
- if (
243
- field.type === 'array' &&
244
- value.some( ( v: any ) => ! validValues.includes( v ) )
245
- ) {
246
- updateFieldValidity(
247
- setFormValidity,
248
- parentFieldId,
249
- field.id,
266
+ return newFormValidity;
267
+ } );
268
+ return;
269
+ }
270
+
271
+ if (
272
+ !! formField.field &&
273
+ formField.field.type === 'array' &&
274
+ formField.field
275
+ .getValue( { item } )
276
+ .some( ( v: any ) => ! validValues.includes( v ) )
277
+ ) {
278
+ setFormValidity( ( prev ) => {
279
+ const newFormValidity = setValidityAtPath(
280
+ prev,
250
281
  {
251
282
  elements: {
252
283
  type: 'invalid',
253
- message: 'Value must be one of the elements.',
284
+ message: __(
285
+ 'Value must be one of the elements.'
286
+ ),
254
287
  },
255
- }
288
+ },
289
+ [ ...path, formField.id ]
256
290
  );
257
- return;
258
- }
291
+ return newFormValidity;
292
+ } );
293
+ }
294
+ } )
295
+ .catch( ( error ) => {
296
+ if ( currentToken !== elementsCounterRef.current[ formField.id ] ) {
297
+ return;
259
298
  }
260
299
 
261
- // isValid.elements (get them via getElements first)
262
- if (
263
- field.isValid.elements &&
264
- field.hasElements &&
265
- typeof field.getElements === 'function'
266
- ) {
267
- const currentToken =
268
- ( elementsValidationCounterRef.current[ field.id ] || 0 ) +
269
- 1;
270
- elementsValidationCounterRef.current[ field.id ] = currentToken;
271
- updateFieldValidity( setFormValidity, parentFieldId, field.id, {
272
- elements: {
273
- type: 'validating',
274
- message: 'Validating...',
300
+ let errorMessage;
301
+ if ( error instanceof Error ) {
302
+ errorMessage = error.message;
303
+ } else {
304
+ errorMessage =
305
+ String( error ) ||
306
+ __(
307
+ 'Unknown error when running elements validation asynchronously.'
308
+ );
309
+ }
310
+
311
+ setFormValidity( ( prev ) => {
312
+ const newFormValidity = setValidityAtPath(
313
+ prev,
314
+ {
315
+ elements: {
316
+ type: 'invalid',
317
+ message: errorMessage,
318
+ },
275
319
  },
276
- } );
320
+ [ ...path, formField.id ]
321
+ );
322
+ return newFormValidity;
323
+ } );
324
+ } );
325
+ }
277
326
 
278
- field
279
- .getElements()
280
- .then( ( result ) => {
281
- if (
282
- elementsValidationCounterRef.current[ field.id ] !==
283
- currentToken
284
- ) {
285
- return;
286
- }
287
-
288
- if ( ! Array.isArray( result ) ) {
289
- updateFieldValidity(
290
- setFormValidity,
291
- parentFieldId,
292
- field.id,
293
- {
294
- elements: {
295
- type: 'invalid',
296
- message: 'Could not validate elements.',
297
- },
298
- }
299
- );
300
- return;
301
- }
302
-
303
- const validValues = result.map( ( el ) => el.value );
304
- if (
305
- field.type !== 'array' &&
306
- ! validValues.includes( value )
307
- ) {
308
- updateFieldValidity(
309
- setFormValidity,
310
- parentFieldId,
311
- field.id,
312
- {
313
- elements: {
314
- type: 'invalid',
315
- message:
316
- 'Value must be one of the elements.',
317
- },
318
- }
319
- );
320
- return;
321
- }
322
-
323
- if (
324
- field.type === 'array' &&
325
- ! Array.isArray( value )
326
- ) {
327
- updateFieldValidity(
328
- setFormValidity,
329
- parentFieldId,
330
- field.id,
331
- {
332
- elements: {
333
- type: 'invalid',
334
- message: 'Value must be an array.',
335
- },
336
- }
337
- );
338
- return;
339
- }
340
-
341
- if (
342
- field.type === 'array' &&
343
- value.some(
344
- ( v: any ) => ! validValues.includes( v )
345
- )
346
- ) {
347
- updateFieldValidity(
348
- setFormValidity,
349
- parentFieldId,
350
- field.id,
351
- {
352
- elements: {
353
- type: 'invalid',
354
- message:
355
- 'Value must be one of the elements.',
356
- },
357
- }
358
- );
359
- }
360
- } )
361
- .catch( ( error ) => {
362
- if (
363
- elementsValidationCounterRef.current[ field.id ] !==
364
- currentToken
365
- ) {
366
- return;
367
- }
368
-
369
- updateFieldValidity(
370
- setFormValidity,
371
- parentFieldId,
372
- field.id,
373
- {
374
- elements: {
375
- type: 'invalid',
376
- message: error.message,
377
- },
378
- }
379
- );
380
- } );
327
+ function handleCustomValidationAsync< Item >(
328
+ promise: Promise< any >,
329
+ formField: FormFieldToValidate< Item >,
330
+ promiseHandler: PromiseHandler< Item >
331
+ ) {
332
+ const { customCounterRef, setFormValidity, path } = promiseHandler;
333
+ const currentToken = ( customCounterRef.current[ formField.id ] || 0 ) + 1;
334
+ customCounterRef.current[ formField.id ] = currentToken;
335
+
336
+ promise
337
+ .then( ( result ) => {
338
+ if ( currentToken !== customCounterRef.current[ formField.id ] ) {
339
+ return;
381
340
  }
382
341
 
383
- // Check isValid.custom
384
- let customError;
385
- try {
386
- customError = field.isValid?.custom?.(
387
- deepMerge(
388
- item,
389
- field.setValue( {
390
- item,
391
- value,
392
- } ) as Partial< Item >
393
- ),
394
- field
395
- );
396
- } catch ( error: any ) {
397
- let errorMessage;
398
- if ( error instanceof Error ) {
399
- errorMessage = error.message;
400
- } else {
401
- errorMessage =
402
- String( error ) ||
403
- __( 'Unknown error when running custom validation.' );
404
- }
342
+ if ( result === null ) {
343
+ setFormValidity( ( prev ) => {
344
+ const newFormValidity = setValidityAtPath(
345
+ prev,
346
+ {
347
+ custom: {
348
+ type: 'valid',
349
+ message: __( 'Valid' ),
350
+ },
351
+ },
352
+ [ ...path, formField.id ]
353
+ );
354
+ return newFormValidity;
355
+ } );
356
+ return;
357
+ }
405
358
 
406
- updateFieldValidity( setFormValidity, parentFieldId, field.id, {
407
- custom: {
408
- type: 'invalid',
409
- message: errorMessage,
410
- },
359
+ if ( typeof result === 'string' ) {
360
+ setFormValidity( ( prev ) => {
361
+ const newFormValidity = setValidityAtPath(
362
+ prev,
363
+ {
364
+ custom: {
365
+ type: 'invalid',
366
+ message: result,
367
+ },
368
+ },
369
+ [ ...path, formField.id ]
370
+ );
371
+ return newFormValidity;
411
372
  } );
373
+ return;
412
374
  }
413
375
 
414
- // isValid.custom (sync version)
415
- if ( typeof customError === 'string' ) {
416
- updateFieldValidity( setFormValidity, parentFieldId, field.id, {
417
- custom: {
418
- type: 'invalid',
419
- message: customError,
376
+ setFormValidity( ( prev ) => {
377
+ const newFormValidity = setValidityAtPath(
378
+ prev,
379
+ {
380
+ custom: {
381
+ type: 'invalid',
382
+ message: __( 'Validation could not be processed.' ),
383
+ },
420
384
  },
421
- } );
385
+ [ ...path, formField.id ]
386
+ );
387
+ return newFormValidity;
388
+ } );
389
+ } )
390
+ .catch( ( error ) => {
391
+ if ( currentToken !== customCounterRef.current[ formField.id ] ) {
422
392
  return;
423
393
  }
424
394
 
425
- // — isValid.custom (async version)
426
- if ( customError instanceof Promise ) {
427
- // Increment token for this field to track the latest validation
428
- const currentToken =
429
- ( customValidationCounterRef.current[ field.id ] || 0 ) + 1;
430
- customValidationCounterRef.current[ field.id ] = currentToken;
431
-
432
- updateFieldValidity( setFormValidity, parentFieldId, field.id, {
433
- custom: {
434
- type: 'validating',
435
- message: 'Validating...',
395
+ let errorMessage;
396
+ if ( error instanceof Error ) {
397
+ errorMessage = error.message;
398
+ } else {
399
+ errorMessage =
400
+ String( error ) ||
401
+ __(
402
+ 'Unknown error when running custom validation asynchronously.'
403
+ );
404
+ }
405
+
406
+ setFormValidity( ( prev ) => {
407
+ const newFormValidity = setValidityAtPath(
408
+ prev,
409
+ {
410
+ custom: {
411
+ type: 'invalid',
412
+ message: errorMessage,
413
+ },
436
414
  },
437
- } );
415
+ [ ...path, formField.id ]
416
+ );
417
+ return newFormValidity;
418
+ } );
419
+ } );
420
+ }
438
421
 
439
- customError
440
- .then( ( result ) => {
441
- if (
442
- customValidationCounterRef.current[ field.id ] !==
443
- currentToken
444
- ) {
445
- return;
446
- }
447
-
448
- if ( result === null ) {
449
- updateFieldValidity(
450
- setFormValidity,
451
- parentFieldId,
452
- field.id,
453
- {
454
- custom: {
455
- type: 'valid',
456
- message: 'Valid',
457
- },
458
- }
459
- );
460
- return;
461
- }
462
-
463
- if ( typeof result === 'string' ) {
464
- updateFieldValidity(
465
- setFormValidity,
466
- parentFieldId,
467
- field.id,
468
- {
469
- custom: {
470
- type: 'invalid',
471
- message: result,
472
- },
473
- }
474
- );
475
- }
476
- } )
477
- .catch( ( error ) => {
478
- if (
479
- customValidationCounterRef.current[ field.id ] !==
480
- currentToken
481
- ) {
482
- return;
483
- }
484
-
485
- updateFieldValidity(
486
- setFormValidity,
487
- parentFieldId,
488
- field.id,
489
- {
490
- custom: {
491
- type: 'invalid',
492
- message: error.message,
493
- },
494
- }
495
- );
496
- } );
422
+ type PromiseHandler< Item > = {
423
+ customCounterRef: React.MutableRefObject< Record< string, number > >;
424
+ elementsCounterRef: React.MutableRefObject< Record< string, number > >;
425
+ setFormValidity: React.Dispatch< React.SetStateAction< FormValidity > >;
426
+ path: string[];
427
+ item: Item;
428
+ };
497
429
 
498
- return;
430
+ function validateFormField< Item >(
431
+ item: Item,
432
+ formField: FormFieldToValidate< Item >,
433
+ promiseHandler: PromiseHandler< Item >
434
+ ): FieldValidity | undefined {
435
+ // Validate the field: isValid.required
436
+ if (
437
+ !! formField.field &&
438
+ formField.field.isValid.required &&
439
+ isInvalidForRequired(
440
+ formField.field.type,
441
+ formField.field.getValue( { item } )
442
+ )
443
+ ) {
444
+ return {
445
+ required: { type: 'invalid' },
446
+ };
447
+ }
448
+
449
+ // Validate the field: isValid.elements (static)
450
+ if (
451
+ !! formField.field &&
452
+ formField.field.isValid.elements &&
453
+ formField.field.hasElements &&
454
+ ! formField.field.getElements &&
455
+ Array.isArray( formField.field.elements )
456
+ ) {
457
+ const value = formField.field.getValue( { item } );
458
+ const validValues = formField.field.elements.map( ( el ) => el.value );
459
+
460
+ if (
461
+ formField.field.type !== 'array' &&
462
+ ! validValues.includes( value )
463
+ ) {
464
+ return {
465
+ elements: {
466
+ type: 'invalid',
467
+ message: __( 'Value must be one of the elements.' ),
468
+ },
469
+ };
470
+ }
471
+
472
+ if ( formField.field.type === 'array' && ! Array.isArray( value ) ) {
473
+ return {
474
+ elements: {
475
+ type: 'invalid',
476
+ message: __( 'Value must be an array.' ),
477
+ },
478
+ };
479
+ }
480
+ if (
481
+ formField.field.type === 'array' &&
482
+ value.some( ( v: any ) => ! validValues.includes( v ) )
483
+ ) {
484
+ return {
485
+ elements: {
486
+ type: 'invalid',
487
+ message: __( 'Value must be one of the elements.' ),
488
+ },
489
+ };
490
+ }
491
+ }
492
+
493
+ // Validate the field: isValid.elements (async)
494
+ if (
495
+ !! formField.field &&
496
+ formField.field.isValid.elements &&
497
+ formField.field.hasElements &&
498
+ typeof formField.field.getElements === 'function'
499
+ ) {
500
+ handleElementsValidationAsync(
501
+ formField.field.getElements(),
502
+ formField,
503
+ promiseHandler
504
+ );
505
+
506
+ return {
507
+ elements: {
508
+ type: 'validating',
509
+ message: __( 'Validating…' ),
510
+ },
511
+ };
512
+ }
513
+
514
+ // Validate the field: isValid.custom (sync)
515
+ let customError;
516
+ if ( !! formField.field ) {
517
+ try {
518
+ const value = formField.field.getValue( { item } );
519
+ customError = formField.field.isValid?.custom?.(
520
+ deepMerge(
521
+ item,
522
+ formField.field.setValue( {
523
+ item,
524
+ value,
525
+ } ) as Partial< Item >
526
+ ),
527
+ formField.field
528
+ );
529
+ } catch ( error ) {
530
+ let errorMessage;
531
+ if ( error instanceof Error ) {
532
+ errorMessage = error.message;
533
+ } else {
534
+ errorMessage =
535
+ String( error ) ||
536
+ __( 'Unknown error when running custom validation.' );
499
537
  }
500
538
 
501
- // No errors for this field, remove from errors object
502
- setFormValidity( ( prev ) => {
503
- if ( ! prev ) {
504
- return prev;
505
- }
539
+ return {
540
+ custom: {
541
+ type: 'invalid',
542
+ message: errorMessage,
543
+ },
544
+ };
545
+ }
546
+ }
506
547
 
507
- if ( parentFieldId ) {
508
- // This field is a child - remove it from parent's children
509
- const parentField = prev[ parentFieldId ];
510
- if ( ! parentField?.children ) {
511
- return prev;
512
- }
513
-
514
- const { [ field.id ]: removed, ...restChildren } =
515
- parentField.children as any;
516
-
517
- // If no more children, remove the children property
518
- if ( Object.keys( restChildren ).length === 0 ) {
519
- const { children, ...restParent } = parentField;
520
- if ( Object.keys( restParent ).length === 0 ) {
521
- // Remove parent field entirely if no other validations
522
- const {
523
- [ parentFieldId ]: removedParent,
524
- ...restFields
525
- } = prev;
526
- return Object.keys( restFields ).length === 0
527
- ? undefined
528
- : restFields;
529
- }
530
- return {
531
- ...prev,
532
- [ parentFieldId ]: restParent,
533
- };
534
- }
535
-
536
- return {
537
- ...prev,
538
- [ parentFieldId ]: {
539
- ...parentField,
540
- children: restChildren,
541
- },
542
- };
543
- }
548
+ if ( typeof customError === 'string' ) {
549
+ return {
550
+ custom: {
551
+ type: 'invalid',
552
+ message: customError,
553
+ },
554
+ };
555
+ }
544
556
 
545
- // Regular field - remove from top level
546
- if ( ! prev[ field.id ] ) {
547
- return prev;
548
- }
557
+ // Validate the field: isValid.custom (async)
558
+ if ( customError instanceof Promise ) {
559
+ handleCustomValidationAsync( customError, formField, promiseHandler );
560
+
561
+ return {
562
+ custom: {
563
+ type: 'validating',
564
+ message: __( 'Validating…' ),
565
+ },
566
+ };
567
+ }
549
568
 
550
- const { [ field.id ]: removed, ...rest } = prev;
569
+ // Validate its children.
570
+ if ( formField.children.length > 0 ) {
571
+ const result: Record< string, FieldValidity | undefined > = {};
572
+ formField.children.forEach( ( child ) => {
573
+ result[ child.id ] = validateFormField( item, child, {
574
+ ...promiseHandler,
575
+ path: [ ...promiseHandler.path, formField.id, 'children' ],
576
+ } );
577
+ } );
551
578
 
552
- if ( Object.keys( rest ).length === 0 ) {
553
- return undefined;
554
- }
579
+ const filteredResult: Record< string, FieldValidity > = {};
580
+ Object.entries( result ).forEach( ( [ key, value ] ) => {
581
+ if ( value !== undefined ) {
582
+ filteredResult[ key ] = value;
583
+ }
584
+ } );
585
+
586
+ if ( Object.keys( filteredResult ).length === 0 ) {
587
+ return undefined;
588
+ }
589
+
590
+ return {
591
+ children: filteredResult,
592
+ };
593
+ }
594
+
595
+ // No errors for this field or its children.
596
+ return undefined;
597
+ }
598
+
599
+ function getFormFieldValue< Item >(
600
+ formField: FormFieldToValidate< Item >,
601
+ item: Item
602
+ ): any {
603
+ const fieldValue = formField?.field?.getValue( { item } );
604
+ if ( formField.children.length === 0 ) {
605
+ return fieldValue;
606
+ }
607
+
608
+ const childrenValues = formField.children.map( ( child ) =>
609
+ getFormFieldValue( child, item )
610
+ );
611
+ if ( ! childrenValues ) {
612
+ return fieldValue;
613
+ }
614
+
615
+ return {
616
+ value: fieldValue,
617
+ children: childrenValues,
618
+ };
619
+ }
620
+
621
+ /**
622
+ * Hook that validates a form item and returns an object with error messages for each field.
623
+ *
624
+ * @param item The item to validate.
625
+ * @param fields Fields config.
626
+ * @param form Form config.
627
+ *
628
+ * @return Record of field IDs to error messages (undefined means no error).
629
+ */
630
+ export function useFormValidity< Item >(
631
+ item: Item,
632
+ fields: Field< Item >[],
633
+ form: Form
634
+ ): { validity: FormValidity; isValid: boolean } {
635
+ const [ formValidity, setFormValidity ] = useState< FormValidity >();
636
+ const customCounterRef = useRef< Record< string, number > >( {} );
637
+ const elementsCounterRef = useRef< Record< string, number > >( {} );
638
+ const previousValuesRef = useRef< Record< string, any > >( {} );
555
639
 
556
- return rest;
640
+ const validate = useCallback( () => {
641
+ const promiseHandler = {
642
+ customCounterRef,
643
+ elementsCounterRef,
644
+ setFormValidity,
645
+ path: [],
646
+ item,
647
+ };
648
+
649
+ const formFieldsToValidate = getFormFieldsToValidate( form, fields );
650
+ if ( formFieldsToValidate.length === 0 ) {
651
+ setFormValidity( undefined );
652
+ return;
653
+ }
654
+
655
+ const newFormValidity: FormValidity = {};
656
+ const untouchedFields: string[] = [];
657
+ formFieldsToValidate.forEach( ( formField ) => {
658
+ // Skip fields that did not change.
659
+ const value = getFormFieldValue< Item >( formField, item );
660
+ if (
661
+ previousValuesRef.current.hasOwnProperty( formField.id ) &&
662
+ fastDeepEqual(
663
+ previousValuesRef.current[ formField.id ],
664
+ value
665
+ )
666
+ ) {
667
+ untouchedFields.push( formField.id );
668
+ return;
669
+ }
670
+ previousValuesRef.current[ formField.id ] = value;
671
+
672
+ // Calculate validity for those fields that changed.
673
+ const fieldValidity = validateFormField(
674
+ item,
675
+ formField,
676
+ promiseHandler
677
+ );
678
+ if ( fieldValidity !== undefined ) {
679
+ newFormValidity[ formField.id ] = fieldValidity;
680
+ }
681
+ } );
682
+
683
+ setFormValidity( ( existingFormValidity ) => {
684
+ let validity: FormValidity = {
685
+ ...existingFormValidity,
686
+ ...newFormValidity,
687
+ };
688
+
689
+ const fieldsToKeep = [
690
+ ...untouchedFields,
691
+ ...Object.keys( newFormValidity ),
692
+ ];
693
+ Object.keys( validity ).forEach( ( key ) => {
694
+ if ( validity && ! fieldsToKeep.includes( key ) ) {
695
+ delete validity[ key ];
696
+ }
557
697
  } );
698
+ if ( Object.keys( validity ).length === 0 ) {
699
+ validity = undefined;
700
+ }
701
+
702
+ const areEqual = fastDeepEqual( existingFormValidity, validity );
703
+ if ( areEqual ) {
704
+ return existingFormValidity;
705
+ }
706
+
707
+ return validity;
558
708
  } );
559
709
  }, [ item, fields, form ] );
560
710