scratch-blocks 2.0.1 → 2.0.2

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.
Files changed (85) hide show
  1. package/AGENTS.md +140 -0
  2. package/dist/main.mjs +1 -1
  3. package/dist/types/src/blocks/procedures.d.ts +1 -1
  4. package/dist/types/src/blocks/procedures.d.ts.map +1 -1
  5. package/dist/types/src/events/events_block_comment_base.d.ts.map +1 -1
  6. package/dist/types/src/events/events_block_drag_end.d.ts.map +1 -1
  7. package/dist/types/src/events/events_block_drag_outside.d.ts.map +1 -1
  8. package/dist/types/src/fields/field_colour_slider.d.ts +4 -4
  9. package/dist/types/src/fields/field_colour_slider.d.ts.map +1 -1
  10. package/dist/types/src/fields/field_matrix.d.ts.map +1 -1
  11. package/dist/types/src/fields/field_note.d.ts.map +1 -1
  12. package/dist/types/src/fields/scratch_field_number.d.ts.map +1 -1
  13. package/dist/types/src/fields/scratch_field_variable.d.ts +1 -1
  14. package/dist/types/src/fields/scratch_field_variable.d.ts.map +1 -1
  15. package/dist/types/src/flyout_checkbox_icon.d.ts.map +1 -1
  16. package/dist/types/src/procedures.d.ts.map +1 -1
  17. package/dist/types/src/renderer/cat/cat_face.d.ts.map +1 -1
  18. package/dist/types/src/renderer/cat/drawer.d.ts +1 -1
  19. package/dist/types/src/renderer/cat/drawer.d.ts.map +1 -1
  20. package/dist/types/src/renderer/cat/render_info.d.ts.map +1 -1
  21. package/dist/types/src/renderer/cat/renderer.d.ts +1 -1
  22. package/dist/types/src/renderer/cat/renderer.d.ts.map +1 -1
  23. package/dist/types/src/renderer/constants.d.ts +41 -0
  24. package/dist/types/src/renderer/constants.d.ts.map +1 -0
  25. package/dist/types/src/renderer/drawer.d.ts +2 -2
  26. package/dist/types/src/renderer/drawer.d.ts.map +1 -1
  27. package/dist/types/src/renderer/render_info.d.ts.map +1 -1
  28. package/dist/types/src/renderer/renderer.d.ts +1 -1
  29. package/dist/types/src/renderer/renderer.d.ts.map +1 -1
  30. package/dist/types/src/scratch_comment_bubble.d.ts +2 -2
  31. package/dist/types/src/scratch_comment_bubble.d.ts.map +1 -1
  32. package/dist/types/src/scratch_comment_icon.d.ts +1 -1
  33. package/dist/types/src/scratch_comment_icon.d.ts.map +1 -1
  34. package/dist/types/src/scratch_dragger.d.ts +1 -1
  35. package/dist/types/src/scratch_variable_model.d.ts +1 -1
  36. package/dist/types/src/scratch_variable_model.d.ts.map +1 -1
  37. package/dist/types/tests/jsunit/connection_db_test.d.ts +3 -3
  38. package/package.json +8 -8
  39. package/src/block_reporting.ts +2 -2
  40. package/src/blocks/control.ts +9 -2
  41. package/src/blocks/data.ts +34 -6
  42. package/src/blocks/procedures.ts +49 -31
  43. package/src/context_menu_items.ts +7 -7
  44. package/src/data_category.ts +4 -4
  45. package/src/events/events_block_comment_base.ts +8 -5
  46. package/src/events/events_block_comment_change.ts +4 -4
  47. package/src/events/events_block_comment_collapse.ts +14 -2
  48. package/src/events/events_block_comment_create.ts +4 -1
  49. package/src/events/events_block_comment_delete.ts +4 -2
  50. package/src/events/events_block_comment_move.ts +4 -4
  51. package/src/events/events_block_comment_resize.ts +4 -4
  52. package/src/events/events_block_drag_end.ts +4 -4
  53. package/src/events/events_block_drag_outside.ts +2 -2
  54. package/src/events/events_scratch_variable_create.ts +20 -2
  55. package/src/fields/field_colour_slider.ts +53 -28
  56. package/src/fields/field_matrix.ts +9 -8
  57. package/src/fields/field_note.ts +34 -27
  58. package/src/fields/field_textinput_removable.ts +2 -2
  59. package/src/fields/field_variable_getter.ts +5 -5
  60. package/src/fields/field_vertical_separator.ts +1 -1
  61. package/src/fields/scratch_field_angle.ts +14 -14
  62. package/src/fields/scratch_field_dropdown.ts +2 -2
  63. package/src/fields/scratch_field_number.ts +13 -12
  64. package/src/fields/scratch_field_variable.ts +8 -5
  65. package/src/flyout_checkbox_icon.ts +1 -1
  66. package/src/glows.ts +2 -2
  67. package/src/procedures.ts +25 -17
  68. package/src/renderer/cat/cat_face.ts +1 -1
  69. package/src/renderer/cat/drawer.ts +3 -3
  70. package/src/renderer/cat/render_info.ts +2 -2
  71. package/src/renderer/cat/renderer.ts +2 -2
  72. package/src/renderer/constants.ts +8 -8
  73. package/src/renderer/drawer.ts +2 -2
  74. package/src/renderer/render_info.ts +7 -4
  75. package/src/renderer/renderer.ts +2 -2
  76. package/src/scratch_block_paster.ts +1 -1
  77. package/src/scratch_comment_bubble.ts +16 -14
  78. package/src/scratch_comment_icon.ts +1 -1
  79. package/src/scratch_dragger.ts +2 -2
  80. package/src/scratch_variable_model.ts +2 -2
  81. package/src/status_indicator_label.ts +3 -3
  82. package/src/status_indicator_label_flyout_inflater.ts +1 -1
  83. package/src/variables.ts +7 -7
  84. package/src/xml.ts +3 -3
  85. package/tsconfig.json +1 -1
