diff --git a/ui/components/cards/Actions.vue b/ui/components/cards/Actions.vue index e1e4a9e..485b28c 100644 --- a/ui/components/cards/Actions.vue +++ b/ui/components/cards/Actions.vue @@ -42,6 +42,10 @@ function fireZone(zone: Zone) { const arena = ref(null) const arenaSize = ref({ w: 0, h: 0 }) +/** Top/bottom labels sit over the card; we hit-test the cursor against their + rects (they're pointer-events:none, so closest() can't see them). */ +const topLabel = ref(null) +const bottomLabel = ref(null) let resizeObserver: ResizeObserver | null = null @@ -80,8 +84,25 @@ function onMove(e: MouseEvent) { const rect = arena.value?.getBoundingClientRect() if (!rect) return - // Cursor over the card body — neutral, no zone active. + // Cursor over the card body — neutral, no zone active. But the top/bottom + // labels now sit *on* the card (above it). The labels are pointer-events:none, + // so the cursor's actual target is the card content beneath them — we can't + // rely on closest(). Instead, geometrically test the cursor against each + // label's rect so hovering the label still resolves to its zone rather than + // being swallowed by the passthrough check below. const target = e.target as HTMLElement | null + for (const zone of ['top', 'bottom'] as const) { + const labelRect = (zone === 'top' ? topLabel : bottomLabel).value?.getBoundingClientRect() + if (!labelRect) + continue + if ( + e.clientX >= labelRect.left && e.clientX <= labelRect.right + && e.clientY >= labelRect.top && e.clientY <= labelRect.bottom + ) { + activeZone.value = commandFor(zone).active.value ? zone : null + return + } + } if (target?.closest('[data-cards-passthrough]')) { activeZone.value = null return @@ -151,9 +172,16 @@ const cardFilter = computed(() => { function onClick(e: MouseEvent) { const target = e.target as HTMLElement | null - if (target?.closest('[data-cards-passthrough]')) - return + // A click whose target is the card body is normally ignored — but the + // top/bottom labels float over the card, so when the cursor is within one of + // them (activeZone already resolved by onMove's rect hit-test) honour it. + const overEdgeLabel = (zone: Zone) => { + const r = (zone === 'top' ? topLabel : bottomLabel).value?.getBoundingClientRect() + return !!r && e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom + } const zone = activeZone.value + if (target?.closest('[data-cards-passthrough]') && !(zone && (zone === 'top' || zone === 'bottom') && overEdgeLabel(zone))) + return if (!zone) return fireZone(zone) @@ -191,6 +219,7 @@ function onClick(e: MouseEvent) { the user can tell the card is already in that state and the click will toggle it off / re-open in edit mode. -->