Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d0624e1
feat: update layers panel style to cover id/class/content with tagname
devvaannsh Sep 5, 2026
38a5038
refactor: highlight body tag differently in layers panel
devvaannsh Sep 5, 2026
b486a69
feat: add scroll to element string for layers panel
devvaannsh Sep 5, 2026
2888b99
feat: add support for editing js rendered elements
devvaannsh Sep 5, 2026
5f5dab5
feat: remove source code search option
devvaannsh Sep 5, 2026
1bead74
feat: add better script generated box styles
devvaannsh Sep 5, 2026
ac0f320
feat: add styles for layers panel inline style editing
devvaannsh Sep 5, 2026
8e66e7d
feat: add styles for layers panel code hints
devvaannsh Sep 5, 2026
5ac4094
feat: unify layers panel field input styles
devvaannsh Sep 5, 2026
c4a5adc
fix: match layers panel field focus style to the search box
devvaannsh Sep 5, 2026
5f98e77
fix: semi colons overflowing in styles section
devvaannsh Sep 5, 2026
911eeb6
feat: allow text node highlighting support
devvaannsh Sep 6, 2026
f4ba30f
feat: not show tagnames in the layers panel
devvaannsh Sep 6, 2026
b4aa92f
feat: italicize the text content in layers panel
devvaannsh Sep 6, 2026
fd93dbc
fix: don't clip text node labels on hover
devvaannsh Sep 6, 2026
639a072
feat: collapse panel sections on double click or chevron click
devvaannsh Sep 6, 2026
5daeca4
fix: center the script generated badge icon
devvaannsh Sep 6, 2026
b442da3
feat: add gap between the elements header buttons
devvaannsh Sep 6, 2026
b6bf5f3
build: update pro deps
devvaannsh Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 122 additions & 3 deletions src/LiveDevelopment/BrowserScripts/RemoteFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// 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
};

Expand Down Expand Up @@ -41,6 +42,12 @@
// 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;
Expand Down Expand Up @@ -176,6 +183,40 @@
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) {

Check warning on line 196 in src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AaBypYMhrlDMFfw2UVVv&open=AaBypYMhrlDMFfw2UVVv&pullRequest=3177
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
*
Expand Down Expand Up @@ -205,6 +246,9 @@
getAllToolHandlers: getAllToolHandlers,
isElementEditable: isElementEditable,
isElementInspectable: isElementInspectable,
isSourceless: isSourceless,
getElementRef: getElementRef,
getElementByRef: getElementByRef,
isElementVisible: isElementVisible,
screenOffset: screenOffset,
selectElement: selectElement,
Expand Down Expand Up @@ -754,6 +798,78 @@
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() {
Expand Down Expand Up @@ -849,12 +965,14 @@
* @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,
Expand Down Expand Up @@ -1542,6 +1660,7 @@
previouslySelectedElement = null;
window.__current_ph_lp_selected = null;
}
_unwatchSourcelessSelection();
_editOptOutOverride = null;

// Reset hover tracking so the same-element skip doesn't suppress
Expand Down
12 changes: 10 additions & 2 deletions src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand Down
10 changes: 9 additions & 1 deletion src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ 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_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",
Expand Down Expand Up @@ -828,9 +830,9 @@ 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_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",
Expand All @@ -841,6 +843,12 @@ 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_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",
"LIVE_PREVIEW_LAYERS_TAG_TOOLTIP": "Show Tag Name Tooltip",

"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",
Expand Down
Loading
Loading