feat: add xpath highlighting logic
This commit is contained in:
@@ -98,6 +98,7 @@ interface RRWebDOMBrowserRendererProps {
|
|||||||
getList?: boolean;
|
getList?: boolean;
|
||||||
getText?: boolean;
|
getText?: boolean;
|
||||||
listSelector?: string | null;
|
listSelector?: string | null;
|
||||||
|
cachedChildSelectors?: string[];
|
||||||
paginationMode?: boolean;
|
paginationMode?: boolean;
|
||||||
paginationType?: string;
|
paginationType?: string;
|
||||||
limitMode?: boolean;
|
limitMode?: boolean;
|
||||||
@@ -106,12 +107,14 @@ interface RRWebDOMBrowserRendererProps {
|
|||||||
selector: string;
|
selector: string;
|
||||||
elementInfo: ElementInfo | null;
|
elementInfo: ElementInfo | null;
|
||||||
childSelectors?: string[];
|
childSelectors?: string[];
|
||||||
|
groupInfo?: any;
|
||||||
}) => void;
|
}) => void;
|
||||||
onElementSelect?: (data: {
|
onElementSelect?: (data: {
|
||||||
rect: DOMRect;
|
rect: DOMRect;
|
||||||
selector: string;
|
selector: string;
|
||||||
elementInfo: ElementInfo | null;
|
elementInfo: ElementInfo | null;
|
||||||
childSelectors?: string[];
|
childSelectors?: string[];
|
||||||
|
groupInfo?: any;
|
||||||
}) => void;
|
}) => void;
|
||||||
onShowDatePicker?: (info: {
|
onShowDatePicker?: (info: {
|
||||||
coordinates: { x: number; y: number };
|
coordinates: { x: number; y: number };
|
||||||
@@ -144,6 +147,7 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
getList = false,
|
getList = false,
|
||||||
getText = false,
|
getText = false,
|
||||||
listSelector = null,
|
listSelector = null,
|
||||||
|
cachedChildSelectors = [],
|
||||||
paginationMode = false,
|
paginationMode = false,
|
||||||
paginationType = "",
|
paginationType = "",
|
||||||
limitMode = false,
|
limitMode = false,
|
||||||
@@ -205,11 +209,24 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
const handleDOMHighlighting = useCallback(
|
const handleDOMHighlighting = useCallback(
|
||||||
(x: number, y: number, iframeDoc: Document) => {
|
(x: number, y: number, iframeDoc: Document) => {
|
||||||
try {
|
try {
|
||||||
|
if (!getText && !getList) {
|
||||||
|
setCurrentHighlight(null);
|
||||||
|
if (onHighlight) {
|
||||||
|
onHighlight({
|
||||||
|
rect: new DOMRect(0, 0, 0, 0),
|
||||||
|
selector: "",
|
||||||
|
elementInfo: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const highlighterData =
|
const highlighterData =
|
||||||
clientSelectorGenerator.generateDataForHighlighter(
|
clientSelectorGenerator.generateDataForHighlighter(
|
||||||
{ x, y },
|
{ x, y },
|
||||||
iframeDoc,
|
iframeDoc,
|
||||||
true
|
true,
|
||||||
|
cachedChildSelectors
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!highlighterData) {
|
if (!highlighterData) {
|
||||||
@@ -224,70 +241,40 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rect, selector, elementInfo, childSelectors } = highlighterData;
|
const { rect, selector, elementInfo, childSelectors, groupInfo } =
|
||||||
|
highlighterData;
|
||||||
|
|
||||||
let shouldHighlight = false;
|
let shouldHighlight = false;
|
||||||
|
|
||||||
if (getList) {
|
if (getList) {
|
||||||
if (listSelector) {
|
// First phase: Allow any group to be highlighted for selection
|
||||||
const hasValidChildSelectors =
|
if (!listSelector && groupInfo?.isGroupElement) {
|
||||||
Array.isArray(childSelectors) && childSelectors.length > 0;
|
shouldHighlight = true;
|
||||||
|
}
|
||||||
|
// Second phase: Show valid children within selected group
|
||||||
|
else if (listSelector) {
|
||||||
if (limitMode) {
|
if (limitMode) {
|
||||||
shouldHighlight = false;
|
shouldHighlight = false;
|
||||||
} else if (paginationMode) {
|
} else if (
|
||||||
if (
|
paginationMode &&
|
||||||
paginationType !== "" &&
|
paginationType !== "" &&
|
||||||
!["none", "scrollDown", "scrollUp"].includes(paginationType)
|
!["none", "scrollDown", "scrollUp"].includes(paginationType)
|
||||||
) {
|
) {
|
||||||
shouldHighlight = true;
|
shouldHighlight = true;
|
||||||
} else {
|
} else if (childSelectors && childSelectors.length > 0) {
|
||||||
shouldHighlight = false;
|
console.log("✅ Child selectors present, highlighting enabled");
|
||||||
}
|
|
||||||
} else if (childSelectors && childSelectors.includes(selector)) {
|
|
||||||
shouldHighlight = true;
|
shouldHighlight = true;
|
||||||
} else if (elementInfo?.isIframeContent && childSelectors) {
|
|
||||||
const isIframeChild = childSelectors.some(
|
|
||||||
(childSelector: string) =>
|
|
||||||
selector.includes(":>>") &&
|
|
||||||
childSelector
|
|
||||||
.split(":>>")
|
|
||||||
.some((part) => selector.includes(part.trim()))
|
|
||||||
);
|
|
||||||
shouldHighlight = isIframeChild;
|
|
||||||
} else if (selector.includes(":>>") && hasValidChildSelectors) {
|
|
||||||
const selectorParts = selector
|
|
||||||
.split(":>>")
|
|
||||||
.map((part: string) => part.trim());
|
|
||||||
const isValidMixedSelector = selectorParts.some((part: any) =>
|
|
||||||
childSelectors!.some((childSelector) =>
|
|
||||||
childSelector.includes(part)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else if (elementInfo?.isShadowRoot && childSelectors) {
|
|
||||||
const isShadowChild = childSelectors.some(
|
|
||||||
(childSelector: string) =>
|
|
||||||
selector.includes(">>") &&
|
|
||||||
childSelector
|
|
||||||
.split(">>")
|
|
||||||
.some((part) => selector.includes(part.trim()))
|
|
||||||
);
|
|
||||||
} else if (selector.includes(">>") && hasValidChildSelectors) {
|
|
||||||
const selectorParts = selector
|
|
||||||
.split(">>")
|
|
||||||
.map((part: string) => part.trim());
|
|
||||||
const isValidMixedSelector = selectorParts.some((part: any) =>
|
|
||||||
childSelectors!.some((childSelector) =>
|
|
||||||
childSelector.includes(part)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
console.log("❌ No child selectors available");
|
||||||
shouldHighlight = false;
|
shouldHighlight = false;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
// No list selector - show regular highlighting
|
||||||
|
else {
|
||||||
shouldHighlight = true;
|
shouldHighlight = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// getText mode - always highlight
|
||||||
shouldHighlight = true;
|
shouldHighlight = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +303,7 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
},
|
},
|
||||||
selector,
|
selector,
|
||||||
childSelectors,
|
childSelectors,
|
||||||
|
groupInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,9 +323,11 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
getText,
|
||||||
getList,
|
getList,
|
||||||
listSelector,
|
listSelector,
|
||||||
paginationMode,
|
paginationMode,
|
||||||
|
cachedChildSelectors,
|
||||||
paginationType,
|
paginationType,
|
||||||
limitMode,
|
limitMode,
|
||||||
onHighlight,
|
onHighlight,
|
||||||
@@ -363,6 +353,10 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isInCaptureMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
if (now - lastMouseMoveTime.current < MOUSE_MOVE_THROTTLE) {
|
if (now - lastMouseMoveTime.current < MOUSE_MOVE_THROTTLE) {
|
||||||
return;
|
return;
|
||||||
@@ -401,11 +395,24 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (currentHighlight && onElementSelect) {
|
if (currentHighlight && onElementSelect) {
|
||||||
|
// Get the group info for the current highlight
|
||||||
|
const highlighterData =
|
||||||
|
clientSelectorGenerator.generateDataForHighlighter(
|
||||||
|
{ x: iframeX, y: iframeY },
|
||||||
|
iframeDoc,
|
||||||
|
true,
|
||||||
|
cachedChildSelectors
|
||||||
|
);
|
||||||
|
|
||||||
onElementSelect({
|
onElementSelect({
|
||||||
rect: currentHighlight.rect,
|
rect: currentHighlight.rect,
|
||||||
selector: currentHighlight.selector,
|
selector: currentHighlight.selector,
|
||||||
elementInfo: currentHighlight.elementInfo,
|
elementInfo: currentHighlight.elementInfo,
|
||||||
childSelectors: currentHighlight.childSelectors || [],
|
childSelectors:
|
||||||
|
cachedChildSelectors.length > 0
|
||||||
|
? cachedChildSelectors
|
||||||
|
: highlighterData?.childSelectors || [],
|
||||||
|
groupInfo: highlighterData?.groupInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
notifyLastAction("select element");
|
notifyLastAction("select element");
|
||||||
@@ -670,8 +677,8 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
if (socket) {
|
if (socket) {
|
||||||
socket.emit("dom:scroll", {
|
socket.emit("dom:scroll", {
|
||||||
deltaX,
|
deltaX,
|
||||||
deltaY
|
deltaY,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
notifyLastAction("scroll");
|
notifyLastAction("scroll");
|
||||||
}
|
}
|
||||||
@@ -799,8 +806,7 @@ export const DOMBrowserRenderer: React.FC<RRWebDOMBrowserRendererProps> = ({
|
|||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'ProxiedFont-${
|
font-family: 'ProxiedFont-${
|
||||||
font.url.split("/").pop()?.split(".")[0] ||
|
font.url.split("/").pop()?.split(".")[0] || "unknown"
|
||||||
"unknown"
|
|
||||||
}';
|
}';
|
||||||
src: url("${font.dataUrl}") format("${format}");
|
src: url("${font.dataUrl}") format("${format}");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
|
|||||||
Reference in New Issue
Block a user