feat: optimize event capturing and playback accuracy
This commit is contained in:
108
components/record-mode/PickingHelper.tsx
Normal file
108
components/record-mode/PickingHelper.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { finder } from '@medv/finder';
|
||||
|
||||
export function PickingHelper() {
|
||||
const [pickingMode, setPickingMode] = useState<'click' | 'scroll' | null>(null);
|
||||
const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (e: MessageEvent) => {
|
||||
if (e.data.type === 'START_PICKING') {
|
||||
setPickingMode(e.data.mode);
|
||||
} else if (e.data.type === 'STOP_PICKING') {
|
||||
setPickingMode(null);
|
||||
setHoveredElement(null);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pickingMode) return;
|
||||
|
||||
const handleMouseOver = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.closest('.record-mode-ignore') || target.closest('.feedback-ui-ignore')) return;
|
||||
setHoveredElement(target);
|
||||
};
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (hoveredElement) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const selector = finder(hoveredElement, {
|
||||
root: document.body,
|
||||
seedMinLength: 3,
|
||||
optimizedMinLength: 2,
|
||||
className: (name) =>
|
||||
!name.startsWith('record-mode-') &&
|
||||
!name.startsWith('feedback-') &&
|
||||
!name.includes('[') &&
|
||||
!name.includes('/') &&
|
||||
!name.match(/^[a-z]-[0-9]/) &&
|
||||
!name.match(/[0-9]{4,}/), // Avoid dynamic IDs in classnames
|
||||
idName: (name) => !name.startsWith('__next') && !name.includes(':') && !name.match(/[0-9]{5,}/),
|
||||
});
|
||||
const rect = hoveredElement.getBoundingClientRect();
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'ELEMENT_SELECTED',
|
||||
selector,
|
||||
rect: {
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
},
|
||||
tagName: hoveredElement.tagName.toLowerCase()
|
||||
}, '*');
|
||||
|
||||
setPickingMode(null);
|
||||
setHoveredElement(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
setPickingMode(null);
|
||||
setHoveredElement(null);
|
||||
window.parent.postMessage({ type: 'PICKING_CANCELLED' }, '*');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('mouseover', handleMouseOver);
|
||||
window.addEventListener('click', handleClick, true);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('mouseover', handleMouseOver);
|
||||
window.removeEventListener('click', handleClick, true);
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [pickingMode, hoveredElement]);
|
||||
|
||||
if (!pickingMode || !hoveredElement) return null;
|
||||
|
||||
const rect = hoveredElement.getBoundingClientRect();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed pointer-events-none border-2 border-[#82ed20] bg-[#82ed20]/5 transition-all z-[9999] shadow-[0_0_40px_rgba(130,237,32,0.4)] rounded-sm backdrop-blur-[2px]"
|
||||
style={{
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}}
|
||||
>
|
||||
<div className="absolute top-0 right-0 bg-[#82ed20] text-black text-[10px] font-black px-1.5 py-1 transform -translate-y-full uppercase tracking-tighter shadow-xl">
|
||||
{hoveredElement.tagName.toLowerCase()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user