Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 84x 84x 5x 5x 84x 3x 3x 84x 84x 7x 1x 6x 1x 1x 5x 7x 3x 2x 1x 1x 5x 5x 5x 84x 18x | /**
* useTapToAssign (Story 15.3)
*
* Touch-friendly slot assignment: tap a session to "arm" it, then tap a target slot to
* place it. Resolves the right backend mode from the armed source and the target state:
*
* - armed from the pool + empty target → ASSIGN
* - armed from the pool + occupied target → INSERT (push the occupant + reflow)
* - armed from a slot + empty target → ASSIGN (move)
* - armed from a slot + occupied target → SWAP
*
* Extracted from the inline tap logic in DragDropSlotAssignment (14.G.3) and generalised to
* also support tap-to-swap. Desktop keeps native HTML5 drag; this hook drives touch viewports.
*/
import { useCallback, useState } from 'react';
import type { SlotAssignmentMode } from '@/services/slotAssignmentService/slotAssignmentService';
/** The currently "picked up" session. `fromSlotKey` is null when armed from the pool. */
export interface ArmedSession {
sessionSlug: string;
fromSlotKey: string | null;
}
export interface UseTapToAssignArgs {
/** Commit an assignment. Receives the resolved mode. */
commit: (
sessionSlug: string,
targetSlotKey: string,
mode: SlotAssignmentMode
) => Promise<void> | void;
}
export interface UseTapToAssignReturn {
armed: ArmedSession | null;
armedSessionSlug: string | null;
/** True while any session is armed (used to highlight candidate target slots). */
hasArmed: boolean;
isArmed: (sessionSlug: string) => boolean;
/** Tap a pool session: arms it, or disarms if it was already the armed pool session. */
armFromPool: (sessionSlug: string) => void;
/** Tap an assigned slot: arms its occupant for a move/swap, or disarms if re-tapped. */
armFromSlot: (sessionSlug: string, slotKey: string) => void;
/** Tap a target slot. No-op if nothing is armed. */
tapSlot: (targetSlotKey: string, occupiedSessionSlug: string | null) => Promise<void>;
clear: () => void;
}
export function useTapToAssign({ commit }: UseTapToAssignArgs): UseTapToAssignReturn {
const [armed, setArmed] = useState<ArmedSession | null>(null);
const armFromPool = useCallback((sessionSlug: string) => {
setArmed((prev) =>
prev && prev.fromSlotKey === null && prev.sessionSlug === sessionSlug
? null
: { sessionSlug, fromSlotKey: null }
);
}, []);
const armFromSlot = useCallback((sessionSlug: string, slotKey: string) => {
setArmed((prev) =>
prev && prev.fromSlotKey === slotKey ? null : { sessionSlug, fromSlotKey: slotKey }
);
}, []);
const clear = useCallback(() => setArmed(null), []);
const tapSlot = useCallback(
async (targetSlotKey: string, occupiedSessionSlug: string | null) => {
if (!armed) {
return;
}
// Tapping the slot you armed from cancels the pick-up.
if (armed.fromSlotKey === targetSlotKey) {
setArmed(null);
return;
}
const targetOccupied = !!occupiedSessionSlug && occupiedSessionSlug !== armed.sessionSlug;
let mode: SlotAssignmentMode;
if (!targetOccupied) {
mode = 'ASSIGN';
} else if (armed.fromSlotKey) {
mode = 'SWAP';
} else {
mode = 'INSERT';
}
try {
await commit(armed.sessionSlug, targetSlotKey, mode);
} finally {
setArmed(null);
}
},
[armed, commit]
);
return {
armed,
armedSessionSlug: armed?.sessionSlug ?? null,
hasArmed: armed !== null,
isArmed: (sessionSlug: string) => armed?.sessionSlug === sessionSlug,
armFromPool,
armFromSlot,
tapSlot,
clear,
};
}
|