Skip to content

Commit 1bf3852

Browse files
committed
fix: prefer current selection when css reverse seleting multiple matches
1 parent 63d558a commit 1bf3852

1 file changed

Lines changed: 73 additions & 30 deletions

File tree

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function RemoteFunctions(config = {}) {
3535

3636
// this will store the element that was clicked previously (before the new click)
3737
// we need this so that we can remove click styling from the previous element when a new element is clicked
38-
let previouslyClickedElement = null;
38+
let previouslySelectedElement = null;
3939

4040
var req, timeout;
4141
function animateHighlight(time) {
@@ -709,7 +709,7 @@ function RemoteFunctions(config = {}) {
709709
_clickHighlight.clear();
710710
_clickHighlight.add(element, true);
711711

712-
previouslyClickedElement = element;
712+
previouslySelectedElement = element;
713713
}
714714

715715
function disableHoverListeners() {
@@ -835,37 +835,80 @@ function RemoteFunctions(config = {}) {
835835
}
836836
}
837837

838-
// highlight a rule
838+
/**
839+
* Find the best element to select from a list of matched nodes
840+
* Prefers: previously selected element > parent of selected > first valid element
841+
* @param {NodeList} nodes - The nodes matching the CSS rule
842+
* @param {string} rule - The CSS rule used to match nodes
843+
* @returns {{element: Element|null, skipSelection: boolean}} - The element to select and whether to skip selection
844+
*/
845+
function findBestElementToSelect(nodes, rule) {
846+
let firstValidElement = null;
847+
let elementToSelect = null;
848+
849+
for (let i = 0; i < nodes.length; i++) {
850+
if(!LivePreviewView.isElementInspectable(nodes[i], true) || nodes[i].tagName === "BR") {
851+
continue;
852+
}
853+
854+
// Store the first valid element as a fallback
855+
if (!firstValidElement) {
856+
firstValidElement = nodes[i];
857+
}
858+
859+
// if hover lock timer is active, skip selection as it's already handled by handleElementClick
860+
if (_hoverLockTimer && nodes[i] === previouslySelectedElement) {
861+
return { element: null, skipSelection: true };
862+
}
863+
864+
// Check if the currently selected element or any of its parents have a highlight
865+
if (previouslySelectedElement) {
866+
if (nodes[i] === previouslySelectedElement) {
867+
// Exact match - prefer this
868+
elementToSelect = previouslySelectedElement;
869+
break;
870+
} else if (!elementToSelect &&
871+
previouslySelectedElement.closest && nodes[i] === previouslySelectedElement.closest(rule)) {
872+
// The node is a parent of the currently selected element. we stop at the first parent, after that
873+
// we only scan for exact match
874+
elementToSelect = nodes[i];
875+
}
876+
}
877+
}
878+
879+
return {
880+
element: elementToSelect || firstValidElement,
881+
skipSelection: false
882+
};
883+
}
884+
885+
/**
886+
* Highlight all elements matching a CSS rule and select the best one
887+
* @param {string} rule - The CSS rule to highlight
888+
*/
839889
function highlightRule(rule) {
840890
hideHighlight();
841-
var i, nodes = window.document.querySelectorAll(rule);
891+
const nodes = window.document.querySelectorAll(rule);
842892

843-
for (i = 0; i < nodes.length; i++) {
893+
// Highlight all matching nodes
894+
for (let i = 0; i < nodes.length; i++) {
844895
highlight(nodes[i]);
845896
}
897+
846898
if (_clickHighlight) {
847899
_clickHighlight.selector = rule;
848900
}
849901

850-
// select the first valid highlighted element
851-
let foundValidElement = false;
852-
for (i = 0; i < nodes.length; i++) {
853-
if(LivePreviewView.isElementInspectable(nodes[i], true) && nodes[i].tagName !== "BR") {
854-
// if hover lock timer is active, we don't call selectElement as,
855-
// it means that its already called by handleElementClick function
856-
if (_hoverLockTimer && nodes[i] === previouslyClickedElement) {
857-
foundValidElement = true;
858-
break;
859-
}
860-
selectElement(nodes[i]);
861-
foundValidElement = true;
862-
break;
863-
}
864-
}
902+
// Find and select the best element
903+
const { element, skipSelection } = findBestElementToSelect(nodes, rule);
865904

866-
// if no valid element present we dismiss the boxes
867-
if (!foundValidElement) {
868-
dismissUIAndCleanupState();
905+
if (!skipSelection) {
906+
if (element) {
907+
selectElement(element);
908+
} else {
909+
// No valid element found, dismiss UI
910+
dismissUIAndCleanupState();
911+
}
869912
}
870913

871914
// In edit mode, create temporary highlights AFTER selection to avoid clearing
@@ -1195,7 +1238,7 @@ function RemoteFunctions(config = {}) {
11951238
this.rememberedNodes = {};
11961239

11971240
// this check makes sure that if the element is no more in the DOM then we remove it
1198-
if (previouslyClickedElement && !previouslyClickedElement.isConnected) {
1241+
if (previouslySelectedElement && !previouslySelectedElement.isConnected) {
11991242
dismissUIAndCleanupState();
12001243
} else {
12011244
redrawEverything();
@@ -1250,14 +1293,14 @@ function RemoteFunctions(config = {}) {
12501293
* Helper function to cleanup previously clicked element highlighting and state
12511294
*/
12521295
function cleanupPreviousElementState() {
1253-
if (previouslyClickedElement) {
1254-
if (previouslyClickedElement._originalOutline !== undefined) {
1255-
previouslyClickedElement.style.outline = previouslyClickedElement._originalOutline;
1296+
if (previouslySelectedElement) {
1297+
if (previouslySelectedElement._originalOutline !== undefined) {
1298+
previouslySelectedElement.style.outline = previouslySelectedElement._originalOutline;
12561299
} else {
1257-
previouslyClickedElement.style.outline = "";
1300+
previouslySelectedElement.style.outline = "";
12581301
}
1259-
delete previouslyClickedElement._originalOutline;
1260-
previouslyClickedElement = null;
1302+
delete previouslySelectedElement._originalOutline;
1303+
previouslySelectedElement = null;
12611304
}
12621305

12631306
if (config.mode === 'edit') {

0 commit comments

Comments
 (0)