Fix card-enter animation re-trigger and drag hover suppression
Remove card-enter class after entrance animation completes to prevent re-triggering when card-highlight is removed. Change fill-mode from both to backwards so stale transforms don't block hover effects. Suppress hover globally during drag via body.cs-drag-active class. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,7 +73,7 @@ section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-enter {
|
.card-enter {
|
||||||
animation: cardEnter 0.25s ease-out both;
|
animation: cardEnter 0.25s ease-out backwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Card drag-and-drop reordering ── */
|
/* ── Card drag-and-drop reordering ── */
|
||||||
@@ -135,14 +135,22 @@ section {
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Suppress hover effects during drag */
|
/* Suppress hover effects globally during drag */
|
||||||
.cs-dragging .card,
|
body.cs-drag-active .card,
|
||||||
.cs-dragging .template-card {
|
body.cs-drag-active .template-card,
|
||||||
|
body.cs-drag-active .add-template-card {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cs-dragging .card-drag-handle {
|
body.cs-drag-active .add-template-card {
|
||||||
|
pointer-events: none;
|
||||||
|
border-color: var(--border-color) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.cs-drag-active .card-drag-handle {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,6 +178,12 @@ export class CardSection {
|
|||||||
* @returns {{added: Set<string>, replaced: Set<string>, removed: Set<string>}}
|
* @returns {{added: Set<string>, replaced: Set<string>, removed: Set<string>}}
|
||||||
*/
|
*/
|
||||||
reconcile(items) {
|
reconcile(items) {
|
||||||
|
// Skip DOM mutations while a drag is in progress — would destroy drag state
|
||||||
|
if (this._dragState) {
|
||||||
|
this._pendingReconcile = items;
|
||||||
|
return { added: new Set(), replaced: new Set(), removed: new Set() };
|
||||||
|
}
|
||||||
|
|
||||||
const content = document.querySelector(`[data-cs-content="${this.sectionKey}"]`);
|
const content = document.querySelector(`[data-cs-content="${this.sectionKey}"]`);
|
||||||
if (!content) return { added: new Set(), replaced: new Set(), removed: new Set() };
|
if (!content) return { added: new Set(), replaced: new Set(), removed: new Set() };
|
||||||
|
|
||||||
@@ -240,6 +246,7 @@ export class CardSection {
|
|||||||
if (card) {
|
if (card) {
|
||||||
card.style.animationDelay = `${delay}ms`;
|
card.style.animationDelay = `${delay}ms`;
|
||||||
card.classList.add('card-enter');
|
card.classList.add('card-enter');
|
||||||
|
card.addEventListener('animationend', () => card.classList.remove('card-enter'), { once: true });
|
||||||
delay += 30;
|
delay += 30;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,6 +354,7 @@ export class CardSection {
|
|||||||
cards.forEach((card, i) => {
|
cards.forEach((card, i) => {
|
||||||
card.style.animationDelay = `${i * 30}ms`;
|
card.style.animationDelay = `${i * 30}ms`;
|
||||||
card.classList.add('card-enter');
|
card.classList.add('card-enter');
|
||||||
|
card.addEventListener('animationend', () => card.classList.remove('card-enter'), { once: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,20 +510,17 @@ export class CardSection {
|
|||||||
ds.clone.style.left = (e.clientX - ds.offsetX) + 'px';
|
ds.clone.style.left = (e.clientX - ds.offsetX) + 'px';
|
||||||
ds.clone.style.top = (e.clientY - ds.offsetY) + 'px';
|
ds.clone.style.top = (e.clientY - ds.offsetY) + 'px';
|
||||||
|
|
||||||
// Find drop target
|
// Only move placeholder when cursor enters a card's rect
|
||||||
const target = this._getDropTarget(e.clientX, e.clientY, ds.content);
|
const { card: target, before } = this._getDropTarget(e.clientX, e.clientY, ds.content);
|
||||||
if (target && target !== ds.placeholder) {
|
if (!target) return; // cursor is in a gap — keep placeholder where it is
|
||||||
const rect = target.getBoundingClientRect();
|
if (target === ds.lastTarget && before === ds.lastBefore) return; // same position
|
||||||
const midX = rect.left + rect.width / 2;
|
ds.lastTarget = target;
|
||||||
const midY = rect.top + rect.height / 2;
|
ds.lastBefore = before;
|
||||||
// For grid: use combined horizontal+vertical check
|
if (before) {
|
||||||
const insertBefore = (e.clientY < midY) || (e.clientY < midY + rect.height * 0.3 && e.clientX < midX);
|
|
||||||
if (insertBefore) {
|
|
||||||
ds.content.insertBefore(ds.placeholder, target);
|
ds.content.insertBefore(ds.placeholder, target);
|
||||||
} else {
|
} else {
|
||||||
ds.content.insertBefore(ds.placeholder, target.nextSibling);
|
ds.content.insertBefore(ds.placeholder, target.nextSibling);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-scroll near viewport edges
|
// Auto-scroll near viewport edges
|
||||||
this._autoScroll(e.clientY, ds);
|
this._autoScroll(e.clientY, ds);
|
||||||
@@ -548,6 +553,7 @@ export class CardSection {
|
|||||||
// Hide original
|
// Hide original
|
||||||
ds.card.style.display = 'none';
|
ds.card.style.display = 'none';
|
||||||
ds.content.classList.add('cs-dragging');
|
ds.content.classList.add('cs-dragging');
|
||||||
|
document.body.classList.add('cs-drag-active');
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDragEnd() {
|
_onDragEnd() {
|
||||||
@@ -564,21 +570,30 @@ export class CardSection {
|
|||||||
ds.placeholder.remove();
|
ds.placeholder.remove();
|
||||||
ds.clone.remove();
|
ds.clone.remove();
|
||||||
ds.content.classList.remove('cs-dragging');
|
ds.content.classList.remove('cs-dragging');
|
||||||
|
document.body.classList.remove('cs-drag-active');
|
||||||
|
|
||||||
// Save new order from DOM
|
// Save new order from DOM
|
||||||
const keys = this._readDomOrder(ds.content);
|
const keys = this._readDomOrder(ds.content);
|
||||||
this._saveOrder(keys);
|
this._saveOrder(keys);
|
||||||
|
|
||||||
|
// Flush any reconcile that was deferred during drag
|
||||||
|
if (this._pendingReconcile) {
|
||||||
|
const items = this._pendingReconcile;
|
||||||
|
this._pendingReconcile = null;
|
||||||
|
this.reconcile(items);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getDropTarget(x, y, content) {
|
_getDropTarget(x, y, content) {
|
||||||
// Temporarily show all cards for hit testing
|
const cards = content.querySelectorAll(`[${this.keyAttr}]`);
|
||||||
const els = document.elementsFromPoint(x, y);
|
for (const card of cards) {
|
||||||
for (const el of els) {
|
if (card.style.display === 'none') continue;
|
||||||
if (el === content) break;
|
const r = card.getBoundingClientRect();
|
||||||
const card = el.closest(`[${this.keyAttr}]`);
|
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) {
|
||||||
if (card && card.style.display !== 'none' && content.contains(card)) return card;
|
return { card, before: x < r.left + r.width / 2 };
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
|
return { card: null, before: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
_readDomOrder(content) {
|
_readDomOrder(content) {
|
||||||
|
|||||||
Reference in New Issue
Block a user