scratch-blocks 2.1.12 → 2.1.14
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.
- package/dist/main.mjs +1 -1
- package/dist/types/src/blocks/procedures.d.ts +2 -1
- package/dist/types/src/blocks/procedures.d.ts.map +1 -1
- package/dist/types/src/fields/scratch_field_dropdown.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/blocks/procedures.ts +42 -38
- package/src/fields/scratch_field_dropdown.ts +44 -0
|
@@ -30,6 +30,7 @@ type ConnectionMap = Record<string, {
|
|
|
30
30
|
/**
|
|
31
31
|
* Possible types for procedure arguments.
|
|
32
32
|
*/
|
|
33
|
+
type ArgumentDefault = string | number | boolean | null;
|
|
33
34
|
declare enum ArgumentType {
|
|
34
35
|
STRING = "s",
|
|
35
36
|
NUMBER = "n",
|
|
@@ -50,7 +51,7 @@ interface ProcedureBlock extends Blockly.BlockSvg {
|
|
|
50
51
|
}
|
|
51
52
|
export interface ProcedureDeclarationBlock extends ProcedureBlock {
|
|
52
53
|
displayNames_: string[];
|
|
53
|
-
argumentDefaults_:
|
|
54
|
+
argumentDefaults_: ArgumentDefault[];
|
|
54
55
|
removeFieldCallback: (field: Blockly.Field) => void;
|
|
55
56
|
createArgumentEditor_: (argumentType: ArgumentType, displayName: string) => Blockly.BlockSvg;
|
|
56
57
|
focusLastEditor_: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../../../../src/blocks/procedures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH;;GAEG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAIvC;;GAEG;AACH,KAAK,aAAa,GAAG,MAAM,CACzB,MAAM,EACN;IACE,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAA;CAC/B,GAAG,IAAI,CACT,CAAA;AAED;;GAEG;AACH,aAAK,YAAY;IACf,MAAM,MAAM;IACZ,MAAM,MAAM;IACZ,OAAO,MAAM;CACd;
|
|
1
|
+
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../../../../src/blocks/procedures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH;;GAEG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAIvC;;GAEG;AACH,KAAK,aAAa,GAAG,MAAM,CACzB,MAAM,EACN;IACE,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAA;CAC/B,GAAG,IAAI,CACT,CAAA;AAED;;GAEG;AACH,KAAK,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;AAEvD,aAAK,YAAY;IACf,MAAM,MAAM;IACZ,MAAM,MAAM;IACZ,OAAO,MAAM;CACd;AAgwCD,UAAU,cAAe,SAAQ,OAAO,CAAC,QAAQ;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B,oBAAoB,EAAE,MAAM,aAAa,CAAA;IACzC,sBAAsB,EAAE,CAAC,aAAa,EAAE,aAAa,KAAK,IAAI,CAAA;IAC9D,gBAAgB,EAAE,CAAC,aAAa,EAAE,aAAa,KAAK,IAAI,CAAA;IACxD,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,iBAAiB,EAAE,CACjB,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,aAAa,EAC5B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,CAAC,KAAK,KACjB,IAAI,CAAA;IACT,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC3C;AAED,MAAM,WAAW,yBAA0B,SAAQ,cAAc;IAC/D,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,iBAAiB,EAAE,eAAe,EAAE,CAAA;IACpC,mBAAmB,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,KAAK,IAAI,CAAA;IACnD,qBAAqB,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAA;IAC5F,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B,OAAO,EAAE,MAAM,OAAO,CAAA;IACtB,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAChC,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B,kBAAkB,EAAE,MAAM,IAAI,CAAA;IAC9B,uBAAuB,EAAE,MAAM,IAAI,CAAA;IACnC,UAAU,EAAE,MAAM,IAAI,CAAA;CACvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scratch_field_dropdown.d.ts","sourceRoot":"","sources":["../../../../src/fields/scratch_field_dropdown.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scratch_field_dropdown.d.ts","sourceRoot":"","sources":["../../../../src/fields/scratch_field_dropdown.ts"],"names":[],"mappings":"AAiFA;;GAEG;AACH,wBAAgB,4BAA4B,SAG3C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scratch-blocks",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.14",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@vitest/browser": "4.1.4",
|
|
42
42
|
"@vitest/browser-playwright": "4.1.4",
|
|
43
43
|
"eslint": "9.39.4",
|
|
44
|
-
"eslint-config-scratch": "14.1.
|
|
44
|
+
"eslint-config-scratch": "14.1.8",
|
|
45
45
|
"husky": "9.1.7",
|
|
46
46
|
"playwright": "1.59.1",
|
|
47
47
|
"prettier": "3.8.2",
|
package/src/blocks/procedures.ts
CHANGED
|
@@ -37,6 +37,8 @@ type ConnectionMap = Record<
|
|
|
37
37
|
/**
|
|
38
38
|
* Possible types for procedure arguments.
|
|
39
39
|
*/
|
|
40
|
+
type ArgumentDefault = string | number | boolean | null
|
|
41
|
+
|
|
40
42
|
enum ArgumentType {
|
|
41
43
|
STRING = 's',
|
|
42
44
|
NUMBER = 'n',
|
|
@@ -75,39 +77,17 @@ function getRequiredMutationAttribute(xmlElement: Element, name: string): string
|
|
|
75
77
|
return value
|
|
76
78
|
}
|
|
77
79
|
|
|
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
80
|
/**
|
|
101
81
|
* Parse an optional mutation attribute as JSON, returning a fallback when the
|
|
102
|
-
* attribute is absent
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
82
|
+
* attribute is absent or malformed. Older saved projects may omit attributes
|
|
83
|
+
* that current serialization always writes, and some projects in the wild
|
|
84
|
+
* contain attributes whose values are not valid for the expected type.
|
|
85
|
+
* Returning the fallback in both cases lets the project load rather than
|
|
86
|
+
* aborting the entire workspace parse.
|
|
107
87
|
* @param xmlElement The mutation element that may contain the attribute.
|
|
108
88
|
* @param name The specific mutation attribute to retrieve and parse.
|
|
109
89
|
* @param parse Validates and narrows the parsed JSON value.
|
|
110
|
-
* @param fallback Value to return when the attribute is absent.
|
|
90
|
+
* @param fallback Value to return when the attribute is absent or malformed.
|
|
111
91
|
* @returns Parsed and validated mutation attribute value, or `fallback`.
|
|
112
92
|
*/
|
|
113
93
|
function parseOptionalMutationJson<T>(
|
|
@@ -120,13 +100,12 @@ function parseOptionalMutationJson<T>(
|
|
|
120
100
|
if (rawValue === null) {
|
|
121
101
|
return fallback
|
|
122
102
|
}
|
|
123
|
-
let parsedValue: unknown
|
|
124
103
|
try {
|
|
125
|
-
|
|
104
|
+
return parse(JSON.parse(rawValue), name)
|
|
126
105
|
} catch {
|
|
127
|
-
|
|
106
|
+
console.warn(`Invalid or unexpected mutation attribute "${name}", using default. Raw value: ${rawValue}`)
|
|
107
|
+
return fallback
|
|
128
108
|
}
|
|
129
|
-
return parse(parsedValue, name)
|
|
130
109
|
}
|
|
131
110
|
|
|
132
111
|
/**
|
|
@@ -155,6 +134,26 @@ function parseStringArrayMutationValue(value: unknown, name: string): string[] {
|
|
|
155
134
|
return value
|
|
156
135
|
}
|
|
157
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Validate a parsed mutation value as an array of primitives. Argument
|
|
139
|
+
* defaults from older projects (especially Scratch 2.0 conversions) contain
|
|
140
|
+
* mixed types — strings for text arguments, numbers for numeric arguments,
|
|
141
|
+
* and booleans for boolean arguments (e.g. `["",1,1,1,1]`). The old fork
|
|
142
|
+
* stored these via untyped `JSON.parse` with no validation, and the VM
|
|
143
|
+
* passes them through as-is.
|
|
144
|
+
* @param value Parsed mutation value.
|
|
145
|
+
* @param name Attribute name used in error messages.
|
|
146
|
+
* @returns Validated array value.
|
|
147
|
+
*/
|
|
148
|
+
function parsePrimitiveArrayMutationValue(value: unknown, name: string): ArgumentDefault[] {
|
|
149
|
+
const isArgumentDefault = (entry: unknown): entry is ArgumentDefault =>
|
|
150
|
+
entry === null || typeof entry === 'string' || typeof entry === 'number' || typeof entry === 'boolean'
|
|
151
|
+
if (!Array.isArray(value) || !value.every(isArgumentDefault)) {
|
|
152
|
+
throw new Error(`Expected ArgumentDefault[] JSON value in mutation attribute: ${name}`)
|
|
153
|
+
}
|
|
154
|
+
return value
|
|
155
|
+
}
|
|
156
|
+
|
|
158
157
|
/**
|
|
159
158
|
* A drag strategy for the procedures_prototype block that delegates all drag
|
|
160
159
|
* operations to its parent (the procedures_definition block). This lets the
|
|
@@ -308,7 +307,7 @@ function callerDomToMutation(this: ProcedureCallBlock, xmlElement: Element) {
|
|
|
308
307
|
this.procCode_ = getRequiredMutationAttribute(xmlElement, 'proccode')
|
|
309
308
|
const generateshadows = xmlElement.getAttribute('generateshadows')
|
|
310
309
|
this.generateShadows_ = generateshadows !== null ? JSON.parse(generateshadows) === true : false
|
|
311
|
-
this.argumentIds_ =
|
|
310
|
+
this.argumentIds_ = parseOptionalMutationJson(xmlElement, 'argumentids', parseStringArrayMutationValue, [])
|
|
312
311
|
this.warp_ = parseOptionalMutationJson(xmlElement, 'warp', parseBooleanMutationValue, false)
|
|
313
312
|
this.updateDisplay_()
|
|
314
313
|
}
|
|
@@ -349,9 +348,14 @@ function definitionDomToMutation(this: ProcedurePrototypeBlock | ProcedureDeclar
|
|
|
349
348
|
const prevArgIds = this.argumentIds_
|
|
350
349
|
const prevDisplayNames = this.displayNames_
|
|
351
350
|
|
|
352
|
-
this.argumentIds_ =
|
|
353
|
-
this.displayNames_ =
|
|
354
|
-
this.argumentDefaults_ =
|
|
351
|
+
this.argumentIds_ = parseOptionalMutationJson(xmlElement, 'argumentids', parseStringArrayMutationValue, [])
|
|
352
|
+
this.displayNames_ = parseOptionalMutationJson(xmlElement, 'argumentnames', parseStringArrayMutationValue, [])
|
|
353
|
+
this.argumentDefaults_ = parseOptionalMutationJson(
|
|
354
|
+
xmlElement,
|
|
355
|
+
'argumentdefaults',
|
|
356
|
+
parsePrimitiveArrayMutationValue,
|
|
357
|
+
[],
|
|
358
|
+
)
|
|
355
359
|
|
|
356
360
|
// During full XML deserialization (Blockly.Xml.domToWorkspace), the mutation element
|
|
357
361
|
// is part of the parsed XML tree and its parent element also contains <value> children
|
|
@@ -1341,7 +1345,7 @@ interface ProcedureBlock extends Blockly.BlockSvg {
|
|
|
1341
1345
|
|
|
1342
1346
|
export interface ProcedureDeclarationBlock extends ProcedureBlock {
|
|
1343
1347
|
displayNames_: string[]
|
|
1344
|
-
argumentDefaults_:
|
|
1348
|
+
argumentDefaults_: ArgumentDefault[]
|
|
1345
1349
|
removeFieldCallback: (field: Blockly.Field) => void
|
|
1346
1350
|
createArgumentEditor_: (argumentType: ArgumentType, displayName: string) => Blockly.BlockSvg
|
|
1347
1351
|
focusLastEditor_: () => void
|
|
@@ -1361,7 +1365,7 @@ interface ProcedureCallBlock extends ProcedureBlock {
|
|
|
1361
1365
|
|
|
1362
1366
|
interface ProcedurePrototypeBlock extends ProcedureBlock {
|
|
1363
1367
|
displayNames_: string[]
|
|
1364
|
-
argumentDefaults_:
|
|
1368
|
+
argumentDefaults_: ArgumentDefault[]
|
|
1365
1369
|
skipArgumentReporters_: boolean
|
|
1366
1370
|
createArgumentReporter_: (argumentType: ArgumentType, displayName: string) => Blockly.BlockSvg
|
|
1367
1371
|
updateArgumentReporterNames_: (prevArgIds: string[], prevDisplayNames: string[]) => void
|
|
@@ -7,6 +7,50 @@ import * as Blockly from 'blockly/core'
|
|
|
7
7
|
class ScratchFieldDropdown extends Blockly.FieldDropdown {
|
|
8
8
|
private originalStyle!: string
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Accept string values even when they are not in the current options list.
|
|
12
|
+
* Scratch populates some dropdowns from dynamic data (e.g. the sprite list
|
|
13
|
+
* for motion_goto_menu, motion_pointtowards_menu, sensing_touchingobject_menu)
|
|
14
|
+
* and deliberately excludes the currently-editing sprite. A block moved into
|
|
15
|
+
* the sprite it now targets carries a stored value that is therefore absent
|
|
16
|
+
* from the option list for this editing context, but is still valid — the
|
|
17
|
+
* runtime reads the stored value directly. Preserve that value so the UI
|
|
18
|
+
* shows what the block actually does, rather than silently reverting to the
|
|
19
|
+
* first option.
|
|
20
|
+
* @param newValue The value to validate.
|
|
21
|
+
* @returns The value unchanged if it is a string; otherwise null to reject
|
|
22
|
+
* the update.
|
|
23
|
+
*/
|
|
24
|
+
protected override doClassValidation_(newValue?: string): string | null {
|
|
25
|
+
if (typeof newValue !== 'string') {
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
return newValue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* When the stored value is not in the current options list, display the raw
|
|
33
|
+
* value. Blockly's base implementation reads from the last-matched option,
|
|
34
|
+
* which stays stale when the incoming value does not appear in the list, so
|
|
35
|
+
* the default would otherwise render as the first option's text.
|
|
36
|
+
* @returns Text to display for the currently-selected value.
|
|
37
|
+
*/
|
|
38
|
+
protected override getText_(): string | null {
|
|
39
|
+
const value = this.getValue()
|
|
40
|
+
if (value === null) {
|
|
41
|
+
return super.getText_()
|
|
42
|
+
}
|
|
43
|
+
for (const option of this.getOptions(true)) {
|
|
44
|
+
if (option === 'separator') continue
|
|
45
|
+
if (option[1] === value) {
|
|
46
|
+
// Delegate to the base for the matched case so image/HTMLElement
|
|
47
|
+
// option types render correctly.
|
|
48
|
+
return super.getText_()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
|
|
10
54
|
showEditor_(event: PointerEvent) {
|
|
11
55
|
super.showEditor_(event)
|
|
12
56
|
const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg
|