mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 20:21:24 +02:00
Implement a two-layer DnD system in @manacore/shared-ui/dnd that coexists with svelte-dnd-action (same-type reordering): - Layer 1 (Pointer Events): dragSource + dropTarget actions for cross-type drags (e.g. Tag → Task). Mobile-first with long-press (300ms) and haptic feedback. - Layer 2 (Passive Overlay): passiveDropZone action detects when svelte-dnd-action drags hover over external targets (e.g. Task → Tag pill, Task → Trash zone). - DragPreview: floating pill that follows the finger/cursor during Layer 1 drags. - ActionZone: auto-appearing drop zones (trash, archive) during any drag. Integrate into Todo app: - TagStrip pills: draggable (dragSource) + accept tasks (passiveDropZone) - TaskList items: accept tags (dropTarget) + register drags for passive layer - ViewColumn + FokusLayout: register svelte-dnd-action drags for passive layer - Layout: DragPreview + ActionZone (trash) added, tasks enriched with resolved label objects from shared tags so tag badges actually render on TaskItem. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
/**
|
|
* Global reactive drag state.
|
|
*
|
|
* Shared between dragSource, dropTarget, passiveDropZone, and UI components
|
|
* (DragPreview, ActionZone) so they can coordinate visuals.
|
|
*/
|
|
|
|
import type { DragPayload, DragType } from './types';
|
|
|
|
// ── State ───────────────────────────────────────────────────
|
|
|
|
/** The item currently being dragged (Layer 1: pointer-events system). */
|
|
let activeDrag = $state<DragPayload | null>(null);
|
|
|
|
/** Current pointer position during drag (screen coordinates). */
|
|
let pointerX = $state(0);
|
|
let pointerY = $state(0);
|
|
|
|
/** The ID of the drop target currently being hovered. */
|
|
let hoveredTargetId = $state<string | null>(null);
|
|
|
|
/**
|
|
* Whether a svelte-dnd-action drag is in progress (Layer 2).
|
|
* Set by passiveDropZone when it detects aria-grabbed elements.
|
|
*/
|
|
let svelteActionDragActive = $state(false);
|
|
|
|
/**
|
|
* Payload inferred from svelte-dnd-action drag (for passiveDropZone).
|
|
* Set via registerSvelteActionDrag() from the app.
|
|
*/
|
|
let svelteActionPayload = $state<DragPayload | null>(null);
|
|
|
|
// ── Accessors ───────────────────────────────────────────────
|
|
|
|
export const dragState = {
|
|
get activeDrag() {
|
|
return activeDrag;
|
|
},
|
|
get pointerX() {
|
|
return pointerX;
|
|
},
|
|
get pointerY() {
|
|
return pointerY;
|
|
},
|
|
get hoveredTargetId() {
|
|
return hoveredTargetId;
|
|
},
|
|
get isDragging() {
|
|
return activeDrag !== null;
|
|
},
|
|
get svelteActionDragActive() {
|
|
return svelteActionDragActive;
|
|
},
|
|
get svelteActionPayload() {
|
|
return svelteActionPayload;
|
|
},
|
|
/** True if ANY drag is happening (Layer 1 or Layer 2). */
|
|
get anyDragActive() {
|
|
return activeDrag !== null || svelteActionDragActive;
|
|
},
|
|
};
|
|
|
|
// ── Mutations ───────────────────────────────────────────────
|
|
|
|
export function startDrag(payload: DragPayload) {
|
|
activeDrag = payload;
|
|
}
|
|
|
|
export function updatePointer(x: number, y: number) {
|
|
pointerX = x;
|
|
pointerY = y;
|
|
}
|
|
|
|
export function setHoveredTarget(id: string | null) {
|
|
hoveredTargetId = id;
|
|
}
|
|
|
|
export function endDrag() {
|
|
activeDrag = null;
|
|
hoveredTargetId = null;
|
|
}
|
|
|
|
/**
|
|
* Called by app code to inform the passive layer that a svelte-dnd-action
|
|
* drag has started, along with the payload of the dragged item.
|
|
*
|
|
* Usage in TaskList:
|
|
* onconsider={(e) => {
|
|
* registerSvelteActionDrag({ type: 'task', data: { id: e.detail.info.id } });
|
|
* }}
|
|
* onfinalize={(e) => {
|
|
* clearSvelteActionDrag();
|
|
* }}
|
|
*/
|
|
export function registerSvelteActionDrag(payload: DragPayload) {
|
|
svelteActionDragActive = true;
|
|
svelteActionPayload = payload;
|
|
}
|
|
|
|
export function clearSvelteActionDrag() {
|
|
svelteActionDragActive = false;
|
|
svelteActionPayload = null;
|
|
hoveredTargetId = null;
|
|
}
|
|
|
|
/**
|
|
* Check whether a given drag type is currently active (either layer).
|
|
*/
|
|
export function isTypeBeingDragged(type: DragType): boolean {
|
|
if (activeDrag?.type === type) return true;
|
|
if (svelteActionPayload?.type === type) return true;
|
|
return false;
|
|
}
|