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 }
@@ -479,36 +479,14 @@ export class LineCursor extends Marker {
479479 * Get the current location of the cursor.
480480 *
481481 * Overrides normal Marker getCurNode to update the current node from the selected
482- * block. We typically update the node from the selection via a listener but
483- * that is not called immediately when `Gesture` calls `Blockly.common.setSelected`.
482+ * block. This typically happens via the selection listener but that is not called
483+ * immediately when `Gesture` calls `Blockly.common.setSelected`.
484484 * In particular the listener runs after showing the context menu.
485485 *
486486 * @returns The current field, connection, or block the cursor is on.
487487 */
488- override getCurNode ( ) : ASTNode | null {
489- const curNode = super . getCurNode ( ) ;
490- let selected = Blockly . common . getSelected ( ) ;
491- if (
492- selected === null &&
493- curNode ?. getType ( ) === Blockly . ASTNode . types . BLOCK
494- ) {
495- // Selection says our curNode is not selected anymore.
496- // this.setCurNode(null as never, true);
497- return super . getCurNode ( ) ;
498- }
499- if ( selected ?. workspace !== this . workspace ) return curNode ;
500- // Selection has a block in our workspace.
501- if ( selected instanceof Blockly . BlockSvg ) {
502- if ( selected . isShadow ( ) ) {
503- // Although the shadow block is selected it's the parent that has the
504- // visual selection.
505- selected = selected . getParent ( ) ;
506- }
507- if ( selected ) {
508- this . setCurNode ( new ASTNode ( ASTNode . types . BLOCK , selected ) , true ) ;
509- }
510- }
511-
488+ override getCurNode ( ) : Blockly . ASTNode | null {
489+ this . updateCurNodeFromSelection ( ) ;
512490 return super . getCurNode ( ) ;
513491 }
514492
@@ -555,25 +533,9 @@ export class LineCursor extends Marker {
555533 * @param newNode The new location of the cursor.
556534 * @param selectionUpToDate If false (the default) we'll update the selection too.
557535 */
558- override setCurNode ( newNode : ASTNode , selectionUpToDate = false ) {
536+ override setCurNode ( newNode : ASTNode | null , selectionUpToDate = false ) {
559537 if ( ! selectionUpToDate ) {
560- if (
561- newNode ?. getType ( ) === ASTNode . types . BLOCK &&
562- // For shadow blocks we need to clear the selection that's drawn on their parent.
563- ! ( newNode . getLocation ( ) as Blockly . BlockSvg ) . isShadow ( )
564- ) {
565- if ( Blockly . common . getSelected ( ) !== newNode . getLocation ( ) ) {
566- Blockly . Events . disable ( ) ;
567- Blockly . common . setSelected ( newNode . getLocation ( ) as Blockly . BlockSvg ) ;
568- Blockly . Events . enable ( ) ;
569- }
570- } else {
571- if ( Blockly . common . getSelected ( ) ) {
572- Blockly . Events . disable ( ) ;
573- Blockly . common . setSelected ( null ) ;
574- Blockly . Events . enable ( ) ;
575- }
576- }
538+ this . updateSelectionFromNode ( newNode ) ;
577539 }
578540
579541 super . setCurNode ( newNode ) ;
@@ -652,6 +614,7 @@ export class LineCursor extends Marker {
652614 // Selection should already be in sync.
653615 } else {
654616 block . addSelect ( ) ;
617+ block . getParent ( ) ?. removeSelect ( ) ;
655618 }
656619 }
657620
@@ -707,18 +670,99 @@ export class LineCursor extends Marker {
707670 }
708671
709672 /**
710- * Event listener that syncs the cursor location to the selected
711- * 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.
712678 *
713679 * @param event The `Selected` event.
714680 */
715- private selectListener ( event : Blockly . Events . Abstract ) {
716- if ( event . type !== Blockly . Events . SELECTED ) return ;
717- const selectedEvent = event as Blockly . Events . Selected ;
718- if ( selectedEvent . workspaceId !== this . workspace . id ) return ;
719- // This runs too late so the logic to update the selection is in
720- // `getCurNode`.
721- this . getCurNode ( ) ;
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 ) ;
693+ }
694+ }
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+ }
759+ } else {
760+ if ( Blockly . common . getSelected ( ) ) {
761+ Blockly . Events . disable ( ) ;
762+ Blockly . common . setSelected ( null ) ;
763+ Blockly . Events . enable ( ) ;
764+ }
765+ }
722766 }
723767}
724768
0 commit comments