scratch-blocks 2.1.4 → 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 (188) hide show
  1. package/AGENTS.md +76 -24
  2. package/README.md +40 -0
  3. package/dist/main.mjs +1 -1
  4. package/dist/types/src/block_reporting.d.ts.map +1 -1
  5. package/dist/types/src/blocks/procedures.d.ts +2 -2
  6. package/dist/types/src/blocks/procedures.d.ts.map +1 -1
  7. package/dist/types/src/checkable_continuous_flyout.d.ts +6 -3
  8. package/dist/types/src/checkable_continuous_flyout.d.ts.map +1 -1
  9. package/dist/types/src/checkbox_bubble.d.ts +7 -7
  10. package/dist/types/src/checkbox_bubble.d.ts.map +1 -1
  11. package/dist/types/src/colours.d.ts.map +1 -1
  12. package/dist/types/src/context_menu_items.d.ts.map +1 -1
  13. package/dist/types/src/events/events_block_comment_base.d.ts +1 -1
  14. package/dist/types/src/events/events_block_comment_base.d.ts.map +1 -1
  15. package/dist/types/src/events/events_block_drag_end.d.ts +1 -1
  16. package/dist/types/src/events/events_block_drag_end.d.ts.map +1 -1
  17. package/dist/types/src/events/events_block_drag_outside.d.ts +1 -1
  18. package/dist/types/src/events/events_block_drag_outside.d.ts.map +1 -1
  19. package/dist/types/src/fields/field_colour_slider.d.ts.map +1 -1
  20. package/dist/types/src/fields/field_matrix.d.ts.map +1 -1
  21. package/dist/types/src/fields/field_note.d.ts +6 -4
  22. package/dist/types/src/fields/field_note.d.ts.map +1 -1
  23. package/dist/types/src/fields/field_textinput_removable.d.ts.map +1 -1
  24. package/dist/types/src/fields/field_variable_getter.d.ts.map +1 -1
  25. package/dist/types/src/fields/field_vertical_separator.d.ts.map +1 -1
  26. package/dist/types/src/fields/scratch_field_angle.d.ts.map +1 -1
  27. package/dist/types/src/fields/scratch_field_dropdown.d.ts.map +1 -1
  28. package/dist/types/src/fields/scratch_field_number.d.ts.map +1 -1
  29. package/dist/types/src/fields/scratch_field_variable.d.ts +1 -0
  30. package/dist/types/src/fields/scratch_field_variable.d.ts.map +1 -1
  31. package/dist/types/src/flyout_checkbox_icon.d.ts +5 -5
  32. package/dist/types/src/flyout_checkbox_icon.d.ts.map +1 -1
  33. package/dist/types/src/glows.d.ts.map +1 -1
  34. package/dist/types/src/index.d.ts +1 -0
  35. package/dist/types/src/index.d.ts.map +1 -1
  36. package/dist/types/src/procedures.d.ts +4 -4
  37. package/dist/types/src/procedures.d.ts.map +1 -1
  38. package/dist/types/src/recyclable_block_flyout_inflater.d.ts +2 -2
  39. package/dist/types/src/recyclable_block_flyout_inflater.d.ts.map +1 -1
  40. package/dist/types/src/renderer/cat/cat_face.d.ts +1 -1
  41. package/dist/types/src/renderer/cat/cat_face.d.ts.map +1 -1
  42. package/dist/types/src/renderer/cat/drawer.d.ts.map +1 -1
  43. package/dist/types/src/renderer/constants.d.ts.map +1 -1
  44. package/dist/types/src/renderer/drawer.d.ts.map +1 -1
  45. package/dist/types/src/renderer/render_info.d.ts.map +1 -1
  46. package/dist/types/src/scratch_blocks_utils.d.ts +22 -0
  47. package/dist/types/src/scratch_blocks_utils.d.ts.map +1 -1
  48. package/dist/types/src/scratch_c_block_wrap.d.ts +2 -0
  49. package/dist/types/src/scratch_c_block_wrap.d.ts.map +1 -0
  50. package/dist/types/src/scratch_comment_bubble.d.ts +4 -4
  51. package/dist/types/src/scratch_comment_bubble.d.ts.map +1 -1
  52. package/dist/types/src/scratch_comment_icon.d.ts +1 -1
  53. package/dist/types/src/scratch_comment_icon.d.ts.map +1 -1
  54. package/dist/types/src/scratch_continuous_category.d.ts +3 -1
  55. package/dist/types/src/scratch_continuous_category.d.ts.map +1 -1
  56. package/dist/types/src/scratch_continuous_toolbox.d.ts +2 -1
  57. package/dist/types/src/scratch_continuous_toolbox.d.ts.map +1 -1
  58. package/dist/types/src/status_indicator_label.d.ts +3 -3
  59. package/dist/types/src/status_indicator_label.d.ts.map +1 -1
  60. package/dist/types/src/status_indicator_label_flyout_inflater.d.ts.map +1 -1
  61. package/dist/types/src/variables.d.ts +1 -1
  62. package/dist/types/src/variables.d.ts.map +1 -1
  63. package/dist/types/src/workspace_block_lookup.d.ts +4 -0
  64. package/dist/types/src/workspace_block_lookup.d.ts.map +1 -0
  65. package/eslint.config.mjs +23 -26
  66. package/package.json +10 -3
  67. package/src/block_reporting.ts +5 -5
  68. package/src/blocks/control.ts +5 -5
  69. package/src/blocks/event.ts +1 -1
  70. package/src/blocks/motion.ts +2 -2
  71. package/src/blocks/procedures.ts +162 -69
  72. package/src/blocks/sensing.ts +0 -1
  73. package/src/blocks/vertical_extensions.ts +11 -8
  74. package/src/checkable_continuous_flyout.ts +45 -12
  75. package/src/checkbox_bubble.ts +7 -7
  76. package/src/colours.ts +4 -2
  77. package/src/context_menu_items.ts +41 -16
  78. package/src/data_category.ts +11 -3
  79. package/src/events/events_block_comment_base.ts +5 -1
  80. package/src/events/events_block_comment_change.ts +5 -1
  81. package/src/events/events_block_comment_collapse.ts +6 -2
  82. package/src/events/events_block_comment_create.ts +5 -1
  83. package/src/events/events_block_comment_move.ts +6 -2
  84. package/src/events/events_block_comment_resize.ts +6 -2
  85. package/src/events/events_block_drag_end.ts +5 -1
  86. package/src/events/events_block_drag_outside.ts +5 -1
  87. package/src/events/events_scratch_variable_create.ts +5 -1
  88. package/src/fields/field_colour_slider.ts +3 -5
  89. package/src/fields/field_matrix.ts +33 -17
  90. package/src/fields/field_note.ts +56 -20
  91. package/src/fields/field_textinput_removable.ts +13 -4
  92. package/src/fields/field_variable_getter.ts +20 -6
  93. package/src/fields/field_vertical_separator.ts +5 -1
  94. package/src/fields/scratch_field_angle.ts +32 -21
  95. package/src/fields/scratch_field_dropdown.ts +6 -2
  96. package/src/fields/scratch_field_number.ts +22 -13
  97. package/src/fields/scratch_field_variable.ts +26 -12
  98. package/src/flyout_checkbox_icon.ts +9 -5
  99. package/src/glows.ts +5 -5
  100. package/src/index.ts +21 -11
  101. package/src/procedures.ts +92 -42
  102. package/src/recyclable_block_flyout_inflater.ts +5 -4
  103. package/src/renderer/cat/cat_face.ts +1 -1
  104. package/src/renderer/cat/drawer.ts +4 -1
  105. package/src/renderer/constants.ts +19 -14
  106. package/src/renderer/drawer.ts +2 -1
  107. package/src/renderer/render_info.ts +12 -9
  108. package/src/renderer/renderer.ts +1 -1
  109. package/src/scratch_blocks_utils.ts +0 -2
  110. package/src/scratch_c_block_wrap.ts +108 -0
  111. package/src/scratch_comment_bubble.ts +30 -19
  112. package/src/scratch_comment_icon.ts +9 -12
  113. package/src/scratch_connection_checker.ts +1 -2
  114. package/src/scratch_continuous_category.ts +20 -11
  115. package/src/scratch_continuous_toolbox.ts +12 -3
  116. package/src/scratch_dragger.ts +2 -2
  117. package/src/scratch_variable_map.ts +1 -1
  118. package/src/status_indicator_label.ts +13 -9
  119. package/src/status_indicator_label_flyout_inflater.ts +2 -1
  120. package/src/variables.ts +21 -14
  121. package/src/workspace_block_lookup.ts +14 -0
  122. package/src/xml.ts +1 -1
  123. package/tsconfig.build.json +4 -0
  124. package/tsconfig.json +1 -1
  125. package/vitest.config.ts +35 -0
  126. package/dist/types/tests/blocks/logic_ternary_test.d.ts +0 -13
  127. package/dist/types/tests/blocks/logic_ternary_test.d.ts.map +0 -1
  128. package/dist/types/tests/jsunit/block_test.d.ts +0 -4
  129. package/dist/types/tests/jsunit/block_test.d.ts.map +0 -1
  130. package/dist/types/tests/jsunit/connection_db_test.d.ts +0 -25
  131. package/dist/types/tests/jsunit/connection_db_test.d.ts.map +0 -1
  132. package/dist/types/tests/jsunit/connection_test.d.ts +0 -39
  133. package/dist/types/tests/jsunit/connection_test.d.ts.map +0 -1
  134. package/dist/types/tests/jsunit/db_test.d.ts +0 -7
  135. package/dist/types/tests/jsunit/db_test.d.ts.map +0 -1
  136. package/dist/types/tests/jsunit/event_test.d.ts +0 -76
  137. package/dist/types/tests/jsunit/event_test.d.ts.map +0 -1
  138. package/dist/types/tests/jsunit/extensions_test.d.ts +0 -18
  139. package/dist/types/tests/jsunit/extensions_test.d.ts.map +0 -1
  140. package/dist/types/tests/jsunit/field_angle_test.d.ts +0 -3
  141. package/dist/types/tests/jsunit/field_angle_test.d.ts.map +0 -1
  142. package/dist/types/tests/jsunit/field_number_test.d.ts +0 -3
  143. package/dist/types/tests/jsunit/field_number_test.d.ts.map +0 -1
  144. package/dist/types/tests/jsunit/field_test.d.ts +0 -8
  145. package/dist/types/tests/jsunit/field_test.d.ts.map +0 -1
  146. package/dist/types/tests/jsunit/field_variable_getter_test.d.ts +0 -5
  147. package/dist/types/tests/jsunit/field_variable_getter_test.d.ts.map +0 -1
  148. package/dist/types/tests/jsunit/field_variable_test.d.ts +0 -19
  149. package/dist/types/tests/jsunit/field_variable_test.d.ts.map +0 -1
  150. package/dist/types/tests/jsunit/generator_test.d.ts +0 -2
  151. package/dist/types/tests/jsunit/generator_test.d.ts.map +0 -1
  152. package/dist/types/tests/jsunit/gesture_test.d.ts +0 -10
  153. package/dist/types/tests/jsunit/gesture_test.d.ts.map +0 -1
  154. package/dist/types/tests/jsunit/input_test.d.ts +0 -9
  155. package/dist/types/tests/jsunit/input_test.d.ts.map +0 -1
  156. package/dist/types/tests/jsunit/json_test.d.ts +0 -11
  157. package/dist/types/tests/jsunit/json_test.d.ts.map +0 -1
  158. package/dist/types/tests/jsunit/names_test.d.ts +0 -5
  159. package/dist/types/tests/jsunit/names_test.d.ts.map +0 -1
  160. package/dist/types/tests/jsunit/procedure_test.d.ts +0 -15
  161. package/dist/types/tests/jsunit/procedure_test.d.ts.map +0 -1
  162. package/dist/types/tests/jsunit/scratch_block_comment_test.d.ts +0 -14
  163. package/dist/types/tests/jsunit/scratch_block_comment_test.d.ts.map +0 -1
  164. package/dist/types/tests/jsunit/svg_test.d.ts +0 -14
  165. package/dist/types/tests/jsunit/svg_test.d.ts.map +0 -1
  166. package/dist/types/tests/jsunit/test_runner.d.ts +0 -2
  167. package/dist/types/tests/jsunit/test_runner.d.ts.map +0 -1
  168. package/dist/types/tests/jsunit/test_utilities.d.ts +0 -50
  169. package/dist/types/tests/jsunit/test_utilities.d.ts.map +0 -1
  170. package/dist/types/tests/jsunit/utils_test.d.ts +0 -10
  171. package/dist/types/tests/jsunit/utils_test.d.ts.map +0 -1
  172. package/dist/types/tests/jsunit/variable_map_test.d.ts +0 -28
  173. package/dist/types/tests/jsunit/variable_map_test.d.ts.map +0 -1
  174. package/dist/types/tests/jsunit/variable_model_test.d.ts +0 -14
  175. package/dist/types/tests/jsunit/variable_model_test.d.ts.map +0 -1
  176. package/dist/types/tests/jsunit/widget_div_test.d.ts +0 -37
  177. package/dist/types/tests/jsunit/widget_div_test.d.ts.map +0 -1
  178. package/dist/types/tests/jsunit/workspace_comment_test.d.ts +0 -13
  179. package/dist/types/tests/jsunit/workspace_comment_test.d.ts.map +0 -1
  180. package/dist/types/tests/jsunit/workspace_test.d.ts +0 -22
  181. package/dist/types/tests/jsunit/workspace_test.d.ts.map +0 -1
  182. package/dist/types/tests/jsunit/workspace_undo_redo_test.d.ts +0 -33
  183. package/dist/types/tests/jsunit/workspace_undo_redo_test.d.ts.map +0 -1
  184. package/dist/types/tests/jsunit/xml_test.d.ts +0 -55
  185. package/dist/types/tests/jsunit/xml_test.d.ts.map +0 -1
  186. package/dist/types/tests/workspace_svg/workspace_svg_test.d.ts +0 -12
  187. package/dist/types/tests/workspace_svg/workspace_svg_test.d.ts.map +0 -1
  188. package/types/continuous-toolbox.d.ts +0 -1
