Skip to content

Commit a53db40

Browse files
committed
release: Merge branch 'develop' into rc/v12.3.0
2 parents c92314d + ef235cf commit a53db40

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1539
-354
lines changed

.github/workflows/appengine_deploy.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ jobs:
3636
needs: prepare
3737
steps:
3838
- name: Download prepared files
39-
uses: actions/download-artifact@v4
39+
uses: actions/download-artifact@v5
4040
with:
4141
name: appengine_files
4242
path: _deploy/
4343

4444
- name: Deploy to App Engine
45-
uses: google-github-actions/[email protected].5
45+
uses: google-github-actions/[email protected].7
4646
# For parameters see:
4747
# https://github.com/google-github-actions/deploy-appengine#inputs
4848
with:

.github/workflows/browser_test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ name: Run browser manually
55

66
on:
77
workflow_dispatch:
8+
schedule:
9+
- cron: '0 6 * * 1' # Runs every Monday at 06:00 UTC
810

911
permissions:
1012
contents: read
1113

1214
jobs:
1315
build:
14-
timeout-minutes: 10
16+
timeout-minutes: 120
1517
runs-on: ${{ matrix.os }}
1618

1719
strategy:

.github/workflows/welcome_new_contributors.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
permissions:
1010
pull-requests: write
1111
steps:
12-
- uses: actions/first-interaction@v1
12+
- uses: actions/first-interaction@v3
1313
with:
1414
repo-token: ${{ secrets.GITHUB_TOKEN }}
1515
pr-message: >

core/block.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -501,22 +501,32 @@ export class Block {
501501
// Detach this block from the parent's tree.
502502
this.previousConnection.disconnect();
503503
}
504-
const nextBlock = this.getNextBlock();
505-
if (opt_healStack && nextBlock && !nextBlock.isShadow()) {
506-
// Disconnect the next statement.
507-
const nextTarget = this.nextConnection?.targetConnection ?? null;
508-
nextTarget?.disconnect();
509-
if (
510-
previousTarget &&
511-
this.workspace.connectionChecker.canConnect(
512-
previousTarget,
513-
nextTarget,
514-
false,
515-
)
516-
) {
517-
// Attach the next statement to the previous statement.
518-
previousTarget.connect(nextTarget!);
519-
}
504+
505+
if (!opt_healStack) return;
506+
507+
// Immovable or shadow next blocks need to move along with the block; keep
508+
// going until we encounter a normal block or run off the end of the stack.
509+
let nextBlock = this.getNextBlock();
510+
while (nextBlock && (nextBlock.isShadow() || !nextBlock.isMovable())) {
511+
nextBlock = nextBlock.getNextBlock();
512+
}
513+
if (!nextBlock) return;
514+
515+
// Disconnect the next statement.
516+
const nextTarget =
517+
nextBlock.previousConnection?.targetBlock()?.nextConnection
518+
?.targetConnection ?? null;
519+
nextTarget?.disconnect();
520+
if (
521+
previousTarget &&
522+
this.workspace.connectionChecker.canConnect(
523+
previousTarget,
524+
nextTarget,
525+
false,
526+
)
527+
) {
528+
// Attach the next statement to the previous statement.
529+
previousTarget.connect(nextTarget!);
520530
}
521531
}
522532

