From 31a73db560e49c0d3114e9bebc429a9378fc5671 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 14:13:57 +0530 Subject: [PATCH 01/11] feat: update layers panel style to cover id/class/content with tagname --- src/nls/root/strings.js | 1 - src/styles/Extn-LayersPanel.less | 130 +++++++++++++++++++------------ 2 files changed, 79 insertions(+), 52 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index a45d9cbae1..981897c9d1 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -830,7 +830,6 @@ define({ "LIVE_PREVIEW_LAYERS_HIGHLIGHT_ON_HOVER": "Highlight on Hover in Preview", "LIVE_PREVIEW_LAYERS_MOVE_UP": "Move Up", "LIVE_PREVIEW_LAYERS_MOVE_DOWN": "Move Down", - "LIVE_PREVIEW_LAYERS_DRAG_TO_MOVE": "Drag to move", "LIVE_PREVIEW_LAYERS_ADD_ATTRIBUTE": "Add Attribute", "LIVE_PREVIEW_LAYERS_REMOVE_ATTRIBUTE": "Remove Attribute", "LIVE_PREVIEW_LAYERS_PROPERTIES": "Properties", diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index b209b3668d..417e453a0f 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -28,6 +28,8 @@ @layers-input-bg: rgba(255, 255, 255, 0.04); @layers-input-bg-focus: rgba(255, 255, 255, 0.06); @layers-row-height: 26px; +@layers-label-min-width: 72px; +@layers-tools-width: 74px; @layers-indent: 14px; @layers-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; @layers-prop-name: #9cdcfe; @@ -418,6 +420,7 @@ } .layers-node { + position: relative; display: flex; align-items: center; height: @layers-row-height; @@ -493,79 +496,64 @@ } } - .layers-node-tag { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - color: @project-panel-text-1; + .layers-tree .layers-node-label { + flex: 1 1 auto; + min-width: @layers-label-min-width; + contain: inline-size; } .layers-node-actions { display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + } + + .layers-node-tools { + display: flex; align-items: center; gap: 1px; position: sticky; right: 0; + width: fit-content; + height: 100%; margin-left: auto; - padding-left: 6px; - flex: none; + padding: 0 6px; + box-sizing: border-box; + pointer-events: auto; background: @layers-row-bg; } .layers-node:hover .layers-node-actions, .layers-node.layers-actions-open .layers-node-actions, .layers-node-actions:focus-within { - display: flex; + display: block; } - .layers-node:hover .layers-node-actions { - background: @layers-row-hover-bg; + .layers-node:hover .layers-node-label, + .layers-node.layers-actions-open .layers-node-label, + .layers-node:focus-within .layers-node-label { + padding-right: @layers-tools-width; } - .layers-node.layers-selected .layers-node-actions { - background: @layers-row-select-bg; + .layers-tree.layers-kbd-nav .layers-node:hover:not(.layers-actions-open) .layers-node-label { + padding-right: 0; } - .layers-node.layers-cursor .layers-node-actions { + .layers-node:hover .layers-node-tools, + .layers-node.layers-cursor .layers-node-tools { background: @layers-row-hover-bg; } - .layers-node-action { - .layers-icon-button(); - - &.layers-disabled { - opacity: 0.3; - cursor: default; - pointer-events: none; - } - } - - .layers-node-grip { - display: flex; - align-items: center; - justify-content: center; - width: 16px; - height: 20px; - flex: none; - color: @project-panel-text-2; - cursor: grab; - - svg { - width: 13px; - height: 13px; - display: block; - } - - &:hover { - color: @project-panel-text-1; - } + .layers-node.layers-selected .layers-node-tools { + background: @layers-row-select-bg; } - .layers-node-text { - color: @project-panel-text-2; - font-style: italic; - overflow: hidden; - text-overflow: ellipsis; + .layers-node-action { + .layers-icon-button(); } .layers-drop-line { @@ -814,6 +802,40 @@ } } +.layers-node-label { + display: flex; + align-items: baseline; + min-width: 0; + overflow: hidden; +} + +.layers-node-name, +.layers-node-text { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} + +.layers-node-name { + color: @project-panel-text-1; +} + +.layers-node-tag { + flex: none; + margin-left: 5px; + color: @project-panel-text-2; + + &:first-child { + margin-left: 0; + color: @project-panel-text-1; + } +} + +.layers-node-text { + color: @project-panel-text-2; + font-style: italic; +} + body.layers-resizing { cursor: row-resize; user-select: none; @@ -844,10 +866,6 @@ body.layers-resizing { body.layers-dnd { cursor: grabbing; user-select: none; - - .layers-node-grip { - cursor: grabbing; - } } body.layers-dnd-invalid { @@ -860,6 +878,7 @@ body.layers-dnd-invalid { display: flex; align-items: center; gap: 5px; + max-width: 260px; padding: 2px 8px 2px 6px; border: 1px solid rgba(107, 158, 255, 0.5); border-radius: 4px; @@ -875,6 +894,15 @@ body.layers-dnd-invalid { height: 14px; display: block; } + + .layers-node-name, + .layers-node-tag:first-child { + color: inherit; + } + + .layers-node-tag { + color: #a0a0a0; + } } @keyframes layers-flash-row { From ce02e3f6cc74ad89d688ace3aaadcd057d7be101 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 14:41:09 +0530 Subject: [PATCH 02/11] refactor: highlight body tag differently in layers panel --- src/styles/Extn-LayersPanel.less | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index 417e453a0f..ff76d07e1e 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -496,6 +496,15 @@ } } + .layers-node.layers-root-node .layers-node-label { + margin-left: -2px; + } + + .layers-node.layers-root-node .layers-node-tag { + font-weight: 500; + color: @project-panel-text-2; + } + .layers-tree .layers-node-label { flex: 1 1 auto; min-width: @layers-label-min-width; From 4ce100f1fb9f35be9d1d82b4e1fe96a722af20de Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 15:25:42 +0530 Subject: [PATCH 03/11] feat: add scroll to element string for layers panel --- src/nls/root/strings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 981897c9d1..9901edfcba 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -828,6 +828,7 @@ define({ "LIVE_PREVIEW_LAYERS_SETTINGS": "Settings", "LIVE_PREVIEW_LAYERS_SHOW_TEXT_NODES": "Show Text Nodes", "LIVE_PREVIEW_LAYERS_HIGHLIGHT_ON_HOVER": "Highlight on Hover in Preview", + "LIVE_PREVIEW_LAYERS_SCROLL_ON_HOVER": "Scroll to Element on Hover", "LIVE_PREVIEW_LAYERS_MOVE_UP": "Move Up", "LIVE_PREVIEW_LAYERS_MOVE_DOWN": "Move Down", "LIVE_PREVIEW_LAYERS_ADD_ATTRIBUTE": "Add Attribute", From 823cdf433c193ec701302d86a7314bbb22772e66 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 17:26:34 +0530 Subject: [PATCH 04/11] feat: add support for editing js rendered elements --- .../BrowserScripts/RemoteFunctions.js | 125 +++++++++++++++++- .../protocol/LiveDevProtocol.js | 12 +- src/nls/root/strings.js | 7 + src/styles/Extn-LayersPanel.less | 29 ++++ 4 files changed, 168 insertions(+), 5 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index df084c80c0..d14a09c913 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -12,6 +12,7 @@ function RemoteFunctions(config = {}) { // to distinguish between phoenix internal vs user created elements PHCODE_INTERNAL_ATTR: "data-phcode-internal-c15r5a9", DATA_BRACKETS_ID_ATTR: "data-brackets-id", // data attribute used to track elements for live preview operations + LP_REF_ATTR: "data-phcode-lp-ref", // identity of a script-added element, stamped only when it is selected HIGHLIGHT_CLASSNAME: "__brackets-ld-highlight" // CSS class name used for highlighting elements in live preview }; @@ -41,6 +42,12 @@ function RemoteFunctions(config = {}) { // 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 let previouslySelectedElement = null; + let _sourcelessObserver = null; + let _sourcelessCheckTimer = null; + let _sourcelessPath = null; + let _sourcelessTag = null; + let _sourcelessClass = null; + const SOURCELESS_RECOVER_DELAY_MS = 60; let _selectedFromEditor = false; // the selected element the `phcode-no-lp-edit` opt-out is lifted for, see _isEditOptedOut let _editOptOutOverride = null; @@ -176,6 +183,40 @@ function RemoteFunctions(config = {}) { return isElementInspectable(element, onlyHighlight) && element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); } + // no data-brackets-id means a script added the element, so there is no HTML source for it + function isSourceless(element) { + return !!element && !element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + } + + let _lpRefCounter = 0; + const LP_REF_PREFIX = "j"; + const RE_NUMERIC_ID = /^\d+$/; + + function getElementRef(element) { + if (!element || element.nodeType !== Node.ELEMENT_NODE) { + return null; + } + const tagId = element.getAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); + if (tagId) { + return tagId; + } + let ref = element.getAttribute(GLOBALS.LP_REF_ATTR); + if (!ref) { + ref = LP_REF_PREFIX + (++_lpRefCounter); + element.setAttribute(GLOBALS.LP_REF_ATTR, ref); + } + return ref; + } + + function getElementByRef(ref) { + if (ref === null || ref === undefined || ref === "") { + return null; + } + const text = String(ref); + const attr = RE_NUMERIC_ID.test(text) ? GLOBALS.DATA_BRACKETS_ID_ATTR : GLOBALS.LP_REF_ATTR; + return window.document.querySelector("[" + attr + '="' + text + '"]'); + } + /** * this function calc the screen offset of an element * @@ -205,6 +246,9 @@ function RemoteFunctions(config = {}) { getAllToolHandlers: getAllToolHandlers, isElementEditable: isElementEditable, isElementInspectable: isElementInspectable, + isSourceless: isSourceless, + getElementRef: getElementRef, + getElementByRef: getElementByRef, isElementVisible: isElementVisible, screenOffset: screenOffset, selectElement: selectElement, @@ -754,6 +798,78 @@ function RemoteFunctions(config = {}) { previouslySelectedElement = element; _selectedFromEditor = fromEditor || false; window.__current_ph_lp_selected = element; + if (isSourceless(element)) { + _watchSourcelessSelection(element); + } + } + + function _elementIndexPath(element) { + const path = []; + let el = element; + while (el && el !== window.document.body) { + const parent = el.parentElement; + if (!parent) { + return null; + } + path.unshift(Array.prototype.indexOf.call(parent.children, el)); + el = parent; + } + return el === window.document.body ? path : null; + } + + function _elementAtIndexPath(path) { + let el = window.document.body; + for (let i = 0; i < path.length && el; i++) { + el = el.children[path[i]]; + } + return el || null; + } + + // a re-render replaces a script-added node, so re-select the same tag in the same place or dismiss + function _watchSourcelessSelection(element) { + _unwatchSourcelessSelection(); + _sourcelessPath = _elementIndexPath(element); + if (!_sourcelessPath) { + return; + } + _sourcelessTag = element.tagName; + _sourcelessClass = typeof element.className === "string" ? element.className : ""; + _sourcelessObserver = new MutationObserver(function () { + if (_sourcelessCheckTimer || !previouslySelectedElement || previouslySelectedElement.isConnected) { + return; + } + _sourcelessCheckTimer = setTimeout(_recoverSourcelessSelection, SOURCELESS_RECOVER_DELAY_MS); + }); + _sourcelessObserver.observe(window.document.body, { childList: true, subtree: true }); + } + + function _unwatchSourcelessSelection() { + if (_sourcelessObserver) { + _sourcelessObserver.disconnect(); + _sourcelessObserver = null; + } + if (_sourcelessCheckTimer) { + clearTimeout(_sourcelessCheckTimer); + _sourcelessCheckTimer = null; + } + _sourcelessPath = null; + } + + function _recoverSourcelessSelection() { + _sourcelessCheckTimer = null; + const old = previouslySelectedElement; + if (!old || old.isConnected || !_sourcelessPath) { + return; + } + const fresh = _elementAtIndexPath(_sourcelessPath); + const className = fresh && typeof fresh.className === "string" ? fresh.className : ""; + if (fresh && fresh.tagName === _sourcelessTag && className === _sourcelessClass && + isSourceless(fresh) && isElementInspectable(fresh, true) && isElementVisible(fresh)) { + const fromEditor = _selectedFromEditor; + selectElement(fresh, fromEditor); + } else { + dismissUIAndCleanupState(); + } } function disableHoverListeners() { @@ -849,12 +965,14 @@ function RemoteFunctions(config = {}) { * @param {HTMLElement} element */ function sendSelectionToEditor(element) { - if (!element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) || - config.syncSourceAndPreview === false) { + if (config.syncSourceAndPreview === false) { return; } + // sent without a tagId too, so a css file in the editor can still jump to the rule + const tagId = element.getAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR); MessageBroker.send({ - "tagId": element.getAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR), + "tagId": tagId || null, + "sourceless": !tagId, "nodeID": element.id, "nodeClassList": element.classList, "nodeName": element.nodeName, @@ -1542,6 +1660,7 @@ function RemoteFunctions(config = {}) { previouslySelectedElement = null; window.__current_ph_lp_selected = null; } + _unwatchSourcelessSelection(); _editOptOutOverride = null; // Reset hover tracking so the same-element skip doesn't suppress diff --git a/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js b/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js index 036eafb3a3..85ff6e3920 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js +++ b/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js @@ -292,6 +292,13 @@ define(function (require, exports, module) { } return; } + // a script-added element has no place in the html, only a css file can show it + if (!tagId) { + if (activeEditor && (liveDoc.isRelated(activeEditorPath) || _isLessOrSCSS(activeEditor))) { + _searchAndCursorIfCSS(activeEditor, allSelectors, nodeName); + } + return; + } const allOpenFileCount = MainViewManager.getWorkingSetSize(MainViewManager.ALL_PANES); function selectInHTMLEditor(fullHtmlEditor) { const positionResult = HTMLInstrumentation.getPositionFromTagId(fullHtmlEditor, parseInt(tagId, 10)); @@ -385,7 +392,7 @@ define(function (require, exports, module) { } } else if (msg.keyForward) { _forwardKeyboardShortcutFromIframe(msg); - } else if (msg.clicked && msg.tagId) { + } else if (msg.clicked && (msg.tagId || msg.sourceless)) { // While previewing an html file, and if css related file is active in the editor, then clicking on the // live preview, here we set the cursor position in the css file. but this will also trigger a css // highlight as the cursor changes which jumps the live preview selection. @@ -402,7 +409,8 @@ define(function (require, exports, module) { } _keepFocusUntil = 0; editMode && liveDoc && liveDoc.disableHighlightOnCursorActivity(false); - liveDoc && liveDoc.updateHighlight(); + // the caret did not move for a script-added element, re-highlighting would drop its selection + liveDoc && !msg.sourceless && liveDoc.updateHighlight(); } else { // enrich received message with clientId msg.clientId = clientId; diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 9901edfcba..6c132eec85 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -187,6 +187,11 @@ define({ "LIVE_DEV_SETTINGS_SHOW_STARTER_BAR_PREFERENCE": "Show the starter bar when the live preview page is empty. Defaults to 'true'", "LIVE_DEV_SETTINGS_STYLES_BAR_POSITION_PREFERENCE": "Which edge the live preview styles bar docks to, 'top' or 'bottom'. Defaults to 'bottom'", "LIVE_DEV_TOOLBOX_SELECT_PARENT": "Select Parent", + "LIVE_DEV_TOOLBOX_SELECT_SOURCE_PARENT": "Select Parent in Source", + "LIVE_DEV_TOOLBOX_FIND_IN_CODE": "Find in Code", + "LIVE_DEV_SCRIPT_GENERATED_BADGE": "Script generated", + "LIVE_DEV_SCRIPT_GENERATED_TOOLTIP": "Added by a script, so it is not in the HTML source. Its CSS rules can still be edited.", + "LIVE_DEV_STYLER_SCRIPT_ELEMENT_NOTICE": "Script-generated element: edits are saved to its CSS rules", "LIVE_DEV_TOOLBOX_EDIT_TEXT": "Edit Text", "LIVE_DEV_TOOLBOX_DOUBLE_CLICK_HINT": "Double-click", "LIVE_DEV_TOOLBOX_EDIT_ELEMENT_PROPS": "Edit Element Properties", @@ -841,6 +846,8 @@ define({ "LIVE_PREVIEW_LAYERS_OPEN_SOURCE": "Open in editor", "LIVE_PREVIEW_LAYERS_RULE_INACTIVE": "Not active in the current state", "LIVE_PREVIEW_LAYERS_EDIT_STYLE": "Edit in Styles Bar", + "LIVE_PREVIEW_LAYERS_SHOW_SCRIPT_NODES": "Show Script-Generated Elements", + "LIVE_PREVIEW_LAYERS_SCRIPT_GENERATED": "Generated by script, not in the source file", "LIVE_DEV_DETACHED_REPLACED_WITH_DEVTOOLS": "Live Preview was canceled because the browser's developer tools were opened", "LIVE_DEV_DETACHED_TARGET_CLOSED": "Live Preview was canceled because the page was closed in the browser", diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index ff76d07e1e..cd0f25440b 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -500,6 +500,35 @@ margin-left: -2px; } + .layers-node.layers-script-node .layers-node-icon, + .layers-node.layers-script-node .layers-node-name, + .layers-node.layers-script-node .layers-node-tag { + opacity: 0.72; + } + + .layers-node-badge { + display: inline-flex; + align-items: center; + flex: none; + margin-left: 5px; + color: @project-panel-text-2; + opacity: 0.8; + + svg { + width: 11px; + height: 11px; + display: block; + } + } + + .layers-props-note { + padding: 4px 5px 6px; + color: @project-panel-text-2; + font-style: italic; + line-height: 1.4; + white-space: normal; + } + .layers-node.layers-root-node .layers-node-tag { font-weight: 500; color: @project-panel-text-2; From 9c071ffe7e5dfbe15e9194bc86badb41f46e886f Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 20:38:09 +0530 Subject: [PATCH 05/11] feat: remove source code search option --- src/nls/root/strings.js | 2 -- src/styles/Extn-LayersPanel.less | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 6c132eec85..82f329a1c2 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -187,8 +187,6 @@ define({ "LIVE_DEV_SETTINGS_SHOW_STARTER_BAR_PREFERENCE": "Show the starter bar when the live preview page is empty. Defaults to 'true'", "LIVE_DEV_SETTINGS_STYLES_BAR_POSITION_PREFERENCE": "Which edge the live preview styles bar docks to, 'top' or 'bottom'. Defaults to 'bottom'", "LIVE_DEV_TOOLBOX_SELECT_PARENT": "Select Parent", - "LIVE_DEV_TOOLBOX_SELECT_SOURCE_PARENT": "Select Parent in Source", - "LIVE_DEV_TOOLBOX_FIND_IN_CODE": "Find in Code", "LIVE_DEV_SCRIPT_GENERATED_BADGE": "Script generated", "LIVE_DEV_SCRIPT_GENERATED_TOOLTIP": "Added by a script, so it is not in the HTML source. Its CSS rules can still be edited.", "LIVE_DEV_STYLER_SCRIPT_ELEMENT_NOTICE": "Script-generated element: edits are saved to its CSS rules", diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index cd0f25440b..b0720683f3 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -503,7 +503,7 @@ .layers-node.layers-script-node .layers-node-icon, .layers-node.layers-script-node .layers-node-name, .layers-node.layers-script-node .layers-node-tag { - opacity: 0.72; + opacity: 0.5; } .layers-node-badge { From c5ea7ad100b90a817dc3345e02ecb3c4e9dca2f4 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 21:05:38 +0530 Subject: [PATCH 06/11] feat: add better script generated box styles --- src/nls/root/strings.js | 1 - src/styles/Extn-LayersPanel.less | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 82f329a1c2..5f9b400a43 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -187,7 +187,6 @@ define({ "LIVE_DEV_SETTINGS_SHOW_STARTER_BAR_PREFERENCE": "Show the starter bar when the live preview page is empty. Defaults to 'true'", "LIVE_DEV_SETTINGS_STYLES_BAR_POSITION_PREFERENCE": "Which edge the live preview styles bar docks to, 'top' or 'bottom'. Defaults to 'bottom'", "LIVE_DEV_TOOLBOX_SELECT_PARENT": "Select Parent", - "LIVE_DEV_SCRIPT_GENERATED_BADGE": "Script generated", "LIVE_DEV_SCRIPT_GENERATED_TOOLTIP": "Added by a script, so it is not in the HTML source. Its CSS rules can still be edited.", "LIVE_DEV_STYLER_SCRIPT_ELEMENT_NOTICE": "Script-generated element: edits are saved to its CSS rules", "LIVE_DEV_TOOLBOX_EDIT_TEXT": "Edit Text", diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index b0720683f3..333f79f3d7 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -522,6 +522,9 @@ } .layers-props-note { + display: flex; + align-items: flex-start; + gap: 5px; padding: 4px 5px 6px; color: @project-panel-text-2; font-style: italic; @@ -529,6 +532,18 @@ white-space: normal; } + .layers-props-note-icon { + display: flex; + flex: none; + margin-top: 1px; + + svg { + width: 13px; + height: 13px; + display: block; + } + } + .layers-node.layers-root-node .layers-node-tag { font-weight: 500; color: @project-panel-text-2; @@ -673,6 +688,21 @@ } } + .layers-prop-static { + flex: 1; + min-width: 0; + padding: 0 5px; + color: @project-panel-text-2; + font-family: @layers-mono; + font-size: @sidebar-xs-font-size; + line-height: 22px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: default; + user-select: none; + } + .layers-attrs { margin-top: 6px; } From 469a82a859aa4958595f2afca0704fa2e4e054ee Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 21:49:18 +0530 Subject: [PATCH 07/11] feat: add styles for layers panel inline style editing --- src/nls/root/strings.js | 2 + src/styles/Extn-LayersPanel.less | 78 ++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 5f9b400a43..879e8cd207 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -843,6 +843,8 @@ define({ "LIVE_PREVIEW_LAYERS_OPEN_SOURCE": "Open in editor", "LIVE_PREVIEW_LAYERS_RULE_INACTIVE": "Not active in the current state", "LIVE_PREVIEW_LAYERS_EDIT_STYLE": "Edit in Styles Bar", + "LIVE_PREVIEW_LAYERS_ADD_DECLARATION": "Add Declaration", + "LIVE_PREVIEW_LAYERS_REMOVE_DECLARATION": "Remove Declaration", "LIVE_PREVIEW_LAYERS_SHOW_SCRIPT_NODES": "Show Script-Generated Elements", "LIVE_PREVIEW_LAYERS_SCRIPT_GENERATED": "Generated by script, not in the source file", diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index 333f79f3d7..899efd3efb 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -769,12 +769,12 @@ .layers-decl { display: flex; align-items: baseline; - gap: 4px; min-height: 20px; - padding-left: 8px; + padding: 0 4px 0 8px; font-family: @layers-mono; font-size: @sidebar-xs-font-size; - user-select: text; + line-height: 18px; + user-select: none; &:hover { background: @layers-hover-bg; @@ -792,15 +792,72 @@ } .layers-decl-value { - flex: 1; + flex: 0 1 auto; min-width: 0; color: @layers-prop-value; word-break: break-all; } + .layers-decl-field, + .layers-decl-input { + margin: 0 -2px; + + &.layers-decl-value { + margin-left: 3px; + } + } + + .layers-decl-field { + padding: 0 2px; + border-radius: 3px; + cursor: text; + + &:hover { + background: @layers-input-bg-focus; + box-shadow: inset 0 0 0 1px @layers-border; + } + } + + .layers-decl-input { + box-sizing: border-box; + height: 18px; + min-width: 0; + padding: 0 2px; + border: 1px solid @layers-accent; + border-radius: 3px; + outline: none; + box-shadow: none; + background: @layers-input-bg-focus; + font: inherit; + line-height: 16px; + text-decoration: none; + + &::placeholder { + color: @project-panel-text-2; + opacity: 0.6; + } + + &.layers-decl-value { + flex: 1; + } + } + + .layers-decl.layers-editing:hover { + background: transparent; + } + + .layers-decl-actions { + display: flex; + align-items: center; + gap: 1px; + flex: none; + align-self: center; + margin-left: auto; + padding-left: 6px; + } + .layers-decl.layers-overridden { - .layers-decl-name, - .layers-decl-value { + .layers-decl-field { text-decoration: line-through; color: @project-panel-text-2; } @@ -811,18 +868,21 @@ align-self: center; } - .layers-decl .layers-icon-btn { + .layers-decl .layers-icon-btn, + .layers-rule-add { opacity: 0; } .layers-decl:hover .layers-icon-btn, - .layers-decl .layers-icon-btn:focus { + .layers-decl .layers-icon-btn:focus, + .layers-rule:hover .layers-rule-add, + .layers-rule-add:focus { opacity: 1; } .layers-rule { margin-bottom: 6px; - padding-bottom: 6px; + padding-bottom: 8px; border-bottom: 1px solid @layers-border; &:last-child { From db3ee90dba80df3f09db3d818fa9ef48a10e4e30 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 21:58:36 +0530 Subject: [PATCH 08/11] feat: add styles for layers panel code hints --- src/styles/Extn-LayersPanel.less | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index 899efd3efb..581c5c18b9 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -969,6 +969,59 @@ body.layers-resizing { user-select: none; } +.layers-hints { + position: fixed; + z-index: 1000; + box-sizing: border-box; + max-height: 220px; + padding: 3px; + overflow-y: auto; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 6px; + background: #2f2f2f; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4); + font-family: @layers-mono; + font-size: @sidebar-xs-font-size; + line-height: 18px; +} + +.layers-hint { + display: flex; + align-items: center; + gap: 6px; + padding: 1px 6px; + border-radius: 3px; + color: #e0e0e0; + white-space: nowrap; + cursor: pointer; + + &:hover { + background: @layers-hover-bg; + } + + &.layers-hint-selected { + background: @layers-accent-soft; + } + + strong { + font-weight: 600; + color: @layers-accent; + } +} + +.layers-hint-label { + overflow: hidden; + text-overflow: ellipsis; +} + +.layers-hint-swatch { + flex: none; + width: 10px; + height: 10px; + border: 1px solid rgba(255, 255, 255, 0.25); + border-radius: 2px; +} + .dropdownbutton-popup.layers-menu-popup { max-height: 80vh; } From 4968036d878b501b620ae3ef49a5089c4db738df Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 22:16:35 +0530 Subject: [PATCH 09/11] feat: unify layers panel field input styles --- src/nls/root/strings.js | 1 + src/styles/Extn-LayersPanel.less | 114 ++++++++++++++----------------- 2 files changed, 51 insertions(+), 64 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 879e8cd207..435721b76c 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -845,6 +845,7 @@ define({ "LIVE_PREVIEW_LAYERS_EDIT_STYLE": "Edit in Styles Bar", "LIVE_PREVIEW_LAYERS_ADD_DECLARATION": "Add Declaration", "LIVE_PREVIEW_LAYERS_REMOVE_DECLARATION": "Remove Declaration", + "LIVE_PREVIEW_LAYERS_EDIT_DECLARATION": "Edit Declaration", "LIVE_PREVIEW_LAYERS_SHOW_SCRIPT_NODES": "Show Script-Generated Elements", "LIVE_PREVIEW_LAYERS_SCRIPT_GENERATED": "Generated by script, not in the source file", diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index 581c5c18b9..0a81b0ae02 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -67,6 +67,38 @@ } } +.layers-field-input() { + box-sizing: border-box; + height: 22px; + padding: 0 6px; + margin: 0; + border: 1px solid transparent; + border-radius: 4px; + outline: none; + box-shadow: none; + background: transparent; + color: @project-panel-text-1; + font-family: @layers-mono; + font-size: @sidebar-xs-font-size; + line-height: 20px; + + &::placeholder { + color: @project-panel-text-2; + opacity: 0.6; + } + + &:hover:not([readonly]) { + border-color: @layers-border; + background: @layers-input-bg; + } + + &:focus { + border-color: @layers-accent; + background: @layers-input-bg-focus; + box-shadow: 0 0 0 2px @layers-accent-soft; + } +} + .layers-tab-container { display: flex; flex-direction: column; @@ -651,34 +683,12 @@ } .layers-prop-input[type="text"] { + .layers-field-input(); flex: 1; min-width: 0; - height: 22px; - padding: 0 5px; - margin: 0; - border: 1px solid transparent; - border-radius: 4px; - outline: none; - box-shadow: none; - background: transparent; - color: @project-panel-text-1; - font-family: @layers-mono; - font-size: @sidebar-xs-font-size; text-overflow: ellipsis; - &::placeholder { - color: @project-panel-text-2; - opacity: 0.6; - } - - &:hover:not([readonly]) { - border-color: @layers-border; - background: @layers-input-bg; - } - &:focus { - border-color: @layers-accent; - background: @layers-input-bg-focus; text-overflow: clip; } @@ -768,12 +778,12 @@ .layers-decl { display: flex; - align-items: baseline; - min-height: 20px; + align-items: center; + min-height: 26px; padding: 0 4px 0 8px; font-family: @layers-mono; font-size: @sidebar-xs-font-size; - line-height: 18px; + line-height: 22px; user-select: none; &:hover { @@ -794,51 +804,28 @@ .layers-decl-value { flex: 0 1 auto; min-width: 0; + margin-left: 5px; color: @layers-prop-value; word-break: break-all; } - .layers-decl-field, - .layers-decl-input { - margin: 0 -2px; - - &.layers-decl-value { - margin-left: 3px; - } - } - - .layers-decl-field { - padding: 0 2px; - border-radius: 3px; - cursor: text; - - &:hover { - background: @layers-input-bg-focus; - box-shadow: inset 0 0 0 1px @layers-border; - } - } - .layers-decl-input { - box-sizing: border-box; - height: 18px; - min-width: 0; - padding: 0 2px; - border: 1px solid @layers-accent; - border-radius: 3px; - outline: none; - box-shadow: none; - background: @layers-input-bg-focus; - font: inherit; - line-height: 16px; + .layers-field-input(); + flex: 0 1 auto; + min-width: 3ch; + max-width: 100%; + padding: 0 4px; + border-color: @layers-border; + background: @layers-input-bg; text-decoration: none; - &::placeholder { - color: @project-panel-text-2; - opacity: 0.6; + &.layers-decl-name { + color: @layers-prop-name; } &.layers-decl-value { - flex: 1; + margin: 0 3px 0 5px; + color: @layers-prop-value; } } @@ -849,11 +836,10 @@ .layers-decl-actions { display: flex; align-items: center; - gap: 1px; + gap: 2px; flex: none; - align-self: center; margin-left: auto; - padding-left: 6px; + padding-left: 8px; } .layers-decl.layers-overridden { From 6e9271b9a76d36ffb8c5ace00942e9c9b933738c Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 22:24:16 +0530 Subject: [PATCH 10/11] fix: match layers panel field focus style to the search box --- src/styles/Extn-LayersPanel.less | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index 0a81b0ae02..ae6fcdaa67 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -73,7 +73,7 @@ padding: 0 6px; margin: 0; border: 1px solid transparent; - border-radius: 4px; + border-radius: 6px; outline: none; box-shadow: none; background: transparent; @@ -95,7 +95,6 @@ &:focus { border-color: @layers-accent; background: @layers-input-bg-focus; - box-shadow: 0 0 0 2px @layers-accent-soft; } } @@ -809,7 +808,7 @@ word-break: break-all; } - .layers-decl-input { + .layers-decl-input[type="text"] { .layers-field-input(); flex: 0 1 auto; min-width: 3ch; From b9fb6e8c50eb808c755c6406d1ce3eb1491702a1 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Sep 2026 23:03:17 +0530 Subject: [PATCH 11/11] fix: semi colons overflowing in styles section --- src/styles/Extn-LayersPanel.less | 59 ++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/styles/Extn-LayersPanel.less b/src/styles/Extn-LayersPanel.less index ae6fcdaa67..a7ca5d83a9 100644 --- a/src/styles/Extn-LayersPanel.less +++ b/src/styles/Extn-LayersPanel.less @@ -73,7 +73,7 @@ padding: 0 6px; margin: 0; border: 1px solid transparent; - border-radius: 6px; + border-radius: 4px; outline: none; box-shadow: none; background: transparent; @@ -81,20 +81,26 @@ font-family: @layers-mono; font-size: @sidebar-xs-font-size; line-height: 20px; + transition: none; &::placeholder { color: @project-panel-text-2; opacity: 0.6; } + &::selection { + background: fade(@layers-accent, 28%); + } + &:hover:not([readonly]) { border-color: @layers-border; background: @layers-input-bg; } - &:focus { + &:focus:not([readonly]) { border-color: @layers-accent; background: @layers-input-bg-focus; + box-shadow: none; } } @@ -681,7 +687,7 @@ } } - .layers-prop-input[type="text"] { + input.layers-prop-input[type="text"] { .layers-field-input(); flex: 1; min-width: 0; @@ -776,8 +782,12 @@ } .layers-decl { + position: relative; display: flex; align-items: center; + box-sizing: border-box; + min-width: 100%; + width: max-content; min-height: 26px; padding: 0 4px 0 8px; font-family: @layers-mono; @@ -801,18 +811,15 @@ } .layers-decl-value { - flex: 0 1 auto; - min-width: 0; + flex: none; margin-left: 5px; color: @layers-prop-value; - word-break: break-all; } - .layers-decl-input[type="text"] { + input.layers-decl-input[type="text"] { .layers-field-input(); - flex: 0 1 auto; + flex: none; min-width: 3ch; - max-width: 100%; padding: 0 4px; border-color: @layers-border; background: @layers-input-bg; @@ -833,12 +840,35 @@ } .layers-decl-actions { + display: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + } + + .layers-decl-tools { display: flex; align-items: center; gap: 2px; - flex: none; + position: sticky; + right: 0; + height: 100%; margin-left: auto; - padding-left: 8px; + padding: 0 4px 0 8px; + pointer-events: auto; + background: @layers-row-hover-bg; + } + + .layers-decl:hover .layers-decl-actions, + .layers-decl-actions:focus-within { + display: flex; + } + + .layers-decl.layers-editing .layers-decl-actions { + display: none; } .layers-decl.layers-overridden { @@ -853,13 +883,10 @@ align-self: center; } - .layers-decl .layers-icon-btn, .layers-rule-add { opacity: 0; } - .layers-decl:hover .layers-icon-btn, - .layers-decl .layers-icon-btn:focus, .layers-rule:hover .layers-rule-add, .layers-rule-add:focus { opacity: 1; @@ -874,10 +901,6 @@ margin-bottom: 0; border-bottom: none; } - - &.layers-inactive { - opacity: 0.6; - } } .layers-rule-header {