Skip to content

Commit 9f87a18

Browse files
committed
feat: highlighting inconsistent for some elements
1 parent 1471a4b commit 9f87a18

2 files changed

Lines changed: 92 additions & 89 deletions

File tree

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 92 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,6 @@ function RemoteFunctions(config = {}) {
266266
this.elements = [];
267267
this.selector = "";
268268
this._divs = [];
269-
this._originalOutlines = new Map();
270269
}
271270

272271
Highlight.prototype = {
@@ -278,12 +277,6 @@ function RemoteFunctions(config = {}) {
278277
_trigger(element, "highlight", 1);
279278
}
280279

281-
// Save original outline and apply highlight outline
282-
this._originalOutlines.set(element, element.style.outline);
283-
const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR);
284-
const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable;
285-
element.style.outline = `1px solid ${outlineColor}`;
286-
287280
this.elements.push(element);
288281
this._createOverlay(element);
289282
},
@@ -302,14 +295,6 @@ function RemoteFunctions(config = {}) {
302295
});
303296
}
304297

305-
// Restore original outlines
306-
this.elements.forEach(function (el) {
307-
if (this._originalOutlines.has(el)) {
308-
el.style.outline = this._originalOutlines.get(el);
309-
}
310-
}, this);
311-
this._originalOutlines = new Map();
312-
313298
this.elements = [];
314299
},
315300