@@ -33,7 +33,7 @@ type ConnectionMap = {
33
33
  [key: string]: {
34
34
  shadow: Element;
35
35
  block: Blockly.BlockSvg;
36
- };
36
+ } | null;
37
37
  };
38
38
 
39
39
  /**
@@ -68,6 +68,14 @@ class DuplicateOnDragDraggable implements Blockly.IDraggable {
68
68
  */
69
69
  startDrag(e: PointerEvent) {
70
70
  const data = this.block.toCopyData();
71
+ if (!data) {
72
+ console.warn(
73
+ "DuplicateOnDragDraggable.startDrag: failed to serialize block for copy",
74
+ this.block.type,
75
+ this.block.id
76
+ );
77
+ return;
78
+ }
71
79
  this.copy = Blockly.clipboard.paste(
72
80
  data,
73
81
  this.block.workspace
@@ -76,9 +84,19 @@ class DuplicateOnDragDraggable implements Blockly.IDraggable {
76
84
  }
77
85
 
78
86
  drag(newLoc: Blockly.utils.Coordinate, e?: PointerEvent) {
79
- (
80
- this.block.workspace.getGesture(e).getCurrentDragger() as ScratchDragger
81
- ).setDraggable(this.copy);
87
+ const gesture = this.block.workspace.getGesture(e);
88
+ if (!gesture || !this.copy) {
89
+ console.warn(
90
+ "DuplicateOnDragDraggable.drag: missing gesture or copied block",
91
+ {
92
+ hasGesture: Boolean(gesture),
93
+ hasCopy: Boolean(this.copy),
94
+ blockId: this.block.id,
95
+ }
96
+ );
97
+ return;
98
+ }
99
+ (gesture.getCurrentDragger() as ScratchDragger).setDraggable(this.copy);
82
100
  this.copy.drag(newLoc, e);
83
101
  }
84
102
 
@@ -120,12 +138,12 @@ function callerMutationToDom(this: ProcedureCallBlock): Element {
120
138
  * @param xmlElement XML storage element.
121
139
  */
122
140
  function callerDomToMutation(this: ProcedureCallBlock, xmlElement: Element) {
123
- this.procCode_ = xmlElement.getAttribute("proccode");
141
+ this.procCode_ = xmlElement.getAttribute("proccode")!;
124
142
  this.generateShadows_ = JSON.parse(
125
- xmlElement.getAttribute("generateshadows")
143
+ xmlElement.getAttribute("generateshadows")!
126
144
  );
127
- this.argumentIds_ = JSON.parse(xmlElement.getAttribute("argumentids"));
128
- this.warp_ = JSON.parse(xmlElement.getAttribute("warp"));
145
+ this.argumentIds_ = JSON.parse(xmlElement.getAttribute("argumentids")!);
146
+ this.warp_ = JSON.parse(xmlElement.getAttribute("warp")!);
129
147
  this.updateDisplay_();
130
148
  }
131
149
 
@@ -167,16 +185,16 @@ function definitionDomToMutation(
167
185
  this: ProcedurePrototypeBlock | ProcedureDeclarationBlock,
168
186
  xmlElement: Element
169
187
  ) {
170
- this.procCode_ = xmlElement.getAttribute("proccode");
171
- this.warp_ = JSON.parse(xmlElement.getAttribute("warp"));
188
+ this.procCode_ = xmlElement.getAttribute("proccode")!;
189
+ this.warp_ = JSON.parse(xmlElement.getAttribute("warp")!);
172
190
 
173
191
  const prevArgIds = this.argumentIds_;
174
192
  const prevDisplayNames = this.displayNames_;
175
193
 
176
- this.argumentIds_ = JSON.parse(xmlElement.getAttribute("argumentids"));
177
- this.displayNames_ = JSON.parse(xmlElement.getAttribute("argumentnames"));
194
+ this.argumentIds_ = JSON.parse(xmlElement.getAttribute("argumentids")!);
195
+ this.displayNames_ = JSON.parse(xmlElement.getAttribute("argumentnames")!);
178
196
  this.argumentDefaults_ = JSON.parse(
179
- xmlElement.getAttribute("argumentdefaults")
197
+ xmlElement.getAttribute("argumentdefaults")!
180
198
  );
181
199
  this.updateDisplay_();
182
200
  if ("updateArgumentReporterNames_" in this) {
@@ -224,7 +242,7 @@ function disconnectOldBlocks_(this: ProcedureBlock): ConnectionMap {
224
242
  if (input.connection) {
225
243
  const target = input.connection.targetBlock() as Blockly.BlockSvg;
226
244
  const saveInfo = {
227
- shadow: input.connection.getShadowDom(true),
245
+ shadow: input.connection.getShadowDom(true) as Element,
228
246
  block: target,
229
247
  };
230
248
  connectionMap[input.name] = saveInfo;
@@ -420,7 +438,7 @@ function attachShadow_(
420
438
  new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock)
421
439
  );
422
440
  }
423
- newBlock.outputConnection.connect(input.connection);
441
+ newBlock.outputConnection!.connect(input.connection!);
424
442
  }
425
443
  }
426
444
 
@@ -486,21 +504,21 @@ function populateArgumentOnCaller_(
486
504
  id: string,
487
505
  input: Blockly.Input
488
506
  ) {
489
- let oldBlock: Blockly.BlockSvg;
490
- let oldShadow: Element;
507
+ let oldBlock: Blockly.BlockSvg | undefined;
508
+ let oldShadow: Element | undefined;
491
509
  if (connectionMap && id in connectionMap) {
492
510
  const saveInfo = connectionMap[id];
493
- oldBlock = saveInfo["block"];
494
- oldShadow = saveInfo["shadow"];
511
+ oldBlock = saveInfo?.["block"];
512
+ oldShadow = saveInfo?.["shadow"];
495
513
  }
496
514
 
497
515
  if (connectionMap && oldBlock) {
498
516
  // Reattach the old block and shadow DOM.
499
517
  connectionMap[input.name] = null;
500
- oldBlock.outputConnection.connect(input.connection);
518
+ oldBlock.outputConnection!.connect(input.connection!);
501
519
  if (type !== ArgumentType.BOOLEAN && this.generateShadows_) {
502
520
  const shadowDom = oldShadow || this.buildShadowDom_(type);
503
- input.connection.setShadowDom(shadowDom);
521
+ input.connection!.setShadowDom(shadowDom);
504
522
  }
505
523
  } else if (this.generateShadows_) {
506
524
  this.attachShadow_(input, type);
@@ -526,10 +544,10 @@ function populateArgumentOnPrototype_(
526
544
  id: string,
527
545
  input: Blockly.Input
528
546
  ) {
529
- let oldBlock = null;
547
+ let oldBlock: Blockly.BlockSvg | null = null;
530
548
  if (connectionMap && id in connectionMap) {
531
549
  const saveInfo = connectionMap[id];
532
- oldBlock = saveInfo["block"];
550
+ oldBlock = saveInfo?.["block"] ?? null;
533
551
  }
534
552
 
535
553
  const oldTypeMatches = checkOldTypeMatches_(oldBlock, type);
@@ -548,7 +566,7 @@ function populateArgumentOnPrototype_(
548
566
  }
549
567
 
550
568
  // Attach the block.
551
- input.connection.connect(argumentReporter.outputConnection);
569
+ input.connection!.connect(argumentReporter.outputConnection!);
552
570
  }
553
571
 
554
572
  /**
@@ -570,10 +588,10 @@ function populateArgumentOnDeclaration_(
570
588
  id: string,
571
589
  input: Blockly.Input
572
590
  ) {
573
- let oldBlock = null;
591
+ let oldBlock: Blockly.BlockSvg | null = null;
574
592
  if (connectionMap && id in connectionMap) {
575
593
  const saveInfo = connectionMap[id];
576
- oldBlock = saveInfo["block"];
594
+ oldBlock = saveInfo?.["block"] ?? null;
577
595
  }
578
596
 
579
597
  // TODO: This always returns false, because it checks for argument reporter
@@ -593,7 +611,7 @@ function populateArgumentOnDeclaration_(
593
611
  }
594
612
 
595
613
  // Attach the block.
596
- input.connection.connect(argumentEditor.outputConnection);
614
+ input.connection!.connect(argumentEditor.outputConnection!);
597
615
  }
598
616
 
599
617
  /**
@@ -686,7 +704,7 @@ function updateDeclarationProcCode_(this: ProcedureDeclarationBlock) {
686
704
  this.procCode_ += input.fieldRow[0].getValue();
687
705
  } else if (input.type === Blockly.inputs.inputTypes.VALUE) {
688
706
  // Inspect the argument editor.
689
- const target = input.connection.targetBlock();
707
+ const target = input.connection!.targetBlock()!;
690
708
  this.displayNames_.push(target.getFieldValue("TEXT"));
691
709
  this.argumentIds_.push(input.name);
692
710
  if (target.type === "argument_editor_boolean") {
@@ -712,8 +730,8 @@ function focusLastEditor_(this: ProcedureDeclarationBlock) {
712
730
  newInput.fieldRow[0].showEditor();
713
731
  } else if (newInput.type === Blockly.inputs.inputTypes.VALUE) {
714
732
  // Inspect the argument editor.
715
- const target = newInput.connection.targetBlock();
716
- target.getField("TEXT").showEditor();
733
+ const target = newInput.connection!.targetBlock()!;
734
+ target.getField("TEXT")!.showEditor();
717
735
  }
718
736
  }
719
737
  }
@@ -791,7 +809,7 @@ function removeFieldCallback(
791
809
  for (var n = 0; n < this.inputList.length; n++) {
792
810
  var input = this.inputList[n];
793
811
  if (input.connection) {
794
- var target = input.connection.targetBlock();
812
+ var target = input.connection!.targetBlock()!;
795
813
  if (field.name && target.getField(field.name) === field) {
796
814
  inputNameToRemove = input.name;
797
815
  }
@@ -12,20 +12,20 @@ import * as Blockly from "blockly/core";
12
12
  export function registerDeleteBlock() {
13
13
  const deleteOption = {
14
14
  displayText(scope: Blockly.ContextMenuRegistry.Scope) {
15
- const descendantCount = getDeletableBlocksInStack(scope.block).length;
15
+ const descendantCount = getDeletableBlocksInStack(scope.block!).length;
16
16
  return descendantCount === 1
17
17
  ? Blockly.Msg["DELETE_BLOCK"]
18
18
  : Blockly.Msg["DELETE_X_BLOCKS"].replace("%1", `${descendantCount}`);
19
19
  },
20
20
  preconditionFn(scope: Blockly.ContextMenuRegistry.Scope) {
21
- if (!scope.block.isInFlyout && scope.block.isDeletable()) {
21
+ if (!scope.block!.isInFlyout && scope.block!.isDeletable()) {
22
22
  return "enabled";
23
23
  }
24
24
  return "hidden";
25
25
  },
26
26
  callback(scope: Blockly.ContextMenuRegistry.Scope) {
27
27
  Blockly.Events.setGroup(true);
28
- scope.block.dispose(true, true);
28
+ scope.block!.dispose(true, true);
29
29
  Blockly.Events.setGroup(false);
30
30
  },
31
31
  scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
@@ -42,7 +42,7 @@ function getDeletableBlocksInStack(
42
42
  if (block.getNextBlock()) {
43
43
  // Next blocks are not deleted.
44
44
  const nextDescendants = block
45
- .getNextBlock()
45
+ .getNextBlock()!
46
46
  .getDescendants(false)
47
47
  .filter(isDeletable);
48
48
  descendants = descendants.filter((b) => !nextDescendants.includes(b));
@@ -161,10 +161,10 @@ function deleteNext(deleteList: Blockly.BlockSvg[], eventGroup?: string) {
161
161
  */
162
162
  export function registerDuplicateBlock() {
163
163
  const original =
164
- Blockly.ContextMenuRegistry.registry.getItem("blockDuplicate");
164
+ Blockly.ContextMenuRegistry.registry.getItem("blockDuplicate")!;
165
165
  const duplicateOption = {
166
- displayText: original.displayText,
167
- preconditionFn: original.preconditionFn,
166
+ displayText: original.displayText!,
167
+ preconditionFn: original.preconditionFn!,
168
168
  callback(scope: Blockly.ContextMenuRegistry.Scope) {
169
169
  if (!scope.block) return;
170
170
  const data = scope.block.toCopyData(true);
@@ -481,14 +481,14 @@ function addCreateButton(
481
481
  let msg = Blockly.Msg.NEW_VARIABLE;
482
482
  let callbackKey = "CREATE_VARIABLE";
483
483
  let callback = function (button: Blockly.FlyoutButton) {
484
- createVariable(button.getTargetWorkspace(), null, SCALAR_VARIABLE_TYPE);
484
+ createVariable(button.getTargetWorkspace(), undefined, SCALAR_VARIABLE_TYPE);
485
485
  };
486
486
 
487
487
  if (type === "LIST") {
488
488
  msg = Blockly.Msg.NEW_LIST;
489
489
  callbackKey = "CREATE_LIST";
490
490
  callback = function (button: Blockly.FlyoutButton) {
491
- createVariable(button.getTargetWorkspace(), null, LIST_VARIABLE_TYPE);
491
+ createVariable(button.getTargetWorkspace(), undefined, LIST_VARIABLE_TYPE);
492
492
  };
493
493
  }
494
494
  button.setAttribute("text", msg);
@@ -551,7 +551,7 @@ function addBlock(
551
551
  ${secondValueField}
552
552
  </block>
553
553
  </xml>`;
554
- var block = Blockly.utils.xml.textToDom(blockText).firstElementChild;
554
+ var block = Blockly.utils.xml.textToDom(blockText).firstElementChild!;
555
555
  xmlList.push(block);
556
556
  }
557
557
  }
@@ -617,6 +617,6 @@ function createValue(valueName: string, type: string, value: string): string {
617
617
  */
618
618
  function addSep(xmlList: Element[]) {
619
619
  const sepText = `<xml><sep gap="36"/></xml>`;
620
- const sep = Blockly.utils.xml.textToDom(sepText).firstElementChild;
620
+ const sep = Blockly.utils.xml.textToDom(sepText).firstElementChild!;
621
621
  xmlList.push(sep);
622
622
  }
@@ -9,9 +9,9 @@ import type { ScratchCommentBubble } from "../scratch_comment_bubble";
9
9
 
10
10
  export class BlockCommentBase extends Blockly.Events.Abstract {
11
11
  isBlank = true;
12
- commentId: string;
13
- blockId: string;
14
- workspaceId: string;
12
+ commentId!: string;
13
+ blockId!: string;
14
+ workspaceId!: string;
15
15
 
16
16
  constructor(opt_blockComment?: ScratchCommentBubble) {
17
17
  super();
@@ -20,8 +20,11 @@ export class BlockCommentBase extends Blockly.Events.Abstract {
20
20
  if (!opt_blockComment) return;
21
21
 
22
22
  this.commentId = opt_blockComment.getId();
23
- this.blockId = opt_blockComment.getSourceBlock()?.id;
24
- this.workspaceId = opt_blockComment.getSourceBlock()?.workspace.id;
23
+ const sourceBlock = opt_blockComment.getSourceBlock();
24
+ if (sourceBlock) {
25
+ this.blockId = sourceBlock.id;
26
+ this.workspaceId = sourceBlock.workspace.id;
27
+ }
25
28
  }
26
29
 
27
30
  toJson(): BlockCommentBaseJson {
@@ -12,8 +12,8 @@ import {
12
12
  import type { ScratchCommentBubble } from "../scratch_comment_bubble";
13
13
 
14
14
  class BlockCommentChange extends BlockCommentBase {
15
- oldContents_: string;
16
- newContents_: string;
15
+ oldContents_!: string;
16
+ newContents_!: string;
17
17
 
18
18
  constructor(
19
19
  opt_blockComment?: ScratchCommentBubble,
@@ -22,8 +22,8 @@ class BlockCommentChange extends BlockCommentBase {
22
22
  ) {
23
23
  super(opt_blockComment);
24
24
  this.type = "block_comment_change";
25
- this.oldContents_ = oldContents;
26
- this.newContents_ = newContents;
25
+ if (oldContents !== undefined) this.oldContents_ = oldContents;
26
+ if (newContents !== undefined) this.newContents_ = newContents;
27
27
  // Disable undo because Blockly already tracks changes to comment text for
28
28
  // undo purposes; this event exists solely to keep the Scratch VM apprised
29
29
  // of the state of things.
@@ -12,12 +12,12 @@ import {
12
12
  import type { ScratchCommentBubble } from "../scratch_comment_bubble";
13
13
 
14
14
  class BlockCommentCollapse extends BlockCommentBase {
15
- newCollapsed: boolean;
15
+ newCollapsed!: boolean;
16
16
 
17
17
  constructor(opt_blockComment?: ScratchCommentBubble, collapsed?: boolean) {
18
18
  super(opt_blockComment);
19
19
  this.type = "block_comment_collapse";
20
- this.newCollapsed = collapsed;
20
+ if (collapsed !== undefined) this.newCollapsed = collapsed;
21
21
  }
22
22
 
23
23
  toJson(): BlockCommentCollapseJson {
@@ -45,7 +45,19 @@ class BlockCommentCollapse extends BlockCommentBase {
45
45
  run(forward: boolean) {
46
46
  const workspace = this.getEventWorkspace_();
47
47
  const block = workspace.getBlockById(this.blockId);
48
+ if (!block) {
49
+ console.warn(
50
+ "BlockCommentCollapse.run: block not found",
51
+ this.blockId,
52
+ this.workspaceId
53
+ );
54
+ return;
55
+ }
48
56
  const comment = block.getIcon(Blockly.icons.IconType.COMMENT);
57
+ if (!comment) {
58
+ console.warn("BlockCommentCollapse.run: comment icon not found", block.id);
59
+ return;
60
+ }
49
61
  comment.setBubbleVisible(forward ? !this.newCollapsed : this.newCollapsed);
50
62
  }
51
63
  }
@@ -12,7 +12,7 @@ import {
12
12
  import type { ScratchCommentBubble } from "../scratch_comment_bubble";
13
13
 
14
14
  class BlockCommentCreate extends BlockCommentBase {
15
- json: {
15
+ json!: {
16
16
  x: number;
17
17
  y: number;
18
18
  width: number;
@@ -22,6 +22,9 @@ class BlockCommentCreate extends BlockCommentBase {
22
22
  constructor(opt_blockComment?: ScratchCommentBubble) {
23
23
  super(opt_blockComment);
24
24
  this.type = "block_comment_create";
25
+ // opt_blockComment is absent when this class is instantiated by fromJson.
26
+ // In that case fromJson sets this.json directly, so return early here.
27
+ if (!opt_blockComment) return;
25
28
  const size = opt_blockComment.getSize();
26
29
  const location = opt_blockComment.getRelativeToSurfaceXY();
27
30
  this.json = {
@@ -15,8 +15,10 @@ class BlockCommentDelete extends BlockCommentBase {
15
15
  ) {
16
16
  super(opt_blockComment);
17
17
  this.type = "block_comment_delete";
18
- this.blockId = sourceBlock.id;
19
- this.workspaceId = sourceBlock.workspace.id;
18
+ if (sourceBlock) {
19
+ this.blockId = sourceBlock.id;
20
+ this.workspaceId = sourceBlock.workspace.id;
21
+ }
20
22
  // Disable undo because Blockly already tracks comment deletion for
21
23
  // undo purposes; this event exists solely to keep the Scratch VM apprised
22
24
  // of the state of things.
@@ -12,8 +12,8 @@ import {
12
12
  import type { ScratchCommentBubble } from "../scratch_comment_bubble";
13
13
 
14
14
  class BlockCommentMove extends BlockCommentBase {
15
- oldCoordinate_: Blockly.utils.Coordinate;
16
- newCoordinate_: Blockly.utils.Coordinate;
15
+ oldCoordinate_!: Blockly.utils.Coordinate;
16
+ newCoordinate_!: Blockly.utils.Coordinate;
17
17
 
18
18
  constructor(
19
19
  opt_blockComment?: ScratchCommentBubble,
@@ -22,8 +22,8 @@ class BlockCommentMove extends BlockCommentBase {
22
22
  ) {
23
23
  super(opt_blockComment);
24
24
  this.type = "block_comment_move";
25
- this.oldCoordinate_ = oldCoordinate;
26
- this.newCoordinate_ = newCoordinate;
25
+ if (oldCoordinate !== undefined) this.oldCoordinate_ = oldCoordinate;
26
+ if (newCoordinate !== undefined) this.newCoordinate_ = newCoordinate;
27
27
  }
28
28
 
29
29
  toJson(): BlockCommentMoveJson {
@@ -12,8 +12,8 @@ import {
12
12
  import type { ScratchCommentBubble } from "../scratch_comment_bubble";
13
13
 
14
14
  class BlockCommentResize extends BlockCommentBase {
15
- oldSize: Blockly.utils.Size;
16
- newSize: Blockly.utils.Size;
15
+ oldSize!: Blockly.utils.Size;
16
+ newSize!: Blockly.utils.Size;
17
17
 
18
18
  constructor(
19
19
  opt_blockComment?: ScratchCommentBubble,
@@ -22,8 +22,8 @@ class BlockCommentResize extends BlockCommentBase {
22
22
  ) {
23
23
  super(opt_blockComment);
24
24
  this.type = "block_comment_resize";
25
- this.oldSize = oldSize;
26
- this.newSize = newSize;
25
+ if (oldSize !== undefined) this.oldSize = oldSize;
26
+ if (newSize !== undefined) this.newSize = newSize;
27
27
  }
28
28
 
29
29
  toJson(): BlockCommentResizeJson {
@@ -7,15 +7,15 @@
7
7
  import * as Blockly from "blockly/core";
8
8
 
9
9
  export class BlockDragEnd extends Blockly.Events.BlockBase {
10
- isOutside: boolean;
11
- xml: Element | DocumentFragment;
10
+ isOutside!: boolean;
11
+ xml!: Element | DocumentFragment;
12
12
 
13
13
  constructor(block?: Blockly.Block, isOutside?: boolean) {
14
14
  super(block);
15
15
  this.type = "endDrag";
16
- this.isOutside = isOutside;
16
+ if (isOutside !== undefined) this.isOutside = isOutside;
17
17
  this.recordUndo = false;
18
- this.xml = Blockly.Xml.blockToDom(block, true);
18
+ if (block) this.xml = Blockly.Xml.blockToDom(block, true);
19
19
  }
20
20
 
21
21
  toJson(): BlockDragEndJson {
@@ -7,12 +7,12 @@
7
7
  import * as Blockly from "blockly/core";
8
8
 
9
9
  export class BlockDragOutside extends Blockly.Events.BlockBase {
10
- isOutside: boolean;
10
+ isOutside!: boolean;
11
11
 
12
12
  constructor(block?: Blockly.Block, isOutside?: boolean) {
13
13
  super(block);
14
14
  this.type = "dragOutside";
15
- this.isOutside = isOutside;
15
+ if (isOutside !== undefined) this.isOutside = isOutside;
16
16
  this.recordUndo = false;
17
17
  }
18
18
 
@@ -8,8 +8,8 @@ import * as Blockly from "blockly/core";
8
8
  import { ScratchVariableModel } from "../scratch_variable_model";
9
9
 
10
10
  class ScratchVariableCreate extends Blockly.Events.VarCreate {
11
- isLocal: boolean;
12
- isCloud: boolean;
11
+ isLocal!: boolean;
12
+ isCloud!: boolean;
13
13
 
14
14
  constructor(variable?: ScratchVariableModel) {
15
15
  super(variable);
@@ -46,6 +46,17 @@ class ScratchVariableCreate extends Blockly.Events.VarCreate {
46
46
  const workspace = this.getEventWorkspace_();
47
47
  const variableMap = workspace.getVariableMap();
48
48
  if (forward) {
49
+ if (this.varName == null || this.varType == null || this.varId == null) {
50
+ console.error(
51
+ "ScratchVariableCreate.run: missing variable data for create",
52
+ {
53
+ varName: this.varName,
54
+ varType: this.varType,
55
+ varId: this.varId,
56
+ }
57
+ );
58
+ return;
59
+ }
49
60
  const variable = new ScratchVariableModel(
50
61
  workspace,
51
62
  this.varName,
@@ -59,6 +70,13 @@ class ScratchVariableCreate extends Blockly.Events.VarCreate {
59
70
  new (Blockly.Events.get(Blockly.Events.VAR_CREATE))(variable)
60
71
  );
61
72
  } else {
73
+ if (this.varId == null) {
74
+ console.error(
75
+ "ScratchVariableCreate.run: missing varId for delete",
76
+ this
77
+ );
78
+ return;
79
+ }
62
80
  const variable = variableMap.getVariableById(this.varId);
63
81
  if (variable) {
64
82
  variableMap.deleteVariable(variable);
@@ -41,9 +41,9 @@ export class FieldColourSlider extends FieldColour {
41
41
  * The button calls this function with a callback to update the field value.
42
42
  * BEWARE: This is not a stable API. It may change.
43
43
  */
44
- static activateEyedropper_: (
44
+ static activateEyedropper_?: (
45
45
  callback: (colour: string) => void
46
- ) => void | null = null;
46
+ ) => void;
47
47
 
48
48
  /**
49
49
  * Path to the eyedropper svg icon.
@@ -61,9 +61,9 @@ export class FieldColourSlider extends FieldColour {
61
61
  private hueReadout_?: Element;
62
62
  private saturationReadout_?: Element;
63
63
  private brightnessReadout_?: Element;
64
- private hue_?: number;
65
- private saturation_?: number;
66
- private brightness_?: number;
64
+ private hue_ = 0;
65
+ private saturation_ = 0;
66
+ private brightness_ = 0;
67
67
  private eyedropperEventData_?: Blockly.browserEvents.Data;
68
68
 
69
69
  /**
@@ -133,34 +133,58 @@ export class FieldColourSlider extends FieldColour {
133
133
  * Update the readouts and slider backgrounds after value has changed.
134
134
  */
135
135
  private updateDom_() {
136
- if (this.hueSlider_) {
137
- // Update the slider backgrounds
138
- this.setGradient_(this.hueSlider_, ColourChannel.HUE);
139
- this.setGradient_(this.saturationSlider_, ColourChannel.SATURATION);
140
- this.setGradient_(this.brightnessSlider_, ColourChannel.BRIGHTNESS);
141
-
142
- // Update the readouts
143
- this.hueReadout_.textContent = Math.floor(
144
- (100 * this.hue_) / 360
145
- ).toFixed(0);
146
- this.saturationReadout_.textContent = Math.floor(
147
- 100 * this.saturation_
148
- ).toFixed(0);
149
- this.brightnessReadout_.textContent = Math.floor(
150
- (100 * this.brightness_) / 255
151
- ).toFixed(0);
136
+ const hueSlider = this.hueSlider_;
137
+ const saturationSlider = this.saturationSlider_;
138
+ const brightnessSlider = this.brightnessSlider_;
139
+ const hueReadout = this.hueReadout_;
140
+ const saturationReadout = this.saturationReadout_;
141
+ const brightnessReadout = this.brightnessReadout_;
142
+ if (
143
+ !hueSlider ||
144
+ !saturationSlider ||
145
+ !brightnessSlider ||
146
+ !hueReadout ||
147
+ !saturationReadout ||
148
+ !brightnessReadout
149
+ ) {
150
+ console.warn(
151
+ "FieldColourSlider.updateDom_: slider/readout DOM is not fully initialized"
152
+ );
153
+ return;
152
154
  }
155
+
156
+ this.setGradient_(hueSlider, ColourChannel.HUE);
157
+ this.setGradient_(saturationSlider, ColourChannel.SATURATION);
158
+ this.setGradient_(brightnessSlider, ColourChannel.BRIGHTNESS);
159
+
160
+ hueReadout.textContent = Math.floor(
161
+ (100 * this.hue_) / 360
162
+ ).toFixed(0);
163
+ saturationReadout.textContent = Math.floor(
164
+ 100 * this.saturation_
165
+ ).toFixed(0);
166
+ brightnessReadout.textContent = Math.floor(
167
+ (100 * this.brightness_) / 255
168
+ ).toFixed(0);
153
169
  }
154
170
 
155
171
  /**
156
172
  * Update the slider handle positions from the current field value.
157
173
  */
158
174
  private updateSliderHandles_() {
159
- if (this.hueSlider_) {
160
- this.hueSlider_.value = `${this.hue_}`;
161
- this.saturationSlider_.value = `${this.saturation_}`;
162
- this.brightnessSlider_.value = `${this.brightness_}`;
175
+ const hueSlider = this.hueSlider_;
176
+ const saturationSlider = this.saturationSlider_;
177
+ const brightnessSlider = this.brightnessSlider_;
178
+ if (!hueSlider || !saturationSlider || !brightnessSlider) {
179
+ console.warn(
180
+ "FieldColourSlider.updateSliderHandles_: slider DOM is not fully initialized"
181
+ );
182
+ return;
163
183
  }
184
+
185
+ hueSlider.value = `${this.hue_}`;
186
+ saturationSlider.value = `${this.saturation_}`;
187
+ brightnessSlider.value = `${this.brightness_}`;
164
188
  }
165
189
 
166
190
  /**
@@ -219,7 +243,7 @@ export class FieldColourSlider extends FieldColour {
219
243
  * Activate the eyedropper, passing in a callback for setting the field value.
220
244
  */
221
245
  private activateEyedropperInternal_() {
222
- FieldColourSlider.activateEyedropper_((chosenColour: string) => {
246
+ FieldColourSlider.activateEyedropper_?.((chosenColour: string) => {
223
247
  // Update the internal hue/saturation/brightness values so sliders update.
224
248
  const components = Blockly.utils.colour.hexToRgb(chosenColour);
225
249
  const { hue, saturation, value } = this.rgbToHsv(
@@ -245,7 +269,8 @@ export class FieldColourSlider extends FieldColour {
245
269
 
246
270
  // Init color component values that are used while the editor is open
247
271
  // in order to keep the slider values stable.
248
- const components = Blockly.utils.colour.hexToRgb(this.getValue());
272
+ const currentValue = this.getValue() ?? "#000000";
273
+ const components = Blockly.utils.colour.hexToRgb(currentValue);
249
274
  const { hue, saturation, value } = this.rgbToHsv(
250
275
  components[0],
251
276
  components[1],
@@ -314,7 +339,7 @@ export class FieldColourSlider extends FieldColour {
314
339
 
315
340
  // Set value updates the slider positions
316
341
  // Do this before attaching callbacks to avoid extra events from initial set
317
- this.setValue(this.getValue());
342
+ this.setValue(this.getValue() ?? currentValue);
318
343
 
319
344
  this.hueChangeEventKey_ = Blockly.browserEvents.bind(
320
345
  this.hueSlider_,