fix: replace 'AnyDuringMigration' for core/field.ts value functions (#6639)

* chore: replace `AnyDuringMigration` for field value functions

* chore: removed unnecessary `KeyboardEvent` comments and `AnyDuringMigration` casts

* chore: cleaned up `doValueUpdate_` and `doClassValidation_` in `Field` subclasses

* fix: updated `FieldValidator` to allow returning `undefined` and restructured `setValue`

* fix: implemented initial `core/field_checkbox.ts` feedback

* fix: updated `Field` to accept `U` and `undefined` and reverted subclass constructor handling of the input value

* fix: reverted `getVars` to returning `string[]` and added related comment

* chore: removed unnecessary comment

* fix: updated `processValidation_` to no longer allow returning `undefined`

* fix: removed `Un` type alias for `undefined`

* chore: removed unnecessary string cast in `core/field_colour.ts`

* fix: updated `doClassValidation_` not to expect `null` since it will never come up in `setValue`

* fix: updated `doClassValidation_` to only allow `undefined` when the new value exists

* Updated `FieldValidator` type to expect `newValue` to exist

* cleanup: updated `picker` from type `Element` to type `HTMLElement`

* fix: updated `doValueInvalid_` type info in `core/field_input.ts` to handle `string` and `undefined`

* fix: reverted `getValue` in `core/field_checkbox.ts` to previous logic

* fix: updated the `Field` constructor to allow `value` to be optional

* chore: consolidated `Validation` with `FieldValidator` and `doClassValidation_`

* fix: reverted generic param `U` while handling diverging user input is being discussed

* fix: updated `doClassValidation_` return comment to work for TSDoc

* fix: misc keyboard event function tweaks
This commit is contained in:
Blake Thomas Williams
2023-01-10 16:13:49 -06:00
committed by GitHub
parent 25d9acb418
commit 0fb64a6772
15 changed files with 169 additions and 162 deletions

View File

@@ -1066,11 +1066,12 @@ export class Block implements IASTNodeLocation, IDeletable {
* @returns List of variable ids.
*/
getVars(): string[] {
const vars = [];
const vars: string[] = [];
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let j = 0, field; field = input.fieldRow[j]; j++) {
if (field.referencesVariables()) {
vars.push(field.getValue());
// NOTE: This only applies to `FieldVariable`, a `Field<string>`
vars.push(field.getValue() as string);
}
}
}

View File

@@ -45,12 +45,30 @@ import * as WidgetDiv from './widgetdiv.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import * as Xml from './xml.js';
export type FieldValidator<T = any> = (value?: T) => T|null|undefined;
/**
* A function that is called to validate changes to the field's value before
* they are set.
*
* **NOTE:** Validation returns one option between `T`, `null`, and `undefined`.
*
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
* @param newValue The value to be validated.
* @returns One of three instructions for setting the new value: `T`, `null`,
* or `undefined`.
*
* - `T` to set this function's returned value instead of `newValue`.
*
* - `null` to invoke `doValueInvalid_` and not set a value.
*
* - `undefined` to set `newValue` as is.
*/
export type FieldValidator<T = any> = (newValue: T) => T|null|undefined;
/**
* Abstract class for an editable field.
*
* @alias Blockly.Field
* @typeParam T - The value stored on the field.
*/
export abstract class Field<T = any> implements IASTNodeLocationSvg,
IASTNodeLocationWithBlock,
@@ -76,6 +94,9 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* instead.
*/
static readonly SKIP_SETUP = new Sentinel();
static isSentinel<T>(value: T|Sentinel): value is Sentinel {
return value === Field.SKIP_SETUP;
}
/**
* Name of field. Unique within each block.
@@ -207,9 +228,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
/** The size of the area rendered by the field. */
this.size_ = new Size(0, 0);
if (value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(value)) return;
if (opt_config) {
this.configure_(opt_config);
}
@@ -372,7 +391,8 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @internal
*/
fromXml(fieldElement: Element) {
this.setValue(fieldElement.textContent);
// Any because gremlins live here. No touchie!
this.setValue(fieldElement.textContent as any);
}
/**
@@ -384,7 +404,8 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @internal
*/
toXml(fieldElement: Element): Element {
fieldElement.textContent = this.getValue();
// Any because gremlins live here. No touchie!
fieldElement.textContent = this.getValue() as any;
return fieldElement;
}
@@ -619,7 +640,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Validation function, or null.
*/
getValidator(): Function|null {
getValidator(): FieldValidator<T>|null {
return this.validator_;
}
@@ -965,41 +986,37 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
return;
}
let validatedValue = this.doClassValidation_(newValue);
// Class validators might accidentally forget to return, we'll ignore that.
newValue = this.processValidation_(newValue, validatedValue);
if (newValue instanceof Error) {
const classValidation = this.doClassValidation_(newValue);
const classValue = this.processValidation_(newValue, classValidation);
if (classValue instanceof Error) {
doLogging && console.log('invalid class validation, return');
return;
}
const localValidator = this.getValidator();
if (localValidator) {
validatedValue = localValidator.call(this, newValue);
// Local validators might accidentally forget to return, we'll ignore
// that.
newValue = this.processValidation_(newValue, validatedValue);
if (newValue instanceof Error) {
doLogging && console.log('invalid local validation, return');
return;
}
const localValidation = this.getValidator()?.call(this, classValue);
const localValue = this.processValidation_(classValue, localValidation);
if (localValue instanceof Error) {
doLogging && console.log('invalid local validation, return');
return;
}
const source = this.sourceBlock_;
if (source && source.disposed) {
doLogging && console.log('source disposed, return');
return;
}
const oldValue = this.getValue();
if (oldValue === newValue) {
if (oldValue === localValue) {
doLogging && console.log('same, doValueUpdate_, return');
this.doValueUpdate_(newValue);
this.doValueUpdate_(localValue);
return;
}
this.doValueUpdate_(newValue);
this.doValueUpdate_(localValue);
if (source && eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
source, 'field', this.name || null, oldValue, newValue));
source, 'field', this.name || null, oldValue, localValue));
}
if (this.isDirty_) {
this.forceRerender();
@@ -1015,8 +1032,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @returns New value, or an Error object.
*/
private processValidation_(
newValue: AnyDuringMigration,
validatedValue: AnyDuringMigration): AnyDuringMigration {
newValue: AnyDuringMigration, validatedValue: T|null|undefined): T|Error {
if (validatedValue === null) {
this.doValueInvalid_(newValue);
if (this.isDirty_) {
@@ -1024,10 +1040,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
}
return Error();
}
if (validatedValue !== undefined) {
newValue = validatedValue;
}
return newValue;
return validatedValue === undefined ? newValue as T : validatedValue;
}
/**
@@ -1035,23 +1048,39 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Current value.
*/
getValue(): AnyDuringMigration {
getValue(): T|null {
return this.value_;
}
/**
* Used to validate a value. Returns input by default. Can be overridden by
* subclasses, see FieldDropdown.
* Validate the changes to a field's value before they are set. See
* **FieldDropdown** for an example of subclass implementation.
*
* @param opt_newValue The value to be validated.
* @returns The validated value, same as input by default.
* **NOTE:** Validation returns one option between `T`, `null`, and
* `undefined`. **Field**'s implementation will never return `undefined`, but
* it is valid for a subclass to return `undefined` if the new value is
* compatible with `T`.
*
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
* @param newValue - The value to be validated.
* @returns One of three instructions for setting the new value: `T`, `null`,
* or `undefined`.
*
* - `T` to set this function's returned value instead of `newValue`.
*
* - `null` to invoke `doValueInvalid_` and not set a value.
*
* - `undefined` to set `newValue` as is.
*/
protected doClassValidation_(opt_newValue?: AnyDuringMigration):
AnyDuringMigration {
if (opt_newValue === null || opt_newValue === undefined) {
protected doClassValidation_(newValue: T): T|null|undefined;
protected doClassValidation_(newValue?: AnyDuringMigration): T|null;
protected doClassValidation_(newValue?: T|AnyDuringMigration): T|null
|undefined {
if (newValue === null || newValue === undefined) {
return null;
}
return opt_newValue;
return newValue as T;
}
/**
@@ -1060,7 +1089,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @param newValue The value to be saved.
*/
protected doValueUpdate_(newValue: AnyDuringMigration) {
protected doValueUpdate_(newValue: T) {
this.value_ = newValue;
this.isDirty_ = true;
}
@@ -1086,7 +1115,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
}
const gesture = (this.sourceBlock_.workspace as WorkspaceSvg).getGesture(e);
if (gesture) {
gesture.setStartField(this as Field);
gesture.setStartField(this);
}
}

View File

@@ -137,9 +137,7 @@ export class FieldAngle extends FieldInput<number> {
opt_config?: FieldAngleConfig) {
super(Field.SKIP_SETUP);
if (opt_value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
}
@@ -404,25 +402,24 @@ export class FieldAngle extends FieldInput<number> {
*
* @param e Keyboard event.
*/
protected override onHtmlInputKeyDown_(e: Event) {
protected override onHtmlInputKeyDown_(e: KeyboardEvent) {
super.onHtmlInputKeyDown_(e);
const block = this.getSourceBlock();
if (!block) {
throw new UnattachedFieldError();
}
const keyboardEvent = e as KeyboardEvent;
let multiplier;
if (keyboardEvent.keyCode === KeyCodes.LEFT) {
if (e.keyCode === KeyCodes.LEFT) {
// decrement (increment in RTL)
multiplier = block.RTL ? 1 : -1;
} else if (keyboardEvent.keyCode === KeyCodes.RIGHT) {
} else if (e.keyCode === KeyCodes.RIGHT) {
// increment (decrement in RTL)
multiplier = block.RTL ? -1 : 1;
} else if (keyboardEvent.keyCode === KeyCodes.DOWN) {
} else if (e.keyCode === KeyCodes.DOWN) {
// decrement
multiplier = -1;
} else if (keyboardEvent.keyCode === KeyCodes.UP) {
} else if (e.keyCode === KeyCodes.UP) {
// increment
multiplier = 1;
}

View File

@@ -20,14 +20,16 @@ import {Field, FieldConfig, FieldValidator} from './field.js';
import * as fieldRegistry from './field_registry.js';
import type {Sentinel} from './utils/sentinel.js';
export type FieldCheckboxValidator = FieldValidator<boolean>;
type BoolString = 'TRUE'|'FALSE';
type CheckboxBool = BoolString|boolean;
export type FieldCheckboxValidator = FieldValidator<CheckboxBool>;
/**
* Class for a checkbox field.
*
* @alias Blockly.FieldCheckbox
*/
export class FieldCheckbox extends Field<boolean> {
export class FieldCheckbox extends Field<CheckboxBool> {
/** Default character for the checkmark. */
static readonly CHECK_CHAR = '✓';
private checkChar_: string;
@@ -42,7 +44,12 @@ export class FieldCheckbox extends Field<boolean> {
* Mouse cursor style when over the hotspot that initiates editability.
*/
override CURSOR = 'default';
override value_: AnyDuringMigration;
/**
* NOTE: The default value is set in `Field`, so maintain that value instead
* of overwriting it here or in the constructor.
*/
override value_: boolean|null = this.value_;
/**
* @param opt_value The initial value of the field. Should either be 'TRUE',
@@ -59,8 +66,7 @@ export class FieldCheckbox extends Field<boolean> {
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string|boolean|Sentinel,
opt_validator?: FieldCheckboxValidator,
opt_value?: CheckboxBool|Sentinel, opt_validator?: FieldCheckboxValidator,
opt_config?: FieldCheckboxConfig) {
super(Field.SKIP_SETUP);
@@ -70,9 +76,7 @@ export class FieldCheckbox extends Field<boolean> {
*/
this.checkChar_ = FieldCheckbox.CHECK_CHAR;
if (opt_value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
}
@@ -153,7 +157,7 @@ export class FieldCheckbox extends Field<boolean> {
* @returns A valid value ('TRUE' or 'FALSE), or null if invalid.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
string|null {
BoolString|null {
if (opt_newValue === true || opt_newValue === 'TRUE') {
return 'TRUE';
}
@@ -169,7 +173,7 @@ export class FieldCheckbox extends Field<boolean> {
* @param newValue The value to be saved. The default validator guarantees
* that this is a either 'TRUE' or 'FALSE'.
*/
protected override doValueUpdate_(newValue: AnyDuringMigration) {
protected override doValueUpdate_(newValue: BoolString) {
this.value_ = this.convertValueToBool_(newValue);
// Update visual.
if (this.textElement_) {
@@ -182,7 +186,7 @@ export class FieldCheckbox extends Field<boolean> {
*
* @returns The value of this field.
*/
override getValue(): string {
override getValue(): BoolString {
return this.value_ ? 'TRUE' : 'FALSE';
}
@@ -191,8 +195,8 @@ export class FieldCheckbox extends Field<boolean> {
*
* @returns The boolean value of this field.
*/
getValueBoolean(): boolean {
return this.value_ as boolean;
getValueBoolean(): boolean|null {
return this.value_;
}
/**
@@ -213,12 +217,9 @@ export class FieldCheckbox extends Field<boolean> {
* @param value The value to convert.
* @returns The converted value.
*/
private convertValueToBool_(value: AnyDuringMigration): boolean {
if (typeof value === 'string') {
return value === 'TRUE';
} else {
return !!value;
}
private convertValueToBool_(value: CheckboxBool|null): boolean {
if (typeof value === 'string') return value === 'TRUE';
return !!value;
}
/**

View File

@@ -80,7 +80,7 @@ export class FieldColour extends Field<string> {
static COLUMNS = 7;
/** The field's colour picker element. */
private picker_: Element|null = null;
private picker_: HTMLElement|null = null;
/** Index of the currently highlighted element. */
private highlightedIndex_: number|null = null;
@@ -134,9 +134,6 @@ export class FieldColour extends Field<string> {
* setting. By default use the global constants for columns.
*/
private columns_ = 0;
override size_: AnyDuringMigration;
override clickTarget_: AnyDuringMigration;
override value_: AnyDuringMigration;
/**
* @param opt_value The initial value of the field. Should be in '#rrggbb'
@@ -157,9 +154,7 @@ export class FieldColour extends Field<string> {
opt_config?: FieldColourConfig) {
super(Field.SKIP_SETUP);
if (opt_value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
}
@@ -230,15 +225,14 @@ export class FieldColour extends Field<string> {
* @param newValue The value to be saved. The default validator guarantees
* that this is a colour in '#rrggbb' format.
*/
protected override doValueUpdate_(newValue: AnyDuringMigration) {
protected override doValueUpdate_(newValue: string) {
this.value_ = newValue;
if (this.borderRect_) {
this.borderRect_.style.fill = newValue as string;
this.borderRect_.style.fill = newValue;
} else if (
this.sourceBlock_ && this.sourceBlock_.rendered &&
this.sourceBlock_ instanceof BlockSvg) {
this.sourceBlock_.pathObject.svgPath.setAttribute(
'fill', newValue as string);
this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue);
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
}
}
@@ -289,16 +283,12 @@ export class FieldColour extends Field<string> {
/** Create and show the colour field's editor. */
protected override showEditor_() {
this.dropdownCreate_();
// AnyDuringMigration because: Argument of type 'Element | null' is not
// assignable to parameter of type 'Node'.
dropDownDiv.getContentDiv().appendChild(this.picker_ as AnyDuringMigration);
dropDownDiv.getContentDiv().appendChild(this.picker_!);
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
// Focus so we can start receiving keyboard events.
// AnyDuringMigration because: Property 'focus' does not exist on type
// 'Element'.
(this.picker_ as AnyDuringMigration)!.focus({preventScroll: true});
this.picker_!.focus({preventScroll: true});
}
/**
@@ -519,9 +509,7 @@ export class FieldColour extends Field<string> {
cell.setAttribute('data-colour', colours[i]);
cell.title = titles[i] || colours[i];
cell.id = idGenerator.getNextUniqueId();
// AnyDuringMigration because: Argument of type 'number' is not
// assignable to parameter of type 'string'.
cell.setAttribute('data-index', i as AnyDuringMigration);
cell.setAttribute('data-index', String(i));
aria.setRole(cell, aria.Role.GRIDCELL);
aria.setState(cell, aria.State.LABEL, colours[i]);
aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour);
@@ -584,7 +572,7 @@ export class FieldColour extends Field<string> {
static fromJson(options: FieldColourFromJsonConfig): FieldColour {
// `this` might be a subclass of FieldColour if that class doesn't override
// the static fromJson method.
return new this(options['colour'], undefined, options);
return new this(options.colour, undefined, options);
}
}

View File

@@ -92,7 +92,7 @@ export class FieldDropdown extends Field<string> {
*/
override suffixField: string|null = null;
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
private selectedOption_!: Array<string|ImageProperties>;
private selectedOption_!: MenuOption;
override clickTarget_: SVGElement|null = null;
/**
@@ -398,8 +398,7 @@ export class FieldDropdown extends Field<string> {
* @param opt_newValue The input value.
* @returns A valid language-neutral option, or null if invalid.
*/
protected override doClassValidation_(opt_newValue?: MenuOption[1]): string
|null {
protected override doClassValidation_(opt_newValue?: string): string|null {
const options = this.getOptions(true);
const isValueValid = options.some((option) => option[1] === opt_newValue);
@@ -421,7 +420,7 @@ export class FieldDropdown extends Field<string> {
* @param newValue The value to be saved. The default validator guarantees
* that this is one of the valid dropdown options.
*/
protected override doValueUpdate_(newValue: MenuOption[1]) {
protected override doValueUpdate_(newValue: string) {
super.doValueUpdate_(newValue);
const options = this.getOptions(true);
for (let i = 0, option; option = options[i]; i++) {
@@ -465,7 +464,7 @@ export class FieldDropdown extends Field<string> {
// Show correct element.
const option = this.selectedOption_ && this.selectedOption_[0];
if (option && typeof option === 'object') {
this.renderSelectedImage_((option));
this.renderSelectedImage_(option);
} else {
this.renderSelectedText_();
}

View File

@@ -60,7 +60,6 @@ export class FieldImage extends Field<string> {
/** Alt text of this image. */
private altText_ = '';
override value_: AnyDuringMigration;
/**
* @param src The URL of the image.
@@ -179,7 +178,7 @@ export class FieldImage extends Field<string> {
* @param newValue The value to be saved. The default validator guarantees
* that this is a string.
*/
protected override doValueUpdate_(newValue: AnyDuringMigration) {
protected override doValueUpdate_(newValue: string) {
this.value_ = newValue;
if (this.imageElement_) {
this.imageElement_.setAttributeNS(

View File

@@ -32,14 +32,16 @@ import * as WidgetDiv from './widgetdiv.js';
import type {WorkspaceSvg} from './workspace_svg.js';
export type InputTypes = string|number;
export type FieldInputValidator<T extends InputTypes> = FieldValidator<T>;
export type FieldInputValidator<T extends InputTypes> =
FieldValidator<string|T>;
/**
* Class for an editable text field.
* Abstract class for an editable input field.
*
* @alias Blockly.FieldInput
* @typeParam T - The value stored on the field.
*/
export abstract class FieldInput<T extends InputTypes> extends Field<T> {
export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
/**
* Pixel size of input border radius.
* Should match blocklyText's border-radius in CSS.
@@ -83,9 +85,6 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
/** Mouse cursor style when over the hotspot that initiates the editor. */
override CURSOR = 'text';
override clickTarget_: AnyDuringMigration;
override value_: AnyDuringMigration;
override isDirty_: AnyDuringMigration;
/**
* @param opt_value The initial value of the field. Should cast to a string.
@@ -106,9 +105,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
opt_config?: FieldInputConfig) {
super(Field.SKIP_SETUP);
if (opt_value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
}
@@ -161,20 +158,6 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
this.createTextElement_();
}
/**
* Ensure that the input value casts to a valid string.
*
* @param opt_newValue The input value.
* @returns A valid string, or null if invalid.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
AnyDuringMigration {
if (opt_newValue === null || opt_newValue === undefined) {
return null;
}
return String(opt_newValue);
}
/**
* Called by setValue if the text input is not valid. If the field is
* currently being edited it reverts value of the field to the previous
@@ -207,7 +190,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
* @param newValue The value to be saved. The default validator guarantees
* that this is a string.
*/
protected override doValueUpdate_(newValue: AnyDuringMigration) {
protected override doValueUpdate_(newValue: string|T) {
this.isDirty_ = true;
this.isTextValid_ = true;
this.value_ = newValue;
@@ -380,7 +363,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
div!.appendChild(htmlInput);
htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
htmlInput.setAttribute('data-untyped-default-value', this.value_);
htmlInput.setAttribute('data-untyped-default-value', String(this.value_));
this.resizeEditor_();
@@ -457,29 +440,19 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
*
* @param e Keyboard event.
*/
protected onHtmlInputKeyDown_(e: Event) {
// AnyDuringMigration because: Property 'keyCode' does not exist on type
// 'Event'.
if ((e as AnyDuringMigration).keyCode === KeyCodes.ENTER) {
protected onHtmlInputKeyDown_(e: KeyboardEvent) {
if (e.keyCode === KeyCodes.ENTER) {
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
// AnyDuringMigration because: Property 'keyCode' does not exist on type
// 'Event'.
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.ESC) {
} else if (e.keyCode === KeyCodes.ESC) {
this.setValue(
this.htmlInput_!.getAttribute('data-untyped-default-value'));
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
// AnyDuringMigration because: Property 'keyCode' does not exist on type
// 'Event'.
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.TAB) {
} else if (e.keyCode === KeyCodes.TAB) {
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
// AnyDuringMigration because: Property 'shiftKey' does not exist on type
// 'Event'. AnyDuringMigration because: Argument of type 'this' is not
// assignable to parameter of type 'Field'.
(this.sourceBlock_ as BlockSvg)
.tab(this as AnyDuringMigration, !(e as AnyDuringMigration).shiftKey);
(this.sourceBlock_ as BlockSvg).tab(this, !e.shiftKey);
e.preventDefault();
}
}

View File

@@ -51,9 +51,7 @@ export class FieldLabel extends Field<string> {
opt_config?: FieldLabelConfig) {
super(Field.SKIP_SETUP);
if (opt_value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
} else {

View File

@@ -70,9 +70,7 @@ export class FieldMultilineInput extends FieldTextInput {
opt_config?: FieldMultilineInputConfig) {
super(Field.SKIP_SETUP);
if (opt_value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
}
@@ -210,9 +208,11 @@ export class FieldMultilineInput extends FieldTextInput {
* @param newValue The value to be saved. The default validator guarantees
* that this is a string.
*/
protected override doValueUpdate_(newValue: AnyDuringMigration) {
protected override doValueUpdate_(newValue: string) {
super.doValueUpdate_(newValue);
this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_;
if (this.value_ !== null) {
this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_;
}
}
/** Updates the text of the textElement. */
@@ -300,7 +300,7 @@ export class FieldMultilineInput extends FieldTextInput {
// absolute longest line, even if it would be truncated after editing.
// Otherwise we would get wrong editor width when there are more
// lines than this.maxLines_.
const actualEditorLines = this.value_.split('\n');
const actualEditorLines = String(this.value_).split('\n');
const dummyTextElement = dom.createSvgElement(
Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
@@ -385,7 +385,7 @@ export class FieldMultilineInput extends FieldTextInput {
div!.appendChild(htmlInput);
htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
htmlInput.setAttribute('data-untyped-default-value', this.value_);
htmlInput.setAttribute('data-untyped-default-value', String(this.value_));
htmlInput.setAttribute('data-old-value', '');
if (userAgent.GECKO) {
// In FF, ensure the browser reflows before resizing to avoid issue #2777.
@@ -428,10 +428,8 @@ export class FieldMultilineInput extends FieldTextInput {
*
* @param e Keyboard event.
*/
protected override onHtmlInputKeyDown_(e: Event) {
// AnyDuringMigration because: Property 'keyCode' does not exist on type
// 'Event'.
if ((e as AnyDuringMigration).keyCode !== KeyCodes.ENTER) {
protected override onHtmlInputKeyDown_(e: KeyboardEvent) {
if (e.keyCode !== KeyCodes.ENTER) {
super.onHtmlInputKeyDown_(e);
}
}

View File

@@ -77,9 +77,7 @@ export class FieldNumber extends FieldInput<number> {
// Pass SENTINEL so that we can define properties before value validation.
super(Field.SKIP_SETUP);
if (opt_value === Field.SKIP_SETUP) {
return;
}
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
} else {
@@ -260,6 +258,7 @@ export class FieldNumber extends FieldInput<number> {
if (opt_newValue === null) {
return null;
}
// Clean up text.
let newValue = String(opt_newValue);
// TODO: Handle cases like 'ten', '1.203,14', etc.

View File

@@ -22,6 +22,11 @@ import type {Sentinel} from './utils/sentinel.js';
export type FieldTextInputValidator = FieldInputValidator<string>;
/**
* Class for an editable text field.
*
* @alias Blockly.FieldTextInput
*/
export class FieldTextInput extends FieldInput<string> {
/**
* @param opt_value The initial value of the field. Should cast to a string.
@@ -43,6 +48,20 @@ export class FieldTextInput extends FieldInput<string> {
super(opt_value, opt_validator, opt_config);
}
/**
* Ensure that the input value casts to a valid string.
*
* @param opt_newValue The input value.
* @returns A valid string, or null if invalid.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
string|null {
if (opt_newValue === undefined) {
return null;
}
return String(opt_newValue);
}
/**
* Construct a FieldTextInput from a JSON arg object,
* dereferencing any string table references.

View File

@@ -306,7 +306,7 @@ export class FieldVariable extends FieldDropdown {
*
* @returns Validation function, or null.
*/
override getValidator(): Function|null {
override getValidator(): FieldVariableValidator|null {
// Validators shouldn't operate on the initial setValue call.
// Normally this is achieved by calling setValidator after setValue, but
// this is not a possibility with variable fields.
@@ -357,7 +357,7 @@ export class FieldVariable extends FieldDropdown {
*
* @param newId The value to be saved.
*/
protected override doValueUpdate_(newId: AnyDuringMigration) {
protected override doValueUpdate_(newId: string) {
const block = this.getSourceBlock();
if (!block) {
throw new UnattachedFieldError();

View File

@@ -969,14 +969,14 @@ export class Gesture {
* @param field The field the gesture started on.
* @internal
*/
setStartField(field: Field) {
setStartField<T>(field: Field<T>) {
if (this.hasStarted_) {
throw Error(
'Tried to call gesture.setStartField, ' +
'but the gesture had already been started.');
}
if (!this.startField_) {
this.startField_ = field;
this.startField_ = field as Field;
}
}

View File

@@ -18,4 +18,10 @@ goog.declareModuleId('Blockly.utils.Sentinel');
*
* @alias Blockly.utils.Sentinel
*/
export class Sentinel {}
export class Sentinel {
/**
* Provide a unique key so that type guarding properly excludes values like
* string.
*/
UNIQUE_KEY?: never;
}