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

@@ -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);
}
}