scratch-blocks 2.1.5 → 2.1.6

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 (117) hide show
  1. package/AGENTS.md +58 -14
  2. package/dist/main.mjs +1 -1
  3. package/dist/types/src/block_reporting.d.ts.map +1 -1
  4. package/dist/types/src/blocks/procedures.d.ts +2 -2
  5. package/dist/types/src/blocks/procedures.d.ts.map +1 -1
  6. package/dist/types/src/checkable_continuous_flyout.d.ts +6 -3
  7. package/dist/types/src/checkable_continuous_flyout.d.ts.map +1 -1
  8. package/dist/types/src/checkbox_bubble.d.ts +7 -7
  9. package/dist/types/src/checkbox_bubble.d.ts.map +1 -1
  10. package/dist/types/src/colours.d.ts.map +1 -1
  11. package/dist/types/src/context_menu_items.d.ts.map +1 -1
  12. package/dist/types/src/events/events_block_comment_base.d.ts +1 -1
  13. package/dist/types/src/events/events_block_comment_base.d.ts.map +1 -1
  14. package/dist/types/src/events/events_block_drag_end.d.ts +1 -1
  15. package/dist/types/src/events/events_block_drag_end.d.ts.map +1 -1
  16. package/dist/types/src/events/events_block_drag_outside.d.ts +1 -1
  17. package/dist/types/src/events/events_block_drag_outside.d.ts.map +1 -1
  18. package/dist/types/src/fields/field_colour_slider.d.ts.map +1 -1
  19. package/dist/types/src/fields/field_matrix.d.ts.map +1 -1
  20. package/dist/types/src/fields/field_note.d.ts +6 -4
  21. package/dist/types/src/fields/field_note.d.ts.map +1 -1
  22. package/dist/types/src/fields/field_textinput_removable.d.ts.map +1 -1
  23. package/dist/types/src/fields/field_variable_getter.d.ts.map +1 -1
  24. package/dist/types/src/fields/field_vertical_separator.d.ts.map +1 -1
  25. package/dist/types/src/fields/scratch_field_angle.d.ts.map +1 -1
  26. package/dist/types/src/fields/scratch_field_dropdown.d.ts.map +1 -1
  27. package/dist/types/src/fields/scratch_field_number.d.ts.map +1 -1
  28. package/dist/types/src/fields/scratch_field_variable.d.ts +1 -0
  29. package/dist/types/src/fields/scratch_field_variable.d.ts.map +1 -1
  30. package/dist/types/src/flyout_checkbox_icon.d.ts +5 -5
  31. package/dist/types/src/flyout_checkbox_icon.d.ts.map +1 -1
  32. package/dist/types/src/glows.d.ts.map +1 -1
  33. package/dist/types/src/procedures.d.ts +4 -4
  34. package/dist/types/src/procedures.d.ts.map +1 -1
  35. package/dist/types/src/recyclable_block_flyout_inflater.d.ts +2 -2
  36. package/dist/types/src/recyclable_block_flyout_inflater.d.ts.map +1 -1
  37. package/dist/types/src/renderer/cat/cat_face.d.ts +1 -1
  38. package/dist/types/src/renderer/cat/cat_face.d.ts.map +1 -1
  39. package/dist/types/src/renderer/cat/drawer.d.ts.map +1 -1
  40. package/dist/types/src/renderer/constants.d.ts.map +1 -1
  41. package/dist/types/src/renderer/drawer.d.ts.map +1 -1
  42. package/dist/types/src/renderer/render_info.d.ts.map +1 -1
  43. package/dist/types/src/scratch_blocks_utils.d.ts +22 -0
  44. package/dist/types/src/scratch_blocks_utils.d.ts.map +1 -1
  45. package/dist/types/src/scratch_comment_bubble.d.ts +4 -4
  46. package/dist/types/src/scratch_comment_bubble.d.ts.map +1 -1
  47. package/dist/types/src/scratch_comment_icon.d.ts +1 -1
  48. package/dist/types/src/scratch_comment_icon.d.ts.map +1 -1
  49. package/dist/types/src/scratch_continuous_category.d.ts +3 -1
  50. package/dist/types/src/scratch_continuous_category.d.ts.map +1 -1
  51. package/dist/types/src/scratch_continuous_toolbox.d.ts +2 -1
  52. package/dist/types/src/scratch_continuous_toolbox.d.ts.map +1 -1
  53. package/dist/types/src/status_indicator_label.d.ts +3 -3
  54. package/dist/types/src/status_indicator_label.d.ts.map +1 -1
  55. package/dist/types/src/status_indicator_label_flyout_inflater.d.ts.map +1 -1
  56. package/dist/types/src/variables.d.ts +1 -1
  57. package/dist/types/src/variables.d.ts.map +1 -1
  58. package/dist/types/src/workspace_block_lookup.d.ts +4 -0
  59. package/dist/types/src/workspace_block_lookup.d.ts.map +1 -0
  60. package/eslint.config.mjs +21 -28
  61. package/package.json +1 -1
  62. package/src/block_reporting.ts +5 -5
  63. package/src/blocks/control.ts +5 -5
  64. package/src/blocks/event.ts +1 -1
  65. package/src/blocks/motion.ts +2 -2
  66. package/src/blocks/procedures.ts +162 -69
  67. package/src/blocks/sensing.ts +0 -1
  68. package/src/blocks/vertical_extensions.ts +11 -8
  69. package/src/checkable_continuous_flyout.ts +45 -12
  70. package/src/checkbox_bubble.ts +7 -7
  71. package/src/colours.ts +4 -2
  72. package/src/context_menu_items.ts +41 -16
  73. package/src/data_category.ts +11 -3
  74. package/src/events/events_block_comment_base.ts +5 -1
  75. package/src/events/events_block_comment_change.ts +5 -1
  76. package/src/events/events_block_comment_collapse.ts +6 -2
  77. package/src/events/events_block_comment_create.ts +5 -1
  78. package/src/events/events_block_comment_move.ts +6 -2
  79. package/src/events/events_block_comment_resize.ts +6 -2
  80. package/src/events/events_block_drag_end.ts +5 -1
  81. package/src/events/events_block_drag_outside.ts +5 -1
  82. package/src/events/events_scratch_variable_create.ts +5 -1
  83. package/src/fields/field_colour_slider.ts +3 -5
  84. package/src/fields/field_matrix.ts +33 -17
  85. package/src/fields/field_note.ts +56 -20
  86. package/src/fields/field_textinput_removable.ts +13 -4
  87. package/src/fields/field_variable_getter.ts +20 -6
  88. package/src/fields/field_vertical_separator.ts +5 -1
  89. package/src/fields/scratch_field_angle.ts +32 -21
  90. package/src/fields/scratch_field_dropdown.ts +6 -2
  91. package/src/fields/scratch_field_number.ts +22 -13
  92. package/src/fields/scratch_field_variable.ts +26 -12
  93. package/src/flyout_checkbox_icon.ts +9 -5
  94. package/src/glows.ts +5 -5
  95. package/src/index.ts +18 -6
  96. package/src/procedures.ts +92 -42
  97. package/src/recyclable_block_flyout_inflater.ts +5 -4
  98. package/src/renderer/cat/cat_face.ts +1 -1
  99. package/src/renderer/cat/drawer.ts +4 -1
  100. package/src/renderer/constants.ts +19 -14
  101. package/src/renderer/drawer.ts +2 -1
  102. package/src/renderer/render_info.ts +12 -9
  103. package/src/renderer/renderer.ts +1 -1
  104. package/src/scratch_blocks_utils.ts +0 -2
  105. package/src/scratch_c_block_wrap.ts +37 -21
  106. package/src/scratch_comment_bubble.ts +30 -19
  107. package/src/scratch_comment_icon.ts +9 -12
  108. package/src/scratch_continuous_category.ts +20 -11
  109. package/src/scratch_continuous_toolbox.ts +12 -3
  110. package/src/scratch_dragger.ts +2 -2
  111. package/src/scratch_variable_map.ts +1 -1
  112. package/src/status_indicator_label.ts +13 -9
  113. package/src/status_indicator_label_flyout_inflater.ts +2 -1
  114. package/src/variables.ts +21 -14
  115. package/src/workspace_block_lookup.ts +14 -0
  116. package/src/xml.ts +1 -1
  117. package/types/continuous-toolbox.d.ts +0 -1
