Skip to content

Commit 907fbfa

Browse files
committed
deploy: 98626ea
1 parent 6d04a70 commit 907fbfa

422 files changed

Lines changed: 6907 additions & 4316 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

JSUtils/ScopeManager.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 127 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ function RemoteFunctions(config = {}) {
2626
const SHARED_STATE = {
2727
__description: "Use this to keep shared state for Live Preview Edit instead of window.*",
2828
_suppressDOMEditDismissal: false,
29-
_suppressDOMEditDismissalTimeout: null
29+
_suppressDOMEditDismissalTimeout: null,
30+
_boxModelHighlightHidden: false
3031
};
3132

3233
let _hoverHighlight;
@@ -72,6 +73,9 @@ function RemoteFunctions(config = {}) {
7273
// called when an item is selected from the more options dropdown
7374
"handleDropdownClick",
7475
"updateContent", // in-place content refresh for control box etc. after drag
76+
// a DOM edit changed the selected element's attributes or rebuilt its node —
77+
// refresh shown UI in place; must never resurrect dismissed UI
78+
"onSelectedElementMutated",
7579
"reRegisterEventHandlers",
7680
"handleClick", // handle click on an icon in the tool box.
7781
// when escape key is presses in the editor, we may need to dismiss the live edit boxes.
@@ -187,7 +191,10 @@ function RemoteFunctions(config = {}) {
187191
disableHoverListeners: disableHoverListeners,
188192
enableHoverListeners: enableHoverListeners,
189193
redrawHighlights: redrawHighlights,
190-
redrawEverything: redrawEverything
194+
redrawEverything: redrawEverything,
195+
getTreePath: _getTreePath,
196+
getElementByTreePath: _getElementByTreePath,
197+
getSourceChildren: _instrumentedChildren
191198
};
192199

193200
/**
@@ -443,15 +450,17 @@ function RemoteFunctions(config = {}) {
443450
s.backgroundColor = color;
444451
}
445452

446-
// Padding region
447-
const padColor = COLORS.highlightPadding;
453+
// Padding region. Rects stay in place when hidden, only their fill goes away,
454+
// so nothing has to be rebuilt when they come back.
455+
const boxModelHidden = SHARED_STATE._boxModelHighlightHidden;
456+
const padColor = boxModelHidden ? "transparent" : COLORS.highlightPadding;
448457
setRect(refs.padTop, paddingBox.left, paddingBox.top, paddingBox.width, pt, padColor);
449458
setRect(refs.padBottom, paddingBox.left, contentBox.top + contentBox.height, paddingBox.width, pb, padColor);
450459
setRect(refs.padLeft, paddingBox.left, contentBox.top, pl, contentBox.height, padColor);
451460
setRect(refs.padRight, contentBox.left + contentBox.width, contentBox.top, pr, contentBox.height, padColor);
452461

453462
// Margin region
454-
const margColor = COLORS.highlightMargin;
463+
const margColor = boxModelHidden ? "transparent" : COLORS.highlightMargin;
455464
setRect(refs.marTop, marginBox.left, marginBox.top, marginBox.width, mt, margColor);
456465
setRect(refs.marBottom, marginBox.left, borderBox.top + borderBox.height, marginBox.width, mb, margColor);
457466
setRect(refs.marLeft, marginBox.left, borderBox.top, ml, borderBox.height, margColor);
@@ -595,14 +604,13 @@ function RemoteFunctions(config = {}) {
595604
}
596605

597606
const element = event.target;
598-
if(!LivePreviewView.isElementInspectable(element) || element.nodeType !== Node.ELEMENT_NODE) {
599-
return;
600-
}
601607

602-
// Same element as last hover — nothing changed, skip entirely
603608
if (element === _lastHoverTarget) {
604609
return;
605610
}
611+
if(!LivePreviewView.isElementInspectable(element) || element.nodeType !== Node.ELEMENT_NODE) {
612+
return;
613+
}
606614
_lastHoverTarget = element;
607615

608616
// if _hoverHighlight is uninitialized, initialize it
@@ -615,21 +623,30 @@ function RemoteFunctions(config = {}) {
615623
}
616624
}
617625

618-
function onElementHoverOut(event) {
619-
// don't want highlighting and stuff when auto scrolling
620-
if (SHARED_STATE.isAutoScrolling) { return; }
626+
function _clearHoverState() {
627+
if (SHARED_STATE.isAutoScrolling) {
628+
return;
629+
}
630+
if (_hoverHighlight && shouldShowHighlightOnHover()) {
631+
_lastHoverTarget = null;
632+
_scheduleHoverUpdate();
633+
}
634+
}
621635

636+
function onElementHoverOut(event) {
622637
const element = event.target;
623638
// Use isElementInspectable (not isElementEditable) so that JS-rendered
624639
// elements also get their hover highlight and hover box properly dismissed.
625640
if(LivePreviewView.isElementInspectable(element) && element.nodeType === Node.ELEMENT_NODE) {
626-
if (_hoverHighlight && shouldShowHighlightOnHover()) {
627-
_lastHoverTarget = null;
628-
_scheduleHoverUpdate();
629-
}
641+
_clearHoverState();
630642
}
631643
}
632644

645+
// for popped out window: the in-panel iframe case is forwarded parent-side via _LD.clearHoverState().
646+
function onDocumentMouseLeave() {
647+
_clearHoverState();
648+
}
649+
633650
function scrollElementToViewPort(element) {
634651
if (!element) {
635652
return;
@@ -711,7 +728,9 @@ function RemoteFunctions(config = {}) {
711728

712729
function disableHoverListeners() {
713730
window.document.removeEventListener("mouseover", onElementHover);
731+
window.document.removeEventListener("mousemove", onElementHover);
714732
window.document.removeEventListener("mouseout", onElementHoverOut);
733+
window.document.documentElement.removeEventListener("mouseleave", onDocumentMouseLeave);
715734
// Cancel any pending rAF hover update so stale callbacks don't fire
716735
if (_pendingHoverRAF) {
717736
cancelAnimationFrame(_pendingHoverRAF);
@@ -732,7 +751,9 @@ function RemoteFunctions(config = {}) {
732751
if (config.mode === 'edit' && shouldShowHighlightOnHover()) {
733752
disableHoverListeners();
734753
window.document.addEventListener("mouseover", onElementHover);
754+
window.document.addEventListener("mousemove", onElementHover);
735755
window.document.addEventListener("mouseout", onElementHoverOut);
756+
window.document.documentElement.addEventListener("mouseleave", onDocumentMouseLeave);
736757
}
737758
}
738759

@@ -1039,6 +1060,25 @@ function RemoteFunctions(config = {}) {
10391060
return results && results[0];
10401061
};
10411062

1063+
// True for elements Phoenix adds to the page itself, like the tool boxes and
1064+
// the highlight overlays. They are not part of the user's source file.
1065+
function _isPhoenixInternalNode(node) {
1066+
return !!node && node.nodeType === Node.ELEMENT_NODE &&
1067+
(node.hasAttribute(GLOBALS.PHCODE_INTERNAL_ATTR) ||
1068+
node.className === GLOBALS.HIGHLIGHT_CLASSNAME);
1069+
}
1070+
1071+
/** The first of the Phoenix elements sitting at the end of `parent`, else null. */
1072+
function _firstTrailingInternalNode(parent) {
1073+
let node = parent.lastChild;
1074+
let first = null;
1075+
while (_isPhoenixInternalNode(node)) {
1076+
first = node;
1077+
node = node.previousSibling;
1078+
}
1079+
return first;
1080+
}
1081+
10421082
/**
10431083
* @private
10441084
* Insert a new child element
@@ -1053,7 +1093,12 @@ function RemoteFunctions(config = {}) {
10531093
if (edit.firstChild) {
10541094
before = targetElement.firstChild;
10551095
} else if (edit.lastChild) {
1056-
after = targetElement.lastChild;
1096+
// Phoenix's tool boxes are the last children of <body>, so appending
1097+
// here would put the new element after them, in the wrong place.
1098+
before = _firstTrailingInternalNode(targetElement);
1099+
if (!before) {
1100+
after = targetElement.lastChild;
1101+
}
10571102
}
10581103

10591104
if (before) {
@@ -1189,6 +1234,7 @@ function RemoteFunctions(config = {}) {
11891234
targetElement,
11901235
childElement,
11911236
self = this;
1237+
let selectedElementMutated = false;
11921238

11931239
this.rememberedNodes = {};
11941240

@@ -1223,9 +1269,15 @@ function RemoteFunctions(config = {}) {
12231269
case "attrChange":
12241270
case "attrAdd":
12251271
targetElement.setAttribute(edit.attribute, self._parseEntities(edit.value));
1272+
if (targetElement === previouslySelectedElement) {
1273+
selectedElementMutated = true;
1274+
}
12261275
break;
12271276
case "attrDelete":
12281277
targetElement.removeAttribute(edit.attribute);
1278+
if (targetElement === previouslySelectedElement) {
1279+
selectedElementMutated = true;
1280+
}
12291281
break;
12301282
case "elementDelete":
12311283
if (targetElement.remove) {
@@ -1360,9 +1412,22 @@ function RemoteFunctions(config = {}) {
13601412
SHARED_STATE._editorBox.element = freshElement;
13611413
}
13621414
redrawEverything();
1415+
selectedElementMutated = true;
13631416
}
13641417
}
13651418
}
1419+
1420+
// neither path above refreshes selection-anchored UI CONTENT (the paths
1421+
// only reposition / re-point element refs), so content rendered at
1422+
// selection time — e.g. the control box's tag/#id/.classes line — would
1423+
// stay stale after the selected element's markup changed
1424+
if (selectedElementMutated && previouslySelectedElement && previouslySelectedElement.isConnected) {
1425+
getAllToolHandlers().forEach(function (handler) {
1426+
if (handler.onSelectedElementMutated) {
1427+
handler.onSelectedElementMutated(previouslySelectedElement);
1428+
}
1429+
});
1430+
}
13661431
};
13671432

13681433
function applyDOMEdits(edits) {
@@ -1444,6 +1509,9 @@ function RemoteFunctions(config = {}) {
14441509
_pendingHoverRAF = null;
14451510
}
14461511

1512+
// the selection is gone, so a popover can no longer turn the fills back on
1513+
SHARED_STATE._boxModelHighlightHidden = false;
1514+
14471515
// Highlight.clear() removes all overlay divs (outline + margin/padding rects)
14481516
hideHighlight();
14491517

@@ -1457,27 +1525,38 @@ function RemoteFunctions(config = {}) {
14571525
}
14581526
}
14591527

1528+
// Only the children that came from the source file. Phoenix's own elements have
1529+
// no data-brackets-id, and counting them would shift the tree path indexes.
1530+
function _instrumentedChildren(parent) {
1531+
const result = [];
1532+
const children = (parent && parent.children) || [];
1533+
for (let i = 0; i < children.length; i++) {
1534+
if (children[i].hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR)) {
1535+
result.push(children[i]);
1536+
}
1537+
}
1538+
return result;
1539+
}
1540+
14601541
/**
14611542
* Compute the tree path of an element as an array of child indices
14621543
* from <html> down. Used to re-locate the element after re-instrumentation
14631544
* when data-brackets-id changes and text matching is ambiguous.
14641545
* E.g. [1, 0, 0, 1] means html > 2nd child > 1st child > 1st child > 2nd child.
1546+
* @return {?Array.<number>} null if the element did not come from the source file.
14651547
*/
14661548
function _getTreePath(element) {
14671549
const path = [];
14681550
let el = element;
14691551
while (el && el.parentElement) {
1470-
const parent = el.parentElement;
1471-
const children = parent.children;
1472-
for (let i = 0; i < children.length; i++) {
1473-
if (children[i] === el) {
1474-
path.unshift(i);
1475-
break;
1476-
}
1552+
const index = _instrumentedChildren(el.parentElement).indexOf(el);
1553+
if (index === -1) {
1554+
return null;
14771555
}
1478-
el = parent;
1556+
path.unshift(index);
1557+
el = el.parentElement;
14791558
}
1480-
return path;
1559+
return path.length ? path : null;
14811560
}
14821561

14831562
/**
@@ -1486,10 +1565,11 @@ function RemoteFunctions(config = {}) {
14861565
function _getElementByTreePath(path) {
14871566
let el = document.documentElement;
14881567
for (let i = 0; i < path.length; i++) {
1489-
if (!el || !el.children || !el.children[path[i]]) {
1568+
const siblings = _instrumentedChildren(el);
1569+
if (!siblings[path[i]]) {
14901570
return null;
14911571
}
1492-
el = el.children[path[i]];
1572+
el = siblings[path[i]];
14931573
}
14941574
return el;
14951575
}
@@ -1570,12 +1650,12 @@ function RemoteFunctions(config = {}) {
15701650

15711651
function _handleEscapeKeyPress() {
15721652
enableHoverListeners(); // so that if hover lock is there it will get cleared
1573-
dismissUIAndCleanupState();
15741653
getAllToolHandlers().forEach(handler => {
15751654
if (handler.handleEscapePress) {
15761655
handler.handleEscapePress();
15771656
}
15781657
});
1658+
dismissUIAndCleanupState();
15791659
}
15801660

15811661
// Modifier shortcuts forwarded to the Phoenix KeyBindingManager. Clipboard
@@ -1692,6 +1772,21 @@ function RemoteFunctions(config = {}) {
16921772
}
16931773
}
16941774

1775+
/**
1776+
* Hide just the margin/padding fills of the selected element highlight, keeping
1777+
* the outline and the selection itself. Used while editing paint properties like
1778+
* background color, where the fills sit on top of what the user is changing.
1779+
* @param {Boolean} hidden
1780+
*/
1781+
function setBoxModelHighlightHidden(hidden) {
1782+
hidden = !!hidden;
1783+
if (SHARED_STATE._boxModelHighlightHidden === hidden) {
1784+
return;
1785+
}
1786+
SHARED_STATE._boxModelHighlightHidden = hidden;
1787+
redrawHighlights();
1788+
}
1789+
16951790
let customReturns = {};
16961791
// only apis that needs to be called from phoenix js layer should be customReturns. APis that are shared within
16971792
// the remote function context only should not be in customReturns and should be in
@@ -1714,7 +1809,9 @@ function RemoteFunctions(config = {}) {
17141809
"getHighlightCount": getHighlightCount,
17151810
"getHighlightTrackingElement": getHighlightTrackingElement,
17161811
"getHighlightStyle": getHighlightStyle,
1717-
"setHotCornerHidden": setHotCornerHidden
1812+
"setHotCornerHidden": setHotCornerHidden,
1813+
"setBoxModelHighlightHidden": setBoxModelHighlightHidden,
1814+
"clearHoverState": _clearHoverState
17181815
};
17191816

17201817
// the below code comment is replaced by added scripts for extensibility

0 commit comments

Comments
 (0)