diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md
index 1a7cce00b..5917d6a51 100644
--- a/docs/API-Reference/view/PanelView.md
+++ b/docs/API-Reference/view/PanelView.md
@@ -171,6 +171,18 @@ The editor holder element, passed from WorkspaceManager
## \_recomputeLayout : function
recomputeLayout callback from WorkspaceManager
+**Kind**: global variable
+
+
+## \_defaultPanelId : string \| null
+The default/quick-access panel ID
+
+**Kind**: global variable
+
+
+## \_$addBtn : jQueryObject
+The "+" button inside the tab overflow area
+
**Kind**: global variable
@@ -205,10 +217,16 @@ during drag without being overly sensitive.
Minimum panel height (matches Resizer minSize) used as a floor
when computing a sensible restore height.
+**Kind**: global constant
+
+
+## PREF\_BOTTOM\_PANEL\_MAXIMIZED
+Preference key for persisting the maximize state across reloads.
+
**Kind**: global constant
-## init($container, $tabBar, $tabsOverflow, $editorHolder, recomputeLayoutFn)
+## init($container, $tabBar, $tabsOverflow, $editorHolder, recomputeLayoutFn, defaultPanelId)
Initializes the PanelView module with references to the bottom panel container DOM elements.
Called by WorkspaceManager during htmlReady.
@@ -221,6 +239,7 @@ Called by WorkspaceManager during htmlReady.
| $tabsOverflow | jQueryObject | The scrollable area holding tab elements. |
| $editorHolder | jQueryObject | The editor holder element (for maximize height calculation). |
| recomputeLayoutFn | function | Callback to trigger workspace layout recomputation. |
+| defaultPanelId | string | The ID of the default/quick-access panel. |
diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js
index cf054caa3..38db63f2d 100644
--- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js
+++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js
@@ -41,19 +41,12 @@ 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 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
// events happen
@@ -181,7 +174,8 @@ function RemoteFunctions(config = {}) {
handleElementClick: handleElementClick,
cleanupPreviousElementState: cleanupPreviousElementState,
disableHoverListeners: disableHoverListeners,
- enableHoverListeners: enableHoverListeners
+ enableHoverListeners: enableHoverListeners,
+ redrawHighlights: redrawHighlights
};
/**
@@ -265,310 +259,141 @@ 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 (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);
}
};
@@ -608,26 +433,34 @@ 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
- element._originalHoverOutline = element.style.outline;
- const outlineColor = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41";
- element.style.outline = `1px solid ${outlineColor}`;
-
- _hoverHighlight.add(element, false);
-
- // create the info box for the hovered element
- const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox");
- if (infoBoxHandler) {
- infoBoxHandler.dismiss();
- infoBoxHandler.createInfoBox(element);
+ // 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);
}
+
+ // commented out for unified box redesign
+ // const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox");
+ // if (infoBoxHandler) {
+ // infoBoxHandler.dismiss();
+ // infoBoxHandler.createInfoBox(element);
+ // }
}
}
@@ -674,7 +507,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;
}
@@ -698,14 +531,15 @@ 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) {
- _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 +646,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 +668,7 @@ function RemoteFunctions(config = {}) {
_clickHighlight = null;
}
if (_hoverHighlight) {
+ _hoverHighlight.elements.forEach(clearElementHoverHighlight);
_hoverHighlight.clear();
_hoverHighlight = null;
}
@@ -840,13 +678,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);
}
}
@@ -934,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
@@ -1263,7 +1102,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}`;
}
}
@@ -1301,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);
}
@@ -1318,6 +1176,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 {
@@ -1380,11 +1242,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();
@@ -1449,7 +1308,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
diff --git a/src/LiveDevelopment/LivePreviewConstants.js b/src/LiveDevelopment/LivePreviewConstants.js
index ecfc9a95d..928b326b6 100644
--- a/src/LiveDevelopment/LivePreviewConstants.js
+++ b/src/LiveDevelopment/LivePreviewConstants.js
@@ -41,4 +41,5 @@ define(function main(require, exports, module) {
exports.HIGHLIGHT_CLICK = "click";
exports.PREFERENCE_SHOW_RULER_LINES = "livePreviewShowMeasurements";
+ exports.PREFERENCE_SHOW_SPACING_HANDLES = "livePreviewShowSpacingHandles";
});
diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js
index 60d4caebf..87a3a5c91 100644
--- a/src/LiveDevelopment/main.js
+++ b/src/LiveDevelopment/main.js
@@ -70,6 +70,7 @@ define(function main(require, exports, module) {
mode: LIVE_HIGHLIGHT_MODE, // will be updated when we fetch entitlements
elemHighlights: CONSTANTS.HIGHLIGHT_HOVER, // default value, this will get updated when the extension loads
showRulerLines: false, // default value, this will get updated when the extension loads
+ showSpacingHandles: true, // default value, this will get updated when the extension loads
isPaidUser: false, // will be updated when we fetch entitlements
isLoggedIn: false, // will be updated when we fetch entitlements
hasLiveEditCapability: false // handled inside _liveEditCapabilityChanged function
@@ -324,6 +325,13 @@ define(function main(require, exports, module) {
MultiBrowserLiveDev.updateConfig(config);
}
+ function updateSpacingHandlesConfig() {
+ const prefValue = PreferencesManager.get(CONSTANTS.PREFERENCE_SHOW_SPACING_HANDLES);
+ const config = MultiBrowserLiveDev.getConfig();
+ config.showSpacingHandles = prefValue !== false;
+ MultiBrowserLiveDev.updateConfig(config);
+ }
+
EventDispatcher.makeEventDispatcher(exports);
// private api
@@ -347,6 +355,7 @@ define(function main(require, exports, module) {
exports.setLivePreviewTransportBridge = setLivePreviewTransportBridge;
exports.updateElementHighlightConfig = updateElementHighlightConfig;
exports.updateRulerLinesConfig = updateRulerLinesConfig;
+ exports.updateSpacingHandlesConfig = updateSpacingHandlesConfig;
exports.getConnectionIds = MultiBrowserLiveDev.getConnectionIds;
exports.getLivePreviewDetails = MultiBrowserLiveDev.getLivePreviewDetails;
exports.hideHighlight = MultiBrowserLiveDev.hideHighlight;
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css
index 6e3536afd..0bda851c2 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css
+++ b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css
@@ -80,13 +80,9 @@
}
#live-preview-plugin-toolbar:hover .lp-settings-icon {
- display: flex;
- align-items: center;
- color: #a0a0a0;
opacity: 1;
visibility: visible;
transition: unset;
- padding-left: 7.5px;
}
.live-preview-settings input.error, .live-preview-settings input:focus.error{
@@ -98,25 +94,37 @@
.lp-settings-icon {
opacity: 0;
color: #a0a0a0;
- display: flex;
- align-items: center;
visibility: hidden;
transition: opacity 1s, visibility 0s linear 1s; /* Fade-out effect */
- padding-left: 7.5px;
+ width: 30px;
+ height: 22px;
+ padding: 1px 6px;
+ flex-shrink: 0;
+ margin-top: 3.5px;
}
.lp-device-size-icon {
- color: #a0a0a0;
+ min-width: fit-content;
display: flex;
align-items: center;
- padding-left: 7.5px;
- margin-right: 7.5px;
+ margin: 3.5px 4px 0 3px;
+ cursor: pointer;
+ background: #3C3F41;
+ box-shadow: none;
+ border: 1px solid #3C3F41;
+ box-sizing: border-box;
+ color: #a0a0a0;
+ padding: 0 0.35em;
+}
+
+.lp-device-size-icon:hover {
+ border: 1px solid rgba(0, 0, 0, 0.24) !important;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12) !important;
}
#deviceSizeBtn.btn-dropdown::after {
position: static;
- margin-top: 2px;
- margin-left: 3px;
+ margin-left: 5px;
}
.device-size-item-icon {
@@ -154,7 +162,7 @@
min-width: fit-content;
display: flex;
align-items: center;
- margin: 3px 4px 0 3px;
+ margin: 3.5px 4px 0 3px;
max-width: 80%;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js
index 6b63e3080..c33c959ce 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/main.js
+++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js
@@ -110,6 +110,12 @@ define(function (require, exports, module) {
description: Strings.LIVE_DEV_SETTINGS_SHOW_RULER_LINES_PREFERENCE
});
+ // live preview spacing handles preference (show/hide spacing handles on element selection)
+ const PREFERENCE_SHOW_SPACING_HANDLES = CONSTANTS.PREFERENCE_SHOW_SPACING_HANDLES;
+ PreferencesManager.definePreference(PREFERENCE_SHOW_SPACING_HANDLES, "boolean", true, {
+ description: Strings.LIVE_DEV_SETTINGS_SHOW_SPACING_HANDLES_PREFERENCE
+ });
+
const LIVE_PREVIEW_PANEL_ID = "live-preview-panel";
const LIVE_PREVIEW_IFRAME_ID = "panel-live-preview-frame";
const LIVE_PREVIEW_IFRAME_HTML = `
@@ -338,6 +344,7 @@ define(function (require, exports, module) {
items.push("---");
items.push(Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON);
items.push(Strings.LIVE_PREVIEW_SHOW_RULER_LINES);
+ items.push(Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES);
}
const currentMode = LiveDevelopment.getCurrentMode();
@@ -372,6 +379,12 @@ define(function (require, exports, module) {
return `✓ ${Strings.LIVE_PREVIEW_SHOW_RULER_LINES}`;
}
return `${'\u00A0'.repeat(4)}${Strings.LIVE_PREVIEW_SHOW_RULER_LINES}`;
+ } else if (item === Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES) {
+ const isEnabled = PreferencesManager.get(PREFERENCE_SHOW_SPACING_HANDLES);
+ if(isEnabled) {
+ return `✓ ${Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES}`;
+ }
+ return `${'\u00A0'.repeat(4)}${Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES}`;
}
return item;
});
@@ -429,6 +442,15 @@ define(function (require, exports, module) {
const currentValue = PreferencesManager.get(PREFERENCE_SHOW_RULER_LINES);
PreferencesManager.set(PREFERENCE_SHOW_RULER_LINES, !currentValue);
return; // Don't dismiss highlights for this option
+ } else if (item === Strings.LIVE_PREVIEW_SHOW_SPACING_HANDLES) {
+ // Don't allow spacing handles toggle if edit features are not active
+ if (!isEditFeaturesActive) {
+ return;
+ }
+ // Toggle spacing handles on/off
+ const currentValue = PreferencesManager.get(PREFERENCE_SHOW_SPACING_HANDLES);
+ PreferencesManager.set(PREFERENCE_SHOW_SPACING_HANDLES, !currentValue);
+ return; // Don't dismiss highlights for this option
}
});
@@ -1227,10 +1249,14 @@ define(function (require, exports, module) {
PreferencesManager.on("change", PREFERENCE_SHOW_RULER_LINES, function() {
LiveDevelopment.updateRulerLinesConfig();
});
+ PreferencesManager.on("change", PREFERENCE_SHOW_SPACING_HANDLES, function() {
+ LiveDevelopment.updateSpacingHandlesConfig();
+ });
- // Initialize element highlight and ruler lines config on startup
+ // Initialize element highlight, ruler lines, and spacing handles config on startup
LiveDevelopment.updateElementHighlightConfig();
LiveDevelopment.updateRulerLinesConfig();
+ LiveDevelopment.updateSpacingHandlesConfig();
LiveDevelopment.openLivePreview();
LiveDevelopment.on(LiveDevelopment.EVENT_OPEN_PREVIEW_URL, _openLivePreviewURL);
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 34ff0b665..58ab3babd 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",
@@ -232,6 +240,91 @@ 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",
+ "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",
@@ -255,6 +348,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",