@@ -210,7 +210,7 @@ class FieldMatrix extends Blockly.Field<string> {
210
210
  this.arrow_.setAttributeNS(
211
211
  'http://www.w3.org/1999/xlink',
212
212
  'xlink:href',
213
- this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_DATAURI,
213
+ this.getConstants()?.FIELD_DROPDOWN_SVG_ARROW_DATAURI ?? '',
214
214
  )
215
215
  this.arrow_.style.cursor = 'default'
216
216
  }
@@ -241,7 +241,7 @@ class FieldMatrix extends Blockly.Field<string> {
241
241
  } else if (this.borderRect_) {
242
242
  this.borderRect_.setAttribute(
243
243
  'fill',
244
- 'colourQuaternary' in style ? `${style.colourQuaternary}` : style.colourTertiary,
244
+ 'colourQuaternary' in style ? String(style.colourQuaternary) : style.colourTertiary,
245
245
  )
246
246
  }
247
247
 
@@ -299,17 +299,22 @@ class FieldMatrix extends Blockly.Field<string> {
299
299
 
300
300
  Blockly.DropDownDiv.showPositionedByBlock(this, sourceBlock, this.dropdownDispose_.bind(this))
301
301
 
302
- this.matrixTouchWrapper_ = Blockly.browserEvents.bind(this.matrixStage_, 'mousedown', this, this.onMouseDown)
303
- this.clearButtonWrapper_ = Blockly.browserEvents.bind(clearButton, 'click', this, this.clearMatrix_)
304
- this.fillButtonWrapper_ = Blockly.browserEvents.bind(fillButton, 'click', this, this.fillMatrix_)
302
+ this.matrixTouchWrapper_ = Blockly.browserEvents.bind(
303
+ this.matrixStage_,
304
+ 'mousedown',
305
+ this,
306
+ this.onMouseDown.bind(this),
307
+ )
308
+ this.clearButtonWrapper_ = Blockly.browserEvents.bind(clearButton, 'click', this, this.clearMatrix_.bind(this))
309
+ this.fillButtonWrapper_ = Blockly.browserEvents.bind(fillButton, 'click', this, this.fillMatrix_.bind(this))
305
310
 
306
311
  // Update the matrix for the current value
307
312
  this.updateMatrix_()
308
313
  }
309
314
 