@@ -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
  }
@@ -29,6 +29,13 @@ import { createVariable, renameVariable } from '../variables'
29
29
  export class ScratchFieldVariable extends Blockly.FieldVariable {
30
30
  private originalStyle!: string
31
31
 
32
+ private getSourceWorkspaceSvg_(sourceBlock: Blockly.Block): Blockly.WorkspaceSvg {
33
+ if (!(sourceBlock.workspace instanceof Blockly.WorkspaceSvg)) {
34
+ throw new Error('[scratch_field_variable] Expected source block workspace to be a WorkspaceSvg')
35
+ }
36
+ return sourceBlock.workspace
37
+ }
38
+
32
39
  constructor(
33
40
  varName: string | null | typeof Blockly.Field.SKIP_SETUP,
34
41
  validator?: Blockly.FieldVariableValidator,
@@ -40,14 +47,15 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
40
47
  // dropdownCreate returns MenuOption[] rather than Blockly.MenuGenerator's
41
48
  // MenuOption[][] variant; the cast is needed to satisfy FieldVariable's
42
49
  // menuGenerator_ type while the actual runtime shape is compatible.
43
- this.menuGenerator_ = ScratchFieldVariable.dropdownCreate as unknown as Blockly.MenuGenerator
50
+ this.menuGenerator_ = ScratchFieldVariable.dropdownCreate.bind(this) as unknown as Blockly.MenuGenerator
44
51
  }
45
52
 
46
53
  initModel() {
47
54
  if (!this.getVariable()) {
48
55
  const sourceBlock = this.getSourceBlock()
49
56
  if (sourceBlock) {
50
- const broadcastVariable = this.initFlyoutBroadcast(sourceBlock.workspace as Blockly.WorkspaceSvg)
57
+ const sourceWorkspace = this.getSourceWorkspaceSvg_(sourceBlock)
58
+ const broadcastVariable = this.initFlyoutBroadcast(sourceWorkspace)
51
59
  if (broadcastVariable) {
52
60
  this.doValueUpdate_(broadcastVariable.getId())
53
61
  return
@@ -96,7 +104,8 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
96
104
  if (option[1] === Blockly.RENAME_VARIABLE_ID) {
97
105
  return [ScratchMsgs.translate('RENAME_LIST'), option[1]]
98
106
  } else if (option[1] === Blockly.DELETE_VARIABLE_ID) {
99
- return [ScratchMsgs.translate('DELETE_LIST').replace('%1', this.getText()), option[1]]
107
+ const fieldText = (this as Blockly.FieldVariable).getText()
108
+ return [String(ScratchMsgs.translate('DELETE_LIST')).replace('%1', fieldText), option[1]]
100
109
  }
101
110
  return option
102
111
  })
@@ -118,8 +127,9 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
118
127
  if (sourceBlock && !sourceBlock.isDeadOrDying()) {
119
128
  const selectedItem = menuItem.getValue()
120
129
  if (selectedItem === Constants.NEW_BROADCAST_MESSAGE_ID) {
130
+ const sourceWorkspace = this.getSourceWorkspaceSvg_(sourceBlock)
121
131
  createVariable(
122
- sourceBlock.workspace as Blockly.WorkspaceSvg,
132
+ sourceWorkspace,
123
133
  (varId) => {
124
134
  if (varId) {
125
135
  this.setValue(varId)
@@ -129,7 +139,7 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
129
139
  )
130
140
  return
131
141
  } else if (selectedItem === Blockly.RENAME_VARIABLE_ID) {
132
- renameVariable(sourceBlock.workspace as Blockly.WorkspaceSvg, this.getVariable() as ScratchVariableModel)
142
+ renameVariable(sourceBlock.workspace, this.getVariable() as ScratchVariableModel)
133
143
  return
134
144
  }
135
145
  }
@@ -138,26 +148,30 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
138
148
 
139
149
  showEditor_(event: PointerEvent) {
140
150
  super.showEditor_(event)
141
- const sourceBlock = this.getSourceBlock()!
151
+ const sourceBlock = this.getSourceBlock()
152
+ if (!sourceBlock) {
153
+ throw new Error('[scratch_field_variable] Missing source block in showEditor_')
154
+ }
155
+ const sourceWorkspace = this.getSourceWorkspaceSvg_(sourceBlock)
142
156
  const styleName = sourceBlock.getStyleName()
143
- const style = (sourceBlock.workspace as Blockly.WorkspaceSvg)
144
- .getRenderer()
145
- .getConstants()
146
- .getBlockStyle(styleName)
157
+ const style = sourceWorkspace.getRenderer().getConstants().getBlockStyle(styleName)
147
158
  if (sourceBlock.isShadow()) {
148
159
  this.originalStyle = styleName
149
160
  sourceBlock.setStyle(`${this.originalStyle}_selected`)
150
161
  } else if (this.borderRect_) {
151
162
  this.borderRect_.setAttribute(
152
163
  'fill',
153
- 'colourQuaternary' in style ? `${style.colourQuaternary}` : style.colourTertiary,
164
+ 'colourQuaternary' in style ? String(style.colourQuaternary) : style.colourTertiary,
154
165
  )
155
166
  }
156
167
  }
157
168
 
158
169
  dropdownDispose_() {
159
170
  super.dropdownDispose_()
160
- const sourceBlock = this.getSourceBlock()!
171
+ const sourceBlock = this.getSourceBlock()
172
+ if (!sourceBlock) {
173
+ throw new Error('[scratch_field_variable] Missing source block in dropdownDispose_')
174
+ }
161
175
  if (sourceBlock.isShadow()) {
162
176
  sourceBlock.setStyle(this.originalStyle)
163
177
  }
@@ -9,7 +9,7 @@ import { CheckboxBubble } from './checkbox_bubble'
9
9
  * Invisible icon that exists solely to host the corresponding checkbox bubble.
10
10
  */
11
11
  export class FlyoutCheckboxIcon extends Blockly.icons.Icon implements Blockly.IHasBubble {
12
- private checkboxBubble!: CheckboxBubble
12
+ private checkboxBubble?: CheckboxBubble
13
13
  private type = new Blockly.icons.IconType('checkbox')
14
14
 
15
15
  constructor(protected override sourceBlock: Blockly.BlockSvg) {
@@ -36,7 +36,7 @@ export class FlyoutCheckboxIcon extends Blockly.icons.Icon implements Blockly.IH
36
36
  return this.sourceBlock.workspace.isFlyout
37
37
  }
38
38
 
39
- onLocationChange(blockOrigin: Blockly.utils.Coordinate) {
39
+ onLocationChange(_blockOrigin: Blockly.utils.Coordinate) {
40
40
  this.checkboxBubble?.updateLocation()
41
41
  }
42
42
 
@@ -52,16 +52,20 @@ export class FlyoutCheckboxIcon extends Blockly.icons.Icon implements Blockly.IH
52
52
  // These methods are required by the interfaces, but intentionally have no
53
53
  // implementation, largely because this icon has no visual representation.
54
54
 
55
- async setBubbleVisible(visible: boolean) {}
55
+ setBubbleVisible(_visible: boolean): Promise<void> {
56
+ return Promise.resolve()
57
+ }
56
58
 
57
- initView(pointerDownListener: (e: PointerEvent) => void) {}
59
+ initView(_pointerDownListener: (e: PointerEvent) => void) {
60
+ return
61
+ }
58
62
 
59
63
  canBeFocused() {
60
64
  return false
61
65
  }
62
66
 
63
67
  getBubble() {
64
- return this.checkboxBubble
68
+ return this.checkboxBubble ?? null
65
69
  }
66
70
  }
67
71
 
package/src/glows.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import * as Blockly from 'blockly/core'
6
6
  import { Colours } from './colours'
7
+ import { getBlockSvgById, getRequiredMainWorkspaceSvg } from './workspace_block_lookup'
7
8
 
8
9
  /**
9
10
  * Glow/unglow a stack in the workspace.
@@ -11,11 +12,10 @@ import { Colours } from './colours'
11
12
  * @param isGlowingStack Whether to glow the stack.
12
13
  */
13
14
  export function glowStack(id: string, isGlowingStack: boolean) {
14
- const block = (Blockly.getMainWorkspace().getBlockById(id) ||
15
- (Blockly.getMainWorkspace() as Blockly.WorkspaceSvg)
16
- .getFlyout()
17
- ?.getWorkspace()
18
- ?.getBlockById(id)) as Blockly.BlockSvg
15
+ const mainWorkspace = getRequiredMainWorkspaceSvg()
16
+ const flyout = mainWorkspace.getFlyout()
17
+ const flyoutBlock = flyout ? getBlockSvgById(flyout.getWorkspace(), id) : null
18
+ const block = getBlockSvgById(mainWorkspace, id) ?? flyoutBlock
19
19
  if (!block) {
20
20
  throw new Error('Tried to glow block that does not exist.')
21
21
  }
package/src/index.ts CHANGED
@@ -47,6 +47,7 @@ import './renderer/cat/renderer'
47
47
  import './renderer/renderer'
48
48
  import { registerScratchBlockPaster } from './scratch_block_paster'
49
49
  import * as scratchBlocksUtils from './scratch_blocks_utils'
50
+ import './scratch_c_block_wrap'
50
51
  import './scratch_comment_icon'
51
52
  import './scratch_connection_checker'
52
53
  import { registerScratchContinuousCategory } from './scratch_continuous_category'
@@ -158,7 +159,7 @@ export function isContentNodeFocused(): boolean {
158
159
  return Blockly.getFocusManager().getFocusedNode() !== null
159
160
  }
160
161
 
161
- registerContinuousToolbox()
162
+ ;(registerContinuousToolbox as () => void)()
162
163
  Blockly.Scrollbar.scrollbarThickness = Blockly.Touch.TOUCH_ENABLED ? 14 : 11
163
164
  Blockly.FlyoutButton.TEXT_MARGIN_X = 40
164
165
  Blockly.FlyoutButton.TEXT_MARGIN_Y = 10
@@ -168,12 +169,23 @@ Blockly.ContextMenuItems.registerCommentOptions()
168
169
  // Blockly hides "Add Comment" for simple reporters because comments can't be
169
170
  // read in the default renderer. In Scratch they're shown differently, so
170
171
  // remove that restriction by dropping the isFullBlockField check.
171
- Blockly.ContextMenuRegistry.registry.getItem('blockComment')!.preconditionFn = (scope) => {
172
- const block = scope.block
173
- if (block && !block.isInFlyout && block.workspace.options.comments && !block.isCollapsed() && block.isEditable()) {
174
- return 'enabled'
172
+ const blockCommentMenuItem = Blockly.ContextMenuRegistry.registry.getItem('blockComment')
173
+ if (!blockCommentMenuItem) {
174
+ console.error('[index] Missing context menu item: blockComment')
175
+ } else {
176
+ blockCommentMenuItem.preconditionFn = (scope) => {
177
+ const block = scope.block
178
+ if (
179
+ block &&
180
+ !block.isInFlyout &&
181
+ block.workspace.options.comments &&
182
+ !block.isCollapsed() &&
183
+ block.isEditable()
184
+ ) {
185
+ return 'enabled'
186
+ }
187
+ return 'hidden'
175
188
  }
176
- return 'hidden'
177
189
  }
178
190
  Blockly.ContextMenuRegistry.registry.unregister('blockDelete')
179
191
  contextMenuItems.registerDeleteBlock()
@@ -190,11 +202,9 @@ Blockly.comments.CommentView.defaultCommentSize = new Blockly.utils.Size(200, 20
190
202
  // to the workspace itself (whose onNodeFocus is a no-op) rather than to a
191
203
  // specific block, so deleting a block doesn't reset the scroll position.
192
204
  // We may need to re-evaluate this when we explicitly work on keyboard navigation.
193
- const originalGetRestoredFocusableNode =
194
- Blockly.WorkspaceSvg.prototype.getRestoredFocusableNode
195
- Blockly.WorkspaceSvg.prototype.getRestoredFocusableNode = function (
196
- previousNode,
197
- ) {
205
+ // eslint-disable-next-line @typescript-eslint/unbound-method -- preserve original prototype method for patched wrapper
206
+ const originalGetRestoredFocusableNode = Blockly.WorkspaceSvg.prototype.getRestoredFocusableNode
207
+ Blockly.WorkspaceSvg.prototype.getRestoredFocusableNode = function (previousNode) {
198
208
  if (!previousNode && !this.isFlyout) return null
199
209
  return originalGetRestoredFocusableNode.call(this, previousNode)
200
210
  }