@@ -326,11 +311,8 @@ function RemoteFunctions(config = {}) {
326311
if (bounds.width === 0 && bounds.height === 0) { return; }
327312

328313
const cs = window.getComputedStyle(element);
329-
const div = window.document.createElement("div");
330-
div.className = GLOBALS.HIGHLIGHT_CLASSNAME;
331-
div.trackingElement = element;
332314

333-
// Parse box model values
315+
// Parse box model values (getComputedStyle always resolves to px)
334316
const bt = parseFloat(cs.borderTopWidth) || 0,
335317
br = parseFloat(cs.borderRightWidth) || 0,
336318
bb = parseFloat(cs.borderBottomWidth) || 0,
@@ -344,72 +326,107 @@ function RemoteFunctions(config = {}) {
344326
mb = parseFloat(cs.marginBottom) || 0,
345327
ml = parseFloat(cs.marginLeft) || 0;
346328

347-
const isBorderBox = cs.boxSizing === "border-box";
348-
const w = parseFloat(cs.width) || 0;
349-
const h = parseFloat(cs.height) || 0;
350-
351-
// Dimensions inside border
352-
let innerW, innerH;
353-
if (isBorderBox) {
354-
innerW = w - bl - br;
355-
innerH = h - bt - bb;
356-
} else {
357-
innerW = w + pl + pr;
358-
innerH = h + pt + pb;
359-
}
360-
const contentH = innerH - pt - pb;
361-
const outerW = innerW + bl + br;
362-
const outerH = innerH + bt + bb;
363-
364-
// Position the overlay to match the element
365-
const offset = LivePreviewView.screenOffset(element);
329+
// Compute the 4 absolute boxes exactly like dev tools:
330+
// getBoundingClientRect() always returns the border box regardless of box-sizing.
331+
const scroll = LivePreviewView.screenOffset(element);
332+
const borderBox = {
333+
left: scroll.left,
334+
top: scroll.top,
335+
width: bounds.width,
336+
height: bounds.height
337+
};
338+
const paddingBox = {
339+
left: borderBox.left + bl,
340+
top: borderBox.top + bt,
341+
width: borderBox.width - bl - br,
342+
height: borderBox.height - bt - bb
343+
};
344+
const contentBox = {
345+
left: paddingBox.left + pl,
346+
top: paddingBox.top + pt,
347+
width: paddingBox.width - pl - pr,
348+
height: paddingBox.height - pt - pb
349+
};
350+
const marginBox = {
351+
left: borderBox.left - ml,
352+
top: borderBox.top - mt,
353+
width: borderBox.width + ml + mr,
354+
height: borderBox.height + mt + mb
355+
};
356+
357+
// Container div — sized to the margin box so all rects fit inside it
358+
const div = window.document.createElement("div");
359+
div.className = GLOBALS.HIGHLIGHT_CLASSNAME;
360+
div.trackingElement = element;
366361
const divStyle = div.style;
367362
divStyle.position = "absolute";
368-
divStyle.left = offset.left + "px";
369-
divStyle.top = offset.top + "px";
370-
divStyle.width = bounds.width + "px";
371-
divStyle.height = bounds.height + "px";
363+
divStyle.left = marginBox.left + "px";
364+
divStyle.top = marginBox.top + "px";
365+
divStyle.width = marginBox.width + "px";
366+
divStyle.height = marginBox.height + "px";
372367
divStyle.zIndex = 2147483645;
373368
divStyle.margin = "0";
374369
divStyle.padding = "0";
370+
divStyle.border = "none";
375371
divStyle.pointerEvents = "none";
376-
divStyle.boxSizing = cs.boxSizing;
377-
divStyle.borderTopWidth = bt + "px";
378-
divStyle.borderRightWidth = br + "px";
379-
divStyle.borderBottomWidth = bb + "px";
380-
divStyle.borderLeftWidth = bl + "px";
381-
divStyle.borderStyle = "solid";
382-
divStyle.borderColor = "transparent";
383-
384-
// Helper to create a colored rect
385-
function makeRect(styles, color) {
386-
if (parseFloat(styles.width) <= 0 || parseFloat(styles.height) <= 0) { return; }
372+
divStyle.boxSizing = "border-box";
373+
374+
// Helper to create a colored rect at absolute page coordinates, offset by the container origin
375+
function makeRect(left, top, width, height, color) {
376+
if (width <= 0 || height <= 0) { return; }
387377
const r = window.document.createElement("div");
388378
r.style.position = "absolute";
379+
r.style.left = (left - marginBox.left) + "px";
380+
r.style.top = (top - marginBox.top) + "px";
381+
r.style.width = width + "px";
382+
r.style.height = height + "px";
389383
r.style.backgroundColor = color;
390-
r.style.transform = "none";
391-
for (const prop in styles) {
392-
r.style[prop] = styles[prop];
393-
}
394384
div.appendChild(r);
395385
}
396386

397-
// Padding rects (top/bottom full width, left/right content height)
387+
// Padding region: 4 rects filling paddingBox minus contentBox
398388
const padColor = COLORS.highlightPadding;
399-
makeRect({ top: "0", left: "0", width: innerW + "px", height: pt + "px" }, padColor);
400-
makeRect({ bottom: "0", left: "0", width: innerW + "px", height: pb + "px" }, padColor);
401-
makeRect({ top: pt + "px", left: "0", width: pl + "px", height: contentH + "px" }, padColor);
402-
makeRect({ top: pt + "px", right: "0", width: pr + "px", height: contentH + "px" }, padColor);
403-
404-
// Margin rects (top/bottom element width, left/right full height)
389+
// top padding
390+
makeRect(paddingBox.left, paddingBox.top,
391+
paddingBox.width, pt, padColor);
392+
// bottom padding
393+
makeRect(paddingBox.left, contentBox.top + contentBox.height,
394+
paddingBox.width, pb, padColor);
395+
// left padding
396+
makeRect(paddingBox.left, contentBox.top,
397+
pl, contentBox.height, padColor);
398+
// right padding
399+
makeRect(contentBox.left + contentBox.width, contentBox.top,
400+
pr, contentBox.height, padColor);
401+
402+
// Margin region: 4 rects filling marginBox minus borderBox
405403
const margColor = COLORS.highlightMargin;
406-
const mTop = -(mt + bt) + "px";
407-
const mBot = -(mb + bb) + "px";
408-
const fullH = (outerH + mt + mb) + "px";
409-
makeRect({ top: mTop, left: -bl + "px", width: outerW + "px", height: mt + "px" }, margColor);
410-
makeRect({ bottom: mBot, left: -bl + "px", width: outerW + "px", height: mb + "px" }, margColor);
411-
makeRect({ top: mTop, left: -(ml + bl) + "px", width: ml + "px", height: fullH }, margColor);
412-
makeRect({ top: mTop, right: -(mr + br) + "px", width: mr + "px", height: fullH }, margColor);
404+
// top margin
405+
makeRect(marginBox.left, marginBox.top,
406+
marginBox.width, mt, margColor);
407+
// bottom margin
408+
makeRect(marginBox.left, borderBox.top + borderBox.height,
409+
marginBox.width, mb, margColor);
410+
// left margin
411+
makeRect(marginBox.left, borderBox.top,
412+
ml, borderBox.height, margColor);
413+
// right margin
414+
makeRect(borderBox.left + borderBox.width, borderBox.top,
415+
mr, borderBox.height, margColor);
416+
417+
// Selection outline: 1px border at the border-box edge (drawn inside the border area)
418+
const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR);
419+
const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable;
420+
const outlineDiv = window.document.createElement("div");
421+
outlineDiv.style.position = "absolute";
422+
outlineDiv.style.left = (borderBox.left - marginBox.left) + "px";
423+
outlineDiv.style.top = (borderBox.top - marginBox.top) + "px";
424+
outlineDiv.style.width = borderBox.width + "px";
425+
outlineDiv.style.height = borderBox.height + "px";
426+
outlineDiv.style.border = `1px solid ${outlineColor}`;
427+
outlineDiv.style.boxSizing = "border-box";
428+
outlineDiv.style.pointerEvents = "none";
429+
div.appendChild(outlineDiv);
413430

414431
window.document.body.appendChild(div);
415432
this._divs.push(div);
@@ -451,8 +468,8 @@ function RemoteFunctions(config = {}) {
451468
if (_hoverHighlight && shouldShowHighlightOnHover()) {
452469
_hoverHighlight.clear();
453470

454-
// Skip hover outline and overlay for the currently click-selected element.
455-
// It already has its own outline and overlay from the click/selection flow,
471+
// Skip hover overlay for the currently click-selected element.
472+
// It already has its own overlay from the click/selection flow,
456473
// and adding hover state on top would stack duplicate overlays.
457474
if (element !== previouslySelectedElement) {
458475
_hoverHighlight.add(element);
@@ -508,7 +525,7 @@ function RemoteFunctions(config = {}) {
508525
* this function is responsible to select an element in the live preview
509526
* @param {Element} element - The DOM element to select
510527
* @param {boolean} [fromEditor] - If true, this is an editor-cursor-driven selection;
511-
* only lightweight highlights (outline + margin/padding) are shown, not interactive
528+
* only lightweight highlights (outline, margin/padding overlay) are shown, not interactive
512529
* UI like control box, spacing handles, or measurements.
513530
*/
514531
function selectElement(element, fromEditor) {
@@ -1113,13 +1130,6 @@ function RemoteFunctions(config = {}) {
11131130
if (previouslySelectedElement && !previouslySelectedElement.isConnected) {
11141131
dismissUIAndCleanupState();
11151132
} else {
1116-
// Re-apply outline since attrChange may have wiped it
1117-
// (e.g. user edited the style attribute in source)
1118-
if (previouslySelectedElement && previouslySelectedElement.isConnected) {
1119-
const isEditable = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR);
1120-
const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable;
1121-
previouslySelectedElement.style.outline = `1px solid ${outlineColor}`;
1122-
}
11231133
redrawEverything();
11241134
}
11251135
} else {
@@ -1177,11 +1187,6 @@ function RemoteFunctions(config = {}) {
11771187
window.__current_ph_lp_selected = freshElement;
11781188
redrawEverything();
11791189
}
1180-
} else if (previouslySelectedElement && previouslySelectedElement.isConnected) {
1181-
// Re-apply outline since attrChange may have wiped it
1182-
const isEditable = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR);
1183-
const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable;
1184-
previouslySelectedElement.style.outline = `1px solid ${outlineColor}`;
11851190
}
11861191
}
11871192
};
@@ -1253,7 +1258,7 @@ function RemoteFunctions(config = {}) {
12531258
window.__current_ph_lp_selected = null;
12541259
}
12551260

1256-
// Highlight.clear() handles both outline restoration and overlay removal
1261+
// Highlight.clear() removes all overlay divs (outline + margin/padding rects)
12571262
hideHighlight();
12581263

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

src/nls/root/strings.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ define({
190190
"LIVE_DEV_HYPERLINK_OPENS_NEW_TAB": "Opens in new tab",
191191
"LIVE_DEV_TOOLBOX_DUPLICATE": "Duplicate",
192192
"LIVE_DEV_TOOLBOX_DELETE": "Delete",
193-
"LIVE_DEV_TOOLBOX_AI": "Edit with AI",
194193
"LIVE_DEV_TOOLBOX_IMAGE_GALLERY": "Image Gallery",
195194
"LIVE_DEV_TOOLBOX_MORE_OPTIONS": "More Options",
196195
"LIVE_DEV_MORE_OPTIONS_CUT": "Cut",
@@ -393,7 +392,6 @@ define({
393392
"IMAGE_SEARCH_LIMIT_MESSAGE_THROTTLE": "Image search is temporarily unavailable due to high demand.<br>Start a paid Phoenix Pro plan to remove trial limits and continue searching.",
394393
"IMAGE_SEARCH_PRO_THROTTLE_TITLE": "Image search limit reached",
395394
"IMAGE_SEARCH_PRO_THROTTLE_MESSAGE": "Image search is temporarily unavailable due to high demand. This usually clears within an hour — please try again shortly.",
396-
"LIVE_DEV_AI_PROMPT_PLACEHOLDER": "Ask Phoenix AI to modify this element...",
397395
"LIVE_PREVIEW_CUSTOM_SERVER_BANNER": "Getting preview from your custom server {0}",
398396
"LIVE_PREVIEW_MODE_TOGGLE_PREVIEW": "Toggle Preview Mode (F8)",
399397
"LIVE_PREVIEW_MODE_PREVIEW": "Preview Mode",

0 commit comments

Comments
 (0)