From 707c21cc0811f875d7aaafcf1c9b07e047423a03 Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 23 Feb 2026 14:47:01 +0530 Subject: [PATCH 01/28] 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 cf054caa3d..1ad277cb86 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 c3325f1b2ad917c56ac3383845ee20a4527f94b6 Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 23 Feb 2026 15:08:09 +0530 Subject: [PATCH 02/28] 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 1ad277cb86..c14bdcb68e 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 eb1fdaa07fbef86d9d90f104eb79b01a044932f1 Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 23 Feb 2026 21:15:27 +0530 Subject: [PATCH 03/28] 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 c14bdcb68e..30d8c841d6 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 d35928821f60099628d25896f6882bc81b694145 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 24 Feb 2026 17:32:44 +0530 Subject: [PATCH 04/28] 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 30d8c841d6..a58c8364c7 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 f073404887cd311a5e0fb7decee33de4317fda99 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 24 Feb 2026 20:39:24 +0530 Subject: [PATCH 05/28] 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 d5b318785f..768376f571 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 ebe043df4a0538ac2a3cacc23f2041784488e3b3 Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 25 Feb 2026 22:01:17 +0530 Subject: [PATCH 06/28] 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 a58c8364c7..f2fbefc91d 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 33990202468912fed46fcfe616eb6b081c756819 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 26 Feb 2026 18:15:44 +0530 Subject: [PATCH 07/28] 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 768376f571..70f8b73193 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 e5bd7d3a8ce5497b7751efc54d3af5de412e513a Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 26 Feb 2026 18:23:11 +0530 Subject: [PATCH 08/28] 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 70f8b73193..c37c2e619e 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 a0483cff70db04805d863bd293ebbe076ecbaa4f Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 28 Feb 2026 17:13:21 +0530 Subject: [PATCH 09/28] 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 ecfc9a95db..928b326b68 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 60d4caebf5..87a3a5c913 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 6b63e3080f..c33c959ce6 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 c37c2e619e..a5ed2d3aa5 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 f8b98e030ef3cb1c2c30e1a7b2530daed53d733c Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 1 Mar 2026 02:42:46 +0530 Subject: [PATCH 10/28] refactor: better live preview toolbar icons positioning --- docs/API-Reference/view/PanelView.md | 61 +++++++++++++++---- .../Phoenix-live-preview/live-preview.css | 34 +++++++---- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md index cd9055fc02..3a00fb116e 100644 --- a/docs/API-Reference/view/PanelView.md +++ b/docs/API-Reference/view/PanelView.md @@ -52,7 +52,8 @@ Determines if the panel is visible ### panel.registerCanBeShownHandler(canShowHandlerFn) ⇒ boolean -Registers a call back function that will be called just before panel is shown. The handler should return true if the panel can be shown, else return false and the panel will not be shown. +Registers a call back function that will be called just before panel is shown. The handler should return true +if the panel can be shown, else return false and the panel will not be shown. **Kind**: instance method of [Panel](#Panel) **Returns**: boolean - true if visible, false if not @@ -70,7 +71,8 @@ Returns true if th panel can be shown, else false. ### panel.registerOnCloseRequestedHandler(handler) -Registers an async handler that is called before the panel is closed via user interaction (e.g. clicking the tab close button). The handler should return `true` to allow the close, or `false` to prevent it. +Registers an async handler that is called before the panel is closed via user interaction (e.g. clicking the +tab close button). The handler should return `true` to allow the close, or `false` to prevent it. **Kind**: instance method of [Panel](#Panel) @@ -81,7 +83,9 @@ Registers an async handler that is called before the panel is closed via user in ### panel.requestClose() ⇒ Promise.<boolean> -Requests the panel to hide, invoking the registered onCloseRequested handler first (if any). If the handler returns false, the panel stays open. If it returns true or no handler is registered, `hide()` is called. +Requests the panel to hide, invoking the registered onCloseRequested handler first (if any). +If the handler returns false, the panel stays open. If it returns true or no handler is +registered, `hide()` is called. **Kind**: instance method of [Panel](#Panel) **Returns**: Promise.<boolean> - Resolves to true if the panel was hidden, false if prevented. @@ -100,7 +104,8 @@ Hides the panel ### panel.focus() ⇒ boolean -Attempts to focus the panel. Override this in panels that support focus (e.g. terminal). The default implementation returns false. +Attempts to focus the panel. Override this in panels that support focus +(e.g. terminal). The default implementation returns false. **Kind**: instance method of [Panel](#Panel) **Returns**: boolean - true if the panel accepted focus, false otherwise @@ -129,7 +134,8 @@ Updates the display title shown in the tab bar for this panel. ### panel.destroy() -Destroys the panel, removing it from the tab bar, internal maps, and the DOM. After calling this, the Panel instance should not be reused. +Destroys the panel, removing it from the tab bar, internal maps, and the DOM. +After calling this, the Panel instance should not be reused. **Kind**: instance method of [Panel](#Panel) @@ -197,6 +203,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 @@ -231,13 +249,17 @@ type for bottom panel ## MAXIMIZE\_THRESHOLD : number -Pixel threshold for detecting near-maximize state during resize. If the editor holder height is within this many pixels of zero, the panel is treated as maximized. Keeps the maximize icon responsive during drag without being overly sensitive. +Pixel threshold for detecting near-maximize state during resize. +If the editor holder height is within this many pixels of zero, the +panel is treated as maximized. Keeps the maximize icon responsive +during drag without being overly sensitive. **Kind**: global constant ## MIN\_PANEL\_HEIGHT : number -Minimum panel height (matches Resizer minSize) used as a floor when computing a sensible restore height. +Minimum panel height (matches Resizer minSize) used as a floor +when computing a sensible restore height. **Kind**: global constant @@ -245,11 +267,18 @@ Minimum panel height (matches Resizer minSize) used as a floor when computing a ## PREF\_BOTTOM\_PANEL\_MAXIMIZED Preference key for persisting the maximize state across reloads. +**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, defaultPanelId) -Initializes the PanelView module with references to the bottom panel container DOM elements. Called by WorkspaceManager during htmlReady. +Initializes the PanelView module with references to the bottom panel container DOM elements. +Called by WorkspaceManager during htmlReady. **Kind**: global function @@ -265,19 +294,26 @@ Initializes the PanelView module with references to the bottom panel container D ## exitMaximizeOnResize() -Exit maximize state without resizing (for external callers like drag-resize). Clears internal maximize state and resets the button icon. +Exit maximize state without resizing (for external callers like drag-resize). +Clears internal maximize state and resets the button icon. **Kind**: global function ## enterMaximizeOnResize() -Enter maximize state during a drag-resize that reaches the maximum height. No pre-maximize height is stored because the user arrived here via continuous dragging; a sensible default will be computed if they later click the Restore button. +Enter maximize state during a drag-resize that reaches the maximum +height. No pre-maximize height is stored because the user arrived +here via continuous dragging; a sensible default will be computed if +they later click the Restore button. **Kind**: global function ## restoreIfMaximized() -Restore the container's CSS height to the pre-maximize value and clear maximize state. Must be called BEFORE Resizer.hide() so the Resizer reads the correct height. If not maximized, this is a no-op. When the saved height is near-max or unknown, a sensible default is used. +Restore the container's CSS height to the pre-maximize value and clear maximize state. +Must be called BEFORE Resizer.hide() so the Resizer reads the correct height. +If not maximized, this is a no-op. +When the saved height is near-max or unknown, a sensible default is used. **Kind**: global function @@ -308,7 +344,8 @@ Returns the currently active (visible) bottom panel, or null if none. ## showNextPanel() ⇒ boolean -Cycle to the next open bottom panel tab. If the container is hidden or no panels are open, does nothing and returns false. +Cycle to the next open bottom panel tab. If the container is hidden +or no panels are open, does nothing and returns false. **Kind**: global function **Returns**: boolean - true if a panel switch occurred diff --git a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css index 6e3536afd7..0bda851c22 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 98cac3da8ec468b27a5db49f885cdec66538f72f Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 1 Mar 2026 18:26:05 +0530 Subject: [PATCH 11/28] 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 f2fbefc91d..38db63f2d7 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 a5ed2d3aa5..78bb7bcfc6 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", From 0bc4df750292059496e351749b272c199b12a924 Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 2 Mar 2026 14:45:59 +0530 Subject: [PATCH 12/28] feat: add hover box support in preview --- .../BrowserScripts/RemoteFunctions.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 38db63f2d7..bc4c450d8e 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -54,6 +54,7 @@ function RemoteFunctions(config = {}) { "dismiss", // when handler gets this event, it should dismiss all ui it renders in the live preview "createToolBox", "createInfoBox", + "createHoverBox", "createMoreOptionsDropdown", // render an icon or html when the selected element toolbox appears in edit mode. "renderToolBoxItem", @@ -455,12 +456,14 @@ function RemoteFunctions(config = {}) { _hoverHighlight.add(element); } - // commented out for unified box redesign - // const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); - // if (infoBoxHandler) { - // infoBoxHandler.dismiss(); - // infoBoxHandler.createInfoBox(element); - // } + // Show minimal hover tooltip (tag + dimensions) + const hoverBoxHandler = LivePreviewView.getToolHandler("HoverBox"); + if (hoverBoxHandler) { + hoverBoxHandler.dismiss(); + if (element !== previouslySelectedElement) { + hoverBoxHandler.createHoverBox(element); + } + } } } @@ -469,15 +472,17 @@ function RemoteFunctions(config = {}) { if (SHARED_STATE.isAutoScrolling) { return; } const element = event.target; - if(LivePreviewView.isElementEditable(element) && element.nodeType === Node.ELEMENT_NODE) { + // Use isElementInspectable (not isElementEditable) so that JS-rendered + // elements also get their hover highlight and hover box properly dismissed. + if(LivePreviewView.isElementInspectable(element) && element.nodeType === Node.ELEMENT_NODE) { // this is to check the user's settings, if they want to show the elements highlights on hover or click if (_hoverHighlight && shouldShowHighlightOnHover()) { _hoverHighlight.clear(); clearElementHoverHighlight(element); - // dismiss the info box - const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox"); - if (infoBoxHandler) { - infoBoxHandler.dismiss(); + // dismiss the hover box + const hoverBoxHandler = LivePreviewView.getToolHandler("HoverBox"); + if (hoverBoxHandler) { + hoverBoxHandler.dismiss(); } } } From ed88ddb58b18ad9d162893e057fdade425193d27 Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 2 Mar 2026 15:29:20 +0530 Subject: [PATCH 13/28] refactor: remove quick styles strings from styles panel --- src/nls/root/strings.js | 70 ----------------------------------------- 1 file changed, 70 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 78bb7bcfc6..27d992a9b9 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -240,76 +240,6 @@ 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_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", - "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_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_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", "LIVE_DEV_STYLES_TAB_ADVANCED": "Advanced Styles", "LIVE_DEV_STYLES_TAB_COMPUTED": "Computed", "LIVE_DEV_STYLES_FILTER_ALL": "All", From 05c9ad5badd04079b8d8b5582f3daa3e274ed4d1 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 3 Mar 2026 02:48:18 +0530 Subject: [PATCH 14/28] feat: export redraw everything function for internal use --- 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 bc4c450d8e..b63baa12f2 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -176,7 +176,8 @@ function RemoteFunctions(config = {}) { cleanupPreviousElementState: cleanupPreviousElementState, disableHoverListeners: disableHoverListeners, enableHoverListeners: enableHoverListeners, - redrawHighlights: redrawHighlights + redrawHighlights: redrawHighlights, + redrawEverything: redrawEverything }; /** From 8fa87f5d9197469ed02227a06bda347d2670b8ed Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 4 Mar 2026 00:08:11 +0530 Subject: [PATCH 15/28] feat: add setting to sync the preview and the editor --- .../BrowserScripts/LiveDevProtocolRemote.js | 7 ++++ .../BrowserScripts/RemoteFunctions.js | 4 +- src/LiveDevelopment/LivePreviewConstants.js | 2 + .../documents/LiveDocument.js | 3 +- .../protocol/LiveDevProtocol.js | 3 ++ src/LiveDevelopment/main.js | 9 +++++ .../Phoenix-live-preview/main.js | 37 +++++++++++++++++-- src/nls/root/strings.js | 4 +- 8 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js b/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js index 67702ce857..9f97dc165d 100644 --- a/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js +++ b/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js @@ -539,6 +539,13 @@ // as a live select. return; } + if (window._LD && !window._LD.isSyncEnabled()) { + // When sync is disabled, highlight the element directly in the browser + // without doing a round-trip through the editor (which would move the cursor) + var tagId = element.getAttribute('data-brackets-id'); + window._LD.highlightRule("[data-brackets-id='" + tagId + "']"); + return; + } MessageBroker.send({ "tagId": element.getAttribute('data-brackets-id'), "nodeID": element.id, diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index b63baa12f2..5d56a4ed80 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -618,7 +618,8 @@ function RemoteFunctions(config = {}) { } // send cursor movement message to editor so cursor jumps to clicked element - if (element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR)) { + if (element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) && + config.syncSourceAndPreview !== false) { MessageBroker.send({ "tagId": element.getAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR), "nodeID": element.id, @@ -1314,6 +1315,7 @@ function RemoteFunctions(config = {}) { "dismissUIAndCleanupState": dismissUIAndCleanupState, "escapeKeyPressInEditor": _handleEscapeKeyPress, "getMode": function() { return config.mode; }, + "isSyncEnabled": function() { return config.syncSourceAndPreview !== false; }, "suppressDOMEditDismissal": suppressDOMEditDismissal, "setHotCornerHidden": function(hidden) { if (SHARED_STATE._hotCorner && SHARED_STATE._hotCorner.hotCorner) { diff --git a/src/LiveDevelopment/LivePreviewConstants.js b/src/LiveDevelopment/LivePreviewConstants.js index 928b326b68..756d8bb1e1 100644 --- a/src/LiveDevelopment/LivePreviewConstants.js +++ b/src/LiveDevelopment/LivePreviewConstants.js @@ -42,4 +42,6 @@ define(function main(require, exports, module) { exports.PREFERENCE_SHOW_RULER_LINES = "livePreviewShowMeasurements"; exports.PREFERENCE_SHOW_SPACING_HANDLES = "livePreviewShowSpacingHandles"; + + exports.PREFERENCE_LIVE_PREVIEW_SYNC = "livePreviewSyncSourceAndPreview"; }); diff --git a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js index 9d5beb6260..48faddc261 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js +++ b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js @@ -192,7 +192,8 @@ define(function (require, exports, module) { if (!this.editor) { return; } - if(!_disableHighlightOnCursor){ + if(!_disableHighlightOnCursor && + PreferencesManager.get(CONSTANTS.PREFERENCE_LIVE_PREVIEW_SYNC) !== false){ this.updateHighlight(); } }; diff --git a/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js b/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js index 8f0ab12a93..70565d3340 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js +++ b/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js @@ -240,6 +240,9 @@ define(function (require, exports, module) { // hilights are enabled only in edit and highlight mode return; } + if(PreferencesManager.get(CONSTANTS.PREFERENCE_LIVE_PREVIEW_SYNC) === false){ + return; + } const liveDoc = LiveDevMultiBrowser.getCurrentLiveDoc(), activeEditor = EditorManager.getActiveEditor(), // this can be an inline editor activeFullEditor = EditorManager.getCurrentFullEditor(); diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 87a3a5c913..f4d2cc3527 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -71,6 +71,7 @@ define(function main(require, exports, module) { 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 + syncSourceAndPreview: 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 @@ -332,6 +333,13 @@ define(function main(require, exports, module) { MultiBrowserLiveDev.updateConfig(config); } + function updateSyncConfig() { + const prefValue = PreferencesManager.get(CONSTANTS.PREFERENCE_LIVE_PREVIEW_SYNC); + const config = MultiBrowserLiveDev.getConfig(); + config.syncSourceAndPreview = prefValue !== false; + MultiBrowserLiveDev.updateConfig(config); + } + EventDispatcher.makeEventDispatcher(exports); // private api @@ -356,6 +364,7 @@ define(function main(require, exports, module) { exports.updateElementHighlightConfig = updateElementHighlightConfig; exports.updateRulerLinesConfig = updateRulerLinesConfig; exports.updateSpacingHandlesConfig = updateSpacingHandlesConfig; + exports.updateSyncConfig = updateSyncConfig; 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 c33c959ce6..912f0199fb 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -116,6 +116,12 @@ define(function (require, exports, module) { description: Strings.LIVE_DEV_SETTINGS_SHOW_SPACING_HANDLES_PREFERENCE }); + // live preview sync source and preview preference + const PREFERENCE_LIVE_PREVIEW_SYNC = CONSTANTS.PREFERENCE_LIVE_PREVIEW_SYNC; + PreferencesManager.definePreference(PREFERENCE_LIVE_PREVIEW_SYNC, "boolean", true, { + description: Strings.LIVE_DEV_SETTINGS_SYNC_SOURCE_AND_PREVIEW_PREFERENCE + }); + const LIVE_PREVIEW_PANEL_ID = "live-preview-panel"; const LIVE_PREVIEW_IFRAME_ID = "panel-live-preview-frame"; const LIVE_PREVIEW_IFRAME_HTML = ` @@ -333,22 +339,30 @@ define(function (require, exports, module) { function _showModeSelectionDropdown(event) { const isEditFeaturesActive = isProEditUser; + const currentMode = LiveDevelopment.getCurrentMode(); + const isNotPreviewMode = currentMode !== LiveDevelopment.CONSTANTS.LIVE_PREVIEW_MODE; const items = [ Strings.LIVE_PREVIEW_MODE_PREVIEW, Strings.LIVE_PREVIEW_MODE_HIGHLIGHT, Strings.LIVE_PREVIEW_MODE_EDIT ]; + // Add sync toggle for highlight and edit modes + if (isNotPreviewMode) { + items.push("---"); + items.push(Strings.LIVE_PREVIEW_SYNC_SOURCE_AND_PREVIEW); + } + // Only add edit highlight option if edit features are active if (isEditFeaturesActive) { - items.push("---"); + if (!isNotPreviewMode) { + 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(); - $dropdown = new DropdownButton.DropdownButton("", items, function(item, index) { if (item === Strings.LIVE_PREVIEW_MODE_PREVIEW) { // using empty spaces to keep content aligned @@ -366,6 +380,12 @@ define(function (require, exports, module) { html: `${checkmark}${item}${crownIcon}`, enabled: true }; + } else if (item === Strings.LIVE_PREVIEW_SYNC_SOURCE_AND_PREVIEW) { + const isEnabled = PreferencesManager.get(PREFERENCE_LIVE_PREVIEW_SYNC) !== false; + if(isEnabled) { + return `✓ ${Strings.LIVE_PREVIEW_SYNC_SOURCE_AND_PREVIEW}`; + } + return `${'\u00A0'.repeat(4)}${Strings.LIVE_PREVIEW_SYNC_SOURCE_AND_PREVIEW}`; } else if (item === Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON) { const isHoverMode = PreferencesManager.get(PREFERENCE_PROJECT_ELEMENT_HIGHLIGHT) === CONSTANTS.HIGHLIGHT_HOVER; @@ -422,6 +442,11 @@ define(function (require, exports, module) { Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "proUpsellDlg", "fail"); } } + } else if (item === Strings.LIVE_PREVIEW_SYNC_SOURCE_AND_PREVIEW) { + // Toggle sync source and preview on/off + const currentValue = PreferencesManager.get(PREFERENCE_LIVE_PREVIEW_SYNC); + PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_SYNC, currentValue === false); + return; // Don't dismiss for this option } else if (item === Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON) { // Don't allow edit highlight toggle if edit features are not active if (!isEditFeaturesActive) { @@ -1252,11 +1277,15 @@ define(function (require, exports, module) { PreferencesManager.on("change", PREFERENCE_SHOW_SPACING_HANDLES, function() { LiveDevelopment.updateSpacingHandlesConfig(); }); + PreferencesManager.on("change", PREFERENCE_LIVE_PREVIEW_SYNC, function() { + LiveDevelopment.updateSyncConfig(); + }); - // Initialize element highlight, ruler lines, and spacing handles config on startup + // Initialize element highlight, ruler lines, spacing handles, and sync config on startup LiveDevelopment.updateElementHighlightConfig(); LiveDevelopment.updateRulerLinesConfig(); LiveDevelopment.updateSpacingHandlesConfig(); + LiveDevelopment.updateSyncConfig(); 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 27d992a9b9..f07b1c70dd 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -240,7 +240,7 @@ 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_TAB_ADVANCED": "Advanced Styles", + "LIVE_DEV_STYLES_TAB_ADVANCED": "Styles", "LIVE_DEV_STYLES_TAB_COMPUTED": "Computed", "LIVE_DEV_STYLES_FILTER_ALL": "All", "LIVE_DEV_STYLES_FILTER_LAYOUT": "Layout", @@ -280,6 +280,8 @@ define({ "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_SYNC_SOURCE_AND_PREVIEW": "Sync Code & Preview", + "LIVE_DEV_SETTINGS_SYNC_SOURCE_AND_PREVIEW_PREFERENCE": "Sync source code cursor with live preview element highlighting. When enabled, moving the cursor in the editor highlights the corresponding element in the live preview, and clicking an element in the live preview jumps the cursor to its source code. 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 e35a2aa7e2b4d901188e25081d7501e590e3fcec Mon Sep 17 00:00:00 2001 From: Pluto Date: Wed, 4 Mar 2026 00:12:48 +0530 Subject: [PATCH 16/28] feat: show only applicable options in the dropdown --- src/extensionsIntegrated/Phoenix-live-preview/main.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index 912f0199fb..5ecbb0031c 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -353,11 +353,9 @@ define(function (require, exports, module) { items.push(Strings.LIVE_PREVIEW_SYNC_SOURCE_AND_PREVIEW); } - // Only add edit highlight option if edit features are active - if (isEditFeaturesActive) { - if (!isNotPreviewMode) { - items.push("---"); - } + // Only add edit-specific options when in edit mode and edit features are active + const isEditMode = currentMode === LiveDevelopment.CONSTANTS.LIVE_EDIT_MODE; + if (isEditFeaturesActive && isEditMode) { items.push(Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON); items.push(Strings.LIVE_PREVIEW_SHOW_RULER_LINES); items.push(Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES); From 54e6137e3241f6f3ae8ccefda84d44e4a45fe80d Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 5 Mar 2026 03:51:26 +0530 Subject: [PATCH 17/28] feat: add info inline editing strings --- src/nls/root/strings.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index f07b1c70dd..e7461514bd 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -255,6 +255,15 @@ define({ "LIVE_DEV_FORMAT_ITALIC": "Italic", "LIVE_DEV_FORMAT_UNDERLINE": "Underline", "LIVE_DEV_FORMAT_STRIKETHROUGH": "Strikethrough", + "LIVE_DEV_ELEMENT_PROPS_TITLE": "Element Properties", + "LIVE_DEV_ELEMENT_PROPS_TAG": "Tag", + "LIVE_DEV_ELEMENT_PROPS_SIZE": "Size", + "LIVE_DEV_ELEMENT_PROPS_CLASS": "Class", + "LIVE_DEV_ELEMENT_PROPS_ID": "ID", + "LIVE_DEV_ELEMENT_PROPS_ADD_CLASS": "+ add class", + "LIVE_DEV_ELEMENT_PROPS_SEARCH_TAGS": "Search tags\u2026", + "LIVE_DEV_ELEMENT_PROPS_COMPUTED": "Computed:", + "LIVE_DEV_ELEMENT_PROPS_USE_CUSTOM": "Use \u201c{0}\u201d", "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 346a5f0195a0b1f525c933967a27a75806c0e620 Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 6 Mar 2026 11:51:08 +0530 Subject: [PATCH 18/28] feat: add link string --- src/nls/root/strings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index e7461514bd..e6fece3a06 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -260,6 +260,7 @@ define({ "LIVE_DEV_ELEMENT_PROPS_SIZE": "Size", "LIVE_DEV_ELEMENT_PROPS_CLASS": "Class", "LIVE_DEV_ELEMENT_PROPS_ID": "ID", + "LIVE_DEV_ELEMENT_PROPS_HREF": "Link", "LIVE_DEV_ELEMENT_PROPS_ADD_CLASS": "+ add class", "LIVE_DEV_ELEMENT_PROPS_SEARCH_TAGS": "Search tags\u2026", "LIVE_DEV_ELEMENT_PROPS_COMPUTED": "Computed:", From a81b8814cb60dfa0d1932b70ecff1c6271eacfc5 Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 6 Mar 2026 15:12:59 +0530 Subject: [PATCH 19/28] =?UTF-8?q?feat:=20don=E2=80=99t=20show=20the=20inte?= =?UTF-8?q?ractive=20controls=20when=20user=20selects=20an=20element=20via?= =?UTF-8?q?=20editor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BrowserScripts/RemoteFunctions.js | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 5d56a4ed80..3866099db3 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -507,8 +507,11 @@ function RemoteFunctions(config = {}) { /** * this function is responsible to select an element in the live preview * @param {Element} element - The DOM element to select + * @param {boolean} [fromEditor] - If true, this is an editor-cursor-driven selection; + * only lightweight highlights (outline + margin/padding) are shown, not interactive + * UI like control box, spacing handles, or measurements. */ - function selectElement(element) { + function selectElement(element, fromEditor) { dismissUIAndCleanupState(); // this should also be there when users are in highlight mode scrollElementToViewPort(element); @@ -517,23 +520,27 @@ function RemoteFunctions(config = {}) { return false; } - // when user clicks on a non-editable element - if (!element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR)) { - getAllToolHandlers().forEach(handler => { - if (handler.onNonEditableElementClick) { - handler.onNonEditableElementClick(element); - } - }); - } + // Only invoke tool handlers for user-initiated clicks in the live preview, + // not for editor cursor movements which should only show lightweight highlights + if (!fromEditor) { + // when user clicks on a non-editable element + if (!element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR)) { + getAllToolHandlers().forEach(handler => { + if (handler.onNonEditableElementClick) { + handler.onNonEditableElementClick(element); + } + }); + } - // make sure that the element is actually visible to the user - if (isElementVisible(element)) { - // Notify handlers about element selection - getAllToolHandlers().forEach(handler => { - if (handler.onElementSelected) { - handler.onElementSelected(element); - } - }); + // make sure that the element is actually visible to the user + if (isElementVisible(element)) { + // Notify handlers about element selection + getAllToolHandlers().forEach(handler => { + if (handler.onElementSelected) { + handler.onElementSelected(element); + } + }); + } } element._originalOutline = element.style.outline; @@ -764,7 +771,7 @@ function RemoteFunctions(config = {}) { if (!skipSelection) { if (element) { - selectElement(element); + selectElement(element, true); } else { // No valid element found, dismiss UI dismissUIAndCleanupState(); From 75aa85aa0a7a85f386536e99d599389dab29af2b Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 7 Mar 2026 00:11:35 +0530 Subject: [PATCH 20/28] chore: localize styler row strings --- src/nls/root/strings.js | 82 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index e6fece3a06..c273aa5128 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -265,6 +265,88 @@ define({ "LIVE_DEV_ELEMENT_PROPS_SEARCH_TAGS": "Search tags\u2026", "LIVE_DEV_ELEMENT_PROPS_COMPUTED": "Computed:", "LIVE_DEV_ELEMENT_PROPS_USE_CUSTOM": "Use \u201c{0}\u201d", + "LIVE_DEV_CB_EXCEEDED_CLASSES": "+{0} more", + "LIVE_DEV_CB_TITLE_FONT": "Font", + "LIVE_DEV_CB_TITLE_SPACING": "Spacing", + "LIVE_DEV_CB_TITLE_BORDER": "Border", + "LIVE_DEV_CB_TITLE_EFFECTS": "Effects", + "LIVE_DEV_CB_TITLE_LAYOUT": "Layout", + "LIVE_DEV_CB_TITLE_OBJECT_FIT": "Object Fit", + "LIVE_DEV_CB_TITLE_LIST_STYLE": "List Style", + "LIVE_DEV_CB_TIP_TEXT_COLOR": "Text Color", + "LIVE_DEV_CB_TIP_BG_COLOR": "Background Color", + "LIVE_DEV_CB_TIP_FONT": "Font", + "LIVE_DEV_CB_TIP_TEXT_SPACING": "Text Spacing", + "LIVE_DEV_CB_TIP_BORDER": "Border", + "LIVE_DEV_CB_TIP_EFFECTS": "Effects", + "LIVE_DEV_CB_TIP_LAYOUT": "Layout", + "LIVE_DEV_CB_TIP_OBJECT_FIT": "Object Fit", + "LIVE_DEV_CB_TIP_LIST_STYLE": "List Style", + "LIVE_DEV_CB_TIP_ALL_STYLES": "All Styles", + "LIVE_DEV_CB_LABEL_FAMILY": "Family", + "LIVE_DEV_CB_LABEL_SIZE": "Size", + "LIVE_DEV_CB_LABEL_WEIGHT": "Weight", + "LIVE_DEV_CB_LABEL_STYLE": "Style", + "LIVE_DEV_CB_LABEL_ALIGN": "Align", + "LIVE_DEV_CB_LABEL_TRANSFORM": "Transform", + "LIVE_DEV_CB_LABEL_LETTER_SPACING": "Letter Spacing", + "LIVE_DEV_CB_LABEL_WORD_SPACING": "Word Spacing", + "LIVE_DEV_CB_LABEL_LINE_HEIGHT": "Line Height", + "LIVE_DEV_CB_LABEL_TEXT_INDENT": "Text Indent", + "LIVE_DEV_CB_LABEL_WIDTH": "Width", + "LIVE_DEV_CB_LABEL_RADIUS": "Radius", + "LIVE_DEV_CB_LABEL_COLOR": "Color", + "LIVE_DEV_CB_LABEL_OPACITY": "Opacity", + "LIVE_DEV_CB_LABEL_SHADOW_X": "X", + "LIVE_DEV_CB_LABEL_SHADOW_Y": "Y", + "LIVE_DEV_CB_LABEL_BLUR": "Blur", + "LIVE_DEV_CB_LABEL_SPREAD": "Spread", + "LIVE_DEV_CB_LABEL_DISPLAY": "Display", + "LIVE_DEV_CB_LABEL_DIRECTION": "Direction", + "LIVE_DEV_CB_LABEL_WRAP": "Wrap", + "LIVE_DEV_CB_LABEL_JUSTIFY": "Justify", + "LIVE_DEV_CB_LABEL_GAP": "Gap", + "LIVE_DEV_CB_LABEL_OVERFLOW": "Overflow", + "LIVE_DEV_CB_LABEL_POSITION": "Position", + "LIVE_DEV_CB_LABEL_TOP": "Top", + "LIVE_DEV_CB_LABEL_RIGHT": "Right", + "LIVE_DEV_CB_LABEL_BOTTOM": "Bottom", + "LIVE_DEV_CB_LABEL_LEFT": "Left", + "LIVE_DEV_CB_LABEL_Z_INDEX": "Z-Index", + "LIVE_DEV_CB_LABEL_FIT": "Fit", + "LIVE_DEV_CB_LABEL_TYPE": "Type", + "LIVE_DEV_CB_WEIGHT_THIN": "Thin", + "LIVE_DEV_CB_WEIGHT_REGULAR": "Regular", + "LIVE_DEV_CB_WEIGHT_BOLD": "Bold", + "LIVE_DEV_CB_WEIGHT_BLACK": "Black", + "LIVE_DEV_CB_STYLE_ITALIC": "Italic", + "LIVE_DEV_CB_STYLE_UNDERLINE": "Underline", + "LIVE_DEV_CB_STYLE_STRIKETHROUGH": "Strikethrough", + "LIVE_DEV_CB_STYLE_OVERLINE": "Overline", + "LIVE_DEV_CB_ALIGN_LEFT": "Left", + "LIVE_DEV_CB_ALIGN_CENTER": "Center", + "LIVE_DEV_CB_ALIGN_RIGHT": "Right", + "LIVE_DEV_CB_ALIGN_JUSTIFY": "Justify", + "LIVE_DEV_CB_TRANSFORM_NONE": "None", + "LIVE_DEV_CB_TRANSFORM_CAPITALIZE": "Capitalize", + "LIVE_DEV_CB_TRANSFORM_UPPERCASE": "Uppercase", + "LIVE_DEV_CB_TRANSFORM_LOWERCASE": "Lowercase", + "LIVE_DEV_CB_SEARCH_FONTS": "Search fonts\u2026", + "LIVE_DEV_CB_BACK": "Back", + "LIVE_DEV_CB_BACK_ESC": "Back (Esc)", + "LIVE_DEV_CB_APPLIES_TO": "Applies to:", + "LIVE_DEV_CB_INLINE": "Inline", + "LIVE_DEV_CB_LIST_DISC": "Disc", + "LIVE_DEV_CB_LIST_CIRCLE": "Circle", + "LIVE_DEV_CB_LIST_SQUARE": "Square", + "LIVE_DEV_CB_LIST_NONE": "None", + "LIVE_DEV_CB_LIST_DECIMAL": "Decimal", + "LIVE_DEV_CB_LIST_LOWER_ALPHA": "Lower Alpha", + "LIVE_DEV_CB_LIST_UPPER_ALPHA": "Upper Alpha", + "LIVE_DEV_CB_LIST_LOWER_ROMAN": "Lower Roman", + "LIVE_DEV_CB_LIST_UPPER_ROMAN": "Upper Roman", + "LIVE_DEV_CB_LIST_OUTSIDE": "Outside", + "LIVE_DEV_CB_LIST_INSIDE": "Inside", "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 2ec0d9102a19a2c5365d32ef55c79eae2f997a3a Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 7 Mar 2026 02:22:46 +0530 Subject: [PATCH 21/28] =?UTF-8?q?fix:=20consecutive=20info=20box=20editing?= =?UTF-8?q?=20doesn=E2=80=99t=20work=20as=20data=20brackets=20id=20gets=20?= =?UTF-8?q?stale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BrowserScripts/RemoteFunctions.js | 105 +++++++++++++++++- src/nls/root/strings.js | 27 +++++ 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 3866099db3..5d94b94ad3 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -1114,8 +1114,67 @@ function RemoteFunctions(config = {}) { redrawEverything(); } } else { - // Suppression is active - re-apply outline since attrChange may have wiped it - if (previouslySelectedElement && previouslySelectedElement.isConnected) { + // Suppression is active (e.g., control box initiated a source edit) + if (previouslySelectedElement && !previouslySelectedElement.isConnected) { + let freshElement = null; + + // Strategy 1: Tree path (most reliable — works even with duplicate + // text content and tag changes). Stored when suppression was activated. + if (SHARED_STATE._suppressedElementPath) { + freshElement = _getElementByTreePath(SHARED_STATE._suppressedElementPath); + } + + // Strategy 2: brackets-id (works when IDs are preserved) + if (!freshElement) { + const bracketsId = previouslySelectedElement.getAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + if (bracketsId) { + freshElement = document.querySelector( + '[' + GLOBALS.DATA_BRACKETS_ID_ATTR + '="' + bracketsId + '"]' + ); + } + } + + // Strategy 3: Text + tag match (fallback — search reverse for deepest match) + if (!freshElement) { + const oldText = previouslySelectedElement.textContent; + const oldTag = previouslySelectedElement.tagName; + let candidates = document.querySelectorAll( + oldTag.toLowerCase() + '[' + GLOBALS.DATA_BRACKETS_ID_ATTR + ']' + ); + for (let i = candidates.length - 1; i >= 0; i--) { + if (candidates[i].textContent === oldText) { + freshElement = candidates[i]; + break; + } + } + // Broaden if tag changed (e.g. h2→footer) + if (!freshElement) { + candidates = document.querySelectorAll('[' + GLOBALS.DATA_BRACKETS_ID_ATTR + ']'); + for (let i = candidates.length - 1; i >= 0; i--) { + if (candidates[i].textContent === oldText) { + freshElement = candidates[i]; + break; + } + } + } + } + + if (freshElement) { + freshElement._originalOutline = freshElement.style.outline; + const isEditable = freshElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable + ? COLORS.outlineEditable : COLORS.outlineNonEditable; + freshElement.style.outline = `1px solid ${outlineColor}`; + if (_clickHighlight) { + _clickHighlight.clear(); + _clickHighlight.add(freshElement); + } + previouslySelectedElement = freshElement; + window.__current_ph_lp_selected = freshElement; + redrawEverything(); + } + } else if (previouslySelectedElement && previouslySelectedElement.isConnected) { + // Re-apply outline since attrChange may have wiped it const isEditable = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; previouslySelectedElement.style.outline = `1px solid ${outlineColor}`; @@ -1216,6 +1275,43 @@ function RemoteFunctions(config = {}) { } } + /** + * Compute the tree path of an element as an array of child indices + * from down. Used to re-locate the element after re-instrumentation + * when data-brackets-id changes and text matching is ambiguous. + * E.g. [1, 0, 0, 1] means html > 2nd child > 1st child > 1st child > 2nd child. + */ + function _getTreePath(element) { + const path = []; + let el = element; + while (el && el.parentElement) { + const parent = el.parentElement; + const children = parent.children; + for (let i = 0; i < children.length; i++) { + if (children[i] === el) { + path.unshift(i); + break; + } + } + el = parent; + } + return path; + } + + /** + * Find an element by its tree path (array of child indices from ). + */ + function _getElementByTreePath(path) { + let el = document.documentElement; + for (let i = 0; i < path.length; i++) { + if (!el || !el.children || !el.children[path[i]]) { + return null; + } + el = el.children[path[i]]; + } + return el; + } + /** * Temporarily suppress the DOM edit dismissal check in apply() * Used when source is modified from UI panels to prevent @@ -1228,9 +1324,14 @@ function RemoteFunctions(config = {}) { clearTimeout(SHARED_STATE._suppressDOMEditDismissalTimeout); } SHARED_STATE._suppressDOMEditDismissal = true; + // Store the tree path while the element is still connected + if (previouslySelectedElement && previouslySelectedElement.isConnected) { + SHARED_STATE._suppressedElementPath = _getTreePath(previouslySelectedElement); + } SHARED_STATE._suppressDOMEditDismissalTimeout = setTimeout(function() { SHARED_STATE._suppressDOMEditDismissal = false; SHARED_STATE._suppressDOMEditDismissalTimeout = null; + SHARED_STATE._suppressedElementPath = null; }, durationMs); } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index c273aa5128..f59fca5b59 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -347,6 +347,33 @@ define({ "LIVE_DEV_CB_LIST_UPPER_ROMAN": "Upper Roman", "LIVE_DEV_CB_LIST_OUTSIDE": "Outside", "LIVE_DEV_CB_LIST_INSIDE": "Inside", + "LIVE_DEV_CB_TITLE_SPACING_BOX": "Spacing", + "LIVE_DEV_CB_TIP_SPACING": "Margin & Padding", + "LIVE_DEV_CB_LABEL_MARGIN": "Margin", + "LIVE_DEV_CB_LABEL_PADDING": "Padding", + "LIVE_DEV_CB_LABEL_BORDER_TOP": "Top", + "LIVE_DEV_CB_LABEL_BORDER_RIGHT": "Right", + "LIVE_DEV_CB_LABEL_BORDER_BOTTOM": "Bottom", + "LIVE_DEV_CB_LABEL_BORDER_LEFT": "Left", + "LIVE_DEV_CB_LABEL_BORDER_TL": "TL", + "LIVE_DEV_CB_LABEL_BORDER_TR": "TR", + "LIVE_DEV_CB_LABEL_BORDER_BR": "BR", + "LIVE_DEV_CB_LABEL_BORDER_BL": "BL", + "LIVE_DEV_CB_PER_SIDE": "Per side", + "LIVE_DEV_CB_PER_CORNER": "Per corner", + "LIVE_DEV_CB_SHADOW_ADD": "Add Shadow", + "LIVE_DEV_CB_SHADOW_INSET": "Inset", + "LIVE_DEV_CB_LABEL_COLUMNS": "Columns", + "LIVE_DEV_CB_LABEL_ROWS": "Rows", + "LIVE_DEV_CB_LABEL_JUSTIFY_ITEMS": "Justify", + "LIVE_DEV_CB_LABEL_ALIGN_ITEMS": "Align", + "LIVE_DEV_CB_LABEL_ROW_GAP": "Row Gap", + "LIVE_DEV_CB_LABEL_COL_GAP": "Col Gap", + "LIVE_DEV_CB_WEIGHT_EXTRA_LIGHT": "Extra Light", + "LIVE_DEV_CB_WEIGHT_LIGHT": "Light", + "LIVE_DEV_CB_WEIGHT_MEDIUM": "Medium", + "LIVE_DEV_CB_WEIGHT_SEMI_BOLD": "Semi Bold", + "LIVE_DEV_CB_WEIGHT_EXTRA_BOLD": "Extra Bold", "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 f1f84b52ff254dbff2b2698ad13604a0fd8717b5 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 7 Mar 2026 16:13:16 +0530 Subject: [PATCH 22/28] fix: prevent * from highlighting the whole page --- src/LiveDevelopment/BrowserScripts/RemoteFunctions.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 5d94b94ad3..bbd0ee9d96 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -755,6 +755,17 @@ function RemoteFunctions(config = {}) { */ function highlightRule(rule) { hideHighlight(); + + // Filter out the universal selector (*) from the rule - highlighting everything + // is not useful, similar to how we skip html/body in isElementInspectable. + // The rule can be a comma-separated list of selectors (from multi-cursor), + // so we filter out any standalone * segments and keep valid ones. + rule = rule.split(",").map(s => s.trim()).filter(s => s !== "*").join(","); + if (!rule) { + dismissUIAndCleanupState(); + return; + } + const nodes = window.document.querySelectorAll(rule); // Highlight all matching nodes From 5d12c0b18123ac9801a7bff98de301b904b7bf2a Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 7 Mar 2026 16:22:13 +0530 Subject: [PATCH 23/28] fix: outline not appearing when element selected via css selector --- .../BrowserScripts/RemoteFunctions.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index bbd0ee9d96..0478292f57 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -649,6 +649,13 @@ function RemoteFunctions(config = {}) { _cssSelectorHighlightTimer = null; } if (_cssSelectorHighlight) { + // Restore original outlines on highlighted elements + _cssSelectorHighlight.elements.forEach(function (el) { + if (el._originalCssSelectorOutline !== undefined) { + el.style.outline = el._originalCssSelectorOutline; + delete el._originalCssSelectorOutline; + } + }); _cssSelectorHighlight.clear(); _cssSelectorHighlight = null; } @@ -667,6 +674,12 @@ function RemoteFunctions(config = {}) { LivePreviewView.isElementInspectable(nodes[i], true) && nodes[i].nodeType === Node.ELEMENT_NODE) { _cssSelectorHighlight.add(nodes[i]); + // Apply outline to all matching elements so they are visible + // even when they have no margin/padding + nodes[i]._originalCssSelectorOutline = nodes[i].style.outline; + const isEditable = nodes[i].hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; + nodes[i].style.outline = `1px solid ${outlineColor}`; } } _cssSelectorHighlight.selector = rule; From c877f2072d9538a5354ce851877689ef390cee6e Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 7 Mar 2026 16:37:23 +0530 Subject: [PATCH 24/28] fix: dont remove the element highlighting when selected from a css selector --- .../BrowserScripts/RemoteFunctions.js | 17 ++++++----------- src/nls/root/strings.js | 11 +++-------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 0478292f57..3d2be4c29b 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -33,7 +33,7 @@ function RemoteFunctions(config = {}) { let _clickHighlight; let _cssSelectorHighlight; // temporary highlight for CSS selector matches in edit mode let _hoverLockTimer = null; - let _cssSelectorHighlightTimer = null; // timer for clearing temporary CSS selector highlights + let _cssSelectorHighlightTimer = null; // this will store the element that was clicked previously (before the new click) // we need this so that we can remove click styling from the previous element when a new element is clicked @@ -642,7 +642,7 @@ function RemoteFunctions(config = {}) { selectElement(element); } - // clear temporary CSS selector highlights + // clear CSS selector highlights function clearCssSelectorHighlight() { if (_cssSelectorHighlightTimer) { clearTimeout(_cssSelectorHighlightTimer); @@ -661,21 +661,19 @@ function RemoteFunctions(config = {}) { } } - // create temporary CSS selector highlights for edit mode + // create CSS selector highlights for edit mode function createCssSelectorHighlight(nodes, rule) { - // Clear any existing temporary highlights + // Clear any existing highlights clearCssSelectorHighlight(); - // Create new temporary highlight for all matching elements - // Skip the selected element since it already has a click highlight + // Highlight all matching elements except the selected one + // (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]); - // Apply outline to all matching elements so they are visible - // even when they have no margin/padding nodes[i]._originalCssSelectorOutline = nodes[i].style.outline; const isEditable = nodes[i].hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; @@ -683,9 +681,6 @@ function RemoteFunctions(config = {}) { } } _cssSelectorHighlight.selector = rule; - - // Clear temporary highlights after 2 seconds - _cssSelectorHighlightTimer = setTimeout(clearCssSelectorHighlight, 2000); } // remove active highlights diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index f59fca5b59..9b51b3d2f1 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -266,13 +266,6 @@ define({ "LIVE_DEV_ELEMENT_PROPS_COMPUTED": "Computed:", "LIVE_DEV_ELEMENT_PROPS_USE_CUSTOM": "Use \u201c{0}\u201d", "LIVE_DEV_CB_EXCEEDED_CLASSES": "+{0} more", - "LIVE_DEV_CB_TITLE_FONT": "Font", - "LIVE_DEV_CB_TITLE_SPACING": "Spacing", - "LIVE_DEV_CB_TITLE_BORDER": "Border", - "LIVE_DEV_CB_TITLE_EFFECTS": "Effects", - "LIVE_DEV_CB_TITLE_LAYOUT": "Layout", - "LIVE_DEV_CB_TITLE_OBJECT_FIT": "Object Fit", - "LIVE_DEV_CB_TITLE_LIST_STYLE": "List Style", "LIVE_DEV_CB_TIP_TEXT_COLOR": "Text Color", "LIVE_DEV_CB_TIP_BG_COLOR": "Background Color", "LIVE_DEV_CB_TIP_FONT": "Font", @@ -347,7 +340,6 @@ define({ "LIVE_DEV_CB_LIST_UPPER_ROMAN": "Upper Roman", "LIVE_DEV_CB_LIST_OUTSIDE": "Outside", "LIVE_DEV_CB_LIST_INSIDE": "Inside", - "LIVE_DEV_CB_TITLE_SPACING_BOX": "Spacing", "LIVE_DEV_CB_TIP_SPACING": "Margin & Padding", "LIVE_DEV_CB_LABEL_MARGIN": "Margin", "LIVE_DEV_CB_LABEL_PADDING": "Padding", @@ -363,6 +355,9 @@ define({ "LIVE_DEV_CB_PER_CORNER": "Per corner", "LIVE_DEV_CB_SHADOW_ADD": "Add Shadow", "LIVE_DEV_CB_SHADOW_INSET": "Inset", + "LIVE_DEV_CB_COLOR_RECENT": "Recent", + "LIVE_DEV_CB_COLOR_FROM_PAGE": "From page", + "LIVE_DEV_CB_COLOR_SUGGESTED": "Suggested", "LIVE_DEV_CB_LABEL_COLUMNS": "Columns", "LIVE_DEV_CB_LABEL_ROWS": "Rows", "LIVE_DEV_CB_LABEL_JUSTIFY_ITEMS": "Justify", From 61dc2eef0271cdd5edad94c08cdcb8db27ff9816 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 7 Mar 2026 17:48:01 +0530 Subject: [PATCH 25/28] chore: auto update api docs --- docs/API-Reference/view/PanelView.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md index 3a00fb116e..0d92aa3432 100644 --- a/docs/API-Reference/view/PanelView.md +++ b/docs/API-Reference/view/PanelView.md @@ -203,18 +203,6 @@ 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 @@ -267,12 +255,6 @@ when computing a sensible restore height. ## PREF\_BOTTOM\_PANEL\_MAXIMIZED Preference key for persisting the maximize state across reloads. -**Kind**: global constant - - -## PREF\_BOTTOM\_PANEL\_MAXIMIZED -Preference key for persisting the maximize state across reloads. - **Kind**: global constant From c89fb7866152e9f3f99be3c0cdfa17791afdaaea Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 8 Mar 2026 20:53:24 +0530 Subject: [PATCH 26/28] fix: element highlighting improvements for better multi selection support --- .../BrowserScripts/RemoteFunctions.js | 9 +++++- .../documents/LiveHTMLDocument.js | 32 ++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 3d2be4c29b..80bf3a1754 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -1130,6 +1130,13 @@ function RemoteFunctions(config = {}) { if (previouslySelectedElement && !previouslySelectedElement.isConnected) { dismissUIAndCleanupState(); } else { + // Re-apply outline since attrChange may have wiped it + // (e.g. user edited the style attribute in source) + if (previouslySelectedElement && previouslySelectedElement.isConnected) { + const isEditable = previouslySelectedElement.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + const outlineColor = isEditable ? COLORS.outlineEditable : COLORS.outlineNonEditable; + previouslySelectedElement.style.outline = `1px solid ${outlineColor}`; + } redrawEverything(); } } else { @@ -1432,7 +1439,7 @@ function RemoteFunctions(config = {}) { customReturns = { // we have to do this else the minifier will strip the customReturns variable ...customReturns, "DOMEditHandler": DOMEditHandler, - "hideHighlight": hideHighlight, + "hideHighlight": dismissUIAndCleanupState, "highlight": highlight, "highlightRule": highlightRule, "redrawHighlights": redrawHighlights, diff --git a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js index 16730b2d7d..4ff3a1fadc 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js +++ b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js @@ -34,6 +34,7 @@ define(function (require, exports, module) { _ = require("thirdparty/lodash"), LiveDocument = require("LiveDevelopment/MultiBrowserImpl/documents/LiveDocument"), HTMLInstrumentation = require("LiveDevelopment/MultiBrowserImpl/language/HTMLInstrumentation"), + HTMLUtils = require("language/HTMLUtils"), CSSUtils = require("language/CSSUtils"); /** @@ -161,19 +162,28 @@ define(function (require, exports, module) { selectors = []; // check if the cursor is in a stylesheet context (internal styles) + // but skip CSS selector lookup for inline style attributes (style="...") + // since they have no selector — the element itself should be highlighted instead if (mode === "css" || mode === "text/x-scss" || mode === "text/x-less") { - // find the css selector - _.each(this.editor.getSelections(), function (sel) { - let selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); - if (selector) { - selectors.push(selector); - } - }); + var primarySel = editor.getSelection(); + var tagInfo = HTMLUtils.getTagInfo(editor, primarySel.start, true); + var isInlineStyle = tagInfo.position.tokenType === HTMLUtils.ATTR_VALUE && + tagInfo.attr.name.toLowerCase() === "style"; + + if (!isInlineStyle) { + // find the css selector + _.each(this.editor.getSelections(), function (sel) { + let selector = CSSUtils.findSelectorAtDocumentPos(editor, (sel.reversed ? sel.end : sel.start)); + if (selector) { + selectors.push(selector); + } + }); - if (selectors.length) { - // to highlight the elements that match the css selectors - this.highlightRule(selectors.join(",")); - return; + if (selectors.length) { + // to highlight the elements that match the css selectors + this.highlightRule(selectors.join(",")); + return; + } } } From 2f5a07ca44bfa7f034a5e95dc170f9cee224096e Mon Sep 17 00:00:00 2001 From: Pluto Date: Mon, 9 Mar 2026 02:58:00 +0530 Subject: [PATCH 27/28] chore: live preview spacing strings --- src/nls/root/strings.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 9b51b3d2f1..945b7dc986 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -280,7 +280,7 @@ define({ "LIVE_DEV_CB_LABEL_SIZE": "Size", "LIVE_DEV_CB_LABEL_WEIGHT": "Weight", "LIVE_DEV_CB_LABEL_STYLE": "Style", - "LIVE_DEV_CB_LABEL_ALIGN": "Align", + "LIVE_DEV_CB_LABEL_ALIGN": "Text Align", "LIVE_DEV_CB_LABEL_TRANSFORM": "Transform", "LIVE_DEV_CB_LABEL_LETTER_SPACING": "Letter Spacing", "LIVE_DEV_CB_LABEL_WORD_SPACING": "Word Spacing", @@ -327,6 +327,7 @@ define({ "LIVE_DEV_CB_SEARCH_FONTS": "Search fonts\u2026", "LIVE_DEV_CB_BACK": "Back", "LIVE_DEV_CB_BACK_ESC": "Back (Esc)", + "LIVE_DEV_CB_RESET": "Reset", "LIVE_DEV_CB_APPLIES_TO": "Applies to:", "LIVE_DEV_CB_INLINE": "Inline", "LIVE_DEV_CB_LIST_DISC": "Disc", @@ -343,6 +344,10 @@ define({ "LIVE_DEV_CB_TIP_SPACING": "Margin & Padding", "LIVE_DEV_CB_LABEL_MARGIN": "Margin", "LIVE_DEV_CB_LABEL_PADDING": "Padding", + "LIVE_DEV_CB_LABEL_CONTENT": "content", + "LIVE_DEV_CB_SPACING_LINK_INDEPENDENT": "Separate", + "LIVE_DEV_CB_SPACING_LINK_AXIS": "Pairs", + "LIVE_DEV_CB_SPACING_LINK_ALL": "All equal", "LIVE_DEV_CB_LABEL_BORDER_TOP": "Top", "LIVE_DEV_CB_LABEL_BORDER_RIGHT": "Right", "LIVE_DEV_CB_LABEL_BORDER_BOTTOM": "Bottom", From 48548147d58d8a9c90f1eb5393bb3ed08400c369 Mon Sep 17 00:00:00 2001 From: Pluto Date: Tue, 10 Mar 2026 04:17:49 +0530 Subject: [PATCH 28/28] feat: add styler row drill in strings --- src/nls/root/strings.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 945b7dc986..90811c3657 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -274,6 +274,8 @@ define({ "LIVE_DEV_CB_TIP_EFFECTS": "Effects", "LIVE_DEV_CB_TIP_LAYOUT": "Layout", "LIVE_DEV_CB_TIP_OBJECT_FIT": "Object Fit", + "LIVE_DEV_CB_TIP_IMAGE": "Image", + "LIVE_DEV_CB_CHANGE_IMAGE": "Change Image", "LIVE_DEV_CB_TIP_LIST_STYLE": "List Style", "LIVE_DEV_CB_TIP_ALL_STYLES": "All Styles", "LIVE_DEV_CB_LABEL_FAMILY": "Family", @@ -374,6 +376,7 @@ define({ "LIVE_DEV_CB_WEIGHT_MEDIUM": "Medium", "LIVE_DEV_CB_WEIGHT_SEMI_BOLD": "Semi Bold", "LIVE_DEV_CB_WEIGHT_EXTRA_BOLD": "Extra Bold", + "LIVE_DEV_CB_GO_TO_SOURCE": "Go to source", "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",