From 84b8b0b0e99f8d28d5ba31b03585d650fd003e4d Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 23 Feb 2026 14:47:01 +0530 Subject: [PATCH 01/11] feat: better non-distracting highlighting on element hover/click --- .../BrowserScripts/RemoteFunctions.js | 435 +++++------------- 1 file changed, 127 insertions(+), 308 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index cf054caa3..1ad277cb8 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -41,19 +41,10 @@ function RemoteFunctions(config = {}) { // Expose the currently selected element globally for external access window.__current_ph_lp_selected = null; - var req, timeout; - function animateHighlight(time) { - if(req) { - window.cancelAnimationFrame(req); - window.clearTimeout(timeout); - } - req = window.requestAnimationFrame(redrawHighlights); - - timeout = setTimeout(function () { - window.cancelAnimationFrame(req); - req = null; - }, time * 1000); - } + const HIGHLIGHT_COLORS = { + padding: "rgba(147, 196, 125, 0.55)", + margin: "rgba(246, 178, 107, 0.66)" + }; // the following fucntions can be in the handler and live preview will call those functions when the below // events happen @@ -265,310 +256,136 @@ function RemoteFunctions(config = {}) { return element.offsetTop + (element.offsetParent ? getDocumentOffsetTop(element.offsetParent) : 0); } - function Highlight(color, trigger) { - this.color = color; + function Highlight(trigger) { this.trigger = !!trigger; this.elements = []; this.selector = ""; + this._divs = []; } Highlight.prototype = { - _elementExists: function (element) { - var i; - for (i in this.elements) { - if (this.elements[i] === element) { - return true; - } - } - return false; - }, - _makeHighlightDiv: function (element, doAnimation) { - const remoteHighlight = { - animateStartValue: { - "background-color": "rgba(0, 162, 255, 0.5)", - "opacity": 0 - }, - animateEndValue: { - "background-color": "rgba(0, 162, 255, 0)", - "opacity": 0.6 - }, - paddingStyling: { - "background-color": "rgba(200, 249, 197, 0.7)" - }, - marginStyling: { - "background-color": "rgba(249, 204, 157, 0.7)" - }, - borderColor: "rgba(200, 249, 197, 0.85)", - showPaddingMargin: true - }; - var elementBounds = element.getBoundingClientRect(), - highlightDiv = window.document.createElement("div"), - elementStyling = window.getComputedStyle(element), - transitionDuration = parseFloat(elementStyling.getPropertyValue('transition-duration')), - animationDuration = parseFloat(elementStyling.getPropertyValue('animation-duration')); - - highlightDiv.trackingElement = element; // save which node are we highlighting - - if (doAnimation) { - if (transitionDuration) { - animateHighlight(transitionDuration); - } - - if (animationDuration) { - animateHighlight(animationDuration); - } - } - - // Don't highlight elements with 0 width & height - if (elementBounds.width === 0 && elementBounds.height === 0) { - return; - } - - var realElBorder = { - right: elementStyling.getPropertyValue('border-right-width'), - left: elementStyling.getPropertyValue('border-left-width'), - top: elementStyling.getPropertyValue('border-top-width'), - bottom: elementStyling.getPropertyValue('border-bottom-width') - }; - - var borderBox = elementStyling.boxSizing === 'border-box'; - - var innerWidth = parseFloat(elementStyling.width), - innerHeight = parseFloat(elementStyling.height), - outerHeight = innerHeight, - outerWidth = innerWidth; - - if (!borderBox) { - innerWidth += parseFloat(elementStyling.paddingLeft) + parseFloat(elementStyling.paddingRight); - innerHeight += parseFloat(elementStyling.paddingTop) + parseFloat(elementStyling.paddingBottom); - outerWidth = innerWidth + parseFloat(realElBorder.right) + - parseFloat(realElBorder.left), - outerHeight = innerHeight + parseFloat(realElBorder.bottom) + parseFloat(realElBorder.top); - } - - - var visualisations = { - horizontal: "left, right", - vertical: "top, bottom" - }; - - var drawPaddingRect = function (side) { - var elStyling = {}; - - if (visualisations.horizontal.indexOf(side) >= 0) { - elStyling["width"] = elementStyling.getPropertyValue("padding-" + side); - elStyling["height"] = innerHeight + "px"; - elStyling["top"] = 0; - - if (borderBox) { - elStyling["height"] = - innerHeight - parseFloat(realElBorder.top) - parseFloat(realElBorder.bottom) + "px"; - } - } else { - elStyling["height"] = elementStyling.getPropertyValue("padding-" + side); - elStyling["width"] = innerWidth + "px"; - elStyling["left"] = 0; - - if (borderBox) { - elStyling["width"] = - innerWidth - parseFloat(realElBorder.left) - parseFloat(realElBorder.right) + "px"; - } - } - - elStyling[side] = 0; - elStyling["position"] = "absolute"; - - return elStyling; - }; - - var drawMarginRect = function (side) { - var elStyling = {}; - - var margin = []; - margin["right"] = parseFloat(elementStyling.getPropertyValue("margin-right")); - margin["top"] = parseFloat(elementStyling.getPropertyValue("margin-top")); - margin["bottom"] = parseFloat(elementStyling.getPropertyValue("margin-bottom")); - margin["left"] = parseFloat(elementStyling.getPropertyValue("margin-left")); - - if (visualisations["horizontal"].indexOf(side) >= 0) { - elStyling["width"] = elementStyling.getPropertyValue("margin-" + side); - elStyling["height"] = outerHeight + margin["top"] + margin["bottom"] + "px"; - elStyling["top"] = "-" + (margin["top"] + parseFloat(realElBorder.top)) + "px"; - } else { - elStyling["height"] = elementStyling.getPropertyValue("margin-" + side); - elStyling["width"] = outerWidth + "px"; - elStyling["left"] = "-" + realElBorder.left; - } - - elStyling[side] = "-" + (margin[side] + parseFloat(realElBorder[side])) + "px"; - elStyling["position"] = "absolute"; - - return elStyling; - }; - - var setVisibility = function (el) { - if ( - !remoteHighlight.showPaddingMargin || - parseInt(el.height, 10) <= 0 || - parseInt(el.width, 10) <= 0 - ) { - el.display = 'none'; - } else { - el.display = 'block'; - } - }; - - var paddingVisualisations = [ - drawPaddingRect("top"), - drawPaddingRect("right"), - drawPaddingRect("bottom"), - drawPaddingRect("left") - ]; - - var marginVisualisations = [ - drawMarginRect("top"), - drawMarginRect("right"), - drawMarginRect("bottom"), - drawMarginRect("left") - ]; - - var setupVisualisations = function (arr, visualConfig) { - var i; - for (i = 0; i < arr.length; i++) { - setVisibility(arr[i]); - - // Applies to every visualisationElement (padding or margin div) - arr[i]["transform"] = "none"; - var el = window.document.createElement("div"), - styles = Object.assign({}, visualConfig, arr[i]); - - _setStyleValues(styles, el.style); - - highlightDiv.appendChild(el); - } - }; - - setupVisualisations( - marginVisualisations, - remoteHighlight.marginStyling - ); - setupVisualisations( - paddingVisualisations, - remoteHighlight.paddingStyling - ); - - highlightDiv.className = GLOBALS.HIGHLIGHT_CLASSNAME; - - var offset = LivePreviewView.screenOffset(element); - - // some code to find element left/top was removed here. This seems to be relevant to box model - // live highlights. firether reading: https://github.com/adobe/brackets/pull/13357/files - // we removed this in phoenix because it was throwing the rendering of live highlight boxes in phonix - // default project at improper places. Some other cases might fail as the above code said they - // introduces that removed computation for fixing some box-model regression. If you are here to fix a - // related bug, check history of this changes in git. - - var stylesToSet = { - "left": offset.left + "px", - "top": offset.top + "px", - "width": elementBounds.width + "px", - "height": elementBounds.height + "px", - "z-index": 2147483645, - "margin": 0, - "padding": 0, - "position": "absolute", - "pointer-events": "none", - "box-shadow": "0 0 1px #fff", - "box-sizing": elementStyling.getPropertyValue('box-sizing'), - "border-right": elementStyling.getPropertyValue('border-right'), - "border-left": elementStyling.getPropertyValue('border-left'), - "border-top": elementStyling.getPropertyValue('border-top'), - "border-bottom": elementStyling.getPropertyValue('border-bottom'), - "border-color": remoteHighlight.borderColor - }; - - var mergedStyles = Object.assign({}, stylesToSet, remoteHighlight.stylesToSet); - - var animateStartValues = remoteHighlight.animateStartValue; - - var animateEndValues = remoteHighlight.animateEndValue; - - var transitionValues = { - "transition-property": "opacity, background-color, transform", - "transition-duration": "300ms, 2.3s" - }; - - function _setStyleValues(styleValues, obj) { - var prop; - - for (prop in styleValues) { - obj.setProperty(prop, styleValues[prop]); - } - } - - _setStyleValues(mergedStyles, highlightDiv.style); - _setStyleValues( - doAnimation ? animateStartValues : animateEndValues, - highlightDiv.style - ); - - - if (doAnimation) { - _setStyleValues(transitionValues, highlightDiv.style); - - window.setTimeout(function () { - _setStyleValues(animateEndValues, highlightDiv.style); - }, 20); - } - - window.document.body.appendChild(highlightDiv); - }, - - add: function (element, doAnimation) { - if (this._elementExists(element) || element === window.document) { + add: function (element) { + if (this.elements.includes(element) || element === window.document) { return; } if (this.trigger) { _trigger(element, "highlight", 1); } - this.elements.push(element); - this._makeHighlightDiv(element, doAnimation); + this._createOverlay(element); }, clear: function () { - var i, highlights = window.document.querySelectorAll("." + GLOBALS.HIGHLIGHT_CLASSNAME), - body = window.document.body; - - for (i = 0; i < highlights.length; i++) { - body.removeChild(highlights[i]); - } - - for (i = 0; i < this.elements.length; i++) { - if (this.trigger) { - _trigger(this.elements[i], "highlight", 0); + this._divs.forEach(function (div) { + if (div.parentNode) { + div.parentNode.removeChild(div); } - clearElementHoverHighlight(this.elements[i]); - } + }); + this._divs = []; + if (this.trigger) { + this.elements.forEach(function (el) { + _trigger(el, "highlight", 0); + }); + } this.elements = []; }, redraw: function () { - var i, highlighted; + const elements = this.selector + ? Array.from(window.document.querySelectorAll(this.selector)) + : this.elements.slice(); + this.clear(); + elements.forEach(function (el) { this.add(el); }, this); + }, - // When redrawing a selector-based highlight, run a new selector - // query to ensure we have the latest set of elements to highlight. - if (this.selector) { - highlighted = window.document.querySelectorAll(this.selector); + _createOverlay: function (element) { + const bounds = element.getBoundingClientRect(); + if (bounds.width === 0 && bounds.height === 0) { return; } + + const cs = window.getComputedStyle(element); + const div = window.document.createElement("div"); + div.className = GLOBALS.HIGHLIGHT_CLASSNAME; + div.trackingElement = element; + + // Parse box model values + const bt = parseFloat(cs.borderTopWidth) || 0, + br = parseFloat(cs.borderRightWidth) || 0, + bb = parseFloat(cs.borderBottomWidth) || 0, + bl = parseFloat(cs.borderLeftWidth) || 0; + const pt = parseFloat(cs.paddingTop) || 0, + pr = parseFloat(cs.paddingRight) || 0, + pb = parseFloat(cs.paddingBottom) || 0, + pl = parseFloat(cs.paddingLeft) || 0; + const mt = parseFloat(cs.marginTop) || 0, + mr = parseFloat(cs.marginRight) || 0, + mb = parseFloat(cs.marginBottom) || 0, + ml = parseFloat(cs.marginLeft) || 0; + + const isBorderBox = cs.boxSizing === "border-box"; + const w = parseFloat(cs.width) || 0; + const h = parseFloat(cs.height) || 0; + + // Dimensions inside border + let innerW, innerH; + if (isBorderBox) { + innerW = w - bl - br; + innerH = h - bt - bb; } else { - highlighted = this.elements.slice(0); + innerW = w + pl + pr; + innerH = h + pt + pb; } - - this.clear(); - for (i = 0; i < highlighted.length; i++) { - this.add(highlighted[i], false); + const contentH = innerH - pt - pb; + const outerW = innerW + bl + br; + const outerH = innerH + bt + bb; + + // Position the overlay to match the element + const offset = LivePreviewView.screenOffset(element); + const divStyle = div.style; + divStyle.position = "absolute"; + divStyle.left = offset.left + "px"; + divStyle.top = offset.top + "px"; + divStyle.width = bounds.width + "px"; + divStyle.height = bounds.height + "px"; + divStyle.zIndex = 2147483645; + divStyle.margin = "0"; + divStyle.padding = "0"; + divStyle.pointerEvents = "none"; + divStyle.boxSizing = cs.boxSizing; + divStyle.borderTopWidth = bt + "px"; + divStyle.borderRightWidth = br + "px"; + divStyle.borderBottomWidth = bb + "px"; + divStyle.borderLeftWidth = bl + "px"; + divStyle.borderStyle = "solid"; + divStyle.borderColor = "transparent"; + + // Helper to create a colored rect + function makeRect(styles, color) { + if (parseFloat(styles.width) <= 0 || parseFloat(styles.height) <= 0) { return; } + const r = window.document.createElement("div"); + r.style.position = "absolute"; + r.style.backgroundColor = color; + r.style.transform = "none"; + for (const prop in styles) { + r.style[prop] = styles[prop]; + } + div.appendChild(r); } + + // Padding rects (non-overlapping: top/bottom full width, left/right content height) + makeRect({ top: "0", left: "0", width: innerW + "px", height: pt + "px" }, HIGHLIGHT_COLORS.padding); + makeRect({ bottom: "0", left: "0", width: innerW + "px", height: pb + "px" }, HIGHLIGHT_COLORS.padding); + makeRect({ top: pt + "px", left: "0", width: pl + "px", height: contentH + "px" }, HIGHLIGHT_COLORS.padding); + makeRect({ top: pt + "px", right: "0", width: pr + "px", height: contentH + "px" }, HIGHLIGHT_COLORS.padding); + + // Margin rects (top/bottom span element width, left/right span full height) + makeRect({ top: -(mt + bt) + "px", left: -bl + "px", width: outerW + "px", height: mt + "px" }, HIGHLIGHT_COLORS.margin); + makeRect({ bottom: -(mb + bb) + "px", left: -bl + "px", width: outerW + "px", height: mb + "px" }, HIGHLIGHT_COLORS.margin); + makeRect({ top: -(mt + bt) + "px", left: -(ml + bl) + "px", width: ml + "px", height: (outerH + mt + mb) + "px" }, HIGHLIGHT_COLORS.margin); + makeRect({ top: -(mt + bt) + "px", right: -(mr + br) + "px", width: mr + "px", height: (outerH + mt + mb) + "px" }, HIGHLIGHT_COLORS.margin); + + window.document.body.appendChild(div); + this._divs.push(div); } }; @@ -608,11 +425,12 @@ function RemoteFunctions(config = {}) { // if _hoverHighlight is uninitialized, initialize it if (!_hoverHighlight && shouldShowHighlightOnHover()) { - _hoverHighlight = new Highlight("#c8f9c5", true); + _hoverHighlight = new Highlight(true); } // this is to check the user's settings, if they want to show the elements highlights on hover or click if (_hoverHighlight && shouldShowHighlightOnHover()) { + _hoverHighlight.elements.forEach(clearElementHoverHighlight); _hoverHighlight.clear(); // Store original outline to restore on hover out, then apply a blue border @@ -620,7 +438,7 @@ function RemoteFunctions(config = {}) { const outlineColor = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; element.style.outline = `1px solid ${outlineColor}`; - _hoverHighlight.add(element, false); + _hoverHighlight.add(element); // create the info box for the hovered element const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); @@ -674,7 +492,7 @@ function RemoteFunctions(config = {}) { // this should also be there when users are in highlight mode scrollElementToViewPort(element); - if(!LivePreviewView.isElementInspectable(element)) { + if(!LivePreviewView.isElementInspectable(element, true)) { return false; } @@ -702,10 +520,10 @@ function RemoteFunctions(config = {}) { element.style.outline = `1px solid ${outlineColor}`; if (!_clickHighlight) { - _clickHighlight = new Highlight("#cfc"); + _clickHighlight = new Highlight(); } _clickHighlight.clear(); - _clickHighlight.add(element, true); + _clickHighlight.add(element); previouslySelectedElement = element; window.__current_ph_lp_selected = element; @@ -812,10 +630,13 @@ function RemoteFunctions(config = {}) { clearCssSelectorHighlight(); // Create new temporary highlight for all matching elements - _cssSelectorHighlight = new Highlight("#cfc"); - for (var i = 0; i < nodes.length; i++) { - if (LivePreviewView.isElementInspectable(nodes[i], true) && nodes[i].nodeType === Node.ELEMENT_NODE) { - _cssSelectorHighlight.add(nodes[i], true); + // Skip the selected element since it already has a click highlight + _cssSelectorHighlight = new Highlight(); + for (let i = 0; i < nodes.length; i++) { + if (nodes[i] !== previouslySelectedElement && + LivePreviewView.isElementInspectable(nodes[i], true) && + nodes[i].nodeType === Node.ELEMENT_NODE) { + _cssSelectorHighlight.add(nodes[i]); } } _cssSelectorHighlight.selector = rule; @@ -831,6 +652,7 @@ function RemoteFunctions(config = {}) { _clickHighlight = null; } if (_hoverHighlight) { + _hoverHighlight.elements.forEach(clearElementHoverHighlight); _hoverHighlight.clear(); _hoverHighlight = null; } @@ -840,13 +662,13 @@ function RemoteFunctions(config = {}) { // highlight an element function highlight(element, clear) { if (!_clickHighlight) { - _clickHighlight = new Highlight("#cfc"); + _clickHighlight = new Highlight(); } if (clear) { _clickHighlight.clear(); } if (LivePreviewView.isElementInspectable(element, true) && element.nodeType === Node.ELEMENT_NODE) { - _clickHighlight.add(element, true); + _clickHighlight.add(element); } } @@ -1380,11 +1202,8 @@ function RemoteFunctions(config = {}) { }); if (config.mode === 'edit') { - // Initialize hover highlight with Chrome-like colors - _hoverHighlight = new Highlight("#c8f9c5", true); // Green similar to Chrome's padding color - - // Initialize click highlight with animation - _clickHighlight = new Highlight("#cfc", true); // Light green for click highlight + _hoverHighlight = new Highlight(true); + _clickHighlight = new Highlight(true); // register the event handlers enableHoverListeners(); From 8342087b84cf6021db5f3543a74f5d6c027a1f3a Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 23 Feb 2026 15:08:09 +0530 Subject: [PATCH 02/11] refactor: move colors as global variables --- .../BrowserScripts/RemoteFunctions.js | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 1ad277cb8..c14bdcb68 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -41,9 +41,11 @@ function RemoteFunctions(config = {}) { // Expose the currently selected element globally for external access window.__current_ph_lp_selected = null; - const HIGHLIGHT_COLORS = { - padding: "rgba(147, 196, 125, 0.55)", - margin: "rgba(246, 178, 107, 0.66)" + const COLORS = { + highlightPadding: "rgba(147, 196, 125, 0.55)", + highlightMargin: "rgba(246, 178, 107, 0.66)", + outlineEditable: "#4285F4", + outlineNonEditable: "#3C3F41" }; // the following fucntions can be in the handler and live preview will call those functions when the below @@ -372,17 +374,22 @@ function RemoteFunctions(config = {}) { div.appendChild(r); } - // Padding rects (non-overlapping: top/bottom full width, left/right content height) - makeRect({ top: "0", left: "0", width: innerW + "px", height: pt + "px" }, HIGHLIGHT_COLORS.padding); - makeRect({ bottom: "0", left: "0", width: innerW + "px", height: pb + "px" }, HIGHLIGHT_COLORS.padding); - makeRect({ top: pt + "px", left: "0", width: pl + "px", height: contentH + "px" }, HIGHLIGHT_COLORS.padding); - makeRect({ top: pt + "px", right: "0", width: pr + "px", height: contentH + "px" }, HIGHLIGHT_COLORS.padding); - - // Margin rects (top/bottom span element width, left/right span full height) - makeRect({ top: -(mt + bt) + "px", left: -bl + "px", width: outerW + "px", height: mt + "px" }, HIGHLIGHT_COLORS.margin); - makeRect({ bottom: -(mb + bb) + "px", left: -bl + "px", width: outerW + "px", height: mb + "px" }, HIGHLIGHT_COLORS.margin); - makeRect({ top: -(mt + bt) + "px", left: -(ml + bl) + "px", width: ml + "px", height: (outerH + mt + mb) + "px" }, HIGHLIGHT_COLORS.margin); - makeRect({ top: -(mt + bt) + "px", right: -(mr + br) + "px", width: mr + "px", height: (outerH + mt + mb) + "px" }, HIGHLIGHT_COLORS.margin); + // Padding rects (top/bottom full width, left/right content height) + const padColor = COLORS.highlightPadding; + makeRect({ top: "0", left: "0", width: innerW + "px", height: pt + "px" }, padColor); + makeRect({ bottom: "0", left: "0", width: innerW + "px", height: pb + "px" }, padColor); + makeRect({ top: pt + "px", left: "0", width: pl + "px", height: contentH + "px" }, padColor); + makeRect({ top: pt + "px", right: "0", width: pr + "px", height: contentH + "px" }, padColor); + + // Margin rects (top/bottom element width, left/right full height) + const margColor = COLORS.highlightMargin; + const mTop = -(mt + bt) + "px"; + const mBot = -(mb + bb) + "px"; + const fullH = (outerH + mt + mb) + "px"; + makeRect({ top: mTop, left: -bl + "px", width: outerW + "px", height: mt + "px" }, margColor); + makeRect({ bottom: mBot, left: -bl + "px", width: outerW + "px", height: mb + "px" }, margColor); + makeRect({ top: mTop, left: -(ml + bl) + "px", width: ml + "px", height: fullH }, margColor); + makeRect({ top: mTop, right: -(mr + br) + "px", width: mr + "px", height: fullH }, margColor); window.document.body.appendChild(div); this._divs.push(div); @@ -433,9 +440,10 @@ function RemoteFunctions(config = {}) { _hoverHighlight.elements.forEach(clearElementHoverHighlight); _hoverHighlight.clear(); - // Store original outline to restore on hover out, then apply a blue border + // Store original outline to restore on hover out, then apply a border element._originalHoverOutline = element.style.outline; - const outlineColor = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; + const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; element.style.outline = `1px solid ${outlineColor}`; _hoverHighlight.add(element); @@ -516,7 +524,8 @@ function RemoteFunctions(config = {}) { } element._originalOutline = element.style.outline; - const outlineColor = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; + const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; element.style.outline = `1px solid ${outlineColor}`; if (!_clickHighlight) { @@ -1085,7 +1094,8 @@ function RemoteFunctions(config = {}) { } else { // Suppression is active - re-apply outline since attrChange may have wiped it if (previouslySelectedElement && previouslySelectedElement.isConnected) { - const outlineColor = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41"; + const isEditable = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; previouslySelectedElement.style.outline = `1px solid ${outlineColor}`; } } From cc5ee3044d0d99b03eb0c2b7a2007c580c323870 Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 23 Feb 2026 21:15:27 +0530 Subject: [PATCH 03/11] chore: export redraw highlights function --- src/LiveDevelopment/BrowserScripts/RemoteFunctions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index c14bdcb68..30d8c841d 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -174,7 +174,8 @@ function RemoteFunctions(config = {}) { handleElementClick: handleElementClick, cleanupPreviousElementState: cleanupPreviousElementState, disableHoverListeners: disableHoverListeners, - enableHoverListeners: enableHoverListeners + enableHoverListeners: enableHoverListeners, + redrawHighlights: redrawHighlights }; /** From bbc927563d1e861c568a501ce97fe27c261174d2 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 24 Feb 2026 17:32:44 +0530 Subject: [PATCH 04/11] fix: stale outline remains when selected element is clicked multiple times --- .../BrowserScripts/RemoteFunctions.js | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 30d8c841d..a58c8364c 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -441,13 +441,19 @@ function RemoteFunctions(config = {}) { _hoverHighlight.elements.forEach(clearElementHoverHighlight); _hoverHighlight.clear(); - // Store original outline to restore on hover out, then apply a border - element._originalHoverOutline = element.style.outline; - const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); - const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; - element.style.outline = `1px solid ${outlineColor}`; + // Skip hover outline and overlay for the currently click-selected element. + // It already has its own outline and overlay from the click/selection flow. + // Adding hover state on top would corrupt _originalHoverOutline (it would capture + // the click outline instead of the true original) and stack duplicate overlays. + if (element !== previouslySelectedElement) { + // Store original outline to restore on hover out, then apply a border + element._originalHoverOutline = element.style.outline; + const isEditable = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; + element.style.outline = `1px solid ${outlineColor}`; - _hoverHighlight.add(element); + _hoverHighlight.add(element); + } // create the info box for the hovered element const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); @@ -1151,6 +1157,10 @@ function RemoteFunctions(config = {}) { */ function cleanupPreviousElementState() { if (previouslySelectedElement) { + // Safety net: clear any stale hover outline tracking before hideHighlight runs. + // This prevents clearElementHoverHighlight from re-applying a captured click outline + // in edge cases where _originalHoverOutline was set on the selected element. + delete previouslySelectedElement._originalHoverOutline; if (previouslySelectedElement._originalOutline !== undefined) { previouslySelectedElement.style.outline = previouslySelectedElement._originalOutline; } else { From 783baefab07512e9f33f3361734ef3341a7d119e Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 24 Feb 2026 20:39:24 +0530 Subject: [PATCH 05/11] feat: add new insert element strings --- src/nls/root/strings.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 34ff0b665..b74ff7951 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -196,6 +196,14 @@ define({ "LIVE_DEV_MORE_OPTIONS_CUT": "Cut", "LIVE_DEV_MORE_OPTIONS_COPY": "Copy", "LIVE_DEV_MORE_OPTIONS_PASTE": "Paste", + "LIVE_DEV_INSERT_ELEMENT": "Insert Element", + "LIVE_DEV_INSERT_BEFORE": "Before", + "LIVE_DEV_INSERT_AFTER": "After", + "LIVE_DEV_INSERT_INSIDE": "Inside", + "LIVE_DEV_INSERT_WRAP": "Wrap", + "LIVE_DEV_INSERT_SEARCH_PLACEHOLDER": "Search elements\u2026", + "LIVE_DEV_INSERT_COMMON": "Common", + "LIVE_DEV_INSERT_NO_RESULTS": "No matching elements", "LIVE_DEV_IMAGE_GALLERY_USE_IMAGE": "Download image", "LIVE_DEV_IMAGE_GALLERY_SELECT_DOWNLOAD_FOLDER": "Choose image download folder", "LIVE_DEV_IMAGE_GALLERY_SEARCH_PLACEHOLDER": "Search images\u2026", From 5dcb22269ee9d40bce87adcf2b3904c3c010ec23 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 25 Feb 2026 22:01:17 +0530 Subject: [PATCH 06/11] fix: prevent hot corner from coming up when live preview panel is resized --- src/LiveDevelopment/BrowserScripts/RemoteFunctions.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index a58c8364c..f2fbefc91 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -1289,7 +1289,16 @@ function RemoteFunctions(config = {}) { "dismissUIAndCleanupState": dismissUIAndCleanupState, "escapeKeyPressInEditor": _handleEscapeKeyPress, "getMode": function() { return config.mode; }, - "suppressDOMEditDismissal": suppressDOMEditDismissal + "suppressDOMEditDismissal": suppressDOMEditDismissal, + "setHotCornerHidden": function(hidden) { + if (SHARED_STATE._hotCorner && SHARED_STATE._hotCorner.hotCorner) { + if (hidden) { + SHARED_STATE._hotCorner.hotCorner.classList.add('hc-hidden'); + } else { + SHARED_STATE._hotCorner.hotCorner.classList.remove('hc-hidden'); + } + } + } }; // the below code comment is replaced by added scripts for extensibility From d638e0d5ab33121b1ec3e95d4f02bbef308c76e3 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 26 Feb 2026 18:15:44 +0530 Subject: [PATCH 07/11] feat: add quick styles strings --- src/nls/root/strings.js | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index b74ff7951..3133dc9ff 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -240,6 +240,50 @@ define({ "LIVE_DEV_STYLES_PANEL_NO_STYLES": "No styles found", "LIVE_DEV_STYLES_PANEL_PROPERTY_PLACEHOLDER": "property", "LIVE_DEV_STYLES_PANEL_VALUE_PLACEHOLDER": "value", + "LIVE_DEV_STYLES_QS_TYPOGRAPHY": "Typography", + "LIVE_DEV_STYLES_QS_BACKGROUND": "Background", + "LIVE_DEV_STYLES_QS_SIZE": "Size", + "LIVE_DEV_STYLES_QS_SPACING": "Spacing", + "LIVE_DEV_STYLES_QS_BORDER": "Border", + "LIVE_DEV_STYLES_QS_LAYOUT": "Layout", + "LIVE_DEV_STYLES_QS_EFFECTS": "Effects", + "LIVE_DEV_STYLES_QS_FONT_FAMILY": "Font", + "LIVE_DEV_STYLES_QS_FONT_SIZE": "Size", + "LIVE_DEV_STYLES_QS_FONT_WEIGHT": "Weight", + "LIVE_DEV_STYLES_QS_LINE_HEIGHT": "Line Height", + "LIVE_DEV_STYLES_QS_TEXT_COLOR": "Color", + "LIVE_DEV_STYLES_QS_TEXT_ALIGN": "Align", + "LIVE_DEV_STYLES_QS_LETTER_SPACING": "Spacing", + "LIVE_DEV_STYLES_QS_TEXT_DECORATION": "Decoration", + "LIVE_DEV_STYLES_QS_TEXT_TRANSFORM": "Transform", + "LIVE_DEV_STYLES_QS_BG_COLOR": "Color", + "LIVE_DEV_STYLES_QS_BG_IMAGE": "Image URL", + "LIVE_DEV_STYLES_QS_BG_SIZE": "Size", + "LIVE_DEV_STYLES_QS_WIDTH": "Width", + "LIVE_DEV_STYLES_QS_HEIGHT": "Height", + "LIVE_DEV_STYLES_QS_MIN_WIDTH": "Min W", + "LIVE_DEV_STYLES_QS_MAX_WIDTH": "Max W", + "LIVE_DEV_STYLES_QS_MIN_HEIGHT": "Min H", + "LIVE_DEV_STYLES_QS_MAX_HEIGHT": "Max H", + "LIVE_DEV_STYLES_QS_OVERFLOW": "Overflow", + "LIVE_DEV_STYLES_QS_MARGIN": "Margin", + "LIVE_DEV_STYLES_QS_PADDING": "Padding", + "LIVE_DEV_STYLES_QS_BORDER_WIDTH": "Width", + "LIVE_DEV_STYLES_QS_BORDER_STYLE": "Style", + "LIVE_DEV_STYLES_QS_BORDER_COLOR": "Color", + "LIVE_DEV_STYLES_QS_BORDER_RADIUS": "Radius", + "LIVE_DEV_STYLES_QS_DISPLAY": "Display", + "LIVE_DEV_STYLES_QS_POSITION": "Position", + "LIVE_DEV_STYLES_QS_FLEX_DIR": "Direction", + "LIVE_DEV_STYLES_QS_ALIGN_ITEMS": "Align", + "LIVE_DEV_STYLES_QS_JUSTIFY": "Justify", + "LIVE_DEV_STYLES_QS_GAP": "Gap", + "LIVE_DEV_STYLES_QS_Z_INDEX": "Z-Index", + "LIVE_DEV_STYLES_QS_OPACITY": "Opacity", + "LIVE_DEV_STYLES_QS_BOX_SHADOW": "Box Shadow", + "LIVE_DEV_STYLES_QS_SELECTOR": "Selector", + "LIVE_DEV_STYLES_QS_ELEMENT_STYLE": "element.style", + "LIVE_DEV_STYLES_QS_SHOW_MORE": "More properties", "LIVE_DEV_TOAST_NOT_EDITABLE": "Element not editable - generated by script", "LIVE_DEV_COPY_TOAST_MESSAGE": "Element copied. Use 'Paste' to add it after the selected element", "LIVE_DEV_TOAST_FIXED_ELEMENT_DISMISSED": "Element doesn't scroll with page - edit boxes hidden", From 1756dd10daa3f84a90e0a2f2bd77dfa76478d5f1 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 26 Feb 2026 18:23:11 +0530 Subject: [PATCH 08/11] feat: add all styles panel string --- src/nls/root/strings.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 3133dc9ff..16b303fa8 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -284,6 +284,22 @@ define({ "LIVE_DEV_STYLES_QS_SELECTOR": "Selector", "LIVE_DEV_STYLES_QS_ELEMENT_STYLE": "element.style", "LIVE_DEV_STYLES_QS_SHOW_MORE": "More properties", + "LIVE_DEV_STYLES_TAB_QUICK": "Quick Styles", + "LIVE_DEV_STYLES_TAB_ADVANCED": "Advanced Styles", + "LIVE_DEV_STYLES_TAB_COMPUTED": "Computed", + "LIVE_DEV_STYLES_FILTER_ALL": "All", + "LIVE_DEV_STYLES_FILTER_LAYOUT": "Layout", + "LIVE_DEV_STYLES_FILTER_TYPOGRAPHY": "Typography", + "LIVE_DEV_STYLES_FILTER_COLOR": "Color", + "LIVE_DEV_STYLES_FILTER_EFFECTS": "Effects", + "LIVE_DEV_STYLES_FILTER_BOX_MODEL": "Box Model", + "LIVE_DEV_STYLES_COMPUTED_SEARCH": "Filter properties\u2026", + "LIVE_DEV_STYLES_COMPUTED_NO_RESULTS": "No results found", + "LIVE_DEV_STYLES_COMPUTED_USER_AGENT": "User Agent", + "LIVE_DEV_FORMAT_BOLD": "Bold", + "LIVE_DEV_FORMAT_ITALIC": "Italic", + "LIVE_DEV_FORMAT_UNDERLINE": "Underline", + "LIVE_DEV_FORMAT_STRIKETHROUGH": "Strikethrough", "LIVE_DEV_TOAST_NOT_EDITABLE": "Element not editable - generated by script", "LIVE_DEV_COPY_TOAST_MESSAGE": "Element copied. Use 'Paste' to add it after the selected element", "LIVE_DEV_TOAST_FIXED_ELEMENT_DISMISSED": "Element doesn't scroll with page - edit boxes hidden", From 2f47df82bde4418c56801dc883319d524ef4fc2a Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 28 Feb 2026 17:13:21 +0530 Subject: [PATCH 09/11] feat: add setting for spacing handles in live preview --- src/LiveDevelopment/LivePreviewConstants.js | 1 + src/LiveDevelopment/main.js | 9 ++++++ .../Phoenix-live-preview/main.js | 28 ++++++++++++++++++- src/nls/root/strings.js | 15 ++++++++-- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/LiveDevelopment/LivePreviewConstants.js b/src/LiveDevelopment/LivePreviewConstants.js index ecfc9a95d..928b326b6 100644 --- a/src/LiveDevelopment/LivePreviewConstants.js +++ b/src/LiveDevelopment/LivePreviewConstants.js @@ -41,4 +41,5 @@ define(function main(require, exports, module) { exports.HIGHLIGHT_CLICK = "click"; exports.PREFERENCE_SHOW_RULER_LINES = "livePreviewShowMeasurements"; + exports.PREFERENCE_SHOW_SPACING_HANDLES = "livePreviewShowSpacingHandles"; }); diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 60d4caebf..87a3a5c91 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -70,6 +70,7 @@ define(function main(require, exports, module) { mode: LIVE_HIGHLIGHT_MODE, // will be updated when we fetch entitlements elemHighlights: CONSTANTS.HIGHLIGHT_HOVER, // default value, this will get updated when the extension loads showRulerLines: false, // default value, this will get updated when the extension loads + showSpacingHandles: true, // default value, this will get updated when the extension loads isPaidUser: false, // will be updated when we fetch entitlements isLoggedIn: false, // will be updated when we fetch entitlements hasLiveEditCapability: false // handled inside _liveEditCapabilityChanged function @@ -324,6 +325,13 @@ define(function main(require, exports, module) { MultiBrowserLiveDev.updateConfig(config); } + function updateSpacingHandlesConfig() { + const prefValue = PreferencesManager.get(CONSTANTS.PREFERENCE_SHOW_SPACING_HANDLES); + const config = MultiBrowserLiveDev.getConfig(); + config.showSpacingHandles = prefValue !== false; + MultiBrowserLiveDev.updateConfig(config); + } + EventDispatcher.makeEventDispatcher(exports); // private api @@ -347,6 +355,7 @@ define(function main(require, exports, module) { exports.setLivePreviewTransportBridge = setLivePreviewTransportBridge; exports.updateElementHighlightConfig = updateElementHighlightConfig; exports.updateRulerLinesConfig = updateRulerLinesConfig; + exports.updateSpacingHandlesConfig = updateSpacingHandlesConfig; exports.getConnectionIds = MultiBrowserLiveDev.getConnectionIds; exports.getLivePreviewDetails = MultiBrowserLiveDev.getLivePreviewDetails; exports.hideHighlight = MultiBrowserLiveDev.hideHighlight; diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index 6b63e3080..c33c959ce 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -110,6 +110,12 @@ define(function (require, exports, module) { description: Strings.LIVE_DEV_SETTINGS_SHOW_RULER_LINES_PREFERENCE }); + // live preview spacing handles preference (show/hide spacing handles on element selection) + const PREFERENCE_SHOW_SPACING_HANDLES = CONSTANTS.PREFERENCE_SHOW_SPACING_HANDLES; + PreferencesManager.definePreference(PREFERENCE_SHOW_SPACING_HANDLES, "boolean", true, { + description: Strings.LIVE_DEV_SETTINGS_SHOW_SPACING_HANDLES_PREFERENCE + }); + const LIVE_PREVIEW_PANEL_ID = "live-preview-panel"; const LIVE_PREVIEW_IFRAME_ID = "panel-live-preview-frame"; const LIVE_PREVIEW_IFRAME_HTML = ` @@ -338,6 +344,7 @@ define(function (require, exports, module) { items.push("---"); items.push(Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON); items.push(Strings.LIVE_PREVIEW_SHOW_RULER_LINES); + items.push(Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES); } const currentMode = LiveDevelopment.getCurrentMode(); @@ -372,6 +379,12 @@ define(function (require, exports, module) { return `✓ ${Strings.LIVE_PREVIEW_SHOW_RULER_LINES}`; } return `${'\u00A0'.repeat(4)}${Strings.LIVE_PREVIEW_SHOW_RULER_LINES}`; + } else if (item === Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES) { + const isEnabled = PreferencesManager.get(PREFERENCE_SHOW_SPACING_HANDLES); + if(isEnabled) { + return `✓ ${Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES}`; + } + return `${'\u00A0'.repeat(4)}${Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES}`; } return item; }); @@ -429,6 +442,15 @@ define(function (require, exports, module) { const currentValue = PreferencesManager.get(PREFERENCE_SHOW_RULER_LINES); PreferencesManager.set(PREFERENCE_SHOW_RULER_LINES, !currentValue); return; // Don't dismiss highlights for this option + } else if (item === Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES) { + // Don't allow spacing handles toggle if edit features are not active + if (!isEditFeaturesActive) { + return; + } + // Toggle spacing handles on/off + const currentValue = PreferencesManager.get(PREFERENCE_SHOW_SPACING_HANDLES); + PreferencesManager.set(PREFERENCE_SHOW_SPACING_HANDLES, !currentValue); + return; // Don't dismiss highlights for this option } }); @@ -1227,10 +1249,14 @@ define(function (require, exports, module) { PreferencesManager.on("change", PREFERENCE_SHOW_RULER_LINES, function() { LiveDevelopment.updateRulerLinesConfig(); }); + PreferencesManager.on("change", PREFERENCE_SHOW_SPACING_HANDLES, function() { + LiveDevelopment.updateSpacingHandlesConfig(); + }); - // Initialize element highlight and ruler lines config on startup + // Initialize element highlight, ruler lines, and spacing handles config on startup LiveDevelopment.updateElementHighlightConfig(); LiveDevelopment.updateRulerLinesConfig(); + LiveDevelopment.updateSpacingHandlesConfig(); LiveDevelopment.openLivePreview(); LiveDevelopment.on(LiveDevelopment.EVENT_OPEN_PREVIEW_URL, _openLivePreviewURL); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 16b303fa8..2199d96b3 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -240,10 +240,13 @@ define({ "LIVE_DEV_STYLES_PANEL_NO_STYLES": "No styles found", "LIVE_DEV_STYLES_PANEL_PROPERTY_PLACEHOLDER": "property", "LIVE_DEV_STYLES_PANEL_VALUE_PLACEHOLDER": "value", + "LIVE_DEV_STYLES_QS_COLORS": "Colors", + "LIVE_DEV_STYLES_QS_TEXT_COLOR_LABEL": "Text", + "LIVE_DEV_STYLES_QS_BG_COLOR_LABEL": "Background", "LIVE_DEV_STYLES_QS_TYPOGRAPHY": "Typography", - "LIVE_DEV_STYLES_QS_BACKGROUND": "Background", "LIVE_DEV_STYLES_QS_SIZE": "Size", "LIVE_DEV_STYLES_QS_SPACING": "Spacing", + "LIVE_DEV_STYLES_QS_SIZE_AND_SPACING": "Size & Spacing", "LIVE_DEV_STYLES_QS_BORDER": "Border", "LIVE_DEV_STYLES_QS_LAYOUT": "Layout", "LIVE_DEV_STYLES_QS_EFFECTS": "Effects", @@ -277,13 +280,19 @@ define({ "LIVE_DEV_STYLES_QS_FLEX_DIR": "Direction", "LIVE_DEV_STYLES_QS_ALIGN_ITEMS": "Align", "LIVE_DEV_STYLES_QS_JUSTIFY": "Justify", + "LIVE_DEV_STYLES_QS_DISTRIBUTE": "Distribute", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_PACKED": "Packed", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_BETWEEN": "Between", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_AROUND": "Around", + "LIVE_DEV_STYLES_QS_DISTRIBUTE_EVENLY": "Evenly", "LIVE_DEV_STYLES_QS_GAP": "Gap", "LIVE_DEV_STYLES_QS_Z_INDEX": "Z-Index", "LIVE_DEV_STYLES_QS_OPACITY": "Opacity", "LIVE_DEV_STYLES_QS_BOX_SHADOW": "Box Shadow", "LIVE_DEV_STYLES_QS_SELECTOR": "Selector", "LIVE_DEV_STYLES_QS_ELEMENT_STYLE": "element.style", - "LIVE_DEV_STYLES_QS_SHOW_MORE": "More properties", + "LIVE_DEV_STYLES_QS_SHOW_MORE": "Show More", + "LIVE_DEV_STYLES_QS_SHOW_LESS": "Show Less", "LIVE_DEV_STYLES_TAB_QUICK": "Quick Styles", "LIVE_DEV_STYLES_TAB_ADVANCED": "Advanced Styles", "LIVE_DEV_STYLES_TAB_COMPUTED": "Computed", @@ -323,6 +332,8 @@ define({ "LIVE_PREVIEW_MODE_EDIT": "Edit Mode", "LIVE_PREVIEW_EDIT_HIGHLIGHT_ON": "Inspect Element on Hover", "LIVE_PREVIEW_SHOW_RULER_LINES": "Show Measurements", + "LIVE_PREVIEW_SHOW_SPACING_HANDLES": "Show Spacing Handles", + "LIVE_DEV_SETTINGS_SHOW_SPACING_HANDLES_PREFERENCE": "Show spacing handles when elements are selected in live preview edit mode. Defaults to 'true'", "LIVE_PREVIEW_MODE_PREFERENCE": "'{0}' shows only the webpage, '{1}' connects the webpage to your code - click on elements to jump to their code and vice versa, '{2}' provides highlighting along with advanced element manipulation", "LIVE_PREVIEW_CONFIGURE_MODES": "Configure Live Preview Modes", From 04142e08dfc5cf2db19869c8a6985cc5bf17cf13 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 1 Mar 2026 02:42:46 +0530 Subject: [PATCH 10/11] refactor: better live preview toolbar icons positioning --- docs/API-Reference/view/PanelView.md | 21 +++++++++++- .../Phoenix-live-preview/live-preview.css | 34 ++++++++++++------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md index 1a7cce00b..5917d6a51 100644 --- a/docs/API-Reference/view/PanelView.md +++ b/docs/API-Reference/view/PanelView.md @@ -171,6 +171,18 @@ The editor holder element, passed from WorkspaceManager ## \_recomputeLayout : function recomputeLayout callback from WorkspaceManager +**Kind**: global variable + + +## \_defaultPanelId : string \| null +The default/quick-access panel ID + +**Kind**: global variable + + +## \_$addBtn : jQueryObject +The "+" button inside the tab overflow area + **Kind**: global variable @@ -205,10 +217,16 @@ during drag without being overly sensitive. Minimum panel height (matches Resizer minSize) used as a floor when computing a sensible restore height. +**Kind**: global constant + + +## PREF\_BOTTOM\_PANEL\_MAXIMIZED +Preference key for persisting the maximize state across reloads. + **Kind**: global constant -## init($container, $tabBar, $tabsOverflow, $editorHolder, recomputeLayoutFn) +## init($container, $tabBar, $tabsOverflow, $editorHolder, recomputeLayoutFn, defaultPanelId) Initializes the PanelView module with references to the bottom panel container DOM elements. Called by WorkspaceManager during htmlReady. @@ -221,6 +239,7 @@ Called by WorkspaceManager during htmlReady. | $tabsOverflow | jQueryObject | The scrollable area holding tab elements. | | $editorHolder | jQueryObject | The editor holder element (for maximize height calculation). | | recomputeLayoutFn | function | Callback to trigger workspace layout recomputation. | +| defaultPanelId | string | The ID of the default/quick-access panel. | diff --git a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css index 6e3536afd..0bda851c2 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css +++ b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css @@ -80,13 +80,9 @@ } #live-preview-plugin-toolbar:hover .lp-settings-icon { - display: flex; - align-items: center; - color: #a0a0a0; opacity: 1; visibility: visible; transition: unset; - padding-left: 7.5px; } .live-preview-settings input.error, .live-preview-settings input:focus.error{ @@ -98,25 +94,37 @@ .lp-settings-icon { opacity: 0; color: #a0a0a0; - display: flex; - align-items: center; visibility: hidden; transition: opacity 1s, visibility 0s linear 1s; /* Fade-out effect */ - padding-left: 7.5px; + width: 30px; + height: 22px; + padding: 1px 6px; + flex-shrink: 0; + margin-top: 3.5px; } .lp-device-size-icon { - color: #a0a0a0; + min-width: fit-content; display: flex; align-items: center; - padding-left: 7.5px; - margin-right: 7.5px; + margin: 3.5px 4px 0 3px; + cursor: pointer; + background: #3C3F41; + box-shadow: none; + border: 1px solid #3C3F41; + box-sizing: border-box; + color: #a0a0a0; + padding: 0 0.35em; +} + +.lp-device-size-icon:hover { + border: 1px solid rgba(0, 0, 0, 0.24) !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12) !important; } #deviceSizeBtn.btn-dropdown::after { position: static; - margin-top: 2px; - margin-left: 3px; + margin-left: 5px; } .device-size-item-icon { @@ -154,7 +162,7 @@ min-width: fit-content; display: flex; align-items: center; - margin: 3px 4px 0 3px; + margin: 3.5px 4px 0 3px; max-width: 80%; text-overflow: ellipsis; overflow: hidden; From c2b1181c9e2b2c18d191d606b5bbf9001d156b28 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 1 Mar 2026 18:26:05 +0530 Subject: [PATCH 11/11] fix: element highlighting disappears when setting is modified --- .../BrowserScripts/RemoteFunctions.js | 65 ++++++++++++------- src/nls/root/strings.js | 16 +++++ 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index f2fbefc91..38db63f2d 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -455,12 +455,12 @@ function RemoteFunctions(config = {}) { _hoverHighlight.add(element); } - // create the info box for the hovered element - const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); - if (infoBoxHandler) { - infoBoxHandler.dismiss(); - infoBoxHandler.createInfoBox(element); - } + // commented out for unified box redesign + // const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); + // if (infoBoxHandler) { + // infoBoxHandler.dismiss(); + // infoBoxHandler.createInfoBox(element); + // } } } @@ -772,23 +772,24 @@ function RemoteFunctions(config = {}) { // recreate UI boxes so that they are placed properly function redrawUIBoxes() { - if (SHARED_STATE._toolBox) { - const element = SHARED_STATE._toolBox.element; - const toolBoxHandler = LivePreviewView.getToolHandler("ToolBox"); - if (toolBoxHandler) { - toolBoxHandler.dismiss(); - toolBoxHandler.createToolBox(element); - } - } - - if (SHARED_STATE._infoBox) { - const element = SHARED_STATE._infoBox.element; - const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); - if (infoBoxHandler) { - infoBoxHandler.dismiss(); - infoBoxHandler.createInfoBox(element); - } - } + // commented out for unified box redesign + // if (SHARED_STATE._toolBox) { + // const element = SHARED_STATE._toolBox.element; + // const toolBoxHandler = LivePreviewView.getToolHandler("ToolBox"); + // if (toolBoxHandler) { + // toolBoxHandler.dismiss(); + // toolBoxHandler.createToolBox(element); + // } + // } + + // if (SHARED_STATE._infoBox) { + // const element = SHARED_STATE._infoBox.element; + // const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); + // if (infoBoxHandler) { + // infoBoxHandler.dismiss(); + // infoBoxHandler.createInfoBox(element); + // } + // } } // redraw active highlights @@ -1140,7 +1141,25 @@ function RemoteFunctions(config = {}) { _handleConfigurationChange(); } + // Preserve the currently selected element across re-registration + // so that toggling options (e.g. show measurements, show spacing handles) + // doesn't clear the element highlighting. + const selectedBeforeReregister = previouslySelectedElement; registerHandlers(); + if (!isModeChanged && !highlightModeChanged && selectedBeforeReregister + && config.mode === 'edit') { + // Restore the click highlight for the previously selected element + if (!_clickHighlight) { + _clickHighlight = new Highlight(true); + } + _clickHighlight.add(selectedBeforeReregister); + previouslySelectedElement = selectedBeforeReregister; + window.__current_ph_lp_selected = selectedBeforeReregister; + // Restore the outline + const isEditable = selectedBeforeReregister.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; + selectedBeforeReregister.style.outline = `1px solid ${outlineColor}`; + } return JSON.stringify(config); } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 2199d96b3..58ab3babd 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -291,6 +291,22 @@ define({ "LIVE_DEV_STYLES_QS_BOX_SHADOW": "Box Shadow", "LIVE_DEV_STYLES_QS_SELECTOR": "Selector", "LIVE_DEV_STYLES_QS_ELEMENT_STYLE": "element.style", + "LIVE_DEV_STYLES_QS_FLEX_WRAP": "Wrap", + "LIVE_DEV_STYLES_QS_FLEX_GROW": "Grow", + "LIVE_DEV_STYLES_QS_FLEX_SHRINK": "Shrink", + "LIVE_DEV_STYLES_QS_ORDER": "Order", + "LIVE_DEV_STYLES_QS_ALIGN_SELF": "Align Self", + "LIVE_DEV_STYLES_QS_JUSTIFY_SELF": "Justify Self", + "LIVE_DEV_STYLES_QS_GRID_TEMPLATE_COLS": "Columns", + "LIVE_DEV_STYLES_QS_GRID_TEMPLATE_ROWS": "Rows", + "LIVE_DEV_STYLES_QS_GRID_COLUMN": "Column", + "LIVE_DEV_STYLES_QS_GRID_ROW": "Row", + "LIVE_DEV_STYLES_QS_OBJECT_FIT": "Object Fit", + "LIVE_DEV_STYLES_QS_FLEX_BASIS": "Basis", + "LIVE_DEV_STYLES_QS_TOP": "Top", + "LIVE_DEV_STYLES_QS_RIGHT": "Right", + "LIVE_DEV_STYLES_QS_BOTTOM": "Bottom", + "LIVE_DEV_STYLES_QS_LEFT": "Left", "LIVE_DEV_STYLES_QS_SHOW_MORE": "Show More", "LIVE_DEV_STYLES_QS_SHOW_LESS": "Show Less", "LIVE_DEV_STYLES_TAB_QUICK": "Quick Styles",