feat: Add spatial workspace architecture design document and refactor card drag logic to use new LayoutCard drag state management.
This commit is contained in:
@@ -1,24 +1,39 @@
|
||||
import { LayoutStore } from './LayoutSystem';
|
||||
|
||||
const capturedCards = new Set<string>();
|
||||
export const handleDragStart = (store: LayoutStore, selection: string[], leadingId: string, offset: { x: number, y: number }) => {
|
||||
const leadingCard = store.items.get(leadingId);
|
||||
if (!leadingCard) return;
|
||||
|
||||
selection.forEach(id => {
|
||||
const card = store.items.get(id);
|
||||
if (!card) return;
|
||||
|
||||
let myOffset = offset;
|
||||
if (id !== leadingId) {
|
||||
myOffset = {
|
||||
x: offset.x + (leadingCard.x - card.x),
|
||||
y: offset.y + (leadingCard.y - card.y)
|
||||
};
|
||||
}
|
||||
|
||||
card.beginDrag(myOffset);
|
||||
});
|
||||
};
|
||||
|
||||
export const handleDragMove = (store: LayoutStore, selection: string[], delta: { x: number, y: number }) => {
|
||||
selection.forEach(id => {
|
||||
const card = store.items.get(id);
|
||||
if (card) {
|
||||
if (!capturedCards.has(id)) {
|
||||
card.captureDragStart();
|
||||
capturedCards.add(id);
|
||||
}
|
||||
|
||||
card.update({
|
||||
x: card.dragStartX + delta.x,
|
||||
y: card.dragStartY + delta.y
|
||||
});
|
||||
card.continueDrag(delta);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const handleDragEnd = () => {
|
||||
capturedCards.clear();
|
||||
export const handleDragEnd = (store: LayoutStore, selection: string[]) => {
|
||||
selection.forEach(id => {
|
||||
const card = store.items.get(id);
|
||||
if (card) {
|
||||
card.finishDrag();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ export class LayoutCard implements LayoutCardState {
|
||||
public store: LayoutStore;
|
||||
public dragStartX: number = 0;
|
||||
public dragStartY: number = 0;
|
||||
private dragOffset: { x: number, y: number } | null = null;
|
||||
|
||||
constructor(id: string, store: LayoutStore, initialData: Partial<LayoutCardState> = {}, ref: HTMLElement | null = null) {
|
||||
this.id = id;
|
||||
@@ -39,11 +40,28 @@ export class LayoutCard implements LayoutCardState {
|
||||
this.applyTransform();
|
||||
}
|
||||
|
||||
captureDragStart() {
|
||||
beginDrag(offset: { x: number, y: number }) {
|
||||
this.dragOffset = offset;
|
||||
this.dragStartX = this.x;
|
||||
this.dragStartY = this.y;
|
||||
}
|
||||
|
||||
continueDrag(delta: { x: number, y: number }) {
|
||||
if (!this.dragOffset) return;
|
||||
|
||||
const newX = this.x + delta.x;
|
||||
const newY = this.y + delta.y;
|
||||
|
||||
this.update({
|
||||
x: newX,
|
||||
y: newY
|
||||
});
|
||||
}
|
||||
|
||||
finishDrag() {
|
||||
this.dragOffset = null;
|
||||
}
|
||||
|
||||
update(changes: Partial<LayoutCardState>) {
|
||||
Object.assign(this, changes);
|
||||
if (changes.width !== undefined || changes.height !== undefined) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
|
||||
import { LayoutCard } from './LayoutSystem';
|
||||
import { handleDragMove, handleDragEnd } from './CardDragLogic';
|
||||
import { handleDragMove, handleDragEnd, handleDragStart } from './CardDragLogic';
|
||||
|
||||
const DRAG_THRESHOLD = 3;
|
||||
|
||||
@@ -17,6 +17,7 @@ export const useCardPointer = (
|
||||
) => {
|
||||
const [state, setState] = React.useState<PointerState>('idle');
|
||||
const initialPosition = useRef<{ x: number, y: number } | null>(null);
|
||||
const lastPosition = useRef<{ x: number, y: number } | null>(null);
|
||||
|
||||
const updateState = useCallback((e: React.PointerEvent) => {
|
||||
if (state === 'click' && initialPosition.current) {
|
||||
@@ -37,6 +38,7 @@ export const useCardPointer = (
|
||||
(e.target as Element).setPointerCapture(e.pointerId);
|
||||
setState('click');
|
||||
initialPosition.current = { x: e.clientX, y: e.clientY };
|
||||
lastPosition.current = { x: e.clientX, y: e.clientY };
|
||||
}, []);
|
||||
|
||||
const onPointerMove = useCallback((e: React.PointerEvent) => {
|
||||
@@ -44,24 +46,45 @@ export const useCardPointer = (
|
||||
updateState(e);
|
||||
|
||||
if (state === 'drag-start') {
|
||||
let effectiveSelection = selection;
|
||||
if (!isSelected) {
|
||||
onSelect([card.id], hasModifier);
|
||||
|
||||
if (!hasModifier) {
|
||||
card.bringToFront();
|
||||
if (hasModifier) {
|
||||
effectiveSelection = [...selection, card.id];
|
||||
} else {
|
||||
effectiveSelection = [card.id];
|
||||
}
|
||||
}
|
||||
|
||||
card.bringToFront();
|
||||
|
||||
setState('drag');
|
||||
|
||||
if (initialPosition.current) {
|
||||
const rect = card.ref.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const offset = {
|
||||
x: initialPosition.current.x - centerX,
|
||||
y: initialPosition.current.y - centerY
|
||||
};
|
||||
|
||||
handleDragStart(card.store, effectiveSelection, card.id, offset);
|
||||
// Reset lastPosition to current pointer to avoid jump on first move
|
||||
lastPosition.current = { x: e.clientX, y: e.clientY };
|
||||
}
|
||||
}
|
||||
|
||||
if (state === 'drag' && initialPosition.current) {
|
||||
if (state === 'drag' && lastPosition.current) {
|
||||
const delta = {
|
||||
x: e.clientX - initialPosition.current.x,
|
||||
y: e.clientY - initialPosition.current.y
|
||||
x: e.clientX - lastPosition.current.x,
|
||||
y: e.clientY - lastPosition.current.y
|
||||
};
|
||||
|
||||
handleDragMove(card.store, selection, delta);
|
||||
if (delta.x !== 0 || delta.y !== 0) {
|
||||
handleDragMove(card.store, selection, delta);
|
||||
lastPosition.current = { x: e.clientX, y: e.clientY };
|
||||
}
|
||||
}
|
||||
}, [card, state, updateState, isSelected, selection, onSelect]);
|
||||
|
||||
@@ -70,7 +93,7 @@ export const useCardPointer = (
|
||||
updateState(e);
|
||||
|
||||
if (state === 'drag' || state === 'drag-start') {
|
||||
handleDragEnd();
|
||||
handleDragEnd(card.store, selection);
|
||||
} else if (state === 'click') {
|
||||
if (isSelected) {
|
||||
const isUnobstructed = card.isUnobstructed();
|
||||
@@ -91,8 +114,9 @@ export const useCardPointer = (
|
||||
|
||||
setState('idle');
|
||||
initialPosition.current = null;
|
||||
lastPosition.current = null;
|
||||
(e.target as Element).releasePointerCapture(e.pointerId);
|
||||
}, [card, state, isSelected, onSelect, onDeselect, onDocumentActivate, updateState]);
|
||||
}, [card, state, isSelected, onSelect, onDeselect, onDocumentActivate, updateState, selection]);
|
||||
|
||||
return {
|
||||
onPointerDown,
|
||||
|
||||
Reference in New Issue
Block a user