Skip to content

Commit 972e3f6

Browse files
committed
fix: replace the cycling logic with the divide total space by number of candidates logic
1 parent 0face1b commit 972e3f6

File tree

1 file changed

+114
-114
lines changed

1 file changed

+114
-114
lines changed

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 114 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,9 @@ function RemoteFunctions(config = {}) {
8080
let _autoScrollTimer = null;
8181
let _isAutoScrolling = false; // to disable highlights when auto scrolling
8282

83-
// these variables are used when cycling between the possible drop locations
84-
let _dragHoverTimer = null;
85-
let _lastDragX = null;
86-
let _lastDragY = null;
83+
// track the last drag target and drop zone to detect when they change
8784
let _lastDragTarget = null;
88-
let _parentCycleList = [];
89-
let _currentCycleIndex = 0;
90-
let _currentDropZone = null;
91-
let _currentIndicatorType = null;
85+
let _lastDropZone = null;
9286

9387
// initialized from config, defaults to true if not set
9488
let imageGallerySelected = config.imageGalleryState !== undefined ? config.imageGalleryState : true;
@@ -535,7 +529,8 @@ function RemoteFunctions(config = {}) {
535529
element.style.opacity = 1;
536530
}
537531
delete element._originalDragOpacity;
538-
_stopParentCycling();
532+
_lastDragTarget = null;
533+
_lastDropZone = null;
539534
}
540535

