@shelamkoff/rector 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (668) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +17 -0
  3. package/README.md +23 -0
  4. package/THIRD_PARTY_LICENSES.txt +298 -0
  5. package/dist/I18n.d.ts +1 -0
  6. package/dist/core/Block.d.ts +110 -0
  7. package/dist/core/Block.js +390 -0
  8. package/dist/core/BlockAnimator.d.ts +39 -0
  9. package/dist/core/BlockAnimator.js +106 -0
  10. package/dist/core/BlockManager.d.ts +163 -0
  11. package/dist/core/BlockManager.js +640 -0
  12. package/dist/core/BlockOperations.d.ts +67 -0
  13. package/dist/core/BlockOperations.js +238 -0
  14. package/dist/core/ChangeNotifier.d.ts +15 -0
  15. package/dist/core/ChangeNotifier.js +55 -0
  16. package/dist/core/CommandDispatcher.d.ts +52 -0
  17. package/dist/core/CommandDispatcher.js +208 -0
  18. package/dist/core/CrossBlockSelection.d.ts +30 -0
  19. package/dist/core/CrossBlockSelection.js +74 -0
  20. package/dist/core/Diagnostics.d.ts +23 -0
  21. package/dist/core/Diagnostics.js +62 -0
  22. package/dist/core/DocumentSchema.d.ts +29 -0
  23. package/dist/core/DocumentSchema.js +147 -0
  24. package/dist/core/DocumentSnapshotStore.d.ts +34 -0
  25. package/dist/core/DocumentSnapshotStore.js +153 -0
  26. package/dist/core/DragManager.d.ts +17 -0
  27. package/dist/core/DragManager.js +223 -0
  28. package/dist/core/EditorFacade.d.ts +154 -0
  29. package/dist/core/EditorFacade.js +361 -0
  30. package/dist/core/EditorHolderOwnership.d.ts +12 -0
  31. package/dist/core/EditorHolderOwnership.js +28 -0
  32. package/dist/core/I18n.d.ts +147 -0
  33. package/dist/core/I18n.js +296 -0
  34. package/dist/core/InlinePatternMatcher.d.ts +26 -0
  35. package/dist/core/InlinePatternMatcher.js +267 -0
  36. package/dist/core/InlinePluginRegistry.d.ts +55 -0
  37. package/dist/core/InlinePluginRegistry.js +128 -0
  38. package/dist/core/KeyboardManager.d.ts +17 -0
  39. package/dist/core/KeyboardManager.js +231 -0
  40. package/dist/core/LifecycleScope.d.ts +19 -0
  41. package/dist/core/LifecycleScope.js +41 -0
  42. package/dist/core/MouseSelectionManager.d.ts +30 -0
  43. package/dist/core/MouseSelectionManager.js +307 -0
  44. package/dist/core/PluginOwnership.d.ts +9 -0
  45. package/dist/core/PluginOwnership.js +40 -0
  46. package/dist/core/PopupManager.d.ts +50 -0
  47. package/dist/core/PopupManager.js +183 -0
  48. package/dist/core/PreservedBlockPlugin.d.ts +11 -0
  49. package/dist/core/PreservedBlockPlugin.js +42 -0
  50. package/dist/core/PublicEditorApi.d.ts +82 -0
  51. package/dist/core/PublicEditorApi.js +158 -0
  52. package/dist/core/SelectionManager.d.ts +50 -0
  53. package/dist/core/SelectionManager.js +205 -0
  54. package/dist/core/ShortcutRegistry.d.ts +29 -0
  55. package/dist/core/ShortcutRegistry.js +106 -0
  56. package/dist/core/SlashCommands.d.ts +34 -0
  57. package/dist/core/SlashCommands.js +483 -0
  58. package/dist/core/StyleInjector.d.ts +1 -0
  59. package/dist/core/StyleInjector.js +1 -0
  60. package/dist/core/Tooltip.d.ts +17 -0
  61. package/dist/core/Tooltip.js +60 -0
  62. package/dist/core/TriggerManager.d.ts +17 -0
  63. package/dist/core/TriggerManager.js +159 -0
  64. package/dist/core/TypeSelector.d.ts +31 -0
  65. package/dist/core/TypeSelector.js +464 -0
  66. package/dist/core/UndoManager.d.ts +72 -0
  67. package/dist/core/UndoManager.js +383 -0
  68. package/dist/core/block-settings-menu/BlockActions.d.ts +81 -0
  69. package/dist/core/block-settings-menu/BlockActions.js +233 -0
  70. package/dist/core/block-settings-menu/BlockSettingsMenu.d.ts +46 -0
  71. package/dist/core/block-settings-menu/BlockSettingsMenu.js +205 -0
  72. package/dist/core/block-settings-menu/MenuBuilder.d.ts +43 -0
  73. package/dist/core/block-settings-menu/MenuBuilder.js +203 -0
  74. package/dist/core/block-settings-menu/MenuPositioner.d.ts +22 -0
  75. package/dist/core/block-settings-menu/MenuPositioner.js +79 -0
  76. package/dist/core/clipboard/Clipboard.d.ts +47 -0
  77. package/dist/core/clipboard/Clipboard.js +653 -0
  78. package/dist/core/clipboard/CrossBlockEditor.d.ts +43 -0
  79. package/dist/core/clipboard/CrossBlockEditor.js +198 -0
  80. package/dist/core/clipboard/PasteRouter.d.ts +27 -0
  81. package/dist/core/clipboard/PasteRouter.js +86 -0
  82. package/dist/core/clipboard/pasteInsert.d.ts +35 -0
  83. package/dist/core/clipboard/pasteInsert.js +215 -0
  84. package/dist/core/clipboard/pasteUtils.d.ts +25 -0
  85. package/dist/core/clipboard/pasteUtils.js +98 -0
  86. package/dist/core/config.d.ts +77 -0
  87. package/dist/core/config.js +100 -0
  88. package/dist/core/constants.d.ts +23 -0
  89. package/dist/core/constants.js +30 -0
  90. package/dist/core/crossBlockConvert.d.ts +38 -0
  91. package/dist/core/crossBlockConvert.js +253 -0
  92. package/dist/core/dom.d.ts +35 -0
  93. package/dist/core/dom.js +65 -0
  94. package/dist/core/editorEvents.d.ts +89 -0
  95. package/dist/core/editorEvents.js +60 -0
  96. package/dist/core/hydrateInlinePlugins.d.ts +9 -0
  97. package/dist/core/hydrateInlinePlugins.js +36 -0
  98. package/dist/core/icons.d.ts +10 -0
  99. package/dist/core/icons.js +21 -0
  100. package/dist/core/index.d.ts +59 -0
  101. package/dist/core/index.js +703 -0
  102. package/dist/core/inline-toolbar/ActionsPanel.d.ts +61 -0
  103. package/dist/core/inline-toolbar/ActionsPanel.js +132 -0
  104. package/dist/core/inline-toolbar/InlinePositioner.d.ts +20 -0
  105. package/dist/core/inline-toolbar/InlinePositioner.js +63 -0
  106. package/dist/core/inline-toolbar/InlineToolbar.d.ts +45 -0
  107. package/dist/core/inline-toolbar/InlineToolbar.js +456 -0
  108. package/dist/core/inline-toolbar/PluginControlsSlot.d.ts +52 -0
  109. package/dist/core/inline-toolbar/PluginControlsSlot.js +118 -0
  110. package/dist/core/inline-toolbar/SelectionTracker.d.ts +63 -0
  111. package/dist/core/inline-toolbar/SelectionTracker.js +173 -0
  112. package/dist/core/inlinePluginInsert.d.ts +13 -0
  113. package/dist/core/inlinePluginInsert.js +56 -0
  114. package/dist/core/locale/en.d.ts +35 -0
  115. package/dist/core/locale/en.js +37 -0
  116. package/dist/core/locale/ru.d.ts +35 -0
  117. package/dist/core/locale/ru.js +37 -0
  118. package/dist/core/menuKeyboardNav.d.ts +15 -0
  119. package/dist/core/menuKeyboardNav.js +52 -0
  120. package/dist/core/sanitize.d.ts +2 -0
  121. package/dist/core/sanitize.js +6 -0
  122. package/dist/core/splitConvert.d.ts +46 -0
  123. package/dist/core/splitConvert.js +190 -0
  124. package/dist/core/textOffset.d.ts +46 -0
  125. package/dist/core/textOffset.js +180 -0
  126. package/dist/core/themes/dark.css +30 -0
  127. package/dist/core/themes/light.css +33 -0
  128. package/dist/core/themes/variables.css +1170 -0
  129. package/dist/core/toolbar/OffcanvasRoot.d.ts +37 -0
  130. package/dist/core/toolbar/OffcanvasRoot.js +127 -0
  131. package/dist/core/toolbar/Toolbar.d.ts +70 -0
  132. package/dist/core/toolbar/Toolbar.js +432 -0
  133. package/dist/core/toolbar/ToolbarPositioner.d.ts +60 -0
  134. package/dist/core/toolbar/ToolbarPositioner.js +188 -0
  135. package/dist/core/toolbar/ToolboxBuilder.d.ts +44 -0
  136. package/dist/core/toolbar/ToolboxBuilder.js +260 -0
  137. package/dist/core/types.d.ts +747 -0
  138. package/dist/core/uid.d.ts +6 -0
  139. package/dist/core/uid.js +29 -0
  140. package/dist/index.d.ts +2 -0
  141. package/dist/index.js +1 -0
  142. package/dist/inline-plugins/README.md +35 -0
  143. package/dist/inline-plugins/README.ru.md +35 -0
  144. package/dist/inline-plugins/color/README.md +61 -0
  145. package/dist/inline-plugins/color/README.ru.md +61 -0
  146. package/dist/inline-plugins/color.d.ts +8 -0
  147. package/dist/inline-plugins/color.js +184 -0
  148. package/dist/inline-plugins/locale/en.d.ts +7 -0
  149. package/dist/inline-plugins/locale/en.js +6 -0
  150. package/dist/inline-plugins/locale/ru.d.ts +7 -0
  151. package/dist/inline-plugins/locale/ru.js +6 -0
  152. package/dist/inline-plugins/mention/README.md +97 -0
  153. package/dist/inline-plugins/mention/README.ru.md +97 -0
  154. package/dist/inline-plugins/mention/index.d.ts +110 -0
  155. package/dist/inline-plugins/mention/index.js +1897 -0
  156. package/dist/inline-plugins/mention/styles.css +183 -0
  157. package/dist/inline-plugins/mention/widget.d.ts +16 -0
  158. package/dist/inline-plugins/mention/widget.js +75 -0
  159. package/dist/inline-tools/align.d.ts +14 -0
  160. package/dist/inline-tools/align.js +120 -0
  161. package/dist/inline-tools/bold.d.ts +8 -0
  162. package/dist/inline-tools/bold.js +12 -0
  163. package/dist/inline-tools/caseTransform.d.ts +8 -0
  164. package/dist/inline-tools/caseTransform.js +87 -0
  165. package/dist/inline-tools/clearFormatting.d.ts +8 -0
  166. package/dist/inline-tools/clearFormatting.js +74 -0
  167. package/dist/inline-tools/code.d.ts +8 -0
  168. package/dist/inline-tools/code.js +12 -0
  169. package/dist/inline-tools/colorPicker.d.ts +7 -0
  170. package/dist/inline-tools/colorPicker.js +271 -0
  171. package/dist/inline-tools/defaults.d.ts +10 -0
  172. package/dist/inline-tools/defaults.js +57 -0
  173. package/dist/inline-tools/fontSize.d.ts +7 -0
  174. package/dist/inline-tools/fontSize.js +524 -0
  175. package/dist/inline-tools/italic.d.ts +8 -0
  176. package/dist/inline-tools/italic.js +12 -0
  177. package/dist/inline-tools/link.d.ts +12 -0
  178. package/dist/inline-tools/link.js +251 -0
  179. package/dist/inline-tools/marker.d.ts +8 -0
  180. package/dist/inline-tools/marker.js +12 -0
  181. package/dist/inline-tools/scriptTool.d.ts +11 -0
  182. package/dist/inline-tools/scriptTool.js +149 -0
  183. package/dist/inline-tools/strikethrough.d.ts +8 -0
  184. package/dist/inline-tools/strikethrough.js +12 -0
  185. package/dist/inline-tools/utils.d.ts +220 -0
  186. package/dist/inline-tools/utils.js +992 -0
  187. package/dist/locale/en.d.ts +311 -0
  188. package/dist/locale/en.js +52 -0
  189. package/dist/locale/ru.d.ts +315 -0
  190. package/dist/locale/ru.js +52 -0
  191. package/dist/plugins/BlockPluginAbstract.d.ts +82 -0
  192. package/dist/plugins/BlockPluginAbstract.js +111 -0
  193. package/dist/plugins/README.md +44 -0
  194. package/dist/plugins/README.ru.md +40 -0
  195. package/dist/plugins/async.d.ts +35 -0
  196. package/dist/plugins/async.js +79 -0
  197. package/dist/plugins/attaches/README.md +86 -0
  198. package/dist/plugins/attaches/README.ru.md +87 -0
  199. package/dist/plugins/attaches/attaches.css +374 -0
  200. package/dist/plugins/attaches/index.d.ts +133 -0
  201. package/dist/plugins/attaches/index.js +921 -0
  202. package/dist/plugins/attaches/locale/en.d.ts +23 -0
  203. package/dist/plugins/attaches/locale/en.js +19 -0
  204. package/dist/plugins/attaches/locale/keys.d.ts +5 -0
  205. package/dist/plugins/attaches/locale/ru.d.ts +25 -0
  206. package/dist/plugins/attaches/locale/ru.js +19 -0
  207. package/dist/plugins/carousel/README.md +108 -0
  208. package/dist/plugins/carousel/README.ru.md +109 -0
  209. package/dist/plugins/carousel/carousel.css +198 -0
  210. package/dist/plugins/carousel/icons.d.ts +12 -0
  211. package/dist/plugins/carousel/icons.js +13 -0
  212. package/dist/plugins/carousel/index.d.ts +137 -0
  213. package/dist/plugins/carousel/index.js +859 -0
  214. package/dist/plugins/carousel/locale/en.d.ts +41 -0
  215. package/dist/plugins/carousel/locale/en.js +40 -0
  216. package/dist/plugins/carousel/locale/keys.d.ts +2 -0
  217. package/dist/plugins/carousel/locale/ru.d.ts +41 -0
  218. package/dist/plugins/carousel/locale/ru.js +40 -0
  219. package/dist/plugins/carousel/state.d.ts +29 -0
  220. package/dist/plugins/carousel/state.js +42 -0
  221. package/dist/plugins/checklist/README.md +54 -0
  222. package/dist/plugins/checklist/README.ru.md +55 -0
  223. package/dist/plugins/checklist/checklist.css +80 -0
  224. package/dist/plugins/checklist/index.d.ts +75 -0
  225. package/dist/plugins/checklist/index.js +407 -0
  226. package/dist/plugins/checklist/locale/en.d.ts +5 -0
  227. package/dist/plugins/checklist/locale/en.js +4 -0
  228. package/dist/plugins/checklist/locale/keys.d.ts +5 -0
  229. package/dist/plugins/checklist/locale/ru.d.ts +5 -0
  230. package/dist/plugins/checklist/locale/ru.js +4 -0
  231. package/dist/plugins/checklist/mapTextFields.d.ts +1 -0
  232. package/dist/plugins/checklist/mapTextFields.js +1 -0
  233. package/dist/plugins/code/README.md +59 -0
  234. package/dist/plugins/code/README.ru.md +60 -0
  235. package/dist/plugins/code/code.css +360 -0
  236. package/dist/plugins/code/index.d.ts +109 -0
  237. package/dist/plugins/code/index.js +1012 -0
  238. package/dist/plugins/code/locale/en.d.ts +11 -0
  239. package/dist/plugins/code/locale/en.js +10 -0
  240. package/dist/plugins/code/locale/keys.d.ts +5 -0
  241. package/dist/plugins/code/locale/ru.d.ts +11 -0
  242. package/dist/plugins/code/locale/ru.js +10 -0
  243. package/dist/plugins/columns/README.md +55 -0
  244. package/dist/plugins/columns/README.ru.md +56 -0
  245. package/dist/plugins/columns/columns.css +103 -0
  246. package/dist/plugins/columns/index.d.ts +58 -0
  247. package/dist/plugins/columns/index.js +236 -0
  248. package/dist/plugins/columns/locale/en.d.ts +6 -0
  249. package/dist/plugins/columns/locale/en.js +5 -0
  250. package/dist/plugins/columns/locale/keys.d.ts +5 -0
  251. package/dist/plugins/columns/locale/ru.d.ts +6 -0
  252. package/dist/plugins/columns/locale/ru.js +5 -0
  253. package/dist/plugins/delimiter/README.md +48 -0
  254. package/dist/plugins/delimiter/README.ru.md +49 -0
  255. package/dist/plugins/delimiter/delimiter.css +22 -0
  256. package/dist/plugins/delimiter/index.d.ts +38 -0
  257. package/dist/plugins/delimiter/index.js +69 -0
  258. package/dist/plugins/delimiter/locale/en.d.ts +4 -0
  259. package/dist/plugins/delimiter/locale/en.js +3 -0
  260. package/dist/plugins/delimiter/locale/keys.d.ts +5 -0
  261. package/dist/plugins/delimiter/locale/ru.d.ts +4 -0
  262. package/dist/plugins/delimiter/locale/ru.js +3 -0
  263. package/dist/plugins/embed/README.md +81 -0
  264. package/dist/plugins/embed/README.ru.md +82 -0
  265. package/dist/plugins/embed/embed.css +558 -0
  266. package/dist/plugins/embed/index.d.ts +271 -0
  267. package/dist/plugins/embed/index.js +967 -0
  268. package/dist/plugins/embed/locale/en.d.ts +20 -0
  269. package/dist/plugins/embed/locale/en.js +19 -0
  270. package/dist/plugins/embed/locale/keys.d.ts +5 -0
  271. package/dist/plugins/embed/locale/ru.d.ts +20 -0
  272. package/dist/plugins/embed/locale/ru.js +19 -0
  273. package/dist/plugins/embed/player.d.ts +1 -0
  274. package/dist/plugins/embed/player.js +1 -0
  275. package/dist/plugins/gallery/README.md +92 -0
  276. package/dist/plugins/gallery/README.ru.md +93 -0
  277. package/dist/plugins/gallery/css.d.ts +52 -0
  278. package/dist/plugins/gallery/css.js +67 -0
  279. package/dist/plugins/gallery/gallery.css +860 -0
  280. package/dist/plugins/gallery/icons.d.ts +13 -0
  281. package/dist/plugins/gallery/icons.js +45 -0
  282. package/dist/plugins/gallery/index.d.ts +126 -0
  283. package/dist/plugins/gallery/index.js +355 -0
  284. package/dist/plugins/gallery/layout.d.ts +35 -0
  285. package/dist/plugins/gallery/layout.js +79 -0
  286. package/dist/plugins/gallery/locale/en.d.ts +36 -0
  287. package/dist/plugins/gallery/locale/en.js +35 -0
  288. package/dist/plugins/gallery/locale/keys.d.ts +5 -0
  289. package/dist/plugins/gallery/locale/ru.d.ts +36 -0
  290. package/dist/plugins/gallery/locale/ru.js +35 -0
  291. package/dist/plugins/gallery/settings.d.ts +25 -0
  292. package/dist/plugins/gallery/settings.js +199 -0
  293. package/dist/plugins/gallery/slot.d.ts +73 -0
  294. package/dist/plugins/gallery/slot.js +260 -0
  295. package/dist/plugins/gallery/state.d.ts +67 -0
  296. package/dist/plugins/gallery/state.js +133 -0
  297. package/dist/plugins/gallery/styles.d.ts +10 -0
  298. package/dist/plugins/gallery/styles.js +24 -0
  299. package/dist/plugins/gallery/uploader.d.ts +31 -0
  300. package/dist/plugins/gallery/uploader.js +111 -0
  301. package/dist/plugins/gallery/view-empty.d.ts +41 -0
  302. package/dist/plugins/gallery/view-empty.js +56 -0
  303. package/dist/plugins/gallery/view-filled.d.ts +55 -0
  304. package/dist/plugins/gallery/view-filled.js +353 -0
  305. package/dist/plugins/heading/HeadingLevelSelect.d.ts +16 -0
  306. package/dist/plugins/heading/HeadingLevelSelect.js +183 -0
  307. package/dist/plugins/heading/README.md +58 -0
  308. package/dist/plugins/heading/README.ru.md +59 -0
  309. package/dist/plugins/heading/heading.css +76 -0
  310. package/dist/plugins/heading/index.d.ts +124 -0
  311. package/dist/plugins/heading/index.js +273 -0
  312. package/dist/plugins/heading/locale/en.d.ts +11 -0
  313. package/dist/plugins/heading/locale/en.js +10 -0
  314. package/dist/plugins/heading/locale/keys.d.ts +5 -0
  315. package/dist/plugins/heading/locale/ru.d.ts +11 -0
  316. package/dist/plugins/heading/locale/ru.js +10 -0
  317. package/dist/plugins/heading/mapTextFields.d.ts +1 -0
  318. package/dist/plugins/heading/mapTextFields.js +1 -0
  319. package/dist/plugins/image/README.md +93 -0
  320. package/dist/plugins/image/README.ru.md +94 -0
  321. package/dist/plugins/image/css.d.ts +47 -0
  322. package/dist/plugins/image/css.js +47 -0
  323. package/dist/plugins/image/icons.d.ts +13 -0
  324. package/dist/plugins/image/icons.js +26 -0
  325. package/dist/plugins/image/image.css +559 -0
  326. package/dist/plugins/image/index.d.ts +129 -0
  327. package/dist/plugins/image/index.js +346 -0
  328. package/dist/plugins/image/locale/en.d.ts +39 -0
  329. package/dist/plugins/image/locale/en.js +38 -0
  330. package/dist/plugins/image/locale/keys.d.ts +5 -0
  331. package/dist/plugins/image/locale/ru.d.ts +39 -0
  332. package/dist/plugins/image/locale/ru.js +38 -0
  333. package/dist/plugins/image/settings.d.ts +23 -0
  334. package/dist/plugins/image/settings.js +347 -0
  335. package/dist/plugins/image/state.d.ts +86 -0
  336. package/dist/plugins/image/state.js +151 -0
  337. package/dist/plugins/image/styles.d.ts +21 -0
  338. package/dist/plugins/image/styles.js +58 -0
  339. package/dist/plugins/image/uploader.d.ts +31 -0
  340. package/dist/plugins/image/uploader.js +92 -0
  341. package/dist/plugins/image/view-empty.d.ts +44 -0
  342. package/dist/plugins/image/view-empty.js +61 -0
  343. package/dist/plugins/image/view-filled.d.ts +49 -0
  344. package/dist/plugins/image/view-filled.js +282 -0
  345. package/dist/plugins/index.d.ts +21 -0
  346. package/dist/plugins/index.js +22 -0
  347. package/dist/plugins/link-preview/README.md +69 -0
  348. package/dist/plugins/link-preview/README.ru.md +70 -0
  349. package/dist/plugins/link-preview/index.d.ts +169 -0
  350. package/dist/plugins/link-preview/index.js +698 -0
  351. package/dist/plugins/link-preview/link-preview.css +254 -0
  352. package/dist/plugins/link-preview/locale/en.d.ts +14 -0
  353. package/dist/plugins/link-preview/locale/en.js +13 -0
  354. package/dist/plugins/link-preview/locale/keys.d.ts +5 -0
  355. package/dist/plugins/link-preview/locale/ru.d.ts +14 -0
  356. package/dist/plugins/link-preview/locale/ru.js +13 -0
  357. package/dist/plugins/list/README.md +59 -0
  358. package/dist/plugins/list/README.ru.md +60 -0
  359. package/dist/plugins/list/index.d.ts +134 -0
  360. package/dist/plugins/list/index.js +566 -0
  361. package/dist/plugins/list/list.css +57 -0
  362. package/dist/plugins/list/locale/en.d.ts +5 -0
  363. package/dist/plugins/list/locale/en.js +4 -0
  364. package/dist/plugins/list/locale/keys.d.ts +5 -0
  365. package/dist/plugins/list/locale/ru.d.ts +5 -0
  366. package/dist/plugins/list/locale/ru.js +4 -0
  367. package/dist/plugins/list/mapTextFields.d.ts +1 -0
  368. package/dist/plugins/list/mapTextFields.js +1 -0
  369. package/dist/plugins/paragraph/README.md +57 -0
  370. package/dist/plugins/paragraph/README.ru.md +58 -0
  371. package/dist/plugins/paragraph/index.d.ts +112 -0
  372. package/dist/plugins/paragraph/index.js +158 -0
  373. package/dist/plugins/paragraph/locale/en.d.ts +5 -0
  374. package/dist/plugins/paragraph/locale/en.js +4 -0
  375. package/dist/plugins/paragraph/locale/keys.d.ts +5 -0
  376. package/dist/plugins/paragraph/locale/ru.d.ts +5 -0
  377. package/dist/plugins/paragraph/locale/ru.js +4 -0
  378. package/dist/plugins/paragraph/mapTextFields.d.ts +1 -0
  379. package/dist/plugins/paragraph/mapTextFields.js +1 -0
  380. package/dist/plugins/paragraph/paragraph.css +56 -0
  381. package/dist/plugins/person/README.md +72 -0
  382. package/dist/plugins/person/README.ru.md +73 -0
  383. package/dist/plugins/person/index.d.ts +189 -0
  384. package/dist/plugins/person/index.js +777 -0
  385. package/dist/plugins/person/locale/en.d.ts +18 -0
  386. package/dist/plugins/person/locale/en.js +17 -0
  387. package/dist/plugins/person/locale/keys.d.ts +5 -0
  388. package/dist/plugins/person/locale/ru.d.ts +18 -0
  389. package/dist/plugins/person/locale/ru.js +17 -0
  390. package/dist/plugins/person/person.css +431 -0
  391. package/dist/plugins/person/socialResolver.d.ts +18 -0
  392. package/dist/plugins/person/socialResolver.js +69 -0
  393. package/dist/plugins/poll/README.md +96 -0
  394. package/dist/plugins/poll/README.ru.md +97 -0
  395. package/dist/plugins/poll/index.d.ts +134 -0
  396. package/dist/plugins/poll/index.js +825 -0
  397. package/dist/plugins/poll/locale/en.d.ts +23 -0
  398. package/dist/plugins/poll/locale/en.js +22 -0
  399. package/dist/plugins/poll/locale/keys.d.ts +5 -0
  400. package/dist/plugins/poll/locale/ru.d.ts +23 -0
  401. package/dist/plugins/poll/locale/ru.js +22 -0
  402. package/dist/plugins/poll/poll.css +400 -0
  403. package/dist/plugins/quote/README.md +55 -0
  404. package/dist/plugins/quote/README.ru.md +56 -0
  405. package/dist/plugins/quote/index.d.ts +81 -0
  406. package/dist/plugins/quote/index.js +165 -0
  407. package/dist/plugins/quote/locale/en.d.ts +6 -0
  408. package/dist/plugins/quote/locale/en.js +5 -0
  409. package/dist/plugins/quote/locale/keys.d.ts +5 -0
  410. package/dist/plugins/quote/locale/ru.d.ts +6 -0
  411. package/dist/plugins/quote/locale/ru.js +5 -0
  412. package/dist/plugins/quote/mapTextFields.d.ts +1 -0
  413. package/dist/plugins/quote/mapTextFields.js +1 -0
  414. package/dist/plugins/quote/quote.css +72 -0
  415. package/dist/plugins/raw/README.md +54 -0
  416. package/dist/plugins/raw/README.ru.md +55 -0
  417. package/dist/plugins/raw/index.d.ts +62 -0
  418. package/dist/plugins/raw/index.js +227 -0
  419. package/dist/plugins/raw/locale/en.d.ts +7 -0
  420. package/dist/plugins/raw/locale/en.js +6 -0
  421. package/dist/plugins/raw/locale/keys.d.ts +5 -0
  422. package/dist/plugins/raw/locale/ru.d.ts +7 -0
  423. package/dist/plugins/raw/locale/ru.js +6 -0
  424. package/dist/plugins/raw/raw.css +127 -0
  425. package/dist/plugins/shared/actionBar.d.ts +19 -0
  426. package/dist/plugins/shared/actionBar.js +35 -0
  427. package/dist/plugins/shared/dropzone.d.ts +106 -0
  428. package/dist/plugins/shared/dropzone.js +107 -0
  429. package/dist/plugins/shared/fileInput.d.ts +44 -0
  430. package/dist/plugins/shared/fileInput.js +62 -0
  431. package/dist/plugins/spoiler/README.md +55 -0
  432. package/dist/plugins/spoiler/README.ru.md +56 -0
  433. package/dist/plugins/spoiler/index.d.ts +61 -0
  434. package/dist/plugins/spoiler/index.js +169 -0
  435. package/dist/plugins/spoiler/locale/en.d.ts +7 -0
  436. package/dist/plugins/spoiler/locale/en.js +6 -0
  437. package/dist/plugins/spoiler/locale/keys.d.ts +5 -0
  438. package/dist/plugins/spoiler/locale/ru.d.ts +7 -0
  439. package/dist/plugins/spoiler/locale/ru.js +6 -0
  440. package/dist/plugins/spoiler/spoiler.css +101 -0
  441. package/dist/plugins/table/README.md +55 -0
  442. package/dist/plugins/table/README.ru.md +56 -0
  443. package/dist/plugins/table/index.d.ts +85 -0
  444. package/dist/plugins/table/index.js +399 -0
  445. package/dist/plugins/table/locale/en.d.ts +9 -0
  446. package/dist/plugins/table/locale/en.js +8 -0
  447. package/dist/plugins/table/locale/keys.d.ts +5 -0
  448. package/dist/plugins/table/locale/ru.d.ts +9 -0
  449. package/dist/plugins/table/locale/ru.js +8 -0
  450. package/dist/plugins/table/table.css +58 -0
  451. package/dist/plugins/toggle/README.md +56 -0
  452. package/dist/plugins/toggle/README.ru.md +57 -0
  453. package/dist/plugins/toggle/index.d.ts +69 -0
  454. package/dist/plugins/toggle/index.js +191 -0
  455. package/dist/plugins/toggle/locale/en.d.ts +7 -0
  456. package/dist/plugins/toggle/locale/en.js +6 -0
  457. package/dist/plugins/toggle/locale/keys.d.ts +5 -0
  458. package/dist/plugins/toggle/locale/ru.d.ts +7 -0
  459. package/dist/plugins/toggle/locale/ru.js +6 -0
  460. package/dist/plugins/toggle/toggle.css +101 -0
  461. package/dist/plugins/warning/README.md +55 -0
  462. package/dist/plugins/warning/README.ru.md +56 -0
  463. package/dist/plugins/warning/index.d.ts +66 -0
  464. package/dist/plugins/warning/index.js +167 -0
  465. package/dist/plugins/warning/locale/en.d.ts +6 -0
  466. package/dist/plugins/warning/locale/en.js +5 -0
  467. package/dist/plugins/warning/locale/keys.d.ts +5 -0
  468. package/dist/plugins/warning/locale/ru.d.ts +6 -0
  469. package/dist/plugins/warning/locale/ru.js +5 -0
  470. package/dist/plugins/warning/warning.css +63 -0
  471. package/dist/renderer/async.d.ts +1 -0
  472. package/dist/renderer/async.js +7 -0
  473. package/dist/renderer/errors.d.ts +27 -0
  474. package/dist/renderer/errors.js +48 -0
  475. package/dist/renderer/index.d.ts +82 -0
  476. package/dist/renderer/index.js +413 -0
  477. package/dist/renderer/inline.d.ts +5 -0
  478. package/dist/renderer/inline.js +61 -0
  479. package/dist/renderer/locale/en.d.ts +35 -0
  480. package/dist/renderer/locale/en.js +29 -0
  481. package/dist/renderer/locale/ru.d.ts +37 -0
  482. package/dist/renderer/locale/ru.js +29 -0
  483. package/dist/renderer/renderers/README.md +7 -0
  484. package/dist/renderer/renderers/README.ru.md +5 -0
  485. package/dist/renderer/renderers/async.d.ts +51 -0
  486. package/dist/renderer/renderers/async.js +99 -0
  487. package/dist/renderer/renderers/attaches/README.md +48 -0
  488. package/dist/renderer/renderers/attaches/README.ru.md +48 -0
  489. package/dist/renderer/renderers/attaches/index.d.ts +37 -0
  490. package/dist/renderer/renderers/attaches/index.js +451 -0
  491. package/dist/renderer/renderers/attaches/locale/en.d.ts +11 -0
  492. package/dist/renderer/renderers/attaches/locale/en.js +7 -0
  493. package/dist/renderer/renderers/attaches/locale/ru.d.ts +13 -0
  494. package/dist/renderer/renderers/attaches/locale/ru.js +7 -0
  495. package/dist/renderer/renderers/attaches/styles.css +196 -0
  496. package/dist/renderer/renderers/carousel/README.md +43 -0
  497. package/dist/renderer/renderers/carousel/README.ru.md +43 -0
  498. package/dist/renderer/renderers/carousel/index.d.ts +7 -0
  499. package/dist/renderer/renderers/carousel/index.js +110 -0
  500. package/dist/renderer/renderers/carousel/styles.css +7 -0
  501. package/dist/renderer/renderers/checklist/README.md +33 -0
  502. package/dist/renderer/renderers/checklist/README.ru.md +33 -0
  503. package/dist/renderer/renderers/checklist/index.d.ts +7 -0
  504. package/dist/renderer/renderers/checklist/index.js +55 -0
  505. package/dist/renderer/renderers/checklist/styles.css +54 -0
  506. package/dist/renderer/renderers/code/README.md +33 -0
  507. package/dist/renderer/renderers/code/README.ru.md +33 -0
  508. package/dist/renderer/renderers/code/index.d.ts +9 -0
  509. package/dist/renderer/renderers/code/index.js +137 -0
  510. package/dist/renderer/renderers/code/locale/en.d.ts +5 -0
  511. package/dist/renderer/renderers/code/locale/en.js +4 -0
  512. package/dist/renderer/renderers/code/locale/ru.d.ts +5 -0
  513. package/dist/renderer/renderers/code/locale/ru.js +4 -0
  514. package/dist/renderer/renderers/code/styles.css +330 -0
  515. package/dist/renderer/renderers/columns/README.md +33 -0
  516. package/dist/renderer/renderers/columns/README.ru.md +33 -0
  517. package/dist/renderer/renderers/columns/index.d.ts +7 -0
  518. package/dist/renderer/renderers/columns/index.js +53 -0
  519. package/dist/renderer/renderers/columns/styles.css +5 -0
  520. package/dist/renderer/renderers/delimiter/README.md +33 -0
  521. package/dist/renderer/renderers/delimiter/README.ru.md +33 -0
  522. package/dist/renderer/renderers/delimiter/index.d.ts +7 -0
  523. package/dist/renderer/renderers/delimiter/index.js +28 -0
  524. package/dist/renderer/renderers/delimiter/styles.css +12 -0
  525. package/dist/renderer/renderers/embed/README.md +35 -0
  526. package/dist/renderer/renderers/embed/README.ru.md +35 -0
  527. package/dist/renderer/renderers/embed/index.d.ts +8 -0
  528. package/dist/renderer/renderers/embed/index.js +66 -0
  529. package/dist/renderer/renderers/embed/styles.css +217 -0
  530. package/dist/renderer/renderers/gallery/README.md +41 -0
  531. package/dist/renderer/renderers/gallery/README.ru.md +41 -0
  532. package/dist/renderer/renderers/gallery/index.d.ts +8 -0
  533. package/dist/renderer/renderers/gallery/index.js +329 -0
  534. package/dist/renderer/renderers/gallery/styles.css +419 -0
  535. package/dist/renderer/renderers/heading/README.md +33 -0
  536. package/dist/renderer/renderers/heading/README.ru.md +33 -0
  537. package/dist/renderer/renderers/heading/index.d.ts +8 -0
  538. package/dist/renderer/renderers/heading/index.js +38 -0
  539. package/dist/renderer/renderers/heading/styles.css +26 -0
  540. package/dist/renderer/renderers/image/README.md +42 -0
  541. package/dist/renderer/renderers/image/README.ru.md +42 -0
  542. package/dist/renderer/renderers/image/index.d.ts +7 -0
  543. package/dist/renderer/renderers/image/index.js +93 -0
  544. package/dist/renderer/renderers/image/styles.css +40 -0
  545. package/dist/renderer/renderers/index.d.ts +47 -0
  546. package/dist/renderer/renderers/index.js +125 -0
  547. package/dist/renderer/renderers/link-preview/README.md +43 -0
  548. package/dist/renderer/renderers/link-preview/README.ru.md +43 -0
  549. package/dist/renderer/renderers/link-preview/index.d.ts +9 -0
  550. package/dist/renderer/renderers/link-preview/index.js +118 -0
  551. package/dist/renderer/renderers/link-preview/styles.css +246 -0
  552. package/dist/renderer/renderers/list/README.md +33 -0
  553. package/dist/renderer/renderers/list/README.ru.md +33 -0
  554. package/dist/renderer/renderers/list/index.d.ts +8 -0
  555. package/dist/renderer/renderers/list/index.js +42 -0
  556. package/dist/renderer/renderers/list/styles.css +20 -0
  557. package/dist/renderer/renderers/paragraph/README.md +33 -0
  558. package/dist/renderer/renderers/paragraph/README.ru.md +33 -0
  559. package/dist/renderer/renderers/paragraph/index.d.ts +7 -0
  560. package/dist/renderer/renderers/paragraph/index.js +35 -0
  561. package/dist/renderer/renderers/paragraph/styles.css +3 -0
  562. package/dist/renderer/renderers/person/README.md +44 -0
  563. package/dist/renderer/renderers/person/README.ru.md +44 -0
  564. package/dist/renderer/renderers/person/index.d.ts +7 -0
  565. package/dist/renderer/renderers/person/index.js +213 -0
  566. package/dist/renderer/renderers/person/locale/en.d.ts +6 -0
  567. package/dist/renderer/renderers/person/locale/en.js +5 -0
  568. package/dist/renderer/renderers/person/locale/ru.d.ts +6 -0
  569. package/dist/renderer/renderers/person/locale/ru.js +5 -0
  570. package/dist/renderer/renderers/person/styles.css +47 -0
  571. package/dist/renderer/renderers/poll/README.md +68 -0
  572. package/dist/renderer/renderers/poll/README.ru.md +68 -0
  573. package/dist/renderer/renderers/poll/index.d.ts +9 -0
  574. package/dist/renderer/renderers/poll/index.js +339 -0
  575. package/dist/renderer/renderers/poll/styles.css +148 -0
  576. package/dist/renderer/renderers/quote/README.md +33 -0
  577. package/dist/renderer/renderers/quote/README.ru.md +33 -0
  578. package/dist/renderer/renderers/quote/index.d.ts +7 -0
  579. package/dist/renderer/renderers/quote/index.js +59 -0
  580. package/dist/renderer/renderers/quote/styles.css +43 -0
  581. package/dist/renderer/renderers/raw/README.md +33 -0
  582. package/dist/renderer/renderers/raw/README.ru.md +33 -0
  583. package/dist/renderer/renderers/raw/index.d.ts +7 -0
  584. package/dist/renderer/renderers/raw/index.js +27 -0
  585. package/dist/renderer/renderers/spoiler/README.md +33 -0
  586. package/dist/renderer/renderers/spoiler/README.ru.md +33 -0
  587. package/dist/renderer/renderers/spoiler/index.d.ts +9 -0
  588. package/dist/renderer/renderers/spoiler/index.js +84 -0
  589. package/dist/renderer/renderers/spoiler/locale/en.d.ts +4 -0
  590. package/dist/renderer/renderers/spoiler/locale/en.js +3 -0
  591. package/dist/renderer/renderers/spoiler/locale/ru.d.ts +4 -0
  592. package/dist/renderer/renderers/spoiler/locale/ru.js +3 -0
  593. package/dist/renderer/renderers/spoiler/styles.css +61 -0
  594. package/dist/renderer/renderers/table/README.md +33 -0
  595. package/dist/renderer/renderers/table/README.ru.md +33 -0
  596. package/dist/renderer/renderers/table/index.d.ts +7 -0
  597. package/dist/renderer/renderers/table/index.js +83 -0
  598. package/dist/renderer/renderers/table/styles.css +34 -0
  599. package/dist/renderer/renderers/toggle/README.md +33 -0
  600. package/dist/renderer/renderers/toggle/README.ru.md +33 -0
  601. package/dist/renderer/renderers/toggle/index.d.ts +7 -0
  602. package/dist/renderer/renderers/toggle/index.js +59 -0
  603. package/dist/renderer/renderers/toggle/styles.css +49 -0
  604. package/dist/renderer/renderers/warning/README.md +33 -0
  605. package/dist/renderer/renderers/warning/README.ru.md +33 -0
  606. package/dist/renderer/renderers/warning/index.d.ts +7 -0
  607. package/dist/renderer/renderers/warning/index.js +62 -0
  608. package/dist/renderer/renderers/warning/styles.css +41 -0
  609. package/dist/renderer/styles/base.css +168 -0
  610. package/dist/renderer/types.d.ts +464 -0
  611. package/dist/shared/blockDataNormalizers.d.ts +14 -0
  612. package/dist/shared/blockDataNormalizers.js +235 -0
  613. package/dist/shared/blockDataValidators.d.ts +81 -0
  614. package/dist/shared/blockDataValidators.js +313 -0
  615. package/dist/shared/blockTypes.d.ts +2 -0
  616. package/dist/shared/blockTypes.js +26 -0
  617. package/dist/shared/carouselData.d.ts +34 -0
  618. package/dist/shared/carouselData.js +111 -0
  619. package/dist/shared/cloneEditorData.d.ts +11 -0
  620. package/dist/shared/cloneEditorData.js +20 -0
  621. package/dist/shared/documentTypes.d.ts +35 -0
  622. package/dist/shared/embedPlayer.d.ts +42 -0
  623. package/dist/shared/embedPlayer.js +141 -0
  624. package/dist/shared/fileUtils.d.ts +33 -0
  625. package/dist/shared/fileUtils.js +109 -0
  626. package/dist/shared/highlightRuntime.d.ts +38 -0
  627. package/dist/shared/highlightRuntime.js +75 -0
  628. package/dist/shared/inlineMarshal.d.ts +82 -0
  629. package/dist/shared/inlineMarshal.js +215 -0
  630. package/dist/shared/jsonData.d.ts +11 -0
  631. package/dist/shared/jsonData.js +53 -0
  632. package/dist/shared/localeTypes.d.ts +11 -0
  633. package/dist/shared/mapTextFields.d.ts +89 -0
  634. package/dist/shared/mapTextFields.js +119 -0
  635. package/dist/shared/pollData.d.ts +90 -0
  636. package/dist/shared/pollData.js +231 -0
  637. package/dist/shared/resolvePath.d.ts +8 -0
  638. package/dist/shared/resolvePath.js +10 -0
  639. package/dist/shared/runtime/dompurify.js +2424 -0
  640. package/dist/shared/runtime/highlightBundle.js +10 -0
  641. package/dist/shared/runtime/jszip.js +13 -0
  642. package/dist/shared/sanitize/allowlist.d.ts +21 -0
  643. package/dist/shared/sanitize/allowlist.js +40 -0
  644. package/dist/shared/sanitize/escapeHtml.d.ts +6 -0
  645. package/dist/shared/sanitize/escapeHtml.js +11 -0
  646. package/dist/shared/sanitize/index.d.ts +8 -0
  647. package/dist/shared/sanitize/index.js +8 -0
  648. package/dist/shared/sanitize/parseInline.d.ts +11 -0
  649. package/dist/shared/sanitize/parseInline.js +25 -0
  650. package/dist/shared/sanitize/sanitizeHtml.d.ts +8 -0
  651. package/dist/shared/sanitize/sanitizeHtml.js +19 -0
  652. package/dist/shared/sanitize/sanitizeRawHtml.d.ts +15 -0
  653. package/dist/shared/sanitize/sanitizeRawHtml.js +162 -0
  654. package/dist/shared/sanitize/sanitizeStyle.d.ts +9 -0
  655. package/dist/shared/sanitize/sanitizeStyle.js +39 -0
  656. package/dist/shared/sanitize/sanitizeUrl.d.ts +28 -0
  657. package/dist/shared/sanitize/sanitizeUrl.js +86 -0
  658. package/dist/shared/sanitize/walker.d.ts +15 -0
  659. package/dist/shared/sanitize/walker.js +98 -0
  660. package/dist/shared/styleRegistry.d.ts +4 -0
  661. package/dist/shared/styleRegistry.js +38 -0
  662. package/dist/shared/textFormat.d.ts +32 -0
  663. package/dist/shared/textFormat.js +48 -0
  664. package/dist/shared/zipRuntime.d.ts +19 -0
  665. package/dist/shared/zipRuntime.js +54 -0
  666. package/dist/styles/editor.css +3 -0
  667. package/dist/types.d.ts +1 -0
  668. package/package.json +149 -0
