From 483dfabe10551a4d6bb2f293267269561e547e50 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sun, 15 Feb 2026 18:17:10 +0100 Subject: [PATCH] feat: refactor clicks to generic mouse interactions with click/hover subtypes --- components/record-mode/RecordModeContext.tsx | 44 +++++++--- components/record-mode/RecordModeOverlay.tsx | 91 ++++++++++++++++---- types/record-mode.ts | 4 +- 3 files changed, 112 insertions(+), 27 deletions(-) diff --git a/components/record-mode/RecordModeContext.tsx b/components/record-mode/RecordModeContext.tsx index 52a3fb2c..a36412f9 100644 --- a/components/record-mode/RecordModeContext.tsx +++ b/components/record-mode/RecordModeContext.tsx @@ -132,7 +132,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode }) if (el) { if (event.type === 'scroll') { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } else if (event.type === 'click') { + } else if (event.type === 'mouse') { const currentRect = el.getBoundingClientRect(); // Calculate Point based on Origin @@ -157,6 +157,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode }) clientX: targetX, clientY: targetY }; + const dispatchMouse = (type: string) => { el.dispatchEvent(new MouseEvent(type, { view: window, @@ -165,10 +166,21 @@ export function RecordModeProvider({ children }: { children: React.ReactNode }) ...eventCoords })); }; - dispatchMouse('mousedown'); - dispatchMouse('mouseup'); - dispatchMouse('click'); - el.click(); + + if (event.interactionType === 'click') { + dispatchMouse('mousedown'); + dispatchMouse('mouseup'); + dispatchMouse('click'); + + if (event.realClick) { + el.click(); + } + } else { + // Hover Interaction + dispatchMouse('mousemove'); + dispatchMouse('mouseover'); + dispatchMouse('mouseenter'); + } } } } @@ -275,7 +287,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode }) if (el) { if (event.type === 'scroll') { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } else if (event.type === 'click') { + } else if (event.type === 'mouse') { const currentRect = el.getBoundingClientRect(); // Calculate Point based on Origin (same as above for parity) @@ -300,6 +312,7 @@ export function RecordModeProvider({ children }: { children: React.ReactNode }) clientX: targetX, clientY: targetY }; + const dispatchMouse = (type: string) => { el.dispatchEvent(new MouseEvent(type, { view: window, @@ -308,10 +321,21 @@ export function RecordModeProvider({ children }: { children: React.ReactNode }) ...eventCoords })); }; - dispatchMouse('mousedown'); - dispatchMouse('mouseup'); - dispatchMouse('click'); - el.click(); + + if (event.interactionType === 'click') { + dispatchMouse('mousedown'); + dispatchMouse('mouseup'); + dispatchMouse('click'); + + if (event.realClick) { + el.click(); + } + } else { + // Hover Interaction + dispatchMouse('mousemove'); + dispatchMouse('mouseover'); + dispatchMouse('mouseenter'); + } } } } diff --git a/components/record-mode/RecordModeOverlay.tsx b/components/record-mode/RecordModeOverlay.tsx index 46665498..74e0f206 100644 --- a/components/record-mode/RecordModeOverlay.tsx +++ b/components/record-mode/RecordModeOverlay.tsx @@ -21,6 +21,7 @@ import { Clock, Maximize2, Box, + ExternalLink, } from 'lucide-react'; import { RecordEvent } from '@/types/record-mode'; import { PlaybackCursor } from './PlaybackCursor'; @@ -42,7 +43,8 @@ export function RecordModeOverlay() { setEvents, // Added setEvents here } = useRecordMode(); - const [pickingMode, setPickingMode] = useState<'click' | 'scroll' | null>(null); + const [pickingMode, setPickingMode] = useState<'mouse' | 'scroll' | null>(null); + const [lastInteractionType, setLastInteractionType] = useState<'click' | 'hover'>('click'); const [hoveredElement, setHoveredElement] = useState(null); const [editingEventId, setEditingEventId] = useState(null); @@ -61,13 +63,14 @@ export function RecordModeOverlay() { if (e.data.type === 'ELEMENT_SELECTED') { const { selector, rect, tagName } = e.data; - if (pickingMode === 'click') { + if (pickingMode === 'mouse') { addEvent({ - type: 'click', + type: 'mouse', + interactionType: lastInteractionType, selector, - duration: 1000, + duration: lastInteractionType === 'click' ? 1000 : 1500, zoom: 1, - description: `Click on ${tagName}`, + description: `${lastInteractionType === 'click' ? 'Click' : 'Hover'} on ${tagName}`, motionBlur: false, rect, }); @@ -156,13 +159,27 @@ export function RecordModeOverlay() { {/* Action Tools */}
+ + + + +
{/* Precise Click Origin */} - {editForm.type === 'click' && ( + {editForm.type === 'mouse' && editForm.interactionType === 'click' && (
@@ -465,6 +508,22 @@ export function RecordModeOverlay() {
{editForm.motionBlur ? :
} + + {editForm.type === 'mouse' && editForm.interactionType === 'click' && ( + + )}
diff --git a/types/record-mode.ts b/types/record-mode.ts index 4332fdcc..ed4a8c4c 100644 --- a/types/record-mode.ts +++ b/types/record-mode.ts @@ -1,6 +1,7 @@ export interface RecordEvent { id: string; - type: 'click' | 'scroll' | 'wait' | 'hover'; + type: 'mouse' | 'scroll' | 'wait'; + interactionType?: 'click' | 'hover'; selector?: string; // CSS selector timestamp: number; // Time in ms since start of recording duration: number; // Duration allocated for this action in playback @@ -9,6 +10,7 @@ export interface RecordEvent { motionBlur?: boolean; // Enable motion blur effect rect?: { x: number; y: number; width: number; height: number }; // Element position for rendering clickOrigin?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + realClick?: boolean; // Trigger real browser action (navigation) } export interface RecordingSession {