@@ -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