fix: menu contextuel en DOM natif (bypass Alpine)
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
This commit is contained in:
@@ -744,43 +744,36 @@ window._wsInitData = (function() {
|
|||||||
|
|
||||||
// ── Context menu ──
|
// ── Context menu ──
|
||||||
onContextMenu(ev) {
|
onContextMenu(ev) {
|
||||||
console.log('onContextMenu fired', ev.clientX, ev.clientY);
|
|
||||||
var el = ev.target.closest('[data-ws-id]');
|
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'));
|
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);
|
var node = this._findNodeById(this.displayTree, id);
|
||||||
if (!node) { this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; return; }
|
if (!node) { menu.style.display = 'none'; return; }
|
||||||
// Reassign whole object for Alpine reactivity
|
// Store in Alpine data for menu actions (ctxMenuOpenPage, etc.)
|
||||||
this.ctxMenu = {
|
this.ctxMenu = {visible:true, x:ev.clientX, y:ev.clientY, node:node, tags:[...(node.tags||[])]};
|
||||||
visible: true,
|
|
||||||
x: ev.clientX,
|
|
||||||
y: ev.clientY,
|
|
||||||
node: node,
|
|
||||||
tags: [...(node.tags||[])]
|
|
||||||
};
|
|
||||||
this.ctxTagAdding = false;
|
this.ctxTagAdding = false;
|
||||||
this.ctxTagExistingOpen = false;
|
this.ctxTagExistingOpen = false;
|
||||||
this.ctxNewTagColor = '#787774';
|
this.ctxNewTagColor = '#787774';
|
||||||
// Load available tags
|
|
||||||
this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) {
|
this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) {
|
||||||
return !(node.tags||[]).some(function(nt) { return nt.id === t.id; });
|
return !(node.tags||[]).some(function(nt) { return nt.id === t.id; });
|
||||||
});
|
});
|
||||||
// Reposition after DOM update
|
// DOM manipulation (bypasses Alpine reactivity issues)
|
||||||
var self = this;
|
menu.style.display = 'block';
|
||||||
self.$nextTick(function() {
|
menu.style.left = ev.clientX + 'px';
|
||||||
var menu = self.$el.querySelector('.ctx-menu');
|
menu.style.top = ev.clientY + 'px';
|
||||||
if (!menu) return;
|
// Reposition if offscreen
|
||||||
var rect = menu.getBoundingClientRect();
|
var rect = menu.getBoundingClientRect();
|
||||||
var x = self.ctxMenu.x;
|
var x = ev.clientX, y = ev.clientY;
|
||||||
var y = self.ctxMenu.y;
|
if (x + rect.width > window.innerWidth - 4) x = window.innerWidth - rect.width - 4;
|
||||||
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 (y + rect.height > window.innerHeight - 4) y = window.innerHeight - rect.height - 4;
|
if (x < 4) x = 4;
|
||||||
if (x < 4) x = 4;
|
if (y < 4) y = 4;
|
||||||
if (y < 4) y = 4;
|
menu.style.left = x + 'px';
|
||||||
// Reassign for reactivity
|
menu.style.top = y + 'px';
|
||||||
self.ctxMenu = {visible:true, x:x, y:y, node:self.ctxMenu.node, tags:self.ctxMenu.tags};
|
this.ctxMenu.x = x;
|
||||||
});
|
this.ctxMenu.y = y;
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Long-press (mobile context menu) ──
|
// ── Long-press (mobile context menu) ──
|
||||||
@@ -829,24 +822,24 @@ window._wsInitData = (function() {
|
|||||||
|
|
||||||
ctxMenuOpenPage() {
|
ctxMenuOpenPage() {
|
||||||
if (this.ctxMenu.node) this.openPage(this.ctxMenu.node.id);
|
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() {
|
ctxMenuOpenFolder() {
|
||||||
if (this.ctxMenu.node) this.navigateToFolder(this.ctxMenu.node.id);
|
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() {
|
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() {
|
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() {
|
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) {
|
ctxRemoveTag(tagId) {
|
||||||
@@ -1928,7 +1921,7 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
|||||||
<script>console.log('FLOWDECK v2.4.6 — ws-split about to render, _wsInitData exists:', typeof window._wsInitData !== 'undefined');</script>
|
<script>console.log('FLOWDECK v2.4.6 — ws-split about to render, _wsInitData exists:', typeof window._wsInitData !== 'undefined');</script>
|
||||||
|
|
||||||
<div class="ws-split"
|
<div class="ws-split"
|
||||||
@contextmenu.prevent="_wsInitData.onContextMenu($event); ctxMenu = _wsInitData.ctxMenu"
|
@contextmenu.prevent="_wsInitData.onContextMenu($event)"
|
||||||
@click.self="ctxMenu.visible = false"
|
@click.self="ctxMenu.visible = false"
|
||||||
@touchstart="onTouchStart($event)"
|
@touchstart="onTouchStart($event)"
|
||||||
@touchend="onTouchEnd($event)"
|
@touchend="onTouchEnd($event)"
|
||||||
@@ -2357,10 +2350,9 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
|||||||
</div><!-- /ws-main -->
|
</div><!-- /ws-main -->
|
||||||
|
|
||||||
<!-- Context menu -->
|
<!-- Context menu -->
|
||||||
<div class="ctx-menu"
|
<div class="ctx-menu" style="display:none"
|
||||||
:style="{ left: ctxMenu.x + 'px', top: ctxMenu.y + 'px', display: ctxMenu.visible ? 'block' : 'none' }"
|
@click.outside="document.querySelector('.ctx-menu').style.display='none'"
|
||||||
@click.outside="ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}"
|
@keydown.escape="document.querySelector('.ctx-menu').style.display='none'"
|
||||||
@keydown.escape="ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}"
|
|
||||||
x-transition>
|
x-transition>
|
||||||
<div class="ctx-item" @click="ctxMenuOpenPage()">📄 Open</div>
|
<div class="ctx-item" @click="ctxMenuOpenPage()">📄 Open</div>
|
||||||
<div class="ctx-item" x-show="ctxMenu.node && ctxMenu.node.is_folder" @click="ctxMenuOpenFolder()">📂 Open folder</div>
|
<div class="ctx-item" x-show="ctxMenu.node && ctxMenu.node.is_folder" @click="ctxMenuOpenFolder()">📂 Open folder</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user