1515
1616import * as Blockly from 'blockly/core' ;
1717import { ASTNode , Marker } from 'blockly/core' ;
18- import { getWorkspaceElement , scrollBoundsIntoView } from './workspace_utilities' ;
18+ import { scrollBoundsIntoView } from './workspace_utilities' ;
1919
2020/** Options object for LineCursor instances. */
2121export type CursorOptions = {
@@ -61,7 +61,7 @@ export class LineCursor extends Marker {
6161 ) {
6262 super ( ) ;
6363 // Bind selectListener to facilitate future install/uninstall.
64- this . selectListener = this . selectListener . bind ( this ) ;
64+ this . changeListener = this . changeListener . bind ( this ) ;
6565 // Regularise options and apply defaults.
6666 this . options = { ...defaultOptions , ...options } ;
6767
@@ -81,7 +81,7 @@ export class LineCursor extends Marker {
8181 markerManager . setCursor ( this ) ;
8282 const oldCursorNode = this . oldCursor ?. getCurNode ( ) ;
8383 if ( oldCursorNode ) this . setCurNode ( oldCursorNode ) ;
84- this . workspace . addChangeListener ( this . selectListener ) ;
84+ this . workspace . addChangeListener ( this . changeListener ) ;
8585 this . installed = true ;
8686 }
8787
@@ -92,7 +92,7 @@ export class LineCursor extends Marker {
9292 */
9393 uninstall ( ) {
9494 if ( ! this . installed ) throw new Error ( 'LineCursor not yet installed' ) ;
95- this . workspace . removeChangeListener ( this . selectListener . bind ( this ) ) ;
95+ this . workspace . removeChangeListener ( this . changeListener . bind ( this ) ) ;
9696 if ( this . oldCursor ) {
9797 this . workspace . getMarkerManager ( ) . setCursor ( this . oldCursor ) ;
9898 }
@@ -475,6 +475,21 @@ export class LineCursor extends Marker {
475475 throw new Error ( 'no valid nodes in this.potentialNodes' ) ;
476476 }
477477
478+ /**
479+ * Get the current location of the cursor.
480+ *
481+ * Overrides normal Marker getCurNode to update the current node from the selected
482+ * block. This typically happens via the selection listener but that is not called
483+ * immediately when `Gesture` calls `Blockly.common.setSelected`.
484+ * In particular the listener runs after showing the context menu.
485+ *
486+ * @returns The current field, connection, or block the cursor is on.
487+ */
488+ override getCurNode ( ) : Blockly . ASTNode | null {
489+ this . updateCurNodeFromSelection ( ) ;
490+ return super . getCurNode ( ) ;
491+ }
492+
478493 /**
479494 * Sets the object in charge of drawing the marker.
480495 *
@@ -516,28 +531,11 @@ export class LineCursor extends Marker {
516531 * this.drawMarker() instead of this.drawer.draw() directly.
517532 *
518533 * @param newNode The new location of the cursor.
534+ * @param selectionUpToDate If false (the default) we'll update the selection too.
519535 */
520- override setCurNode ( newNode : ASTNode | null , selectionInSync = false ) {
521- if ( newNode ?. getLocation ( ) === this . getCurNode ( ) ?. getLocation ( ) ) {
522- return ;
523- }
524- if ( ! selectionInSync ) {
525- if (
526- newNode ?. getType ( ) === ASTNode . types . BLOCK &&
527- ! ( newNode . getLocation ( ) as Blockly . BlockSvg ) . isShadow ( )
528- ) {
529- if ( Blockly . common . getSelected ( ) !== newNode . getLocation ( ) ) {
530- Blockly . Events . disable ( ) ;
531- Blockly . common . setSelected ( newNode . getLocation ( ) as Blockly . BlockSvg ) ;
532- Blockly . Events . enable ( ) ;
533- }
534- } else {
535- if ( Blockly . common . getSelected ( ) ) {
536- Blockly . Events . disable ( ) ;
537- Blockly . common . setSelected ( null ) ;
538- Blockly . Events . enable ( ) ;
539- }
540- }
536+ override setCurNode ( newNode : ASTNode | null , selectionUpToDate = false ) {
537+ if ( ! selectionUpToDate ) {
538+ this . updateSelectionFromNode ( newNode ) ;
541539 }
542540
543541 super . setCurNode ( newNode ) ;
@@ -616,6 +614,7 @@ export class LineCursor extends Marker {
616614 // Selection should already be in sync.
617615 } else {
618616 block . addSelect ( ) ;
617+ block . getParent ( ) ?. removeSelect ( ) ;
619618 }
620619 }
621620
@@ -671,23 +670,98 @@ export class LineCursor extends Marker {
671670 }
672671
673672 /**
674- * Event listener that syncs the cursor location to the selected
675- * block on SELECTED events.
673+ * Event listener that syncs the cursor location to the selected block on
674+ * SELECTED events.
675+ *
676+ * This does not run early enough in all cases so `getCurNode()` also updates
677+ * the node from the selection.
678+ *
679+ * @param event The `Selected` event.
676680 */
677- private selectListener ( event : Blockly . Events . Abstract ) {
678- if ( event . type !== Blockly . Events . SELECTED ) return ;
679- const selectedEvent = event as Blockly . Events . Selected ;
680- if ( selectedEvent . workspaceId !== this . workspace . id ) return ;
681- if ( selectedEvent . newElementId ) {
682- const block = this . workspace . getBlockById ( selectedEvent . newElementId ) ;
683- if ( block ) {
684- const node = ASTNode . createBlockNode ( block ) ;
685- if ( node ) {
686- this . setCurNode ( node , true ) ;
681+ private changeListener ( event : Blockly . Events . Abstract ) {
682+ switch ( event . type ) {
683+ case Blockly . Events . SELECTED :
684+ this . updateCurNodeFromSelection ( ) ;
685+ break ;
686+ case Blockly . Events . CLICK : {
687+ const click = event as Blockly . Events . Click ;
688+ if (
689+ click . workspaceId === this . workspace . id &&
690+ click . targetType === Blockly . Events . ClickTarget . WORKSPACE
691+ ) {
692+ this . setCurNode ( null ) ;
687693 }
688694 }
695+ }
696+ }
697+
698+ /**
699+ * Updates the current node to match the selection.
700+ *
701+ * Clears the current node if it's on a block but the selection is null.
702+ * Sets the node to a block if selected for our workspace.
703+ * For shadow blocks selections the parent is used by default (unless we're
704+ * already on the shadow block via keyboard) as that's where the visual
705+ * selection is.
706+ */
707+ private updateCurNodeFromSelection ( ) {
708+ const curNode = super . getCurNode ( ) ;
709+ const selected = Blockly . common . getSelected ( ) ;
710+
711+ if (
712+ selected === null &&
713+ curNode ?. getType ( ) === Blockly . ASTNode . types . BLOCK
714+ ) {
715+ this . setCurNode ( null , true ) ;
716+ return ;
717+ }
718+ if ( selected ?. workspace !== this . workspace ) {
719+ return ;
720+ }
721+ if ( selected instanceof Blockly . BlockSvg ) {
722+ let block : Blockly . BlockSvg | null = selected ;
723+ if ( selected . isShadow ( ) ) {
724+ // OK if the current node is on the parent OR the shadow block.
725+ // The former happens for clicks, the latter for keyboard nav.
726+ if (
727+ curNode &&
728+ ( curNode . getLocation ( ) === block ||
729+ curNode . getLocation ( ) === block . getParent ( ) )
730+ ) {
731+ return ;
732+ }
733+ block = block . getParent ( ) ;
734+ }
735+ if ( block ) {
736+ this . setCurNode ( Blockly . ASTNode . createBlockNode ( block ) ! , true ) ;
737+ }
738+ }
739+ }
740+
741+ /**
742+ * Updates the selection from the node.
743+ *
744+ * Clears the selection for non-block nodes.
745+ * Clears the selection for shadow blocks as the selection is drawn on
746+ * the parent but the cursor will be drawn on the shadow block itself.
747+ * We need to take care not to later clear the current node due to that null
748+ * selection, so we track the latest selection we're in sync with.
749+ *
750+ * @param newNode The new node.
751+ */
752+ private updateSelectionFromNode ( newNode : Blockly . ASTNode | null ) {
753+ if ( newNode ?. getType ( ) === ASTNode . types . BLOCK ) {
754+ if ( Blockly . common . getSelected ( ) !== newNode . getLocation ( ) ) {
755+ Blockly . Events . disable ( ) ;
756+ Blockly . common . setSelected ( newNode . getLocation ( ) as Blockly . BlockSvg ) ;
757+ Blockly . Events . enable ( ) ;
758+ }
689759 } else {
690- this . setCurNode ( null as never , true ) ;
760+ if ( Blockly . common . getSelected ( ) ) {
761+ Blockly . Events . disable ( ) ;
762+ Blockly . common . setSelected ( null ) ;
763+ Blockly . Events . enable ( ) ;
764+ }
691765 }
692766 }
693767}
0 commit comments