mirror of
https://github.com/google/blockly.git
synced 2026-01-09 01:50:11 +01:00
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:
committed by
GitHub
parent
25d9acb418
commit
0fb64a6772
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
core/field.ts
113
core/field.ts
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_();
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user