From 54d58424d2475c2bc5fda57ee446500784ecd65d Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 22 Dec 2025 09:42:10 -0800 Subject: [PATCH 1/2] fix: Fix bug that prevented the first block change event in a flyout from being dispatched --- core/flyout_base.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/flyout_base.ts b/core/flyout_base.ts index 492d3341762..358c5532c9e 100644 --- a/core/flyout_base.ts +++ b/core/flyout_base.ts @@ -135,6 +135,12 @@ export abstract class Flyout */ private reflowWrapper: ((e: AbstractEvent) => void) | null = null; + /** + * If true, prevents the reflow wrapper from running. Used to prevent infinite + * recursion. + */ + private inhibitReflowWrapper = false; + /** * List of flyout elements. */ @@ -647,6 +653,7 @@ export abstract class Flyout // accommodates e.g. resizing a non-autoclosing flyout in response to the // user typing long strings into fields on the blocks in the flyout. this.reflowWrapper = (event) => { + if (this.inhibitReflowWrapper) return; if ( event.type === EventType.BLOCK_CHANGE || event.type === EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE @@ -844,13 +851,9 @@ export abstract class Flyout * Reflow flyout contents. */ reflow() { - if (this.reflowWrapper) { - this.workspace_.removeChangeListener(this.reflowWrapper); - } + this.inhibitReflowWrapper = true; this.reflowInternal_(); - if (this.reflowWrapper) { - this.workspace_.addChangeListener(this.reflowWrapper); - } + this.inhibitReflowWrapper = false; } /** From 96e67a088825bdeda30d41316d5be1c8457b17bc Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 23 Dec 2025 10:03:12 -0800 Subject: [PATCH 2/2] chore: Add test for flyout event listeners --- tests/mocha/flyout_test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/mocha/flyout_test.js b/tests/mocha/flyout_test.js index 998279a0874..e2812b25ba3 100644 --- a/tests/mocha/flyout_test.js +++ b/tests/mocha/flyout_test.js @@ -43,6 +43,26 @@ suite('Flyout', function () { sharedTestTeardown.call(this); }); + suite('workspace change listeners', function () { + test('are triggered when a child block changes', function () { + let listenerTriggered = false; + const listener = (e) => { + if (e.type === Blockly.Events.BLOCK_CHANGE) { + listenerTriggered = true; + } + }; + this.workspace.getFlyout().getWorkspace().addChangeListener(listener); + const boolBlock = this.workspace + .getFlyout() + .getWorkspace() + .getBlocksByType('logic_compare')[0]; + boolBlock.getField('OP').setValue('LTE'); + this.clock.tick(1000); + assert.isTrue(listenerTriggered); + this.workspace.getFlyout().getWorkspace().removeChangeListener(listener); + }); + }); + suite('position', function () { suite('vertical flyout', function () { suite('simple flyout', function () {