@plone/volto 19.0.0-alpha.3 → 19.0.0-alpha.30

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 (502) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +37 -6
  3. package/CHANGELOG.md +534 -2
  4. package/README.md +23 -21
  5. package/babel.js +1 -9
  6. package/cypress/docker/prefixed-rules.yml +26 -0
  7. package/cypress/docker/prefixed.yml +24 -0
  8. package/cypress/support/commands.js +12 -6
  9. package/cypress/support/guillotina.js +1 -0
  10. package/cypress.config.js +1 -0
  11. package/global-test-setup.js +1 -2
  12. package/locales/af/LC_MESSAGES/volto.po +5500 -0
  13. package/locales/af.json +1 -1
  14. package/locales/ar/LC_MESSAGES/volto.po +5500 -0
  15. package/locales/ar.json +1 -1
  16. package/locales/bg/LC_MESSAGES/volto.po +5500 -0
  17. package/locales/bg.json +1 -1
  18. package/locales/bn/LC_MESSAGES/volto.po +5500 -0
  19. package/locales/bn.json +1 -1
  20. package/locales/ca/LC_MESSAGES/volto.po +559 -349
  21. package/locales/ca.json +1 -1
  22. package/locales/cs/LC_MESSAGES/volto.po +5500 -0
  23. package/locales/cs.json +1 -1
  24. package/locales/cy/LC_MESSAGES/volto.po +5500 -0
  25. package/locales/cy.json +1 -1
  26. package/locales/da/LC_MESSAGES/volto.po +5500 -0
  27. package/locales/da.json +1 -1
  28. package/locales/de/LC_MESSAGES/volto.po +232 -21
  29. package/locales/de.json +1 -1
  30. package/locales/el/LC_MESSAGES/volto.po +5500 -0
  31. package/locales/el.json +1 -1
  32. package/locales/en/LC_MESSAGES/volto.po +226 -11
  33. package/locales/en.json +1 -1
  34. package/locales/en_AU/LC_MESSAGES/volto.po +5500 -0
  35. package/locales/en_AU.json +1 -1
  36. package/locales/en_GB/LC_MESSAGES/volto.po +5500 -0
  37. package/locales/en_GB.json +1 -1
  38. package/locales/eo/LC_MESSAGES/volto.po +5500 -0
  39. package/locales/eo.json +1 -1
  40. package/locales/es/LC_MESSAGES/volto.po +321 -111
  41. package/locales/es.json +1 -1
  42. package/locales/et/LC_MESSAGES/volto.po +5500 -0
  43. package/locales/et.json +1 -1
  44. package/locales/eu/LC_MESSAGES/volto.po +404 -194
  45. package/locales/eu.json +1 -1
  46. package/locales/fa/LC_MESSAGES/volto.po +5500 -0
  47. package/locales/fa.json +1 -1
  48. package/locales/fi/LC_MESSAGES/volto.po +221 -11
  49. package/locales/fi.json +1 -1
  50. package/locales/fr/LC_MESSAGES/volto.po +220 -10
  51. package/locales/fr.json +1 -1
  52. package/locales/fu/LC_MESSAGES/volto.po +5500 -0
  53. package/locales/fu.json +1 -1
  54. package/locales/gl/LC_MESSAGES/volto.po +5501 -0
  55. package/locales/gl.json +1 -1
  56. package/locales/he/LC_MESSAGES/volto.po +5500 -0
  57. package/locales/he.json +1 -1
  58. package/locales/hi/LC_MESSAGES/volto.po +225 -10
  59. package/locales/hi.json +1 -1
  60. package/locales/hr/LC_MESSAGES/volto.po +5500 -0
  61. package/locales/hr.json +1 -1
  62. package/locales/hu/LC_MESSAGES/volto.po +5500 -0
  63. package/locales/hu.json +1 -1
  64. package/locales/hy/LC_MESSAGES/volto.po +5500 -0
  65. package/locales/hy.json +1 -1
  66. package/locales/id/LC_MESSAGES/volto.po +5500 -0
  67. package/locales/id.json +1 -1
  68. package/locales/it/LC_MESSAGES/volto.po +239 -24
  69. package/locales/it.json +1 -1
  70. package/locales/ja/LC_MESSAGES/volto.po +264 -53
  71. package/locales/ja.json +1 -1
  72. package/locales/ka/LC_MESSAGES/volto.po +5500 -0
  73. package/locales/ka.json +1 -1
  74. package/locales/kn/LC_MESSAGES/volto.po +5500 -0
  75. package/locales/kn.json +1 -1
  76. package/locales/ko/LC_MESSAGES/volto.po +5500 -0
  77. package/locales/ko.json +1 -1
  78. package/locales/lt/LC_MESSAGES/volto.po +5500 -0
  79. package/locales/lt.json +1 -1
  80. package/locales/lv/LC_MESSAGES/volto.po +5500 -0
  81. package/locales/lv.json +1 -1
  82. package/locales/mi/LC_MESSAGES/volto.po +5500 -0
  83. package/locales/mi.json +1 -1
  84. package/locales/mk/LC_MESSAGES/volto.po +5500 -0
  85. package/locales/mk.json +1 -1
  86. package/locales/my/LC_MESSAGES/volto.po +5500 -0
  87. package/locales/my.json +1 -1
  88. package/locales/nb_NO/LC_MESSAGES/volto.po +5500 -0
  89. package/locales/nb_NO.json +1 -1
  90. package/locales/nl/LC_MESSAGES/volto.po +243 -32
  91. package/locales/nl.json +1 -1
  92. package/locales/nn/LC_MESSAGES/volto.po +5500 -0
  93. package/locales/nn.json +1 -1
  94. package/locales/pl/LC_MESSAGES/volto.po +5500 -0
  95. package/locales/pl.json +1 -1
  96. package/locales/pt/LC_MESSAGES/volto.po +869 -659
  97. package/locales/pt.json +1 -1
  98. package/locales/pt_BR/LC_MESSAGES/volto.po +229 -19
  99. package/locales/pt_BR.json +1 -1
  100. package/locales/rm/LC_MESSAGES/volto.po +5500 -0
  101. package/locales/rm.json +1 -1
  102. package/locales/ro/LC_MESSAGES/volto.po +252 -42
  103. package/locales/ro.json +1 -1
  104. package/locales/ru/LC_MESSAGES/volto.po +220 -10
  105. package/locales/ru.json +1 -1
  106. package/locales/sk/LC_MESSAGES/volto.po +5500 -0
  107. package/locales/sk.json +1 -1
  108. package/locales/sl/LC_MESSAGES/volto.po +5500 -0
  109. package/locales/sl.json +1 -1
  110. package/locales/sm/LC_MESSAGES/volto.po +5500 -0
  111. package/locales/sm.json +1 -1
  112. package/locales/sq/LC_MESSAGES/volto.po +5500 -0
  113. package/locales/sq.json +1 -1
  114. package/locales/sr/LC_MESSAGES/volto.po +5500 -0
  115. package/locales/sr.json +1 -1
  116. package/locales/sr@cyrl/LC_MESSAGES/volto.po +5500 -0
  117. package/locales/sr@cyrl.json +1 -1
  118. package/locales/sr@latn/LC_MESSAGES/volto.po +5500 -0
  119. package/locales/sr@latn.json +1 -1
  120. package/locales/sv/LC_MESSAGES/volto.po +5500 -0
  121. package/locales/sv.json +1 -1
  122. package/locales/ta/LC_MESSAGES/volto.po +5501 -0
  123. package/locales/ta.json +1 -1
  124. package/locales/te/LC_MESSAGES/volto.po +5500 -0
  125. package/locales/te.json +1 -1
  126. package/locales/th/LC_MESSAGES/volto.po +5500 -0
  127. package/locales/th.json +1 -1
  128. package/locales/to/LC_MESSAGES/volto.po +5500 -0
  129. package/locales/to.json +1 -1
  130. package/locales/tr/LC_MESSAGES/volto.po +5501 -0
  131. package/locales/tr.json +1 -1
  132. package/locales/uk/LC_MESSAGES/volto.po +5500 -0
  133. package/locales/uk.json +1 -1
  134. package/locales/vi/LC_MESSAGES/volto.po +5500 -0
  135. package/locales/vi.json +1 -1
  136. package/locales/volto.pot +220 -10
  137. package/locales/zh_CN/LC_MESSAGES/volto.po +221 -10
  138. package/locales/zh_CN.json +1 -1
  139. package/locales/zh_Hant/LC_MESSAGES/volto.po +5500 -0
  140. package/locales/zh_Hant.json +1 -1
  141. package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +5500 -0
  142. package/locales/zh_Hant_HK.json +1 -1
  143. package/package.json +72 -146
  144. package/razzle.config.js +32 -25
  145. package/src/actions/blockTypes/blockTypes.ts +24 -0
  146. package/src/components/manage/Actions/Actions.test.jsx +1 -5
  147. package/src/components/manage/Add/Add.jsx +15 -10
  148. package/src/components/manage/BlockChooser/BlockChooser.jsx +1 -0
  149. package/src/components/manage/Blocks/Block/BlocksForm.jsx +10 -7
  150. package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +3 -14
  151. package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +9 -4
  152. package/src/components/manage/Blocks/Block/Order/Item.jsx +27 -13
  153. package/src/components/manage/Blocks/Block/Order/Item.test.jsx +90 -0
  154. package/src/components/manage/Blocks/Block/Order/Order.jsx +116 -67
  155. package/src/components/manage/Blocks/Block/Order/utilities.js +28 -11
  156. package/src/components/manage/Blocks/Block/Settings.test.jsx +1 -5
  157. package/src/components/manage/Blocks/Grid/View.jsx +14 -11
  158. package/src/components/manage/Blocks/Grid/context.js +3 -0
  159. package/src/components/manage/Blocks/HTML/Edit.test.jsx +1 -5
  160. package/src/components/manage/Blocks/Image/Edit.jsx +5 -1
  161. package/src/components/manage/Blocks/Image/ImageSidebar.test.jsx +2 -5
  162. package/src/components/manage/Blocks/LeadImage/Edit.jsx +2 -2
  163. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.jsx +1 -1
  164. package/src/components/manage/Blocks/LeadImage/LeadImageSidebar.test.jsx +2 -5
  165. package/src/components/manage/Blocks/Listing/Edit.jsx +1 -0
  166. package/src/components/manage/Blocks/Listing/ImageGallery.jsx +6 -4
  167. package/src/components/manage/Blocks/Listing/ListingBody.jsx +4 -0
  168. package/src/components/manage/Blocks/Maps/Edit.jsx +2 -1
  169. package/src/components/manage/Blocks/Maps/MapsSidebar.test.jsx +1 -5
  170. package/src/components/manage/Blocks/Search/SearchBlockView.jsx +21 -4
  171. package/src/components/manage/Blocks/Search/components/DateRangeFacet.test.jsx +1 -6
  172. package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +22 -1
  173. package/src/components/manage/Blocks/Search/components/SelectFacet.test.jsx +1 -6
  174. package/src/components/manage/Blocks/Search/components/SortOn.jsx +8 -2
  175. package/src/components/manage/Blocks/Search/components/ToggleFacet.jsx +14 -0
  176. package/src/components/manage/Blocks/Teaser/Data.jsx +21 -7
  177. package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +11 -3
  178. package/src/components/manage/Blocks/Teaser/View.jsx +0 -1
  179. package/src/components/manage/Blocks/Teaser/utils.js +13 -0
  180. package/src/components/manage/Blocks/Teaser/utils.test.js +34 -0
  181. package/src/components/manage/Blocks/Title/Edit.jsx +5 -0
  182. package/src/components/manage/Blocks/Video/Body.jsx +69 -43
  183. package/src/components/manage/Blocks/Video/Body.test.jsx +122 -5
  184. package/src/components/manage/Blocks/Video/Edit.jsx +22 -3
  185. package/src/components/manage/Blocks/Video/Edit.test.jsx +6 -0
  186. package/src/components/manage/Blocks/Video/VideoSidebar.test.jsx +1 -5
  187. package/src/components/manage/Blocks/Video/View.jsx +1 -0
  188. package/src/components/manage/Blocks/Video/View.test.jsx +29 -15
  189. package/src/components/manage/Blocks/Video/schema.js +14 -1
  190. package/src/components/manage/Contents/Contents.jsx +697 -659
  191. package/src/components/manage/Contents/Contents.test.jsx +1 -5
  192. package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +6 -5
  193. package/src/components/manage/Contents/ContentsItem.jsx +1 -1
  194. package/src/components/manage/Contents/ContentsPropertiesModal.test.jsx +1 -5
  195. package/src/components/manage/Contents/ContentsRenameModal.test.jsx +1 -5
  196. package/src/components/manage/Contents/ContentsTagsModal.test.jsx +1 -5
  197. package/src/components/manage/Contents/ContentsWorkflowModal.test.jsx +1 -5
  198. package/src/components/manage/Contents/DropZoneContent.jsx +338 -0
  199. package/src/components/manage/Contents/__mocks__/index.tsx +2 -18
  200. package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +7 -0
  201. package/src/components/manage/Controlpanels/Aliases.test.jsx +1 -5
  202. package/src/components/manage/Controlpanels/BlockType.tsx +166 -0
  203. package/src/components/manage/Controlpanels/BlockTypes.tsx +145 -0
  204. package/src/components/manage/Controlpanels/ContentType.jsx +1 -1
  205. package/src/components/manage/Controlpanels/ContentType.test.jsx +1 -5
  206. package/src/components/manage/Controlpanels/ContentTypeSchema.jsx +1 -1
  207. package/src/components/manage/Controlpanels/Controlpanels.jsx +28 -5
  208. package/src/components/manage/Controlpanels/Controlpanels.test.jsx +10 -0
  209. package/src/components/manage/Controlpanels/DatabaseInformation.jsx +9 -0
  210. package/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +3 -2
  211. package/src/components/manage/Controlpanels/ModerateComments.jsx +8 -0
  212. package/src/components/manage/Controlpanels/Relations/Relations.jsx +1 -1
  213. package/src/components/manage/Controlpanels/Rules/AddRule.test.jsx +1 -5
  214. package/src/components/manage/Controlpanels/Rules/EditRule.test.jsx +1 -5
  215. package/src/components/manage/Controlpanels/UndoControlpanel.test.jsx +1 -5
  216. package/src/components/manage/Controlpanels/Users/RenderUsers.jsx +156 -175
  217. package/src/components/manage/Controlpanels/Users/UserGroupMembershipControlPanel.test.jsx +3 -0
  218. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +575 -631
  219. package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +58 -11
  220. package/src/components/manage/Diff/Diff.jsx +201 -298
  221. package/src/components/manage/Diff/Diff.test.jsx +1 -6
  222. package/src/components/manage/Diff/DiffField.test.jsx +1 -6
  223. package/src/components/manage/Display/Display.test.jsx +2 -11
  224. package/src/components/manage/Edit/Edit.test.jsx +1 -5
  225. package/src/components/manage/Form/BlockDataForm.test.jsx +1 -5
  226. package/src/components/manage/Form/Form.jsx +3 -3
  227. package/src/components/manage/Form/Form.test.jsx +1 -5
  228. package/src/components/manage/Form/InlineForm.jsx +2 -2
  229. package/src/components/manage/Form/InlineForm.test.jsx +1 -5
  230. package/src/components/manage/Form/ModalForm.jsx +12 -10
  231. package/src/components/manage/Form/ModalForm.test.jsx +27 -5
  232. package/src/components/manage/Form/__mocks__/index.tsx +9 -27
  233. package/src/components/manage/Multilingual/CompareLanguages.jsx +6 -6
  234. package/src/components/manage/Multilingual/CreateTranslation.jsx +16 -13
  235. package/src/components/manage/Multilingual/ManageTranslations.jsx +5 -5
  236. package/src/components/manage/Multilingual/TranslationObject.jsx +11 -8
  237. package/src/components/manage/Preferences/ChangePassword.test.jsx +1 -5
  238. package/src/components/manage/Preferences/PersonalPreferences.jsx +8 -5
  239. package/src/components/manage/Preferences/PersonalPreferences.test.jsx +1 -17
  240. package/src/components/manage/Sharing/Sharing.jsx +21 -15
  241. package/src/components/manage/Sidebar/ObjectBrowser.jsx +3 -0
  242. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +18 -2
  243. package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +2 -1
  244. package/src/components/manage/Sidebar/SidebarPortal.test.tsx +42 -0
  245. package/src/components/manage/Sidebar/SidebarPortal.tsx +48 -0
  246. package/src/components/manage/TemplateChooser/TemplateChooser.jsx +2 -1
  247. package/src/components/manage/TemplateChooser/TemplateChooser.test.jsx +1 -0
  248. package/src/components/manage/Toast/Toast.jsx +32 -0
  249. package/src/components/manage/Toast/Toast.test.jsx +9 -5
  250. package/src/components/manage/Toolbar/PersonalTools.jsx +2 -1
  251. package/src/components/manage/Toolbar/PersonalTools.test.jsx +15 -0
  252. package/src/components/manage/Toolbar/Toolbar.jsx +14 -4
  253. package/src/components/manage/Toolbar/Types.crash.test.jsx +48 -0
  254. package/src/components/manage/Toolbar/Types.jsx +6 -4
  255. package/src/components/manage/UniversalLink/UniversalLink.test.jsx +16 -0
  256. package/src/components/manage/UniversalLink/UniversalLink.tsx +2 -0
  257. package/src/components/manage/Widgets/AlignWidget.stories.jsx +9 -0
  258. package/src/components/manage/Widgets/AlignWidget.test.tsx +95 -0
  259. package/src/components/manage/Widgets/{AlignWidget.jsx → AlignWidget.tsx} +23 -7
  260. package/src/components/manage/Widgets/ArrayWidget.test.jsx +1 -6
  261. package/src/components/manage/Widgets/BlockAlignment.stories.tsx +104 -0
  262. package/src/components/manage/Widgets/BlockAlignment.test.tsx +104 -0
  263. package/src/components/manage/Widgets/BlockAlignment.tsx +88 -0
  264. package/src/components/manage/Widgets/BlockWidth.stories.tsx +69 -0
  265. package/src/components/manage/Widgets/BlockWidth.test.tsx +62 -0
  266. package/src/components/manage/Widgets/BlockWidth.tsx +101 -0
  267. package/src/components/manage/Widgets/ButtonsWidget.stories.jsx +61 -0
  268. package/src/components/manage/Widgets/ButtonsWidget.test.tsx +138 -0
  269. package/src/components/manage/Widgets/ButtonsWidget.tsx +195 -0
  270. package/src/components/manage/Widgets/CheckboxGroupWidget.test.jsx +1 -6
  271. package/src/components/manage/Widgets/DatetimeWidget.jsx +102 -53
  272. package/src/components/manage/Widgets/DatetimeWidget.test.jsx +56 -6
  273. package/src/components/manage/Widgets/FileWidget.jsx +21 -8
  274. package/src/components/manage/Widgets/FormFieldWrapper.jsx +146 -168
  275. package/src/components/manage/Widgets/ImageWidget.jsx +177 -38
  276. package/src/components/manage/Widgets/InternalUrlWidget.jsx +2 -0
  277. package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +8 -0
  278. package/src/components/manage/Widgets/ObjectListWidget.test.jsx +2 -11
  279. package/src/components/manage/Widgets/ObjectWidget.test.jsx +1 -5
  280. package/src/components/manage/Widgets/QueryWidget.jsx +137 -9
  281. package/src/components/manage/Widgets/RadioGroupWidget.test.jsx +1 -6
  282. package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.test.jsx +1 -6
  283. package/src/components/manage/Widgets/RegistryImageWidget.jsx +1 -1
  284. package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +4 -2
  285. package/src/components/manage/Widgets/SchemaWidget.test.jsx +1 -6
  286. package/src/components/manage/Widgets/SchemaWidgetFieldset.test.jsx +1 -6
  287. package/src/components/manage/Widgets/SelectAutoComplete.jsx +29 -12
  288. package/src/components/manage/Widgets/SelectAutoComplete.test.jsx +1 -6
  289. package/src/components/manage/Widgets/SelectWidget.test.jsx +1 -6
  290. package/src/components/manage/Widgets/Size.stories.tsx +69 -0
  291. package/src/components/manage/Widgets/Size.test.tsx +59 -0
  292. package/src/components/manage/Widgets/Size.tsx +78 -0
  293. package/src/components/manage/Widgets/TimeWidget.test.jsx +1 -6
  294. package/src/components/manage/Widgets/TokenWidget.test.jsx +1 -6
  295. package/src/components/manage/Widgets/UrlWidget.jsx +49 -18
  296. package/src/components/manage/Widgets/VocabularyTermsWidget.test.jsx +2 -11
  297. package/src/components/manage/Widgets/__mocks__/index.tsx +33 -51
  298. package/src/components/manage/Widgets/index.tsx +21 -0
  299. package/src/components/manage/Workflow/Workflow.test.jsx +2 -11
  300. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +1 -0
  301. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +30 -0
  302. package/src/components/theme/App/App.jsx +5 -1
  303. package/src/components/theme/Avatar/Avatar.jsx +2 -1
  304. package/src/components/theme/Comments/CommentEditModal.test.jsx +1 -5
  305. package/src/components/theme/Comments/Comments.test.jsx +2 -11
  306. package/src/components/theme/ConnectionRefused/ConnectionRefused.jsx +3 -2
  307. package/src/components/theme/ContactForm/ContactForm.test.jsx +1 -5
  308. package/src/components/theme/Image/Image.jsx +25 -13
  309. package/src/components/theme/Image/Image.test.jsx +247 -146
  310. package/src/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.tsx +7 -0
  311. package/src/components/theme/LanguageSelector/LanguageSelector.tsx +89 -0
  312. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +42 -12
  313. package/src/components/theme/PasswordReset/PasswordReset.jsx +108 -191
  314. package/src/components/theme/PasswordReset/RequestPasswordReset.test.jsx +1 -5
  315. package/src/components/theme/PreviewImage/PreviewImage.jsx +1 -1
  316. package/src/components/theme/Register/Register.test.jsx +1 -5
  317. package/src/components/theme/RequestTimeout/RequestTimeout.jsx +1 -1
  318. package/src/components/theme/Sitemap/Sitemap.stories.jsx +82 -0
  319. package/src/components/theme/SlotRenderer/SlotRenderer.tsx +12 -6
  320. package/src/components/theme/Unauthorized/Unauthorized.jsx +35 -25
  321. package/src/components/theme/Unauthorized/Unauthorized.test.jsx +28 -1
  322. package/src/components/theme/VideoEmbed/VideoEmbed.jsx +100 -0
  323. package/src/components/theme/View/EventDatesInfo.test.jsx +1 -6
  324. package/src/components/theme/View/EventView.stories.jsx +89 -0
  325. package/src/components/theme/View/EventView.test.jsx +1 -6
  326. package/src/components/theme/View/FileView.stories.jsx +50 -0
  327. package/src/components/theme/View/ImageView.jsx +2 -1
  328. package/src/components/theme/View/ImageView.test.jsx +3 -0
  329. package/src/components/theme/View/LinkView.stories.jsx +57 -0
  330. package/src/components/theme/View/ListingView.stories.jsx +70 -0
  331. package/src/components/theme/View/NewsItemView.stories.jsx +58 -0
  332. package/src/components/theme/View/RenderBlocks.jsx +8 -10
  333. package/src/components/theme/View/RenderBlocks.stories.jsx +112 -0
  334. package/src/components/theme/View/RenderBlocks.test.jsx +14 -4
  335. package/src/components/theme/View/SummaryView.stories.jsx +71 -0
  336. package/src/components/theme/View/TabularView.stories.jsx +66 -0
  337. package/src/components/theme/View/View.jsx +8 -1
  338. package/src/components/theme/Widgets/ImageWidget.jsx +2 -1
  339. package/src/components/theme/Widgets/ImageWidget.test.jsx +31 -11
  340. package/src/config/Blocks.jsx +3 -0
  341. package/src/config/ControlPanels.js +3 -0
  342. package/src/config/Widgets.jsx +7 -0
  343. package/src/config/index.js +19 -12
  344. package/src/config/server.js +0 -2
  345. package/src/config/slots.js +19 -0
  346. package/src/config/validation.ts +8 -0
  347. package/src/constants/ActionTypes.js +1 -0
  348. package/src/express-middleware/devproxy.js +22 -5
  349. package/src/express-middleware/files.js +1 -0
  350. package/src/express-middleware/files.test.js +59 -0
  351. package/src/express-middleware/images.js +1 -0
  352. package/src/express-middleware/images.test.js +50 -0
  353. package/src/helpers/Api/APIResourceWithAuth.js +8 -3
  354. package/src/helpers/Api/Api.js +7 -4
  355. package/src/helpers/AsyncConnect/ssr.js +4 -1
  356. package/src/helpers/AuthToken/AuthToken.js +1 -6
  357. package/src/helpers/Blocks/Blocks.js +113 -28
  358. package/src/helpers/Blocks/Blocks.test.js +100 -0
  359. package/src/helpers/Content/Content.js +23 -0
  360. package/src/helpers/Content/Content.test.js +39 -0
  361. package/src/helpers/Content/withClientSideContent.jsx +35 -0
  362. package/src/helpers/Extensions/withBlockSchemaEnhancer.jsx +4 -1
  363. package/src/helpers/FormValidation/FormValidation.test.js +31 -0
  364. package/src/helpers/FormValidation/validators.ts +52 -6
  365. package/src/helpers/Html/Html.jsx +13 -4
  366. package/src/helpers/Loadable/__mocks__/Loadable.jsx +7 -22
  367. package/src/helpers/MessageLabels/MessageLabels.js +10 -0
  368. package/src/helpers/Sitemap/Sitemap.js +4 -4
  369. package/src/helpers/Url/Url.js +33 -2
  370. package/src/helpers/Url/Url.test.js +62 -0
  371. package/src/helpers/Utils/Utils.jsx +17 -0
  372. package/src/helpers/Utils/Utils.test.jsx +39 -0
  373. package/src/hooks/user/useUser.js +1 -1
  374. package/src/internalChecks.test.ts +11 -0
  375. package/src/middleware/api.js +17 -8
  376. package/src/middleware/storeProtectLoadUtils.test.js +3 -3
  377. package/src/reducers/blockTypes/blockTypes.js +38 -0
  378. package/src/reducers/content/content.js +3 -18
  379. package/src/reducers/diff/diff.js +5 -1
  380. package/src/reducers/diff/diff.test.js +60 -4
  381. package/src/reducers/index.js +2 -0
  382. package/src/reducers/querystring/querystring.js +8 -1
  383. package/src/reducers/users/users.js +1 -1
  384. package/src/routes.js +13 -1
  385. package/src/server.jsx +47 -13
  386. package/src/start-client.jsx +9 -2
  387. package/src/start-server.js +9 -3
  388. package/test-addons-loader.js +3 -0
  389. package/test-setup-globals.js +56 -2
  390. package/theme/themes/default/elements/segment.variables +9 -16
  391. package/theme/themes/pastanaga/collections/form.overrides +1 -1
  392. package/theme/themes/pastanaga/elements/segment.variables +1 -4
  393. package/theme/themes/pastanaga/extras/block-types.less +17 -0
  394. package/theme/themes/pastanaga/extras/blocks.less +19 -0
  395. package/theme/themes/pastanaga/extras/contents.less +75 -0
  396. package/theme/themes/pastanaga/extras/main.less +20 -4
  397. package/theme/themes/pastanaga/extras/toolbar.less +10 -5
  398. package/theme/themes/pastanaga/extras/videoembed.less +22 -0
  399. package/theme/themes/pastanaga/extras/widgets.less +79 -0
  400. package/tsconfig.declarations.json +1 -1
  401. package/tsconfig.json +4 -5
  402. package/types/actions/blockTypes/blockTypes.d.ts +7 -0
  403. package/types/components/index.d.ts +1 -1
  404. package/types/components/manage/Blocks/Block/Order/Item.test.d.ts +1 -0
  405. package/types/components/manage/Blocks/Block/Order/utilities.d.ts +2 -1
  406. package/types/components/manage/Blocks/Grid/context.d.ts +1 -0
  407. package/types/components/manage/Blocks/Teaser/utils.d.ts +5 -0
  408. package/types/components/manage/Blocks/Video/Body.d.ts +4 -2
  409. package/types/components/manage/Blocks/Video/schema.d.ts +4 -0
  410. package/types/components/manage/Contents/DropZoneContent.d.ts +2 -0
  411. package/types/components/manage/Contents/__mocks__/index.d.ts +2 -2
  412. package/types/components/manage/Controlpanels/BlockType.d.ts +7 -0
  413. package/types/components/manage/Controlpanels/BlockTypes.d.ts +7 -0
  414. package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
  415. package/types/components/manage/Controlpanels/Users/RenderUsers.d.ts +18 -2
  416. package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +6 -2
  417. package/types/components/manage/Controlpanels/index.d.ts +2 -2
  418. package/types/components/manage/Diff/Diff.d.ts +7 -2
  419. package/types/components/manage/Form/__mocks__/index.d.ts +8 -8
  420. package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
  421. package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
  422. package/types/components/manage/Sidebar/SidebarPortal.d.ts +7 -15
  423. package/types/components/manage/Toolbar/Types.crash.test.d.ts +1 -0
  424. package/types/components/manage/Widgets/AlignWidget.d.ts +8 -10
  425. package/types/components/manage/Widgets/AlignWidget.stories.d.ts +1 -0
  426. package/types/components/manage/Widgets/BlockAlignment.d.ts +7 -0
  427. package/types/components/manage/Widgets/BlockAlignment.stories.d.ts +8 -0
  428. package/types/components/manage/Widgets/BlockWidth.d.ts +7 -0
  429. package/types/components/manage/Widgets/BlockWidth.stories.d.ts +6 -0
  430. package/types/components/manage/Widgets/ButtonsWidget.d.ts +48 -1
  431. package/types/components/manage/Widgets/ButtonsWidget.stories.d.ts +3 -0
  432. package/types/components/manage/Widgets/FormFieldWrapper.d.ts +28 -5
  433. package/types/components/manage/Widgets/ImageWidget.d.ts +41 -1
  434. package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
  435. package/types/components/manage/Widgets/ObjectBrowserWidget.d.ts +2 -0
  436. package/types/components/manage/Widgets/QueryWidget.d.ts +5 -2
  437. package/types/components/manage/Widgets/RecurrenceWidget/Utils.d.ts +12 -18
  438. package/types/components/manage/Widgets/Size.d.ts +7 -0
  439. package/types/components/manage/Widgets/Size.stories.d.ts +6 -0
  440. package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
  441. package/types/components/manage/Widgets/__mocks__/index.d.ts +33 -33
  442. package/types/components/manage/Widgets/index.d.ts +11 -6
  443. package/types/components/theme/ConnectionRefused/ConnectionRefused.d.ts +2 -2
  444. package/types/components/theme/InjectPloneComponentsCSS/InjectPloneComponentsCSS.d.ts +3 -0
  445. package/types/components/theme/LanguageSelector/LanguageSelector.d.ts +3 -10
  446. package/types/components/theme/PasswordReset/PasswordReset.d.ts +6 -2
  447. package/types/components/theme/Sitemap/Sitemap.stories.d.ts +13 -0
  448. package/types/components/theme/SlotRenderer/SlotRenderer.d.ts +4 -5
  449. package/types/components/theme/Unauthorized/Unauthorized.d.ts +2 -2
  450. package/types/components/theme/VideoEmbed/VideoEmbed.d.ts +2 -0
  451. package/types/components/theme/View/EventView.stories.d.ts +19 -0
  452. package/types/components/theme/View/FileView.stories.d.ts +18 -0
  453. package/types/components/theme/View/LinkView.stories.d.ts +18 -0
  454. package/types/components/theme/View/ListingView.stories.d.ts +24 -0
  455. package/types/components/theme/View/NewsItemView.stories.d.ts +23 -0
  456. package/types/components/theme/View/RenderBlocks.stories.d.ts +23 -0
  457. package/types/components/theme/View/SummaryView.stories.d.ts +23 -0
  458. package/types/components/theme/View/TabularView.stories.d.ts +23 -0
  459. package/types/config/ControlPanels.d.ts +1 -0
  460. package/types/config/Views.d.ts +1 -1
  461. package/types/config/Widgets.d.ts +6 -0
  462. package/types/config/slots.d.ts +7 -0
  463. package/types/constants/ActionTypes.d.ts +1 -0
  464. package/types/helpers/Blocks/Blocks.d.ts +4 -0
  465. package/types/helpers/Content/Content.d.ts +7 -0
  466. package/types/helpers/Content/withClientSideContent.d.ts +1 -0
  467. package/types/helpers/Extensions/withBlockSchemaEnhancer.d.ts +4 -5
  468. package/types/helpers/FormValidation/validators.d.ts +18 -1
  469. package/types/helpers/Helmet/Helmet.d.ts +1 -1
  470. package/types/helpers/Loadable/__mocks__/Loadable.d.ts +2 -2
  471. package/types/helpers/MessageLabels/MessageLabels.d.ts +105 -93
  472. package/types/helpers/Url/Url.d.ts +14 -0
  473. package/types/helpers/Url/bulkFlattenToAppURL.d.ts +5 -0
  474. package/types/helpers/Utils/Utils.d.ts +1 -0
  475. package/types/reducers/blockTypes/blockTypes.d.ts +16 -0
  476. package/types/reducers/index.d.ts +3 -0
  477. package/types/routes.d.ts +7 -5
  478. package/types/start-client.d.ts +0 -1
  479. package/vitest.config.mjs +84 -42
  480. package/webpack-plugins/webpack-less-plugin.js +1 -1
  481. package/webpack-plugins/webpack-scss-plugin.js +172 -0
  482. package/cypress/downloads/downloads.html +0 -0
  483. package/jest-addons-loader.js +0 -3
  484. package/jest-extender-plugin.js +0 -39
  485. package/jest-setup-afterenv.js +0 -2
  486. package/jest-svgsystem-transform.js +0 -10
  487. package/package-why.json +0 -34
  488. package/patches/patchit.sh +0 -2
  489. package/patches/razzle-jest.patch +0 -10
  490. package/src/components/manage/Contents/__mocks__/index.vitest.tsx +0 -5
  491. package/src/components/manage/Form/__mocks__/index.vitest.tsx +0 -73
  492. package/src/components/manage/Sidebar/SidebarPortal.jsx +0 -47
  493. package/src/components/manage/Sidebar/SidebarPortal.test.jsx +0 -26
  494. package/src/components/manage/Widgets/AlignWidget.test.jsx +0 -59
  495. package/src/components/manage/Widgets/ButtonsWidget.jsx +0 -41
  496. package/src/components/manage/Widgets/ButtonsWidget.test.jsx +0 -70
  497. package/src/components/manage/Widgets/__mocks__/index.vitest.tsx +0 -41
  498. package/src/components/theme/LanguageSelector/LanguageSelector.jsx +0 -79
  499. package/src/helpers/Loadable/__mocks__/Loadable.vitest.jsx +0 -39
  500. package/test-setup-globals-vitest.js +0 -46
  501. package/theme/themes/pastanaga/extras/utils.less +0 -63
  502. /package/src/components/theme/LanguageSelector/{LanguageSelector.test.jsx → LanguageSelector.test.tsx} +0 -0