310
315
  dropdownDispose_() {
311
- const sourceBlock = this.getSourceBlock()!
312
- if (sourceBlock.isShadow()) {
316
+ const sourceBlock = this.getSourceBlock()
317
+ if (sourceBlock?.isShadow()) {
313
318
  sourceBlock.setStyle(this.originalStyle)
314
319
  }
315
320
  this.updateMatrix_()
@@ -355,10 +360,12 @@ class FieldMatrix extends Blockly.Field<string> {
355
360
  * Redraw the matrix with the current value.
356
361
  */
357
362
  private updateMatrix_() {
358
- const matrix = this.getValue()!
359
- const sourceBlock = this.getSourceBlock()! as Blockly.BlockSvg
363
+ const matrix = this.getValue()
364
+ if (!matrix) return
365
+ const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg | null
366
+ if (!sourceBlock) return
360
367
  for (let i = 0; i < matrix.length; i++) {
361
- if (matrix[i] === LEDState.OFF) {
368
+ if ((matrix[i] as LEDState) === LEDState.OFF) {
362
369
  this.fillMatrixNode_(this.ledButtons_, i, sourceBlock.getColourTertiary())
363
370
  this.fillMatrixNode_(this.ledThumbNodes_, i, sourceBlock.getColourSecondary())
364
371
  } else {
@@ -393,13 +400,14 @@ class FieldMatrix extends Blockly.Field<string> {
393
400
  * @param fill The fill colour in '#rrggbb' format.
394
401
  */
395
402
  fillMatrixNode_(node: SVGElement[], index: number, fill: string) {
396
- if (!node?.[index] || !fill) return
403
+ if (!node[index] || !fill) return
397
404
  node[index].setAttribute('fill', fill)
398
405
  }
399
406
 
400
407
  setLEDNode_(led: number, state: LEDState) {
401
408
  if (led < 0 || led > 24) return
402
- const oldMatrix = this.getValue()!
409
+ const oldMatrix = this.getValue()
410
+ if (!oldMatrix) return
403
411
  const newMatrix = oldMatrix.substr(0, led) + state + oldMatrix.substr(led + 1)
404
412
  this.setValue(newMatrix)
405
413
  }
@@ -416,7 +424,9 @@ class FieldMatrix extends Blockly.Field<string> {
416
424
 
417
425
  toggleLEDNode_(led: number) {
418
426
  if (led < 0 || led > 24) return
419
- if (this.getValue()!.charAt(led) === LEDState.OFF) {
427
+ const value = this.getValue()
428
+ if (!value) return
429
+ if ((value.charAt(led) as LEDState) === LEDState.OFF) {
420
430
  this.setLEDNode_(led, LEDState.ON)
421
431
  } else {
422
432
  this.setLEDNode_(led, LEDState.OFF)
@@ -428,11 +438,17 @@ class FieldMatrix extends Blockly.Field<string> {
428
438
  * @param e Mouse event.
429
439
  */
430
440
  onMouseDown(e: PointerEvent) {
431
- this.matrixMoveWrapper_ = Blockly.browserEvents.bind(document.body, 'mousemove', this, this.onMouseMove)
432
- this.matrixReleaseWrapper_ = Blockly.browserEvents.bind(document.body, 'mouseup', this, this.onMouseUp)
441
+ this.matrixMoveWrapper_ = Blockly.browserEvents.bind(
442
+ document.body,
443
+ 'mousemove',
444
+ this,
445
+ this.onMouseMove.bind(this),
446
+ )
447
+ this.matrixReleaseWrapper_ = Blockly.browserEvents.bind(document.body, 'mouseup', this, this.onMouseUp.bind(this))
433
448
  const ledHit = this.checkForLED_(e)
434
449
  if (ledHit > -1) {
435
- if (this.getValue()!.charAt(ledHit) === LEDState.OFF) {
450
+ const value = this.getValue()
451
+ if (value && (value.charAt(ledHit) as LEDState) === LEDState.OFF) {
436
452
  this.paintStyle_ = PaintStyle.FILL
437
453
  } else {
438
454
  this.paintStyle_ = PaintStyle.CLEAR
@@ -470,7 +486,7 @@ class FieldMatrix extends Blockly.Field<string> {
470
486
  if (led < 0) return
471
487
  if (this.paintStyle_ === PaintStyle.CLEAR) {
472
488
  this.clearLEDNode_(led)
473
- } else if (this.paintStyle_ === PaintStyle.FILL) {
489
+ } else {
474
490
  this.fillLEDNode_(led)
475
491
  }
476
492
  }
@@ -284,6 +284,12 @@ export class FieldNote extends Blockly.FieldTextInput {
284
284
  */
285
285
  showEditor_(event: PointerEvent, quietInput = false) {
286
286
  super.showEditor_(event, quietInput, false)
287
+ const parentBlock = this.getSourceBlock()?.getParent() as Blockly.BlockSvg | undefined
288
+ if (!parentBlock) {
289
+ throw new Error('[field_note] Missing parent block for note field editor')
290
+ }
291
+ const parentColour = parentBlock.getColour()
292
+ const parentTertiary = parentBlock.getColourTertiary()
287
293
 
288
294
  // Build the SVG DOM.
289
295
  const div = Blockly.DropDownDiv.getContentDiv()
@@ -314,9 +320,21 @@ export class FieldNote extends Blockly.FieldTextInput {
314
320
  // Add three piano octaves, so we can animate moving up or down an octave.
315
321
  // Only the middle octave gets bound to events.
316
322
  this.keySVGs_ = []
317
- this.addPianoOctave_(-this.fieldEditorWidth_ + FieldNote.EDGE_PADDING, whiteKeyGroup, blackKeyGroup, null)
318
- this.addPianoOctave_(0, whiteKeyGroup, blackKeyGroup, this.keySVGs_)
319
- this.addPianoOctave_(this.fieldEditorWidth_ - FieldNote.EDGE_PADDING, whiteKeyGroup, blackKeyGroup, null)
323
+ this.addPianoOctave_(
324
+ -this.fieldEditorWidth_ + FieldNote.EDGE_PADDING,
325
+ whiteKeyGroup,
326
+ blackKeyGroup,
327
+ null,
328
+ parentBlock,
329
+ )
330
+ this.addPianoOctave_(0, whiteKeyGroup, blackKeyGroup, this.keySVGs_, parentBlock)
331
+ this.addPianoOctave_(
332
+ this.fieldEditorWidth_ - FieldNote.EDGE_PADDING,
333
+ whiteKeyGroup,
334
+ blackKeyGroup,
335
+ null,
336
+ parentBlock,
337
+ )
320
338
 
321
339
  // Note name indicator at the top of the field
322
340
  this.noteNameText_ = Blockly.utils.dom.createSvgElement(
@@ -341,7 +359,7 @@ export class FieldNote extends Blockly.FieldTextInput {
341
359
  Blockly.utils.dom.createSvgElement(
342
360
  'line',
343
361
  {
344
- stroke: (this.sourceBlock_!.getParent() as Blockly.BlockSvg).getColourTertiary(),
362
+ stroke: parentTertiary,
345
363
  x1: 0,
346
364
  y1: FieldNote.TOP_MENU_HEIGHT,
347
365
  x2: this.fieldEditorWidth_,
@@ -365,11 +383,12 @@ export class FieldNote extends Blockly.FieldTextInput {
365
383
  )
366
384
 
367
385
  // Octave buttons
368
- const octaveDownButton = this.addOctaveButton_(0, true, svg)
386
+ const octaveDownButton = this.addOctaveButton_(0, true, svg, parentBlock)
369
387
  const octaveUpButton = this.addOctaveButton_(
370
388
  this.fieldEditorWidth_ + FieldNote.INSET * 2 - FieldNote.OCTAVE_BUTTON_SIZE,
371
389
  false,
372
390
  svg,
391
+ parentBlock,
373
392
  )
374
393
 
375
394
  this.octaveDownMouseDownWrapper_ = Blockly.browserEvents.bind(octaveDownButton, 'mousedown', this, () => {
@@ -379,8 +398,9 @@ export class FieldNote extends Blockly.FieldTextInput {
379
398
  this.changeOctaveBy_(1)
380
399
  })
381
400
  const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg
382
- Blockly.DropDownDiv.setColour(sourceBlock.getParent()!.getColour(), sourceBlock.getParent()!.getColourTertiary())
383
- Blockly.DropDownDiv.showPositionedByBlock(this, sourceBlock)
401
+ const dropdownAnchor = this as unknown as Blockly.Field<string | null>
402
+ Blockly.DropDownDiv.setColour(parentColour, parentTertiary)
403
+ Blockly.DropDownDiv.showPositionedByBlock(dropdownAnchor, sourceBlock)
384
404
 
385
405
  this.updateSelection_()
386
406
  }
@@ -391,14 +411,17 @@ export class FieldNote extends Blockly.FieldTextInput {
391
411
  * @param whiteKeyGroup The group for all white piano keys.
392
412
  * @param blackKeyGroup The group for all black piano keys.
393
413
  * @param keySVGarray An array containing all the key SVGs.
414
+ * @param parentBlock The validated parent block providing styling.
394
415
  */
395
416
  private addPianoOctave_(
396
417
  x: number,
397
418
  whiteKeyGroup: SVGElement,
398
419
  blackKeyGroup: SVGElement,
399
420
  keySVGarray: SVGElement[] | null,
421
+ parentBlock: Blockly.BlockSvg,
400
422
  ) {
401
423
  let xIncrement, width, height, fill, stroke, group
424
+ const parentTertiary = parentBlock.getColourTertiary()
402
425
  x += FieldNote.EDGE_PADDING / 2
403
426
  const y = FieldNote.TOP_MENU_HEIGHT
404
427
  for (let i = 0; i < FieldNote.KEY_INFO.length; i++) {
@@ -417,7 +440,7 @@ export class FieldNote extends Blockly.FieldTextInput {
417
440
  width = FieldNote.WHITE_KEY_WIDTH
418
441
  height = FieldNote.WHITE_KEY_HEIGHT
419
442
  fill = FieldNote.WHITE_KEY_COLOR
420
- stroke = (this.sourceBlock_!.getParent() as Blockly.BlockSvg).getColourTertiary()
443
+ stroke = parentTertiary
421
444
  group = whiteKeyGroup
422
445
  }
423
446
  const attr = {
@@ -435,8 +458,18 @@ export class FieldNote extends Blockly.FieldTextInput {
435
458
  keySVG.setAttribute('data-name', `${FieldNote.KEY_INFO[i].name}`)
436
459
  keySVG.setAttribute('data-isBlack', `${FieldNote.KEY_INFO[i].isBlack}`)
437
460
 
438
- this.mouseDownWrappers_[i] = Blockly.browserEvents.bind(keySVG, 'mousedown', this, this.onMouseDownOnKey_)
439
- this.mouseEnterWrappers_[i] = Blockly.browserEvents.bind(keySVG, 'mouseenter', this, this.onMouseEnter_)
461
+ this.mouseDownWrappers_[i] = Blockly.browserEvents.bind(
462
+ keySVG,
463
+ 'mousedown',
464
+ this,
465
+ this.onMouseDownOnKey_.bind(this),
466
+ )
467
+ this.mouseEnterWrappers_[i] = Blockly.browserEvents.bind(
468
+ keySVG,
469
+ 'mouseenter',
470
+ this,
471
+ this.onMouseEnter_.bind(this),
472
+ )
440
473
  }
441
474
  }
442
475
  }
@@ -502,10 +535,12 @@ export class FieldNote extends Blockly.FieldTextInput {
502
535
  * @param x The x position of the button.
503
536
  * @param flipped If true, the icon should be flipped.
504
537
  * @param svg The svg element to add the buttons to.
538
+ * @param parentBlock The validated parent block providing styling.
505
539
  * @returns A group containing the button SVG elements.
506
540
  */
507
- private addOctaveButton_(x: number, flipped: boolean, svg: SVGElement): SVGElement {
541
+ private addOctaveButton_(x: number, flipped: boolean, svg: SVGElement, parentBlock: Blockly.BlockSvg): SVGElement {
508
542
  const group = Blockly.utils.dom.createSvgElement('g', {}, svg)
543
+ const parentTertiary = parentBlock.getColourTertiary()
509
544
  const imageSize = FieldNote.OCTAVE_BUTTON_SIZE
510
545
  const arrow = Blockly.utils.dom.createSvgElement(
511
546
  'image',
@@ -525,7 +560,7 @@ export class FieldNote extends Blockly.FieldTextInput {
525
560
  Blockly.utils.dom.createSvgElement(
526
561
  'line',
527
562
  {
528
- stroke: (this.sourceBlock_!.getParent() as Blockly.BlockSvg).getColourTertiary(),
563
+ stroke: parentTertiary,
529
564
  x1: x - FieldNote.INSET,
530
565
  y1: 0,
531
566
  x2: x - FieldNote.INSET,
@@ -588,7 +623,7 @@ export class FieldNote extends Blockly.FieldTextInput {
588
623
  */
589
624
  private onMouseDownOnKey_(e: PointerEvent) {
590
625
  this.mouseIsDown_ = true
591
- this.mouseUpWrapper_ = Blockly.browserEvents.bind(document.body, 'mouseup', this, this.onMouseUp_)
626
+ this.mouseUpWrapper_ = Blockly.browserEvents.bind(document.body, 'mouseup', this, this.onMouseUp_.bind(this))
592
627
  this.selectNoteWithMouseEvent_(e)
593
628
  }
594
629
 
@@ -628,18 +663,19 @@ export class FieldNote extends Blockly.FieldTextInput {
628
663
  * Play a note, by calling the externally overriden play note function.
629
664
  */
630
665
  private playNoteInternal_() {
631
- if (FieldNote.playNote_) {
632
- FieldNote.playNote_(Number(this.getValue()!), 'Music')
666
+ const noteNum = this.getValue()
667
+ if (FieldNote.playNote_ && noteNum !== null) {
668
+ FieldNote.playNote_(Number(noteNum), 'Music')
633
669
  }
634
670
  }
635
671
 
636
672
  /**
637
673
  * Function to play a musical note corresponding to the key selected.
638
674
  * Overridden externally.
639
- * @param noteNum the MIDI note number to play.
640
- * @param id An id to select a scratch extension to play the note.
675
+ * @param _noteNum the MIDI note number to play.
676
+ * @param _id An id to select a scratch extension to play the note.
641
677
  */
642
- static playNote_ = function (noteNum: number, id: string) {
678
+ static playNote_: ((noteNum: number, id: string) => void) | null = function (_noteNum: number, _id: string) {
643
679
  return
644
680
  }
645
681
 
@@ -739,7 +775,7 @@ export class FieldNote extends Blockly.FieldTextInput {
739
775
  this.noteNameText_.textContent = noteName + ' (' + Math.floor(noteNum) + ')'
740
776
  }
741
777
  // Update the low and high C note names
742
- const lowCNum = (this.displayedOctave_ ?? 0) * 12
778
+ const lowCNum = this.displayedOctave_ * 12
743
779
  if (this.lowCText_) this.lowCText_.textContent = 'C(' + lowCNum + ')'
744
780
  if (this.highCText_) this.highCText_.textContent = 'C(' + (lowCNum + 12) + ')'
745
781
  }
@@ -750,7 +786,7 @@ export class FieldNote extends Blockly.FieldTextInput {
750
786
  * @param text The user's text.
751
787
  * @returns A string representing a valid note number, or null if invalid.
752
788
  */
753
- doClassValidation_(text: string): string | null {
789
+ doClassValidation_(text: string | null): string | null {
754
790
  if (text === null) {
755
791
  return null
756
792
  }
@@ -35,19 +35,28 @@ export class FieldTextInputRemovable extends Blockly.FieldTextInput {
35
35
  showEditor_() {
36
36
  // Wait for our parent block to render so we can examine its metrics to
37
37
  // calculate rounded corners on the editor as needed.
38
- Blockly.renderManagement.finishQueuedRenders().then(() => {
38
+ void Blockly.renderManagement.finishQueuedRenders().then(() => {
39
39
  super.showEditor_()
40
40
 
41
- const div = Blockly.WidgetDiv.getDiv()!
41
+ const div = Blockly.WidgetDiv.getDiv()
42
+ if (!div) {
43
+ console.error('[field_textinput_removable] Missing WidgetDiv for removable text input')
44
+ return
45
+ }
46
+ if (!this.sourceBlock_) {
47
+ console.error('[field_textinput_removable] Missing source block for removable text input')
48
+ return
49
+ }
50
+
42
51
  div.className += ' removableTextInput'
43
52
  const removeButton = document.createElement('img')
44
53
  removeButton.className = 'blocklyTextRemoveIcon'
45
- removeButton.setAttribute('src', this.sourceBlock_!.workspace.options.pathToMedia + 'icons/remove.svg')
54
+ removeButton.setAttribute('src', this.sourceBlock_.workspace.options.pathToMedia + 'icons/remove.svg')
46
55
  this.removeButtonMouseWrapper_ = Blockly.browserEvents.bind(
47
56
  removeButton,
48
57
  'mousedown',
49
58
  this,
50
- this.removeCallback_,
59
+ this.removeCallback_.bind(this),
51
60
  )
52
61
  div.appendChild(removeButton)
53
62
  })
@@ -70,8 +70,13 @@ class FieldVariableGetter extends Blockly.FieldLabel {
70
70
  */
71
71
  doValueUpdate_(newVariableId: string) {
72
72
  super.doValueUpdate_(newVariableId)
73
- const workspace = this.getSourceBlock()!.workspace
74
- this.variable = Blockly.Variables.getVariable(workspace, newVariableId)
73
+ const sourceBlock = this.getSourceBlock()
74
+ if (!sourceBlock) {
75
+ console.error('[field_variable_getter] Missing source block in doValueUpdate_')
76
+ this.variable = null
77
+ return
78
+ }
79
+ this.variable = Blockly.Variables.getVariable(sourceBlock.workspace, newVariableId)
75
80
  }
76
81
 
77
82
  /**
@@ -92,13 +97,22 @@ class FieldVariableGetter extends Blockly.FieldLabel {
92
97
  }
93
98
 
94
99
  fromXml(element: Element) {
95
- this.setValue(element.getAttribute('id')!)
100
+ const id = element.getAttribute('id')
101
+ if (!id) {
102
+ console.error('[field_variable_getter] Missing variable id in XML')
103
+ return
104
+ }
105
+ this.setValue(id)
96
106
  }
97
107
 
98
108
  toXml(element: Element): Element {
99
- element.setAttribute('id', this.variable!.getId())
100
- element.setAttribute('variabletype', this.variable!.getType())
101
- element.textContent = this.variable!.getName()
109
+ if (!this.variable) {
110
+ console.error('[field_variable_getter] Missing variable in toXml')
111
+ return element
112
+ }
113
+ element.setAttribute('id', this.variable.getId())
114
+ element.setAttribute('variabletype', this.variable.getType())
115
+ element.textContent = this.variable.getName()
102
116
  return element
103
117
  }
104
118
  }
@@ -73,7 +73,11 @@ class FieldVerticalSeparator extends Blockly.Field {
73
73
  * @package
74
74
  */
75
75
  setLineHeight(newHeight: number) {
76
- this.lineElement!.setAttribute('y2', `${newHeight}`)
76
+ if (!this.lineElement) {
77
+ console.error('[field_vertical_separator] Missing lineElement in setLineHeight')
78
+ return
79
+ }
80
+ this.lineElement.setAttribute('y2', `${newHeight}`)
77
81
  }
78
82
 
79
83
  /**
@@ -47,17 +47,17 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
47
47
  /**
48
48
  * Opaque identifier used to unbind event listener in dispose().
49
49
  */
50
- private mouseDownWrapper_!: Blockly.browserEvents.Data
50
+ private mouseDownWrapper_?: Blockly.browserEvents.Data
51
51
 
52
52
  /**
53
53
  * Opaque identifier used to unbind event listener in dispose().
54
54
  */
55
- private mouseMoveWrapper!: Blockly.browserEvents.Data
55
+ private mouseMoveWrapper?: Blockly.browserEvents.Data
56
56
 
57
57
  /**
58
58
  * Opaque identifier used to unbind event listener in dispose().
59
59
  */
60
- private mouseUpWrapper!: Blockly.browserEvents.Data
60
+ private mouseUpWrapper?: Blockly.browserEvents.Data
61
61
 
62
62
  /**
63
63
  * Round angles to the nearest 15 degrees when using mouse.
@@ -164,6 +164,14 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
164
164
  Blockly.DropDownDiv.hideWithoutAnimation()
165
165
  Blockly.DropDownDiv.clearContent()
166
166
  const div = Blockly.DropDownDiv.getContentDiv()
167
+ const sourceBlock = this.getSourceBlock()
168
+ if (!(sourceBlock instanceof Blockly.BlockSvg)) {
169
+ throw new Error('[scratch_field_angle] Missing source BlockSvg for showEditor_')
170
+ }
171
+ const parentBlock = sourceBlock.getParent()
172
+ if (!parentBlock) {
173
+ throw new Error('[scratch_field_angle] Missing parent block for showEditor_')
174
+ }
167
175
  // Build the SVG DOM.
168
176
  const svg = Blockly.utils.dom.createSvgElement(
169
177
  'svg',
@@ -183,8 +191,8 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
183
191
  cx: this.HALF,
184
192
  cy: this.HALF,
185
193
  r: this.RADIUS,
186
- fill: (this.getSourceBlock()!.getParent() as Blockly.BlockSvg).getColourSecondary(),
187
- stroke: (this.getSourceBlock()!.getParent() as Blockly.BlockSvg).getColourTertiary(),
194
+ fill: parentBlock.getColourSecondary(),
195
+ stroke: parentBlock.getColourTertiary(),
188
196
  class: 'blocklyAngleCircle',
189
197
  },
190
198
  svg,
@@ -268,13 +276,10 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
268
276
  Blockly.getMainWorkspace().options.pathToMedia + this.ARROW_SVG_PATH,
269
277
  )
270
278
 
271
- Blockly.DropDownDiv.setColour(
272
- (this.getSourceBlock()!.getParent() as Blockly.BlockSvg).getColour(),
273
- (this.getSourceBlock()!.getParent() as Blockly.BlockSvg).getColourTertiary(),
274
- )
275
- Blockly.DropDownDiv.showPositionedByBlock(this, this.getSourceBlock() as Blockly.BlockSvg)
279
+ Blockly.DropDownDiv.setColour(parentBlock.getColour(), parentBlock.getColourTertiary())
280
+ Blockly.DropDownDiv.showPositionedByBlock(this as Blockly.Field<string | number | null>, sourceBlock)
276
281
 
277
- this.mouseDownWrapper_ = Blockly.browserEvents.bind(this.handle, 'mousedown', this, this.onMouseDown)
282
+ this.mouseDownWrapper_ = Blockly.browserEvents.bind(this.handle, 'mousedown', this, this.onMouseDown.bind(this))
278
283
 
279
284
  this.updateGraph()
280
285
  }
@@ -283,16 +288,20 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
283
288
  * Set the angle to match the mouse's position.
284
289
  */
285
290
  onMouseDown() {
286
- this.mouseMoveWrapper = Blockly.browserEvents.bind(document.body, 'mousemove', this, this.onMouseMove)
287
- this.mouseUpWrapper = Blockly.browserEvents.bind(document.body, 'mouseup', this, this.onMouseUp)
291
+ this.mouseMoveWrapper = Blockly.browserEvents.bind(document.body, 'mousemove', this, this.onMouseMove.bind(this))
292
+ this.mouseUpWrapper = Blockly.browserEvents.bind(document.body, 'mouseup', this, this.onMouseUp.bind(this))
288
293
  }
289
294
 
290
295
  /**
291
296
  * Set the angle to match the mouse's position.
292
297
  */
293
298
  onMouseUp() {
294
- Blockly.browserEvents.unbind(this.mouseMoveWrapper)
295
- Blockly.browserEvents.unbind(this.mouseUpWrapper)
299
+ if (this.mouseMoveWrapper) {
300
+ Blockly.browserEvents.unbind(this.mouseMoveWrapper)
301
+ }
302
+ if (this.mouseUpWrapper) {
303
+ Blockly.browserEvents.unbind(this.mouseUpWrapper)
304
+ }
296
305
  }
297
306
 
298
307
  /**
@@ -301,7 +310,9 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
301
310
  */
302
311
  onMouseMove(e: PointerEvent) {
303
312
  e.preventDefault()
304
- const bBox = this.gauge!.ownerSVGElement!.getBoundingClientRect()
313
+ const ownerSvg = this.gauge?.ownerSVGElement
314
+ if (!ownerSvg) return
315
+ const bBox = ownerSvg.getBoundingClientRect()
305
316
  const dx = e.clientX - bBox.left - this.HALF
306
317
  const dy = e.clientY - bBox.top - this.HALF
307
318
  let angle = Math.atan(-dy / dx)
@@ -383,12 +394,12 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
383
394
  } else {
384
395
  imageRotation = -angleDegrees
385
396
  }
386
- this.arrow!.setAttribute('transform', 'rotate(' + imageRotation + ')')
397
+ this.arrow?.setAttribute('transform', 'rotate(' + imageRotation + ')')
387
398
  }
388
399
  this.gauge.setAttribute('d', path.join(''))
389
- this.line!.setAttribute('x2', `${x2}`)
390
- this.line!.setAttribute('y2', `${y2}`)
391
- this.handle!.setAttribute('transform', 'translate(' + x2 + ',' + y2 + ')')
400
+ this.line?.setAttribute('x2', `${x2}`)
401
+ this.line?.setAttribute('y2', `${y2}`)
402
+ this.handle?.setAttribute('transform', 'translate(' + x2 + ',' + y2 + ')')
392
403
  }
393
404
 
394
405
  /**
@@ -396,7 +407,7 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
396
407
  * @param text The user's text.
397
408
  * @returns A string representing a valid angle, or null if invalid.
398
409
  */
399
- doClassValidation_(text: string): number | null {
410
+ doClassValidation_(text: string | null): number | null {
400
411
  if (text === null) {
401
412
  return null
402
413
  }
@@ -17,14 +17,18 @@ class ScratchFieldDropdown extends Blockly.FieldDropdown {
17
17
  } else if (this.borderRect_) {
18
18
  this.borderRect_.setAttribute(
19
19
  'fill',
20
- 'colourQuaternary' in style ? `${style.colourQuaternary}` : style.colourTertiary,
20
+ 'colourQuaternary' in style ? String(style.colourQuaternary) : style.colourTertiary,
21
21
  )
22
22
  }
23
23
  }
24
24
 
25
25
  dropdownDispose_() {
26
26
  super.dropdownDispose_()
27
- const sourceBlock = this.getSourceBlock()!
27
+ const sourceBlock = this.getSourceBlock()
28
+ if (!sourceBlock) {
29
+ console.error('[scratch_field_dropdown] Missing source block in dropdownDispose_')
30
+ return
31
+ }
28
32
  if (sourceBlock.isShadow()) {
29
33
  sourceBlock.setStyle(this.originalStyle)
30
34
  }
@@ -110,14 +110,14 @@ class ScratchFieldNumber extends Blockly.FieldTextInput {
110
110
  * appropriate.
111
111
  * @param e The triggering pointer event.
112
112
  */
113
- showEditor_(e: PointerEvent) {
113
+ showEditor_(e?: PointerEvent) {
114
114
  // Do not focus on mobile devices so we can show the num-pad
115
115
  const showNumPad = e?.pointerType === 'touch'
116
116
  super.showEditor_(e, showNumPad)
117
117
 
118
118
  // Show a numeric keypad in the drop-down on touch
119
119
  if (showNumPad) {
120
- this.htmlInput_!.select()
120
+ this.htmlInput_?.select()
121
121
  this.showNumPad_()
122
122
  }
123
123
  }
@@ -148,7 +148,10 @@ class ScratchFieldNumber extends Blockly.FieldTextInput {
148
148
 
149
149
  // Set colour and size of drop-down
150
150
  const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg
151
- Blockly.DropDownDiv.setColour(sourceBlock.getParent()!.getColour(), sourceBlock.getColourTertiary())
151
+ const parentBlock = sourceBlock.getParent()
152
+ if (parentBlock) {
153
+ Blockly.DropDownDiv.setColour(parentBlock.getColour(), sourceBlock.getColourTertiary())
154
+ }
152
155
  contentDiv.style.width = ScratchFieldNumber.DROPDOWN_WIDTH + 'px'
153
156
 
154
157
  this.position_()
@@ -176,7 +179,7 @@ class ScratchFieldNumber extends Blockly.FieldTextInput {
176
179
  Blockly.DropDownDiv.setBoundsElement(sourceBlock.workspace.getParentSvg().parentElement)
177
180
  Blockly.DropDownDiv.show(
178
181
  this,
179
- this.getSourceBlock()!.RTL,
182
+ sourceBlock.RTL,
180
183
  primaryX,
181
184
  primaryY,
182
185
  secondaryX,
@@ -193,8 +196,9 @@ class ScratchFieldNumber extends Blockly.FieldTextInput {
193
196
  */
194
197
  private addButtons_(contentDiv: Element) {
195
198
  const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg
196
- const buttonColour = sourceBlock.getParent()!.getColour()
197
- const buttonBorderColour = sourceBlock.getParent()!.getColourTertiary()
199
+ const parent = sourceBlock.getParent()
200
+ const buttonColour = parent?.getColour() ?? sourceBlock.getColour()
201
+ const buttonBorderColour = parent?.getColourTertiary() ?? sourceBlock.getColourTertiary()
198
202
 
199
203
  // Add numeric keypad buttons
200
204
  const buttons = ScratchFieldNumber.NUMPAD_BUTTONS
@@ -248,10 +252,12 @@ class ScratchFieldNumber extends Blockly.FieldTextInput {
248
252
  // String of the button (e.g., '7')
249
253
  const spliceValue = (e.target as HTMLElement).innerText
250
254
  // Old value of the text field
251
- const oldValue = this.htmlInput_!.value
255
+ const htmlInput = this.htmlInput_
256
+ if (!htmlInput) return
257
+ const oldValue = htmlInput.value
252
258
  // Determine the selected portion of the text field
253
- const selectionStart = this.htmlInput_!.selectionStart ?? 0
254
- const selectionEnd = this.htmlInput_!.selectionEnd ?? 0
259
+ const selectionStart = htmlInput.selectionStart ?? 0
260
+ const selectionEnd = htmlInput.selectionEnd ?? 0
255
261
 
256
262
  // Splice in the new value
257
263
  const newValue = oldValue.slice(0, selectionStart) + spliceValue + oldValue.slice(selectionEnd)
@@ -273,10 +279,12 @@ class ScratchFieldNumber extends Blockly.FieldTextInput {
273
279
  */
274
280
  numPadEraseButtonTouch(e: PointerEvent) {
275
281
  // Old value of the text field
276
- const oldValue = this.htmlInput_!.value
282
+ const htmlInput = this.htmlInput_
283
+ if (!htmlInput) return
284
+ const oldValue = htmlInput.value
277
285
  // Determine what is selected to erase (if anything)
278
- let selectionStart = this.htmlInput_!.selectionStart ?? 0
279
- const selectionEnd = this.htmlInput_!.selectionEnd ?? 0
286
+ let selectionStart = htmlInput.selectionStart ?? 0
287
+ const selectionEnd = htmlInput.selectionEnd ?? 0
280
288
 
281
289
  // If selection is zero-length, shift start to the left 1 character
282
290
  if (selectionStart == selectionEnd) {
@@ -303,7 +311,8 @@ class ScratchFieldNumber extends Blockly.FieldTextInput {
303
311
  private updateDisplay_(newValue: string, newSelection: number) {
304
312
  this.setEditorValue_(newValue)
305
313
  // Resize and scroll the text field appropriately
306
- const htmlInput = this.htmlInput_!
314
+ const htmlInput = this.htmlInput_
315
+ if (!htmlInput) return
307
316
  htmlInput.setSelectionRange(newSelection, newSelection)
308
317
  htmlInput.scrollLeft = htmlInput.scrollWidth
309
318
  }