feat: Add physics model design document, implement multi-card snapping and layout system improvements, and refine pointer event handling.
This commit is contained in:
@@ -4,15 +4,34 @@ export const handleDragStart = (store: LayoutStore, selection: string[], leading
|
||||
const leadingCard = store.items.get(leadingId);
|
||||
if (!leadingCard) return;
|
||||
|
||||
// Snap all non-leading cards to the leading card's position
|
||||
selection.forEach(id => {
|
||||
if (id === leadingId) return;
|
||||
const card = store.items.get(id);
|
||||
if (card) {
|
||||
const leadingCenterX = leadingCard.x + leadingCard.width / 2;
|
||||
const leadingCenterY = leadingCard.y + leadingCard.height / 2;
|
||||
|
||||
const targetX = leadingCenterX - card.width / 2;
|
||||
const targetY = leadingCenterY - card.height / 2;
|
||||
|
||||
card.snapTo(targetX, targetY);
|
||||
}
|
||||
});
|
||||
|
||||
selection.forEach(id => {
|
||||
const card = store.items.get(id);
|
||||
if (!card) return;
|
||||
|
||||
let myOffset = offset;
|
||||
|
||||
if (id !== leadingId) {
|
||||
const pointerX = leadingCard.x + offset.x;
|
||||
const pointerY = leadingCard.y + offset.y;
|
||||
|
||||
myOffset = {
|
||||
x: offset.x + (leadingCard.x - card.x),
|
||||
y: offset.y + (leadingCard.y - card.y)
|
||||
x: pointerX - card.x,
|
||||
y: pointerY - card.y
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,27 @@ export class LayoutCard implements LayoutCardState {
|
||||
});
|
||||
}
|
||||
|
||||
snapTo(x: number, y: number) {
|
||||
this.update({ x, y });
|
||||
|
||||
if (!this.ref)
|
||||
return;
|
||||
|
||||
this.ref.classList.add('desk-item--swoop');
|
||||
this.ref.style.transform = `translate3d(${this.x}px, ${this.y}px, 0) rotate(${this.rotation}deg)`;
|
||||
|
||||
const cleanup = () => {
|
||||
if (this.ref) {
|
||||
this.ref.classList.remove('desk-item--swoop');
|
||||
}
|
||||
};
|
||||
|
||||
this.ref.addEventListener('transitionend', cleanup, { once: true });
|
||||
|
||||
// Safety timeout in case transitionend doesn't fire (e.g. element removed)
|
||||
setTimeout(cleanup, 350);
|
||||
}
|
||||
|
||||
toSnapshot(): LayoutCardState {
|
||||
return {
|
||||
id: this.id,
|
||||
@@ -252,14 +273,29 @@ export class LayoutStore {
|
||||
for (const card of this.items.values()) {
|
||||
const dx = card.x - x;
|
||||
const dy = card.y - y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (distance < radius + card.outerRadius) {
|
||||
|
||||
// Check if center is within inner radius of top card
|
||||
// Use squared distance to avoid sqrt
|
||||
const distSq = dx * dx + dy * dy;
|
||||
if (distSq < radius * radius) {
|
||||
result.push(card);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getStackBelow(topCard: LayoutCard): string[] {
|
||||
const candidates = this.getCardsInCircle(topCard.x, topCard.y, topCard.innerRadius);
|
||||
|
||||
return candidates
|
||||
.filter(other => {
|
||||
if (other.id === topCard.id) return false;
|
||||
if (other.z >= topCard.z) return false;
|
||||
return true;
|
||||
})
|
||||
.map(c => c.id);
|
||||
}
|
||||
|
||||
getSnapshot() {
|
||||
return Array.from(this.items.values()).map(card => card.toSnapshot());
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ export const useCardPointer = (
|
||||
// Only left click
|
||||
if (e.button !== 0) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
(e.target as Element).setPointerCapture(e.pointerId);
|
||||
setState('click');
|
||||
initialPosition.current = { x: e.clientX, y: e.clientY };
|
||||
@@ -42,6 +44,8 @@ export const useCardPointer = (
|
||||
}, []);
|
||||
|
||||
const onPointerMove = useCallback((e: React.PointerEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const hasModifier = e.metaKey || e.ctrlKey || e.shiftKey;
|
||||
updateState(e);
|
||||
|
||||
@@ -89,6 +93,8 @@ export const useCardPointer = (
|
||||
}, [card, state, updateState, isSelected, selection, onSelect]);
|
||||
|
||||
const onPointerUp = useCallback((e: React.PointerEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const hasModifier = e.metaKey || e.ctrlKey || e.shiftKey;
|
||||
updateState(e);
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.desk-item--swoop {
|
||||
transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
.desk-item__card img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
Reference in New Issue
Block a user