@@ -1118,7 +1128,7 @@ export class Block {
11181128
*
11191129
* @yields A generator that can be used to iterate the fields on the block.
11201130
*/
1121-
*getFields(): Generator<Field> {
1131+
*getFields(): Generator<Field, undefined, void> {
11221132
for (const input of this.inputList) {
11231133
for (const field of input.fieldRow) {
11241134
yield field;

core/block_svg.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -951,17 +951,21 @@ export class BlockSvg
951951
/**
952952
* Encode a block for copying.
953953
*
954+
* @param addNextBlocks If true, copy subsequent blocks attached to this one
955+
* as well.
956+
*
954957
* @returns Copy metadata, or null if the block is an insertion marker.
955958
*/
956-
toCopyData(): BlockCopyData | null {
959+
toCopyData(addNextBlocks = false): BlockCopyData | null {
957960
if (this.isInsertionMarker_) {
958961
return null;
959962
}
960963
return {
961964
paster: BlockPaster.TYPE,
962965
blockState: blocks.save(this, {
963966
addCoordinates: true,
964-
addNextBlocks: false,
967+
addNextBlocks,
968+
saveIds: false,
965969
}) as blocks.State,
966970
typeCounts: common.getBlockTypeCounts(this, true),
967971
};

core/bubbles/bubble.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import * as common from '../common.js';
99
import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js';
1010
import {getFocusManager} from '../focus_manager.js';
1111
import {IBubble} from '../interfaces/i_bubble.js';
12+
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
1213
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
14+
import type {IHasBubble} from '../interfaces/i_has_bubble.js';
1315
import {ISelectable} from '../interfaces/i_selectable.js';
1416
import {ContainerRegion} from '../metrics_manager.js';
1517
import {Scrollbar} from '../scrollbar.js';
@@ -27,7 +29,7 @@ import {WorkspaceSvg} from '../workspace_svg.js';
2729
* bubble, where it has a "tail" that points to the block, and a "head" that
2830
* displays arbitrary svg elements.
2931
*/
30-
export abstract class Bubble implements IBubble, ISelectable {
32+
export abstract class Bubble implements IBubble, ISelectable, IFocusableNode {
3133
/** The width of the border around the bubble. */
3234
static readonly BORDER_WIDTH = 6;
3335

@@ -100,12 +102,14 @@ export abstract class Bubble implements IBubble, ISelectable {
100102
* element that's represented by this bubble (as a focusable node). This
101103
* element will have its ID overwritten. If not provided, the focusable
102104
* element of this node will default to the bubble's SVG root.
105+
* @param owner The object responsible for hosting/spawning this bubble.
103106
*/
104107
constructor(
105108
public readonly workspace: WorkspaceSvg,
106109
protected anchor: Coordinate,
107110
protected ownerRect?: Rect,
108111
overriddenFocusableElement?: SVGElement | HTMLElement,
112+
protected owner?: IHasBubble & IFocusableNode,
109113
) {
110114
this.id = idGenerator.getNextUniqueId();
111115
this.svgRoot = dom.createSvgElement(
@@ -145,6 +149,13 @@ export abstract class Bubble implements IBubble, ISelectable {
145149
this,
146150
this.onMouseDown,
147151
);
152+
153+
browserEvents.conditionalBind(
154+
this.focusableElement,
155+
'keydown',
156+
this,
157+
this.onKeyDown,
158+
);
148159
}
149160

150161
/** Dispose of this bubble. */
@@ -229,6 +240,19 @@ export abstract class Bubble implements IBubble, ISelectable {
229240
getFocusManager().focusNode(this);
230241
}
231242

243+
/**
244+
* Handles key events when this bubble is focused. By default, closes the
245+
* bubble on Escape.
246+
*
247+
* @param e The keyboard event to handle.
248+
*/
249+
protected onKeyDown(e: KeyboardEvent) {
250+
if (e.key === 'Escape' && this.owner) {
251+
this.owner.setBubbleVisible(false);
252+
getFocusManager().focusNode(this.owner);
253+
}
254+
}
255+
232256
/** Positions the bubble relative to its anchor. Does not render its tail. */
233257
protected positionRelativeToAnchor() {
234258
let left = this.anchor.x;
@@ -694,4 +718,11 @@ export abstract class Bubble implements IBubble, ISelectable {
694718
canBeFocused(): boolean {
695719
return true;
696720
}
721+
722+
/**
723+
* Returns the object that owns/hosts this bubble, if any.
724+
*/
725+
getOwner(): (IHasBubble & IFocusableNode) | undefined {
726+
return this.owner;
727+
}
697728
}

0 commit comments

Comments
 (0)