From 0ad1a699943f93c47e75a8b52e8b2e9cfe3664eb Mon Sep 17 00:00:00 2001 From: bruno Date: Mon, 13 Jul 2026 10:33:52 -0400 Subject: [PATCH] fix: menu contextuel en DOM natif (bypass Alpine) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alpine ne détecte pas les changements sur ctxMenu (objet imbriqué hors du scope _wsInitData dans le DOM). Le :style et x-show restent bloqués sur les valeurs initiales. Solution radicale: manipulation DOM directe - onContextMenu: menu.style.display='block' + position left/top - ctxMenuOpen*/Rename/Duplicate/Delete: menu.style.display='none' - @click.outside/@keydown.escape: DOM direct aussi - ctx-menu HTML: style="display:none" initial --- app/templates/local_workspace.html | 68 +++++++++++++----------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/app/templates/local_workspace.html b/app/templates/local_workspace.html index 4a0af13..f6dd1de 100644 --- a/app/templates/local_workspace.html +++ b/app/templates/local_workspace.html @@ -744,43 +744,36 @@ window._wsInitData = (function() { // ── Context menu ── onContextMenu(ev) { - console.log('onContextMenu fired', ev.clientX, ev.clientY); var el = ev.target.closest('[data-ws-id]'); - if (!el) { this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; return; } + var menu = document.querySelector('.ctx-menu'); + if (!el || !menu) { if (menu) menu.style.display = 'none'; return; } var id = parseInt(el.getAttribute('data-ws-id')); - if (!id) { this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; return; } + if (!id) { menu.style.display = 'none'; return; } var node = this._findNodeById(this.displayTree, id); - if (!node) { this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; return; } - // Reassign whole object for Alpine reactivity - this.ctxMenu = { - visible: true, - x: ev.clientX, - y: ev.clientY, - node: node, - tags: [...(node.tags||[])] - }; + if (!node) { menu.style.display = 'none'; return; } + // Store in Alpine data for menu actions (ctxMenuOpenPage, etc.) + this.ctxMenu = {visible:true, x:ev.clientX, y:ev.clientY, node:node, tags:[...(node.tags||[])]}; this.ctxTagAdding = false; this.ctxTagExistingOpen = false; this.ctxNewTagColor = '#787774'; - // Load available tags this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) { return !(node.tags||[]).some(function(nt) { return nt.id === t.id; }); }); - // Reposition after DOM update - var self = this; - self.$nextTick(function() { - var menu = self.$el.querySelector('.ctx-menu'); - if (!menu) return; - var rect = menu.getBoundingClientRect(); - var x = self.ctxMenu.x; - var y = self.ctxMenu.y; - if (x + rect.width > window.innerWidth - 4) x = window.innerWidth - rect.width - 4; - if (y + rect.height > window.innerHeight - 4) y = window.innerHeight - rect.height - 4; - if (x < 4) x = 4; - if (y < 4) y = 4; - // Reassign for reactivity - self.ctxMenu = {visible:true, x:x, y:y, node:self.ctxMenu.node, tags:self.ctxMenu.tags}; - }); + // DOM manipulation (bypasses Alpine reactivity issues) + menu.style.display = 'block'; + menu.style.left = ev.clientX + 'px'; + menu.style.top = ev.clientY + 'px'; + // Reposition if offscreen + var rect = menu.getBoundingClientRect(); + var x = ev.clientX, y = ev.clientY; + if (x + rect.width > window.innerWidth - 4) x = window.innerWidth - rect.width - 4; + if (y + rect.height > window.innerHeight - 4) y = window.innerHeight - rect.height - 4; + if (x < 4) x = 4; + if (y < 4) y = 4; + menu.style.left = x + 'px'; + menu.style.top = y + 'px'; + this.ctxMenu.x = x; + this.ctxMenu.y = y; }, // ── Long-press (mobile context menu) ── @@ -829,24 +822,24 @@ window._wsInitData = (function() { ctxMenuOpenPage() { if (this.ctxMenu.node) this.openPage(this.ctxMenu.node.id); - this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; + document.querySelector('.ctx-menu').style.display = 'none'; }, ctxMenuOpenFolder() { if (this.ctxMenu.node) this.navigateToFolder(this.ctxMenu.node.id); - this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; + document.querySelector('.ctx-menu').style.display = 'none'; }, ctxMenuRename() { - if (this.ctxMenu.node) { this.startRename(this.ctxMenu.node); this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; } + if (this.ctxMenu.node) { this.startRename(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; } }, ctxMenuDuplicate() { - if (this.ctxMenu.node) { this.duplicateItem(this.ctxMenu.node); this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; } + if (this.ctxMenu.node) { this.duplicateItem(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; } }, ctxMenuDelete() { - if (this.ctxMenu.node) { this.deleteWithUndo(this.ctxMenu.node); this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; } + if (this.ctxMenu.node) { this.deleteWithUndo(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; } }, ctxRemoveTag(tagId) { @@ -1928,7 +1921,7 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
-