typo3-typescript-sources 13.4.20

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 (415) hide show
  1. package/.github/workflows/sync-and-publish.yml +265 -0
  2. package/.last-synced-tag +1 -0
  3. package/LICENSE.txt +339 -0
  4. package/README-TYPO3.md +166 -0
  5. package/README.md +28 -0
  6. package/adminpanel/admin-panel.ts +375 -0
  7. package/adminpanel/modules/cache.ts +27 -0
  8. package/adminpanel/modules/preview.ts +75 -0
  9. package/backend/action-button/abstract-action.ts +22 -0
  10. package/backend/action-button/deferred-action.ts +41 -0
  11. package/backend/action-button/immediate-action.ts +31 -0
  12. package/backend/action-dispatcher.ts +104 -0
  13. package/backend/ajax-data-handler/message-interface.ts +18 -0
  14. package/backend/ajax-data-handler/response-interface.ts +20 -0
  15. package/backend/ajax-data-handler.ts +214 -0
  16. package/backend/backend-exception.ts +22 -0
  17. package/backend/broadcast-message.ts +50 -0
  18. package/backend/broadcast-service.ts +52 -0
  19. package/backend/browse-database.ts +38 -0
  20. package/backend/clear-cache.ts +88 -0
  21. package/backend/clipboard-panel.ts +240 -0
  22. package/backend/close-current-window.ts +2 -0
  23. package/backend/code-editor/autocomplete/completion-result.ts +105 -0
  24. package/backend/code-editor/autocomplete/ts-code-completion.ts +142 -0
  25. package/backend/code-editor/autocomplete/ts-parser.ts +485 -0
  26. package/backend/code-editor/autocomplete/ts-ref.ts +164 -0
  27. package/backend/code-editor/element/code-mirror-element.ts +282 -0
  28. package/backend/code-editor/language/typoscript.ts +181 -0
  29. package/backend/code-editor/stream-parser/typoscript.ts +1600 -0
  30. package/backend/color-picker.ts +158 -0
  31. package/backend/color-scheme-switch.ts +179 -0
  32. package/backend/column-selector-button.ts +302 -0
  33. package/backend/context-help.ts +68 -0
  34. package/backend/context-menu-actions.ts +322 -0
  35. package/backend/context-menu.ts +751 -0
  36. package/backend/copy-to-clipboard.ts +79 -0
  37. package/backend/date-time-picker.ts +270 -0
  38. package/backend/document-save-actions.ts +159 -0
  39. package/backend/drag-tooltip.ts +285 -0
  40. package/backend/drag-uploader.ts +914 -0
  41. package/backend/element/alert-element.ts +116 -0
  42. package/backend/element/dispatch-modal-button.ts +50 -0
  43. package/backend/element/draggable-resizable-element.ts +249 -0
  44. package/backend/element/editable-page-title.ts +306 -0
  45. package/backend/element/icon-element.ts +84 -0
  46. package/backend/element/immediate-action-element.ts +89 -0
  47. package/backend/element/pagination.ts +59 -0
  48. package/backend/element/progress-bar-element.ts +172 -0
  49. package/backend/element/progress-tracker-element.ts +119 -0
  50. package/backend/element/pseudo-button.ts +58 -0
  51. package/backend/element/spinner-element.ts +51 -0
  52. package/backend/element/thumbnail-element.ts +80 -0
  53. package/backend/element-browser.ts +182 -0
  54. package/backend/enum/data-transfer-types.ts +21 -0
  55. package/backend/enum/icon-types.ts +31 -0
  56. package/backend/enum/key-types.ts +27 -0
  57. package/backend/enum/severity.ts +20 -0
  58. package/backend/enum/viewport/scaffold-identifier.ts +24 -0
  59. package/backend/event/client-request.ts +25 -0
  60. package/backend/event/consumable.ts +18 -0
  61. package/backend/event/consumer-scope.ts +54 -0
  62. package/backend/event/event-dispatcher.ts +26 -0
  63. package/backend/event/interaction-request-assignment.ts +24 -0
  64. package/backend/event/interaction-request-map.ts +75 -0
  65. package/backend/event/interaction-request.ts +48 -0
  66. package/backend/event/trigger-request.ts +56 -0
  67. package/backend/form/submit-interceptor.ts +66 -0
  68. package/backend/form-engine/container/files-control-container.ts +716 -0
  69. package/backend/form-engine/container/flex-form-container-container.ts +166 -0
  70. package/backend/form-engine/container/flex-form-section-container.ts +210 -0
  71. package/backend/form-engine/container/inline-control-container.ts +1230 -0
  72. package/backend/form-engine/container/site-language-container.ts +616 -0
  73. package/backend/form-engine/element/abstract-sortable-select-items.ts +186 -0
  74. package/backend/form-engine/element/category-element.ts +160 -0
  75. package/backend/form-engine/element/color-element.ts +81 -0
  76. package/backend/form-engine/element/datetime-element.ts +66 -0
  77. package/backend/form-engine/element/extra/char-counter.ts +112 -0
  78. package/backend/form-engine/element/extra/select-box-filter.ts +85 -0
  79. package/backend/form-engine/element/folder-element.ts +66 -0
  80. package/backend/form-engine/element/group-element.ts +47 -0
  81. package/backend/form-engine/element/json-element.ts +52 -0
  82. package/backend/form-engine/element/link-element.ts +121 -0
  83. package/backend/form-engine/element/mfa-info-element.ts +169 -0
  84. package/backend/form-engine/element/modifier/resizable.ts +27 -0
  85. package/backend/form-engine/element/modifier/tabbable.ts +29 -0
  86. package/backend/form-engine/element/online-media-form-element.ts +65 -0
  87. package/backend/form-engine/element/password-element.ts +71 -0
  88. package/backend/form-engine/element/select-multiple-side-by-side-element.ts +83 -0
  89. package/backend/form-engine/element/select-single-element.ts +82 -0
  90. package/backend/form-engine/element/select-tree-element.ts +148 -0
  91. package/backend/form-engine/element/select-tree-toolbar.ts +118 -0
  92. package/backend/form-engine/element/select-tree.ts +210 -0
  93. package/backend/form-engine/element/slug-element.ts +243 -0
  94. package/backend/form-engine/element/suggest/result-container.ts +118 -0
  95. package/backend/form-engine/element/suggest/result-item.ts +90 -0
  96. package/backend/form-engine/element/table-permission-element.ts +101 -0
  97. package/backend/form-engine/element/table-wizard-element.ts +427 -0
  98. package/backend/form-engine/element/text-element.ts +52 -0
  99. package/backend/form-engine/element/text-table-element.ts +31 -0
  100. package/backend/form-engine/field-control/add-record.ts +40 -0
  101. package/backend/form-engine/field-control/edit-popup.ts +64 -0
  102. package/backend/form-engine/field-control/insert-clipboard.ts +50 -0
  103. package/backend/form-engine/field-control/link-popup.ts +53 -0
  104. package/backend/form-engine/field-control/list-module.ts +40 -0
  105. package/backend/form-engine/field-control/password-generator.ts +102 -0
  106. package/backend/form-engine/field-control/reset-selection.ts +48 -0
  107. package/backend/form-engine/field-wizard/localization-state-selector.ts +67 -0
  108. package/backend/form-engine/field-wizard/value-picker.ts +123 -0
  109. package/backend/form-engine/field-wizard/value-slider.ts +86 -0
  110. package/backend/form-engine/inline-relation/ajax-dispatcher.ts +124 -0
  111. package/backend/form-engine/inline-relation/inline-response-interface.ts +41 -0
  112. package/backend/form-engine-link-browser-adapter.ts +129 -0
  113. package/backend/form-engine-review.ts +155 -0
  114. package/backend/form-engine-suggest.ts +133 -0
  115. package/backend/form-engine-validation.ts +686 -0
  116. package/backend/form-engine.ts +1391 -0
  117. package/backend/global-event-handler.ts +252 -0
  118. package/backend/grid-editor.ts +1077 -0
  119. package/backend/hashing/md5.ts +252 -0
  120. package/backend/helper.ts +24 -0
  121. package/backend/hotkeys/hotkey-storage.ts +63 -0
  122. package/backend/hotkeys.ts +216 -0
  123. package/backend/icons.ts +236 -0
  124. package/backend/image-manipulation.ts +885 -0
  125. package/backend/info-window.ts +46 -0
  126. package/backend/input/clearable.ts +128 -0
  127. package/backend/input/index.d.ts +8 -0
  128. package/backend/java-script-module-import-event-handler.ts +19 -0
  129. package/backend/key-bindings.ts +42 -0
  130. package/backend/layout-module/drag-drop.ts +359 -0
  131. package/backend/layout-module/paste.ts +165 -0
  132. package/backend/link-browser.ts +89 -0
  133. package/backend/live-search/element/backend-search.ts +34 -0
  134. package/backend/live-search/element/hint.ts +33 -0
  135. package/backend/live-search/element/provider/default-result-item.ts +52 -0
  136. package/backend/live-search/element/provider/page-provider-result-item.ts +52 -0
  137. package/backend/live-search/element/result/item/action/action-container.ts +135 -0
  138. package/backend/live-search/element/result/item/action/action.ts +56 -0
  139. package/backend/live-search/element/result/item/item-container.ts +224 -0
  140. package/backend/live-search/element/result/item/item.ts +74 -0
  141. package/backend/live-search/element/result/result-container.ts +110 -0
  142. package/backend/live-search/element/result/result-detail-container.ts +50 -0
  143. package/backend/live-search/element/result/result-pagination.ts +122 -0
  144. package/backend/live-search/element/search-option-item.ts +70 -0
  145. package/backend/live-search/live-search-configurator.ts +54 -0
  146. package/backend/live-search/live-search-shortcut.ts +29 -0
  147. package/backend/live-search/result-types/backend-module-result-type.ts +8 -0
  148. package/backend/live-search/result-types/default-result-type.ts +21 -0
  149. package/backend/live-search/result-types/page-result-type.ts +23 -0
  150. package/backend/localization.ts +390 -0
  151. package/backend/login-refresh.ts +525 -0
  152. package/backend/login.ts +179 -0
  153. package/backend/mail-link-handler.ts +45 -0
  154. package/backend/modal-interface.ts +19 -0
  155. package/backend/modal.ts +685 -0
  156. package/backend/module/iframe.ts +125 -0
  157. package/backend/module/router.ts +275 -0
  158. package/backend/module-menu.ts +550 -0
  159. package/backend/module.ts +133 -0
  160. package/backend/multi-record-selection-action.ts +44 -0
  161. package/backend/multi-record-selection-delete-action.ts +108 -0
  162. package/backend/multi-record-selection-edit-action.ts +56 -0
  163. package/backend/multi-record-selection.ts +468 -0
  164. package/backend/multi-step-wizard.ts +568 -0
  165. package/backend/new-content-element-wizard-button.ts +51 -0
  166. package/backend/new-multiple-pages.ts +108 -0
  167. package/backend/new-record-wizard.ts +680 -0
  168. package/backend/notification.ts +366 -0
  169. package/backend/offset.ts +40 -0
  170. package/backend/online-media.ts +110 -0
  171. package/backend/page-actions.ts +91 -0
  172. package/backend/page-link-handler.ts +51 -0
  173. package/backend/pagetsconfig/pagetsconfig-includes.ts +77 -0
  174. package/backend/popover.ts +165 -0
  175. package/backend/record-download-button.ts +104 -0
  176. package/backend/record-link-handler.ts +31 -0
  177. package/backend/record-search.ts +44 -0
  178. package/backend/recordlist.ts +230 -0
  179. package/backend/resource/resource.ts +35 -0
  180. package/backend/security/element/csp-reports.ts +406 -0
  181. package/backend/security/element/sudo-mode.ts +249 -0
  182. package/backend/security/sudo-mode-interceptor.ts +19 -0
  183. package/backend/settings/editor/editable-setting.ts +231 -0
  184. package/backend/settings/editor.ts +451 -0
  185. package/backend/settings/enum/settings-mode.enum.ts +24 -0
  186. package/backend/settings/type/base.ts +200 -0
  187. package/backend/settings/type/bool.ts +56 -0
  188. package/backend/settings/type/color.ts +64 -0
  189. package/backend/settings/type/int.ts +85 -0
  190. package/backend/settings/type/number.ts +61 -0
  191. package/backend/settings/type/page.ts +100 -0
  192. package/backend/settings/type/string.ts +77 -0
  193. package/backend/settings/type/stringlist.ts +87 -0
  194. package/backend/severity.ts +88 -0
  195. package/backend/site-inline-actions.ts +32 -0
  196. package/backend/sortable-table.ts +208 -0
  197. package/backend/storage/abstract-client-storage.ts +76 -0
  198. package/backend/storage/browser-session.ts +28 -0
  199. package/backend/storage/client.ts +28 -0
  200. package/backend/storage/module-state-storage.ts +118 -0
  201. package/backend/storage/persistent.ts +204 -0
  202. package/backend/switch-user.ts +82 -0
  203. package/backend/tabs.ts +72 -0
  204. package/backend/telephone-link-handler.ts +40 -0
  205. package/backend/tests/backend/storage/browser-storage-test.ts +87 -0
  206. package/backend/tests/backend/storage/client-test.ts +87 -0
  207. package/backend/tests/backend/utility/dom-helper-test.ts +51 -0
  208. package/backend/tests/backend-exception-test.ts +28 -0
  209. package/backend/tests/element/immediate-action-element-test.ts +81 -0
  210. package/backend/tests/form-engine-validation-test.ts +304 -0
  211. package/backend/tests/grid-editor-test.ts +29 -0
  212. package/backend/tests/hashing/md5Test.ts +28 -0
  213. package/backend/tests/icons-test.ts +23 -0
  214. package/backend/tests/notification-test.ts +161 -0
  215. package/backend/tests/popover-test.ts +83 -0
  216. package/backend/toolbar/clear-cache-menu.ts +88 -0
  217. package/backend/toolbar/live-search.ts +284 -0
  218. package/backend/toolbar/shortcut-menu.ts +254 -0
  219. package/backend/toolbar/system-information-menu.ts +161 -0
  220. package/backend/toolbar.ts +41 -0
  221. package/backend/tree/file-storage-browser.ts +194 -0
  222. package/backend/tree/file-storage-tree-container.ts +465 -0
  223. package/backend/tree/file-storage-tree.ts +42 -0
  224. package/backend/tree/page-browser.ts +272 -0
  225. package/backend/tree/page-tree-element.ts +602 -0
  226. package/backend/tree/page-tree.ts +152 -0
  227. package/backend/tree/tree-module-state.ts +159 -0
  228. package/backend/tree/tree-node-toggle.ts +31 -0
  229. package/backend/tree/tree-node.ts +97 -0
  230. package/backend/tree/tree-toolbar.ts +117 -0
  231. package/backend/tree/tree.ts +1595 -0
  232. package/backend/url-link-handler.ts +36 -0
  233. package/backend/user-pass-login.ts +255 -0
  234. package/backend/user-settings-manager.ts +189 -0
  235. package/backend/utility/collapse-state-persister.ts +205 -0
  236. package/backend/utility/collapse-state-search.ts +183 -0
  237. package/backend/utility/dom-helper.ts +91 -0
  238. package/backend/utility/drag-drop-utility.ts +47 -0
  239. package/backend/utility/message-utility.ts +45 -0
  240. package/backend/utility/top-level-module-import.ts +32 -0
  241. package/backend/utility.ts +194 -0
  242. package/backend/viewport/abstract-container.ts +22 -0
  243. package/backend/viewport/content-container.ts +98 -0
  244. package/backend/viewport/loader.ts +28 -0
  245. package/backend/viewport/navigation-container.ts +119 -0
  246. package/backend/viewport/resizable-navigation.ts +176 -0
  247. package/backend/viewport/toolbar.ts +27 -0
  248. package/backend/viewport/topbar.ts +39 -0
  249. package/backend/viewport.ts +46 -0
  250. package/backend/window-manager.ts +84 -0
  251. package/backend/wizard/move-content-element.ts +107 -0
  252. package/backend/wizard/move-page.ts +74 -0
  253. package/backend/wizard.ts +331 -0
  254. package/belog/backend-log.ts +88 -0
  255. package/beuser/backend-user-listing.ts +40 -0
  256. package/beuser/permissions.ts +291 -0
  257. package/core/ajax/ajax-request-types.ts +15 -0
  258. package/core/ajax/ajax-request.ts +202 -0
  259. package/core/ajax/ajax-response.ts +57 -0
  260. package/core/ajax/input-transformer.ts +92 -0
  261. package/core/ajax/simple-response-interface.ts +5 -0
  262. package/core/authentication/mfa-provider/totp.ts +54 -0
  263. package/core/cache/dedupe-async-task.ts +95 -0
  264. package/core/directive/markdown.ts +82 -0
  265. package/core/directive/nl2br.ts +16 -0
  266. package/core/document-service.ts +35 -0
  267. package/core/event/debounce-event.ts +40 -0
  268. package/core/event/event-interface.ts +24 -0
  269. package/core/event/regular-event.ts +58 -0
  270. package/core/event/request-animation-frame-event.ts +42 -0
  271. package/core/event/throttle-event.ts +54 -0
  272. package/core/java-script-item-handler.ts +29 -0
  273. package/core/java-script-item-processor.ts +171 -0
  274. package/core/lit-helper.ts +126 -0
  275. package/core/literals.ts +17 -0
  276. package/core/referrer-refresh.ts +2 -0
  277. package/core/security-utility.ts +89 -0
  278. package/core/tests/ajax/ajax-request-test.ts +290 -0
  279. package/core/tests/ajax/input-transformer-test.ts +64 -0
  280. package/core/tests/cache/dedupe-async-task-test.ts +152 -0
  281. package/core/tests/event/throttle-event-test.ts +47 -0
  282. package/core/tests/literals-test.ts +24 -0
  283. package/core/tests/security-utility-test.ts +50 -0
  284. package/core/utility/types.ts +9 -0
  285. package/dashboard/chart-initializer.ts +136 -0
  286. package/dashboard/dashboard.ts +1487 -0
  287. package/extensionmanager/distribution-image.ts +55 -0
  288. package/extensionmanager/main.ts +291 -0
  289. package/extensionmanager/repository.ts +195 -0
  290. package/extensionmanager/update.ts +118 -0
  291. package/extensionmanager/upload-form.ts +42 -0
  292. package/filelist/browse-files.ts +148 -0
  293. package/filelist/browse-folders.ts +94 -0
  294. package/filelist/context-menu-actions.ts +335 -0
  295. package/filelist/create-folder.ts +69 -0
  296. package/filelist/file-delete.ts +69 -0
  297. package/filelist/file-list-actions.ts +132 -0
  298. package/filelist/file-list-dragdrop.ts +139 -0
  299. package/filelist/file-list-rename-handler.ts +128 -0
  300. package/filelist/file-list-transfer-handler.ts +138 -0
  301. package/filelist/file-list.ts +396 -0
  302. package/filelist/file-replace.ts +37 -0
  303. package/filelist/linkbrowser-file-handler.ts +94 -0
  304. package/filelist/linkbrowser-folder-handler.ts +74 -0
  305. package/filelist/rename-file.ts +109 -0
  306. package/form/backend/form-editor/component/property-grid-editor.ts +340 -0
  307. package/form/backend/form-editor/core.ts +2104 -0
  308. package/form/backend/form-editor/helper.ts +226 -0
  309. package/form/backend/form-editor/inspector-component.ts +2323 -0
  310. package/form/backend/form-editor/mediator.ts +941 -0
  311. package/form/backend/form-editor/modals-component.ts +433 -0
  312. package/form/backend/form-editor/stage-component.ts +908 -0
  313. package/form/backend/form-editor/tree-component.ts +548 -0
  314. package/form/backend/form-editor/view-model.ts +1343 -0
  315. package/form/backend/form-editor.ts +846 -0
  316. package/form/backend/form-manager/main.ts +39 -0
  317. package/form/backend/form-manager/view-model.ts +789 -0
  318. package/form/backend/form-manager.ts +180 -0
  319. package/form/backend/helper.ts +60 -0
  320. package/form/frontend/date-picker.ts +38 -0
  321. package/frontend/default_frontend.ts +82 -0
  322. package/impexp/context-menu-actions.ts +50 -0
  323. package/impexp/import-export.ts +62 -0
  324. package/indexed-search/pagination.ts +26 -0
  325. package/indexed-search/statistics.ts +50 -0
  326. package/info/translation-status.ts +48 -0
  327. package/install/ajax/ajax-queue.ts +83 -0
  328. package/install/init-install.ts +2 -0
  329. package/install/init-installer.ts +2 -0
  330. package/install/install.ts +25 -0
  331. package/install/installer.ts +458 -0
  332. package/install/message-interface.ts +18 -0
  333. package/install/module/abstract-inline-module.ts +27 -0
  334. package/install/module/abstract-interactable-module.ts +107 -0
  335. package/install/module/environment/environment-check.ts +92 -0
  336. package/install/module/environment/folder-structure.ts +145 -0
  337. package/install/module/environment/image-processing.ts +141 -0
  338. package/install/module/environment/mail-test.ts +127 -0
  339. package/install/module/environment/php-info.ts +50 -0
  340. package/install/module/environment/system-information.ts +50 -0
  341. package/install/module/maintenance/cache.ts +59 -0
  342. package/install/module/maintenance/clear-tables.ts +134 -0
  343. package/install/module/maintenance/clear-typo3temp-files.ts +137 -0
  344. package/install/module/maintenance/create-admin.ts +126 -0
  345. package/install/module/maintenance/database-analyzer.ts +266 -0
  346. package/install/module/maintenance/dump-autoload.ts +58 -0
  347. package/install/module/maintenance/language-packs.ts +338 -0
  348. package/install/module/maintenance/reset-backend-user-uc.ts +58 -0
  349. package/install/module/password-strength.ts +56 -0
  350. package/install/module/settings/change-install-tool-password.ts +101 -0
  351. package/install/module/settings/extension-configuration.ts +224 -0
  352. package/install/module/settings/features.ts +102 -0
  353. package/install/module/settings/local-configuration.ts +316 -0
  354. package/install/module/settings/presets.ts +147 -0
  355. package/install/module/settings/system-maintainer.ts +111 -0
  356. package/install/module/upgrade/core-update.ts +255 -0
  357. package/install/module/upgrade/extension-compat-tester.ts +195 -0
  358. package/install/module/upgrade/extension-scanner.ts +381 -0
  359. package/install/module/upgrade/tca-ext-tables-check.ts +93 -0
  360. package/install/module/upgrade/tca-migrations-check.ts +101 -0
  361. package/install/module/upgrade/upgrade-docs.ts +310 -0
  362. package/install/module/upgrade/upgrade-wizards.ts +612 -0
  363. package/install/renderable/clearable.ts +113 -0
  364. package/install/renderable/flash-message.ts +64 -0
  365. package/install/renderable/info-box.ts +66 -0
  366. package/install/renderable/language-packs.ts +304 -0
  367. package/install/renderable/offset-group.ts +38 -0
  368. package/install/renderable/severity.ts +49 -0
  369. package/install/renderable/wrap-group.ts +36 -0
  370. package/install/router.ts +468 -0
  371. package/linkvalidator/linkvalidator-modal.ts +45 -0
  372. package/linkvalidator/linkvalidator.ts +101 -0
  373. package/lowlevel/query-generator.ts +132 -0
  374. package/lowlevel/reference-index.ts +44 -0
  375. package/opendocs/toolbar/opendocs-menu.ts +117 -0
  376. package/package.json +34 -0
  377. package/recycler/recycler.ts +539 -0
  378. package/redirects/event-handler.ts +108 -0
  379. package/redirects/form-engine-evaluation.ts +34 -0
  380. package/redirects/redirects-module.ts +58 -0
  381. package/rte-ckeditor/ckeditor5.ts +402 -0
  382. package/rte-ckeditor/css-prefixer.ts +94 -0
  383. package/rte-ckeditor/legacy/ckeditor5-bundle.ts +17 -0
  384. package/rte-ckeditor/legacy/ckeditor5-inspector.ts +2 -0
  385. package/rte-ckeditor/plugin/typo3-link.ts +810 -0
  386. package/rte-ckeditor/plugin/whitespace.ts +114 -0
  387. package/rte-ckeditor/rte-link-browser.ts +112 -0
  388. package/rte-ckeditor/tests/css-prefixer-test.ts +314 -0
  389. package/scheduler/scheduler-add-group.ts +119 -0
  390. package/scheduler/scheduler-editable-group-name.ts +307 -0
  391. package/scheduler/scheduler-sortable-groups.ts +59 -0
  392. package/scheduler/scheduler.ts +466 -0
  393. package/setup/setup-module.ts +122 -0
  394. package/styleguide/checkbox-demo.ts +5 -0
  395. package/styleguide/element/theme-switcher-element.ts +101 -0
  396. package/styleguide/filter.ts +22 -0
  397. package/styleguide/find-icons.ts +78 -0
  398. package/styleguide/processing-indicator.ts +78 -0
  399. package/styleguide/render-notifications.ts +64 -0
  400. package/sys-note/element/delete-button.ts +78 -0
  401. package/tstemplate/constant-editor.ts +64 -0
  402. package/tstemplate/information-module.ts +31 -0
  403. package/tstemplate/template-analyzer.ts +77 -0
  404. package/viewpage/main.ts +253 -0
  405. package/workspaces/backend.ts +979 -0
  406. package/workspaces/preview.ts +232 -0
  407. package/workspaces/renderable/comment-view.ts +79 -0
  408. package/workspaces/renderable/diff-view.ts +58 -0
  409. package/workspaces/renderable/history-view.ts +97 -0
  410. package/workspaces/renderable/record-information.ts +118 -0
  411. package/workspaces/renderable/record-table.ts +355 -0
  412. package/workspaces/renderable/send-to-stage-form.ts +108 -0
  413. package/workspaces/toolbar/workspaces-menu.ts +190 -0
  414. package/workspaces/utility/icon-helper.ts +57 -0
  415. package/workspaces/workspaces.ts +136 -0
