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
@@ -4,26 +4,38 @@
4
4
  */
5
5
  import * as Blockly from 'blockly/core'
6
6
 
7
+ type ActionRegistryItem = Extract<Blockly.ContextMenuRegistry.RegistryItem, { callback: unknown }>
8
+
9
+ function isActionRegistryItem(item: Blockly.ContextMenuRegistry.RegistryItem): item is ActionRegistryItem {
10
+ return 'callback' in item && 'displayText' in item && 'preconditionFn' in item
11
+ }
12
+
7
13
  /**
8
14
  * Registers a block delete option that ignores shadows in the block count.
9
15
  */
10
16
  export function registerDeleteBlock() {
11
17
  const deleteOption = {
12
18
  displayText(scope: Blockly.ContextMenuRegistry.Scope) {
13
- const descendantCount = getDeletableBlocksInStack(scope.block!).length
19
+ if (!scope.block) {
20
+ return Blockly.Msg.DELETE_BLOCK
21
+ }
22
+ const descendantCount = getDeletableBlocksInStack(scope.block).length
14
23
  return descendantCount === 1
15
24
  ? Blockly.Msg.DELETE_BLOCK
16
25
  : Blockly.Msg.DELETE_X_BLOCKS.replace('%1', `${descendantCount}`)
17
26
  },
18
27
  preconditionFn(scope: Blockly.ContextMenuRegistry.Scope) {
19
- if (!scope.block!.isInFlyout && scope.block!.isDeletable()) {
28
+ if (scope.block && !scope.block.isInFlyout && scope.block.isDeletable()) {
20
29
  return 'enabled'
21
30
  }
22
31
  return 'hidden'
23
32
  },
24
33
  callback(scope: Blockly.ContextMenuRegistry.Scope) {
34
+ if (!scope.block) {
35
+ return
36
+ }
25
37
  Blockly.Events.setGroup(true)
26
- scope.block!.dispose(true, true)
38
+ scope.block.dispose(true, true)
27
39
  Blockly.Events.setGroup(false)
28
40
  },
29
41
  scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
@@ -33,17 +45,18 @@ export function registerDeleteBlock() {
33
45
  Blockly.ContextMenuRegistry.registry.register(deleteOption)
34
46
  }
35
47
 
36
- function getDeletableBlocksInStack(block: Blockly.BlockSvg): Blockly.BlockSvg[] {
48
+ function getDeletableBlocksInStack(block: Blockly.Block): Blockly.Block[] {
37
49
  let descendants = block.getDescendants(false).filter(isDeletable)
38
- if (block.getNextBlock()) {
50
+ const nextBlock = block.getNextBlock()
51
+ if (nextBlock) {
39
52
  // Next blocks are not deleted.
40
- const nextDescendants = block.getNextBlock()!.getDescendants(false).filter(isDeletable)
53
+ const nextDescendants = nextBlock.getDescendants(false).filter(isDeletable)
41
54
  descendants = descendants.filter((b) => !nextDescendants.includes(b))
42
55
  }
43
56
  return descendants
44
57
  }
45
58
 
46
- function isDeletable(block: Blockly.BlockSvg): boolean {
59
+ function isDeletable(block: Blockly.Block): boolean {
47
60
  return block.isDeletable() && !block.isShadow()
48
61
  }
49
62
 
@@ -101,8 +114,8 @@ export function registerDeleteAll() {
101
114
  * @param workspace to delete all blocks from.
102
115
  * @returns list of blocks to delete.
103
116
  */
104
- function getDeletableBlocksInWorkspace(workspace: Blockly.WorkspaceSvg): Blockly.BlockSvg[] {
105
- return workspace.getTopBlocks(true).flatMap((b: Blockly.BlockSvg) => b.getDescendants(false).filter(isDeletable))
117
+ function getDeletableBlocksInWorkspace(workspace: Blockly.Workspace): Blockly.Block[] {
118
+ return workspace.getTopBlocks(true).flatMap((b) => b.getDescendants(false).filter(isDeletable))
106
119
  }
107
120
 
108
121
  /**
@@ -111,7 +124,7 @@ function getDeletableBlocksInWorkspace(workspace: Blockly.WorkspaceSvg): Blockly
111
124
  * @param eventGroup Event group ID with which all delete events should be
112
125
  * associated. If not specified, create a new group.
113
126
  */
114
- function deleteNext(deleteList: Blockly.BlockSvg[], eventGroup?: string) {
127
+ function deleteNext(deleteList: Blockly.Block[], eventGroup?: string) {
115
128
  const DELAY = 10
116
129
  if (eventGroup) {
117
130
  Blockly.Events.setGroup(eventGroup)
@@ -122,8 +135,12 @@ function deleteNext(deleteList: Blockly.BlockSvg[], eventGroup?: string) {
122
135
  const block = deleteList.shift()
123
136
  if (block) {
124
137
  if (!block.isDeadOrDying()) {
125
- block.dispose(false, true)
126
- setTimeout(deleteNext, DELAY, deleteList, eventGroup)
138
+ if (block instanceof Blockly.BlockSvg) {
139
+ block.dispose(false, true)
140
+ } else {
141
+ block.dispose(false)
142
+ }
143
+ setTimeout(() => deleteNext(deleteList, eventGroup), DELAY)
127
144
  } else {
128
145
  deleteNext(deleteList, eventGroup)
129
146
  }
@@ -136,10 +153,18 @@ function deleteNext(deleteList: Blockly.BlockSvg[], eventGroup?: string) {
136
153
  * all subsequent blocks in the stack.
137
154
  */
138
155
  export function registerDuplicateBlock() {
139
- const original = Blockly.ContextMenuRegistry.registry.getItem('blockDuplicate')!
140
- const duplicateOption = {
141
- displayText: original.displayText!,
142
- preconditionFn: original.preconditionFn!,
156
+ const original = Blockly.ContextMenuRegistry.registry.getItem('blockDuplicate')
157
+ if (!original) {
158
+ console.error('[context_menu_items] Missing required blockDuplicate menu item')
159
+ return
160
+ }
161
+ if (!isActionRegistryItem(original)) {
162
+ console.error('[context_menu_items] Expected blockDuplicate to be an action item')
163
+ return
164
+ }
165
+ const duplicateOption: ActionRegistryItem = {
166
+ displayText: original.displayText,
167
+ preconditionFn: original.preconditionFn,
143
168
  callback(scope: Blockly.ContextMenuRegistry.Scope) {
144
169
  if (!scope.block) return
145
170
  const data = scope.block.toCopyData(true)
@@ -440,7 +440,11 @@ function addBlock(
440
440
  ${secondValueField}
441
441
  </block>
442
442
  </xml>`
443
- const block = Blockly.utils.xml.textToDom(blockText).firstElementChild!
443
+ const block = Blockly.utils.xml.textToDom(blockText).firstElementChild
444
+ if (!block) {
445
+ console.error(`[data_category/addBlock] Failed to create block XML for type ${blockType}`)
446
+ return
447
+ }
444
448
  xmlList.push(block)
445
449
  }
446
450
  }
@@ -456,7 +460,7 @@ function generateVariableFieldXml(
456
460
  opt_name?: string,
457
461
  ): string {
458
462
  const field = document.createElement('field')
459
- field.setAttribute('name', opt_name || 'VARIABLE')
463
+ field.setAttribute('name', opt_name ?? 'VARIABLE')
460
464
  field.setAttribute('id', variableModel.getId())
461
465
  field.setAttribute('variabletype', variableModel.getType())
462
466
  field.textContent = variableModel.getName()
@@ -503,6 +507,10 @@ function createValue(valueName: string, type: string, value: string): string {
503
507
  */
504
508
  function addSep(xmlList: Element[]) {
505
509
  const sepText = `<xml><sep gap="36"/></xml>`
506
- const sep = Blockly.utils.xml.textToDom(sepText).firstElementChild!
510
+ const sep = Blockly.utils.xml.textToDom(sepText).firstElementChild
511
+ if (!sep) {
512
+ console.error('[data_category/addSep] Failed to create separator XML')
513
+ return
514
+ }
507
515
  xmlList.push(sep)
508
516
  }
@@ -33,7 +33,11 @@ export class BlockCommentBase extends Blockly.Events.Abstract {
33
33
  }
34
34
  }
35
35
 
36
- static fromJson(json: BlockCommentBaseJson, workspace: Blockly.Workspace, event?: any): BlockCommentBase {
36
+ static fromJson(
37
+ json: BlockCommentBaseJson,
38
+ workspace: Blockly.Workspace,
39
+ event?: Blockly.Events.Abstract,
40
+ ): BlockCommentBase {
37
41
  const newEvent = super.fromJson(json, workspace, event ?? new BlockCommentBase()) as BlockCommentBase
38
42
  newEvent.commentId = json.commentId
39
43
  newEvent.blockId = json.blockId
@@ -29,7 +29,11 @@ class BlockCommentChange extends BlockCommentBase {
29
29
  }
30
30
  }
31
31
 
32
- static fromJson(json: BlockCommentChangeJson, workspace: Blockly.Workspace, event?: any): BlockCommentChange {
32
+ static fromJson(
33
+ json: BlockCommentChangeJson,
34
+ workspace: Blockly.Workspace,
35
+ event?: Blockly.Events.Abstract,
36
+ ): BlockCommentChange {
33
37
  const newEvent = super.fromJson(json, workspace, event ?? new BlockCommentChange()) as BlockCommentChange
34
38
  newEvent.newContents_ = json.newContents
35
39
  newEvent.oldContents_ = json.oldContents
@@ -22,7 +22,11 @@ class BlockCommentCollapse extends BlockCommentBase {
22
22
  }
23
23
  }
24
24
 
25
- static fromJson(json: BlockCommentCollapseJson, workspace: Blockly.Workspace, event?: any): BlockCommentCollapse {
25
+ static fromJson(
26
+ json: BlockCommentCollapseJson,
27
+ workspace: Blockly.Workspace,
28
+ event?: Blockly.Events.Abstract,
29
+ ): BlockCommentCollapse {
26
30
  const newEvent = super.fromJson(json, workspace, event ?? new BlockCommentCollapse()) as BlockCommentCollapse
27
31
  newEvent.newCollapsed = json.collapsed
28
32
 
@@ -41,7 +45,7 @@ class BlockCommentCollapse extends BlockCommentBase {
41
45
  console.warn('BlockCommentCollapse.run: comment icon not found', block.id)
42
46
  return
43
47
  }
44
- comment.setBubbleVisible(forward ? !this.newCollapsed : this.newCollapsed)
48
+ void comment.setBubbleVisible(forward ? !this.newCollapsed : this.newCollapsed)
45
49
  }
46
50
  }
47
51
 
@@ -41,7 +41,11 @@ class BlockCommentCreate extends BlockCommentBase {
41
41
  }
42
42
  }
43
43
 
44
- static fromJson(json: BlockCommentCreateJson, workspace: Blockly.Workspace, event?: any): BlockCommentCreate {
44
+ static fromJson(
45
+ json: BlockCommentCreateJson,
46
+ workspace: Blockly.Workspace,
47
+ event?: Blockly.Events.Abstract,
48
+ ): BlockCommentCreate {
45
49
  const newEvent = super.fromJson(json, workspace, event ?? new BlockCommentCreate()) as BlockCommentCreate
46
50
  newEvent.json = {
47
51
  x: json.x,
@@ -29,7 +29,11 @@ class BlockCommentMove extends BlockCommentBase {
29
29
  }
30
30
  }
31
31
 
32
- static fromJson(json: BlockCommentMoveJson, workspace: Blockly.Workspace, event?: any): BlockCommentMove {
32
+ static fromJson(
33
+ json: BlockCommentMoveJson,
34
+ workspace: Blockly.Workspace,
35
+ event?: Blockly.Events.Abstract,
36
+ ): BlockCommentMove {
33
37
  const newEvent = super.fromJson(json, workspace, event ?? new BlockCommentMove()) as BlockCommentMove
34
38
  newEvent.newCoordinate_ = new Blockly.utils.Coordinate(json.newCoordinate.x, json.newCoordinate.y)
35
39
  newEvent.oldCoordinate_ = new Blockly.utils.Coordinate(json.oldCoordinate.x, json.oldCoordinate.y)
@@ -39,7 +43,7 @@ class BlockCommentMove extends BlockCommentBase {
39
43
 
40
44
  run(forward: boolean) {
41
45
  const workspace = this.getEventWorkspace_()
42
- const block = workspace?.getBlockById(this.blockId)
46
+ const block = workspace.getBlockById(this.blockId)
43
47
  const comment = block?.getIcon(Blockly.icons.IconType.COMMENT)
44
48
  comment?.setBubbleLocation(forward ? this.newCoordinate_ : this.oldCoordinate_)
45
49
  }
@@ -31,7 +31,11 @@ class BlockCommentResize extends BlockCommentBase {
31
31
  }
32
32
  }
33
33
 
34
- static fromJson(json: BlockCommentResizeJson, workspace: Blockly.Workspace, event?: any): BlockCommentResize {
34
+ static fromJson(
35
+ json: BlockCommentResizeJson,
36
+ workspace: Blockly.Workspace,
37
+ event?: Blockly.Events.Abstract,
38
+ ): BlockCommentResize {
35
39
  const newEvent = super.fromJson(json, workspace, event ?? new BlockCommentResize()) as BlockCommentResize
36
40
  newEvent.newSize = new Blockly.utils.Size(json.newSize.width, json.newSize.height)
37
41
  newEvent.oldSize = new Blockly.utils.Size(json.oldSize.width, json.oldSize.height)
@@ -41,7 +45,7 @@ class BlockCommentResize extends BlockCommentBase {
41
45
 
42
46
  run(forward: boolean) {
43
47
  const workspace = this.getEventWorkspace_()
44
- const block = workspace?.getBlockById(this.blockId)
48
+ const block = workspace.getBlockById(this.blockId)
45
49
  const comment = block?.getIcon(Blockly.icons.IconType.COMMENT)
46
50
  comment?.setBubbleSize(forward ? this.newSize : this.oldSize)
47
51
  }
@@ -24,7 +24,11 @@ export class BlockDragEnd extends Blockly.Events.BlockBase {
24
24
  }
25
25
  }
26
26
 
27
- static fromJson(json: BlockDragEndJson, workspace: Blockly.Workspace, event?: any): BlockDragEnd {
27
+ static fromJson(
28
+ json: BlockDragEndJson,
29
+ workspace: Blockly.Workspace,
30
+ event?: Blockly.Events.Abstract,
31
+ ): BlockDragEnd {
28
32
  const newEvent = super.fromJson(json, workspace, event ?? new BlockDragEnd()) as BlockDragEnd
29
33
  newEvent.isOutside = json.isOutside
30
34
  newEvent.xml = Blockly.utils.xml.textToDom(json.xml)
@@ -21,7 +21,11 @@ export class BlockDragOutside extends Blockly.Events.BlockBase {
21
21
  }
22
22
  }
23
23
 
24
- static fromJson(json: BlockDragOutsideJson, workspace: Blockly.Workspace, event?: any): BlockDragOutside {
24
+ static fromJson(
25
+ json: BlockDragOutsideJson,
26
+ workspace: Blockly.Workspace,
27
+ event?: Blockly.Events.Abstract,
28
+ ): BlockDragOutside {
25
29
  const newEvent = super.fromJson(json, workspace, event ?? new BlockDragOutside()) as BlockDragOutside
26
30
  newEvent.isOutside = json.isOutside
27
31
 
@@ -25,7 +25,11 @@ class ScratchVariableCreate extends Blockly.Events.VarCreate {
25
25
  }
26
26
  }
27
27
 
28
- static fromJson(json: ScratchVariableCreateJson, workspace: Blockly.Workspace, event?: any): ScratchVariableCreate {
28
+ static fromJson(
29
+ json: ScratchVariableCreateJson,
30
+ workspace: Blockly.Workspace,
31
+ event?: Blockly.Events.Abstract,
32
+ ): ScratchVariableCreate {
29
33
  const newEvent = super.fromJson(json, workspace, event ?? new ScratchVariableCreate()) as ScratchVariableCreate
30
34
  newEvent.isLocal = json.isLocal
31
35
  newEvent.isCloud = json.isCloud
@@ -96,7 +96,7 @@ export class FieldColourSlider extends FieldColour {
96
96
  stops.push(Blockly.utils.colour.hsvToHex(this.hue_, this.saturation_, (255 * n) / 360))
97
97
  break
98
98
  default:
99
- throw new Error('Unknown channel for colour sliders: ' + channel)
99
+ throw new Error(`Unknown channel for colour sliders: ${String(channel)}`)
100
100
  }
101
101
  }
102
102
  return stops
@@ -198,9 +198,7 @@ export class FieldColourSlider extends FieldColour {
198
198
  break
199
199
  }
200
200
  const colour = Blockly.utils.colour.hsvToHex(this.hue_, this.saturation_, this.brightness_)
201
- if (colour !== null) {
202
- this.setValue(colour, true)
203
- }
201
+ this.setValue(colour, true)
204
202
  }
205
203
  }
206
204
 
@@ -279,7 +277,7 @@ export class FieldColourSlider extends FieldColour {
279
277
  button,
280
278
  'click',
281
279
  this,
282
- this.activateEyedropperInternal_,
280
+ this.activateEyedropperInternal_.bind(this),
283
281
  )
284
282
  }
285
283
 
@@ -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
  }