@@ -0,0 +1,138 @@
1
+ import React from 'react';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render, fireEvent, screen } from '@testing-library/react';
4
+ import configureStore from 'redux-mock-store';
5
+ import { Provider } from 'react-intl-redux';
6
+ import imageFullSVG from '@plone/volto/icons/image-full.svg';
7
+ import textJustifiedSVG from '@plone/volto/icons/align-justify.svg';
8
+ import textCenteredSVG from '@plone/volto/icons/align-center.svg';
9
+ import textLeftSVG from '@plone/volto/icons/align-left.svg';
10
+ import textRightSVG from '@plone/volto/icons/align-right.svg';
11
+
12
+ import ButtonsWidget from './ButtonsWidget';
13
+
14
+ const mockStore = configureStore();
15
+
16
+ const renderWidget = (ui: React.ReactElement) =>
17
+ render(
18
+ <Provider
19
+ store={mockStore({
20
+ intl: {
21
+ locale: 'en',
22
+ messages: {},
23
+ },
24
+ })}
25
+ >
26
+ {ui}
27
+ </Provider>,
28
+ );
29
+
30
+ describe('ButtonsWidget', () => {
31
+ it('renders string-based actions', () => {
32
+ const { asFragment } = renderWidget(
33
+ <ButtonsWidget
34
+ id="align"
35
+ title="Alignment"
36
+ fieldSet="default"
37
+ onChange={() => {}}
38
+ actions={['left', 'right', 'centered', 'justified']}
39
+ actionsInfoMap={{
40
+ left: [textLeftSVG, 'Text Left'],
41
+ right: [textRightSVG, 'Text Right'],
42
+ justified: [textJustifiedSVG, 'Text Justified'],
43
+ centered: [textCenteredSVG, 'Text Centered'],
44
+ }}
45
+ />,
46
+ );
47
+
48
+ expect(asFragment()).toMatchSnapshot();
49
+ });
50
+
51
+ it('renders actions info provided via props', () => {
52
+ const { asFragment } = renderWidget(
53
+ <ButtonsWidget
54
+ id="align"
55
+ title="Alignment"
56
+ fieldSet="default"
57
+ onChange={() => {}}
58
+ actions={['additional']}
59
+ actionsInfoMap={{
60
+ additional: [imageFullSVG, 'Additional action title'],
61
+ }}
62
+ />,
63
+ );
64
+
65
+ expect(asFragment()).toMatchSnapshot();
66
+ });
67
+
68
+ it('falls back to the action name when no info map entry is present', () => {
69
+ renderWidget(
70
+ <ButtonsWidget
71
+ id="align"
72
+ title="Alignment"
73
+ fieldSet="default"
74
+ onChange={() => {}}
75
+ actions={['missing']}
76
+ />,
77
+ );
78
+
79
+ expect(screen.getByRole('radio', { name: 'missing' })).toBeInTheDocument();
80
+ });
81
+
82
+ it('normalizes style definitions when an action is pressed', () => {
83
+ const handleChange = vi.fn();
84
+
85
+ renderWidget(
86
+ <ButtonsWidget
87
+ id="align"
88
+ title="Alignment"
89
+ fieldSet="default"
90
+ onChange={handleChange}
91
+ actions={[
92
+ {
93
+ name: 'wide',
94
+ label: 'Wide',
95
+ style: {
96
+ '--layout-width': 'wide',
97
+ },
98
+ },
99
+ ]}
100
+ actionsInfoMap={{
101
+ wide: ['Wide', 'Wide width'],
102
+ }}
103
+ />,
104
+ );
105
+
106
+ fireEvent.click(screen.getByRole('radio', { name: 'Wide width' }));
107
+
108
+ expect(handleChange).toHaveBeenCalledWith('align', {
109
+ '--layout-width': 'wide',
110
+ });
111
+ });
112
+
113
+ it('selects default value from string names', () => {
114
+ renderWidget(
115
+ <ButtonsWidget
116
+ id="align"
117
+ title="Alignment"
118
+ fieldSet="default"
119
+ onChange={() => {}}
120
+ actions={[
121
+ {
122
+ name: 'justified',
123
+ label: 'Justified',
124
+ style: {
125
+ '--layout-width': 'wide',
126
+ },
127
+ },
128
+ ]}
129
+ default="justified"
130
+ actionsInfoMap={{
131
+ justified: [textJustifiedSVG, 'Text Justified'],
132
+ }}
133
+ />,
134
+ );
135
+
136
+ expect(screen.getByRole('radio', { name: 'Text Justified' })).toBeChecked();
137
+ });
138
+ });
@@ -0,0 +1,195 @@
1
+ import React, { useEffect } from 'react';
2
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
3
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
4
+ import { Radio, RadioGroup } from '@plone/components';
5
+ import isEqual from 'lodash/isEqual';
6
+ import type { StyleDefinition } from '@plone/types';
7
+
8
+ /**
9
+ * A tuple that has an icon in the first element and a i18n string in the second.
10
+ */
11
+ export type ActionInfo = [React.ReactElement<any>, string] | [string, string];
12
+
13
+ type ActionValue = string | Record<`--${string}`, string>;
14
+
15
+ export type ButtonsWidgetProps = {
16
+ /**
17
+ * Unique identifier for the widget.
18
+ */
19
+ id: string;
20
+
21
+ /**
22
+ * Callback function to handle changes.
23
+ */
24
+ onChange: (id: string, value: ActionValue) => void;
25
+
26
+ /**
27
+ * List of actions available for the widget.
28
+ */
29
+ actions?: Array<StyleDefinition | string>;
30
+
31
+ /**
32
+ * Map containing additional the information (icon and i18n string) for each action.
33
+ */
34
+ actionsInfoMap?: Record<string, ActionInfo>;
35
+
36
+ /**
37
+ * List of actions to be filtered out. In case that we don't want the default ones
38
+ * we can filter them out.
39
+ */
40
+ filterActions?: string[];
41
+
42
+ /**
43
+ * Current value of the widget.
44
+ */
45
+ value?: ActionValue;
46
+
47
+ /**
48
+ * Default value of the widget.
49
+ */
50
+ default?: ActionValue;
51
+
52
+ /**
53
+ * Indicates if the widget is disabled.
54
+ */
55
+ disabled?: boolean;
56
+
57
+ /**
58
+ * Indicates if the widget is disabled (alternative flag for compatibility reasons).
59
+ */
60
+ isDisabled?: boolean;
61
+ [key: string]: any;
62
+ };
63
+
64
+ type NormalizedAction = {
65
+ name: string;
66
+ value: ActionValue;
67
+ };
68
+
69
+ const ButtonsWidget = (props: ButtonsWidgetProps) => {
70
+ const {
71
+ disabled,
72
+ id,
73
+ onChange,
74
+ actions = [],
75
+ actionsInfoMap,
76
+ value,
77
+ isDisabled,
78
+ default: defaultValue,
79
+ } = props;
80
+
81
+ const normalizedActions: NormalizedAction[] = actions.map((action) =>
82
+ typeof action === 'string'
83
+ ? { name: action, value: action }
84
+ : {
85
+ name: action.name,
86
+ value: action.style ?? action.name,
87
+ },
88
+ );
89
+
90
+ const selectedActionName = normalizedActions.find((action) =>
91
+ isEqual(value, action.value),
92
+ )?.name;
93
+
94
+ const defaultSelectedActionName = (() => {
95
+ if (!defaultValue) {
96
+ return undefined;
97
+ }
98
+
99
+ if (typeof defaultValue === 'string') {
100
+ const matchedByName = normalizedActions.find(
101
+ ({ name }) => name === defaultValue,
102
+ );
103
+
104
+ if (matchedByName) {
105
+ return matchedByName.name;
106
+ }
107
+ }
108
+
109
+ return normalizedActions.find(({ value: actionValue }) =>
110
+ isEqual(defaultValue, actionValue),
111
+ )?.name;
112
+ })();
113
+
114
+ const radioGroupValueProps: {
115
+ value?: string;
116
+ defaultValue?: string;
117
+ } = selectedActionName
118
+ ? { value: selectedActionName }
119
+ : defaultSelectedActionName
120
+ ? { defaultValue: defaultSelectedActionName }
121
+ : {};
122
+
123
+ const handleChange = (selectedName: string) => {
124
+ const selectedAction = normalizedActions.find(
125
+ ({ name }) => name === selectedName,
126
+ );
127
+
128
+ if (selectedAction) {
129
+ onChange(id, selectedAction.value);
130
+ }
131
+ };
132
+
133
+ // Synchronize default value if no value is set to data prop.
134
+ useEffect(() => {
135
+ // If `value` already matches any normalized action value, do nothing.
136
+ const existingMatch = normalizedActions.find(({ value: actionValue }) =>
137
+ isEqual(value, actionValue),
138
+ );
139
+
140
+ if (existingMatch) return;
141
+
142
+ // Otherwise, if there's a default value set the default style.
143
+ if (!defaultSelectedActionName) return;
144
+ const selected = normalizedActions.find(
145
+ ({ name }) => name === defaultSelectedActionName,
146
+ );
147
+ if (!selected) return;
148
+
149
+ onChange(id, selected.value);
150
+ }, [value, defaultSelectedActionName, onChange, normalizedActions, id]);
151
+
152
+ return (
153
+ <FormFieldWrapper {...props} className="widget">
154
+ <RadioGroup
155
+ aria-label={props.title || props.label || id}
156
+ orientation="horizontal"
157
+ {...radioGroupValueProps}
158
+ onChange={handleChange}
159
+ isDisabled={disabled || isDisabled}
160
+ className="buttons buttons-widget"
161
+ >
162
+ {normalizedActions.map((action) => {
163
+ const actionInfo = actionsInfoMap?.[action.name];
164
+ const [iconOrText, ariaLabel] = actionInfo ?? [
165
+ action.name,
166
+ action.name,
167
+ ];
168
+
169
+ return (
170
+ <Radio
171
+ key={action.name}
172
+ aria-label={ariaLabel}
173
+ value={action.name}
174
+ className="buttons-widget-option"
175
+ >
176
+ {typeof iconOrText === 'string' ? (
177
+ <div className="image-sizes-text">{iconOrText}</div>
178
+ ) : (
179
+ <Icon
180
+ // TODO: Refactor Icon component and type it correctly
181
+ name={iconOrText as any}
182
+ title={ariaLabel || action.name}
183
+ size="24px"
184
+ ariaHidden={true}
185
+ />
186
+ )}
187
+ </Radio>
188
+ );
189
+ })}
190
+ </RadioGroup>
191
+ </FormFieldWrapper>
192
+ );
193
+ };
194
+
195
+ export default ButtonsWidget;
@@ -7,12 +7,7 @@ import CheckboxGroupWidget from './CheckboxGroupWidget';
7
7
 
