scratch-blocks 2.1.5 → 2.1.7

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 (118) hide show
  1. package/AGENTS.md +58 -14
  2. package/dist/main.mjs +1 -1
  3. package/dist/types/src/block_reporting.d.ts.map +1 -1
  4. package/dist/types/src/blocks/procedures.d.ts +2 -2
  5. package/dist/types/src/blocks/procedures.d.ts.map +1 -1
  6. package/dist/types/src/checkable_continuous_flyout.d.ts +6 -3
  7. package/dist/types/src/checkable_continuous_flyout.d.ts.map +1 -1
  8. package/dist/types/src/checkbox_bubble.d.ts +7 -7
  9. package/dist/types/src/checkbox_bubble.d.ts.map +1 -1
  10. package/dist/types/src/colours.d.ts.map +1 -1
  11. package/dist/types/src/context_menu_items.d.ts.map +1 -1
  12. package/dist/types/src/events/events_block_comment_base.d.ts +1 -1
  13. package/dist/types/src/events/events_block_comment_base.d.ts.map +1 -1
  14. package/dist/types/src/events/events_block_drag_end.d.ts +1 -1
  15. package/dist/types/src/events/events_block_drag_end.d.ts.map +1 -1
  16. package/dist/types/src/events/events_block_drag_outside.d.ts +1 -1
  17. package/dist/types/src/events/events_block_drag_outside.d.ts.map +1 -1
  18. package/dist/types/src/fields/field_colour_slider.d.ts.map +1 -1
  19. package/dist/types/src/fields/field_matrix.d.ts.map +1 -1
  20. package/dist/types/src/fields/field_note.d.ts +6 -4
  21. package/dist/types/src/fields/field_note.d.ts.map +1 -1
  22. package/dist/types/src/fields/field_textinput_removable.d.ts.map +1 -1
  23. package/dist/types/src/fields/field_variable_getter.d.ts.map +1 -1
  24. package/dist/types/src/fields/field_vertical_separator.d.ts.map +1 -1
  25. package/dist/types/src/fields/scratch_field_angle.d.ts.map +1 -1
  26. package/dist/types/src/fields/scratch_field_dropdown.d.ts.map +1 -1
  27. package/dist/types/src/fields/scratch_field_number.d.ts.map +1 -1
  28. package/dist/types/src/fields/scratch_field_variable.d.ts +1 -0
  29. package/dist/types/src/fields/scratch_field_variable.d.ts.map +1 -1
  30. package/dist/types/src/flyout_checkbox_icon.d.ts +5 -5
  31. package/dist/types/src/flyout_checkbox_icon.d.ts.map +1 -1
  32. package/dist/types/src/glows.d.ts.map +1 -1
  33. package/dist/types/src/procedures.d.ts +4 -4
  34. package/dist/types/src/procedures.d.ts.map +1 -1
  35. package/dist/types/src/recyclable_block_flyout_inflater.d.ts +2 -2
  36. package/dist/types/src/recyclable_block_flyout_inflater.d.ts.map +1 -1
  37. package/dist/types/src/renderer/cat/cat_face.d.ts +1 -1
  38. package/dist/types/src/renderer/cat/cat_face.d.ts.map +1 -1
  39. package/dist/types/src/renderer/cat/drawer.d.ts.map +1 -1
  40. package/dist/types/src/renderer/constants.d.ts.map +1 -1
  41. package/dist/types/src/renderer/drawer.d.ts.map +1 -1
  42. package/dist/types/src/renderer/render_info.d.ts.map +1 -1
  43. package/dist/types/src/scratch_blocks_utils.d.ts +22 -0
  44. package/dist/types/src/scratch_blocks_utils.d.ts.map +1 -1
  45. package/dist/types/src/scratch_comment_bubble.d.ts +4 -4
  46. package/dist/types/src/scratch_comment_bubble.d.ts.map +1 -1
  47. package/dist/types/src/scratch_comment_icon.d.ts +1 -1
  48. package/dist/types/src/scratch_comment_icon.d.ts.map +1 -1
  49. package/dist/types/src/scratch_continuous_category.d.ts +3 -1
  50. package/dist/types/src/scratch_continuous_category.d.ts.map +1 -1
  51. package/dist/types/src/scratch_continuous_toolbox.d.ts +2 -1
  52. package/dist/types/src/scratch_continuous_toolbox.d.ts.map +1 -1
  53. package/dist/types/src/status_indicator_label.d.ts +3 -3
  54. package/dist/types/src/status_indicator_label.d.ts.map +1 -1
  55. package/dist/types/src/status_indicator_label_flyout_inflater.d.ts.map +1 -1
  56. package/dist/types/src/variables.d.ts +1 -1
  57. package/dist/types/src/variables.d.ts.map +1 -1
  58. package/dist/types/src/workspace_block_lookup.d.ts +4 -0
  59. package/dist/types/src/workspace_block_lookup.d.ts.map +1 -0
  60. package/eslint.config.mjs +21 -28
  61. package/package.json +1 -1
  62. package/src/block_reporting.ts +5 -5
  63. package/src/blocks/control.ts +5 -5
  64. package/src/blocks/data.ts +1 -1
  65. package/src/blocks/event.ts +1 -1
  66. package/src/blocks/motion.ts +2 -2
  67. package/src/blocks/procedures.ts +162 -69
  68. package/src/blocks/sensing.ts +0 -1
  69. package/src/blocks/vertical_extensions.ts +11 -8
  70. package/src/checkable_continuous_flyout.ts +45 -12
  71. package/src/checkbox_bubble.ts +7 -7
  72. package/src/colours.ts +4 -2
  73. package/src/context_menu_items.ts +41 -16
  74. package/src/data_category.ts +11 -3
  75. package/src/events/events_block_comment_base.ts +5 -1
  76. package/src/events/events_block_comment_change.ts +5 -1
  77. package/src/events/events_block_comment_collapse.ts +6 -2
  78. package/src/events/events_block_comment_create.ts +5 -1
  79. package/src/events/events_block_comment_move.ts +6 -2
  80. package/src/events/events_block_comment_resize.ts +6 -2
  81. package/src/events/events_block_drag_end.ts +5 -1
  82. package/src/events/events_block_drag_outside.ts +5 -1
  83. package/src/events/events_scratch_variable_create.ts +5 -1
  84. package/src/fields/field_colour_slider.ts +3 -5
  85. package/src/fields/field_matrix.ts +33 -17
  86. package/src/fields/field_note.ts +56 -20
  87. package/src/fields/field_textinput_removable.ts +13 -4
  88. package/src/fields/field_variable_getter.ts +20 -6
  89. package/src/fields/field_vertical_separator.ts +5 -1
  90. package/src/fields/scratch_field_angle.ts +32 -21
  91. package/src/fields/scratch_field_dropdown.ts +6 -2
  92. package/src/fields/scratch_field_number.ts +22 -13
  93. package/src/fields/scratch_field_variable.ts +26 -12
  94. package/src/flyout_checkbox_icon.ts +9 -5
  95. package/src/glows.ts +5 -5
  96. package/src/index.ts +18 -6
  97. package/src/procedures.ts +92 -42
  98. package/src/recyclable_block_flyout_inflater.ts +5 -4
  99. package/src/renderer/cat/cat_face.ts +1 -1
  100. package/src/renderer/cat/drawer.ts +4 -1
  101. package/src/renderer/constants.ts +19 -14
  102. package/src/renderer/drawer.ts +2 -1
  103. package/src/renderer/render_info.ts +12 -9
  104. package/src/renderer/renderer.ts +1 -1
  105. package/src/scratch_blocks_utils.ts +0 -2
  106. package/src/scratch_c_block_wrap.ts +37 -21
  107. package/src/scratch_comment_bubble.ts +30 -19
  108. package/src/scratch_comment_icon.ts +9 -12
  109. package/src/scratch_continuous_category.ts +20 -11
  110. package/src/scratch_continuous_toolbox.ts +12 -3
  111. package/src/scratch_dragger.ts +2 -2
  112. package/src/scratch_variable_map.ts +1 -1
  113. package/src/status_indicator_label.ts +13 -9
  114. package/src/status_indicator_label_flyout_inflater.ts +2 -1
  115. package/src/variables.ts +21 -14
  116. package/src/workspace_block_lookup.ts +14 -0
  117. package/src/xml.ts +1 -1
  118. package/types/continuous-toolbox.d.ts +0 -1
