From d288b414c6b42c3dab37f51e77f9173c5b76e713 Mon Sep 17 00:00:00 2001 From: Julien Tison Date: Fri, 27 Mar 2026 15:22:12 +0100 Subject: [PATCH] feat: add functionality to close popover on button click in contextual menu --- .../widgets/nodeTree/contextualMenu.html.twig | 10 +++ .../RzNodeTreeContextualMenu.ts | 78 ++++++++++++------- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/lib/RoadizRozierBundle/templates/widgets/nodeTree/contextualMenu.html.twig b/lib/RoadizRozierBundle/templates/widgets/nodeTree/contextualMenu.html.twig index 821f3c9e1..7cc4b8dc2 100644 --- a/lib/RoadizRozierBundle/templates/widgets/nodeTree/contextualMenu.html.twig +++ b/lib/RoadizRozierBundle/templates/widgets/nodeTree/contextualMenu.html.twig @@ -34,6 +34,7 @@ 'type': 'button', 'command': '--move-first', 'commandfor': 'DYNAMICALLY_SET_BY_JS', + 'data-close-popover-on-click': 'true', } }) }} {{ rz_dropdown_menu.item({ @@ -43,6 +44,7 @@ 'type': 'button', 'command': '--move-last', 'commandfor': 'DYNAMICALLY_SET_BY_JS', + 'data-close-popover-on-click': 'true', } }) }} {%- endif -%} @@ -59,6 +61,7 @@ 'type': 'button', 'command': '--copy', 'commandfor': 'DYNAMICALLY_SET_BY_JS', + 'data-close-popover-on-click': 'true', } }) }} {{ rz_dropdown_menu.item({ @@ -68,6 +71,7 @@ 'type': 'button', 'command': '--paste-inside', 'commandfor': 'DYNAMICALLY_SET_BY_JS', + 'data-close-popover-on-click': 'true', disabled: 'true', } }) }} @@ -78,6 +82,7 @@ 'type': 'button', 'command': '--paste-after', 'commandfor': 'DYNAMICALLY_SET_BY_JS', + 'data-close-popover-on-click': 'true', disabled: 'true', } }) }} @@ -88,6 +93,7 @@ 'type': 'button', 'command': '--duplicate', 'commandfor': 'DYNAMICALLY_SET_BY_JS', + 'data-close-popover-on-click': 'true', } }) }} @@ -106,6 +112,7 @@ 'data-status': "visible", 'data-value': "0", 'type': "button", + 'data-close-popover-on-click': 'true', } }) }} {% else %} @@ -119,6 +126,7 @@ 'data-status': "visible", 'data-value': "1", 'type': "button", + 'data-close-popover-on-click': 'true', } }) }} {% endif %} @@ -132,6 +140,7 @@ 'data-status': "status", 'data-value': "publish", 'type': "button", + 'data-close-popover-on-click': 'true', } }) }} {% elseif node.published and workflow_can(node, 'reject') %} @@ -144,6 +153,7 @@ 'data-status': "status", 'data-value': "reject", 'type': "button", + 'data-close-popover-on-click': 'true', } }) }} {% endif %} diff --git a/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts b/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts index c0b328b02..63f89c69e 100644 --- a/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts +++ b/lib/Rozier/app/custom-elements/RzNodeTreeContextualMenu.ts @@ -6,6 +6,22 @@ type UpdatePayloadDict = Record export default class RzNodeTreeContextualMenu extends HTMLElement { popoverInstance: Popover | null = null + private hidePopover() { + const popover = this.popoverElement as unknown as { + hidePopover?: () => void + } | null + popover?.hidePopover?.() + } + + private closePopoverIfRequested(source: HTMLElement | null) { + if (!source) return + if (source.getAttribute('data-close-popover-on-click') !== 'true') { + return + } + + this.hidePopover() + } + static get observedAttributes() { return [...ATTRIBUTES_OPTIONS] } @@ -163,28 +179,32 @@ export default class RzNodeTreeContextualMenu extends HTMLElement { window.dispatchEvent(new CustomEvent('requestLoaderShow')) - switch (event.command) { - case '--duplicate': - await this.duplicateNode() - break - case '--copy': - this.copyNode() - break - case '--paste-inside': - await this.pasteInsideNode() - break - case '--paste-after': - await this.pasteAfterNode() - break - case '--update-status': - await this.changeStatus(event) - break - case '--move-first': - await this.moveNodeToPosition('first', event) - break - case '--move-last': - await this.moveNodeToPosition('last', event) - break + try { + switch (event.command) { + case '--duplicate': + await this.duplicateNode() + break + case '--copy': + this.copyNode() + break + case '--paste-inside': + await this.pasteInsideNode() + break + case '--paste-after': + await this.pasteAfterNode() + break + case '--update-status': + await this.changeStatus(event) + break + case '--move-first': + await this.moveNodeToPosition('first', event) + break + case '--move-last': + await this.moveNodeToPosition('last', event) + break + } + } finally { + this.closePopoverIfRequested(event.source) } } @@ -283,14 +303,18 @@ export default class RzNodeTreeContextualMenu extends HTMLElement { window.dispatchEvent(new CustomEvent('requestLoaderShow')) const nodeTreeElement = (event.currentTarget as HTMLElement).closest( - '.nodetree-element', + '.nodetree-element, .rz-tree__item', ) - const parentNodeId = parseInt( + const parentNodeTreeElement = nodeTreeElement - .closest('ul') - ?.getAttribute('data-parent-node-id') || '', - ) + ?.closest('ul') + ?.getAttribute('data-parent-node-id') ?? + nodeTreeElement?.closest('ul')?.getAttribute('data-parent-id') + + const parentNodeId = parentNodeTreeElement + ? parseInt(parentNodeTreeElement) + : null const postData: UpdatePayloadDict = { csrfToken: window.RozierConfig.ajaxToken,