Skip to content

Commit 74802bc

Browse files
authored
Merge pull request #8100 from maribethb/merge-v11
release: Merge branch 'develop' into rc/v11.0.0
2 parents 59584c3 + 0255c58 commit 74802bc

25 files changed

Lines changed: 298 additions & 84 deletions

core/field.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,14 +1086,22 @@ export abstract class Field<T = any>
10861086
}
10871087

10881088
const classValidation = this.doClassValidation_(newValue);
1089-
const classValue = this.processValidation_(newValue, classValidation);
1089+
const classValue = this.processValidation_(
1090+
newValue,
1091+
classValidation,
1092+
fireChangeEvent,
1093+
);
10901094
if (classValue instanceof Error) {
10911095
doLogging && console.log('invalid class validation, return');
10921096
return;
10931097
}
10941098

10951099
const localValidation = this.getValidator()?.call(this, classValue);
1096-
const localValue = this.processValidation_(classValue, localValidation);
1100+
const localValue = this.processValidation_(
1101+
classValue,
1102+
localValidation,
1103+
fireChangeEvent,
1104+
);
10971105
if (localValue instanceof Error) {
10981106
doLogging && console.log('invalid local validation, return');
10991107
return;
@@ -1135,14 +1143,16 @@ export abstract class Field<T = any>
11351143
*
11361144
* @param newValue New value.
11371145
* @param validatedValue Validated value.
1146+
* @param fireChangeEvent Whether to fire a change event if the value changes.
11381147
* @returns New value, or an Error object.
11391148
*/
11401149
private processValidation_(
11411150
newValue: AnyDuringMigration,
11421151
validatedValue: T | null | undefined,
1152+
fireChangeEvent: boolean,
11431153
): T | Error {
11441154
if (validatedValue === null) {
1145-
this.doValueInvalid_(newValue);
1155+
this.doValueInvalid_(newValue, fireChangeEvent);
11461156
if (this.isDirty_) {
11471157
this.forceRerender();
11481158
}
@@ -1209,8 +1219,12 @@ export abstract class Field<T = any>
12091219
* No-op by default.
12101220
*
12111221
* @param _invalidValue The input value that was determined to be invalid.
1222+
* @param _fireChangeEvent Whether to fire a change event if the value changes.
12121223
*/
1213-
protected doValueInvalid_(_invalidValue: AnyDuringMigration) {}
1224+
protected doValueInvalid_(
1225+
_invalidValue: AnyDuringMigration,
1226+
_fireChangeEvent: boolean = true,
1227+
) {}
12141228
// NOP
12151229

12161230
/**
@@ -1419,6 +1433,22 @@ export abstract class Field<T = any>
14191433
workspace.getMarker(MarkerManager.LOCAL_MARKER)!.draw();
14201434
}
14211435
}
1436+
1437+
/**
1438+
* Subclasses should reimplement this method to construct their Field
1439+
* subclass from a JSON arg object.
1440+
*
1441+
* It is an error to attempt to register a field subclass in the
1442+
* FieldRegistry if that subclass has not overridden this method.
1443+
*
1444+
* @param _options JSON configuration object with properties needed
1445+
* to configure a specific field.
1446+
*/
1447+
static fromJson(_options: FieldConfig): Field {
1448+
throw new Error(
1449+
`Attempted to instantiate a field from the registry that hasn't defined a 'fromJson' method.`,
1450+
);
1451+
}
14221452
}
14231453

14241454
/**
@@ -1429,12 +1459,14 @@ export interface FieldConfig {
14291459
}
14301460

14311461
/**
1432-
* For use by Field and descendants of Field. Constructors can change
1462+
* Represents an object that has all the prototype properties of the `Field`
1463+
* class. This is necessary because constructors can change
14331464
* in descendants, though they should contain all of Field's prototype methods.
14341465
*
1435-
* @internal
1466+
* This type should only be used in places where we directly access the prototype
1467+
* of a Field class or subclass.
14361468
*/
1437-
export type FieldProto = Pick<typeof Field, 'prototype'>;
1469+
type FieldProto = Pick<typeof Field, 'prototype'>;
14381470

14391471
/**
14401472
* Represents an error where the field is trying to access its block or

core/field_checkbox.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ export class FieldCheckbox extends Field<CheckboxBool> {
226226
* @nocollapse
227227
* @internal
228228
*/
229-
static fromJson(options: FieldCheckboxFromJsonConfig): FieldCheckbox {
229+
static override fromJson(
230+
options: FieldCheckboxFromJsonConfig,
231+
): FieldCheckbox {
230232
// `this` might be a subclass of FieldCheckbox if that class doesn't
231233
// 'override' the static fromJson method.
232234
return new this(options.checked, undefined, options);

core/field_dropdown.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ export class FieldDropdown extends Field<string> {
229229
: ' ' + FieldDropdown.ARROW_CHAR,
230230
),
231231
);
232+
if (this.getConstants()!.FIELD_TEXT_BASELINE_CENTER) {
233+
this.arrow.setAttribute('dominant-baseline', 'central');
234+
}
232235
if (this.getSourceBlock()?.RTL) {
233236
this.getTextElement().insertBefore(this.arrow, this.textContent_);
234237
} else {
@@ -644,7 +647,9 @@ export class FieldDropdown extends Field<string> {
644647
* @nocollapse
645648
* @internal
646649
*/
647-
static fromJson(options: FieldDropdownFromJsonConfig): FieldDropdown {
650+
static override fromJson(
651+
options: FieldDropdownFromJsonConfig,
652+
): FieldDropdown {
648653
if (!options.options) {
649654
throw new Error(
650655
'options are required for the dropdown field. The ' +

core/field_image.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export class FieldImage extends Field<string> {
250250
* @nocollapse
251251
* @internal
252252
*/
253-
static fromJson(options: FieldImageFromJsonConfig): FieldImage {
253+
static override fromJson(options: FieldImageFromJsonConfig): FieldImage {
254254
if (!options.src || !options.width || !options.height) {
255255
throw new Error(
256256
'src, width, and height values for an image field are' +

core/field_input.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,26 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
166166
* value while allowing the display text to be handled by the htmlInput_.
167167
*
168168
* @param _invalidValue The input value that was determined to be invalid.
169-
* This is not used by the text input because its display value is stored
170-
* on the htmlInput_.
169+
* This is not used by the text input because its display value is stored
170+
* on the htmlInput_.
171+
* @param fireChangeEvent Whether to fire a change event if the value changes.
171172
*/
172-
protected override doValueInvalid_(_invalidValue: AnyDuringMigration) {
173+
protected override doValueInvalid_(
174+
_invalidValue: AnyDuringMigration,
175+
fireChangeEvent: boolean = true,
176+
) {
173177
if (this.isBeingEdited_) {
174178
this.isDirty_ = true;
175179
this.isTextValid_ = false;
176180
const oldValue = this.value_;
177181
// Revert value when the text becomes invalid.
178-
this.value_ = this.htmlInput_!.getAttribute('data-untyped-default-value');
179-
if (this.sourceBlock_ && eventUtils.isEnabled()) {
182+
this.value_ = this.valueWhenEditorWasOpened_;
183+
if (
184+
this.sourceBlock_ &&
185+
eventUtils.isEnabled() &&
186+
this.value_ !== oldValue &&
187+
fireChangeEvent
188+
) {
180189
eventUtils.fire(
181190
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
182191
this.sourceBlock_,
@@ -566,7 +575,10 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
566575
// intermediate changes that do not get recorded in undo history.
567576
const oldValue = this.value_;
568577
// Change the field's value without firing the normal change event.
569-
this.setValue(this.getValueFromEditorText_(this.htmlInput_!.value), false);
578+
this.setValue(
579+
this.getValueFromEditorText_(this.htmlInput_!.value),
580+
/* fireChangeEvent= */ false,
581+
);
570582
if (
571583
this.sourceBlock_ &&
572584
eventUtils.isEnabled() &&

core/field_label.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class FieldLabel extends Field<string> {
117117
* @nocollapse
118118
* @internal
119119
*/
120-
static fromJson(options: FieldLabelFromJsonConfig): FieldLabel {
120+
static override fromJson(options: FieldLabelFromJsonConfig): FieldLabel {
121121
const text = parsing.replaceMessageReferences(options.text);
122122
// `this` might be a subclass of FieldLabel if that class doesn't override
123123
// the static fromJson method.

core/field_number.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ export class FieldNumber extends FieldInput<number> {
315315
* @nocollapse
316316
* @internal
317317
*/
318-
static fromJson(options: FieldNumberFromJsonConfig): FieldNumber {
318+
static override fromJson(options: FieldNumberFromJsonConfig): FieldNumber {
319319
// `this` might be a subclass of FieldNumber if that class doesn't override
320320
// the static fromJson method.
321321
return new this(

core/field_registry.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,46 @@
66

77
// Former goog.module ID: Blockly.fieldRegistry
88

9-
import type {Field, FieldProto} from './field.js';
9+
import type {Field, FieldConfig} from './field.js';
1010
import * as registry from './registry.js';
1111

12-
interface RegistryOptions {
12+
/**
13+
* When constructing a field from JSON using the registry, the
14+
* `fromJson` method in this file is called with an options parameter
15+
* object consisting of the "type" which is the name of the field, and
16+
* other options that are part of the field's config object.
17+
*
18+
* These options are then passed to the field's static `fromJson`
19+
* method. That method accepts an options parameter with a type that usually
20+
* extends from FieldConfig, and may or may not have a "type" attribute (in
21+
* fact, it shouldn't, because we'd overwrite it as described above!)
22+
*
23+
* Unfortunately the registry has no way of knowing the actual Field subclass
24+
* that will be returned from passing in the name of the field. Therefore it
25+
* also has no way of knowing that the options object not only implements
26+
* `FieldConfig`, but it also should satisfy the Config that belongs to that
27+
* specific class's `fromJson` method.
28+
*
29+
* Because of this uncertainty, we just give up on type checking the properties
30+
* passed to the `fromJson` method, and allow arbitrary string keys with
31+
* unknown types.
32+
*/
33+
type RegistryOptions = FieldConfig & {
34+
// The name of the field, e.g. field_dropdown
1335
type: string;
1436
[key: string]: unknown;
37+
};
38+
39+
/**
40+
* Represents the static methods that must be defined on any
41+
* field that is registered, i.e. the constructor and fromJson methods.
42+
*
43+
* Because we don't know which Field subclass will be registered, we
44+
* are unable to typecheck the parameters of the constructor.
45+
*/
46+
export interface RegistrableField {
47+
new (...args: any[]): Field;
48+
fromJson(options: FieldConfig): Field;
1549
}
1650

1751
/**
@@ -25,7 +59,7 @@ interface RegistryOptions {
2559
* @throws {Error} if the type name is empty, the field is already registered,
2660
* or the fieldClass is not an object containing a fromJson function.
2761
*/
28-
export function register(type: string, fieldClass: FieldProto) {
62+
export function register(type: string, fieldClass: RegistrableField) {
2963
registry.register(registry.Type.FIELD, type, fieldClass);
3064
}
3165

@@ -59,7 +93,10 @@ export function fromJson<T>(options: RegistryOptions): Field<T> | null {
5993
* @param options
6094
*/
6195
function fromJsonInternal<T>(options: RegistryOptions): Field<T> | null {
62-
const fieldObject = registry.getObject(registry.Type.FIELD, options.type);
96+
const fieldObject = registry.getObject(
97+
registry.Type.FIELD,
98+
options.type,
99+
) as unknown as RegistrableField;
63100
if (!fieldObject) {
64101
console.warn(
65102
'Blockly could not create a field of type ' +
@@ -69,12 +106,8 @@ function fromJsonInternal<T>(options: RegistryOptions): Field<T> | null {
69106
' #1584), or the registration is not being reached.',
70107
);
71108
return null;
72-
} else if (typeof (fieldObject as any).fromJson !== 'function') {
73-
throw new TypeError('returned Field was not a IRegistrableField');
74-
} else {
75-
type fromJson = (options: {}) => Field<T>;
76-
return (fieldObject as unknown as {fromJson: fromJson}).fromJson(options);
77109
}
110+
return fieldObject.fromJson(options);
78111
}
79112

80113
export const TEST_ONLY = {

core/field_textinput.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ export class FieldTextInput extends FieldInput<string> {
7373
* @nocollapse
7474
* @internal
7575
*/
76-
static fromJson(options: FieldTextInputFromJsonConfig): FieldTextInput {
76+
static override fromJson(
77+
options: FieldTextInputFromJsonConfig,
78+
): FieldTextInput {
7779
const text = parsing.replaceMessageReferences(options.text);
7880
// `this` might be a subclass of FieldTextInput if that class doesn't
7981
// override the static fromJson method.

core/flyout_base.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -574,16 +574,7 @@ export abstract class Flyout
574574
* @param contents - The array of items for the flyout.
575575
*/
576576
setContents(contents: FlyoutItem[]): void {
577-
const blocksAndButtons = contents.map((item) => {
578-
if (item.type === 'block' && item.block) {
579-
return item.block as BlockSvg;
580-
}
581-
if (item.type === 'button' && item.button) {
582-
return item.button as FlyoutButton;
583-
}
584-
});
585-
586-
this.contents = blocksAndButtons as FlyoutItem[];
577+
this.contents = contents;
587578
}
588579
/**
589580
* Update the display property of the flyout based whether it thinks it should

0 commit comments

Comments
 (0)