@@ -42,7 +42,7 @@ export declare class StatusIndicatorLabel extends Blockly.FlyoutButton {
42
42
  /**
43
43
  * Function to be invoked when the status indicator is clicked.
44
44
  */
45
- static statusButtonCallback: (extensionId: string) => void;
45
+ static statusButtonCallback: ((extensionId: string) => void) | null;
46
46
  /**
47
47
  * Creates a new StatusIndicatorLabel.
48
48
  * @param workspace The workspace in which to place this header.
@@ -62,10 +62,10 @@ export declare class StatusIndicatorLabel extends Blockly.FlyoutButton {
62
62
  setImageSrc(src: string): void;
63
63
  /**
64
64
  * Gets the extension state. Overridden externally.
65
- * @param extensionId A string identifying which extension's state to retrieve.
65
+ * @param _extensionId A string identifying which extension's state to retrieve.
66
66
  * @returns Whether the extension is ready to be used.
67
67
  */
68
- getExtensionState(extensionId: string): StatusButtonState;
68
+ getExtensionState(_extensionId: string): StatusButtonState;
69
69
  /**
70
70
  * Disposes of this status indicator label.
71
71
  */
@@ -1 +1 @@
1
- {"version":3,"file":"status_indicator_label.d.ts","sourceRoot":"","sources":["../../../src/status_indicator_label.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH;;;;GAIG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,OAAO,CAAC,YAAY;IAC5D;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,YAAY,EAAE,eAAe,CAAA;IAE7B;;OAEG;IACH,cAAc,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAA;IAE1C;;OAEG;IACH,MAAM,CAAC,oBAAoB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAA;IAE1D;;;;;OAKG;gBAED,SAAS,EAAE,OAAO,CAAC,YAAY,EAC/B,eAAe,EAAE,OAAO,CAAC,YAAY,EACrC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS;IAoDvC;;OAEG;IACH,aAAa;IAWb;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM;IAMvB;;;;OAIG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB;IAIzD;;OAEG;IACH,OAAO;CAIR;AAED;;GAEG;AACH,oBAAY,iBAAiB;IAC3B,KAAK,UAAU;IACf,SAAS,cAAc;CACxB"}
1
+ {"version":3,"file":"status_indicator_label.d.ts","sourceRoot":"","sources":["../../../src/status_indicator_label.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH;;;;GAIG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,OAAO,CAAC,YAAY;IAC5D;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,YAAY,EAAE,eAAe,CAAA;IAE7B;;OAEG;IACH,cAAc,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAA;IAE1C;;OAEG;IACH,MAAM,CAAC,oBAAoB,EAAE,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAO;IAE1E;;;;;OAKG;gBAED,SAAS,EAAE,OAAO,CAAC,YAAY,EAC/B,eAAe,EAAE,OAAO,CAAC,YAAY,EACrC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS;IA0DvC;;OAEG;IACH,aAAa;IAWb;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM;IAIvB;;;;OAIG;IACH,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,iBAAiB;IAI1D;;OAEG;IACH,OAAO;CAIR;AAED;;GAEG;AACH,oBAAY,iBAAiB;IAC3B,KAAK,UAAU;IACf,SAAS,cAAc;CACxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"status_indicator_label_flyout_inflater.d.ts","sourceRoot":"","sources":["../../../src/status_indicator_label_flyout_inflater.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,2BAA2B,2BAA2B,CAAA;AAmBnE;;GAEG;AACH,wBAAgB,0CAA0C,SAMzD"}
1
+ {"version":3,"file":"status_indicator_label_flyout_inflater.d.ts","sourceRoot":"","sources":["../../../src/status_indicator_label_flyout_inflater.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,2BAA2B,2BAA2B,CAAA;AAoBnE;;GAEG;AACH,wBAAgB,0CAA0C,SAMzD"}
@@ -49,6 +49,6 @@ export declare function createVariable(workspace: Blockly.WorkspaceSvg, opt_call
49
49
  * name, or null if change is to be aborted (cancel button), or undefined if
50
50
  * an existing variable was chosen.
51
51
  */
52
- export declare function renameVariable(workspace: Blockly.WorkspaceSvg, variable: ScratchVariableModel, opt_callback?: (id?: string) => void): void;
52
+ export declare function renameVariable(workspace: Blockly.Workspace, variable: ScratchVariableModel, opt_callback?: (id?: string) => void): void;
53
53
  export { getVariablesCategory } from './data_category';
54
54
  //# sourceMappingURL=variables.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../../../src/variables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAIvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAS/D,KAAK,UAAU,GAAG,CAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,CACR,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EAAE,EACxB,eAAe,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,KACpD,IAAI,EACT,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,KACb,IAAI,CAAA;AAIT;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,QAEnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,CAAC,YAAY,EAC/B,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,EACpC,QAAQ,CAAC,EAAE,MAAM,QAiElB;AAsID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,CAAC,YAAY,EAC/B,QAAQ,EAAE,oBAAoB,EAC9B,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,QAsDrC;AAED,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../../../src/variables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAIvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAS/D,KAAK,UAAU,GAAG,CAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,CACR,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,EAAE,EACxB,eAAe,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,KACpD,IAAI,EACT,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,KACb,IAAI,CAAA;AAIT;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,QAEnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,CAAC,YAAY,EAC/B,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,EACpC,QAAQ,CAAC,EAAE,MAAM,QAoElB;AAsID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,OAAO,CAAC,SAAS,EAC5B,QAAQ,EAAE,oBAAoB,EAC9B,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,QA0DrC;AAED,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,4 @@
1
+ import * as Blockly from 'blockly/core';
2
+ export declare function getRequiredMainWorkspaceSvg(): Blockly.WorkspaceSvg;
3
+ export declare function getBlockSvgById(workspace: Blockly.WorkspaceSvg, id: string): Blockly.BlockSvg | null;
4
+ //# sourceMappingURL=workspace_block_lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace_block_lookup.d.ts","sourceRoot":"","sources":["../../../src/workspace_block_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC,wBAAgB,2BAA2B,IAAI,OAAO,CAAC,YAAY,CAMlE;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAGpG"}
package/eslint.config.mjs CHANGED
@@ -17,35 +17,28 @@ export default eslintConfigScratch.defineConfig(
17
17
  plugins: { '@typescript-eslint': tseslint.plugin },
18
18
  },
19
19
  {
20
- // TypeScript rule overrides (type-checked rules must be scoped to TS files)
21
- // Fixing these requires non-trivial adjustments to code that was written before these rules were in place.
22
- files: ['src/**/*.{ts,tsx,mts,cts}'],
20
+ // TODO: upstream to eslint-config-scratch
21
+ files: ['**/*.{ts,tsx,mts,cts}'],
23
22
  rules: {
24
- // TODO: improve TypeScript type annotations to remove `any` usage
25
- '@typescript-eslint/no-explicit-any': 'warn',
26
- '@typescript-eslint/no-unsafe-argument': 'warn',
27
- '@typescript-eslint/no-unsafe-assignment': 'warn',
28
- '@typescript-eslint/no-unsafe-call': 'warn',
29
- '@typescript-eslint/no-unsafe-enum-comparison': 'warn',
30
- '@typescript-eslint/no-unsafe-member-access': 'warn',
31
- '@typescript-eslint/no-unsafe-return': 'warn',
32
- '@typescript-eslint/unbound-method': 'warn',
33
-
34
- // TODO: fix incrementally
35
- '@typescript-eslint/no-non-null-assertion': 'warn',
36
- '@typescript-eslint/no-unnecessary-condition': 'warn',
37
- '@typescript-eslint/prefer-nullish-coalescing': 'warn',
38
- '@typescript-eslint/no-base-to-string': 'warn',
39
- '@typescript-eslint/no-empty-function': 'warn',
40
- '@typescript-eslint/no-floating-promises': 'warn',
41
- '@typescript-eslint/no-unnecessary-type-assertion': 'warn',
42
- '@typescript-eslint/no-unused-vars': 'warn',
43
- '@typescript-eslint/only-throw-error': 'warn',
44
- '@typescript-eslint/prefer-optional-chain': 'warn',
45
- '@typescript-eslint/require-await': 'warn',
46
- '@typescript-eslint/restrict-plus-operands': 'warn',
47
- '@typescript-eslint/prefer-for-of': 'warn',
48
- '@typescript-eslint/restrict-template-expressions': 'warn',
23
+ '@typescript-eslint/no-empty-function': 'off',
24
+ '@typescript-eslint/no-unused-vars': [
25
+ 'error',
26
+ {
27
+ argsIgnorePattern: '^_',
28
+ caughtErrorsIgnorePattern: '^_',
29
+ destructuredArrayIgnorePattern: '^_',
30
+ ignoreRestSiblings: true,
31
+ },
32
+ ],
33
+ },
34
+ },
35
+ {
36
+ // Tests frequently introspect internals and invoke prototype methods directly.
37
+ files: ['tests/**/*.{ts,tsx,mts,cts}'],
38
+ rules: {
39
+ '@typescript-eslint/no-explicit-any': 'off',
40
+ '@typescript-eslint/no-unsafe-call': 'off',
41
+ '@typescript-eslint/no-unsafe-member-access': 'off',
49
42
  },
50
43
  },
51
44
  globalIgnores([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scratch-blocks",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
4
4
  "description": "Scratch Blocks is a library for building creative computing interfaces.",
5
5
  "author": "Massachusetts Institute of Technology",
6
6
  "license": "Apache-2.0",
@@ -1,12 +1,12 @@
1
1
  import * as Blockly from 'blockly/core'
2
2
  import { Colours } from './colours'
3
+ import { getBlockSvgById, getRequiredMainWorkspaceSvg } from './workspace_block_lookup'
3
4
 
4
5
  export function reportValue(id: string, value: string) {
5
- const block = (Blockly.getMainWorkspace().getBlockById(id) ||
6
- (Blockly.getMainWorkspace() as Blockly.WorkspaceSvg)
7
- .getFlyout()
8
- ?.getWorkspace()
9
- ?.getBlockById(id)) as Blockly.BlockSvg
6
+ const mainWorkspace = getRequiredMainWorkspaceSvg()
7
+ const flyout = mainWorkspace.getFlyout()
8
+ const flyoutBlock = flyout ? getBlockSvgById(flyout.getWorkspace(), id) : null
9
+ const block = getBlockSvgById(mainWorkspace, id) ?? flyoutBlock
10
10
  if (!block) {
11
11
  throw new Error('Tried to report value on block that does not exist.')
12
12
  }
@@ -24,7 +24,7 @@ Blockly.Blocks.control_forever = {
24
24
  * https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#5eke39
25
25
  */
26
26
  init: function (this: Blockly.Block) {
27
- const ws = this.workspace.options.parentWorkspace || this.workspace
27
+ const ws = this.workspace.options.parentWorkspace ?? this.workspace
28
28
  this.jsonInit({
29
29
  id: 'control_forever',
30
30
  message0: Blockly.Msg.CONTROL_FOREVER,
@@ -58,7 +58,7 @@ Blockly.Blocks.control_repeat = {
58
58
  * https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#so57n9
59
59
  */
60
60
  init: function (this: Blockly.Block) {
61
- const ws = this.workspace.options.parentWorkspace || this.workspace
61
+ const ws = this.workspace.options.parentWorkspace ?? this.workspace
62
62
  this.jsonInit({
63
63
  id: 'control_repeat',
64
64
  message0: Blockly.Msg.CONTROL_REPEAT,
@@ -164,7 +164,7 @@ Blockly.Blocks.control_stop = {
164
164
  const OTHER_SCRIPTS = 'other scripts in sprite'
165
165
  const stopDropdown = new Blockly.FieldDropdown(
166
166
  function () {
167
- if (this.sourceBlock_ && this.sourceBlock_.nextConnection && this.sourceBlock_.nextConnection.isConnected()) {
167
+ if (this.sourceBlock_?.nextConnection?.isConnected()) {
168
168
  return [[Blockly.Msg.CONTROL_STOP_OTHER, OTHER_SCRIPTS]]
169
169
  }
170
170
  return [
@@ -232,7 +232,7 @@ Blockly.Blocks.control_repeat_until = {
232
232
  * Block to repeat until a condition becomes true.
233
233
  */
234
234
  init: function (this: Blockly.Block) {
235
- const ws = this.workspace.options.parentWorkspace || this.workspace
235
+ const ws = this.workspace.options.parentWorkspace ?? this.workspace
236
236
  this.jsonInit({
237
237
  message0: Blockly.Msg.CONTROL_REPEATUNTIL,
238
238
  message1: '%1',
@@ -272,7 +272,7 @@ Blockly.Blocks.control_while = {
272
272
  * (This is an obsolete "hacked" block, for compatibility with 2.0.)
273
273
  */
274
274
  init: function (this: Blockly.Block) {
275
- const ws = this.workspace.options.parentWorkspace || this.workspace
275
+ const ws = this.workspace.options.parentWorkspace ?? this.workspace
276
276
  this.jsonInit({
277
277
  message0: Blockly.Msg.CONTROL_WHILE,
278
278
  message1: '%1',
@@ -615,7 +615,7 @@ const RENAME_OPTION_CALLBACK_FACTORY = function (block: Blockly.Block, fieldName
615
615
  return () => {
616
616
  const workspace = block.workspace
617
617
  const variable = (block.getField(fieldName) as ScratchFieldVariable).getVariable() as ScratchVariableModel
618
- renameVariable(workspace as Blockly.WorkspaceSvg, variable)
618
+ renameVariable(workspace, variable)
619
619
  }
620
620
  }
621
621
 
@@ -64,7 +64,7 @@ Blockly.Blocks.event_whenflagclicked = {
64
64
  * Block for when flag clicked.
65
65
  */
66
66
  init: function (this: Blockly.Block) {
67
- const ws = this.workspace.options.parentWorkspace || this.workspace
67
+ const ws = this.workspace.options.parentWorkspace ?? this.workspace
68
68
  this.jsonInit({
69
69
  id: 'event_whenflagclicked',
70
70
  message0: Blockly.Msg.EVENT_WHENFLAGCLICKED,
@@ -41,7 +41,7 @@ Blockly.Blocks.motion_turnright = {
41
41
  * Block to turn right.
42
42
  */
43
43
  init: function (this: Blockly.Block) {
44
- const ws = this.workspace.options.parentWorkspace || this.workspace
44
+ const ws = this.workspace.options.parentWorkspace ?? this.workspace
45
45
  this.jsonInit({
46
46
  message0: Blockly.Msg.MOTION_TURNRIGHT,
47
47
  args0: [
@@ -66,7 +66,7 @@ Blockly.Blocks.motion_turnleft = {
66
66
  * Block to turn left.
67
67
  */
68
68
  init: function (this: Blockly.Block) {
69
- const ws = this.workspace.options.parentWorkspace || this.workspace
69
+ const ws = this.workspace.options.parentWorkspace ?? this.workspace
70
70
  this.jsonInit({
71
71
  message0: Blockly.Msg.MOTION_TURNLEFT,
72
72
  args0: [
@@ -29,8 +29,8 @@ import type { ScratchDragger } from '../scratch_dragger'
29
29
  type ConnectionMap = Record<
30
30
  string,
31
31
  {
32
- shadow: Element
33
- block: Blockly.BlockSvg
32
+ shadow: Element | undefined
33
+ block: Blockly.BlockSvg | null
34
34
  } | null
35
35
  >
36
36
 
@@ -43,6 +43,86 @@ enum ArgumentType {
43
43
  BOOLEAN = 'b',
44
44
  }
45
45
 
46
+ /**
47
+ * Parse a serialized procedure argument type token.
48
+ * @param value Serialized token from procCode.
49
+ * @returns The matching argument type.
50
+ */
51
+ function parseArgumentType(value: string): ArgumentType {
52
+ switch (value) {
53
+ case 'n':
54
+ return ArgumentType.NUMBER
55
+ case 'b':
56
+ return ArgumentType.BOOLEAN
57
+ case 's':
58
+ return ArgumentType.STRING
59
+ default:
60
+ throw new Error(`Found a custom procedure with an invalid type: ${value}`)
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Read a required mutation attribute or throw if missing.
66
+ * @param xmlElement The mutation element that should contain required attributes.
67
+ * @param name The specific mutation attribute to retrieve.
68
+ * @returns Attribute value.
69
+ */
70
+ function getRequiredMutationAttribute(xmlElement: Element, name: string): string {
71
+ const value = xmlElement.getAttribute(name)
72
+ if (value === null) {
73
+ throw new Error(`Missing required mutation attribute: ${name}`)
74
+ }
75
+ return value
76
+ }
77
+
78
+ /**
79
+ * Parse a required mutation attribute as JSON, then validate its type.
80
+ * @param xmlElement The mutation element that should contain required attributes.
81
+ * @param name The specific mutation attribute to retrieve and parse.
82
+ * @param parse Validates and narrows the parsed JSON value.
83
+ * @returns Parsed and validated mutation attribute value.
84
+ */
85
+ function parseRequiredMutationJson<T>(
86
+ xmlElement: Element,
87
+ name: string,
88
+ parse: (value: unknown, name: string) => T,
89
+ ): T {
90
+ const rawValue = getRequiredMutationAttribute(xmlElement, name)
91
+ let parsedValue: unknown
92
+ try {
93
+ parsedValue = JSON.parse(rawValue)
94
+ } catch {
95
+ throw new Error(`Invalid JSON in mutation attribute: ${name}`)
96
+ }
97
+ return parse(parsedValue, name)
98
+ }
99
+
100
+ /**
101
+ * Validate a parsed mutation value as a boolean.
102
+ * @param value Parsed mutation value.
103
+ * @param name Attribute name used in error messages.
104
+ * @returns Validated boolean value.
105
+ */
106
+ function parseBooleanMutationValue(value: unknown, name: string): boolean {
107
+ if (typeof value !== 'boolean') {
108
+ throw new Error(`Expected boolean JSON value in mutation attribute: ${name}`)
109
+ }
110
+ return value
111
+ }
112
+
113
+ /**
114
+ * Validate a parsed mutation value as a string array.
115
+ * @param value Parsed mutation value.
116
+ * @param name Attribute name used in error messages.
117
+ * @returns Validated string array value.
118
+ */
119
+ function parseStringArrayMutationValue(value: unknown, name: string): string[] {
120
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string')) {
121
+ throw new Error(`Expected string[] JSON value in mutation attribute: ${name}`)
122
+ }
123
+ return value
124
+ }
125
+
46
126
  /**
47
127
  * A drag strategy for the procedures_prototype block that delegates all drag
48
128
  * operations to its parent (the procedures_definition block). This lets the
@@ -193,10 +273,10 @@ function callerMutationToDom(this: ProcedureCallBlock): Element {
193
273
  * @param xmlElement XML storage element.
194
274
  */
195
275
  function callerDomToMutation(this: ProcedureCallBlock, xmlElement: Element) {
196
- this.procCode_ = xmlElement.getAttribute('proccode')!
197
- this.generateShadows_ = JSON.parse(xmlElement.getAttribute('generateshadows')!)
198
- this.argumentIds_ = JSON.parse(xmlElement.getAttribute('argumentids')!)
199
- this.warp_ = JSON.parse(xmlElement.getAttribute('warp')!)
276
+ this.procCode_ = getRequiredMutationAttribute(xmlElement, 'proccode')
277
+ this.generateShadows_ = parseRequiredMutationJson(xmlElement, 'generateshadows', parseBooleanMutationValue)
278
+ this.argumentIds_ = parseRequiredMutationJson(xmlElement, 'argumentids', parseStringArrayMutationValue)
279
+ this.warp_ = parseRequiredMutationJson(xmlElement, 'warp', parseBooleanMutationValue)
200
280
  this.updateDisplay_()
201
281
  }
202
282
 
@@ -230,15 +310,15 @@ function definitionMutationToDom(
230
310
  * @param xmlElement XML storage element.
231
311
  */
232
312
  function definitionDomToMutation(this: ProcedurePrototypeBlock | ProcedureDeclarationBlock, xmlElement: Element) {
233
- this.procCode_ = xmlElement.getAttribute('proccode')!
234
- this.warp_ = JSON.parse(xmlElement.getAttribute('warp')!)
313
+ this.procCode_ = getRequiredMutationAttribute(xmlElement, 'proccode')
314
+ this.warp_ = parseRequiredMutationJson(xmlElement, 'warp', parseBooleanMutationValue)
235
315
 
236
316
  const prevArgIds = this.argumentIds_
237
317
  const prevDisplayNames = this.displayNames_
238
318
 
239
- this.argumentIds_ = JSON.parse(xmlElement.getAttribute('argumentids')!)
240
- this.displayNames_ = JSON.parse(xmlElement.getAttribute('argumentnames')!)
241
- this.argumentDefaults_ = JSON.parse(xmlElement.getAttribute('argumentdefaults')!)
319
+ this.argumentIds_ = parseRequiredMutationJson(xmlElement, 'argumentids', parseStringArrayMutationValue)
320
+ this.displayNames_ = parseRequiredMutationJson(xmlElement, 'argumentnames', parseStringArrayMutationValue)
321
+ this.argumentDefaults_ = parseRequiredMutationJson(xmlElement, 'argumentdefaults', parseStringArrayMutationValue)
242
322
 
243
323
  // During full XML deserialization (Blockly.Xml.domToWorkspace), the mutation element
244
324
  // is part of the parsed XML tree and its parent element also contains <value> children
@@ -308,13 +388,11 @@ function disconnectOldBlocks_(this: ProcedureBlock): ConnectionMap {
308
388
  const connectionMap: ConnectionMap = {}
309
389
  for (const input of this.inputList) {
310
390
  if (input.connection) {
311
- const target = input.connection.targetBlock() as Blockly.BlockSvg
312
- const saveInfo = {
313
- shadow: input.connection.getShadowDom(true)!,
314
- block: target,
391
+ const target = input.connection.targetBlock()
392
+ connectionMap[input.name] = {
393
+ shadow: input.connection.getShadowDom(true) ?? undefined,
394
+ block: target as Blockly.BlockSvg | null,
315
395
  }
316
- connectionMap[input.name] = saveInfo
317
-
318
396
  if (target) {
319
397
  input.connection.disconnect()
320
398
  }
@@ -349,16 +427,7 @@ function createAllInputs_(this: ProcedureBlock, connectionMap: ConnectionMap) {
349
427
  for (const component of procComponents) {
350
428
  let labelText
351
429
  if (component.startsWith('%')) {
352
- const argumentType = component.substring(1, 2)
353
- if (
354
- !(
355
- argumentType === ArgumentType.NUMBER ||
356
- argumentType === ArgumentType.BOOLEAN ||
357
- argumentType === ArgumentType.STRING
358
- )
359
- ) {
360
- throw new Error('Found an custom procedure with an invalid type: ' + argumentType)
361
- }
430
+ const argumentType = parseArgumentType(component.substring(1, 2))
362
431
  labelText = component.substring(2).trim()
363
432
 
364
433
  const id = this.argumentIds_[argumentCount]
@@ -386,19 +455,20 @@ function createAllInputs_(this: ProcedureBlock, connectionMap: ConnectionMap) {
386
455
  * connected to those IDs at the beginning of the mutation.
387
456
  */
388
457
  function disposeObsoleteBlocks_(this: ProcedureBlock, connectionMap: ConnectionMap) {
389
- for (const id in connectionMap) {
390
- const saveInfo = connectionMap[id]
391
- if (saveInfo) {
392
- const block = saveInfo.block
393
- const isOrphanedArgumentReporter =
394
- this.type === 'procedures_prototype' &&
395
- (block.type === 'argument_reporter_string_number' || block.type === 'argument_reporter_boolean')
396
- if (block.isShadow() || isOrphanedArgumentReporter) {
397
- block.dispose()
398
- connectionMap[id] = null
399
- // At this point we know which shadow DOMs are about to be orphaned in
400
- // the VM. What do we do with that information?
401
- }
458
+ for (const [id, saveInfo] of Object.entries(connectionMap)) {
459
+ const block = saveInfo?.block
460
+ if (!block) {
461
+ continue
462
+ }
463
+
464
+ const isOrphanedArgumentReporter =
465
+ this.type === 'procedures_prototype' &&
466
+ (block.type === 'argument_reporter_string_number' || block.type === 'argument_reporter_boolean')
467
+ if (block.isShadow() || isOrphanedArgumentReporter) {
468
+ block.dispose()
469
+ connectionMap[id] = null
470
+ // At this point we know which shadow DOMs are about to be orphaned in
471
+ // the VM. What do we do with that information?
402
472
  }
403
473
  }
404
474
  }
@@ -458,6 +528,9 @@ function buildShadowDom_(type: ArgumentType): Element {
458
528
  */
459
529
  function attachShadow_(this: ProcedureCallBlock, input: Blockly.Input, argumentType: ArgumentType) {
460
530
  if (argumentType === ArgumentType.NUMBER || argumentType === ArgumentType.STRING) {
531
+ if (!input.connection) {
532
+ throw new Error(`Expected input connection for argument ${String(argumentType)}`)
533
+ }
461
534
  const blockType = argumentType === ArgumentType.NUMBER ? 'math_number' : 'text'
462
535
  Blockly.Events.disable()
463
536
  let newBlock
@@ -479,7 +552,7 @@ function attachShadow_(this: ProcedureCallBlock, input: Blockly.Input, argumentT
479
552
  if (Blockly.Events.isEnabled()) {
480
553
  Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
481
554
  }
482
- newBlock.outputConnection.connect(input.connection!)
555
+ newBlock.outputConnection.connect(input.connection)
483
556
  }
484
557
  }
485
558
 
@@ -516,7 +589,7 @@ function createArgumentReporter_(
516
589
  Blockly.Events.enable()
517
590
  }
518
591
  if (Blockly.Events.isEnabled()) {
519
- Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
592
+ void Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
520
593
  }
521
594
  return newBlock
522
595
  }
@@ -538,21 +611,25 @@ function populateArgumentOnCaller_(
538
611
  id: string,
539
612
  input: Blockly.Input,
540
613
  ) {
541
- let oldBlock: Blockly.BlockSvg | undefined
614
+ let oldBlock: Blockly.BlockSvg | null | undefined
542
615
  let oldShadow: Element | undefined
543
- if (connectionMap && id in connectionMap) {
616
+ if (id in connectionMap) {
544
617
  const saveInfo = connectionMap[id]
545
618
  oldBlock = saveInfo?.block
546
619
  oldShadow = saveInfo?.shadow
547
620
  }
548
621
 
549
- if (connectionMap && oldBlock) {
622
+ const conn = input.connection
623
+ if (!conn) {
624
+ throw new Error(`Expected caller input connection for argument id ${id}`)
625
+ }
626
+ if (oldBlock) {
550
627
  // Reattach the old block and shadow DOM.
551
628
  connectionMap[input.name] = null
552
- oldBlock.outputConnection.connect(input.connection!)
629
+ oldBlock.outputConnection.connect(conn)
553
630
  if (type !== ArgumentType.BOOLEAN && this.generateShadows_) {
554
- const shadowDom = oldShadow || this.buildShadowDom_(type)
555
- input.connection!.setShadowDom(shadowDom)
631
+ const shadowDom = oldShadow ?? this.buildShadowDom_(type)
632
+ conn.setShadowDom(shadowDom)
556
633
  }
557
634
  } else if (this.generateShadows_) {
558
635
  this.attachShadow_(input, type)
@@ -584,7 +661,12 @@ function populateArgumentOnPrototype_(
584
661
  }
585
662
 
586
663
  let oldBlock: Blockly.BlockSvg | null = null
587
- if (connectionMap && id in connectionMap) {
664
+ const conn = input.connection
665
+ if (!conn) {
666
+ throw new Error(`Expected prototype input connection for argument id ${id}`)
667
+ }
668
+
669
+ if (id in connectionMap) {
588
670
  const saveInfo = connectionMap[id]
589
671
  oldBlock = saveInfo?.block ?? null
590
672
  }
@@ -594,7 +676,7 @@ function populateArgumentOnPrototype_(
594
676
 
595
677
  // Decide which block to attach.
596
678
  let argumentReporter: Blockly.BlockSvg
597
- if (connectionMap && oldBlock && oldTypeMatches) {
679
+ if (oldBlock && oldTypeMatches) {
598
680
  // Update the text if needed. The old argument reporter is the same type,
599
681
  // and on the same input, but the argument's display name may have changed.
600
682
  argumentReporter = oldBlock
@@ -605,7 +687,7 @@ function populateArgumentOnPrototype_(
605
687
  }
606
688
 
607
689
  // Attach the block.
608
- input.connection!.connect(argumentReporter.outputConnection)
690
+ conn.connect(argumentReporter.outputConnection)
609
691
  }
610
692
 
611
693
  /**
@@ -627,13 +709,17 @@ function populateArgumentOnDeclaration_(
627
709
  input: Blockly.Input,
628
710
  ) {
629
711
  let oldBlock: Blockly.BlockSvg | null = null
630
- if (connectionMap && id in connectionMap) {
712
+ if (id in connectionMap) {
631
713
  const saveInfo = connectionMap[id]
632
714
  oldBlock = saveInfo?.block ?? null
633
715
  }
634
716
 
635
717
  const oldTypeMatches = checkOldEditorTypeMatches_(oldBlock, type)
636
718
  const displayName = this.displayNames_[index]
719
+ const conn = input.connection
720
+ if (!conn) {
721
+ throw new Error(`Expected declaration input connection for argument id ${id}`)
722
+ }
637
723
 
638
724
  // Decide which block to attach.
639
725
  let argumentEditor: Blockly.BlockSvg
@@ -646,7 +732,7 @@ function populateArgumentOnDeclaration_(
646
732
  }
647
733
 
648
734
  // Attach the block.
649
- input.connection!.connect(argumentEditor.outputConnection)
735
+ conn.connect(argumentEditor.outputConnection)
650
736
  }
651
737
 
652
738
  /**
@@ -721,13 +807,13 @@ function createArgumentEditor_(
721
807
  newBlock.setShadow(true)
722
808
  if (!this.isInsertionMarker()) {
723
809
  newBlock.initSvg()
724
- newBlock.queueRender()
810
+ void newBlock.queueRender()
725
811
  }
726
812
  } finally {
727
813
  Blockly.Events.enable()
728
814
  }
729
815
  if (Blockly.Events.isEnabled()) {
730
- Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
816
+ void Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock))
731
817
  }
732
818
  return newBlock
733
819
  }
@@ -749,8 +835,11 @@ function updateDeclarationProcCode_(this: ProcedureDeclarationBlock) {
749
835
  this.procCode_ += input.fieldRow[0].getValue()
750
836
  } else if (input.type === Blockly.inputs.inputTypes.VALUE) {
751
837
  // Inspect the argument editor.
752
- const target = input.connection!.targetBlock()!
753
- this.displayNames_.push(target.getFieldValue('TEXT'))
838
+ const target = input.connection?.targetBlock()
839
+ if (!target) {
840
+ throw new Error(`Expected argument editor block on input ${input.name}`)
841
+ }
842
+ this.displayNames_.push(String(target.getFieldValue('TEXT')))
754
843
  this.argumentIds_.push(input.name)
755
844
  if (target.type === 'argument_editor_boolean') {
756
845
  this.procCode_ += '%b'
@@ -758,7 +847,7 @@ function updateDeclarationProcCode_(this: ProcedureDeclarationBlock) {
758
847
  this.procCode_ += '%s'
759
848
  }
760
849
  } else {
761
- throw new Error('Unexpected input type on a procedure mutator root: ' + input.type)
850
+ throw new Error(`Unexpected input type on a procedure mutator root: ${String(input.type)}`)
762
851
  }
763
852
  }
764
853
  }
@@ -773,8 +862,15 @@ function focusLastEditor_(this: ProcedureDeclarationBlock) {
773
862
  newInput.fieldRow[0].showEditor()
774
863
  } else if (newInput.type === Blockly.inputs.inputTypes.VALUE) {
775
864
  // Inspect the argument editor.
776
- const target = newInput.connection!.targetBlock()!
777
- target.getField('TEXT')!.showEditor()
865
+ const target = newInput.connection?.targetBlock()
866
+ if (!target) {
867
+ throw new Error(`Expected argument editor block on input ${newInput.name}`)
868
+ }
869
+ const field = target.getField('TEXT')
870
+ if (!field) {
871
+ throw new Error(`Expected TEXT field on argument editor block ${target.id}`)
872
+ }
873
+ field.showEditor()
778
874
  }
779
875
  }
780
876
  }
@@ -843,16 +939,15 @@ function removeFieldCallback(this: ProcedureDeclarationBlock, field: Blockly.Fie
843
939
  return
844
940
  }
845
941
  let inputNameToRemove = null
846
- for (let n = 0; n < this.inputList.length; n++) {
847
- const input = this.inputList[n]
942
+ for (const input of this.inputList) {
848
943
  if (input.connection) {
849
- const target = input.connection.targetBlock()!
850
- if (field.name && target.getField(field.name) === field) {
944
+ const target = input.connection.targetBlock()
945
+ if (target && field.name && target.getField(field.name) === field) {
851
946
  inputNameToRemove = input.name
852
947
  }
853
948
  } else {
854
- for (let j = 0; j < input.fieldRow.length; j++) {
855
- if (input.fieldRow[j] === field) {
949
+ for (const inputField of input.fieldRow) {
950
+ if (inputField === field) {
856
951
  inputNameToRemove = input.name
857
952
  }
858
953
  }
@@ -875,9 +970,7 @@ function removeArgumentCallback_(
875
970
  field: Blockly.Field,
876
971
  ) {
877
972
  const parent = this.getParent()
878
- if (parent && parent.removeFieldCallback) {
879
- parent.removeFieldCallback(field)
880
- }
973
+ ;(parent as ProcedureDeclarationBlock | null)?.removeFieldCallback(field)
881
974
  }
882
975
 
883
976
  /**
@@ -17,7 +17,6 @@
17
17
  * limitations under the License.
18
18
  */
19
19
  import * as Blockly from 'blockly/core'
20
- import * as Constants from '../constants'
21
20
 
22
21
  Blockly.Blocks.sensing_touchingobject = {
23
22
  /**