scratch-blocks 2.0.0-spork.3 → 2.0.0-spork.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,18 @@
1
1
  /**
2
2
  * @license
3
- * Copyright 2020 Google LLC
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- /**
8
- * @license
9
- * Copyright 2021 Google LLC
3
+ * Copyright 2023 Google LLC
10
4
  * SPDX-License-Identifier: Apache-2.0
11
5
  */
12
6
 
13
7
  /**
14
8
  * @license
15
- * Copyright 2023 Google LLC
9
+ * Copyright 2024 Google LLC
16
10
  * SPDX-License-Identifier: Apache-2.0
17
11
  */
18
12
 
19
13
  /**
20
14
  * @license
21
- * Copyright 2024 Google LLC
15
+ * Copyright 2025 Google LLC
22
16
  * SPDX-License-Identifier: Apache-2.0
23
17
  */
24
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scratch-blocks",
3
- "version": "2.0.0-spork.3",
3
+ "version": "2.0.0-spork.5",
4
4
  "description": "Scratch Blocks is a library for building creative computing interfaces.",
5
5
  "author": "Massachusetts Institute of Technology",
6
6
  "license": "Apache-2.0",
@@ -32,9 +32,9 @@
32
32
  "webpack-dev-server": "^4.11.1"
33
33
  },
34
34
  "dependencies": {
35
- "@blockly/continuous-toolbox": "^6.0.9",
36
- "@blockly/field-colour": "^5.0.9",
37
- "blockly": "^12.0.0-beta.0"
35
+ "@blockly/continuous-toolbox": "^7.0.1",
36
+ "@blockly/field-colour": "^6.0.3",
37
+ "blockly": "12.3.0-beta.0"
38
38
  },
