Skip to content

Commit 677986d

Browse files
committed
fix: consecutive info box editing doesn’t work as data brackets id gets stale
1 parent 116fa06 commit 677986d

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,8 +1114,67 @@ function RemoteFunctions(config = {}) {
11141114
redrawEverything();
11151115
}
11161116
} else {
1117-
// Suppression is active - re-apply outline since attrChange may have wiped it
1118-
if (previouslySelectedElement && previouslySelectedElement.isConnected) {
1117+
// Suppression is active (e.g., control box initiated a source edit)
1118+
if (previouslySelectedElement && !previouslySelectedElement.isConnected) {
1119+
let freshElement = null;
1120+
1121+
// Strategy 1: Tree path (most reliable — works even with duplicate
1122+
// text content and tag changes). Stored when suppression was activated.
1123+
if (SHARED_STATE._suppressedElementPath) {
1124+
freshElement = _getElementByTreePath(SHARED_STATE._suppressedElementPath);
1125+
}
1126+
1127+
// Strategy 2: brackets-id (works when IDs are preserved)
1128+
if (!freshElement) {
1129+
const bracketsId = previouslySelectedElement.getAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR);
1130+
if (bracketsId) {
1131+
freshElement = document.querySelector(
1132+
'[' + GLOBALS.DATA_BRACKETS_ID_ATTR + '="' + bracketsId + '"]'
1133+
);
1134+
}
1135+
}
1136+
1137+
// Strategy 3: Text + tag match (fallback — search reverse for deepest match)
1138+
if (!freshElement) {
1139+
const oldText = previouslySelectedElement.textContent;
1140+
const oldTag = previouslySelectedElement.tagName;
1141+
let candidates = document.querySelectorAll(
1142+
oldTag.toLowerCase() + '[' + GLOBALS.DATA_BRACKETS_ID_ATTR + ']'
1143+
);
1144+
for (let i = candidates.length - 1; i >= 0; i--) {
1145+
if (candidates[i].textContent === oldText) {
1146+
freshElement = candidates[i];
1147+
break;
1148+
}
1149+
}
1150+
// Broaden if tag changed (e.g. h2→footer)
1151+
if (!freshElement) {
1152+
candidates = document.querySelectorAll('[' + GLOBALS.DATA_BRACKETS_ID_ATTR + ']');
1153+
for (let i = candidates.length - 1; i >= 0; i--) {
1154+
if (candidates[i].textContent === oldText) {
1155+
freshElement = candidates[i];
1156+
break;
1157+
}
1158+
}
1159+
}
1160+
}
1161+
1162+
if (freshElement) {
1163+
freshElement._originalOutline = freshElement.style.outline;
1164+
const isEditable = freshElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR);
1165+
const outlineColor = isEditable
1166+
? COLORS.outlineEditable : COLORS.outlineNonEditable;
1167+
freshElement.style.outline = `1px solid ${outlineColor}`;
1168+
if (_clickHighlight) {
1169+
_clickHighlight.clear();
1170+
_clickHighlight.add(freshElement);
1171+
}
1172+
previouslySelectedElement = freshElement;
1173+
window.__current_ph_lp_selected = freshElement;
1174+
redrawEverything();
1175+
}
1176+
} else if (previouslySelectedElement && previouslySelectedElement.isConnected) {
1177+
// Re-apply outline since attrChange may have wiped it
11191178
const isEditable = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR);
11201179
const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable;
11211180
previouslySelectedElement.style.outline = `1px solid ${outlineColor}`;
@@ -1216,6 +1275,43 @@ function RemoteFunctions(config = {}) {
12161275
}
12171276
}
12181277

