fix: v4.6.0 - cross-day drag: releasePointerCapture + bounding-rect day detection (no elementFromPoint)
Some checks failed
Build & Push Docker / build (push) Has been cancelled
Some checks failed
Build & Push Docker / build (push) Has been cancelled
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
<header class="header">
|
||||
<div class="header-left">
|
||||
<h1 class="header-title">Scénář Creator</h1>
|
||||
<span class="header-version">v4.5</span>
|
||||
<span class="header-version">v4.6</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<label class="btn btn-secondary btn-sm" id="importJsonBtn">
|
||||
|
||||
@@ -300,16 +300,30 @@ const Canvas = {
|
||||
return minutes * this.pxPerMinute;
|
||||
},
|
||||
|
||||
// Native pointer drag — creates a ghost on document.body so clipping never happens
|
||||
// Native pointer drag — ghost on document.body (no overflow/clipping issues)
|
||||
// Day detection uses pre-captured bounding rects, NOT elementFromPoint (unreliable with ghost)
|
||||
_startPointerDrag(e, el, block) {
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const elRect = el.getBoundingClientRect();
|
||||
const startMin = this.parseTime(block.start);
|
||||
const duration = this.parseTime(block.end) - startMin;
|
||||
const snapGrid = this._minutesPx(this.GRID_MINUTES);
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const elRect = el.getBoundingClientRect();
|
||||
const startMin = this.parseTime(block.start);
|
||||
const duration = this.parseTime(block.end) - startMin;
|
||||
const snapGrid = this._minutesPx(this.GRID_MINUTES);
|
||||
|
||||
// Create floating ghost
|
||||
// ── Capture day row positions BEFORE any DOM change ──────────
|
||||
const dayTimelines = Array.from(document.querySelectorAll('.day-timeline'));
|
||||
const dayRows = dayTimelines.map(t => {
|
||||
const r = t.getBoundingClientRect();
|
||||
return { date: t.dataset.date, el: t, top: r.top, bottom: r.bottom };
|
||||
});
|
||||
|
||||
// Release implicit pointer capture so pointermove fires freely on document
|
||||
try { el.releasePointerCapture(e.pointerId); } catch (_) {}
|
||||
|
||||
// Prevent text selection during drag
|
||||
document.body.style.userSelect = 'none';
|
||||
|
||||
// ── Ghost element ─────────────────────────────────────────────
|
||||
const ghost = document.createElement('div');
|
||||
ghost.className = 'block-el drag-ghost';
|
||||
ghost.innerHTML = el.innerHTML;
|
||||
@@ -325,52 +339,65 @@ const Canvas = {
|
||||
opacity: '0.88',
|
||||
pointerEvents: 'none',
|
||||
cursor: 'grabbing',
|
||||
boxShadow: '0 6px 20px rgba(0,0,0,0.28)',
|
||||
boxShadow: '0 6px 20px rgba(0,0,0,0.3)',
|
||||
borderRadius: '4px',
|
||||
transition: 'none',
|
||||
margin: '0',
|
||||
});
|
||||
document.body.appendChild(ghost);
|
||||
el.style.opacity = '0.25'; // dim original, don't hide (keeps layout)
|
||||
el.style.opacity = '0.2';
|
||||
|
||||
const clearHighlights = () =>
|
||||
document.querySelectorAll('.day-timeline.drag-target')
|
||||
.forEach(r => r.classList.remove('drag-target'));
|
||||
|
||||
const onMove = (ev) => {
|
||||
const dx = ev.clientX - startX;
|
||||
const dy = ev.clientY - startY;
|
||||
ghost.style.left = (elRect.left + dx) + 'px';
|
||||
ghost.style.top = (elRect.top + dy) + 'px';
|
||||
|
||||
// Highlight target row — elementFromPoint sees through ghost (pointer-events:none)
|
||||
clearHighlights();
|
||||
const under = document.elementFromPoint(ev.clientX, ev.clientY);
|
||||
under?.closest('.day-timeline')?.classList.add('drag-target');
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
const findRow = (clientY) => {
|
||||
for (const d of dayRows) {
|
||||
if (clientY >= d.top && clientY <= d.bottom) return d;
|
||||
}
|
||||
// Clamp to nearest row when pointer is between rows or outside canvas
|
||||
if (!dayRows.length) return null;
|
||||
if (clientY < dayRows[0].top) return dayRows[0];
|
||||
return dayRows[dayRows.length - 1];
|
||||
};
|
||||
|
||||
const clearHighlights = () =>
|
||||
dayTimelines.forEach(r => r.classList.remove('drag-target'));
|
||||
|
||||
// ── Drag move ─────────────────────────────────────────────────
|
||||
const onMove = (ev) => {
|
||||
ghost.style.left = (elRect.left + ev.clientX - startX) + 'px';
|
||||
ghost.style.top = (elRect.top + ev.clientY - startY) + 'px';
|
||||
clearHighlights();
|
||||
const row = findRow(ev.clientY);
|
||||
if (row) row.el.classList.add('drag-target');
|
||||
};
|
||||
|
||||
// ── Drag end ──────────────────────────────────────────────────
|
||||
const onUp = (ev) => {
|
||||
document.removeEventListener('pointermove', onMove);
|
||||
document.removeEventListener('pointerup', onUp);
|
||||
|
||||
const dx = ev.clientX - startX;
|
||||
|
||||
// Snap X to 15-min grid
|
||||
const snappedDx = Math.round(dx / snapGrid) * snapGrid;
|
||||
const deltaMin = snappedDx / this.pxPerMinute;
|
||||
const newStart = this.snapMinutes(startMin + deltaMin);
|
||||
const clamped = Math.max(this._startMin, Math.min(this._endMin - duration, newStart));
|
||||
block.start = this.formatTime(clamped);
|
||||
block.end = this.formatTime(clamped + duration);
|
||||
|
||||
// Determine target day
|
||||
const under = document.elementFromPoint(ev.clientX, ev.clientY);
|
||||
const timeline = under?.closest('.day-timeline');
|
||||
if (timeline?.dataset.date) block.date = timeline.dataset.date;
|
||||
|
||||
ghost.remove();
|
||||
el.style.opacity = '';
|
||||
clearHighlights();
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
const dx = ev.clientX - startX;
|
||||
const dy = ev.clientY - startY;
|
||||
|
||||
// Ignore micro-movements (treat as click, let click handler open modal)
|
||||
if (Math.abs(dx) < 5 && Math.abs(dy) < 5) return;
|
||||
|
||||
// ── Update time (X axis) ──────────────────────────────────
|
||||
const snappedDx = Math.round(dx / snapGrid) * snapGrid;
|
||||
const deltaMin = snappedDx / this.pxPerMinute;
|
||||
const newStart = this.snapMinutes(startMin + deltaMin);
|
||||
const clamped = Math.max(this._startMin, Math.min(this._endMin - duration, newStart));
|
||||
block.start = this.formatTime(clamped);
|
||||
block.end = this.formatTime(clamped + duration);
|
||||
|
||||
// ── Update day (Y axis) — bounding rects, NO elementFromPoint ──
|
||||
const row = findRow(ev.clientY);
|
||||
if (row) block.date = row.date;
|
||||
|
||||
App.renderCanvas();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user