541536
/**
@@ -1275,26 +1270,108 @@ function RemoteFunctions(config = {}) {
12751270
}
12761271

12771272
/**
1278-
* Cycle to the next parent target and update drop markers
1273+
* this function decides which target should we select based on the list of the candidates
1274+
* so the total stable area (stable area: where marker remains consistent) will be divided by the no. of candidates
1275+
* and then based on the mouse pointer location we see which candidate is currently positioned and then return it
1276+
*
1277+
* @param {Element} initialTarget - the base element
1278+
* @param {Array<Element>} candidates - all the possible valid targets
1279+
* @param {number} clientX
1280+
* @param {number} clientY
1281+
* @param {string} indicatorType - "horizontal" or "vertical"
1282+
* @param {string} dropZone - "before", "after", or "inside"
1283+
* @returns {Element} - The selected target based on section
12791284
*/
1280-
function _cycleToNextParent() {
1281-
if (_parentCycleList.length <= 1) {
1282-
return;
1285+
function _selectTargetFromSection(initialTarget, candidates, clientX, clientY, indicatorType, dropZone) {
1286+
// when only one candidate is available or the drop zone is inside, we just return it
1287+
if (candidates.length === 1 || dropZone === 'inside') {
1288+
return candidates[0];
12831289
}
12841290

1285-
_currentCycleIndex = (_currentCycleIndex + 1) % _parentCycleList.length;
1286-
const nextTarget = _parentCycleList[_currentCycleIndex];
1291+
const rect = initialTarget.getBoundingClientRect();
1292+
let relativePosition;
12871293

1288-
_currentIndicatorType = _getIndicatorType(nextTarget);
1294+
// for horz, we divide the height by n no. of candidates
1295+
if (indicatorType === "horizontal") {
1296+
const height = rect.height;
1297+
const stableAreaSize = height * 0.3; // 0.3 is the drop zone threshold
12891298

1290-
_clearDropMarkers();
1291-
_createDropMarker(nextTarget, _currentDropZone, _currentIndicatorType);
1299+
if (dropZone === "before") {
1300+
// for top arrow: stable area is top 30% of height
1301+
const stableAreaStart = rect.top;
1302+
relativePosition = (clientY - stableAreaStart) / stableAreaSize;
1303+
} else {
1304+
// for bottom arrow: stable area is bottom 30% of height
1305+
const stableAreaStart = rect.bottom - stableAreaSize;
1306+
relativePosition = (clientY - stableAreaStart) / stableAreaSize;
1307+
}
1308+
} else {
1309+
// for vertical, we divide the width by n
1310+
const width = rect.width;
1311+
const stableAreaSize = width * 0.3;
1312+
1313+
if (dropZone === "before") {
1314+
// left arrow: stable area is left 30% of width
1315+
const stableAreaStart = rect.left;
1316+
relativePosition = (clientX - stableAreaStart) / stableAreaSize;
1317+
} else {
1318+
// right arrow: stable area is right 30% of width
1319+
const stableAreaStart = rect.right - stableAreaSize;
1320+
relativePosition = (clientX - stableAreaStart) / stableAreaSize;
1321+
}
1322+
}
1323+
1324+
1325+
relativePosition = Math.max(0, Math.min(1, relativePosition));
1326+
// for "before" markers (top/left arrows), reverse the candidate order
1327+
// because innermost should be at bottom/right of stable area
1328+
let orderedCandidates = candidates;
1329+
if (dropZone === "before") {
1330+
orderedCandidates = [...candidates].reverse();
1331+
}
1332+
1333+
// find out which section the mouse is in
1334+
const sectionIndex = Math.floor(relativePosition * orderedCandidates.length);
1335+
// clamp the index to valid range (handles edge case where relativePosition = 1)
1336+
const clampedIndex = Math.min(sectionIndex, orderedCandidates.length - 1);
1337+
1338+
return orderedCandidates[clampedIndex];
1339+
}
1340+
1341+
/**
1342+
* this function gets the list of all the valid target candidates
1343+
* below are the 3 cases to satisfy to be a valid target candidate:
1344+
* - Same arrow type (indicator type)
1345+
* - 3 edges aligned based on drop zone
1346+
* - Semantic element
1347+
*
1348+
* @param {Element} initialTarget - base element
1349+
* @param {string} dropZone - "before", "after", or "inside"
1350+
* @param {string} indicatorType - "horizontal" or "vertical"
1351+
* @returns {Array<Element>} - list of valid candidates [initialTarget, ...validParents]
1352+
*/
1353+
function _getValidTargetCandidates(initialTarget, dropZone, indicatorType) {
1354+
// for inside we don't need to check
1355+
if (dropZone === "inside") {
1356+
return [initialTarget];
1357+
}
1358+
1359+
const allParents = _getAllValidParentCandidates(initialTarget);
1360+
1361+
const validParents = allParents.filter(parent => {
1362+
if (!_isSemanticElement(parent)) {
1363+
return false;
1364+
}
1365+
1366+
const parentIndicatorType = _getIndicatorType(parent);
1367+
if (parentIndicatorType !== indicatorType) {
1368+
return false;
1369+
}
12921370

1293-
_lastDragTarget = nextTarget;
1371+
return _hasThreeEdgesAligned(initialTarget, parent, dropZone);
1372+
});
12941373

1295-
_dragHoverTimer = setTimeout(() => {
1296-
_cycleToNextParent();
1297-
}, 1000);
1374+
return [initialTarget, ...validParents];
12981375
}
12991376

13001377
/**
@@ -1342,67 +1419,6 @@ function RemoteFunctions(config = {}) {
13421419
return false;
13431420
}
13441421

1345-
/**
1346-
* start the parent cycling timer, only to go it when parent has a genuine possibility of being the drop target
1347-
* 3 cases we check to say if parent might be a possibility:
1348-
* (same arrow type, correct 3 edges aligned, semantic parent)
1349-
*
1350-
* @param {DOMElement} initialTarget - The element user is hovering over
1351-
*/
1352-
function _startParentCycling(initialTarget) {
1353-
// no cycling when drop zone is inside
1354-
if (_currentDropZone === "inside") {
1355-
return;
1356-
}
1357-
1358-
const initialIndicatorType = _currentIndicatorType;
1359-
const allParents = _getAllValidParentCandidates(initialTarget);
1360-
1361-
// (same arrow type, correct 3 edges aligned, semantic parent)
1362-
// all this cases should match for it to be a valid parent
1363-
const validParents = allParents.filter(parent => {
1364-
if (!_isSemanticElement(parent)) {
1365-
return false;
1366-
}
1367-
1368-
// make sure parent has the same indicator type
1369-
const parentIndicatorType = _getIndicatorType(parent);
1370-
if (parentIndicatorType !== initialIndicatorType) {
1371-
return false;
1372-
}
1373-
1374-
// the correct 3 edges should align
1375-
return _hasThreeEdgesAligned(initialTarget, parent, _currentDropZone);
1376-
});
1377-
1378-
_parentCycleList = [initialTarget, ...validParents];
1379-
if (_parentCycleList.length <= 1) {
1380-
return;
1381-
}
1382-
1383-
_currentCycleIndex = 0;
1384-
_dragHoverTimer = setTimeout(() => {
1385-
_cycleToNextParent();
1386-
}, 1000);
1387-
}
1388-
1389-
/**
1390-
* Stop parent cycling and clean up state
1391-
*/
1392-
function _stopParentCycling() {
1393-
if (_dragHoverTimer) {
1394-
clearTimeout(_dragHoverTimer);
1395-
_dragHoverTimer = null;
1396-
}
1397-
_parentCycleList = [];
1398-
_currentCycleIndex = 0;
1399-
_lastDragX = null;
1400-
_lastDragY = null;
1401-
_lastDragTarget = null;
1402-
_currentDropZone = null;
1403-
_currentIndicatorType = null;
1404-
}
1405-
14061422
/**
14071423
* this function is for finding the best target element on where to drop the dragged element
14081424
* for ex: div > image...here both the div and image are of the exact same size, then when user is dragging some
@@ -1525,42 +1541,27 @@ function RemoteFunctions(config = {}) {
15251541

15261542
// Check if we should prefer a parent when all edges are aligned
15271543
const bestParent = _findBestParentTarget(target);
1528-
if (bestParent) {
1529-
target = bestParent;
1530-
}
1531-
1532-
// check if cursor position changed significantly (>5px threshold to avoid jitter)
1533-
const positionChanged = _lastDragX === null || _lastDragY === null ||
1534-
Math.abs(_lastDragX - event.clientX) > 5 ||
1535-
Math.abs(_lastDragY - event.clientY) > 5;
1544+
const initialTarget = bestParent || target;
15361545

1537-
// check if we moved to a different target element
1538-
// during cycling, ignore element detection to prevent reset loop
1539-
const elementChanged = (_parentCycleList.length === 0) && (_lastDragTarget !== target);
1546+
// calc indicator type and drop zone for initial target
1547+
const indicatorType = _getIndicatorType(initialTarget);
1548+
const dropZone = _getDropZone(initialTarget, event.clientX, event.clientY, indicatorType, window._currentDraggedElement);
15401549

1541-
if (elementChanged || positionChanged) {
1542-
// reset as user moved to a new element or position changed significantly
1543-
_stopParentCycling();
1550+
// get the list of all the valid candidates for section selection
1551+
const candidates = _getValidTargetCandidates(initialTarget, dropZone, indicatorType);
15441552

1545-
_lastDragX = event.clientX;
1546-
_lastDragY = event.clientY;
1547-
_lastDragTarget = target;
1553+
// select the target based on mouse position within sections
1554+
const selectedTarget = _selectTargetFromSection(initialTarget, candidates, event.clientX, event.clientY, indicatorType, dropZone);
15481555

1549-
const indicatorType = _getIndicatorType(target);
1550-
const dropZone = _getDropZone(target, event.clientX, event.clientY, indicatorType, window._currentDraggedElement);
1551-
1552-
_currentIndicatorType = indicatorType;
1553-
_currentDropZone = dropZone;
1556+
// if the target or drop zone changes, then we can update
1557+
if (_lastDragTarget !== selectedTarget || _lastDropZone !== dropZone) {
1558+
_lastDragTarget = selectedTarget;
1559+
_lastDropZone = dropZone;
15541560

15551561
if (!_isAutoScrolling) {
15561562
_clearDropMarkers();
1557-
_createDropMarker(target, dropZone, indicatorType);
1563+
_createDropMarker(selectedTarget, dropZone, indicatorType);
15581564
}
1559-
1560-
_startParentCycling(target);
1561-
} else if (_dragHoverTimer === null && !_isAutoScrolling) {
1562-
// hovering in same spot but timer was cleared, so restart it
1563-
_startParentCycling(target);
15641565
}
15651566

15661567
// Handle auto-scroll
@@ -1575,7 +1576,8 @@ function RemoteFunctions(config = {}) {
15751576
if (!event.relatedTarget) {
15761577
_clearDropMarkers();
15771578
_stopAutoScroll();
1578-
_stopParentCycling();
1579+
_lastDragTarget = null;
1580+
_lastDropZone = null;
15791581
}
15801582
}
15811583

@@ -1617,7 +1619,6 @@ function RemoteFunctions(config = {}) {
16171619

16181620
// skip if no valid target found or if it's the dragged element
16191621
if (!isElementEditable(target) || target === window._currentDraggedElement) {
1620-
_stopParentCycling();
16211622
_clearDropMarkers();
16221623
_stopAutoScroll();
16231624
_dragEndChores(window._currentDraggedElement);
@@ -1658,7 +1659,6 @@ function RemoteFunctions(config = {}) {
16581659
// send message to the editor
16591660
window._Brackets_MessageBroker.send(messageData);
16601661

1661-
_stopParentCycling();
16621662
_clearDropMarkers();
16631663
_stopAutoScroll();
16641664
_dragEndChores(window._currentDraggedElement);

0 commit comments

Comments
 (0)