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
@@ -27,16 +27,16 @@ export class ScratchCommentBubble
27
27
  this.getSvgRoot().setAttribute('style', `--colour-commentBorder: ${sourceBlock.getColourTertiary()};`)
28
28
  this.getSvgRoot().setAttribute('id', this.id)
29
29
 
30
- Blockly.browserEvents.conditionalBind(this.getSvgRoot(), 'pointerdown', this, this.startGesture)
30
+ Blockly.browserEvents.conditionalBind(this.getSvgRoot(), 'pointerdown', this, this.startGesture.bind(this))
31
31
  // Don't zoom with mousewheel; let it scroll instead.
32
32
  Blockly.browserEvents.conditionalBind(this.getSvgRoot(), 'wheel', this, (e: WheelEvent) => {
33
33
  e.stopPropagation()
34
34
  })
35
35
  }
36
36
 
37
- setDeleteStyle(enable: boolean) {}
37
+ setDeleteStyle(_enable: boolean) {}
38
38
  showContextMenu() {}
39
- setDragging(start: boolean) {}
39
+ setDragging(_start: boolean) {}
40
40
  select() {}
41
41
  unselect() {}
42
42
 
@@ -51,10 +51,15 @@ export class ScratchCommentBubble
51
51
  moveTo(xOrCoordinate: number, y: number): void
52
52
  moveTo(xOrCoordinate: Blockly.utils.Coordinate): void
53
53
  moveTo(xOrCoordinate: Blockly.utils.Coordinate | number, y?: number) {
54
- const destination =
55
- xOrCoordinate instanceof Blockly.utils.Coordinate
56
- ? xOrCoordinate
57
- : new Blockly.utils.Coordinate(xOrCoordinate, y!)
54
+ let destination: Blockly.utils.Coordinate
55
+ if (xOrCoordinate instanceof Blockly.utils.Coordinate) {
56
+ destination = xOrCoordinate
57
+ } else {
58
+ if (y === undefined) {
59
+ throw new Error('ScratchCommentBubble.moveTo: missing y coordinate')
60
+ }
61
+ destination = new Blockly.utils.Coordinate(xOrCoordinate, y)
62
+ }
58
63
  super.moveTo(destination)
59
64
  this.redrawAnchorChain()
60
65
  }
@@ -69,14 +74,14 @@ export class ScratchCommentBubble
69
74
  }
70
75
  }
71
76
 
