[eric] dashboard: note textarea + browser chrome clicks select (capture-phase); card presses never arm the marquee deselect

This commit is contained in:
ciregenz
2026-07-08 23:52:31 -07:00
parent 3b0d3bb6a7
commit 9fdfa12cc5
3 changed files with 19 additions and 5 deletions
@@ -823,7 +823,11 @@ const BrowserCard: React.FC<Props> = ({
data-select-meta={JSON.stringify({ name: activeTitle || 'Browser', url: activeUrl })}
// Marks a kept-alive card parked off-screen (it belongs to another dashboard); fit-to-view must skip it or it pans the canvas to chase it and the card bleeds onto the dashboard you're viewing.
data-keepalive-hidden={keepAliveHidden ? '1' : undefined}
onPointerDownCapture={() => onBringToFront?.(browserId, 'browser')}
onPointerDownCapture={(e: React.PointerEvent) => {
onBringToFront?.(browserId, 'browser');
// Capture-phase so chrome clicks (tab strip, URL bar) the children swallow still select the card; clicks inside the guest page never reach the host at all. Shift keeps the bubbled toggle path.
if (e.button === 0 && !e.shiftKey) onCardSelect?.(browserId, 'browser', false);
}}
onClick={(e: React.MouseEvent) => {
if (justDraggedRef.current) return;
onCardSelect?.(browserId, 'browser', e.shiftKey);
@@ -255,7 +255,11 @@ const NoteCard: React.FC<Props> = ({
data-select-type="note-card"
data-select-id={noteId}
data-select-meta={JSON.stringify({ name: 'Note', content: content.slice(0, 60) })}
onPointerDownCapture={() => onBringToFront?.(noteId, 'note')}
onPointerDownCapture={(e: React.PointerEvent) => {
onBringToFront?.(noteId, 'note');
// Capture-phase so a click the textarea swallows still selects the note; shift keeps the bubbled toggle path.
if (e.button === 0 && !e.shiftKey) onCardSelect?.(noteId, 'note', false);
}}
onClick={(e: React.MouseEvent) => {
if (justDraggedRef.current) return;
onCardSelect?.(noteId, 'note', e.shiftKey);
@@ -96,9 +96,13 @@ export function useDashboardSelection(
}
return next;
}
// Plain click selects the clicked card (collapsing any multi-select to it) so spawn-beside-selection actually fires; deselect = empty-canvas click or Esc.
if (prev.size === 1 && prev.has(id)) {
return prev;
// Plain click/press selects the clicked card so spawn-beside-selection actually fires; deselect = empty-canvas click or Esc. An already-selected member keeps the whole selection (a press also starts multi-drag; collapsing would break it) but moves to last so the clicked card is the spawn anchor.
if (prev.has(id)) {
if (Array.from(prev.keys()).pop() === id) return prev;
const next = new Map(prev);
next.delete(id);
next.set(id, type);
return next;
}
return new Map([[id, type]]);
});
@@ -212,6 +216,8 @@ export function useDashboardSelection(
const handleCanvasMouseDown = useCallback(
(e: MouseEvent) => {
if (e.button !== 0 && e.button !== 2) return;
// A press starting on a card is a card interaction (select/drag), not a marquee; arming here would make the mouseup deselect the card that was just clicked.
if ((e.target as HTMLElement)?.closest?.('[data-select-id]')) return;
marqueeOriginRef.current = { screenX: e.clientX, screenY: e.clientY };
isDraggingMarqueeRef.current = false;