1278+
/**
1279+
* Compute the tree path of an element as an array of child indices
1280+
* from <html> down. Used to re-locate the element after re-instrumentation
1281+
* when data-brackets-id changes and text matching is ambiguous.
1282+
* E.g. [1, 0, 0, 1] means html > 2nd child > 1st child > 1st child > 2nd child.
1283+
*/
1284+
function _getTreePath(element) {
1285+
const path = [];
1286+
let el = element;
1287+
while (el && el.parentElement) {
1288+
const parent = el.parentElement;
1289+
const children = parent.children;
1290+
for (let i = 0; i < children.length; i++) {
1291+
if (children[i] === el) {
1292+
path.unshift(i);
1293+
break;
1294+
}
1295+
}
1296+
el = parent;
1297+
}
1298+
return path;
1299+
}
1300+
1301+
/**
1302+
* Find an element by its tree path (array of child indices from <html>).
1303+
*/
1304+
function _getElementByTreePath(path) {
1305+
let el = document.documentElement;
1306+
for (let i = 0; i < path.length; i++) {
1307+
if (!el || !el.children || !el.children[path[i]]) {
1308+
return null;
1309+
}
1310+
el = el.children[path[i]];
1311+
}
1312+
return el;
1313+
}
1314+
12191315
/**
12201316
* Temporarily suppress the DOM edit dismissal check in apply()
12211317
* Used when source is modified from UI panels to prevent
@@ -1228,9 +1324,14 @@ function RemoteFunctions(config = {}) {
12281324
clearTimeout(SHARED_STATE._suppressDOMEditDismissalTimeout);
12291325
}
12301326
SHARED_STATE._suppressDOMEditDismissal = true;
1327+
// Store the tree path while the element is still connected
1328+
if (previouslySelectedElement && previouslySelectedElement.isConnected) {
1329+
SHARED_STATE._suppressedElementPath = _getTreePath(previouslySelectedElement);
1330+
}
12311331
SHARED_STATE._suppressDOMEditDismissalTimeout = setTimeout(function() {
12321332
SHARED_STATE._suppressDOMEditDismissal = false;
12331333
SHARED_STATE._suppressDOMEditDismissalTimeout = null;
1334+
SHARED_STATE._suppressedElementPath = null;
12341335
}, durationMs);
12351336
}
12361337

src/nls/root/strings.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,33 @@ define({
347347
"LIVE_DEV_CB_LIST_UPPER_ROMAN": "Upper Roman",
348348
"LIVE_DEV_CB_LIST_OUTSIDE": "Outside",
349349
"LIVE_DEV_CB_LIST_INSIDE": "Inside",
350+
"LIVE_DEV_CB_TITLE_SPACING_BOX": "Spacing",
351+
"LIVE_DEV_CB_TIP_SPACING": "Margin & Padding",
352+
"LIVE_DEV_CB_LABEL_MARGIN": "Margin",
353+
"LIVE_DEV_CB_LABEL_PADDING": "Padding",
354+
"LIVE_DEV_CB_LABEL_BORDER_TOP": "Top",
355+
"LIVE_DEV_CB_LABEL_BORDER_RIGHT": "Right",
356+
"LIVE_DEV_CB_LABEL_BORDER_BOTTOM": "Bottom",
357+
"LIVE_DEV_CB_LABEL_BORDER_LEFT": "Left",
358+
"LIVE_DEV_CB_LABEL_BORDER_TL": "TL",
359+
"LIVE_DEV_CB_LABEL_BORDER_TR": "TR",
360+
"LIVE_DEV_CB_LABEL_BORDER_BR": "BR",
361+
"LIVE_DEV_CB_LABEL_BORDER_BL": "BL",
362+
"LIVE_DEV_CB_PER_SIDE": "Per side",
363+
"LIVE_DEV_CB_PER_CORNER": "Per corner",
364+
"LIVE_DEV_CB_SHADOW_ADD": "Add Shadow",
365+
"LIVE_DEV_CB_SHADOW_INSET": "Inset",
366+
"LIVE_DEV_CB_LABEL_COLUMNS": "Columns",
367+
"LIVE_DEV_CB_LABEL_ROWS": "Rows",
368+
"LIVE_DEV_CB_LABEL_JUSTIFY_ITEMS": "Justify",
369+
"LIVE_DEV_CB_LABEL_ALIGN_ITEMS": "Align",
370+
"LIVE_DEV_CB_LABEL_ROW_GAP": "Row Gap",
371+
"LIVE_DEV_CB_LABEL_COL_GAP": "Col Gap",
372+
"LIVE_DEV_CB_WEIGHT_EXTRA_LIGHT": "Extra Light",
373+
"LIVE_DEV_CB_WEIGHT_LIGHT": "Light",
374+
"LIVE_DEV_CB_WEIGHT_MEDIUM": "Medium",
375+
"LIVE_DEV_CB_WEIGHT_SEMI_BOLD": "Semi Bold",
376+
"LIVE_DEV_CB_WEIGHT_EXTRA_BOLD": "Extra Bold",
350377
"LIVE_DEV_TOAST_NOT_EDITABLE": "Element not editable - generated by script",
351378
"LIVE_DEV_COPY_TOAST_MESSAGE": "Element copied. Use 'Paste' to add it after the selected element",
352379
"LIVE_DEV_TOAST_FIXED_ELEMENT_DISMISSED": "Element doesn't scroll with page - edit boxes hidden",

0 commit comments

Comments
 (0)