72
- startDrag(event: PointerEvent) {
77
+ startDrag(_event: PointerEvent) {
73
78
  this.dragStartLocation = this.getRelativeToSurfaceXY()
74
79
  this.workspace.setResizesEnabled(false)
75
80
  this.workspace.getLayerManager()?.moveToDragLayer(this)
76
81
  Blockly.utils.dom.addClass(this.getSvgRoot(), 'blocklyDragging')
77
82
  }
78
83
 
79
- drag(newLocation: Blockly.utils.Coordinate, event?: PointerEvent) {
84
+ drag(newLocation: Blockly.utils.Coordinate, _event?: PointerEvent) {
80
85
  this.moveTo(newLocation)
81
86
  }
82
87
 
@@ -90,35 +95,41 @@ export class ScratchCommentBubble
90
95
  }
91
96
 
92
97
  revertDrag() {
93
- this.moveTo(this.dragStartLocation!)
98
+ if (!this.dragStartLocation) {
99
+ throw new Error('ScratchCommentBubble.revertDrag: missing drag start location')
100
+ }
101
+ this.moveTo(this.dragStartLocation)
94
102
  }
95
103
 
96
104
  setAnchorLocation(newAnchor: Blockly.utils.Coordinate) {
97
105
  const oldAnchor = this.anchor
98
- const alreadyAnchored = !!this.anchor
106
+ const alreadyAnchored = oldAnchor !== undefined
99
107
  this.anchor = newAnchor
100
108
  if (!alreadyAnchored) {
101
109
  this.dropAnchor()
102
110
  } else {
103
111
  const oldLocation = this.getRelativeToSurfaceXY()
104
- const delta = Blockly.utils.Coordinate.difference(this.anchor, oldAnchor!)
112
+ const delta = Blockly.utils.Coordinate.difference(this.anchor, oldAnchor)
105
113
  const newLocation = Blockly.utils.Coordinate.sum(oldLocation, delta)
106
114
  this.moveTo(newLocation)
107
115
  }
108
116
  }
109
117
 
110
118
  dropAnchor() {
119
+ if (!this.anchor || !this.sourceBlock) {
120
+ throw new Error('ScratchCommentBubble.dropAnchor: missing anchor or source block')
121
+ }
111
122
  const verticalOffset = 16
112
- this.moveTo(this.anchor!.x + 40 * (this.workspace.RTL ? -1 : 1), this.anchor!.y - verticalOffset)
123
+ this.moveTo(this.anchor.x + 40 * (this.workspace.RTL ? -1 : 1), this.anchor.y - verticalOffset)
113
124
  const location = this.getRelativeToSurfaceXY()
114
125
  this.anchorChain = Blockly.utils.dom.createSvgElement(
115
126
  Blockly.utils.Svg.LINE,
116
127
  {
117
- x1: this.anchor!.x - location.x,
118
- y1: this.anchor!.y - location.y,
128
+ x1: this.anchor.x - location.x,
129
+ y1: this.anchor.y - location.y,
119
130
  x2: (this.getSize().width / 2) * (this.workspace.RTL ? -1 : 1),
120
131
  y2: verticalOffset,
121
- style: `stroke: ${this.sourceBlock!.getColourTertiary()}; stroke-width: 1`,
132
+ style: `stroke: ${this.sourceBlock.getColourTertiary()}; stroke-width: 1`,
122
133
  },
123
134
  this.getSvgRoot(),
124
135
  )
@@ -126,11 +137,11 @@ export class ScratchCommentBubble
126
137
  }
127
138
 
128
139
  redrawAnchorChain() {
129
- if (!this.anchorChain) return
140
+ if (!this.anchorChain || !this.anchor) return
130
141
 
131
142
  const location = this.getRelativeToSurfaceXY()
132
- this.anchorChain.setAttribute('x1', `${this.anchor!.x - location.x}`)
133
- this.anchorChain.setAttribute('y1', `${this.anchor!.y - location.y}`)
143
+ this.anchorChain.setAttribute('x1', `${this.anchor.x - location.x}`)
144
+ this.anchorChain.setAttribute('y1', `${this.anchor.y - location.y}`)
134
145
  }
135
146
 
136
147
  getId() {
@@ -39,7 +39,7 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
39
39
  return Blockly.icons.IconType.COMMENT
40
40
  }
41
41
 
42
- initView(pointerDownListener: (e: PointerEvent) => void) {
42
+ initView(_pointerDownListener: (e: PointerEvent) => void) {
43
43
  // Scratch comments have no indicator icon on the block.
44
44
  return
45
45
  }
@@ -57,8 +57,6 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
57
57
  }
58
58
 
59
59
  onLocationChange(blockOrigin: Blockly.utils.Coordinate) {
60
- if (!this.sourceBlock || !this.commentBubble) return
61
-
62
60
  if (this.sourceBlock.isInsertionMarker()) {
63
61
  this.commentBubble.dispose()
64
62
  return
@@ -74,11 +72,11 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
74
72
  }
75
73
 
76
74
  setText(text: string) {
77
- this.commentBubble?.setText(text)
75
+ this.commentBubble.setText(text)
78
76
  }
79
77
 
80
78
  getText(): string {
81
- return this.commentBubble?.getText() ?? ''
79
+ return this.commentBubble.getText()
82
80
  }
83
81
 
84
82
  onTextChanged(oldText: string, newText: string) {
@@ -97,26 +95,24 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
97
95
  }
98
96
 
99
97
  setBubbleSize(size: Blockly.utils.Size) {
100
- this.commentBubble?.setSize(size)
98
+ this.commentBubble.setSize(size)
101
99
  }
102
100
 
103
101
  getBubbleSize(): Blockly.utils.Size {
104
- return this.commentBubble?.getSize() ?? new Blockly.utils.Size(0, 0)
102
+ return this.commentBubble.getSize()
105
103
  }
106
104
 
107
105
  setBubbleLocation(newLocation: Blockly.utils.Coordinate) {
108
106
  const oldLocation = this.getBubbleLocation()
109
- this.commentBubble?.moveTo(newLocation)
107
+ this.commentBubble.moveTo(newLocation)
110
108
  Blockly.Events.fire(new (Blockly.Events.get('block_comment_move'))(this.commentBubble, oldLocation, newLocation))
111
109
  }
112
110
 
113
111
  getBubbleLocation(): Blockly.utils.Coordinate {
114
- return this.commentBubble?.getRelativeToSurfaceXY()
112
+ return this.commentBubble.getRelativeToSurfaceXY()
115
113
  }
116
114
 
117
115
  saveState(): CommentState | null {
118
- if (!this.commentBubble) return null
119
-
120
116
  const size = this.getBubbleSize()
121
117
  const bubbleLocation = this.commentBubble.getRelativeToSurfaceXY()
122
118
  const delta = Blockly.utils.Coordinate.difference(bubbleLocation, this.workspaceLocation)
@@ -145,8 +141,9 @@ export class ScratchCommentIcon extends Blockly.icons.Icon implements Blockly.IS
145
141
  return true
146
142
  }
147
143
 
148
- async setBubbleVisible(visible: boolean) {
144
+ setBubbleVisible(visible: boolean) {
149
145
  this.commentBubble.setCollapsed(!visible)
146
+ return Promise.resolve()
150
147
  }
151
148
 
152
149
  getBubble(): ScratchCommentBubble | null {
@@ -16,8 +16,7 @@ class ScratchConnectionChecker extends Blockly.ConnectionChecker {
16
16
  ): number {
17
17
  // The prototype's next connection is visual-only and should not accept any connections.
18
18
  const isPrototypeNextConn = (c: Blockly.Connection | null) =>
19
- c?.type === Blockly.ConnectionType.NEXT_STATEMENT &&
20
- c.getSourceBlock().type === 'procedures_prototype'
19
+ c?.type === Blockly.ConnectionType.NEXT_STATEMENT && c.getSourceBlock().type === 'procedures_prototype'
21
20
  if (isPrototypeNextConn(a) || isPrototypeNextConn(b)) {
22
21
  return Blockly.Connection.REASON_CHECKS_FAILED
23
22
  }
@@ -19,6 +19,8 @@ export class ScratchContinuousCategory extends ContinuousCategory {
19
19
  * devices.
20
20
  */
21
21
  private showStatusButton = false
22
+ private iconURI?: string
23
+ private secondaryColour?: string
22
24
 
23
25
  /**
24
26
  * Creates a new ScratchContinuousCategory.
@@ -33,21 +35,29 @@ export class ScratchContinuousCategory extends ContinuousCategory {
33
35
  ) {
34
36
  super(toolboxItemDef, parentToolbox, opt_parent)
35
37
  this.showStatusButton = toolboxItemDef.showStatusButton === 'true'
38
+ const iconURI = 'iconURI' in toolboxItemDef ? toolboxItemDef.iconURI : undefined
39
+ const secondaryColour = 'secondaryColour' in toolboxItemDef ? toolboxItemDef.secondaryColour : undefined
40
+ this.iconURI = typeof iconURI === 'string' ? iconURI : undefined
41
+ this.secondaryColour = typeof secondaryColour === 'string' ? secondaryColour : undefined
36
42
  }
37
43
 
38
44
  /**
39
45
  * Creates a DOM element for this category's icon.
40
46
  * @returns A DOM element for this category's icon.
41
47
  */
42
- createIconDom_(): HTMLElement {
43
- if (this.toolboxItemDef_.iconURI) {
48
+ createIconDom_(): Element {
49
+ if (this.iconURI) {
44
50
  const icon = document.createElement('img')
45
- icon.src = this.toolboxItemDef_.iconURI
51
+ icon.src = this.iconURI
46
52
  icon.className = 'categoryIconBubble'
47
53
  return icon
48
54
  } else {
49
55
  const icon = super.createIconDom_()
50
- icon.style.border = `1px solid ${this.toolboxItemDef_.secondaryColour}`
56
+ if (icon instanceof HTMLElement) {
57
+ if (this.secondaryColour) {
58
+ icon.style.border = `1px solid ${this.secondaryColour}`
59
+ }
60
+ }
51
61
  return icon
52
62
  }
53
63
  }
@@ -59,7 +69,9 @@ export class ScratchContinuousCategory extends ContinuousCategory {
59
69
  setSelected(isSelected: boolean) {
60
70
  super.setSelected(isSelected)
61
71
  // Prevent hardcoding the background color to grey.
62
- this.rowDiv_.style.backgroundColor = ''
72
+ if (this.rowDiv_) {
73
+ this.rowDiv_.style.backgroundColor = ''
74
+ }
63
75
  }
64
76
 
65
77
  /**
@@ -74,10 +86,7 @@ export class ScratchContinuousCategory extends ContinuousCategory {
74
86
 
75
87
  /** Registers this toolbox category and unregisters the default one. */
76
88
  export function registerScratchContinuousCategory() {
77
- Blockly.registry.unregister(Blockly.registry.Type.TOOLBOX_ITEM, ScratchContinuousCategory.registrationName)
78
- Blockly.registry.register(
79
- Blockly.registry.Type.TOOLBOX_ITEM,
80
- ScratchContinuousCategory.registrationName,
81
- ScratchContinuousCategory,
82
- )
89
+ const registrationName = ScratchContinuousCategory.registrationName
90
+ Blockly.registry.unregister(Blockly.registry.Type.TOOLBOX_ITEM, registrationName)
91
+ Blockly.registry.register(Blockly.registry.Type.TOOLBOX_ITEM, registrationName, ScratchContinuousCategory)
83
92
  }
@@ -16,6 +16,10 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
16
16
  */
17
17
  private postRenderCallbacks: (() => void)[] = []
18
18
 
19
+ private getInitialFlyoutContents_(): Blockly.utils.toolbox.FlyoutItemInfoArray {
20
+ return this.getToolboxItems().flatMap((item) => this.convertToolboxItemToFlyoutItems(item))
21
+ }
22
+
19
23
  refreshSelection() {
20
24
  // Intentionally a no-op, Scratch manually manages refreshing the toolbox
21
25
  // via forceRerender().
@@ -45,9 +49,14 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
45
49
  * Forcibly rerenders the toolbox, preserving selection when possible.
46
50
  */
47
51
  forceRerender() {
48
- const selectedCategoryName = this.selectedItem_?.getName()
49
- this.getFlyout().show(this.getInitialFlyoutContents())
50
- this.selectCategoryByName(selectedCategoryName)
52
+ const selectedCategoryName = this.getSelectedItem()?.getName()
53
+ if (selectedCategoryName === '') {
54
+ throw new Error('Expected selected category name to be non-empty')
55
+ }
56
+ this.getFlyout().show(this.getInitialFlyoutContents_())
57
+ if (selectedCategoryName) {
58
+ this.selectCategoryByName(selectedCategoryName)
59
+ }
51
60
  let callback
52
61
  while ((callback = this.postRenderCallbacks.shift())) {
53
62
  callback()
@@ -93,7 +93,7 @@ export class ScratchDragger extends Blockly.dragging.Dragger {
93
93
  dragRoot.type === 'procedures_definition' &&
94
94
  this.wouldDeleteDraggable(event, dragRoot.getRootBlock())
95
95
  ) {
96
- const prototype = dragRoot.getInput('custom_block')!.connection!.targetBlock()
96
+ const prototype = dragRoot.getInput('custom_block')?.connection?.targetBlock()
97
97
  const hasCaller =
98
98
  prototype instanceof Blockly.BlockSvg &&
99
99
  isProcedureBlock(prototype) &&
@@ -118,7 +118,7 @@ export class ScratchDragger extends Blockly.dragging.Dragger {
118
118
  // on the workspace in order to depict the block mid-drag needs to be
119
119
  // deleted.
120
120
  if (this.originatedFromFlyout && this.draggedOutOfBounds) {
121
- Blockly.renderManagement.finishQueuedRenders().then(() => {
121
+ void Blockly.renderManagement.finishQueuedRenders().then(() => {
122
122
  const rootBlock = this.getDragRoot(this.draggable)
123
123
  if (rootBlock instanceof Blockly.BlockSvg) {
124
124
  rootBlock.dispose()
@@ -12,7 +12,7 @@ class ScratchVariableMap extends Blockly.VariableMap {
12
12
  // Variable names in Blockly are case-insensitive, but case sensitive in
13
13
  // Scratch. Override the implementation to only return a variable whose name
14
14
  // is identical to the one requested.
15
- const variables = this.getVariablesOfType(type ?? '')
15
+ const variables = this.getVariablesOfType(type)
16
16
  if (!variables.length) return null
17
17
  return variables.find((v) => v.getName() === name) ?? null
18
18
  }
@@ -46,7 +46,7 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
46
46
  /**
47
47
  * Function to be invoked when the status indicator is clicked.
48
48
  */
49
- static statusButtonCallback: (extensionId: string) => void
49
+ static statusButtonCallback: ((extensionId: string) => void) | null = null
50
50
 
51
51
  /**
52
52
  * Creates a new StatusIndicatorLabel.
@@ -60,11 +60,17 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
60
60
  json: Blockly.utils.toolbox.LabelInfo,
61
61
  ) {
62
62
  super(workspace, targetWorkspace, json, true)
63
- this.extensionId = json.id!
63
+ if (!json.id) {
64
+ throw new Error('StatusIndicatorLabel: missing required extension id in toolbox JSON')
65
+ }
66
+ this.extensionId = json.id
64
67
 
65
68
  const heightDelta = 40 - this.height
66
69
  this.height = 40
67
- const text = this.getSvgRoot().querySelector('text')!
70
+ const text = this.getSvgRoot().querySelector('text')
71
+ if (!text) {
72
+ throw new Error('StatusIndicatorLabel: missing flyout text element')
73
+ }
68
74
  const previousY = Number(text.getAttribute('y'))
69
75
 
70
76
  text.setAttribute('y', `${previousY + heightDelta / 2}`)
@@ -73,7 +79,7 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
73
79
  const marginX = 20
74
80
  const marginY = 5
75
81
  const touchPadding = 16
76
- const flyoutWidth = targetWorkspace.getFlyout()!.getWidth()
82
+ const flyoutWidth = targetWorkspace.getFlyout()?.getWidth() ?? 0
77
83
 
78
84
  const statusButtonX = workspace.RTL
79
85
  ? marginX - flyoutWidth + statusButtonWidth
@@ -129,17 +135,15 @@ export class StatusIndicatorLabel extends Blockly.FlyoutButton {
129
135
  * @package
130
136
  */
131
137
  setImageSrc(src: string) {
132
- if (this.imageElement) {
133
- this.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', src)
134
- }
138
+ this.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', src)
135
139
  }
136
140
 
137
141
  /**
138
142
  * Gets the extension state. Overridden externally.
139
- * @param extensionId A string identifying which extension's state to retrieve.
143
+ * @param _extensionId A string identifying which extension's state to retrieve.
140
144
  * @returns Whether the extension is ready to be used.
141
145
  */
142
- getExtensionState(extensionId: string): StatusButtonState {
146
+ getExtensionState(_extensionId: string): StatusButtonState {
143
147
  return StatusButtonState.NOT_READY
144
148
  }
145
149
 
@@ -18,7 +18,8 @@ class StatusIndicatorLabelFlyoutInflater extends Blockly.LabelFlyoutInflater {
18
18
  * @returns The newly created status indicator label.
19
19
  */
20
20
  load(state: Blockly.utils.toolbox.LabelInfo, flyout: Blockly.IFlyout): Blockly.FlyoutItem {
21
- const label = new StatusIndicatorLabel(flyout.getWorkspace(), flyout.targetWorkspace!, state)
21
+ const targetWorkspace = flyout.targetWorkspace ?? flyout.getWorkspace()
22
+ const label = new StatusIndicatorLabel(flyout.getWorkspace(), targetWorkspace, state)
22
23
  label.show()
23
24
  return new Blockly.FlyoutItem(label, STATUS_INDICATOR_LABEL_TYPE)
24
25
  }
package/src/variables.ts CHANGED
@@ -84,23 +84,26 @@ export function createVariable(
84
84
  // opt_type -- turns a falsey opt_type into ''
85
85
  // TODO (#1251) Warn developers that they didn't provide an opt_type/
86
86
  // provided a falsey opt_type
87
- opt_type = opt_type ? opt_type : ''
87
+ opt_type = opt_type ?? ''
88
88
  newMsg = Blockly.Msg.NEW_VARIABLE_TITLE
89
89
  modalTitle = Blockly.Msg.VARIABLE_MODAL_TITLE
90
90
  }
91
91
  const validate = nameValidator.bind(null, opt_type)
92
+ if (!prompt) {
93
+ throw new Error('createVariable: prompt handler is not set')
94
+ }
92
95
 
93
96
  // Prompt the user to enter a name for the variable
94
- prompt!(
97
+ prompt(
95
98
  newMsg,
96
99
  '',
97
- (text: string, additionalVars: string[], variableOptions?: { scope?: string; isCloud?: boolean }) => {
98
- variableOptions = variableOptions || {}
100
+ (text: string, additionalVars?: string[], variableOptions?: { scope?: string; isCloud?: boolean }) => {
101
+ variableOptions = variableOptions ?? {}
99
102
  const scope = variableOptions.scope
100
- const isLocal = scope === 'local' || false
101
- const isCloud = variableOptions.isCloud || false
103
+ const isLocal = scope === 'local'
104
+ const isCloud = variableOptions.isCloud ?? false
102
105
  // Default to [] if additionalVars is not provided
103
- additionalVars = additionalVars || []
106
+ additionalVars = additionalVars ?? []
104
107
  // Only use additionalVars for global variable creation.
105
108
  const additionalVarNames = isLocal ? [] : additionalVars
106
109
 
@@ -159,7 +162,7 @@ export function createVariable(
159
162
  function nameValidator(
160
163
  type: string,
161
164
  text: string,
162
- workspace: Blockly.WorkspaceSvg,
165
+ workspace: Blockly.Workspace,
163
166
  additionalVars: string[],
164
167
  isCloud: boolean,
165
168
  opt_callback?: (id?: string) => void,
@@ -200,7 +203,7 @@ function nameValidator(
200
203
  */
201
204
  function validateBroadcastMessageName(
202
205
  name: string,
203
- workspace: Blockly.WorkspaceSvg,
206
+ workspace: Blockly.Workspace,
204
207
  opt_callback?: (id?: string) => void,
205
208
  ): string | null {
206
209
  if (!name) {
@@ -242,7 +245,7 @@ function validateBroadcastMessageName(
242
245
  */
243
246
  function validateScalarVarOrListName(
244
247
  name: string,
245
- workspace: Blockly.WorkspaceSvg,
248
+ workspace: Blockly.Workspace,
246
249
  additionalVars: string[],
247
250
  isCloud: boolean,
248
251
  type: string,
@@ -275,7 +278,7 @@ function validateScalarVarOrListName(
275
278
  * an existing variable was chosen.
276
279
  */
277
280
  export function renameVariable(
278
- workspace: Blockly.WorkspaceSvg,
281
+ workspace: Blockly.Workspace,
279
282
  variable: ScratchVariableModel,
280
283
  opt_callback?: (id?: string) => void,
281
284
  ) {
@@ -305,15 +308,19 @@ export function renameVariable(
305
308
  promptDefaultText = promptDefaultText.substring(CLOUD_PREFIX.length)
306
309
  }
307
310
 
308
- prompt!(
311
+ if (!prompt) {
312
+ throw new Error('renameVariable: prompt handler is not set')
313
+ }
314
+
315
+ prompt(
309
316
  promptText,
310
317
  promptDefaultText,
311
- (newName: string, additionalVars: string[]) => {
318
+ (newName: string, additionalVars?: string[]) => {
312
319
  if (variable.isCloud && newName.length > 0 && newName.startsWith(CLOUD_PREFIX)) {
313
320
  newName = newName.substring(CLOUD_PREFIX.length)
314
321
  // The name validator will add the prefix back
315
322
  }
316
- additionalVars = additionalVars || []
323
+ additionalVars = additionalVars ?? []
317
324
  const additionalVarNames = variable.isLocal ? [] : additionalVars
318
325
  const validatedText = validate(newName, workspace, additionalVarNames, variable.isCloud)
319
326
  if (validatedText) {
@@ -0,0 +1,14 @@
1
+ import * as Blockly from 'blockly/core'
2
+
3
+ export function getRequiredMainWorkspaceSvg(): Blockly.WorkspaceSvg {
4
+ const mainWorkspace = Blockly.getMainWorkspace()
5
+ if (!(mainWorkspace instanceof Blockly.WorkspaceSvg)) {
6
+ throw new Error('Expected main workspace to be a WorkspaceSvg')
7
+ }
8
+ return mainWorkspace
9
+ }
10
+
11
+ export function getBlockSvgById(workspace: Blockly.WorkspaceSvg, id: string): Blockly.BlockSvg | null {
12
+ const block = workspace.getBlockById(id)
13
+ return block instanceof Blockly.BlockSvg ? block : null
14
+ }
package/src/xml.ts CHANGED
@@ -22,7 +22,7 @@ export function clearWorkspaceAndLoadFromXml(xml: Element, workspace: Blockly.Wo
22
22
  const id = variable.getAttribute('id')
23
23
  if (!id) continue
24
24
  const type = variable.getAttribute('type') ?? ''
25
- const name = variable.textContent ?? ''
25
+ const name = variable.textContent
26
26
  const isLocal = variable.getAttribute('islocal') === 'true'
27
27
  const isCloud = variable.getAttribute('iscloud') === 'true'
28
28
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["./src/", "./types/"]
4
+ }
package/tsconfig.json CHANGED
@@ -24,5 +24,5 @@
24
24
  /* Language and Environment */
25
25
  "target": "es2020"
26
26
  },
27
- "include": ["./src/", "./tests/", "./types/"]
27
+ "include": ["./eslint.config.mjs", "./prettier.config.mjs", "./src/", "./tests/", "./types/", "./vitest.config.ts"]
28
28
  }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Copyright 2025 Google LLC
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import { playwright } from '@vitest/browser-playwright'
6
+ import { defineConfig } from 'vitest/config'
7
+
8
+ export default defineConfig({
9
+ test: {
10
+ projects: [
11
+ {
12
+ test: {
13
+ name: 'unit',
14
+ include: ['tests/unit/**/*.test.ts'],
15
+ environment: 'jsdom',
16
+ },
17
+ },
18
+ {
19
+ test: {
20
+ name: 'browser',
21
+ include: ['tests/browser/**/*.test.ts'],
22
+ browser: {
23
+ provider: playwright(),
24
+ enabled: true,
25
+ // to see the browser, run something like one of these:
26
+ // `npm run test:browser -- --browser.headless=false`
27
+ // `PWDEBUG=1 npm run test:browser` (also pauses on startup and opens devtools)
28
+ headless: true,
29
+ instances: [{ browser: 'chromium' }],
30
+ },
31
+ },
32
+ },
33
+ ],
34
+ },
35
+ })
@@ -1,13 +0,0 @@
1
- declare function test_logic_ternary_structure(): void;
2
- declare function test_logic_ternary_attachSameTypeCheckInThenAndElseWithoutParent(): void;
3
- declare function test_logic_ternary_attachDifferectTypeChecksInThenAndElseWithoutParent(): void;
4
- declare function test_logic_ternary_attachSameTypeCheckInThenAndElseWithMatchingParent(): void;
5
- declare function test_logic_ternary_attachDifferectTypeChecksInThenAndElseWithUncheckedParent(): void;
6
- declare function test_logic_ternary_attachDifferectTypeChecksInThenAndElseWithPermissiveParent(): void;
7
- declare function test_logic_ternary_attachMismatchTypeToThen_breakWithParent(): void;
8
- declare function test_logic_ternary_attachMismatchTypeToElse_breakWithParent(): void;
9
- declare function test_logic_ternary_attachToUncheckedParentWithDifferentTypes(): void;
10
- declare function test_logic_ternary_attachToPermissiveParentWithDifferentTypes(): void;
11
- declare function test_logic_ternary_attachToParentWithMismatchingThen_disconnectThen(): void;
12
- declare function test_logic_ternary_attachToParentWithMismatchingElse_disconnectElse(): void;
13
- //# sourceMappingURL=logic_ternary_test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logic_ternary_test.d.ts","sourceRoot":"","sources":["../../../../tests/blocks/logic_ternary_test.js"],"names":[],"mappings":"AAqBA,sDAWC;AAED,0FAkBC;AAED,gGAkBC;AAED,+FAwBC;AAED,sGAwBC;AAED,uGAwBC;AAED,qFA2BC;AAED,qFA2BC;AAED,sFAwBC;AAED,uFAyBC;AAED,6FAyBC;AAED,6FAyBC"}
@@ -1,4 +0,0 @@
1
- declare function test_appendField_FieldIconMenu(): void;
2
- declare function test_jsonInit_FieldIconMenu(): void;
3
- declare function test_jsonInit_colors(): void;
4
- //# sourceMappingURL=block_test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"block_test.d.ts","sourceRoot":"","sources":["../../../../tests/jsunit/block_test.js"],"names":[],"mappings":"AAqBA,wDAsBC;AAED,qDA+BC;AAED,8CAmCC"}
@@ -1,25 +0,0 @@
1
- declare function verify_DB_(msg: any, expected: any, db: any): void;
2
- declare function test_DB_addConnection(): void;
3
- declare function test_DB_removeConnection(): void;
4
- declare function test_DB_getNeighbours(): void;
5
- declare function test_DB_getNeighbours(): void;
6
- declare function test_DB_findPositionForConnection(): void;
7
- declare function test_DB_findConnection(): void;
8
- declare function test_DB_ordering(): void;
9
- declare function test_SearchForClosest(): void;
10
- declare function helper_getNeighbours(db: any, x: any, y: any, radius: any): any;
11
- declare function helper_getNeighbours(db: any, x: any, y: any, radius: any): any;
12
- declare function helper_searchDB(db: any, x: any, y: any, radius: any, shared_workspace: any): any;
13
- declare function helper_makeSourceBlock(sharedWorkspace: any): {
14
- workspace: any;
15
- parentBlock_: null;
16
- getParent: () => null;
17
- movable_: boolean;
18
- isMovable: () => boolean;
19
- isShadow: () => boolean;
20
- isInsertionMarker: () => boolean;
21
- getFirstStatementConnection: () => null;
22
- };
23
- declare function helper_createConnection(x: any, y: any, type: any, opt_shared_workspace: any, opt_rendered: any): any;
24
- declare function helper_createConnection(x: any, y: any, type: any): any;
25
- //# sourceMappingURL=connection_db_test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connection_db_test.d.ts","sourceRoot":"","sources":["../../../../tests/jsunit/connection_db_test.js"],"names":[],"mappings":"AAqBA,oEAeC;AAED,+CAqBC;AAED,kDAiCC;AAED,+CA2CC;;AAED,2DAWC;AAED,gDAaC;AAED,0CA8BC;AAED,+CAoCC;AAED,iFAEC;;AAED,mGAMC;AAED;;;;;;;;;EAqBC;AAED,uHAUC"}