@@ -0,0 +1,751 @@
1
+ /*
2
+ * This file is part of the TYPO3 CMS project.
3
+ *
4
+ * It is free software; you can redistribute it and/or modify it under
5
+ * the terms of the GNU General Public License, either version 2
6
+ * of the License, or any later version.
7
+ *
8
+ * For the full copyright and license information, please read the
9
+ * LICENSE.txt file that was distributed with this source code.
10
+ *
11
+ * The TYPO3 project - inspiring people to share!
12
+ */
13
+
14
+ import { AjaxResponse } from '@typo3/core/ajax/ajax-response';
15
+ import AjaxRequest from '@typo3/core/ajax/ajax-request';
16
+ import ContextMenuActions from './context-menu-actions';
17
+ import '@typo3/backend/element/spinner-element';
18
+ import { customElement, queryAll, state } from 'lit/decorators';
19
+ import { html, LitElement, nothing, type TemplateResult } from 'lit';
20
+ import { delay } from '@typo3/core/lit-helper';
21
+ import { styleMap } from 'lit/directives/style-map';
22
+ import { unsafeHTML } from 'lit/directives/unsafe-html';
23
+ import { Task, initialState } from '@lit/task';
24
+ import Notification from '@typo3/backend/notification';
25
+
26
+ interface RawMenuItemInterface {
27
+ identifier: string;
28
+ type: string;
29
+ icon: string;
30
+ label: string;
31
+ additionalAttributes?: Record<string, string>;
32
+ childItems?: RawMenuItemInterface[];
33
+ callbackAction?: string;
34
+ }
35
+
36
+ interface MenuItemInterface extends RawMenuItemInterface {
37
+ childItems: MenuItemInterface[];
38
+ // Calculated Internal
39
+ __contextMenuIdentifier: string;
40
+ __contextMenuParentIdentifier: string;
41
+ __expanded: boolean;
42
+ }
43
+
44
+ enum ContextMenuEvent {
45
+ open = 'typo3:contextmenu:open',
46
+ close = 'typo3:contextmenu:close',
47
+ }
48
+
49
+ interface ContextMenuEventDetail {
50
+ table: string;
51
+ uid: number | string;
52
+ context: string;
53
+ eventSource: HTMLElement;
54
+ originalEvent: PointerEvent;
55
+ }
56
+
57
+ /**
58
+ * Module: @typo3/backend/context-menu
59
+ * Container used to load the context menu via AJAX to render the result in a layer next to the mouse cursor
60
+ */
61
+ class ContextMenu {
62
+ constructor() {
63
+ document.addEventListener('click', (event: PointerEvent) => {
64
+ this.handleTriggerEvent(event);
65
+ });
66
+ document.addEventListener('contextmenu', (event: PointerEvent) => {
67
+ this.handleTriggerEvent(event);
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Main function, called from most context menu links
73
+ *
74
+ * @param {string} table Table from where info should be fetched
75
+ * @param {number|string} uid The UID of the item
76
+ * @param {string|null} context Context of the item
77
+ * @param {string} unusedParam1
78
+ * @param {string} unusedParam2
79
+ * @param {HTMLElement} eventSource Source Element
80
+ * @param {Event} originalEvent
81
+ */
82
+ public show(
83
+ table: string,
84
+ uid: number | string,
85
+ context: string | null,
86
+ unusedParam1: string,
87
+ unusedParam2: string,
88
+ eventSource: HTMLElement = null,
89
+ originalEvent: PointerEvent = null
90
+ ): void {
91
+ const contextMenuEvent = new CustomEvent<ContextMenuEventDetail>(ContextMenuEvent.open, {
92
+ detail: {
93
+ table: table,
94
+ uid: uid,
95
+ context: context,
96
+ eventSource: eventSource,
97
+ originalEvent: originalEvent,
98
+ },
99
+ bubbles: true,
100
+ composed: true
101
+ });
102
+ top.document.dispatchEvent(contextMenuEvent);
103
+ }
104
+
105
+ private handleTriggerEvent(event: PointerEvent): void {
106
+ if (!(event.target instanceof Element)) {
107
+ return;
108
+ }
109
+
110
+ const contextTarget = event.target.closest('[data-contextmenu-trigger]');
111
+ if (contextTarget instanceof HTMLElement) {
112
+ this.handleContextMenuEvent(event, contextTarget);
113
+ return;
114
+ }
115
+ }
116
+
117
+ private handleContextMenuEvent(event: PointerEvent, element: HTMLElement): void {
118
+ const contextTrigger: string = element.dataset.contextmenuTrigger;
119
+ if (contextTrigger === 'click' || contextTrigger === event.type) {
120
+ event.preventDefault();
121
+ if ((element.dataset.contextmenuTable ?? '') === '') {
122
+ console.error('Referenced element misses data-contextmenu-table', element);
123
+ throw new Error('No data-contextmenu-table attribute provided in contextmenu trigger markup');
124
+ }
125
+ if ((element.dataset.contextmenuUid ?? '') === '') {
126
+ console.error('Referenced element misses data-contextmenu-uid', element);
127
+ throw new Error('No data-contextmenu-uid attribute provided in contextmenu trigger markup');
128
+ }
129
+ this.show(
130
+ element.dataset.contextmenuTable ?? '',
131
+ element.dataset.contextmenuUid ?? '',
132
+ element.dataset.contextmenuContext ?? '',
133
+ '',
134
+ '',
135
+ element,
136
+ event
137
+ );
138
+ }
139
+ }
140
+ }
141
+
142
+ export default new ContextMenu();
143
+
144
+ @customElement('typo3-backend-context-menu')
145
+ export class ContextMenuElement extends LitElement {
146
+ @state() open: boolean = false;
147
+ @state() table: string = '';
148
+ @state() uid: number | string = '';
149
+ @state() context: string = '';
150
+ @state() rootPositionY: number = 0;
151
+ @state() rootPositionX: number = 0;
152
+ @state() eventSource: HTMLElement | null = null;
153
+
154
+ @queryAll('.context-menu') contextMenuElements: HTMLDivElement[];
155
+ @queryAll('.context-menu-item') contextMenuItemElements: HTMLButtonElement[];
156
+ private readonly rootIdentifier: string = 'root';
157
+
158
+ private focusFirstElement: boolean = false;
159
+
160
+ private readonly fetchTask = new Task(this, {
161
+ autoRun: false,
162
+ args: () => [this.table, this.uid, this.context, this.open] as const,
163
+ task: async ([table, uid, context, isOpen], { signal }): Promise<MenuItemInterface[] | typeof initialState> => {
164
+ if (!isOpen) {
165
+ return initialState;
166
+ }
167
+
168
+ const parameters = new URLSearchParams();
169
+ if (table !== '') {
170
+ parameters.set('table', table);
171
+ }
172
+ if (uid !== '') {
173
+ parameters.set('uid', uid.toString());
174
+ }
175
+ if ((context ?? '') !== '') {
176
+ parameters.set('context', context);
177
+ }
178
+ if (parameters.size === 0) {
179
+ return initialState;
180
+ }
181
+
182
+ const url = TYPO3.settings.ajaxUrls.contextmenu;
183
+ const response = await new AjaxRequest(url)
184
+ .withQueryArguments(parameters)
185
+ .get({ signal });
186
+ const data: RawMenuItemInterface[] = Object.values(await response.resolve());
187
+ if (data.length === 0) {
188
+ return initialState;
189
+ }
190
+ return this.enhanceNodes('root', data);
191
+ },
192
+ onComplete: () => {
193
+ this.focusFirstElement = true;
194
+ },
195
+ onError: (error: Error|AjaxResponse) => {
196
+ if (error instanceof AjaxResponse) {
197
+ Notification.error('', error.response.status + ' ' + error.response.statusText, 5);
198
+ } else {
199
+ Notification.error('', error.message);
200
+ }
201
+ },
202
+ });
203
+
204
+ private get nodes(): MenuItemInterface[] {
205
+ return this.fetchTask.value ?? [];
206
+ }
207
+
208
+ public override connectedCallback(): void {
209
+ super.connectedCallback();
210
+ window.addEventListener('resize', this.hide);
211
+ document.addEventListener(ContextMenuEvent.open, this.show);
212
+ document.addEventListener(ContextMenuEvent.close, this.hide);
213
+ }
214
+
215
+ public override disconnectedCallback(): void {
216
+ super.disconnectedCallback();
217
+ window.removeEventListener('resize', this.hide);
218
+ document.removeEventListener(ContextMenuEvent.open, this.show);
219
+ document.removeEventListener(ContextMenuEvent.close, this.hide);
220
+ }
221
+
222
+ protected override updated(): void {
223
+ if (this.focusFirstElement) {
224
+ if (this.contextMenuItemElements.length > 0) {
225
+ const firstItemElement = Array.from(this.contextMenuItemElements).at(0);
226
+ firstItemElement.focus();
227
+ }
228
+ this.focusFirstElement = false;
229
+ }
230
+ this.updateContextMenuPositions();
231
+ }
232
+
233
+ protected override createRenderRoot(): HTMLElement | ShadowRoot {
234
+ return this;
235
+ }
236
+
237
+ protected override render() {
238
+ return this.fetchTask.render({
239
+ initial: () => nothing,
240
+ pending: () => [
241
+ this.renderOverlay(),
242
+ // Delay the spinner by 80 ms to prevent flickering for fast connections.
243
+ delay(80, () => this.renderMenu(null, this.rootIdentifier, this.rootPositionY, this.rootPositionX)),
244
+ ],
245
+ complete: (nodes) => [
246
+ this.renderOverlay(),
247
+ this.renderMenu(nodes, this.rootIdentifier, this.rootPositionY, this.rootPositionX),
248
+ this.flattenMenuItems(nodes)
249
+ .filter(node => node.type === 'submenu')
250
+ .map(node => !this.isNodeExpanded(node) ? nothing : this.renderMenu(
251
+ node.childItems,
252
+ this.getNodeIdentifier(node),
253
+ this.rootPositionY,
254
+ this.rootPositionX
255
+ )),
256
+ ],
257
+ });
258
+ }
259
+
260
+ private readonly show = (event: CustomEvent<ContextMenuEventDetail>): void => {
261
+ const detail = event.detail;
262
+ this.open = true;
263
+ this.table = detail.table;
264
+ this.uid = detail.uid;
265
+ this.context = detail.context;
266
+ this.eventSource = detail.eventSource;
267
+
268
+ if (detail.originalEvent !== null) {
269
+ const offset = this.calculateIframeOffset(detail.originalEvent.view, window);
270
+ let x: number, y: number;
271
+ // Event was triggered by a non pointing device, see
272
+ // https://w3c.github.io/pointerevents/#dom-pointerevent-pointerid
273
+ // > The pointerId value of -1 MUST be reserved and used to indicate events that
274
+ // > were generated by something other than a pointing device.
275
+ if (detail.originalEvent.pointerId === -1 && detail.originalEvent.target) {
276
+ const rect = (detail.originalEvent.target as Element).getBoundingClientRect();
277
+ y = rect.top + rect.height / 2;
278
+ // @todo remove positioning offset in `this.updateContextMenuPosition()`
279
+ // and add rect.width/2 here
280
+ x = rect.left;
281
+ } else {
282
+ y = detail.originalEvent.clientY;
283
+ x = detail.originalEvent.clientX;
284
+ }
285
+ this.rootPositionY = y + offset.y;
286
+ this.rootPositionX = x + offset.x;
287
+ }
288
+
289
+ this.fetchTask.run();
290
+ };
291
+
292
+ private readonly hide = async (): Promise<void> => {
293
+ if (!this.open) {
294
+ return;
295
+ }
296
+ this.open = false;
297
+ this.fetchTask.run();
298
+ await this.updateComplete;
299
+ this.eventSource?.focus();
300
+ };
301
+
302
+ private renderOverlay(): TemplateResult {
303
+ return html`
304
+ <div
305
+ class="context-menu-overlay"
306
+ @click="${(event: PointerEvent) => this.handleOverlayClick(event)}"
307
+ @contextmenu="${(event: PointerEvent) => this.handleOverlayClick(event)}"
308
+ ></div>
309
+ `;
310
+ }
311
+
312
+ private renderMenu(nodes: MenuItemInterface[]|null, parentIdentifier: string, posY: number, posX: number): TemplateResult | typeof nothing {
313
+ const styles = {
314
+ top: posY + 'px',
315
+ insetInlineStart: posX + 'px',
316
+ };
317
+
318
+ // pending state
319
+ if (nodes === null) {
320
+ return html`
321
+ <div id="contextmenu-${parentIdentifier}" data-contextmenu-parent=${parentIdentifier} class="context-menu" style=${styleMap(styles)}>
322
+ <typo3-backend-spinner size="medium"></typo3-backend-spinner>
323
+ </div>
324
+ `;
325
+ }
326
+
327
+ if (nodes.length === 0) {
328
+ return nothing;
329
+ }
330
+
331
+ return html`
332
+ <div id="contextmenu-${parentIdentifier}" data-contextmenu-parent=${parentIdentifier} class="context-menu" style=${styleMap(styles)}>
333
+ <ul class="context-menu-group" role="menu">
334
+ ${nodes.map(node => html`
335
+ <li role="presentation">${this.renderMenuItem(node)}</li>
336
+ `)}
337
+ </ul>
338
+ </div>
339
+ `;
340
+ }
341
+
342
+ private renderMenuItem(node: MenuItemInterface): TemplateResult {
343
+ if (node.type === 'divider') {
344
+ return html`<hr class="context-menu-divider">`;
345
+ }
346
+
347
+ return html`
348
+ <button
349
+ type="button"
350
+ class="context-menu-item"
351
+ tabindex="-1"
352
+ role="menuitem"
353
+ data-contextmenu-id=${this.getNodeIdentifier(node)}
354
+ data-contextmenu-type=${node.type}
355
+ aria-posinset=${this.getNodePositionInSet(node)}
356
+ aria-setsize=${this.getNodeSetSize(node)}
357
+ aria-label=${node.label}
358
+ aria-popup=${(node.type === 'submenu' ? 'menu' : false) || nothing}
359
+ @click=${(event: PointerEvent) => { this.handleNodeClick(event, node); }}
360
+ @keydown=${(event: KeyboardEvent) => { this.handleNodeKeyDown(event, node); }}
361
+ >
362
+ <span class="context-menu-item-icon" role="presentation">${unsafeHTML(node.icon)}</span>
363
+ <span class="context-menu-item-label" role="presentation">${unsafeHTML(node.label)}</span>
364
+ ${node.type === 'submenu' ? html`<span class="context-menu-item-indicator"><typo3-backend-icon identifier="actions-chevron-${this.getDocumentDirection() === 'ltr' ? 'right' : 'left'}" size="small"></typo3-backend-icon></span>` : ''}
365
+ </button>
366
+ `;
367
+ }
368
+
369
+ private showSubmenu(node: MenuItemInterface): void {
370
+ node.__expanded = true;
371
+ }
372
+
373
+ private hideSubmenu(node: MenuItemInterface): void {
374
+ node.__expanded = false;
375
+ }
376
+
377
+ private handleNodeClick(event: PointerEvent, node: MenuItemInterface): void {
378
+ if (node.type === 'submenu') {
379
+ if (this.isNodeExpanded(node)) {
380
+ this.hideSubmenu(node);
381
+ } else {
382
+ this.showSubmenu(node);
383
+ }
384
+
385
+ return;
386
+ }
387
+
388
+ const dataset = this.extractDataAttributesAndConvertToDataset(node.additionalAttributes);
389
+ const callbackAction = node.callbackAction;
390
+ const { callbackModule, ...dataAttributesToPass } = dataset;
391
+ if (callbackModule) {
392
+ import(callbackModule + '.js').then(({ default: callbackModuleCallback }: { default: any }): void => {
393
+ callbackModuleCallback[callbackAction](this.table, this.uid, dataAttributesToPass);
394
+ });
395
+ } else if (ContextMenuActions && typeof (ContextMenuActions as any)[callbackAction] === 'function') {
396
+ (ContextMenuActions as any)[callbackAction](this.table, this.uid, dataAttributesToPass);
397
+ } else {
398
+ console.error('action: ' + callbackAction + ' not found');
399
+ }
400
+
401
+ this.hide();
402
+ }
403
+
404
+ private mapIframeTarget(target: Element, event: PointerEvent): Element {
405
+ if (target.tagName !== 'IFRAME') {
406
+ return target;
407
+ }
408
+
409
+ const iframe = target as HTMLIFrameElement;
410
+ let win: Window;
411
+ try {
412
+ win = iframe.contentWindow;
413
+ } catch {
414
+ return target;
415
+ }
416
+
417
+ const rect = iframe.getBoundingClientRect();
418
+ const iframeTarget = win.document.elementFromPoint(event.clientX - rect.x, event.clientY - rect.y);
419
+ return iframeTarget ?? target;
420
+ }
421
+
422
+ private async handleOverlayClick(event: PointerEvent): Promise<void> {
423
+ event.preventDefault();
424
+ event.stopPropagation();
425
+ this.eventSource = null;
426
+ await this.hide();
427
+
428
+ let target = document.elementFromPoint(event.clientX, event.clientY);
429
+ if (target && target !== event.currentTarget) {
430
+ target = this.mapIframeTarget(target, event);
431
+ target.dispatchEvent(new PointerEvent(event.type, event));
432
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
433
+ (target as HTMLInputElement|HTMLTextAreaElement).focus();
434
+ }
435
+ }
436
+ }
437
+
438
+ private async handleNodeKeyDown(event: KeyboardEvent, node: MenuItemInterface): Promise<void> {
439
+ const handledKeys = [
440
+ 'ArrowDown',
441
+ 'ArrowUp',
442
+ 'ArrowLeft',
443
+ 'ArrowRight',
444
+ 'Home',
445
+ 'End',
446
+ 'Enter',
447
+ 'Space',
448
+ 'Escape',
449
+ 'Tab',
450
+ ];
451
+ if (!handledKeys.includes(event.code) || event.altKey || event.ctrlKey) {
452
+ return;
453
+ }
454
+ event.preventDefault();
455
+ event.stopPropagation();
456
+
457
+ const nodeList = this.getParralellNodesForNavigation(node);
458
+ const firstNode = this.getFirstNode(nodeList);
459
+ const lastNode = this.getLastNode(nodeList);
460
+ const parentNode = this.getParentNode(node);
461
+ const previousNode = this.getPreviousNode(node);
462
+ const nextNode = this.getNextNode(node);
463
+
464
+ switch (event.code) {
465
+ case 'Enter':
466
+ case 'Space':
467
+ if (node.type === 'submenu') {
468
+ this.showSubmenu(node);
469
+ await this.updateComplete;
470
+ const firstNode = this.getFirstNode(node.childItems);
471
+ if (firstNode) {
472
+ this.getElementFromNode(firstNode)?.focus();
473
+ }
474
+ } else {
475
+ this.getElementFromNode(node)?.click();
476
+ }
477
+ break;
478
+ case 'Tab':
479
+ this.hide();
480
+ break;
481
+ case 'Escape':
482
+ if (parentNode) {
483
+ this.hideSubmenu(parentNode);
484
+ await this.updateComplete;
485
+ this.getElementFromNode(parentNode)?.focus();
486
+ } else {
487
+ this.hide();
488
+ }
489
+ break;
490
+ case 'ArrowUp':
491
+ if (previousNode) {
492
+ this.getElementFromNode(previousNode)?.focus();
493
+ }
494
+ break;
495
+ case 'ArrowDown':
496
+ if (nextNode) {
497
+ this.getElementFromNode(nextNode)?.focus();
498
+ }
499
+ break;
500
+ case 'ArrowRight':
501
+ if (node.type === 'submenu') {
502
+ this.showSubmenu(node);
503
+ await this.updateComplete;
504
+ const firstNode = this.getFirstNode(node.childItems);
505
+ if (firstNode) {
506
+ this.getElementFromNode(firstNode)?.focus();
507
+ }
508
+ }
509
+ break;
510
+ case 'ArrowLeft':
511
+ if (parentNode) {
512
+ this.hideSubmenu(parentNode);
513
+ await this.updateComplete;
514
+ this.getElementFromNode(parentNode)?.focus();
515
+ }
516
+ break;
517
+ case 'Home':
518
+ this.getElementFromNode(firstNode)?.focus();
519
+ break;
520
+ case 'End':
521
+ this.getElementFromNode(lastNode)?.focus();
522
+ break;
523
+ default:
524
+ return;
525
+ }
526
+
527
+ return;
528
+ }
529
+
530
+ private getNodeIdentifier(node: MenuItemInterface): string {
531
+ return node.__contextMenuIdentifier;
532
+ }
533
+
534
+ private getNodeParentIdentifier(node: MenuItemInterface): string {
535
+ return node.__contextMenuParentIdentifier;
536
+ }
537
+
538
+ private getNodePositionInSet(node: MenuItemInterface): number {
539
+ const nodeSet = this.getParralellNodesForNavigation(node);
540
+ return nodeSet.indexOf(node) + 1;
541
+ }
542
+
543
+ private getNodeSetSize(node: MenuItemInterface): number {
544
+ return this.getParralellNodesForNavigation(node).length;
545
+ }
546
+
547
+ private getParentNode(node: MenuItemInterface): MenuItemInterface | null {
548
+ const parentNodeIdentifier = this.getNodeParentIdentifier(node);
549
+ return this.getNodeByIdentifier(parentNodeIdentifier);
550
+ }
551
+
552
+ private getNodeByIdentifier(identifier: string): MenuItemInterface | null {
553
+ const flattenedNodes = this.flattenMenuItems(this.nodes);
554
+ return flattenedNodes.find((node: MenuItemInterface) => {
555
+ return this.getNodeIdentifier(node) === identifier;
556
+ }) ?? null;
557
+ }
558
+
559
+ private getPreviousNode(node: MenuItemInterface): MenuItemInterface | null {
560
+ const nodeList = this.getParralellNodesForNavigation(node);
561
+ const position = nodeList.indexOf(node);
562
+ const nextPosition = position - 1;
563
+
564
+ if (nodeList[nextPosition]) {
565
+ return nodeList[nextPosition];
566
+ }
567
+
568
+ return this.getLastNode(nodeList);
569
+ }
570
+
571
+ private getNextNode(node: MenuItemInterface): MenuItemInterface | null {
572
+ const nodeList = this.getParralellNodesForNavigation(node);
573
+ const position = nodeList.indexOf(node);
574
+ const nextPosition = position + 1;
575
+
576
+ if (nodeList[nextPosition]) {
577
+ return nodeList[nextPosition];
578
+ }
579
+
580
+ return this.getFirstNode(nodeList);
581
+ }
582
+
583
+ private getFirstNode(nodes: MenuItemInterface[]): MenuItemInterface | null {
584
+ return nodes.at(0) ?? null;
585
+ }
586
+
587
+ private getLastNode(nodes: MenuItemInterface[]): MenuItemInterface | null {
588
+ return nodes.at(-1) ?? null;
589
+ }
590
+
591
+ private isNodeExpanded(node: MenuItemInterface): boolean {
592
+ return node.__expanded === true;
593
+ }
594
+
595
+ private getElementFromNode(node: MenuItemInterface): HTMLElement | null {
596
+ return this.querySelector('[data-contextmenu-id="' + this.getNodeIdentifier(node) + '"]');
597
+ }
598
+
599
+ private enhanceNodes(parentIdentifier: string, nodes: RawMenuItemInterface[]): MenuItemInterface[] {
600
+ const enhancedNodes = nodes.reduce((nodes: MenuItemInterface[], node: RawMenuItemInterface) => {
601
+ const contextMenuIdentifier = parentIdentifier + '_' + node.identifier;
602
+ const enhancedNode: MenuItemInterface = {
603
+ ...node,
604
+ __contextMenuIdentifier: contextMenuIdentifier,
605
+ __contextMenuParentIdentifier: parentIdentifier,
606
+ __expanded: false,
607
+ childItems: this.enhanceNodes(contextMenuIdentifier, Object.values(node.childItems ?? {})),
608
+ };
609
+
610
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
611
+ const that = this;
612
+ return [
613
+ ...nodes,
614
+ new Proxy(enhancedNode, {
615
+ set<K extends keyof MenuItemInterface>(target: MenuItemInterface, key: K, value: MenuItemInterface[K]) {
616
+ if (target[key] !== value) {
617
+ target[key] = value as never;
618
+ that.requestUpdate();
619
+ }
620
+ return true;
621
+ },
622
+ })
623
+ ];
624
+ }, []);
625
+
626
+ return enhancedNodes;
627
+ }
628
+
629
+ private calculateIframeOffset(contentWindow: Window, currentWindow: Window): { x: number, y: number } {
630
+ let x = 0, y = 0;
631
+
632
+ if (contentWindow === currentWindow) {
633
+ return { x, y };
634
+ }
635
+
636
+ const parentOffset = this.calculateIframeOffset(contentWindow.parent, currentWindow);
637
+ x += parentOffset.x;
638
+ y += parentOffset.y;
639
+
640
+ const iframe = contentWindow.frameElement;
641
+ if (iframe) {
642
+ const rect = iframe.getBoundingClientRect();
643
+ x += rect.x;
644
+ y += rect.y;
645
+ }
646
+
647
+ return { x, y };
648
+ }
649
+
650
+ private extractDataAttributesAndConvertToDataset(attributes: Record<string, unknown>): Record<string, string> {
651
+ const camelCase = (key: string) =>
652
+ key
653
+ .replace(/^data-/, '')
654
+ .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
655
+
656
+ return Object.fromEntries(
657
+ Object.entries(attributes)
658
+ .filter(([key]) => key.startsWith('data-'))
659
+ .map(([key, value]) => [camelCase(key), String(value)])
660
+ );
661
+ };
662
+
663
+ private updateContextMenuPositions(): void {
664
+ if (this.contextMenuElements.length > 0) {
665
+ const firstMenuElement = this.contextMenuElements[0];
666
+ this.updateContextMenuPosition(firstMenuElement, this.rootPositionY, this.rootPositionX);
667
+
668
+ const subMenuElements = [...this.contextMenuElements].slice(1);
669
+ const documentDirection = this.getDocumentDirection();
670
+ subMenuElements.forEach((element) => {
671
+ const parentNode = this.getNodeByIdentifier(element.dataset.contextmenuParent);
672
+ const parentElement = this.getElementFromNode(parentNode);
673
+ const parentRect = parentElement.getBoundingClientRect();
674
+ const top = parentRect.top - 7;
675
+ const start = documentDirection === 'ltr' ? parentRect.right : parentRect.left;
676
+ this.updateContextMenuPosition(element, top, start);
677
+ });
678
+ }
679
+ }
680
+
681
+ private updateContextMenuPosition(element: HTMLDivElement, startPositionY: number, startPositionX: number): void {
682
+ const space = 10;
683
+ const offset = 5;
684
+ const direction = this.getDocumentDirection();
685
+ const dimensions = {
686
+ width: element.offsetWidth,
687
+ height: element.offsetHeight
688
+ };
689
+ const windowDimentions = {
690
+ width: document.documentElement.clientWidth,
691
+ height: document.documentElement.clientHeight,
692
+ };
693
+
694
+ let
695
+ start: number = 0,
696
+ top: number = 0;
697
+
698
+ top = startPositionY;
699
+ start = direction === 'ltr' ? startPositionX : windowDimentions.width - startPositionX;
700
+ const canPlaceBelow = (top + dimensions.height + space + offset < windowDimentions.height);
701
+ const canPlaceStart = (start + dimensions.width + space + offset < windowDimentions.width);
702
+
703
+ // adjusting the top position of the layer to fit it into the window frame
704
+ if (!canPlaceBelow) {
705
+ top = windowDimentions.height - dimensions.height - space;
706
+ } else {
707
+ top += offset;
708
+ }
709
+
710
+ // adjusting the start position of the layer to fit it into the window frame
711
+ if (!canPlaceStart) {
712
+ start = windowDimentions.width - dimensions.width - space;
713
+ } else {
714
+ start += offset;
715
+ }
716
+
717
+ element.style.top = Math.round(top) + 'px';
718
+ element.style.insetInlineStart = Math.round(start) + 'px';
719
+ }
720
+
721
+ private flattenMenuItems(nodes: MenuItemInterface[]): MenuItemInterface[] {
722
+ const items: MenuItemInterface[] = [];
723
+ for (const node of nodes) {
724
+ items.push(node);
725
+ if (node.childItems) {
726
+ items.push(...this.flattenMenuItems(node.childItems));
727
+ }
728
+ }
729
+
730
+ return items;
731
+ }
732
+
733
+ private getParralellNodesForNavigation(node: MenuItemInterface): MenuItemInterface[] {
734
+ const parentNode = this.getParentNode(node);
735
+ if (parentNode) {
736
+ return parentNode.childItems.filter(node => node.type !== 'divider');
737
+ }
738
+
739
+ return this.nodes.filter(node => node.type !== 'divider');
740
+ }
741
+
742
+ private getDocumentDirection(): string {
743
+ return document.querySelector('html').dir === 'rtl' ? 'rtl' : 'ltr';
744
+ }
745
+ }
746
+
747
+ declare global {
748
+ interface HTMLElementTagNameMap {
749
+ 'typo3-backend-context-menu': ContextMenuElement;
750
+ }
751
+ }