diff --git a/packages/blockly/core/block.ts b/packages/blockly/core/block.ts index 4c8f80842..b74e32102 100644 --- a/packages/blockly/core/block.ts +++ b/packages/blockly/core/block.ts @@ -1721,8 +1721,8 @@ export class Block { // Validate that each arg has a corresponding message let n = 0; - while (json['args' + n]) { - if (json['message' + n] === undefined) { + while (json[`args${n}`]) { + if (json[`message${n}`] === undefined) { throw Error( warningPrefix + `args${n} must have a corresponding message (message${n}).`, @@ -1732,14 +1732,13 @@ export class Block { } // Set basic properties of block. - // Makes styles backward compatible with old way of defining hat style. - if (json['style'] && json['style'].hat) { - this.hat = json['style'].hat; + // Handle legacy style object format for backwards compatibility + if (json['style'] && typeof json['style'] === 'object') { + this.hat = (json['style'] as {hat?: string}).hat; // Must set to null so it doesn't error when checking for style and // colour. json['style'] = null; } - if (json['style'] && json['colour']) { throw Error(warningPrefix + 'Must not have both a colour and a style.'); } else if (json['style']) { @@ -1750,12 +1749,12 @@ export class Block { // Interpolate the message blocks. let i = 0; - while (json['message' + i] !== undefined) { + while (json[`message${i}`] !== undefined) { this.interpolate( - json['message' + i], - json['args' + i] || [], + json[`message${i}`] || '', + json[`args${i}`] || [], // Backwards compatibility: lastDummyAlign aliases implicitAlign. - json['implicitAlign' + i] || json['lastDummyAlign' + i], + json[`implicitAlign${i}`] || (json as any)[`lastDummyAlign${i}`], warningPrefix, ); i++; diff --git a/packages/blockly/core/interfaces/i_json_block_definition.ts b/packages/blockly/core/interfaces/i_json_block_definition.ts new file mode 100644 index 000000000..0b3d57676 --- /dev/null +++ b/packages/blockly/core/interfaces/i_json_block_definition.ts @@ -0,0 +1,126 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {FieldCheckboxFromJsonConfig} from '../field_checkbox.js'; +import {FieldDropdownFromJsonConfig} from '../field_dropdown'; +import {FieldImageFromJsonConfig} from '../field_image'; +import {FieldNumberFromJsonConfig} from '../field_number'; +import {FieldTextInputFromJsonConfig} from '../field_textinput'; +import {FieldVariableFromJsonConfig} from '../field_variable'; +import {Align} from '../inputs/align.js'; + +/** + * Defines the JSON structure for a block definition. + * + * @example + * ```typescript + * const blockDef: JsonBlockDefinition = { + * type: 'custom_block', + * message0: 'move %1 steps', + * args0: [ + * { + * 'type': 'field_number', + * 'name': 'INPUT', + * }, + * ], + * previousStatement: null, + * nextStatement: null, + * }; + * ``` + */ +export interface JsonBlockDefinition { + type: string; + style?: string | null; + colour?: string | number; + output?: string | string[] | null; + previousStatement?: string | string[] | null; + nextStatement?: string | string[] | null; + outputShape?: number; + inputsInline?: boolean; + tooltip?: string; + helpUrl?: string; + extensions?: string[]; + mutator?: string; + enableContextMenu?: boolean; + suppressPrefixSuffix?: boolean; + + [key: `message${number}`]: string | undefined; + [key: `args${number}`]: JsonBlockArg[] | undefined; + [key: `implicitAlign${number}`]: string | undefined; +} + +export type JsonBlockArg = + | InputValueArg + | InputStatementArg + | InputDummyArg + | InputEndRowArg + | FieldInputArg + | FieldNumberArg + | FieldDropdownArg + | FieldCheckboxArg + | FieldImageArg + | FieldVariableArg + | UnknownArg; + +interface UnknownArg { + type: string; + [key: string]: unknown; +} + +/** Input args */ +interface InputValueArg { + type: 'input_value'; + name?: string; + check?: string | string[]; + align?: Align; +} + +interface InputStatementArg { + type: 'input_statement'; + name?: string; + check?: string | string[]; +} + +interface InputDummyArg { + type: 'input_dummy'; + name?: string; +} + +interface InputEndRowArg { + type: 'input_end_row'; + name?: string; +} + +/** Field args */ +interface FieldInputArg extends FieldTextInputFromJsonConfig { + type: 'field_input'; + name?: string; +} + +interface FieldNumberArg extends FieldNumberFromJsonConfig { + type: 'field_number'; + name?: string; +} + +interface FieldDropdownArg extends FieldDropdownFromJsonConfig { + type: 'field_dropdown'; + name?: string; +} + +interface FieldCheckboxArg extends FieldCheckboxFromJsonConfig { + type: 'field_checkbox'; + name?: string; +} + +interface FieldImageArg extends FieldImageFromJsonConfig { + type: 'field_image'; + name?: string; +} + +interface FieldVariableArg extends FieldVariableFromJsonConfig { + type: 'field_variable'; + name?: string; +} diff --git a/packages/blockly/tests/typescript/src/field/json_block_custom_args.ts b/packages/blockly/tests/typescript/src/field/json_block_custom_args.ts new file mode 100644 index 000000000..9cafdbdef --- /dev/null +++ b/packages/blockly/tests/typescript/src/field/json_block_custom_args.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {defineBlocksWithJsonArray} from 'blockly-test/core'; +import type {JsonBlockDefinition} from 'blockly-test/core/interfaces/i_json_block_definition'; + +import './different_user_input'; + +const mitosisBlockDefinition: JsonBlockDefinition = { + type: 'mitosis_block', + message0: 'split cell %1', + args0: [ + { + type: 'field_mitosis', + name: 'CELL', + cellId: 'cell-A', + }, + ], + previousStatement: null, + nextStatement: null, +}; + +defineBlocksWithJsonArray([mitosisBlockDefinition]);