Skip to content

Commit 2bd8b63

Browse files
authored
Merge pull request #9335 from google/rc/v12.3.0
release: Merge `rc/v12.3.0` into `master`.
2 parents c92314d + 5f21e9b commit 2bd8b63

File tree

159 files changed

+2247
-629
lines changed

Some content is hidden

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

159 files changed

+2247
-629
lines changed

.github/workflows/appengine_deploy.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
steps:
1616
# Checks-out the repository under $GITHUB_WORKSPACE.
1717
# When running manually this checks out the master branch.
18-
- uses: actions/checkout@v4
18+
- uses: actions/checkout@v5
1919

2020
- name: Prepare demo files
2121
# Install all dependencies, then copy all the files needed for demos.
@@ -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: 4 additions & 2 deletions
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:
@@ -24,7 +26,7 @@ jobs:
2426
# https://nodejs.org/en/about/releases/
2527

2628
steps:
27-
- uses: actions/checkout@v4
29+
- uses: actions/checkout@v5
2830
with:
2931
persist-credentials: false
3032

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ jobs:
1818
# TODO (#2114): re-enable osx build.
1919
# os: [ubuntu-latest, macos-latest]
2020
os: [ubuntu-latest]
21-
node-version: [18.x, 20.x, 22.x]
21+
node-version: [18.x, 20.x, 22.x, 24.x]
2222
# See supported Node.js release schedule at
2323
# https://nodejs.org/en/about/releases/
2424

2525
steps:
26-
- uses: actions/checkout@v4
26+
- uses: actions/checkout@v5
2727
with:
2828
persist-credentials: false
2929

@@ -54,7 +54,7 @@ jobs:
5454
timeout-minutes: 5
5555
runs-on: ubuntu-latest
5656
steps:
57-
- uses: actions/checkout@v4
57+
- uses: actions/checkout@v5
5858

5959
- name: Use Node.js 20.x
6060
uses: actions/setup-node@v4
@@ -71,7 +71,7 @@ jobs:
7171
timeout-minutes: 5
7272
runs-on: ubuntu-latest
7373
steps:
74-
- uses: actions/checkout@v4
74+
- uses: actions/checkout@v5
7575

7676
- name: Use Node.js 20.x
7777
uses: actions/setup-node@v4

.github/workflows/keyboard_plugin_test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ jobs:
2525

2626
steps:
2727
- name: Checkout core Blockly
28-
uses: actions/checkout@v4
28+
uses: actions/checkout@v5
2929
with:
3030
path: core-blockly
3131

3232
- name: Checkout keyboard navigation plugin
33-
uses: actions/checkout@v4
33+
uses: actions/checkout@v5
3434
with:
3535
repository: 'google/blockly-keyboard-experimentation'
3636
ref: 'main'

.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: 28 additions & 18 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

@@ -1116,9 +1126,9 @@ export class Block {
11161126
/**
11171127
* Returns a generator that provides every field on the block.
11181128
*
1119-
* @yields A generator that can be used to iterate the fields on the block.
1129+
* @returns 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: 9 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
};
@@ -1840,6 +1844,9 @@ export class BlockSvg
18401844
/** See IFocusableNode.onNodeFocus. */
18411845
onNodeFocus(): void {
18421846
this.select();
1847+
this.workspace.scrollBoundsIntoView(
1848+
this.getBoundingRectangleWithoutChildren(),
1849+
);
18431850
}
18441851

18451852
/** See IFocusableNode.onNodeBlur. */

core/bubbles/bubble.ts

Lines changed: 36 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;
@@ -683,6 +707,10 @@ export abstract class Bubble implements IBubble, ISelectable {
683707
onNodeFocus(): void {
684708
this.select();
685709
this.bringToFront();
710+
const xy = this.getRelativeToSurfaceXY();
711+
const size = this.getSize();
712+
const bounds = new Rect(xy.y, xy.y + size.height, xy.x, xy.x + size.width);
713+
this.workspace.scrollBoundsIntoView(bounds);
686714
}
687715

688716
/** See IFocusableNode.onNodeBlur. */
@@ -694,4 +722,11 @@ export abstract class Bubble implements IBubble, ISelectable {
694722
canBeFocused(): boolean {
695723
return true;
696724
}
725+
726+
/**
727+
* Returns the object that owns/hosts this bubble, if any.
728+
*/
729+
getOwner(): (IHasBubble & IFocusableNode) | undefined {
730+
return this.owner;
731+
}
697732
}

0 commit comments

Comments
 (0)