39
39
  "config": {
40
40
  "commitizen": {
@@ -224,7 +224,7 @@ function disconnectOldBlocks_(this: ProcedureBlock): ConnectionMap {
224
224
  if (input.connection) {
225
225
  const target = input.connection.targetBlock() as Blockly.BlockSvg;
226
226
  const saveInfo = {
227
- shadow: input.connection.getShadowDom(),
227
+ shadow: input.connection.getShadowDom(true),
228
228
  block: target,
229
229
  };
230
230
  connectionMap[input.name] = saveInfo;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2024 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as Blockly from "blockly/core";
8
+ import { ContinuousFlyout } from "@blockly/continuous-toolbox";
9
+ import { CheckboxBubble } from "./checkbox_bubble";
10
+ import { StatusIndicatorLabel } from "./status_indicator_label";
11
+ import { STATUS_INDICATOR_LABEL_TYPE } from "./status_indicator_label_flyout_inflater";
12
+
13
+ export class CheckableContinuousFlyout extends ContinuousFlyout {
14
+ /**
15
+ * Creates a new CheckableContinuousFlyout.
16
+ *
17
+ * @param workspaceOptions Configuration options for the flyout workspace.
18
+ */
19
+ constructor(workspaceOptions: Blockly.Options) {
20
+ workspaceOptions.modalInputs = false;
21
+ super(workspaceOptions);
22
+ this.tabWidth_ = 0;
23
+ this.MARGIN = 12;
24
+ this.GAP_Y = 12;
25
+ }
26
+
27
+ /**
28
+ * Serializes a block to JSON in order to copy it to the main workspace.
29
+ *
30
+ * @param block The block to serialize.
31
+ * @returns A JSON representation of the block.
32
+ */
33
+ protected serializeBlock(block: Blockly.BlockSvg) {
34
+ const json = super.serializeBlock(block);
35
+ // Delete the serialized block's ID so that a new one is generated when it is
36
+ // placed on the workspace. Otherwise, the block on the workspace may be
37
+ // indistinguishable from the one in the flyout, which can cause reporter blocks
38
+ // to have their value dropdown shown in the wrong place.
39
+ delete json.id;
40
+ return json;
41
+ }
42
+
43
+ /**
44
+ * Set the state of a checkbox by block ID.
45
+ *
46
+ * @param blockId ID of the block whose checkbox should be set
47
+ * @param value Value to set the checkbox to.
48
+ */
49
+ setCheckboxState(blockId: string, value: boolean) {
50
+ this.getWorkspace()
51
+ .getBlockById(blockId)
52
+ ?.getIcon("checkbox")
53
+ ?.setChecked(value);
54
+ }
55
+
56
+ getFlyoutScale() {
57
+ return 0.675;
58
+ }
59
+
60
+ getWidth() {
61
+ return 250;
62
+ }
63
+
64
+ protected reflowInternal_() {
65
+ super.reflowInternal_();
66
+
67
+ if (this.RTL) {
68
+ // The parent implementation assumes that the flyout grows to fit its
69
+ // contents, and adjusts blocks in RTL mode accordingly. In Scratch, the
70
+ // flyout width is fixed (and blocks may exceed it), so re-adjust blocks
71
+ // accordingly based on the actual fixed width.
72
+ for (const item of this.getContents()) {
73
+ const oldX = item.getElement().getBoundingRectangle().left;
74
+ let newX =
75
+ this.getWidth() / this.workspace_.scale -
76
+ item.getElement().getBoundingRectangle().getWidth() -
77
+ this.MARGIN;
78
+ if (
79
+ "checkboxInFlyout" in item.getElement() &&
80
+ item.getElement().checkboxInFlyout
81
+ ) {
82
+ newX -= CheckboxBubble.CHECKBOX_SIZE + CheckboxBubble.CHECKBOX_MARGIN;
83
+ }
84
+ item.getElement().moveBy(newX - oldX, 0);
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Validates that the given toolbox item represents a label.
91
+ *
92
+ * @param item The toolbox item to check.
93
+ * @returns True if the item represents a label in the flyout.
94
+ */
95
+ protected toolboxItemIsLabel(item: Blockly.FlyoutItem) {
96
+ return (
97
+ item.getType() === STATUS_INDICATOR_LABEL_TYPE ||
98
+ super.toolboxItemIsLabel(item)
99
+ );
100
+ }
101
+
102
+ /**
103
+ * Updates the state of status indicators for hardware-based extensions.
104
+ */
105
+ refreshStatusButtons() {
106
+ for (const item of this.contents) {
107
+ if (item.element instanceof StatusIndicatorLabel) {
108
+ item.element.refreshStatus();
109
+ }
110
+ }
111
+ }
112
+
113
+ scrollTo(position: number) {
114
+ super.scrollTo(Math.ceil(position));
115
+ }
116
+ }
@@ -203,15 +203,14 @@ export class CheckboxBubble
203
203
  * Recalculates this bubble's location, keeping it adjacent to its block.
204
204
  */
205
205
  updateLocation() {
206
- const blockLocation = this.sourceBlock.getRelativeToSurfaceXY();
207
- const blockBounds = this.sourceBlock.getHeightWidth();
206
+ const bounds = this.sourceBlock.getBoundingRectangle();
208
207
  const x = this.sourceBlock.workspace.RTL
209
- ? blockLocation.x + blockBounds.width + CheckboxBubble.CHECKBOX_MARGIN
210
- : blockLocation.x -
208
+ ? bounds.right + CheckboxBubble.CHECKBOX_MARGIN
209
+ : bounds.left -
211
210
  CheckboxBubble.CHECKBOX_MARGIN -
212
211
  CheckboxBubble.CHECKBOX_SIZE;
213
212
  const y =
214
- blockLocation.y + (blockBounds.height - CheckboxBubble.CHECKBOX_SIZE) / 2;
213
+ bounds.top + (bounds.getHeight() - CheckboxBubble.CHECKBOX_SIZE) / 2;
215
214
  this.moveTo(x, y);
216
215
  }
217
216
 
@@ -244,6 +243,27 @@ export class CheckboxBubble
244
243
  Blockly.browserEvents.unbind(this.clickListener);
245
244
  }
246
245
 
246
+ /** See IFocusableNode.getFocusableElement. */
247
+ getFocusableElement(): HTMLElement | SVGElement {
248
+ return this.svgRoot;
249
+ }
250
+
251
+ /** See IFocusableNode.getFocusableTree. */
252
+ getFocusableTree(): Blockly.IFocusableTree {
253
+ return this.sourceBlock.workspace;
254
+ }
255
+
256
+ /** See IFocusableNode.onNodeFocus. */
257
+ onNodeFocus(): void {}
258
+
259
+ /** See IFocusableNode.onNodeBlur. */
260
+ onNodeBlur(): void {}
261
+
262
+ /** See IFocusableNode.canBeFocused. */
263
+ canBeFocused(): boolean {
264
+ return true;
265
+ }
266
+
247
267
  // These methods are required by the interfaces, but intentionally have no
248
268
  // implementation, largely because this bubble's location is fixed relative
249
269
  // to its block and is not draggable by the user.
package/src/colours.ts CHANGED
@@ -53,6 +53,7 @@ const Colours = {
53
53
  valueReportBackground: "#FFFFFF",
54
54
  valueReportBorder: "#AAAAAA",
55
55
  contextualMenuHover: "rgba(77, 151, 255, .25)",
56
+ menuHover: "rgba(0, 0, 0, .2)",
56
57
  };
57
58
 
58
59
  /**
@@ -154,3 +154,27 @@ function deleteNext(deleteList: Blockly.BlockSvg[], eventGroup?: string) {
154
154
  }
155
155
  Blockly.Events.setGroup(false);
156
156
  }
157
+
158
+ /**
159
+ * Registers a block duplicate option that duplicates the selected block and
160
+ * all subsequent blocks in the stack.
161
+ */
162
+ export function registerDuplicateBlock() {
163
+ const original =
164
+ Blockly.ContextMenuRegistry.registry.getItem("blockDuplicate");
165
+ const duplicateOption = {
166
+ displayText: original.displayText,
167
+ preconditionFn: original.preconditionFn,
168
+ callback(scope: Blockly.ContextMenuRegistry.Scope) {
169
+ if (!scope.block) return;
170
+ const data = scope.block.toCopyData(true);
171
+ if (!data) return;
172
+ Blockly.clipboard.paste(data, scope.block.workspace);
173
+ },
174
+ scopeType: original.scopeType,
175
+ id: original.id,
176
+ weight: original.weight,
177
+ };
178
+ Blockly.ContextMenuRegistry.registry.unregister(duplicateOption.id);
179
+ Blockly.ContextMenuRegistry.registry.register(duplicateOption);
180
+ }
package/src/css.ts CHANGED
@@ -702,16 +702,16 @@ const styles = `
702
702
  }
703
703
 
704
704
  .blocklyAngleCenterPoint {
705
- stroke: #fff;
705
+ stroke: var(--colour-text);
706
706
  stroke-width: 1;
707
- fill: #fff;
707
+ fill: var(--colour-text);
708
708
  }
709
709
 
710
710
  .blocklyAngleDragHandle {
711
- stroke: #fff;
711
+ stroke: var(--colour-text);
712
712
  stroke-width: 5;
713
713
  stroke-opacity: 0.25;
714
- fill: #fff;
714
+ fill: var(--colour-text);
715
715
  cursor: pointer;
716
716
  }
717
717
 
@@ -720,18 +720,18 @@ const styles = `
720
720
  }
721
721
 
722
722
  .blocklyAngleMarks {
723
- stroke: #fff;
723
+ stroke: var(--colour-text);
724
724
  stroke-width: 1;
725
725
  stroke-opacity: 0.5;
726
726
  }
727
727
 
728
728
  .blocklyAngleGauge {
729
- fill: #fff;
729
+ fill: var(--colour-text);
730
730
  fill-opacity: 0.20;
731
731
  }
732
732
 
733
733
  .blocklyAngleLine {
734
- stroke: #fff;
734
+ stroke: var(--colour-text);
735
735
  stroke-width: 1;
736
736
  stroke-linecap: round;
737
737
  pointer-events: none;
@@ -751,8 +751,9 @@ const styles = `
751
751
  }
752
752
 
753
753
  /* Category tree in Toolbox. */
754
- .blocklyToolboxDiv {
754
+ .blocklyToolbox {
755
755
  background-color: var(--colour-toolbox);
756
+ border-right: 1px solid #ddd;
756
757
  color: var(--colour-toolboxText);
757
758
  overflow-x: visible;
758
759
  overflow-y: auto;
@@ -763,6 +764,11 @@ const styles = `
763
764
  padding: 0;
764
765
  }
765
766
 
767
+ .blocklyToolbox[dir="RTL"] {
768
+ border-right: none;
769
+ border-left: 1px solid #ddd;
770
+ }
771
+
766
772
  .blocklyTreeRoot {
767
773
  padding: 4px 0;
768
774
  }
@@ -771,7 +777,7 @@ const styles = `
771
777
  outline: none;
772
778
  }
773
779
 
774
- .blocklyToolboxDiv .blocklyTreeRow {
780
+ .blocklyToolbox .blocklyToolboxCategory {
775
781
  line-height: 22px;
776
782
  margin: 0;
777
783
  padding: 0.375rem 0px;
@@ -789,11 +795,11 @@ const styles = `
789
795
  margin: 1px 0 8px 5px;
790
796
  }
791
797
 
792
- .blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {
793
- margin-left: 8px;
798
+ .blocklyToolbox[dir="RTL"] .blocklyToolboxCategory {
799
+ margin-left: 0px;
794
800
  }
795
801
 
796
- .blocklyTreeRow:hover {
802
+ .blocklyToolboxCategory:hover {
797
803
  color: var(--colour-toolboxHover);
798
804
  }
799
805
 
@@ -844,7 +850,7 @@ const styles = `
844
850
  background-position: -48px -1px;
845
851
  }
846
852
 
847
- .blocklyTreeLabel {
853
+ .blocklyToolboxCategoryLabel {
848
854
  cursor: default;
849
855
  font-family: "Helvetica Neue", Helvetica, sans-serif;
850
856
  font-size: .65rem;
@@ -855,7 +861,7 @@ const styles = `
855
861
  text-wrap: wrap;
856
862
  }
857
863
 
858
- .blocklyTreeSelected .blocklyTreeLabel {
864
+ .blocklyToolboxSelected .blocklyToolboxCategoryLabel {
859
865
  color: inherit;
860
866
  }
861
867
 
@@ -1005,12 +1011,12 @@ const styles = `
1005
1011
  z-index: 20000; /* Arbitrary, but some apps depend on it... */
1006
1012
  }
1007
1013
 
1008
- .blocklyDropDownDiv .blocklyMenu .blocklyMenuItem:hover {
1009
- background: var(--colour-menuHover);
1014
+ .blocklyDropDownDiv .blocklyMenu .blocklyMenuItem.blocklyMenuItemHighlight {
1015
+ background-color: var(--colour-menuHover);
1010
1016
  }
1011
1017
 
1012
- .blocklyWidgetDiv .blocklyMenu .blocklyMenuItem:hover {
1013
- background: var(--colour-contextualMenuHover);
1018
+ .blocklyWidgetDiv .blocklyMenu .blocklyMenuItem.blocklyMenuItemHighlight {
1019
+ background-color: var(--colour-contextualMenuHover);
1014
1020
  }
1015
1021
 
1016
1022
  .blocklyWidgetDiv .blocklyMenu .blocklyMenuItemDisabled.blocklyMenuItem:hover {
@@ -1162,13 +1168,12 @@ const styles = `
1162
1168
  color: #4c97ff;
1163
1169
  }
1164
1170
  .blocklyDropDownDiv .blocklyMenuItem {
1165
- color: #fff;
1166
1171
  font-weight: bold;
1167
1172
  min-height: 32px;
1168
1173
  padding: 4px 7em 4px 28px;
1169
1174
  }
1170
- .high-contrast-theme.blocklyDropDownDiv .blocklyMenuItem {
1171
- color: #000;
1175
+ .scratch-renderer.blocklyDropDownDiv .blocklyMenuItem .blocklyMenuItemContent {
1176
+ color: var(--colour-text);
1172
1177
  }
1173
1178
  .blocklyToolboxSelected .blocklyTreeLabel {
1174
1179
  color: var(--colour-toolboxText);
@@ -1192,6 +1197,10 @@ const styles = `
1192
1197
  stroke: revert-layer;
1193
1198
  stroke-width: 1;
1194
1199
  }
1200
+
1201
+ .blocklyInsertionMarker > g:not(:last-child) {
1202
+ visibility: hidden;
1203
+ }
1195
1204
  `;
1196
1205
 
1197
1206
  Blockly.Css.register(styles);
@@ -215,7 +215,7 @@ class FieldMatrix extends Blockly.Field<string> {
215
215
  this.arrow_.setAttributeNS(
216
216
  "http://www.w3.org/1999/xlink",
217
217
  "xlink:href",
218
- Blockly.getMainWorkspace().options.pathToMedia + "dropdown-arrow.svg"
218
+ this.getConstants().FIELD_DROPDOWN_SVG_ARROW_DATAURI
219
219
  );
220
220
  this.arrow_.style.cursor = "default";
221
221
  }
@@ -238,6 +238,25 @@ class FieldMatrix extends Blockly.Field<string> {
238
238
  * Show the drop-down menu for editing this field.
239
239
  */
240
240
  showEditor_() {
241
+ const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg;
242
+ Blockly.DropDownDiv.setColour(
243
+ sourceBlock.getColour(),
244
+ sourceBlock.getColourTertiary()
245
+ );
246
+
247
+ const style = sourceBlock.style;
248
+ if (sourceBlock.isShadow()) {
249
+ this.originalStyle = sourceBlock.getStyleName();
250
+ sourceBlock.setStyle(`${this.originalStyle}_selected`);
251
+ } else if (this.borderRect_) {
252
+ this.borderRect_.setAttribute(
253
+ "fill",
254
+ "colourQuaternary" in style
255
+ ? `${style.colourQuaternary}`
256
+ : style.colourTertiary
257
+ );
258
+ }
259
+
241
260
  const div = Blockly.DropDownDiv.getContentDiv();
242
261
  // Build the SVG DOM.
243
262
  const matrixSize =
@@ -286,23 +305,19 @@ class FieldMatrix extends Blockly.Field<string> {
286
305
  // Button to clear matrix
287
306
  const clearButtonDiv = document.createElement("div");
288
307
  clearButtonDiv.className = "scratchMatrixButtonDiv";
289
- const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg;
308
+
290
309
  const clearButton = this.createButton_(sourceBlock.getColourSecondary());
291
310
  clearButtonDiv.appendChild(clearButton);
292
311
  // Button to fill matrix
293
312
  const fillButtonDiv = document.createElement("div");
294
313
  fillButtonDiv.className = "scratchMatrixButtonDiv";
295
- const fillButton = this.createButton_("#FFFFFF");
314
+ const fillButton = this.createButton_("var(--colour-text)");
296
315
  fillButtonDiv.appendChild(fillButton);
297
316
 
298
317
  buttonDiv.appendChild(clearButtonDiv);
299
318
  buttonDiv.appendChild(fillButtonDiv);
300
319
  div.appendChild(buttonDiv);
301
320
 
302
- Blockly.DropDownDiv.setColour(
303
- sourceBlock.getColour(),
304
- sourceBlock.getColourTertiary()
305
- );
306
321
  Blockly.DropDownDiv.showPositionedByBlock(
307
322
  this,
308
323
  sourceBlock,
@@ -328,19 +343,6 @@ class FieldMatrix extends Blockly.Field<string> {
328
343
  this.fillMatrix_
329
344
  );
330
345
 
331
- const style = sourceBlock.style;
332
- if (sourceBlock.isShadow()) {
333
- this.originalStyle = sourceBlock.getStyleName();
334
- sourceBlock.setStyle(`${this.originalStyle}_selected`);
335
- } else if (this.borderRect_) {
336
- this.borderRect_.setAttribute(
337
- "fill",
338
- "colourQuaternary" in style
339
- ? `${style.colourQuaternary}`
340
- : style.colourTertiary
341
- );
342
- }
343
-
344
346
  // Update the matrix for the current value
345
347
  this.updateMatrix_();
346
348
  }
@@ -350,6 +352,7 @@ class FieldMatrix extends Blockly.Field<string> {
350
352
  if (sourceBlock.isShadow()) {
351
353
  sourceBlock.setStyle(this.originalStyle);
352
354
  }
355
+ this.updateMatrix_();
353
356
  }
354
357
 
355
358
  /**
@@ -400,12 +403,16 @@ class FieldMatrix extends Blockly.Field<string> {
400
403
  this.fillMatrixNode_(
401
404
  this.ledButtons_,
402
405
  i,
406
+ sourceBlock.getColourTertiary()
407
+ );
408
+ this.fillMatrixNode_(
409
+ this.ledThumbNodes_,
410
+ i,
403
411
  sourceBlock.getColourSecondary()
404
412
  );
405
- this.fillMatrixNode_(this.ledThumbNodes_, i, sourceBlock.getColour());
406
413
  } else {
407
- this.fillMatrixNode_(this.ledButtons_, i, "#FFFFFF");
408
- this.fillMatrixNode_(this.ledThumbNodes_, i, "#FFFFFF");
414
+ this.fillMatrixNode_(this.ledButtons_, i, "var(--colour-text)");
415
+ this.fillMatrixNode_(this.ledThumbNodes_, i, "var(--colour-text)");
409
416
  }
410
417
  }
411
418
  }
@@ -505,10 +512,14 @@ class FieldMatrix extends Blockly.Field<string> {
505
512
  * Unbind mouse move event and clear the paint style.
506
513
  */
507
514
  onMouseUp() {
508
- Blockly.browserEvents.unbind(this.matrixMoveWrapper_);
509
- this.matrixMoveWrapper_ = null;
510
- Blockly.browserEvents.unbind(this.matrixReleaseWrapper_);
511
- this.matrixReleaseWrapper_ = null;
515
+ if (this.matrixMoveWrapper_) {
516
+ Blockly.browserEvents.unbind(this.matrixMoveWrapper_);
517
+ this.matrixMoveWrapper_ = null;
518
+ }
519
+ if (this.matrixReleaseWrapper_) {
520
+ Blockly.browserEvents.unbind(this.matrixReleaseWrapper_);
521
+ this.matrixReleaseWrapper_ = null;
522
+ }
512
523
  this.paintStyle_ = null;
513
524
  }
514
525
 
@@ -286,7 +286,7 @@ export class FieldNote extends Blockly.FieldTextInput {
286
286
  * Show a field with piano keys.
287
287
  */
288
288
  showEditor_(event: PointerEvent, quietInput = false) {
289
- super.showEditor_(event, quietInput);
289
+ super.showEditor_(event, quietInput, false);
290
290
 
291
291
  // Build the SVG DOM.
292
292
  const div = Blockly.DropDownDiv.getContentDiv();
@@ -157,7 +157,15 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
157
157
  * Show the inline free-text editor on top of the text.
158
158
  */
159
159
  showEditor_(event: PointerEvent) {
160
- super.showEditor_(event);
160
+ // Mobile browsers have issues with in-line textareas (focus & keyboards).
161
+ // Also, don't let the parent take ephemeral focus since the drop-down div
162
+ // below will handle it, instead.
163
+ const noFocus =
164
+ Blockly.utils.userAgent.MOBILE ||
165
+ Blockly.utils.userAgent.ANDROID ||
166
+ Blockly.utils.userAgent.IPAD;
167
+ super.showEditor_(event, noFocus, false);
168
+
161
169
  // If there is an existing drop-down someone else owns, hide it immediately and clear it.
162
170
  Blockly.DropDownDiv.hideWithoutAnimation();
163
171
  Blockly.DropDownDiv.clearContent();
@@ -94,7 +94,7 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
94
94
  * @returns Array of variable names.
95
95
  */
96
96
  static dropdownCreate(this: ScratchFieldVariable): Blockly.MenuOption[] {
97
- const options = super.dropdownCreate();
97
+ let options = super.dropdownCreate();
98
98
  const type = this.getDefaultType();
99
99
  if (type === Constants.BROADCAST_MESSAGE_VARIABLE_TYPE) {
100
100
  options.splice(-2, 2, [
@@ -102,16 +102,17 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
102
102
  Constants.NEW_BROADCAST_MESSAGE_ID,
103
103
  ]);
104
104
  } else if (type === Constants.LIST_VARIABLE_TYPE) {
105
- for (const option of options) {
105
+ options = options.map((option) => {
106
106
  if (option[1] === Blockly.RENAME_VARIABLE_ID) {
107
- option[0] = ScratchMsgs.translate("RENAME_LIST");
107
+ return [ScratchMsgs.translate("RENAME_LIST"), option[1]];
108
108
  } else if (option[1] === Blockly.DELETE_VARIABLE_ID) {
109
- option[0] = ScratchMsgs.translate("DELETE_LIST").replace(
110
- "%1",
111
- this.getText()
112
- );
109
+ return [
110
+ ScratchMsgs.translate("DELETE_LIST").replace("%1", this.getText()),
111
+ option[1],
112
+ ];
113
113
  }
114
- }
114
+ return option;
115
+ });
115
116
  }
116
117
 
117
118
  return options;
@@ -10,11 +10,15 @@ import { CheckboxBubble } from "./checkbox_bubble";
10
10
  /**
11
11
  * Invisible icon that exists solely to host the corresponding checkbox bubble.
12
12
  */
13
- export class FlyoutCheckboxIcon implements Blockly.IIcon, Blockly.IHasBubble {
13
+ export class FlyoutCheckboxIcon
14
+ extends Blockly.icons.Icon
15
+ implements Blockly.IHasBubble
16
+ {
14
17
  private checkboxBubble: CheckboxBubble;
15
18
  private type = new Blockly.icons.IconType("checkbox");
16
19
 
17
- constructor(private sourceBlock: Blockly.BlockSvg) {
20
+ constructor(protected override sourceBlock: Blockly.BlockSvg) {
21
+ super(sourceBlock);
18
22
  if (this.sourceBlock.workspace.isFlyout) {
19
23
  this.checkboxBubble = new CheckboxBubble(this.sourceBlock);
20
24
  }
@@ -24,19 +28,11 @@ export class FlyoutCheckboxIcon implements Blockly.IIcon, Blockly.IHasBubble {
24
28
  return this.type;
25
29
  }
26
30
 
27
- getWeight(): number {
28
- return -1;
29
- }
30
-
31
31
  getSize(): Blockly.utils.Size {
32
32
  // Awful hack to cancel out the default padding added to icons.
33
33
  return new Blockly.utils.Size(-8, 0);
34
34
  }
35
35
 
36
- isShownWhenCollapsed(): boolean {
37
- return false;
38
- }
39
-
40
36
  isClickableInFlyout(): boolean {
41
37
  return false;
42
38
  }
@@ -55,25 +51,23 @@ export class FlyoutCheckboxIcon implements Blockly.IIcon, Blockly.IHasBubble {
55
51
 
56
52
  dispose() {
57
53
  this.checkboxBubble?.dispose();
54
+ super.dispose();
58
55
  }
59
56
 
60
57
  // These methods are required by the interfaces, but intentionally have no
61
58
  // implementation, largely because this icon has no visual representation.
62
- applyColour() {}
63
-
64
- hideForInsertionMarker() {}
65
-
66
- updateEditable() {}
67
-
68
- updateCollapsed() {}
69
-
70
- setOffsetInBlock() {}
71
-
72
- onClick() {}
73
59
 
74
60
  async setBubbleVisible(visible: boolean) {}
75
61
 
76
62
  initView(pointerDownListener: (e: PointerEvent) => void) {}
63
+
64
+ canBeFocused() {
65
+ return false;
66
+ }
67
+
68
+ getBubble() {
69
+ return this.checkboxBubble;
70
+ }
77
71
  }
78
72
 
79
73
  Blockly.registry.register(