8
8
  const mockStore = configureStore();
9
9
 
10
- vi.mock('@plone/volto/helpers/Loadable/Loadable', async () => {
11
- return await import(
12
- '@plone/volto/helpers/Loadable/__mocks__/Loadable.vitest.jsx'
13
- );
14
- });
15
-
10
+ vi.mock('@plone/volto/helpers/Loadable/Loadable');
16
11
  beforeAll(async () => {
17
12
  const { __setLoadables } = await import(
18
13
  '@plone/volto/helpers/Loadable/Loadable'
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, useRef } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { defineMessages, useIntl } from 'react-intl';
4
4
  import loadable from '@loadable/component';
@@ -27,6 +27,10 @@ const messages = defineMessages({
27
27
  id: 'Time',
28
28
  defaultMessage: 'Time',
29
29
  },
30
+ clearDateTime: {
31
+ id: 'Clear date/time',
32
+ defaultMessage: 'Clear date and time',
33
+ },
30
34
  });
31
35
 
32
36
  const PrevIcon = () => (
@@ -80,16 +84,23 @@ const DatetimeWidgetComponent = (props) => {
80
84
  widget,
81
85
  noPastDates: propNoPastDates,
82
86
  isDisabled,
87
+ formData,
88
+ required,
83
89
  } = props;
84
90
 
85
91
  const intl = useIntl();
86
92
  const lang = intl.locale;
87
93
 
94
+ // timeInputRef: for aria-required (rc-time-picker has no aria props)
95
+ const timeInputRef = useRef(null);
96
+
88
97
  const [focused, setFocused] = useState(false);
89
98
  const [isDefault, setIsDefault] = useState(false);
90
99
 
91
100
  const { SingleDatePicker } = reactDates;
92
101
 
102
+ const renderWidget = !(id === 'end' && formData?.open_end);
103
+
93
104
  useEffect(() => {
94
105
  const parsedDateTime = parseDateTime(
95
106
  toBackendLang(lang),
@@ -107,7 +118,11 @@ const DatetimeWidgetComponent = (props) => {
107
118
  };
108
119
 
109
120
  const getDateOnly = () => {
110
- return dateOnly || widget === 'date';
121
+ return (
122
+ dateOnly ||
123
+ widget === 'date' ||
124
+ ((id === 'start' || id === 'end') && formData?.whole_day)
125
+ );
111
126
  };
112
127
 
113
128
  const onDateChange = (date) => {
@@ -151,67 +166,101 @@ const DatetimeWidgetComponent = (props) => {
151
166
  const datetime = getInternalValue();
152
167
  const isDateOnly = getDateOnly();
153
168
 
169
+ // aria-required for the time input (rc-time-picker is lazy-loaded,
170
+ // so MutationObserver is needed to catch when it mounts its input)
171
+
172
+ // rc-time-picker does not have aria props, so we need to set aria-required
173
+ // manually on the input element when the required prop changes
174
+
175
+ useEffect(() => {
176
+ if (!renderWidget || isDateOnly) return;
177
+
178
+ function applyTimeAria() {
179
+ const input = timeInputRef.current?.querySelector('input');
180
+ if (!input) return;
181
+ if (required) input.setAttribute('aria-required', 'true');
182
+ else input.removeAttribute('aria-required');
183
+ }
184
+
185
+ applyTimeAria();
186
+
187
+ const observer = new MutationObserver(applyTimeAria);
188
+ if (timeInputRef.current) {
189
+ observer.observe(timeInputRef.current, {
190
+ childList: true,
191
+ subtree: true,
192
+ });
193
+ }
194
+
195
+ return () => observer.disconnect();
196
+ }, [required, isDateOnly, renderWidget]);
197
+
154
198
  return (
155
199
  <FormFieldWrapper {...props}>
156
- <div className="date-time-widget-wrapper">
157
- <div
158
- className={cx('ui input date-input', {
159
- 'default-date': isDefault,
160
- })}
161
- >
162
- <SingleDatePicker
163
- date={datetime}
164
- disabled={isDisabled}
165
- onDateChange={onDateChange}
166
- focused={focused}
167
- numberOfMonths={1}
168
- {...(noPastDates ? {} : { isOutsideRange: () => false })}
169
- onFocusChange={onFocusChange}
170
- noBorder
171
- displayFormat={moment.default
172
- .localeData(toBackendLang(lang))
173
- .longDateFormat('L')}
174
- navPrev={<PrevIcon />}
175
- navNext={<NextIcon />}
176
- id={`${id}-date`}
177
- placeholder={intl.formatMessage(messages.date)}
178
- />
179
- </div>
180
- {!isDateOnly && (
200
+ {renderWidget && (
201
+ <div className="date-time-widget-wrapper">
181
202
  <div
182
- className={cx('ui input time-input', {
203
+ className={cx('ui input date-input', {
183
204
  'default-date': isDefault,
184
205
  })}
185
206
  >
186
- <TimePicker
207
+ <SingleDatePicker
208
+ date={datetime}
187
209
  disabled={isDisabled}
188
- defaultValue={datetime}
189
- value={datetime}
190
- onChange={onTimeChange}
191
- allowEmpty={false}
192
- showSecond={false}
193
- use12Hours={lang === 'en'}
194
- id={`${id}-time`}
195
- format={moment.default
210
+ onDateChange={onDateChange}
211
+ focused={focused}
212
+ numberOfMonths={1}
213
+ {...(noPastDates ? {} : { isOutsideRange: () => false })}
214
+ onFocusChange={onFocusChange}
215
+ noBorder
216
+ required={required}
217
+ displayFormat={moment.default
196
218
  .localeData(toBackendLang(lang))
197
- .longDateFormat('LT')}
198
- placeholder={intl.formatMessage(messages.time)}
199
- focusOnOpen
200
- placement="bottomRight"
219
+ .longDateFormat('L')}
220
+ navPrev={<PrevIcon />}
221
+ navNext={<NextIcon />}
222
+ id={`${id}-date`}
223
+ placeholder={intl.formatMessage(messages.date)}
201
224
  />
202
225
  </div>
203
- )}
204
- {resettable && (
205
- <button
206
- type="button"
207
- disabled={isDisabled || !datetime}
208
- onClick={onResetDates}
209
- className="item ui noborder button"
210
- >
211
- <Icon name={clearSVG} size="24px" className="close" />
212
- </button>
213
- )}
214
- </div>
226
+ {!isDateOnly && (
227
+ <div
228
+ ref={timeInputRef}
229
+ className={cx('ui input time-input', {
230
+ 'default-date': isDefault,
231
+ })}
232
+ >
233
+ <TimePicker
234
+ disabled={isDisabled}
235
+ defaultValue={datetime}
236
+ value={datetime}
237
+ onChange={onTimeChange}
238
+ allowEmpty={false}
239
+ showSecond={false}
240
+ use12Hours={lang === 'en'}
241
+ id={`${id}-time`}
242
+ format={moment.default
243
+ .localeData(toBackendLang(lang))
244
+ .longDateFormat('LT')}
245
+ placeholder={intl.formatMessage(messages.time)}
246
+ focusOnOpen
247
+ placement="bottomRight"
248
+ />
249
+ </div>
250
+ )}
251
+ {resettable && (
252
+ <button
253
+ type="button"
254
+ disabled={isDisabled || !datetime}
255
+ onClick={onResetDates}
256
+ className="item ui noborder button"
257
+ aria-label={intl.formatMessage(messages.clearDateTime)}
258
+ >
259
+ <Icon name={clearSVG} size="24px" className="close" />
260
+ </button>
261
+ )}
262
+ </div>
263
+ )}
215
264
  </FormFieldWrapper>
216
265
  );
217
266
  };
@@ -6,12 +6,7 @@ import { waitFor, render, screen } from '@testing-library/react';
6
6
 
7
7
  const mockStore = configureStore();
8
8
 
9
- vi.mock('@plone/volto/helpers/Loadable/Loadable', async () => {
10
- return await import(
11
- '@plone/volto/helpers/Loadable/__mocks__/Loadable.vitest.jsx'
12
- );
13
- });
14
-
9
+ vi.mock('@plone/volto/helpers/Loadable/Loadable');
15
10
  beforeAll(async () => {
16
11
  const { __setLoadables } = await import(
17
12
  '@plone/volto/helpers/Loadable/Loadable'
@@ -71,3 +66,58 @@ test('datetime widget converts UTC date and adapts to local datetime', async ()
71
66
  await waitFor(() => screen.getByPlaceholderText('Time'));
72
67
  expect(container).toMatchSnapshot();
73
68
  });
69
+
70
+ test('applies aria-required attribute to the date input when required prop is true', async () => {
71
+ const store = mockStore({
72
+ intl: {
73
+ locale: 'en',
74
+ messages: {},
75
+ },
76
+ });
77
+
78
+ const { container } = render(
79
+ <Provider store={store}>
80
+ <DatetimeWidget
81
+ id="required-field"
82
+ title="Required Field"
83
+ onChange={() => {}}
84
+ required={true}
85
+ />
86
+ </Provider>,
87
+ );
88
+
89
+ await waitFor(() => screen.getByPlaceholderText('Date'));
90
+
91
+ const dateInput = container.querySelector('.date-input input');
92
+
93
+ expect(dateInput).toHaveAttribute('required');
94
+ });
95
+
96
+ test('applies aria-required attribute to the time input when required prop is true', async () => {
97
+ const store = mockStore({
98
+ intl: {
99
+ locale: 'en',
100
+ messages: {},
101
+ },
102
+ });
103
+
104
+ const { container } = render(
105
+ <Provider store={store}>
106
+ <DatetimeWidget
107
+ id="required-field"
108
+ title="Required Field"
109
+ onChange={() => {}}
110
+ required={true}
111
+ />
112
+ </Provider>,
113
+ );
114
+
115
+ // Wait for the lazy-loaded TimePicker to be mounted in the DOM
116
+ await waitFor(() => screen.getByPlaceholderText('Time'));
117
+
118
+ // The rc-time-picker doesn't support aria-required natively,
119
+ // so we verify if our MutationObserver/useEffect successfully injected it.
120
+ const timeInput = container.querySelector('.time-input input');
121
+
122
+ expect(timeInput).toHaveAttribute('aria-required', 'true');
123
+ });
@@ -15,7 +15,6 @@ import UniversalLink from '@plone/volto/components/manage/UniversalLink/Universa
15
15
  import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
16
16
  import Image from '@plone/volto/components/theme/Image/Image';
17
17
  import loadable from '@loadable/component';
18
- import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
19
18
  import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
20
19
  import { defineMessages, useIntl } from 'react-intl';
21
20
  import { toast } from 'react-toastify';
@@ -60,6 +59,10 @@ const messages = defineMessages({
60
59
  id: 'File is not of the accepted type {accept}',
61
60
  defaultMessage: 'File is not of the accepted type {accept}',
62
61
  },
62
+ dragAndDropActionA11y: {
63
+ id: 'Press Enter to browse files from your computer.',
64
+ defaultMessage: 'Press Enter to browse files from your computer.',
65
+ },
63
66
  });
64
67
 
65
68
  /**
@@ -90,18 +93,25 @@ const FileWidget = (props) => {
90
93
  const [fileType, setFileType] = React.useState(false);
91
94
  const intl = useIntl();
92
95
 
96
+ const imgAttrs = React.useMemo(() => {
97
+ const data = {};
98
+ if (value?.download) {
99
+ data.item = {
100
+ '@id': value.download.substring(0, value.download.indexOf('/@@images')),
101
+ image: value,
102
+ };
103
+ } else if (value?.data) {
104
+ data.src = `data:${value['content-type']};${value.encoding},${value.data}`;
105
+ }
106
+ return data;
107
+ }, [value]);
108
+
93
109
  React.useEffect(() => {
94
110
  if (value && imageMimetypes.includes(value['content-type'])) {
95
111
  setFileType(true);
96
112
  }
97
113
  }, [value]);
98
114
 
99
- const imgsrc = value?.download
100
- ? `${flattenToAppURL(value?.download)}?id=${Date.now()}`
101
- : null || value?.data
102
- ? `data:${value['content-type']};${value.encoding},${value.data}`
103
- : null;
104
-
105
115
  /**
106
116
  * Drop handler
107
117
  * @method onDrop
@@ -175,7 +185,7 @@ const FileWidget = (props) => {
175
185
  <Image
176
186
  className="image-preview small ui image"
177
187
  id={`field-${id}-image`}
178
- src={imgsrc}
188
+ {...imgAttrs}
179
189
  />
180
190
  ) : (
181
191
  <div className="dropzone-placeholder">
@@ -199,6 +209,9 @@ const FileWidget = (props) => {
199
209
  {value
200
210
  ? intl.formatMessage(messages.replaceFile)
201
211
  : intl.formatMessage(messages.addNewFile)}
212
+ <span className="visually-hidden">
213
+ {intl.formatMessage(messages.dragAndDropActionA11y)}
214
+ </span>
202
215
  </label>
203
216
  <input
204
217
  {...getInputProps({