diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 3ae76fb3c8..b79eae6201 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -595,14 +595,13 @@ function RemoteFunctions(config = {}) { } const element = event.target; - if(!LivePreviewView.isElementInspectable(element) || element.nodeType !== Node.ELEMENT_NODE) { - return; - } - // Same element as last hover — nothing changed, skip entirely if (element === _lastHoverTarget) { return; } + if(!LivePreviewView.isElementInspectable(element) || element.nodeType !== Node.ELEMENT_NODE) { + return; + } _lastHoverTarget = element; // if _hoverHighlight is uninitialized, initialize it @@ -615,21 +614,30 @@ function RemoteFunctions(config = {}) { } } - function onElementHoverOut(event) { - // don't want highlighting and stuff when auto scrolling - if (SHARED_STATE.isAutoScrolling) { return; } + function _clearHoverState() { + if (SHARED_STATE.isAutoScrolling) { + return; + } + if (_hoverHighlight && shouldShowHighlightOnHover()) { + _lastHoverTarget = null; + _scheduleHoverUpdate(); + } + } + function onElementHoverOut(event) { const element = event.target; // 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) { - if (_hoverHighlight && shouldShowHighlightOnHover()) { - _lastHoverTarget = null; - _scheduleHoverUpdate(); - } + _clearHoverState(); } } + // for popped out window: the in-panel iframe case is forwarded parent-side via _LD.clearHoverState(). + function onDocumentMouseLeave() { + _clearHoverState(); + } + function scrollElementToViewPort(element) { if (!element) { return; @@ -711,7 +719,9 @@ function RemoteFunctions(config = {}) { function disableHoverListeners() { window.document.removeEventListener("mouseover", onElementHover); + window.document.removeEventListener("mousemove", onElementHover); window.document.removeEventListener("mouseout", onElementHoverOut); + window.document.documentElement.removeEventListener("mouseleave", onDocumentMouseLeave); // Cancel any pending rAF hover update so stale callbacks don't fire if (_pendingHoverRAF) { cancelAnimationFrame(_pendingHoverRAF); @@ -732,7 +742,9 @@ function RemoteFunctions(config = {}) { if (config.mode === 'edit' && shouldShowHighlightOnHover()) { disableHoverListeners(); window.document.addEventListener("mouseover", onElementHover); + window.document.addEventListener("mousemove", onElementHover); window.document.addEventListener("mouseout", onElementHoverOut); + window.document.documentElement.addEventListener("mouseleave", onDocumentMouseLeave); } } @@ -1714,7 +1726,8 @@ function RemoteFunctions(config = {}) { "getHighlightCount": getHighlightCount, "getHighlightTrackingElement": getHighlightTrackingElement, "getHighlightStyle": getHighlightStyle, - "setHotCornerHidden": setHotCornerHidden + "setHotCornerHidden": setHotCornerHidden, + "clearHoverState": _clearHoverState }; // the below code comment is replaced by added scripts for extensibility diff --git a/src/LiveDevelopment/LiveDevMultiBrowser.js b/src/LiveDevelopment/LiveDevMultiBrowser.js index 2b0f8bc417..6a0f36deb2 100644 --- a/src/LiveDevelopment/LiveDevMultiBrowser.js +++ b/src/LiveDevelopment/LiveDevMultiBrowser.js @@ -733,6 +733,16 @@ define(function (require, exports, module) { } } + /** + * Clear hover highlight and hover box in the preview. Forwarded from the parent because the + * previewed iframe does not reliably receive mouseout/mouseleave on a slow pointer exit. + */ + function clearHoverState() { + if (_protocol) { + _protocol.evaluate("_LD.clearHoverState && _LD.clearHoverState()"); + } + } + /** * Update configuration in the remote browser */ @@ -860,6 +870,7 @@ define(function (require, exports, module) { exports.showHighlight = showHighlight; exports.hideHighlight = hideHighlight; exports.redrawHighlight = redrawHighlight; + exports.clearHoverState = clearHoverState; exports.getConfig = getConfig; exports.updateConfig = updateConfig; exports.refreshConfig = refreshConfig; diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index e4482312a1..2a9e9e4152 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -828,6 +828,11 @@ define(function (require, exports, module) { $panel.find(".custom-server-banner-close-icon").on("click", ()=>{ $panel.find(".live-preview-custom-banner").addClass("forced-hidden"); }); + // The previewed iframe does not reliably fire mouseout/mouseleave on a slow pointer exit, + // leaving the hover highlight/box stuck. Detect the leave parent-side and forward a clear. + $panel.on("mouseleave", "#panel-live-preview-frame", function () { + MultiBrowserLiveDev.clearHoverState(); + }); $iframe[0].onload = function () { $iframe.attr('srcdoc', null); }; diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7729c46e1e..51b877e2fe 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -194,7 +194,18 @@ define({ "LIVE_DEV_TOOLBOX_DUPLICATE": "Duplicate", "LIVE_DEV_TOOLBOX_DELETE": "Delete", "LIVE_DEV_TOOLBOX_IMAGE_GALLERY": "Image Gallery", + "LIVE_DEV_TOOLBOX_STYLER": "Styles", "LIVE_DEV_TOOLBOX_MORE_OPTIONS": "More Options", + "LIVE_DEV_STYLER_BACKGROUND": "Background color", + "LIVE_DEV_STYLER_TEXT_COLOR": "Text color", + "LIVE_DEV_STYLER_FONT": "Font family", + "LIVE_DEV_STYLER_SIZE": "Font size", + "LIVE_DEV_STYLER_WEIGHT": "Font weight", + "LIVE_DEV_STYLER_ALIGN": "Text alignment", + "LIVE_DEV_STYLER_BORDER": "Border", + "LIVE_DEV_STYLER_SPACING": "Margin & padding", + "LIVE_DEV_STYLER_LAYOUT": "Layout", + "LIVE_DEV_STYLER_FIT": "Image fit", "LIVE_DEV_MORE_OPTIONS_CUT": "Cut", "LIVE_DEV_MORE_OPTIONS_COPY": "Copy", "LIVE_DEV_MORE_OPTIONS_PASTE": "Paste",