diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md
index cd9055fc02..0d92aa3432 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)
@@ -231,13 +237,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
@@ -249,7 +259,8 @@ Preference key for persisting the maximize state across reloads.
## 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 +276,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 +326,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/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 cf054caa3d..80bf3a1754 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
@@ -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
@@ -61,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",
@@ -181,7 +175,9 @@ function RemoteFunctions(config = {}) {
handleElementClick: handleElementClick,
cleanupPreviousElementState: cleanupPreviousElementState,
disableHoverListeners: disableHoverListeners,
- enableHoverListeners: enableHoverListeners
+ enableHoverListeners: enableHoverListeners,
+ redrawHighlights: redrawHighlights,
+ redrawEverything: redrawEverything
};
/**
@@ -265,310 +261,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,25 +435,35 @@ 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);
+ // 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);
+ }
- // create the info box for the hovered element
- 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);
+ }
}
}
}
@@ -636,15 +473,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();
}
}
}
@@ -668,44 +507,52 @@ 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);
- if(!LivePreviewView.isElementInspectable(element)) {
+ if(!LivePreviewView.isElementInspectable(element, true)) {
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;
- 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;
@@ -778,7 +625,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,
@@ -794,34 +642,45 @@ function RemoteFunctions(config = {}) {
selectElement(element);
}
- // clear temporary CSS selector highlights
+ // clear CSS selector highlights
function clearCssSelectorHighlight() {
if (_cssSelectorHighlightTimer) {
clearTimeout(_cssSelectorHighlightTimer);
_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;
}
}
- // 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
- _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);
+ // 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]);
+ 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;
-
- // Clear temporary highlights after 2 seconds
- _cssSelectorHighlightTimer = setTimeout(clearCssSelectorHighlight, 2000);
}
// remove active highlights
@@ -831,6 +690,7 @@ function RemoteFunctions(config = {}) {
_clickHighlight = null;
}
if (_hoverHighlight) {
+ _hoverHighlight.elements.forEach(clearElementHoverHighlight);
_hoverHighlight.clear();
_hoverHighlight = null;
}
@@ -840,13 +700,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);
}
}
@@ -903,6 +763,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
@@ -919,7 +790,7 @@ function RemoteFunctions(config = {}) {
if (!skipSelection) {
if (element) {
- selectElement(element);
+ selectElement(element, true);
} else {
// No valid element found, dismiss UI
dismissUIAndCleanupState();
@@ -934,23 +805,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
@@ -1258,12 +1130,79 @@ 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 {
- // 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";
+ // 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}`;
}
}
@@ -1301,7 +1240,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 +1275,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 {
@@ -1340,6 +1301,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
@@ -1352,9 +1350,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);
}
@@ -1380,11 +1383,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();
@@ -1439,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,
@@ -1449,7 +1449,17 @@ function RemoteFunctions(config = {}) {
"dismissUIAndCleanupState": dismissUIAndCleanupState,
"escapeKeyPressInEditor": _handleEscapeKeyPress,
"getMode": function() { return config.mode; },
- "suppressDOMEditDismissal": suppressDOMEditDismissal
+ "isSyncEnabled": function() { return config.syncSourceAndPreview !== false; },
+ "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 ecfc9a95db..756d8bb1e1 100644
--- a/src/LiveDevelopment/LivePreviewConstants.js
+++ b/src/LiveDevelopment/LivePreviewConstants.js
@@ -41,4 +41,7 @@ define(function main(require, exports, module) {
exports.HIGHLIGHT_CLICK = "click";
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/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;
+ }
}
}
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 60d4caebf5..f4d2cc3527 100644
--- a/src/LiveDevelopment/main.js
+++ b/src/LiveDevelopment/main.js
@@ -70,6 +70,8 @@ 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
+ 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
@@ -324,6 +326,20 @@ 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);
+ }
+
+ 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
@@ -347,6 +363,8 @@ define(function main(require, exports, module) {
exports.setLivePreviewTransportBridge = setLivePreviewTransportBridge;
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/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;
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js
index 6b63e3080f..5ecbb0031c 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/main.js
+++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js
@@ -110,6 +110,18 @@ 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
+ });
+
+ // 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 = `
@@ -327,21 +339,28 @@ 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
];
- // Only add edit highlight option if edit features are active
- if (isEditFeaturesActive) {
+ // Add sync toggle for highlight and edit modes
+ if (isNotPreviewMode) {
items.push("---");
+ items.push(Strings.LIVE_PREVIEW_SYNC_SOURCE_AND_PREVIEW);
+ }
+
+ // 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);
}
- 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
@@ -359,6 +378,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;
@@ -372,6 +397,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;
});
@@ -409,6 +440,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) {
@@ -429,6 +465,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 +1272,18 @@ 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();
+ });
+ PreferencesManager.on("change", PREFERENCE_LIVE_PREVIEW_SYNC, function() {
+ LiveDevelopment.updateSyncConfig();
+ });
- // Initialize element highlight and ruler lines 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 d5b318785f..90811c3657 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,143 @@ 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": "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_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_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:",
+ "LIVE_DEV_ELEMENT_PROPS_USE_CUSTOM": "Use \u201c{0}\u201d",
+ "LIVE_DEV_CB_EXCEEDED_CLASSES": "+{0} more",
+ "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_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",
+ "LIVE_DEV_CB_LABEL_SIZE": "Size",
+ "LIVE_DEV_CB_LABEL_WEIGHT": "Weight",
+ "LIVE_DEV_CB_LABEL_STYLE": "Style",
+ "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",
+ "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_RESET": "Reset",
+ "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_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",
+ "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_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",
+ "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_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",
@@ -255,6 +400,10 @@ 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_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",