@@ -0,0 +1,859 @@
1
+ import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
2
+ import { uid } from '../../core/uid.js'
3
+ import { resolvePath } from '../../shared/resolvePath.js'
4
+ import {
5
+ normalizeCarouselAspectRatio,
6
+ normalizeCarouselData,
7
+ validateCarouselData,
8
+ } from '../../shared/carouselData.js'
9
+ import {
10
+ sanitizeRawHtml,
11
+ sanitizeUrl,
12
+ setSafeUrlAttribute,
13
+ setSanitizedRawHtml,
14
+ } from '../../shared/sanitize/index.js'
15
+ import { makeActionBtn, makeSep } from '../shared/actionBar.js'
16
+ import { renderDropzone } from '../shared/dropzone.js'
17
+ import { triggerFileInput } from '../shared/fileInput.js'
18
+ import { CarouselState } from './state.js'
19
+ import { READ_ONLY_INTERACTIVE_ATTRIBUTE } from '../../core/constants.js'
20
+ import {
21
+ ICON, ICON_BACK, ICON_CHEVRON, ICON_CODE, ICON_NEXT, ICON_PREVIOUS,
22
+ ICON_REPLACE, ICON_SELECT, ICON_SETTINGS, ICON_TRASH, ICON_UPLOAD, ICON_URL,
23
+ } from './icons.js'
24
+
25
+ const editorStyles = resolvePath('./carousel.css', import.meta.url)
26
+
27
+ /**
28
+ * Read a local image while respecting the owning block's lifecycle.
29
+ * @param {File} file File to encode.
30
+ * @param {AbortSignal} signal Lifecycle signal owned by the rendered block.
31
+ * @returns {Promise<string>} A data URL for the file.
32
+ */
33
+ function readFileDataUrl(file, signal) {
34
+ if (signal.aborted) {
35
+ return Promise.reject(signal.reason || new DOMException('File read aborted', 'AbortError'))
36
+ }
37
+ return new Promise((resolve, reject) => {
38
+ const reader = new FileReader()
39
+ let settled = false
40
+ /**
41
+ * Run a completion callback exactly once and detach the abort listener.
42
+ * @param {() => void} callback Completion callback to invoke.
43
+ * @returns {void}
44
+ */
45
+ const finish = (callback) => {
46
+ if (settled) return
47
+ settled = true
48
+ signal.removeEventListener('abort', onSignalAbort)
49
+ callback()
50
+ }
51
+ const rejectAbort = () => reject(signal.reason || new DOMException('File read aborted', 'AbortError'))
52
+ const onSignalAbort = () => {
53
+ if (reader.readyState === FileReader.LOADING) reader.abort()
54
+ finish(rejectAbort)
55
+ }
56
+ signal.addEventListener('abort', onSignalAbort, { once: true })
57
+ reader.onload = () => finish(() => resolve(typeof reader.result === 'string' ? reader.result : ''))
58
+ reader.onerror = () => finish(() => reject(reader.error || new Error('Failed to read file')))
59
+ reader.onabort = () => finish(rejectAbort)
60
+ try {
61
+ reader.readAsDataURL(file)
62
+ } catch (error) {
63
+ finish(() => reject(error))
64
+ }
65
+ })
66
+ }
67
+
68
+ /**
69
+ * Determine the slide type represented by a local file. Some file pickers omit
70
+ * the MIME type, so common image and video extensions are used as a narrow
71
+ * fallback only when no MIME type is available.
72
+ * @param {File} file File selected by the user or supplied by a drop operation.
73
+ * @returns {'image' | 'video' | null} Supported slide type, or `null`.
74
+ */
75
+ function getMediaFileType(file) {
76
+ const type = file.type.toLowerCase()
77
+ if (type.startsWith('image/')) return 'image'
78
+ if (type.startsWith('video/')) return 'video'
79
+ if (type) return null
80
+ if (/\.(?:avif|bmp|gif|jpe?g|png|svg|webp)$/i.test(file.name)) return 'image'
81
+ if (/\.(?:mp4|m4v|mov|ogv|webm)$/i.test(file.name)) return 'video'
82
+ return null
83
+ }
84
+
85
+ /**
86
+ * Check whether a local file can be represented by a carousel slide.
87
+ * @param {File} file File selected by the user or supplied by a drop operation.
88
+ * @returns {boolean} Whether the file is a supported image or video.
89
+ */
90
+ function isSupportedMediaFile(file) {
91
+ return getMediaFileType(file) !== null
92
+ }
93
+
94
+ /**
95
+ * Infer the media type represented by a sanitized URL. URLs without a known
96
+ * video extension remain images because browsers can still decode extensionless
97
+ * image endpoints, while treating them as videos would remove image semantics.
98
+ * @param {string} url Sanitized media URL.
99
+ * @returns {'image' | 'video'} Slide type inferred from the URL path.
100
+ */
101
+ function getMediaUrlType(url) {
102
+ return /\.(?:m4v|mov|mp4|ogg|ogv|webm)(?:[?#]|$)/i.test(url) ? 'video' : 'image'
103
+ }
104
+
105
+ /**
106
+ * @typedef {import('../../shared/carouselData').CarouselData} CarouselData
107
+ * @typedef {import('../../shared/carouselData').CarouselSlide} CarouselSlide
108
+ * @typedef {Object} CarouselAction
109
+ * @property {string} label User-visible label for the application-owned source.
110
+ * @property {string} [icon] Trusted application-provided SVG or HTML markup.
111
+ * @property {(context: { signal: AbortSignal }) => Promise<CarouselSlide[] | null>} handler Loads slides and observes the supplied lifecycle signal.
112
+ * @typedef {Object} CarouselConfig
113
+ * @property {(file: File, context: { signal: AbortSignal }) => Promise<{ url: string, poster?: string }>} [uploadFile] Uploads an image or video selected in the browser. Without this callback images become data URLs, while videos use temporary object URLs retained until editor disposal.
114
+ * @property {CarouselAction[]} [actions] Additional application-owned slide sources such as a media library. Returned slides are normalized and unusable entries are ignored.
115
+ * @property {boolean} [injectStyles=true] Whether the editor should load the built-in carousel stylesheet.
116
+ * @property {string} [css] Additional stylesheet URL, or the replacement URL when `injectStyles` is `false`.
117
+ */
118
+
119
+ /**
120
+ * Editable mixed-media carousel with extensible file sources and display controls.
121
+ * @extends {BlockPluginAbstract<CarouselConfig>}
122
+ */
123
+ export class CarouselBlock extends BlockPluginAbstract {
124
+ static isTextBlock = false
125
+ static styles = [editorStyles]
126
+ type = 'carousel'
127
+ icon = ICON
128
+ inlineTools = false
129
+ pasteConfig = { files: ['image/*', 'video/*'] }
130
+
131
+ /** @type {WeakMap<HTMLElement, CarouselState>} */
132
+ #states = new WeakMap()
133
+ /** Temporary local URLs retained for undo/redo until editor disposal. @type {Set<string>} */
134
+ #objectUrls = new Set()
135
+ /**
136
+ * Return the localized toolbox label for this block.
137
+ * @returns {string}
138
+ */
139
+ get title() { return this._t('title', 'Carousel') }
140
+
141
+ /** Create a stable identifier for a new slide. @returns {string} */
142
+ #createId() { return `slide-${uid()}` }
143
+ /**
144
+ * Create the editable DOM owned by this block instance.
145
+ * @param {Record<string, unknown>} data Serialized carousel data.
146
+ * @param {import('../../core/types').BlockMutationContext} context Editor mutation and lifecycle context.
147
+ * @returns {HTMLElement} Root element owned by this block render.
148
+ */
149
+ render(data, context) {
150
+ const wrapper = document.createElement('div')
151
+ wrapper.className = 'oe-carousel-block'
152
+ wrapper.contentEditable = 'false'
153
+ wrapper.tabIndex = -1
154
+ const state = new CarouselState(data, () => this.#createId(), context)
155
+ this.#states.set(wrapper, state)
156
+ this.#build(wrapper, state)
157
+
158
+ const pending = /** @type {File | undefined} */ (/** @type {any} */ (data)?._pendingFile)
159
+ if (pending && !context.readOnly) {
160
+ state.pendingUpload = this.#addFiles(wrapper, [pending]).finally(() => {
161
+ if (this.#states.get(wrapper) === state) state.pendingUpload = null
162
+ })
163
+ }
164
+ return wrapper
165
+ }
166
+
167
+ /**
168
+ * Serialize the current block DOM into document data.
169
+ * @param {HTMLElement} element Root element returned by `render()`.
170
+ * @returns {CarouselData} Normalized serializable carousel data.
171
+ */
172
+ save(element) {
173
+ const state = this.#states.get(element)
174
+ return state ? structuredClone(state.data) : normalizeCarouselData({}, () => this.#createId())
175
+ }
176
+
177
+ /**
178
+ * Check whether serialized data satisfies this block's schema.
179
+ * @param {Record<string, unknown>} data Candidate serialized block data.
180
+ * @returns {boolean} Whether the candidate satisfies the carousel schema.
181
+ */
182
+ validate(data) { return validateCarouselData(data) }
183
+
184
+ /**
185
+ * Check whether the block has no meaningful user content.
186
+ * @param {HTMLElement} element Root element returned by `render()`.
187
+ * @returns {boolean} Whether the carousel contains no slides.
188
+ */
189
+ isEmpty(element) { return (this.#states.get(element)?.data.slides.length || 0) === 0 }
190
+
191
+ /**
192
+ * Extract neutral text that can initialize another block type.
193
+ * @param {HTMLElement} element Root element returned by `render()`.
194
+ * @returns {{ text: string }} Text assembled from slide captions and alternatives.
195
+ */
196
+ exportData(element) {
197
+ const state = this.#states.get(element)
198
+ return { text: state?.data.slides.map(slide => slide.caption || slide.alt || '').filter(Boolean).join(', ') || '' }
199
+ }
200
+
201
+ /**
202
+ * Report whether paste handling completes asynchronously.
203
+ * @param {HTMLElement} element Root element returned by `render()`.
204
+ * @returns {Promise<void>} Promise settled after a pending pasted file is handled.
205
+ */
206
+ waitForPaste(element) { return this.#states.get(element)?.pendingUpload ?? Promise.resolve() }
207
+
208
+ /**
209
+ * Release listeners and resources owned by this block element.
210
+ * @param {HTMLElement} element Root element returned by `render()`.
211
+ * @returns {void}
212
+ */
213
+ destroy(element) {
214
+ const state = this.#states.get(element)
215
+ if (!state) return
216
+ state.dispose()
217
+ this.#states.delete(element)
218
+ }
219
+
220
+ /**
221
+ * Release temporary local video URLs after the owning editor has discarded
222
+ * its blocks and history snapshots.
223
+ * @returns {void}
224
+ */
225
+ dispose() {
226
+ for (const url of this.#objectUrls) URL.revokeObjectURL(url)
227
+ this.#objectUrls.clear()
228
+ }
229
+
230
+ /**
231
+ * Handle supported pasted content for this block.
232
+ * @param {import('../../types').PasteEvent} event Normalized editor paste event.
233
+ * @returns {{ _pendingFile: File } | null} Deferred file marker, or `null` for unsupported content.
234
+ */
235
+ onPaste(event) {
236
+ if (event.type === 'file' && event.file) return { _pendingFile: event.file }
237
+ return null
238
+ }
239
+
240
+ /**
241
+ * Rebuild the current carousel view from mutable block state.
242
+ * @param {HTMLElement} wrapper Root element owned by this block render.
243
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
244
+ * @returns {void}
245
+ */
246
+ #build(wrapper, state) {
247
+ const signal = state.resetView()
248
+ wrapper.replaceChildren()
249
+ wrapper.classList.toggle('oe-carousel-block--filled', state.data.slides.length > 0)
250
+ if (state.data.slides.length === 0) {
251
+ this.#renderEmpty(wrapper, state, signal)
252
+ return
253
+ }
254
+ wrapper.appendChild(this.#stage(wrapper, state, signal))
255
+ if (!state.context.readOnly) wrapper.appendChild(this.#actions(wrapper, state, signal))
256
+ }
257
+
258
+ /**
259
+ * Render the empty-state file and application-source chooser.
260
+ * @param {HTMLElement} wrapper Root element owned by this block render.
261
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
262
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
263
+ * @returns {void}
264
+ */
265
+ #renderEmpty(wrapper, state, signal) {
266
+ if (state.context.readOnly) {
267
+ const empty = document.createElement('div')
268
+ empty.className = 'oe-carousel-block__empty'
269
+ empty.textContent = this._t('emptyReadonly', 'No slides')
270
+ wrapper.appendChild(empty)
271
+ return
272
+ }
273
+ renderDropzone(wrapper, signal, {
274
+ select: 'oe-carousel-block__select',
275
+ selectIcon: 'oe-carousel-block__select-icon',
276
+ selectText: 'oe-carousel-block__select-text',
277
+ selectLink: 'oe-carousel-block__select-link',
278
+ selectActions: 'oe-carousel-block__select-actions',
279
+ selectAction: 'oe-carousel-block__select-action',
280
+ dropzoneActive: 'oe-carousel-block__select--dragover',
281
+ filled: 'oe-carousel-block--filled',
282
+ }, {
283
+ iconHtml: ICON_SELECT,
284
+ uploadText: this._t('upload', 'Upload'),
285
+ afterText: this._t('dropzoneText', 'images or videos from your device or drag and drop them here'),
286
+ onUploadClick: () => this.#triggerFileInput(wrapper),
287
+ onDrop: dataTransfer => { if (dataTransfer.files.length) void this.#addFiles(wrapper, [...dataTransfer.files]) },
288
+ actions: [
289
+ ...this.#sourceActions(wrapper, state),
290
+ { icon: ICON_URL, label: this._t('addUrl', 'Add URL'), onSelect: () => this.#addUrl(wrapper, state) },
291
+ { icon: ICON_CODE, label: this._t('addHtml', 'Add HTML'), onSelect: () => this.#addHtml(wrapper, state) },
292
+ ],
293
+ })
294
+ }
295
+
296
+ /**
297
+ * Adapt application-provided slide sources to dropzone actions.
298
+ * @param {HTMLElement} wrapper Root element owned by this block render.
299
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
300
+ * @returns {Array<{icon?: string,label: string,onSelect: () => void}>} Selectable source actions.
301
+ */
302
+ #sourceActions(wrapper, state) {
303
+ return (this._config.actions || []).map(action => ({
304
+ icon: action.icon,
305
+ label: action.label,
306
+ onSelect: () => { void this.#runAction(wrapper, state, action) },
307
+ }))
308
+ }
309
+
310
+ /**
311
+ * Build the active media stage and optional navigation controls.
312
+ * @param {HTMLElement} wrapper Root element owned by this block render.
313
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
314
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
315
+ * @returns {HTMLElement} Carousel stage for the active slide.
316
+ */
317
+ #stage(wrapper, state, signal) {
318
+ const slide = state.data.slides[state.activeIndex]
319
+ const stage = document.createElement('div')
320
+ stage.className = 'oe-carousel-block__stage'
321
+ if (state.data.options.aspectRatio) stage.style.aspectRatio = state.data.options.aspectRatio
322
+
323
+ const media = document.createElement('div')
324
+ media.className = 'oe-carousel-block__media'
325
+ if (slide.type === 'image') {
326
+ const image = document.createElement('img')
327
+ setSafeUrlAttribute(image, 'src', slide.src || '', 'media')
328
+ image.alt = slide.alt || ''
329
+ media.appendChild(image)
330
+ } else if (slide.type === 'video') {
331
+ const video = document.createElement('video')
332
+ video.controls = true
333
+ video.preload = 'metadata'
334
+ setSafeUrlAttribute(video, 'src', slide.src || '', 'media')
335
+ setSafeUrlAttribute(video, 'poster', slide.poster || '', 'media')
336
+ video.setAttribute('aria-label', slide.alt || this._t('video', 'Video slide'))
337
+ media.appendChild(video)
338
+ } else {
339
+ const html = document.createElement('div')
340
+ html.className = 'oe-carousel-block__html'
341
+ setSanitizedRawHtml(html, slide.html || '')
342
+ media.appendChild(html)
343
+ }
344
+ stage.appendChild(media)
345
+
346
+ if (state.data.slides.length > 1 && state.data.options.navigation) {
347
+ const previous = this.#navButton(this._t('previous', 'Previous slide'), ICON_PREVIOUS, 'previous', () => this.#activate(wrapper, state, state.activeIndex - 1), signal)
348
+ const next = this.#navButton(this._t('next', 'Next slide'), ICON_NEXT, 'next', () => this.#activate(wrapper, state, state.activeIndex + 1), signal)
349
+ if (!state.data.options.loop) {
350
+ previous.disabled = state.activeIndex === 0
351
+ next.disabled = state.activeIndex === state.data.slides.length - 1
352
+ }
353
+ stage.append(previous, next)
354
+ }
355
+
356
+ const counter = document.createElement('span')
357
+ counter.className = 'oe-carousel-block__counter'
358
+ counter.setAttribute('aria-live', 'polite')
359
+ counter.textContent = `${state.activeIndex + 1} / ${state.data.slides.length}`
360
+ stage.appendChild(counter)
361
+
362
+ const caption = document.createElement('div')
363
+ caption.className = 'oe-carousel-block__caption'
364
+ caption.contentEditable = state.context.readOnly ? 'false' : 'true'
365
+ caption.dataset.placeholder = this._t('caption', 'Caption')
366
+ caption.textContent = slide.caption || ''
367
+ if (!state.context.readOnly) {
368
+ caption.addEventListener('input', () => { slide.caption = caption.textContent?.trim() || '' }, { signal })
369
+ caption.addEventListener('keydown', event => {
370
+ if (event.key === 'Backspace' && !caption.textContent?.trim()) {
371
+ event.preventDefault()
372
+ event.stopPropagation()
373
+ }
374
+ }, { signal })
375
+ }
376
+ stage.appendChild(caption)
377
+
378
+ if (state.data.slides.length > 1 && state.data.options.pagination) {
379
+ const dots = document.createElement('div')
380
+ dots.className = 'oe-carousel-block__dots'
381
+ state.data.slides.forEach((_, index) => {
382
+ const dot = document.createElement('button')
383
+ dot.type = 'button'
384
+ dot.setAttribute(READ_ONLY_INTERACTIVE_ATTRIBUTE, '')
385
+ dot.className = 'oe-carousel-block__dot'
386
+ dot.classList.toggle('oe-carousel-block__dot--active', index === state.activeIndex)
387
+ dot.setAttribute('aria-label', this._t('goToSlide', 'Go to slide {index}').replace('{index}', String(index + 1)))
388
+ dot.setAttribute('aria-current', index === state.activeIndex ? 'true' : 'false')
389
+ dot.addEventListener('click', () => this.#activate(wrapper, state, index), { signal })
390
+ dots.appendChild(dot)
391
+ })
392
+ stage.appendChild(dots)
393
+ }
394
+
395
+ if (state.data.options.thumbnails && state.data.slides.length > 1) {
396
+ const thumbnails = document.createElement('div')
397
+ thumbnails.className = 'oe-carousel-block__thumbnails'
398
+ state.data.slides.forEach((item, index) => thumbnails.appendChild(this.#thumbnail(wrapper, state, item, index, signal)))
399
+ stage.appendChild(thumbnails)
400
+ }
401
+ return stage
402
+ }
403
+
404
+ /**
405
+ * Create one previous or next navigation button.
406
+ * @param {string} label Accessible button label.
407
+ * @param {string} icon Trusted internal SVG markup.
408
+ * @param {string} direction Direction modifier used by the stylesheet.
409
+ * @param {() => void} activate Callback that activates the adjacent slide.
410
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
411
+ * @returns {HTMLButtonElement} Configured navigation button.
412
+ */
413
+ #navButton(label, icon, direction, activate, signal) {
414
+ const button = document.createElement('button')
415
+ button.type = 'button'
416
+ button.setAttribute(READ_ONLY_INTERACTIVE_ATTRIBUTE, '')
417
+ button.className = `oe-carousel-block__nav oe-carousel-block__nav--${direction}`
418
+ button.innerHTML = icon
419
+ button.setAttribute('aria-label', label)
420
+ button.addEventListener('click', activate, { signal })
421
+ return button
422
+ }
423
+
424
+ /**
425
+ * Create one thumbnail navigation control.
426
+ * @param {HTMLElement} wrapper Root element owned by this block render.
427
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
428
+ * @param {CarouselSlide} slide Slide represented by the thumbnail.
429
+ * @param {number} index Zero-based slide index.
430
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
431
+ * @returns {HTMLButtonElement} Configured thumbnail button.
432
+ */
433
+ #thumbnail(wrapper, state, slide, index, signal) {
434
+ const button = document.createElement('button')
435
+ button.type = 'button'
436
+ button.setAttribute(READ_ONLY_INTERACTIVE_ATTRIBUTE, '')
437
+ button.className = 'oe-carousel-block__thumbnail'
438
+ button.classList.toggle('oe-carousel-block__thumbnail--active', index === state.activeIndex)
439
+ button.setAttribute('aria-label', this._t('goToSlide', 'Go to slide {index}').replace('{index}', String(index + 1)))
440
+ button.setAttribute('aria-current', index === state.activeIndex ? 'true' : 'false')
441
+ if (slide.type === 'image' && slide.src) {
442
+ const image = document.createElement('img')
443
+ setSafeUrlAttribute(image, 'src', slide.src, 'media')
444
+ image.alt = ''
445
+ button.appendChild(image)
446
+ } else {
447
+ button.textContent = String(index + 1)
448
+ }
449
+ button.addEventListener('click', () => this.#activate(wrapper, state, index), { signal })
450
+ return button
451
+ }
452
+
453
+ /**
454
+ * Activate a slide, applying loop or boundary behavior from current options.
455
+ * @param {HTMLElement} wrapper Root element owned by this block render.
456
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
457
+ * @param {number} index Requested zero-based slide index.
458
+ * @returns {void}
459
+ */
460
+ #activate(wrapper, state, index) {
461
+ const count = state.data.slides.length
462
+ if (!count) return
463
+ state.activeIndex = state.data.options.loop ? (index + count) % count : Math.max(0, Math.min(index, count - 1))
464
+ this.#build(wrapper, state)
465
+ }
466
+
467
+ /**
468
+ * Build the editing action bar shown below a populated carousel.
469
+ * @param {HTMLElement} wrapper Root element owned by this block render.
470
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
471
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
472
+ * @returns {HTMLElement} Carousel action bar.
473
+ */
474
+ #actions(wrapper, state, signal) {
475
+ const actions = document.createElement('div')
476
+ actions.className = 'oe-carousel-block__actions'
477
+ const main = document.createElement('div')
478
+ main.className = 'oe-carousel-block__actions-view'
479
+
480
+ const settings = document.createElement('div')
481
+ settings.className = 'oe-carousel-block__dropdown'
482
+ const settingsButton = makeActionBtn('oe-carousel-block__action-btn', `${ICON_SETTINGS} ${this._t('settings', 'Settings')}`, () => {
483
+ const open = !settings.classList.contains('oe-carousel-block__dropdown--open')
484
+ settings.classList.toggle('oe-carousel-block__dropdown--open', open)
485
+ settingsButton.setAttribute('aria-expanded', String(open))
486
+ }, signal)
487
+ settingsButton.setAttribute('aria-haspopup', 'true')
488
+ settingsButton.setAttribute('aria-expanded', 'false')
489
+ const panel = this.#settingsPanel(wrapper, state, signal)
490
+ settings.append(settingsButton, panel)
491
+ document.addEventListener('click', event => {
492
+ const target = event.target
493
+ if (!(target instanceof Node) || !settings.contains(target)) {
494
+ settings.classList.remove('oe-carousel-block__dropdown--open')
495
+ settingsButton.setAttribute('aria-expanded', 'false')
496
+ }
497
+ }, { signal })
498
+ settings.addEventListener('keydown', event => {
499
+ if (event.key !== 'Escape') return
500
+ event.stopPropagation()
501
+ settings.classList.remove('oe-carousel-block__dropdown--open')
502
+ settingsButton.setAttribute('aria-expanded', 'false')
503
+ settingsButton.focus()
504
+ }, { signal })
505
+ main.append(settings, makeSep('oe-carousel-block__actions-sep'))
506
+
507
+ main.appendChild(makeActionBtn(
508
+ 'oe-carousel-block__action-btn',
509
+ `${ICON_REPLACE} ${this._t('add', 'Add')} ${ICON_CHEVRON}`,
510
+ () => this.#showAddView(actions, main, wrapper, state, signal),
511
+ signal,
512
+ ))
513
+ main.appendChild(makeSep('oe-carousel-block__actions-sep'))
514
+ const deleteAll = makeActionBtn('oe-carousel-block__action-btn oe-carousel-block__action-btn--danger', ICON_TRASH, () => {
515
+ state.context.mutate(() => {
516
+ state.data.slides = []
517
+ state.activeIndex = 0
518
+ this.#build(wrapper, state)
519
+ })
520
+ }, signal)
521
+ deleteAll.setAttribute('aria-label', this._t('deleteAll', 'Remove all slides'))
522
+ main.appendChild(deleteAll)
523
+ actions.appendChild(main)
524
+ return actions
525
+ }
526
+
527
+ /**
528
+ * Replace the primary action bar with controls for adding more slides.
529
+ * @param {HTMLElement} actions Action bar container.
530
+ * @param {HTMLElement} main Primary action bar view to hide temporarily.
531
+ * @param {HTMLElement} wrapper Root element owned by this block render.
532
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
533
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
534
+ * @returns {void}
535
+ */
536
+ #showAddView(actions, main, wrapper, state, signal) {
537
+ main.hidden = true
538
+ const view = document.createElement('div')
539
+ view.className = 'oe-carousel-block__actions-view'
540
+ const restore = () => { view.remove(); main.hidden = false }
541
+ view.appendChild(makeActionBtn('oe-carousel-block__action-btn', `${ICON_BACK} ${this._t('back', 'Back')}`, restore, signal))
542
+ view.appendChild(makeSep('oe-carousel-block__actions-sep'))
543
+ view.appendChild(makeActionBtn('oe-carousel-block__action-btn', `${ICON_UPLOAD} ${this._t('upload', 'Upload')}`, () => {
544
+ this.#triggerFileInput(wrapper); restore()
545
+ }, signal))
546
+ for (const action of this._config.actions || []) {
547
+ view.appendChild(makeActionBtn('oe-carousel-block__action-btn', `${action.icon || ''} ${action.label}`, () => {
548
+ void this.#runAction(wrapper, state, action); restore()
549
+ }, signal))
550
+ }
551
+ view.appendChild(makeActionBtn('oe-carousel-block__action-btn', `${ICON_URL} URL`, () => { this.#addUrl(wrapper, state); restore() }, signal))
552
+ view.appendChild(makeActionBtn('oe-carousel-block__action-btn', `${ICON_CODE} HTML`, () => { this.#addHtml(wrapper, state); restore() }, signal))
553
+ actions.appendChild(view)
554
+ }
555
+
556
+ /**
557
+ * Build controls for the active slide and global carousel behavior.
558
+ * @param {HTMLElement} wrapper Root element owned by this block render.
559
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
560
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
561
+ * @returns {HTMLElement} Settings panel for the current carousel state.
562
+ */
563
+ #settingsPanel(wrapper, state, signal) {
564
+ const slide = state.data.slides[state.activeIndex]
565
+ const panel = document.createElement('div')
566
+ panel.className = 'oe-carousel-block__dropdown-panel oe-carousel-block__settings'
567
+ panel.setAttribute('role', 'group')
568
+ panel.addEventListener('click', event => event.stopPropagation(), { signal })
569
+
570
+ panel.appendChild(this.#sectionTitle(this._t('currentSlide', 'Current slide')))
571
+ if (slide.type === 'html') {
572
+ panel.appendChild(this.#field(this._t('html', 'HTML'), slide.html || '', true, value => {
573
+ const html = sanitizeRawHtml(value)
574
+ if (html.trim()) slide.html = html
575
+ }, wrapper, state, signal, 'text', true))
576
+ } else {
577
+ panel.appendChild(this.#field(this._t('source', 'Source URL'), slide.src || '', false, value => {
578
+ const src = sanitizeUrl(value, { policy: 'media', fallback: '' })
579
+ if (src) slide.src = src
580
+ }, wrapper, state, signal, 'text', true))
581
+ panel.appendChild(this.#field(this._t('alt', 'Alternative text'), slide.alt || '', false, value => { slide.alt = value }, wrapper, state, signal))
582
+ if (slide.type === 'video') {
583
+ panel.appendChild(this.#field(this._t('poster', 'Poster URL'), slide.poster || '', false, value => {
584
+ slide.poster = sanitizeUrl(value, { policy: 'media', fallback: '' })
585
+ }, wrapper, state, signal))
586
+ }
587
+ }
588
+ panel.appendChild(this.#field(this._t('caption', 'Caption'), slide.caption || '', false, value => { slide.caption = value }, wrapper, state, signal))
589
+
590
+ const order = document.createElement('div')
591
+ order.className = 'oe-carousel-block__slide-actions'
592
+ const backward = makeActionBtn('oe-carousel-block__settings-button', `${ICON_PREVIOUS} ${this._t('movePreviousShort', 'Earlier')}`, () => this.#move(wrapper, state, state.activeIndex, state.activeIndex - 1), signal)
593
+ backward.setAttribute('aria-label', this._t('movePrevious', 'Move slide backward'))
594
+ backward.disabled = state.activeIndex === 0
595
+ const forward = makeActionBtn('oe-carousel-block__settings-button', `${this._t('moveNextShort', 'Later')} ${ICON_NEXT}`, () => this.#move(wrapper, state, state.activeIndex, state.activeIndex + 1), signal)
596
+ forward.setAttribute('aria-label', this._t('moveNext', 'Move slide forward'))
597
+ forward.disabled = state.activeIndex === state.data.slides.length - 1
598
+ const remove = makeActionBtn('oe-carousel-block__settings-button oe-carousel-block__settings-button--danger', `${ICON_TRASH} ${this._t('removeSlide', 'Remove slide')}`, () => this.#removeSlide(wrapper, state, state.activeIndex), signal)
599
+ order.append(backward, forward, remove)
600
+ panel.appendChild(order)
601
+
602
+ panel.appendChild(this.#sectionTitle(this._t('behavior', 'Behavior')))
603
+ const switches = document.createElement('div')
604
+ switches.className = 'oe-carousel-block__switches'
605
+ for (const key of ['loop', 'autoplay', 'navigation', 'pagination', 'thumbnails']) {
606
+ switches.appendChild(this.#checkbox(this._t(key, key), !!state.data.options[key], checked => {
607
+ state.data.options[key] = checked
608
+ }, wrapper, state, signal))
609
+ }
610
+ panel.appendChild(switches)
611
+ panel.appendChild(this.#field(this._t('autoplayDelay', 'Autoplay delay, ms'), String(state.data.options.autoplayDelay), false, value => {
612
+ const delay = Number(value)
613
+ if (Number.isFinite(delay) && delay > 0) state.data.options.autoplayDelay = Math.floor(delay)
614
+ }, wrapper, state, signal, 'number'))
615
+ panel.appendChild(this.#field(this._t('aspectRatio', 'Aspect ratio'), state.data.options.aspectRatio || '', false, value => {
616
+ const normalized = normalizeCarouselAspectRatio(value)
617
+ if (normalized) state.data.options.aspectRatio = normalized
618
+ else delete state.data.options.aspectRatio
619
+ }, wrapper, state, signal))
620
+ return panel
621
+ }
622
+
623
+ /**
624
+ * Create a heading inside the settings panel.
625
+ * @param {string} text Localized heading text.
626
+ * @returns {HTMLElement} Settings section heading.
627
+ */
628
+ #sectionTitle(text) {
629
+ const title = document.createElement('div')
630
+ title.className = 'oe-carousel-block__settings-title'
631
+ title.textContent = text
632
+ return title
633
+ }
634
+
635
+ /**
636
+ * Create a settings field that commits one mutation when its value changes.
637
+ * @param {string} label Localized field label.
638
+ * @param {string} value Initial field value.
639
+ * @param {boolean} multiline Whether to create a textarea instead of an input.
640
+ * @param {(value: string) => void} update State update performed inside the mutation.
641
+ * @param {HTMLElement} wrapper Root element owned by this block render.
642
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
643
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
644
+ * @param {string} [type] HTML input type used for a single-line field.
645
+ * @param {boolean} [fullWidth] Whether the field spans the settings panel width.
646
+ * @returns {HTMLElement} Label containing the configured input control.
647
+ */
648
+ #field(label, value, multiline, update, wrapper, state, signal, type = 'text', fullWidth = false) {
649
+ const row = document.createElement('label')
650
+ row.className = 'oe-carousel-block__field'
651
+ row.classList.toggle('oe-carousel-block__field--full', fullWidth)
652
+ const title = document.createElement('span')
653
+ title.textContent = label
654
+ const input = multiline
655
+ ? document.createElement('textarea')
656
+ : document.createElement('input')
657
+ if (input instanceof HTMLInputElement) input.type = type
658
+ input.value = value
659
+ input.addEventListener('change', () => state.context.mutate(() => {
660
+ update(input.value)
661
+ this.#build(wrapper, state)
662
+ }), { signal })
663
+ row.append(title, input)
664
+ return row
665
+ }
666
+
667
+ /**
668
+ * Create a boolean settings control that commits one mutation per change.
669
+ * @param {string} label Localized control label.
670
+ * @param {boolean} checked Initial checked state.
671
+ * @param {(checked: boolean) => void} update State update performed inside the mutation.
672
+ * @param {HTMLElement} wrapper Root element owned by this block render.
673
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
674
+ * @param {AbortSignal} signal Signal invalidated when the current view is rebuilt.
675
+ * @returns {HTMLLabelElement} Label containing the checkbox.
676
+ */
677
+ #checkbox(label, checked, update, wrapper, state, signal) {
678
+ const row = document.createElement('label')
679
+ row.className = 'oe-carousel-block__switch'
680
+ const input = document.createElement('input')
681
+ input.type = 'checkbox'
682
+ input.checked = checked
683
+ input.addEventListener('change', () => state.context.mutate(() => {
684
+ update(input.checked)
685
+ this.#build(wrapper, state)
686
+ }), { signal })
687
+ row.append(input, document.createTextNode(label))
688
+ return row
689
+ }
690
+
691
+ /**
692
+ * Move one slide and keep it active as a single history operation.
693
+ * @param {HTMLElement} wrapper Root element owned by this block render.
694
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
695
+ * @param {number} from Current zero-based slide index.
696
+ * @param {number} to Requested zero-based destination index.
697
+ * @returns {void}
698
+ */
699
+ #move(wrapper, state, from, to) {
700
+ if (from < 0 || from >= state.data.slides.length || to < 0 || to >= state.data.slides.length || from === to) return
701
+ state.context.mutate(() => {
702
+ const [slide] = state.data.slides.splice(from, 1)
703
+ state.data.slides.splice(to, 0, slide)
704
+ state.activeIndex = to
705
+ this.#build(wrapper, state)
706
+ })
707
+ }
708
+
709
+ /**
710
+ * Remove one slide as a single history operation.
711
+ * @param {HTMLElement} wrapper Root element owned by this block render.
712
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
713
+ * @param {number} index Zero-based index of the slide to remove.
714
+ * @returns {void}
715
+ */
716
+ #removeSlide(wrapper, state, index) {
717
+ if (index < 0 || index >= state.data.slides.length) return
718
+ state.context.mutate(() => {
719
+ state.data.slides.splice(index, 1)
720
+ state.activeIndex = Math.max(0, Math.min(index, state.data.slides.length - 1))
721
+ this.#build(wrapper, state)
722
+ })
723
+ }
724
+
725
+ /**
726
+ * Open the temporary local-media picker for this rendered block.
727
+ * @param {HTMLElement} wrapper Root element owned by this block render.
728
+ * @returns {void}
729
+ */
730
+ #triggerFileInput(wrapper) {
731
+ const state = this.#states.get(wrapper)
732
+ if (!state || state.context.readOnly) return
733
+ triggerFileInput({
734
+ accept: 'image/*,video/*',
735
+ multiple: true,
736
+ signal: state.lifecycleController.signal,
737
+ onFiles: files => { void this.#addFiles(wrapper, files) },
738
+ })
739
+ }
740
+
741
+ /**
742
+ * Prompt for an image or video URL and append a slide when it is safe.
743
+ * @param {HTMLElement} wrapper Root element owned by this block render.
744
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
745
+ * @returns {void}
746
+ */
747
+ #addUrl(wrapper, state) {
748
+ if (state.context.readOnly) return
749
+ const source = prompt(this._t('sourcePrompt', 'Media URL'))
750
+ if (!source) return
751
+ const src = sanitizeUrl(source, { policy: 'media', fallback: '' })
752
+ if (!src) return
753
+ const type = getMediaUrlType(src)
754
+ state.context.mutate(() => {
755
+ state.data.slides.push({ id: this.#createId(), type, src, alt: '', caption: '' })
756
+ state.activeIndex = state.data.slides.length - 1
757
+ this.#build(wrapper, state)
758
+ })
759
+ }
760
+
761
+ /**
762
+ * Prompt for sanitized HTML and append it as a slide.
763
+ * @param {HTMLElement} wrapper Root element owned by this block render.
764
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
765
+ * @returns {void}
766
+ */
767
+ #addHtml(wrapper, state) {
768
+ if (state.context.readOnly) return
769
+ const html = prompt(this._t('htmlPrompt', 'Slide HTML'))
770
+ if (!html) return
771
+ const safe = sanitizeRawHtml(html)
772
+ if (!safe.trim()) return
773
+ state.context.mutate(() => {
774
+ state.data.slides.push({ id: this.#createId(), type: 'html', html: safe, caption: '' })
775
+ state.activeIndex = state.data.slides.length - 1
776
+ this.#build(wrapper, state)
777
+ })
778
+ }
779
+
780
+ /**
781
+ * Convert supported local files to slides and commit them atomically.
782
+ * @param {HTMLElement} wrapper Root element owned by this block render.
783
+ * @param {File[]} files Files selected by the user, pasted, or dropped.
784
+ * @returns {Promise<void>} Promise settled after all supported files are processed.
785
+ */
786
+ async #addFiles(wrapper, files) {
787
+ const state = this.#states.get(wrapper)
788
+ if (!state || state.context.readOnly || state.lifecycleController.signal.aborted) return
789
+ const signal = state.lifecycleController.signal
790
+ const supportedFiles = files.filter(isSupportedMediaFile)
791
+ if (!supportedFiles.length) return
792
+ const slides = await Promise.all(supportedFiles.map(async file => {
793
+ try {
794
+ const type = getMediaFileType(file)
795
+ if (!type) return null
796
+ let src = ''
797
+ let poster = ''
798
+ if (this._config.uploadFile) {
799
+ const result = await this._config.uploadFile(file, { signal })
800
+ src = sanitizeUrl(result?.url || '', { policy: 'media', fallback: '' })
801
+ poster = sanitizeUrl(result?.poster || '', { policy: 'media', fallback: '' })
802
+ } else if (type === 'image') {
803
+ src = sanitizeUrl(await readFileDataUrl(file, signal), { policy: 'media', fallback: '' })
804
+ } else {
805
+ src = URL.createObjectURL(file)
806
+ this.#objectUrls.add(src)
807
+ }
808
+ if (!src) return null
809
+ return {
810
+ id: this.#createId(), type, src, alt: file.name, caption: '',
811
+ ...(type === 'video' && poster ? { poster } : {}),
812
+ }
813
+ } catch (error) {
814
+ if (!signal.aborted) console.warn(`[CarouselBlock] Failed to add "${file.name}":`, error)
815
+ return null
816
+ }
817
+ }))
818
+ if (signal.aborted || this.#states.get(wrapper) !== state) return
819
+ const valid = /** @type {CarouselSlide[]} */ (slides.filter(slide => slide !== null))
820
+ if (!valid.length) return
821
+ state.context.mutate(() => {
822
+ state.data.slides.push(...valid)
823
+ state.activeIndex = state.data.slides.length - valid.length
824
+ this.#build(wrapper, state)
825
+ })
826
+ }
827
+
828
+ /**
829
+ * Run an application-provided slide source and commit valid results atomically.
830
+ * @param {HTMLElement} wrapper Root element owned by this block render.
831
+ * @param {CarouselState} state Mutable state associated with `wrapper`.
832
+ * @param {CarouselAction} action Application source to execute.
833
+ * @returns {Promise<void>} Promise settled after the source completes or is cancelled.
834
+ */
835
+ async #runAction(wrapper, state, action) {
836
+ try {
837
+ const signal = state.lifecycleController.signal
838
+ const slides = await action.handler({ signal })
839
+ if (!slides || signal.aborted || this.#states.get(wrapper) !== state) return
840
+ const normalized = normalizeCarouselData({ slides, options: state.data.options }, () => this.#createId()).slides
841
+ .filter(slide => slide.type === 'html' ? !!slide.html?.trim() : !!slide.src)
842
+ if (!normalized.length) return
843
+ const usedIds = new Set(state.data.slides.map(slide => slide.id))
844
+ for (const slide of normalized) {
845
+ while (usedIds.has(slide.id)) slide.id = this.#createId()
846
+ usedIds.add(slide.id)
847
+ }
848
+ state.context.mutate(() => {
849
+ state.data.slides.push(...normalized)
850
+ state.activeIndex = state.data.slides.length - normalized.length
851
+ this.#build(wrapper, state)
852
+ })
853
+ } catch (error) {
854
+ if (!state.lifecycleController.signal.aborted) console.warn('[CarouselBlock] Action failed:', error)
855
+ }
856
+ }
857
+ }
858
+
859
+ export { CarouselBlock as Carousel }