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.
- package/.github/workflows/sync-and-publish.yml +265 -0
- package/.last-synced-tag +1 -0
- package/LICENSE.txt +339 -0
- package/README-TYPO3.md +166 -0
- package/README.md +28 -0
- package/adminpanel/admin-panel.ts +375 -0
- package/adminpanel/modules/cache.ts +27 -0
- package/adminpanel/modules/preview.ts +75 -0
- package/backend/action-button/abstract-action.ts +22 -0
- package/backend/action-button/deferred-action.ts +41 -0
- package/backend/action-button/immediate-action.ts +31 -0
- package/backend/action-dispatcher.ts +104 -0
- package/backend/ajax-data-handler/message-interface.ts +18 -0
- package/backend/ajax-data-handler/response-interface.ts +20 -0
- package/backend/ajax-data-handler.ts +214 -0
- package/backend/backend-exception.ts +22 -0
- package/backend/broadcast-message.ts +50 -0
- package/backend/broadcast-service.ts +52 -0
- package/backend/browse-database.ts +38 -0
- package/backend/clear-cache.ts +88 -0
- package/backend/clipboard-panel.ts +240 -0
- package/backend/close-current-window.ts +2 -0
- package/backend/code-editor/autocomplete/completion-result.ts +105 -0
- package/backend/code-editor/autocomplete/ts-code-completion.ts +142 -0
- package/backend/code-editor/autocomplete/ts-parser.ts +485 -0
- package/backend/code-editor/autocomplete/ts-ref.ts +164 -0
- package/backend/code-editor/element/code-mirror-element.ts +282 -0
- package/backend/code-editor/language/typoscript.ts +181 -0
- package/backend/code-editor/stream-parser/typoscript.ts +1600 -0
- package/backend/color-picker.ts +158 -0
- package/backend/color-scheme-switch.ts +179 -0
- package/backend/column-selector-button.ts +302 -0
- package/backend/context-help.ts +68 -0
- package/backend/context-menu-actions.ts +322 -0
- package/backend/context-menu.ts +751 -0
- package/backend/copy-to-clipboard.ts +79 -0
- package/backend/date-time-picker.ts +270 -0
- package/backend/document-save-actions.ts +159 -0
- package/backend/drag-tooltip.ts +285 -0
- package/backend/drag-uploader.ts +914 -0
- package/backend/element/alert-element.ts +116 -0
- package/backend/element/dispatch-modal-button.ts +50 -0
- package/backend/element/draggable-resizable-element.ts +249 -0
- package/backend/element/editable-page-title.ts +306 -0
- package/backend/element/icon-element.ts +84 -0
- package/backend/element/immediate-action-element.ts +89 -0
- package/backend/element/pagination.ts +59 -0
- package/backend/element/progress-bar-element.ts +172 -0
- package/backend/element/progress-tracker-element.ts +119 -0
- package/backend/element/pseudo-button.ts +58 -0
- package/backend/element/spinner-element.ts +51 -0
- package/backend/element/thumbnail-element.ts +80 -0
- package/backend/element-browser.ts +182 -0
- package/backend/enum/data-transfer-types.ts +21 -0
- package/backend/enum/icon-types.ts +31 -0
- package/backend/enum/key-types.ts +27 -0
- package/backend/enum/severity.ts +20 -0
- package/backend/enum/viewport/scaffold-identifier.ts +24 -0
- package/backend/event/client-request.ts +25 -0
- package/backend/event/consumable.ts +18 -0
- package/backend/event/consumer-scope.ts +54 -0
- package/backend/event/event-dispatcher.ts +26 -0
- package/backend/event/interaction-request-assignment.ts +24 -0
- package/backend/event/interaction-request-map.ts +75 -0
- package/backend/event/interaction-request.ts +48 -0
- package/backend/event/trigger-request.ts +56 -0
- package/backend/form/submit-interceptor.ts +66 -0
- package/backend/form-engine/container/files-control-container.ts +716 -0
- package/backend/form-engine/container/flex-form-container-container.ts +166 -0
- package/backend/form-engine/container/flex-form-section-container.ts +210 -0
- package/backend/form-engine/container/inline-control-container.ts +1230 -0
- package/backend/form-engine/container/site-language-container.ts +616 -0
- package/backend/form-engine/element/abstract-sortable-select-items.ts +186 -0
- package/backend/form-engine/element/category-element.ts +160 -0
- package/backend/form-engine/element/color-element.ts +81 -0
- package/backend/form-engine/element/datetime-element.ts +66 -0
- package/backend/form-engine/element/extra/char-counter.ts +112 -0
- package/backend/form-engine/element/extra/select-box-filter.ts +85 -0
- package/backend/form-engine/element/folder-element.ts +66 -0
- package/backend/form-engine/element/group-element.ts +47 -0
- package/backend/form-engine/element/json-element.ts +52 -0
- package/backend/form-engine/element/link-element.ts +121 -0
- package/backend/form-engine/element/mfa-info-element.ts +169 -0
- package/backend/form-engine/element/modifier/resizable.ts +27 -0
- package/backend/form-engine/element/modifier/tabbable.ts +29 -0
- package/backend/form-engine/element/online-media-form-element.ts +65 -0
- package/backend/form-engine/element/password-element.ts +71 -0
- package/backend/form-engine/element/select-multiple-side-by-side-element.ts +83 -0
- package/backend/form-engine/element/select-single-element.ts +82 -0
- package/backend/form-engine/element/select-tree-element.ts +148 -0
- package/backend/form-engine/element/select-tree-toolbar.ts +118 -0
- package/backend/form-engine/element/select-tree.ts +210 -0
- package/backend/form-engine/element/slug-element.ts +243 -0
- package/backend/form-engine/element/suggest/result-container.ts +118 -0
- package/backend/form-engine/element/suggest/result-item.ts +90 -0
- package/backend/form-engine/element/table-permission-element.ts +101 -0
- package/backend/form-engine/element/table-wizard-element.ts +427 -0
- package/backend/form-engine/element/text-element.ts +52 -0
- package/backend/form-engine/element/text-table-element.ts +31 -0
- package/backend/form-engine/field-control/add-record.ts +40 -0
- package/backend/form-engine/field-control/edit-popup.ts +64 -0
- package/backend/form-engine/field-control/insert-clipboard.ts +50 -0
- package/backend/form-engine/field-control/link-popup.ts +53 -0
- package/backend/form-engine/field-control/list-module.ts +40 -0
- package/backend/form-engine/field-control/password-generator.ts +102 -0
- package/backend/form-engine/field-control/reset-selection.ts +48 -0
- package/backend/form-engine/field-wizard/localization-state-selector.ts +67 -0
- package/backend/form-engine/field-wizard/value-picker.ts +123 -0
- package/backend/form-engine/field-wizard/value-slider.ts +86 -0
- package/backend/form-engine/inline-relation/ajax-dispatcher.ts +124 -0
- package/backend/form-engine/inline-relation/inline-response-interface.ts +41 -0
- package/backend/form-engine-link-browser-adapter.ts +129 -0
- package/backend/form-engine-review.ts +155 -0
- package/backend/form-engine-suggest.ts +133 -0
- package/backend/form-engine-validation.ts +686 -0
- package/backend/form-engine.ts +1391 -0
- package/backend/global-event-handler.ts +252 -0
- package/backend/grid-editor.ts +1077 -0
- package/backend/hashing/md5.ts +252 -0
- package/backend/helper.ts +24 -0
- package/backend/hotkeys/hotkey-storage.ts +63 -0
- package/backend/hotkeys.ts +216 -0
- package/backend/icons.ts +236 -0
- package/backend/image-manipulation.ts +885 -0
- package/backend/info-window.ts +46 -0
- package/backend/input/clearable.ts +128 -0
- package/backend/input/index.d.ts +8 -0
- package/backend/java-script-module-import-event-handler.ts +19 -0
- package/backend/key-bindings.ts +42 -0
- package/backend/layout-module/drag-drop.ts +359 -0
- package/backend/layout-module/paste.ts +165 -0
- package/backend/link-browser.ts +89 -0
- package/backend/live-search/element/backend-search.ts +34 -0
- package/backend/live-search/element/hint.ts +33 -0
- package/backend/live-search/element/provider/default-result-item.ts +52 -0
- package/backend/live-search/element/provider/page-provider-result-item.ts +52 -0
- package/backend/live-search/element/result/item/action/action-container.ts +135 -0
- package/backend/live-search/element/result/item/action/action.ts +56 -0
- package/backend/live-search/element/result/item/item-container.ts +224 -0
- package/backend/live-search/element/result/item/item.ts +74 -0
- package/backend/live-search/element/result/result-container.ts +110 -0
- package/backend/live-search/element/result/result-detail-container.ts +50 -0
- package/backend/live-search/element/result/result-pagination.ts +122 -0
- package/backend/live-search/element/search-option-item.ts +70 -0
- package/backend/live-search/live-search-configurator.ts +54 -0
- package/backend/live-search/live-search-shortcut.ts +29 -0
- package/backend/live-search/result-types/backend-module-result-type.ts +8 -0
- package/backend/live-search/result-types/default-result-type.ts +21 -0
- package/backend/live-search/result-types/page-result-type.ts +23 -0
- package/backend/localization.ts +390 -0
- package/backend/login-refresh.ts +525 -0
- package/backend/login.ts +179 -0
- package/backend/mail-link-handler.ts +45 -0
- package/backend/modal-interface.ts +19 -0
- package/backend/modal.ts +685 -0
- package/backend/module/iframe.ts +125 -0
- package/backend/module/router.ts +275 -0
- package/backend/module-menu.ts +550 -0
- package/backend/module.ts +133 -0
- package/backend/multi-record-selection-action.ts +44 -0
- package/backend/multi-record-selection-delete-action.ts +108 -0
- package/backend/multi-record-selection-edit-action.ts +56 -0
- package/backend/multi-record-selection.ts +468 -0
- package/backend/multi-step-wizard.ts +568 -0
- package/backend/new-content-element-wizard-button.ts +51 -0
- package/backend/new-multiple-pages.ts +108 -0
- package/backend/new-record-wizard.ts +680 -0
- package/backend/notification.ts +366 -0
- package/backend/offset.ts +40 -0
- package/backend/online-media.ts +110 -0
- package/backend/page-actions.ts +91 -0
- package/backend/page-link-handler.ts +51 -0
- package/backend/pagetsconfig/pagetsconfig-includes.ts +77 -0
- package/backend/popover.ts +165 -0
- package/backend/record-download-button.ts +104 -0
- package/backend/record-link-handler.ts +31 -0
- package/backend/record-search.ts +44 -0
- package/backend/recordlist.ts +230 -0
- package/backend/resource/resource.ts +35 -0
- package/backend/security/element/csp-reports.ts +406 -0
- package/backend/security/element/sudo-mode.ts +249 -0
- package/backend/security/sudo-mode-interceptor.ts +19 -0
- package/backend/settings/editor/editable-setting.ts +231 -0
- package/backend/settings/editor.ts +451 -0
- package/backend/settings/enum/settings-mode.enum.ts +24 -0
- package/backend/settings/type/base.ts +200 -0
- package/backend/settings/type/bool.ts +56 -0
- package/backend/settings/type/color.ts +64 -0
- package/backend/settings/type/int.ts +85 -0
- package/backend/settings/type/number.ts +61 -0
- package/backend/settings/type/page.ts +100 -0
- package/backend/settings/type/string.ts +77 -0
- package/backend/settings/type/stringlist.ts +87 -0
- package/backend/severity.ts +88 -0
- package/backend/site-inline-actions.ts +32 -0
- package/backend/sortable-table.ts +208 -0
- package/backend/storage/abstract-client-storage.ts +76 -0
- package/backend/storage/browser-session.ts +28 -0
- package/backend/storage/client.ts +28 -0
- package/backend/storage/module-state-storage.ts +118 -0
- package/backend/storage/persistent.ts +204 -0
- package/backend/switch-user.ts +82 -0
- package/backend/tabs.ts +72 -0
- package/backend/telephone-link-handler.ts +40 -0
- package/backend/tests/backend/storage/browser-storage-test.ts +87 -0
- package/backend/tests/backend/storage/client-test.ts +87 -0
- package/backend/tests/backend/utility/dom-helper-test.ts +51 -0
- package/backend/tests/backend-exception-test.ts +28 -0
- package/backend/tests/element/immediate-action-element-test.ts +81 -0
- package/backend/tests/form-engine-validation-test.ts +304 -0
- package/backend/tests/grid-editor-test.ts +29 -0
- package/backend/tests/hashing/md5Test.ts +28 -0
- package/backend/tests/icons-test.ts +23 -0
- package/backend/tests/notification-test.ts +161 -0
- package/backend/tests/popover-test.ts +83 -0
- package/backend/toolbar/clear-cache-menu.ts +88 -0
- package/backend/toolbar/live-search.ts +284 -0
- package/backend/toolbar/shortcut-menu.ts +254 -0
- package/backend/toolbar/system-information-menu.ts +161 -0
- package/backend/toolbar.ts +41 -0
- package/backend/tree/file-storage-browser.ts +194 -0
- package/backend/tree/file-storage-tree-container.ts +465 -0
- package/backend/tree/file-storage-tree.ts +42 -0
- package/backend/tree/page-browser.ts +272 -0
- package/backend/tree/page-tree-element.ts +602 -0
- package/backend/tree/page-tree.ts +152 -0
- package/backend/tree/tree-module-state.ts +159 -0
- package/backend/tree/tree-node-toggle.ts +31 -0
- package/backend/tree/tree-node.ts +97 -0
- package/backend/tree/tree-toolbar.ts +117 -0
- package/backend/tree/tree.ts +1595 -0
- package/backend/url-link-handler.ts +36 -0
- package/backend/user-pass-login.ts +255 -0
- package/backend/user-settings-manager.ts +189 -0
- package/backend/utility/collapse-state-persister.ts +205 -0
- package/backend/utility/collapse-state-search.ts +183 -0
- package/backend/utility/dom-helper.ts +91 -0
- package/backend/utility/drag-drop-utility.ts +47 -0
- package/backend/utility/message-utility.ts +45 -0
- package/backend/utility/top-level-module-import.ts +32 -0
- package/backend/utility.ts +194 -0
- package/backend/viewport/abstract-container.ts +22 -0
- package/backend/viewport/content-container.ts +98 -0
- package/backend/viewport/loader.ts +28 -0
- package/backend/viewport/navigation-container.ts +119 -0
- package/backend/viewport/resizable-navigation.ts +176 -0
- package/backend/viewport/toolbar.ts +27 -0
- package/backend/viewport/topbar.ts +39 -0
- package/backend/viewport.ts +46 -0
- package/backend/window-manager.ts +84 -0
- package/backend/wizard/move-content-element.ts +107 -0
- package/backend/wizard/move-page.ts +74 -0
- package/backend/wizard.ts +331 -0
- package/belog/backend-log.ts +88 -0
- package/beuser/backend-user-listing.ts +40 -0
- package/beuser/permissions.ts +291 -0
- package/core/ajax/ajax-request-types.ts +15 -0
- package/core/ajax/ajax-request.ts +202 -0
- package/core/ajax/ajax-response.ts +57 -0
- package/core/ajax/input-transformer.ts +92 -0
- package/core/ajax/simple-response-interface.ts +5 -0
- package/core/authentication/mfa-provider/totp.ts +54 -0
- package/core/cache/dedupe-async-task.ts +95 -0
- package/core/directive/markdown.ts +82 -0
- package/core/directive/nl2br.ts +16 -0
- package/core/document-service.ts +35 -0
- package/core/event/debounce-event.ts +40 -0
- package/core/event/event-interface.ts +24 -0
- package/core/event/regular-event.ts +58 -0
- package/core/event/request-animation-frame-event.ts +42 -0
- package/core/event/throttle-event.ts +54 -0
- package/core/java-script-item-handler.ts +29 -0
- package/core/java-script-item-processor.ts +171 -0
- package/core/lit-helper.ts +126 -0
- package/core/literals.ts +17 -0
- package/core/referrer-refresh.ts +2 -0
- package/core/security-utility.ts +89 -0
- package/core/tests/ajax/ajax-request-test.ts +290 -0
- package/core/tests/ajax/input-transformer-test.ts +64 -0
- package/core/tests/cache/dedupe-async-task-test.ts +152 -0
- package/core/tests/event/throttle-event-test.ts +47 -0
- package/core/tests/literals-test.ts +24 -0
- package/core/tests/security-utility-test.ts +50 -0
- package/core/utility/types.ts +9 -0
- package/dashboard/chart-initializer.ts +136 -0
- package/dashboard/dashboard.ts +1487 -0
- package/extensionmanager/distribution-image.ts +55 -0
- package/extensionmanager/main.ts +291 -0
- package/extensionmanager/repository.ts +195 -0
- package/extensionmanager/update.ts +118 -0
- package/extensionmanager/upload-form.ts +42 -0
- package/filelist/browse-files.ts +148 -0
- package/filelist/browse-folders.ts +94 -0
- package/filelist/context-menu-actions.ts +335 -0
- package/filelist/create-folder.ts +69 -0
- package/filelist/file-delete.ts +69 -0
- package/filelist/file-list-actions.ts +132 -0
- package/filelist/file-list-dragdrop.ts +139 -0
- package/filelist/file-list-rename-handler.ts +128 -0
- package/filelist/file-list-transfer-handler.ts +138 -0
- package/filelist/file-list.ts +396 -0
- package/filelist/file-replace.ts +37 -0
- package/filelist/linkbrowser-file-handler.ts +94 -0
- package/filelist/linkbrowser-folder-handler.ts +74 -0
- package/filelist/rename-file.ts +109 -0
- package/form/backend/form-editor/component/property-grid-editor.ts +340 -0
- package/form/backend/form-editor/core.ts +2104 -0
- package/form/backend/form-editor/helper.ts +226 -0
- package/form/backend/form-editor/inspector-component.ts +2323 -0
- package/form/backend/form-editor/mediator.ts +941 -0
- package/form/backend/form-editor/modals-component.ts +433 -0
- package/form/backend/form-editor/stage-component.ts +908 -0
- package/form/backend/form-editor/tree-component.ts +548 -0
- package/form/backend/form-editor/view-model.ts +1343 -0
- package/form/backend/form-editor.ts +846 -0
- package/form/backend/form-manager/main.ts +39 -0
- package/form/backend/form-manager/view-model.ts +789 -0
- package/form/backend/form-manager.ts +180 -0
- package/form/backend/helper.ts +60 -0
- package/form/frontend/date-picker.ts +38 -0
- package/frontend/default_frontend.ts +82 -0
- package/impexp/context-menu-actions.ts +50 -0
- package/impexp/import-export.ts +62 -0
- package/indexed-search/pagination.ts +26 -0
- package/indexed-search/statistics.ts +50 -0
- package/info/translation-status.ts +48 -0
- package/install/ajax/ajax-queue.ts +83 -0
- package/install/init-install.ts +2 -0
- package/install/init-installer.ts +2 -0
- package/install/install.ts +25 -0
- package/install/installer.ts +458 -0
- package/install/message-interface.ts +18 -0
- package/install/module/abstract-inline-module.ts +27 -0
- package/install/module/abstract-interactable-module.ts +107 -0
- package/install/module/environment/environment-check.ts +92 -0
- package/install/module/environment/folder-structure.ts +145 -0
- package/install/module/environment/image-processing.ts +141 -0
- package/install/module/environment/mail-test.ts +127 -0
- package/install/module/environment/php-info.ts +50 -0
- package/install/module/environment/system-information.ts +50 -0
- package/install/module/maintenance/cache.ts +59 -0
- package/install/module/maintenance/clear-tables.ts +134 -0
- package/install/module/maintenance/clear-typo3temp-files.ts +137 -0
- package/install/module/maintenance/create-admin.ts +126 -0
- package/install/module/maintenance/database-analyzer.ts +266 -0
- package/install/module/maintenance/dump-autoload.ts +58 -0
- package/install/module/maintenance/language-packs.ts +338 -0
- package/install/module/maintenance/reset-backend-user-uc.ts +58 -0
- package/install/module/password-strength.ts +56 -0
- package/install/module/settings/change-install-tool-password.ts +101 -0
- package/install/module/settings/extension-configuration.ts +224 -0
- package/install/module/settings/features.ts +102 -0
- package/install/module/settings/local-configuration.ts +316 -0
- package/install/module/settings/presets.ts +147 -0
- package/install/module/settings/system-maintainer.ts +111 -0
- package/install/module/upgrade/core-update.ts +255 -0
- package/install/module/upgrade/extension-compat-tester.ts +195 -0
- package/install/module/upgrade/extension-scanner.ts +381 -0
- package/install/module/upgrade/tca-ext-tables-check.ts +93 -0
- package/install/module/upgrade/tca-migrations-check.ts +101 -0
- package/install/module/upgrade/upgrade-docs.ts +310 -0
- package/install/module/upgrade/upgrade-wizards.ts +612 -0
- package/install/renderable/clearable.ts +113 -0
- package/install/renderable/flash-message.ts +64 -0
- package/install/renderable/info-box.ts +66 -0
- package/install/renderable/language-packs.ts +304 -0
- package/install/renderable/offset-group.ts +38 -0
- package/install/renderable/severity.ts +49 -0
- package/install/renderable/wrap-group.ts +36 -0
- package/install/router.ts +468 -0
- package/linkvalidator/linkvalidator-modal.ts +45 -0
- package/linkvalidator/linkvalidator.ts +101 -0
- package/lowlevel/query-generator.ts +132 -0
- package/lowlevel/reference-index.ts +44 -0
- package/opendocs/toolbar/opendocs-menu.ts +117 -0
- package/package.json +34 -0
- package/recycler/recycler.ts +539 -0
- package/redirects/event-handler.ts +108 -0
- package/redirects/form-engine-evaluation.ts +34 -0
- package/redirects/redirects-module.ts +58 -0
- package/rte-ckeditor/ckeditor5.ts +402 -0
- package/rte-ckeditor/css-prefixer.ts +94 -0
- package/rte-ckeditor/legacy/ckeditor5-bundle.ts +17 -0
- package/rte-ckeditor/legacy/ckeditor5-inspector.ts +2 -0
- package/rte-ckeditor/plugin/typo3-link.ts +810 -0
- package/rte-ckeditor/plugin/whitespace.ts +114 -0
- package/rte-ckeditor/rte-link-browser.ts +112 -0
- package/rte-ckeditor/tests/css-prefixer-test.ts +314 -0
- package/scheduler/scheduler-add-group.ts +119 -0
- package/scheduler/scheduler-editable-group-name.ts +307 -0
- package/scheduler/scheduler-sortable-groups.ts +59 -0
- package/scheduler/scheduler.ts +466 -0
- package/setup/setup-module.ts +122 -0
- package/styleguide/checkbox-demo.ts +5 -0
- package/styleguide/element/theme-switcher-element.ts +101 -0
- package/styleguide/filter.ts +22 -0
- package/styleguide/find-icons.ts +78 -0
- package/styleguide/processing-indicator.ts +78 -0
- package/styleguide/render-notifications.ts +64 -0
- package/sys-note/element/delete-button.ts +78 -0
- package/tstemplate/constant-editor.ts +64 -0
- package/tstemplate/information-module.ts +31 -0
- package/tstemplate/template-analyzer.ts +77 -0
- package/viewpage/main.ts +253 -0
- package/workspaces/backend.ts +979 -0
- package/workspaces/preview.ts +232 -0
- package/workspaces/renderable/comment-view.ts +79 -0
- package/workspaces/renderable/diff-view.ts +58 -0
- package/workspaces/renderable/history-view.ts +97 -0
- package/workspaces/renderable/record-information.ts +118 -0
- package/workspaces/renderable/record-table.ts +355 -0
- package/workspaces/renderable/send-to-stage-form.ts +108 -0
- package/workspaces/toolbar/workspaces-menu.ts +190 -0
- package/workspaces/utility/icon-helper.ts +57 -0
- 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
|
+
}
|