chore: Reduce delta on ports to blockly-samples (#6886)

* Reduce usage of obsolete .keyCode property.
* Rename private properties/methods which violate eslint rules.
* Use arrays of bound events rather than individual properties.
* Improve typing info.
* Also fix O(n^2) recursive performance issue in theme's getComponentStyle function.
* And replace String(...) with '${...}' (smaller, faster).
* .toString() is considered harmful.
This commit is contained in:
Neil Fraser
2023-03-15 21:28:57 +01:00
committed by GitHub
parent 66fd055a83
commit 42fde0f81b
56 changed files with 756 additions and 847 deletions

View File

@@ -5,7 +5,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: > value: >
Thank you for taking the time to fill out a bug report! Thank you for taking the time to fill out a bug report!
If you have a question about how to use Blockly in your application, If you have a question about how to use Blockly in your application,
please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue. please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue.
- type: checkboxes - type: checkboxes

View File

@@ -6,7 +6,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: > value: >
Thank you for taking the time to fill out a feature request! Thank you for taking the time to fill out a feature request!
If you have a question about how to use Blockly in your application, If you have a question about how to use Blockly in your application,
please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue. please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue.
- type: checkboxes - type: checkboxes

View File

@@ -133,8 +133,8 @@ function connectionUiStep(ripple: SVGElement, start: Date, scale: number) {
if (percent > 1) { if (percent > 1) {
dom.removeNode(ripple); dom.removeNode(ripple);
} else { } else {
ripple.setAttribute('r', (percent * 25 * scale).toString()); ripple.setAttribute('r', String(percent * 25 * scale));
ripple.style.opacity = (1 - percent).toString(); ripple.style.opacity = String(1 - percent);
disconnectPid = setTimeout(connectionUiStep, 10, ripple, start, scale); disconnectPid = setTimeout(connectionUiStep, 10, ripple, start, scale);
} }
} }

View File

@@ -145,10 +145,7 @@ export function unbind(bindData: Data): (e: Event) => void {
// should only pass Data from bind or conditionalBind. // should only pass Data from bind or conditionalBind.
const callback = bindData[bindData.length - 1][2]; const callback = bindData[bindData.length - 1][2];
while (bindData.length) { while (bindData.length) {
const bindDatum = bindData.pop(); const [node, name, func] = bindData.pop()!;
const node = bindDatum![0];
const name = bindDatum![1];
const func = bindDatum![2];
node.removeEventListener(name, func, false); node.removeEventListener(name, func, false);
} }
return callback; return callback;

View File

@@ -218,27 +218,26 @@ export class Bubble implements IBubble {
'blocklyResizeSE', 'blocklyResizeSE',
}, },
this.bubbleGroup); this.bubbleGroup);
const resizeSize = 2 * Bubble.BORDER_WIDTH; const size = 2 * Bubble.BORDER_WIDTH;
dom.createSvgElement( dom.createSvgElement(
Svg.POLYGON, Svg.POLYGON, {'points': `0,${size} ${size},${size} ${size},0`},
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
this.resizeGroup); this.resizeGroup);
dom.createSvgElement( dom.createSvgElement(
Svg.LINE, { Svg.LINE, {
'class': 'blocklyResizeLine', 'class': 'blocklyResizeLine',
'x1': resizeSize / 3, 'x1': size / 3,
'y1': resizeSize - 1, 'y1': size - 1,
'x2': resizeSize - 1, 'x2': size - 1,
'y2': resizeSize / 3, 'y2': size / 3,
}, },
this.resizeGroup); this.resizeGroup);
dom.createSvgElement( dom.createSvgElement(
Svg.LINE, { Svg.LINE, {
'class': 'blocklyResizeLine', 'class': 'blocklyResizeLine',
'x1': resizeSize * 2 / 3, 'x1': size * 2 / 3,
'y1': resizeSize - 1, 'y1': size - 1,
'x2': resizeSize - 1, 'x2': size - 1,
'y2': resizeSize * 2 / 3, 'y2': size * 2 / 3,
}, },
this.resizeGroup); this.resizeGroup);
} else { } else {
@@ -660,8 +659,8 @@ export class Bubble implements IBubble {
height = Math.max(height, doubleBorderWidth + 20); height = Math.max(height, doubleBorderWidth + 20);
this.width = width; this.width = width;
this.height = height; this.height = height;
this.bubbleBack?.setAttribute('width', width.toString()); this.bubbleBack?.setAttribute('width', `${width}`);
this.bubbleBack?.setAttribute('height', height.toString()); this.bubbleBack?.setAttribute('height', `${height}`);
if (this.resizeGroup) { if (this.resizeGroup) {
if (this.workspace_.RTL) { if (this.workspace_.RTL) {
// Mirror the resize group. // Mirror the resize group.
@@ -901,8 +900,7 @@ export class Bubble implements IBubble {
textElement = paragraphElement.childNodes[i] as SVGTSpanElement; textElement = paragraphElement.childNodes[i] as SVGTSpanElement;
i++) { i++) {
textElement.setAttribute('text-anchor', 'end'); textElement.setAttribute('text-anchor', 'end');
textElement.setAttribute( textElement.setAttribute('x', String(maxWidth + Bubble.BORDER_WIDTH));
'x', (maxWidth + Bubble.BORDER_WIDTH).toString());
} }
} }
return bubble; return bubble;

View File

@@ -42,17 +42,12 @@ export class Comment extends Icon {
*/ */
private cachedText: string|null = ''; private cachedText: string|null = '';
/** Mouse up event data. */ /**
private onMouseUpWrapper: browserEvents.Data|null = null; * Array holding info needed to unbind events.
* Used for disposing.
/** Wheel event data. */ * Ex: [[node, name, func], [node, name, func]].
private onWheelWrapper: browserEvents.Data|null = null; */
private boundEvents: browserEvents.Data[] = [];
/** Change event data. */
private onChangeWrapper: browserEvents.Data|null = null;
/** Input event data. */
private onInputWrapper: browserEvents.Data|null = null;
/** /**
* The SVG element that contains the text edit area, or null if not created. * The SVG element that contains the text edit area, or null if not created.
@@ -147,14 +142,14 @@ export class Comment extends Icon {
body.appendChild(textarea); body.appendChild(textarea);
this.foreignObject!.appendChild(body); this.foreignObject!.appendChild(body);
this.onMouseUpWrapper = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'focus', this, this.startEdit, true); textarea, 'focus', this, this.startEdit, true));
// Don't zoom with mousewheel. // Don't zoom with mousewheel.
this.onWheelWrapper = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'wheel', this, function(e: Event) { textarea, 'wheel', this, function(e: Event) {
e.stopPropagation(); e.stopPropagation();
}); }));
this.onChangeWrapper = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'change', this, textarea, 'change', this,
/** /**
* @param _e Unused event parameter. * @param _e Unused event parameter.
@@ -165,15 +160,15 @@ export class Comment extends Icon {
this.getBlock(), 'comment', null, this.cachedText, this.getBlock(), 'comment', null, this.cachedText,
this.model.text)); this.model.text));
} }
}); }));
this.onInputWrapper = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'input', this, textarea, 'input', this,
/** /**
* @param _e Unused event parameter. * @param _e Unused event parameter.
*/ */
function(this: Comment, _e: Event) { function(this: Comment, _e: Event) {
this.model.text = textarea.value; this.model.text = textarea.value;
}); }));
setTimeout(textarea.focus.bind(textarea), 0); setTimeout(textarea.focus.bind(textarea), 0);
@@ -277,22 +272,10 @@ export class Comment extends Icon {
* Dispose of the bubble. * Dispose of the bubble.
*/ */
private disposeBubble() { private disposeBubble() {
if (this.onMouseUpWrapper) { for (const event of this.boundEvents) {
browserEvents.unbind(this.onMouseUpWrapper); browserEvents.unbind(event);
this.onMouseUpWrapper = null;
}
if (this.onWheelWrapper) {
browserEvents.unbind(this.onWheelWrapper);
this.onWheelWrapper = null;
}
if (this.onChangeWrapper) {
browserEvents.unbind(this.onChangeWrapper);
this.onChangeWrapper = null;
}
if (this.onInputWrapper) {
browserEvents.unbind(this.onInputWrapper);
this.onInputWrapper = null;
} }
this.boundEvents.length = 0;
if (this.bubble_) { if (this.bubble_) {
this.bubble_.dispose(); this.bubble_.dispose();
this.bubble_ = null; this.bubble_ = null;

View File

@@ -118,7 +118,7 @@ export class ComponentManager {
'Plugin "' + id + 'already has capability "' + capability + '"'); 'Plugin "' + id + 'already has capability "' + capability + '"');
return; return;
} }
capability = String(capability).toLowerCase(); capability = `${capability}`.toLowerCase();
this.componentData.get(id)?.capabilities.push(capability); this.componentData.get(id)?.capabilities.push(capability);
this.capabilityToComponentIds.get(capability)?.push(id); this.capabilityToComponentIds.get(capability)?.push(id);
} }
@@ -141,7 +141,7 @@ export class ComponentManager {
'" to remove'); '" to remove');
return; return;
} }
capability = String(capability).toLowerCase(); capability = `${capability}`.toLowerCase();
arrayUtils.removeElem(this.componentData.get(id)!.capabilities, capability); arrayUtils.removeElem(this.componentData.get(id)!.capabilities, capability);
arrayUtils.removeElem(this.capabilityToComponentIds.get(capability)!, id); arrayUtils.removeElem(this.capabilityToComponentIds.get(capability)!, id);
} }
@@ -154,7 +154,7 @@ export class ComponentManager {
* @returns Whether the component has the capability. * @returns Whether the component has the capability.
*/ */
hasCapability<T>(id: string, capability: string|Capability<T>): boolean { hasCapability<T>(id: string, capability: string|Capability<T>): boolean {
capability = String(capability).toLowerCase(); capability = `${capability}`.toLowerCase();
return this.componentData.has(id) && return this.componentData.has(id) &&
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1; this.componentData.get(id)!.capabilities.indexOf(capability) !== -1;
} }
@@ -178,7 +178,7 @@ export class ComponentManager {
*/ */
getComponents<T extends IComponent>( getComponents<T extends IComponent>(
capability: string|Capability<T>, sorted: boolean): T[] { capability: string|Capability<T>, sorted: boolean): T[] {
capability = String(capability).toLowerCase(); capability = `${capability}`.toLowerCase();
const componentIds = this.capabilityToComponentIds.get(capability); const componentIds = this.capabilityToComponentIds.get(capability);
if (!componentIds) { if (!componentIds) {
return []; return [];

View File

@@ -261,10 +261,8 @@ export function registerDeleteAll() {
const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length; const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length;
if (deletableBlocksLength === 1) { if (deletableBlocksLength === 1) {
return Msg['DELETE_BLOCK']; return Msg['DELETE_BLOCK'];
} else {
return Msg['DELETE_X_BLOCKS'].replace(
'%1', String(deletableBlocksLength));
} }
return Msg['DELETE_X_BLOCKS'].replace('%1', `${deletableBlocksLength}`);
}, },
preconditionFn(scope: Scope) { preconditionFn(scope: Scope) {
if (!scope.workspace) { if (!scope.workspace) {
@@ -489,7 +487,7 @@ export function registerDelete() {
} }
return descendantCount === 1 ? return descendantCount === 1 ?
Msg['DELETE_BLOCK'] : Msg['DELETE_BLOCK'] :
Msg['DELETE_X_BLOCKS'].replace('%1', String(descendantCount)); Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`);
}, },
preconditionFn(scope: Scope) { preconditionFn(scope: Scope) {
if (!scope.block!.isInFlyout && scope.block!.isDeletable()) { if (!scope.block!.isInFlyout && scope.block!.isDeletable()) {

View File

@@ -203,16 +203,16 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
* subclasses that want to handle configuration and setting the field value * subclasses that want to handle configuration and setting the field value
* after their own constructors have run). * after their own constructors have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a value & returns a validated value, or null to * field's value. Takes in a value & returns a validated value, or null to
* abort the change. * abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* Refer to the individual field's documentation for a list of properties * Refer to the individual field's documentation for a list of properties
* this parameter supports. * this parameter supports.
*/ */
constructor( constructor(
value: T|Sentinel, opt_validator?: FieldValidator<T>|null, value: T|Sentinel, validator?: FieldValidator<T>|null,
opt_config?: FieldConfig) { config?: FieldConfig) {
/** /**
* A generic value possessed by the field. * A generic value possessed by the field.
* Should generally be non-null, only null when the field is created. * Should generally be non-null, only null when the field is created.
@@ -225,12 +225,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
this.size_ = new Size(0, 0); this.size_ = new Size(0, 0);
if (Field.isSentinel(value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} }
this.setValue(value); this.setValue(value);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -715,14 +715,14 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* Calls showEditor_ when the field is clicked if the field is clickable. * Calls showEditor_ when the field is clicked if the field is clickable.
* Do not override. * Do not override.
* *
* @param opt_e Optional mouse event that triggered the field to open, or * @param e Optional mouse event that triggered the field to open, or
* undefined if triggered programmatically. * undefined if triggered programmatically.
* @sealed * @sealed
* @internal * @internal
*/ */
showEditor(opt_e?: Event) { showEditor(e?: Event) {
if (this.isClickable()) { if (this.isClickable()) {
this.showEditor_(opt_e); this.showEditor_(e);
} }
} }
@@ -739,11 +739,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
/** /**
* Updates the size of the field based on the text. * Updates the size of the field based on the text.
* *
* @param opt_margin margin to use when positioning the text element. * @param margin margin to use when positioning the text element.
*/ */
protected updateSize_(opt_margin?: number) { protected updateSize_(margin?: number) {
const constants = this.getConstants(); const constants = this.getConstants();
const xOffset = opt_margin !== undefined ? opt_margin : const xOffset = margin !== undefined ? margin :
this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING :
0; 0;
let totalWidth = xOffset * 2; let totalWidth = xOffset * 2;
@@ -783,17 +783,17 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
this.textElement_.setAttribute( this.textElement_.setAttribute(
'x', 'x',
`${ String(
this.getSourceBlock()?.RTL ? this.getSourceBlock()?.RTL ?
this.size_.width - contentWidth - xOffset : this.size_.width - contentWidth - xOffset :
xOffset}`); xOffset));
this.textElement_.setAttribute( this.textElement_.setAttribute(
'y', 'y',
`${ String(
constants!.FIELD_TEXT_BASELINE_CENTER ? constants!.FIELD_TEXT_BASELINE_CENTER ?
halfHeight : halfHeight :
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 + halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
constants!.FIELD_TEXT_BASELINE}`); constants!.FIELD_TEXT_BASELINE));
} }
/** Position a field's border rect after a size change. */ /** Position a field's border rect after a size change. */
@@ -801,12 +801,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
if (!this.borderRect_) { if (!this.borderRect_) {
return; return;
} }
this.borderRect_.setAttribute('width', `${this.size_.width}`); this.borderRect_.setAttribute('width', String(this.size_.width));
this.borderRect_.setAttribute('height', `${this.size_.height}`); this.borderRect_.setAttribute('height', String(this.size_.height));
this.borderRect_.setAttribute( this.borderRect_.setAttribute(
'rx', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`); 'rx', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
this.borderRect_.setAttribute( this.borderRect_.setAttribute(
'ry', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`); 'ry', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
} }
/** /**

View File

@@ -20,7 +20,6 @@ import {Field, UnattachedFieldError} from './field.js';
import * as fieldRegistry from './field_registry.js'; import * as fieldRegistry from './field_registry.js';
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js'; import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';
import {KeyCodes} from './utils/keycodes.js';
import * as math from './utils/math.js'; import * as math from './utils/math.js';
import type {Sentinel} from './utils/sentinel.js'; import type {Sentinel} from './utils/sentinel.js';
import {Svg} from './utils/svg.js'; import {Svg} from './utils/svg.js';
@@ -31,15 +30,15 @@ import * as WidgetDiv from './widgetdiv.js';
* Class for an editable angle field. * Class for an editable angle field.
*/ */
export class FieldAngle extends FieldInput<number> { export class FieldAngle extends FieldInput<number> {
/**
* The default amount to round angles to when using a mouse or keyboard nav
* input. Must be a positive integer to support keyboard navigation.
*/
static readonly ROUND = 15;
/** Half the width of protractor image. */ /** Half the width of protractor image. */
static readonly HALF = 100 / 2; static readonly HALF = 100 / 2;
/**
* Radius of protractor circle. Slightly smaller than protractor size since
* otherwise SVG crops off half the border at the edges.
*/
static readonly RADIUS: number = FieldAngle.HALF - 1;
/** /**
* Default property describing which direction makes an angle field's value * Default property describing which direction makes an angle field's value
* increase. Angle increases clockwise (true) or counterclockwise (false). * increase. Angle increases clockwise (true) or counterclockwise (false).
@@ -60,84 +59,73 @@ export class FieldAngle extends FieldInput<number> {
static readonly WRAP = 360; static readonly WRAP = 360;
/** /**
* Radius of protractor circle. Slightly smaller than protractor size since * The default amount to round angles to when using a mouse or keyboard nav
* otherwise SVG crops off half the border at the edges. * input. Must be a positive integer to support keyboard navigation.
*/ */
static readonly RADIUS: number = FieldAngle.HALF - 1; static readonly ROUND = 15;
/** /**
* Whether the angle should increase as the angle picker is moved clockwise * Whether the angle should increase as the angle picker is moved clockwise
* (true) or counterclockwise (false). * (true) or counterclockwise (false).
*/ */
private clockwise_ = FieldAngle.CLOCKWISE; private clockwise = FieldAngle.CLOCKWISE;
/** /**
* The offset of zero degrees (and all other angles). * The offset of zero degrees (and all other angles).
*/ */
private offset_ = FieldAngle.OFFSET; private offset = FieldAngle.OFFSET;
/** /**
* The maximum angle to allow before wrapping. * The maximum angle to allow before wrapping.
*/ */
private wrap_ = FieldAngle.WRAP; private wrap = FieldAngle.WRAP;
/** /**
* The amount to round angles to when using a mouse or keyboard nav input. * The amount to round angles to when using a mouse or keyboard nav input.
*/ */
private round_ = FieldAngle.ROUND; private round = FieldAngle.ROUND;
/** The angle picker's SVG element. */ /**
private editor_: SVGSVGElement|null = null; * Array holding info needed to unbind events.
* Used for disposing.
* Ex: [[node, name, func], [node, name, func]].
*/
private boundEvents: browserEvents.Data[] = [];
/** The angle picker's gauge path depending on the value. */ /** Dynamic red line pointing at the value's angle. */
gauge_: SVGPathElement|null = null; private line: SVGLineElement|null = null;
/** The angle picker's line drawn representing the value's angle. */ /** Dynamic pink area extending from 0 to the value's angle. */
line_: SVGLineElement|null = null; private gauge: SVGPathElement|null = null;
/** The degree symbol for this field. */ /** The degree symbol for this field. */
protected symbol_: SVGTSpanElement|null = null; protected symbol_: SVGTSpanElement|null = null;
/** Wrapper click event data. */
private clickWrapper_: browserEvents.Data|null = null;
/** Surface click event data. */
private clickSurfaceWrapper_: browserEvents.Data|null = null;
/** Surface mouse move event data. */
private moveSurfaceWrapper_: browserEvents.Data|null = null;
/** /**
* Serializable fields are saved by the serializer, non-serializable fields * @param value The initial value of the field. Should cast to a number.
* are not. Editable fields should also be serializable.
*/
override SERIALIZABLE = true;
/**
* @param opt_value The initial value of the field. Should cast to a number.
* Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup * Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup
* (only used by subclasses that want to handle configuration and setting * (only used by subclasses that want to handle configuration and setting
* the field value after their own constructors have run). * the field value after their own constructors have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a number & returns a validated number, or null * field's value. Takes in a number & returns a validated number, or null
* to abort the change. * to abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: string|number|Sentinel, opt_validator?: FieldAngleValidator, value?: string|number|Sentinel, validator?: FieldAngleValidator,
opt_config?: FieldAngleConfig) { config?: FieldAngleConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} }
this.setValue(opt_value); this.setValue(value);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -151,22 +139,22 @@ export class FieldAngle extends FieldInput<number> {
switch (config.mode) { switch (config.mode) {
case Mode.COMPASS: case Mode.COMPASS:
this.clockwise_ = true; this.clockwise = true;
this.offset_ = 90; this.offset = 90;
break; break;
case Mode.PROTRACTOR: case Mode.PROTRACTOR:
// This is the default mode, so we could do nothing. But just to // This is the default mode, so we could do nothing. But just to
// future-proof, we'll set it anyway. // future-proof, we'll set it anyway.
this.clockwise_ = false; this.clockwise = false;
this.offset_ = 0; this.offset = 0;
break; break;
} }
// Allow individual settings to override the mode setting. // Allow individual settings to override the mode setting.
if (config.clockwise) this.clockwise_ = config.clockwise; if (config.clockwise) this.clockwise = config.clockwise;
if (config.offset) this.offset_ = config.offset; if (config.offset) this.offset = config.offset;
if (config.wrap) this.wrap_ = config.wrap; if (config.wrap) this.wrap = config.wrap;
if (config.round) this.round_ = config.round; if (config.round) this.round = config.round;
} }
/** /**
@@ -176,32 +164,32 @@ export class FieldAngle extends FieldInput<number> {
*/ */
override initView() { override initView() {
super.initView(); super.initView();
// Add the degree symbol to the left of the number, even in RTL (issue // Add the degree symbol to the left of the number,
// #2380) // even in RTL (issue #2380).
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}); this.symbol_ = dom.createSvgElement(Svg.TSPAN, {});
this.symbol_.appendChild(document.createTextNode('°')); this.symbol_.appendChild(document.createTextNode('°'));
this.getTextElement().appendChild(this.symbol_); this.getTextElement().appendChild(this.symbol_);
} }
/** Updates the graph when the field rerenders. */ /** Updates the angle when the field rerenders. */
protected override render_() { protected override render_() {
super.render_(); super.render_();
this.updateGraph_(); this.updateGraph();
} }
/** /**
* Create and show the angle field's editor. * Create and show the angle field's editor.
* *
* @param opt_e Optional mouse event that triggered the field to open, or * @param e Optional mouse event that triggered the field to open,
* undefined if triggered programmatically. * or undefined if triggered programmatically.
*/ */
protected override showEditor_(opt_e?: Event) { protected override showEditor_(e?: Event) {
// Mobile browsers have issues with in-line textareas (focus & keyboards). // Mobile browsers have issues with in-line textareas (focus & keyboards).
const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD; const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
super.showEditor_(opt_e, noFocus); super.showEditor_(e, noFocus);
this.dropdownCreate_(); const editor = this.dropdownCreate();
dropDownDiv.getContentDiv().appendChild(this.editor_!); dropDownDiv.getContentDiv().appendChild(editor);
if (this.sourceBlock_ instanceof BlockSvg) { if (this.sourceBlock_ instanceof BlockSvg) {
dropDownDiv.setColour( dropDownDiv.setColour(
@@ -209,13 +197,17 @@ export class FieldAngle extends FieldInput<number> {
this.sourceBlock_.style.colourTertiary); this.sourceBlock_.style.colourTertiary);
} }
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
this.updateGraph_(); this.updateGraph();
} }
/** Create the angle dropdown editor. */ /**
private dropdownCreate_() { * Creates the angle dropdown editor.
*
* @returns The newly created slider.
*/
private dropdownCreate(): SVGSVGElement {
const svg = dom.createSvgElement(Svg.SVG, { const svg = dom.createSvgElement(Svg.SVG, {
'xmlns': dom.SVG_NS, 'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS, 'xmlns:html': dom.HTML_NS,
@@ -233,9 +225,9 @@ export class FieldAngle extends FieldInput<number> {
'class': 'blocklyAngleCircle', 'class': 'blocklyAngleCircle',
}, },
svg); svg);
this.gauge_ = this.gauge =
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg); dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
this.line_ = dom.createSvgElement( this.line = dom.createSvgElement(
Svg.LINE, { Svg.LINE, {
'x1': FieldAngle.HALF, 'x1': FieldAngle.HALF,
'y1': FieldAngle.HALF, 'y1': FieldAngle.HALF,
@@ -261,38 +253,30 @@ export class FieldAngle extends FieldInput<number> {
// The angle picker is different from other fields in that it updates on // The angle picker is different from other fields in that it updates on
// mousemove even if it's not in the middle of a drag. In future we may // mousemove even if it's not in the middle of a drag. In future we may
// change this behaviour. // change this behaviour.
this.clickWrapper_ = this.boundEvents.push(
browserEvents.conditionalBind(svg, 'click', this, this.hide_); browserEvents.conditionalBind(svg, 'click', this, this.hide));
// On touch devices, the picker's value is only updated with a drag. Add // On touch devices, the picker's value is only updated with a drag. Add
// a click handler on the drag surface to update the value if the surface // a click handler on the drag surface to update the value if the surface
// is clicked. // is clicked.
this.clickSurfaceWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointerdown', this, this.onMouseMove_, true); circle, 'pointerdown', this, this.onMouseMove_, true));
this.moveSurfaceWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointermove', this, this.onMouseMove_, true); circle, 'pointermove', this, this.onMouseMove_, true));
this.editor_ = svg; return svg;
} }
/** Disposes of events and DOM-references belonging to the angle editor. */ /** Disposes of events and DOM-references belonging to the angle editor. */
private dropdownDispose_() { private dropdownDispose() {
if (this.clickWrapper_) { for (const event of this.boundEvents) {
browserEvents.unbind(this.clickWrapper_); browserEvents.unbind(event);
this.clickWrapper_ = null;
} }
if (this.clickSurfaceWrapper_) { this.boundEvents.length = 0;
browserEvents.unbind(this.clickSurfaceWrapper_); this.gauge = null;
this.clickSurfaceWrapper_ = null; this.line = null;
}
if (this.moveSurfaceWrapper_) {
browserEvents.unbind(this.moveSurfaceWrapper_);
this.moveSurfaceWrapper_ = null;
}
this.gauge_ = null;
this.line_ = null;
} }
/** Hide the editor. */ /** Hide the editor. */
private hide_() { private hide() {
dropDownDiv.hideIfOwner(this); dropDownDiv.hideIfOwner(this);
WidgetDiv.hide(); WidgetDiv.hide();
} }
@@ -304,7 +288,7 @@ export class FieldAngle extends FieldInput<number> {
*/ */
protected onMouseMove_(e: PointerEvent) { protected onMouseMove_(e: PointerEvent) {
// Calculate angle. // Calculate angle.
const bBox = this.gauge_!.ownerSVGElement!.getBoundingClientRect(); const bBox = this.gauge!.ownerSVGElement!.getBoundingClientRect();
const dx = e.clientX - bBox.left - FieldAngle.HALF; const dx = e.clientX - bBox.left - FieldAngle.HALF;
const dy = e.clientY - bBox.top - FieldAngle.HALF; const dy = e.clientY - bBox.top - FieldAngle.HALF;
let angle = Math.atan(-dy / dx); let angle = Math.atan(-dy / dx);
@@ -321,13 +305,13 @@ export class FieldAngle extends FieldInput<number> {
} }
// Do offsetting. // Do offsetting.
if (this.clockwise_) { if (this.clockwise) {
angle = this.offset_ + 360 - angle; angle = this.offset + 360 - angle;
} else { } else {
angle = 360 - (this.offset_ - angle); angle = 360 - (this.offset - angle);
} }
this.displayMouseOrKeyboardValue_(angle); this.displayMouseOrKeyboardValue(angle);
} }
/** /**
@@ -337,31 +321,31 @@ export class FieldAngle extends FieldInput<number> {
* *
* @param angle New angle. * @param angle New angle.
*/ */
private displayMouseOrKeyboardValue_(angle: number) { private displayMouseOrKeyboardValue(angle: number) {
if (this.round_) { if (this.round) {
angle = Math.round(angle / this.round_) * this.round_; angle = Math.round(angle / this.round) * this.round;
} }
angle = this.wrapValue_(angle); angle = this.wrapValue(angle);
if (angle !== this.value_) { if (angle !== this.value_) {
this.setEditorValue_(angle); this.setEditorValue_(angle);
} }
} }
/** Redraw the graph with the current angle. */ /** Redraw the graph with the current angle. */
private updateGraph_() { private updateGraph() {
if (!this.gauge_) { if (!this.gauge || !this.line) {
return; return;
} }
// Always display the input (i.e. getText) even if it is invalid. // Always display the input (i.e. getText) even if it is invalid.
let angleDegrees = Number(this.getText()) + this.offset_; let angleDegrees = Number(this.getText()) + this.offset;
angleDegrees %= 360; angleDegrees %= 360;
let angleRadians = math.toRadians(angleDegrees); let angleRadians = math.toRadians(angleDegrees);
const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF]; const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
let x2 = FieldAngle.HALF; let x2 = FieldAngle.HALF;
let y2 = FieldAngle.HALF; let y2 = FieldAngle.HALF;
if (!isNaN(angleRadians)) { if (!isNaN(angleRadians)) {
const clockwiseFlag = Number(this.clockwise_); const clockwiseFlag = Number(this.clockwise);
const angle1 = math.toRadians(this.offset_); const angle1 = math.toRadians(this.offset);
const x1 = Math.cos(angle1) * FieldAngle.RADIUS; const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
const y1 = Math.sin(angle1) * -FieldAngle.RADIUS; const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
if (clockwiseFlag) { if (clockwiseFlag) {
@@ -379,9 +363,9 @@ export class FieldAngle extends FieldInput<number> {
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS, ' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z'); ' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
} }
this.gauge_.setAttribute('d', path.join('')); this.gauge.setAttribute('d', path.join(''));
this.line_?.setAttribute('x2', `${x2}`); this.line.setAttribute('x2', `${x2}`);
this.line_?.setAttribute('y2', `${y2}`); this.line.setAttribute('y2', `${y2}`);
} }
/** /**
@@ -396,23 +380,28 @@ export class FieldAngle extends FieldInput<number> {
throw new UnattachedFieldError(); throw new UnattachedFieldError();
} }
let multiplier; let multiplier = 0;
if (e.keyCode === KeyCodes.LEFT) { switch (e.key) {
// decrement (increment in RTL) case 'ArrowLeft':
multiplier = block.RTL ? 1 : -1; // decrement (increment in RTL)
} else if (e.keyCode === KeyCodes.RIGHT) { multiplier = block.RTL ? 1 : -1;
// increment (decrement in RTL) break;
multiplier = block.RTL ? -1 : 1; case 'ArrowRight':
} else if (e.keyCode === KeyCodes.DOWN) { // increment (decrement in RTL)
// decrement multiplier = block.RTL ? -1 : 1;
multiplier = -1; break;
} else if (e.keyCode === KeyCodes.UP) { case 'ArrowDown':
// increment // decrement
multiplier = 1; multiplier = -1;
break;
case 'ArrowUp':
// increment
multiplier = 1;
break;
} }
if (multiplier) { if (multiplier) {
const value = this.getValue() as number; const value = this.getValue() as number;
this.displayMouseOrKeyboardValue_(value + multiplier * this.round_); this.displayMouseOrKeyboardValue(value + multiplier * this.round);
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
} }
@@ -421,15 +410,15 @@ export class FieldAngle extends FieldInput<number> {
/** /**
* Ensure that the input value is a valid angle. * Ensure that the input value is a valid angle.
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A valid angle, or null if invalid. * @returns A valid angle, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: any): number|null { protected override doClassValidation_(newValue?: any): number|null {
const value = Number(opt_newValue); const value = Number(newValue);
if (isNaN(value) || !isFinite(value)) { if (isNaN(value) || !isFinite(value)) {
return null; return null;
} }
return this.wrapValue_(value); return this.wrapValue(value);
} }
/** /**
@@ -438,12 +427,12 @@ export class FieldAngle extends FieldInput<number> {
* @param value The value to wrap. * @param value The value to wrap.
* @returns The wrapped value. * @returns The wrapped value.
*/ */
private wrapValue_(value: number): number { private wrapValue(value: number): number {
value %= 360; value %= 360;
if (value < 0) { if (value < 0) {
value += 360; value += 360;
} }
if (value > this.wrap_) { if (value > this.wrap) {
value -= 360; value -= 360;
} }
return value; return value;
@@ -464,13 +453,20 @@ export class FieldAngle extends FieldInput<number> {
} }
} }
/** CSS for angle field. See css.js for use. */ fieldRegistry.register('field_angle', FieldAngle);
FieldAngle.prototype.DEFAULT_VALUE = 0;
/**
* CSS for angle field.
*/
Css.register(` Css.register(`
.blocklyAngleCircle { .blocklyAngleCircle {
stroke: #444; stroke: #444;
stroke-width: 1; stroke-width: 1;
fill: #ddd; fill: #ddd;
fill-opacity: .8; fill-opacity: 0.8;
} }
.blocklyAngleMarks { .blocklyAngleMarks {
@@ -480,7 +476,7 @@ Css.register(`
.blocklyAngleGauge { .blocklyAngleGauge {
fill: #f88; fill: #f88;
fill-opacity: .8; fill-opacity: 0.8;
pointer-events: none; pointer-events: none;
} }
@@ -492,10 +488,6 @@ Css.register(`
} }
`); `);
fieldRegistry.register('field_angle', FieldAngle);
FieldAngle.prototype.DEFAULT_VALUE = 0;
/** /**
* The two main modes of the angle field. * The two main modes of the angle field.
* Compass specifies: * Compass specifies:

View File

@@ -49,22 +49,22 @@ export class FieldCheckbox extends Field<CheckboxBool> {
override value_: boolean|null = this.value_; override value_: boolean|null = this.value_;
/** /**
* @param opt_value The initial value of the field. Should either be 'TRUE', * @param value The initial value of the field. Should either be 'TRUE',
* 'FALSE' or a boolean. Defaults to 'FALSE'. Also accepts * 'FALSE' or a boolean. Defaults to 'FALSE'. Also accepts
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses * Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting the field value after * that want to handle configuration and setting the field value after
* their own constructors have run). * their own constructors have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a value ('TRUE' or 'FALSE') & returns a * field's value. Takes in a value ('TRUE' or 'FALSE') & returns a
* validated value ('TRUE' or 'FALSE'), or null to abort the change. * validated value ('TRUE' or 'FALSE'), or null to abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: CheckboxBool|Sentinel, opt_validator?: FieldCheckboxValidator, value?: CheckboxBool|Sentinel, validator?: FieldCheckboxValidator,
opt_config?: FieldCheckboxConfig) { config?: FieldCheckboxConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
/** /**
@@ -73,13 +73,13 @@ export class FieldCheckbox extends Field<CheckboxBool> {
*/ */
this.checkChar_ = FieldCheckbox.CHECK_CHAR; this.checkChar_ = FieldCheckbox.CHECK_CHAR;
if (Field.isSentinel(opt_value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} }
this.setValue(opt_value); this.setValue(value);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -150,15 +150,15 @@ export class FieldCheckbox extends Field<CheckboxBool> {
/** /**
* Ensure that the input value is valid ('TRUE' or 'FALSE'). * Ensure that the input value is valid ('TRUE' or 'FALSE').
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A valid value ('TRUE' or 'FALSE), or null if invalid. * @returns A valid value ('TRUE' or 'FALSE), or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: AnyDuringMigration): protected override doClassValidation_(newValue?: AnyDuringMigration):
BoolString|null { BoolString|null {
if (opt_newValue === true || opt_newValue === 'TRUE') { if (newValue === true || newValue === 'TRUE') {
return 'TRUE'; return 'TRUE';
} }
if (opt_newValue === false || opt_newValue === 'FALSE') { if (newValue === false || newValue === 'FALSE') {
return 'FALSE'; return 'FALSE';
} }
return null; return null;

View File

@@ -25,7 +25,6 @@ import * as fieldRegistry from './field_registry.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import * as colour from './utils/colour.js'; import * as colour from './utils/colour.js';
import * as idGenerator from './utils/idgenerator.js'; import * as idGenerator from './utils/idgenerator.js';
import {KeyCodes} from './utils/keycodes.js';
import type {Sentinel} from './utils/sentinel.js'; import type {Sentinel} from './utils/sentinel.js';
import {Size} from './utils/size.js'; import {Size} from './utils/size.js';
@@ -76,25 +75,17 @@ export class FieldColour extends Field<string> {
static COLUMNS = 7; static COLUMNS = 7;
/** The field's colour picker element. */ /** The field's colour picker element. */
private picker_: HTMLElement|null = null; private picker: HTMLElement|null = null;
/** Index of the currently highlighted element. */ /** Index of the currently highlighted element. */
private highlightedIndex_: number|null = null; private highlightedIndex: number|null = null;
/** Mouse click event data. */ /**
private onClickWrapper_: browserEvents.Data|null = null; * Array holding info needed to unbind events.
* Used for disposing.
/** Mouse move event data. */ * Ex: [[node, name, func], [node, name, func]].
private onMouseMoveWrapper_: browserEvents.Data|null = null; */
private boundEvents: browserEvents.Data[] = [];
/** Mouse enter event data. */
private onMouseEnterWrapper_: browserEvents.Data|null = null;
/** Mouse leave event data. */
private onMouseLeaveWrapper_: browserEvents.Data|null = null;
/** Key down event data. */
private onKeyDownWrapper_: browserEvents.Data|null = null;
/** /**
* Serializable fields are saved by the serializer, non-serializable fields * Serializable fields are saved by the serializer, non-serializable fields
@@ -113,46 +104,46 @@ export class FieldColour extends Field<string> {
protected override isDirty_ = false; protected override isDirty_ = false;
/** Array of colours used by this field. If null, use the global list. */ /** Array of colours used by this field. If null, use the global list. */
private colours_: string[]|null = null; private colours: string[]|null = null;
/** /**
* Array of colour tooltips used by this field. If null, use the global * Array of colour tooltips used by this field. If null, use the global
* list. * list.
*/ */
private titles_: string[]|null = null; private titles: string[]|null = null;
/** /**
* Number of colour columns used by this field. If 0, use the global * Number of colour columns used by this field. If 0, use the global
* setting. By default use the global constants for columns. * setting. By default use the global constants for columns.
*/ */
private columns_ = 0; private columns = 0;
/** /**
* @param opt_value The initial value of the field. Should be in '#rrggbb' * @param value The initial value of the field. Should be in '#rrggbb'
* format. Defaults to the first value in the default colour array. Also * format. Defaults to the first value in the default colour array. Also
* accepts Field.SKIP_SETUP if you wish to skip setup (only used by * accepts Field.SKIP_SETUP if you wish to skip setup (only used by
* subclasses that want to handle configuration and setting the field * subclasses that want to handle configuration and setting the field
* value after their own constructors have run). * value after their own constructors have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a colour string & returns a validated colour * field's value. Takes in a colour string & returns a validated colour
* string ('#rrggbb' format), or null to abort the change.Blockly. * string ('#rrggbb' format), or null to abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: string|Sentinel, opt_validator?: FieldColourValidator, value?: string|Sentinel, validator?: FieldColourValidator,
opt_config?: FieldColourConfig) { config?: FieldColourConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} }
this.setValue(opt_value); this.setValue(value);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -163,9 +154,9 @@ export class FieldColour extends Field<string> {
*/ */
protected override configure_(config: FieldColourConfig) { protected override configure_(config: FieldColourConfig) {
super.configure_(config); super.configure_(config);
if (config.colourOptions) this.colours_ = config.colourOptions; if (config.colourOptions) this.colours = config.colourOptions;
if (config.colourTitles) this.titles_ = config.colourTitles; if (config.colourTitles) this.titles = config.colourTitles;
if (config.columns) this.columns_ = config.columns; if (config.columns) this.columns = config.columns;
} }
/** /**
@@ -185,6 +176,11 @@ export class FieldColour extends Field<string> {
} }
} }
/**
* Updates text field to match the colour/style of the block.
*
* @internal
*/
override applyColour() { override applyColour() {
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) { if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
if (this.borderRect_) { if (this.borderRect_) {
@@ -200,14 +196,14 @@ export class FieldColour extends Field<string> {
/** /**
* Ensure that the input value is a valid colour. * Ensure that the input value is a valid colour.
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A valid colour, or null if invalid. * @returns A valid colour, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: any): string|null { protected override doClassValidation_(newValue?: any): string|null {
if (typeof opt_newValue !== 'string') { if (typeof newValue !== 'string') {
return null; return null;
} }
return colour.parse(opt_newValue); return colour.parse(newValue);
} }
/** /**
@@ -247,14 +243,14 @@ export class FieldColour extends Field<string> {
* *
* @param colours Array of colours for this block, or null to use default * @param colours Array of colours for this block, or null to use default
* (FieldColour.COLOURS). * (FieldColour.COLOURS).
* @param opt_titles Optional array of colour tooltips, or null to use default * @param titles Optional array of colour tooltips, or null to use default
* (FieldColour.TITLES). * (FieldColour.TITLES).
* @returns Returns itself (for method chaining). * @returns Returns itself (for method chaining).
*/ */
setColours(colours: string[], opt_titles?: string[]): FieldColour { setColours(colours: string[], titles?: string[]): FieldColour {
this.colours_ = colours; this.colours = colours;
if (opt_titles) { if (titles) {
this.titles_ = opt_titles; this.titles = titles;
} }
return this; return this;
} }
@@ -267,19 +263,19 @@ export class FieldColour extends Field<string> {
* @returns Returns itself (for method chaining). * @returns Returns itself (for method chaining).
*/ */
setColumns(columns: number): FieldColour { setColumns(columns: number): FieldColour {
this.columns_ = columns; this.columns = columns;
return this; return this;
} }
/** Create and show the colour field's editor. */ /** Create and show the colour field's editor. */
protected override showEditor_() { protected override showEditor_() {
this.dropdownCreate_(); this.dropdownCreate();
dropDownDiv.getContentDiv().appendChild(this.picker_!); dropDownDiv.getContentDiv().appendChild(this.picker!);
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this)); dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
// Focus so we can start receiving keyboard events. // Focus so we can start receiving keyboard events.
this.picker_!.focus({preventScroll: true}); this.picker!.focus({preventScroll: true});
} }
/** /**
@@ -287,7 +283,7 @@ export class FieldColour extends Field<string> {
* *
* @param e Mouse event. * @param e Mouse event.
*/ */
private onClick_(e: PointerEvent) { private onClick(e: PointerEvent) {
const cell = e.target as Element; const cell = e.target as Element;
const colour = cell && cell.getAttribute('data-colour'); const colour = cell && cell.getAttribute('data-colour');
if (colour !== null) { if (colour !== null) {
@@ -302,31 +298,35 @@ export class FieldColour extends Field<string> {
* *
* @param e Keyboard event. * @param e Keyboard event.
*/ */
private onKeyDown_(e: KeyboardEvent) { private onKeyDown(e: KeyboardEvent) {
let handled = false; let handled = true;
if (e.keyCode === KeyCodes.UP) { let highlighted: HTMLElement|null;
this.moveHighlightBy_(0, -1); switch (e.key) {
handled = true; case 'ArrowUp':
} else if (e.keyCode === KeyCodes.DOWN) { this.moveHighlightBy(0, -1);
this.moveHighlightBy_(0, 1); break;
handled = true; case 'ArrowDown':
} else if (e.keyCode === KeyCodes.LEFT) { this.moveHighlightBy(0, 1);
this.moveHighlightBy_(-1, 0); break;
handled = true; case 'ArrowLeft':
} else if (e.keyCode === KeyCodes.RIGHT) { this.moveHighlightBy(-1, 0);
this.moveHighlightBy_(1, 0); break;
handled = true; case 'ArrowRight':
} else if (e.keyCode === KeyCodes.ENTER) { this.moveHighlightBy(1, 0);
// Select the highlighted colour. break;
const highlighted = this.getHighlighted_(); case 'Enter':
if (highlighted) { // Select the highlighted colour.
const colour = highlighted && highlighted.getAttribute('data-colour'); highlighted = this.getHighlighted();
if (colour !== null) { if (highlighted) {
this.setValue(colour); const colour = highlighted.getAttribute('data-colour');
if (colour !== null) {
this.setValue(colour);
}
} }
} dropDownDiv.hideWithoutAnimation();
dropDownDiv.hideWithoutAnimation(); break;
handled = true; default:
handled = false;
} }
if (handled) { if (handled) {
e.stopPropagation(); e.stopPropagation();
@@ -336,22 +336,22 @@ export class FieldColour extends Field<string> {
/** /**
* Move the currently highlighted position by dx and dy. * Move the currently highlighted position by dx and dy.
* *
* @param dx Change of x * @param dx Change of x.
* @param dy Change of y * @param dy Change of y.
*/ */
private moveHighlightBy_(dx: number, dy: number) { private moveHighlightBy(dx: number, dy: number) {
if (!this.highlightedIndex_) { if (!this.highlightedIndex) {
return; return;
} }
const colours = this.colours_ || FieldColour.COLOURS; const colours = this.colours || FieldColour.COLOURS;
const columns = this.columns_ || FieldColour.COLUMNS; const columns = this.columns || FieldColour.COLUMNS;
// Get the current x and y coordinates // Get the current x and y coordinates.
let x = this.highlightedIndex_ % columns; let x = this.highlightedIndex % columns;
let y = Math.floor(this.highlightedIndex_ / columns); let y = Math.floor(this.highlightedIndex / columns);
// Add the offset // Add the offset.
x += dx; x += dx;
y += dy; y += dy;
@@ -386,9 +386,9 @@ export class FieldColour extends Field<string> {
} }
// Move the highlight to the new coordinates. // Move the highlight to the new coordinates.
const cell = this.picker_!.childNodes[y].childNodes[x] as Element; const cell = this.picker!.childNodes[y].childNodes[x] as Element;
const index = y * columns + x; const index = y * columns + x;
this.setHighlightedCell_(cell, index); this.setHighlightedCell(cell, index);
} }
/** /**
@@ -396,26 +396,26 @@ export class FieldColour extends Field<string> {
* *
* @param e Mouse event. * @param e Mouse event.
*/ */
private onMouseMove_(e: PointerEvent) { private onMouseMove(e: PointerEvent) {
const cell = e.target as Element; const cell = e.target as Element;
const index = cell && Number(cell.getAttribute('data-index')); const index = cell && Number(cell.getAttribute('data-index'));
if (index !== null && index !== this.highlightedIndex_) { if (index !== null && index !== this.highlightedIndex) {
this.setHighlightedCell_(cell, index); this.setHighlightedCell(cell, index);
} }
} }
/** Handle a mouse enter event. Focus the picker. */ /** Handle a mouse enter event. Focus the picker. */
private onMouseEnter_() { private onMouseEnter() {
this.picker_?.focus({preventScroll: true}); this.picker?.focus({preventScroll: true});
} }
/** /**
* Handle a mouse leave event. Blur the picker and unhighlight * Handle a mouse leave event. Blur the picker and unhighlight
* the currently highlighted colour. * the currently highlighted colour.
*/ */
private onMouseLeave_() { private onMouseLeave() {
this.picker_?.blur(); this.picker?.blur();
const highlighted = this.getHighlighted_(); const highlighted = this.getHighlighted();
if (highlighted) { if (highlighted) {
dom.removeClass(highlighted, 'blocklyColourHighlighted'); dom.removeClass(highlighted, 'blocklyColourHighlighted');
} }
@@ -426,54 +426,53 @@ export class FieldColour extends Field<string> {
* *
* @returns Highlighted item (null if none). * @returns Highlighted item (null if none).
*/ */
private getHighlighted_(): HTMLElement|null { private getHighlighted(): HTMLElement|null {
if (!this.highlightedIndex_) { if (!this.highlightedIndex) {
return null; return null;
} }
const columns = this.columns_ || FieldColour.COLUMNS; const columns = this.columns || FieldColour.COLUMNS;
const x = this.highlightedIndex_ % columns; const x = this.highlightedIndex % columns;
const y = Math.floor(this.highlightedIndex_ / columns); const y = Math.floor(this.highlightedIndex / columns);
const row = this.picker_!.childNodes[y]; const row = this.picker!.childNodes[y];
if (!row) { if (!row) {
return null; return null;
} }
const col = row.childNodes[x] as HTMLElement; return row.childNodes[x] as HTMLElement;
return col;
} }
/** /**
* Update the currently highlighted cell. * Update the currently highlighted cell.
* *
* @param cell the new cell to highlight * @param cell The new cell to highlight.
* @param index the index of the new cell * @param index The index of the new cell.
*/ */
private setHighlightedCell_(cell: Element, index: number) { private setHighlightedCell(cell: Element, index: number) {
// Unhighlight the current item. // Unhighlight the current item.
const highlighted = this.getHighlighted_(); const highlighted = this.getHighlighted();
if (highlighted) { if (highlighted) {
dom.removeClass(highlighted, 'blocklyColourHighlighted'); dom.removeClass(highlighted, 'blocklyColourHighlighted');
} }
// Highlight new item. // Highlight new item.
dom.addClass(cell, 'blocklyColourHighlighted'); dom.addClass(cell, 'blocklyColourHighlighted');
// Set new highlighted index. // Set new highlighted index.
this.highlightedIndex_ = index; this.highlightedIndex = index;
// Update accessibility roles. // Update accessibility roles.
const cellId = cell.getAttribute('id'); const cellId = cell.getAttribute('id');
if (cellId && this.picker_) { if (cellId && this.picker) {
aria.setState(this.picker_, aria.State.ACTIVEDESCENDANT, cellId); aria.setState(this.picker, aria.State.ACTIVEDESCENDANT, cellId);
} }
} }
/** Create a colour picker dropdown editor. */ /** Create a colour picker dropdown editor. */
private dropdownCreate_() { private dropdownCreate() {
const columns = this.columns_ || FieldColour.COLUMNS; const columns = this.columns || FieldColour.COLUMNS;
const colours = this.colours_ || FieldColour.COLOURS; const colours = this.colours || FieldColour.COLOURS;
const titles = this.titles_ || FieldColour.TITLES; const titles = this.titles || FieldColour.TITLES;
const selectedColour = this.getValue(); const selectedColour = this.getValue();
// Create the palette. // Create the palette.
const table = (document.createElement('table')); const table = document.createElement('table');
table.className = 'blocklyColourTable'; table.className = 'blocklyColourTable';
table.tabIndex = 0; table.tabIndex = 0;
table.dir = 'ltr'; table.dir = 'ltr';
@@ -495,56 +494,40 @@ export class FieldColour extends Field<string> {
cell.setAttribute('data-colour', colours[i]); cell.setAttribute('data-colour', colours[i]);
cell.title = titles[i] || colours[i]; cell.title = titles[i] || colours[i];
cell.id = idGenerator.getNextUniqueId(); cell.id = idGenerator.getNextUniqueId();
cell.setAttribute('data-index', String(i)); cell.setAttribute('data-index', `${i}`);
aria.setRole(cell, aria.Role.GRIDCELL); aria.setRole(cell, aria.Role.GRIDCELL);
aria.setState(cell, aria.State.LABEL, colours[i]); aria.setState(cell, aria.State.LABEL, colours[i]);
aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour); aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour);
cell.style.backgroundColor = colours[i]; cell.style.backgroundColor = colours[i];
if (colours[i] === selectedColour) { if (colours[i] === selectedColour) {
cell.className = 'blocklyColourSelected'; cell.className = 'blocklyColourSelected';
this.highlightedIndex_ = i; this.highlightedIndex = i;
} }
} }
// Configure event handler on the table to listen for any event in a cell. // Configure event handler on the table to listen for any event in a cell.
this.onClickWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerdown', this, this.onClick_, true); table, 'pointerdown', this, this.onClick, true));
this.onMouseMoveWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointermove', this, this.onMouseMove_, true); table, 'pointermove', this, this.onMouseMove, true));
this.onMouseEnterWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerenter', this, this.onMouseEnter_, true); table, 'pointerenter', this, this.onMouseEnter, true));
this.onMouseLeaveWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerleave', this, this.onMouseLeave_, true); table, 'pointerleave', this, this.onMouseLeave, true));
this.onKeyDownWrapper_ = this.boundEvents.push(browserEvents.conditionalBind(
browserEvents.conditionalBind(table, 'keydown', this, this.onKeyDown_); table, 'keydown', this, this.onKeyDown, false));
this.picker_ = table; this.picker = table;
} }
/** Disposes of events and DOM-references belonging to the colour editor. */ /** Disposes of events and DOM-references belonging to the colour editor. */
private dropdownDispose_() { private dropdownDispose() {
if (this.onClickWrapper_) { for (const event of this.boundEvents) {
browserEvents.unbind(this.onClickWrapper_); browserEvents.unbind(event);
this.onClickWrapper_ = null;
} }
if (this.onMouseMoveWrapper_) { this.boundEvents.length = 0;
browserEvents.unbind(this.onMouseMoveWrapper_); this.picker = null;
this.onMouseMoveWrapper_ = null; this.highlightedIndex = null;
}
if (this.onMouseEnterWrapper_) {
browserEvents.unbind(this.onMouseEnterWrapper_);
this.onMouseEnterWrapper_ = null;
}
if (this.onMouseLeaveWrapper_) {
browserEvents.unbind(this.onMouseLeaveWrapper_);
this.onMouseLeaveWrapper_ = null;
}
if (this.onKeyDownWrapper_) {
browserEvents.unbind(this.onKeyDownWrapper_);
this.onKeyDownWrapper_ = null;
}
this.picker_ = null;
this.highlightedIndex_ = null;
} }
/** /**
@@ -565,7 +548,12 @@ export class FieldColour extends Field<string> {
/** The default value for this field. */ /** The default value for this field. */
FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0]; FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0];
/** CSS for colour picker. See css.js for use. */ fieldRegistry.register('field_colour', FieldColour);
/**
* CSS for colour picker.
*/
Css.register(` Css.register(`
.blocklyColourTable { .blocklyColourTable {
border-collapse: collapse; border-collapse: collapse;
@@ -575,7 +563,7 @@ Css.register(`
} }
.blocklyColourTable>tr>td { .blocklyColourTable>tr>td {
border: .5px solid #888; border: 0.5px solid #888;
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
@@ -586,7 +574,7 @@ Css.register(`
.blocklyColourTable>tr>td.blocklyColourHighlighted { .blocklyColourTable>tr>td.blocklyColourHighlighted {
border-color: #eee; border-color: #eee;
box-shadow: 2px 2px 7px 2px rgba(0,0,0,.3); box-shadow: 2px 2px 7px 2px rgba(0, 0, 0, 0.3);
position: relative; position: relative;
} }
@@ -597,8 +585,6 @@ Css.register(`
} }
`); `);
fieldRegistry.register('field_colour', FieldColour);
/** /**
* Config options for the colour field. * Config options for the colour field.
*/ */

View File

@@ -97,11 +97,11 @@ export class FieldDropdown extends Field<string> {
* if you wish to skip setup (only used by subclasses that want to handle * if you wish to skip setup (only used by subclasses that want to handle
* configuration and setting the field value after their own constructors * configuration and setting the field value after their own constructors
* have run). * have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a language-neutral dropdown option & returns a * field's value. Takes in a language-neutral dropdown option & returns a
* validated language-neutral dropdown option, or null to abort the * validated language-neutral dropdown option, or null to abort the
* change. * change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
@@ -109,14 +109,14 @@ export class FieldDropdown extends Field<string> {
*/ */
constructor( constructor(
menuGenerator: MenuGenerator, menuGenerator: MenuGenerator,
opt_validator?: FieldDropdownValidator, validator?: FieldDropdownValidator,
opt_config?: FieldDropdownConfig, config?: FieldDropdownConfig,
); );
constructor(menuGenerator: Sentinel); constructor(menuGenerator: Sentinel);
constructor( constructor(
menuGenerator: MenuGenerator|Sentinel, menuGenerator: MenuGenerator|Sentinel,
opt_validator?: FieldDropdownValidator, validator?: FieldDropdownValidator,
opt_config?: FieldDropdownConfig, config?: FieldDropdownConfig,
) { ) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
@@ -139,12 +139,12 @@ export class FieldDropdown extends Field<string> {
*/ */
this.selectedOption_ = this.getOptions(false)[0]; this.selectedOption_ = this.getOptions(false)[0];
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} }
this.setValue(this.selectedOption_[1]); this.setValue(this.selectedOption_[1]);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -244,17 +244,17 @@ export class FieldDropdown extends Field<string> {
/** /**
* Create a dropdown menu under the text. * Create a dropdown menu under the text.
* *
* @param opt_e Optional mouse event that triggered the field to open, or * @param e Optional mouse event that triggered the field to open, or
* undefined if triggered programmatically. * undefined if triggered programmatically.
*/ */
protected override showEditor_(opt_e?: MouseEvent) { protected override showEditor_(e?: MouseEvent) {
const block = this.getSourceBlock(); const block = this.getSourceBlock();
if (!block) { if (!block) {
throw new UnattachedFieldError(); throw new UnattachedFieldError();
} }
this.dropdownCreate_(); this.dropdownCreate_();
if (opt_e && typeof opt_e.clientX === 'number') { if (e && typeof e.clientX === 'number') {
this.menu_!.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY); this.menu_!.openingCoords = new Coordinate(e.clientX, e.clientY);
} else { } else {
this.menu_!.openingCoords = null; this.menu_!.openingCoords = null;
} }
@@ -368,20 +368,20 @@ export class FieldDropdown extends Field<string> {
/** /**
* Return a list of the options for this dropdown. * Return a list of the options for this dropdown.
* *
* @param opt_useCache For dynamic options, whether or not to use the cached * @param useCache For dynamic options, whether or not to use the cached
* options or to re-generate them. * options or to re-generate them.
* @returns A non-empty array of option tuples: * @returns A non-empty array of option tuples:
* (human-readable text or image, language-neutral name). * (human-readable text or image, language-neutral name).
* @throws {TypeError} If generated options are incorrectly structured. * @throws {TypeError} If generated options are incorrectly structured.
*/ */
getOptions(opt_useCache?: boolean): MenuOption[] { getOptions(useCache?: boolean): MenuOption[] {
if (!this.menuGenerator_) { if (!this.menuGenerator_) {
// A subclass improperly skipped setup without defining the menu // A subclass improperly skipped setup without defining the menu
// generator. // generator.
throw TypeError('A menu generator was never defined.'); throw TypeError('A menu generator was never defined.');
} }
if (Array.isArray(this.menuGenerator_)) return this.menuGenerator_; if (Array.isArray(this.menuGenerator_)) return this.menuGenerator_;
if (opt_useCache && this.generatedOptions_) return this.generatedOptions_; if (useCache && this.generatedOptions_) return this.generatedOptions_;
this.generatedOptions_ = this.menuGenerator_(); this.generatedOptions_ = this.menuGenerator_();
validateOptions(this.generatedOptions_); validateOptions(this.generatedOptions_);
@@ -391,23 +391,23 @@ export class FieldDropdown extends Field<string> {
/** /**
* Ensure that the input value is a valid language-neutral option. * Ensure that the input value is a valid language-neutral option.
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A valid language-neutral option, or null if invalid. * @returns A valid language-neutral option, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: string): string|null { protected override doClassValidation_(newValue?: string): string|null {
const options = this.getOptions(true); const options = this.getOptions(true);
const isValueValid = options.some((option) => option[1] === opt_newValue); const isValueValid = options.some((option) => option[1] === newValue);
if (!isValueValid) { if (!isValueValid) {
if (this.sourceBlock_) { if (this.sourceBlock_) {
console.warn( console.warn(
'Cannot set the dropdown\'s value to an unavailable option.' + 'Cannot set the dropdown\'s value to an unavailable option.' +
' Block type: ' + this.sourceBlock_.type + ' Block type: ' + this.sourceBlock_.type +
', Field name: ' + this.name + ', Value: ' + opt_newValue); ', Field name: ' + this.name + ', Value: ' + newValue);
} }
return null; return null;
} }
return opt_newValue as string; return newValue as string;
} }
/** /**
@@ -481,8 +481,8 @@ export class FieldDropdown extends Field<string> {
this.imageElement_!.style.display = ''; this.imageElement_!.style.display = '';
this.imageElement_!.setAttributeNS( this.imageElement_!.setAttributeNS(
dom.XLINK_NS, 'xlink:href', imageJson.src); dom.XLINK_NS, 'xlink:href', imageJson.src);
this.imageElement_!.setAttribute('height', `${imageJson.height}`); this.imageElement_!.setAttribute('height', String(imageJson.height));
this.imageElement_!.setAttribute('width', `${imageJson.width}`); this.imageElement_!.setAttribute('width', String(imageJson.width));
const imageHeight = Number(imageJson.height); const imageHeight = Number(imageJson.height);
const imageWidth = Number(imageJson.width); const imageWidth = Number(imageJson.width);
@@ -512,14 +512,13 @@ export class FieldDropdown extends Field<string> {
let arrowX = 0; let arrowX = 0;
if (block.RTL) { if (block.RTL) {
const imageX = xPadding + arrowWidth; const imageX = xPadding + arrowWidth;
this.imageElement_!.setAttribute('x', imageX.toString()); this.imageElement_!.setAttribute('x', `${imageX}`);
} else { } else {
arrowX = imageWidth + arrowWidth; arrowX = imageWidth + arrowWidth;
this.getTextElement().setAttribute('text-anchor', 'end'); this.getTextElement().setAttribute('text-anchor', 'end');
this.imageElement_!.setAttribute('x', xPadding.toString()); this.imageElement_!.setAttribute('x', `${xPadding}`);
} }
this.imageElement_!.setAttribute( this.imageElement_!.setAttribute('y', String(height / 2 - imageHeight / 2));
'y', (height / 2 - imageHeight / 2).toString());
this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth); this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth);
} }

View File

@@ -64,19 +64,19 @@ export class FieldImage extends Field<string> {
* after their own constructors have run). * after their own constructors have run).
* @param width Width of the image. * @param width Width of the image.
* @param height Height of the image. * @param height Height of the image.
* @param opt_alt Optional alt text for when block is collapsed. * @param alt Optional alt text for when block is collapsed.
* @param opt_onClick Optional function to be called when the image is * @param onClick Optional function to be called when the image is
* clicked. If opt_onClick is defined, opt_alt must also be defined. * clicked. If onClick is defined, alt must also be defined.
* @param opt_flipRtl Whether to flip the icon in RTL. * @param flipRtl Whether to flip the icon in RTL.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
src: string|Sentinel, width: string|number, height: string|number, src: string|Sentinel, width: string|number, height: string|number,
opt_alt?: string, opt_onClick?: (p1: FieldImage) => void, alt?: string, onClick?: (p1: FieldImage) => void, flipRtl?: boolean,
opt_flipRtl?: boolean, opt_config?: FieldImageConfig) { config?: FieldImageConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
const imageHeight = Number(parsing.replaceMessageReferences(height)); const imageHeight = Number(parsing.replaceMessageReferences(height));
@@ -100,19 +100,19 @@ export class FieldImage extends Field<string> {
*/ */
this.imageHeight_ = imageHeight; this.imageHeight_ = imageHeight;
if (typeof opt_onClick === 'function') { if (typeof onClick === 'function') {
this.clickHandler_ = opt_onClick; this.clickHandler_ = onClick;
} }
if (src === Field.SKIP_SETUP) { if (src === Field.SKIP_SETUP) {
return; return;
} }
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} else { } else {
this.flipRtl_ = !!opt_flipRtl; this.flipRtl_ = !!flipRtl;
this.altText_ = parsing.replaceMessageReferences(opt_alt) || ''; this.altText_ = parsing.replaceMessageReferences(alt) || '';
} }
this.setValue(parsing.replaceMessageReferences(src)); this.setValue(parsing.replaceMessageReferences(src));
} }
@@ -157,14 +157,14 @@ export class FieldImage extends Field<string> {
/** /**
* Ensure that the input value (the source URL) is a string. * Ensure that the input value (the source URL) is a string.
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A string, or null if invalid. * @returns A string, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: any): string|null { protected override doClassValidation_(newValue?: any): string|null {
if (typeof opt_newValue !== 'string') { if (typeof newValue !== 'string') {
return null; return null;
} }
return opt_newValue; return newValue;
} }
/** /**
@@ -177,7 +177,7 @@ export class FieldImage extends Field<string> {
this.value_ = newValue; this.value_ = newValue;
if (this.imageElement_) { if (this.imageElement_) {
this.imageElement_.setAttributeNS( this.imageElement_.setAttributeNS(
dom.XLINK_NS, 'xlink:href', String(this.value_)); dom.XLINK_NS, 'xlink:href', this.value_);
} }
} }

View File

@@ -25,7 +25,6 @@ import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.
import {Msg} from './msg.js'; import {Msg} from './msg.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import {KeyCodes} from './utils/keycodes.js';
import type {Sentinel} from './utils/sentinel.js'; import type {Sentinel} from './utils/sentinel.js';
import * as userAgent from './utils/useragent.js'; import * as userAgent from './utils/useragent.js';
import * as WidgetDiv from './widgetdiv.js'; import * as WidgetDiv from './widgetdiv.js';
@@ -90,31 +89,31 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
override CURSOR = 'text'; override CURSOR = 'text';
/** /**
* @param opt_value The initial value of the field. Should cast to a string. * @param value The initial value of the field. Should cast to a string.
* Defaults to an empty string if null or undefined. Also accepts * Defaults to an empty string if null or undefined. Also accepts
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses * Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting the field value after * that want to handle configuration and setting the field value after
* their own constructors have run). * their own constructors have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a string & returns a validated string, or null * field's value. Takes in a string & returns a validated string, or null
* to abort the change. * to abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: string|Sentinel, opt_validator?: FieldInputValidator<T>|null, value?: string|Sentinel, validator?: FieldInputValidator<T>|null,
opt_config?: FieldInputConfig) { config?: FieldInputConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} }
this.setValue(opt_value); this.setValue(value);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -262,14 +261,13 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* Shows a prompt editor for mobile browsers if the modalInputs option is * Shows a prompt editor for mobile browsers if the modalInputs option is
* enabled. * enabled.
* *
* @param _opt_e Optional mouse event that triggered the field to open, or * @param _e Optional mouse event that triggered the field to open, or
* undefined if triggered programmatically. * undefined if triggered programmatically.
* @param opt_quietInput True if editor should be created without focus. * @param quietInput True if editor should be created without focus.
* Defaults to false. * Defaults to false.
*/ */
protected override showEditor_(_opt_e?: Event, opt_quietInput?: boolean) { protected override showEditor_(_e?: Event, quietInput = false) {
this.workspace_ = (this.sourceBlock_ as BlockSvg).workspace; this.workspace_ = (this.sourceBlock_ as BlockSvg).workspace;
const quietInput = opt_quietInput || false;
if (!quietInput && this.workspace_.options.modalInputs && if (!quietInput && this.workspace_.options.modalInputs &&
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) { (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
this.showPromptEditor_(); this.showPromptEditor_();
@@ -358,7 +356,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
div!.style.transition = 'box-shadow 0.25s ease 0s'; div!.style.transition = 'box-shadow 0.25s ease 0s';
if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) { if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) {
div!.style.boxShadow = div!.style.boxShadow =
'rgba(255, 255, 255, 0.3) 0 0 0 ' + 4 * scale + 'px'; 'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px';
} }
} }
htmlInput.style.borderRadius = borderRadius; htmlInput.style.borderRadius = borderRadius;
@@ -444,15 +442,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* @param e Keyboard event. * @param e Keyboard event.
*/ */
protected onHtmlInputKeyDown_(e: KeyboardEvent) { protected onHtmlInputKeyDown_(e: KeyboardEvent) {
if (e.keyCode === KeyCodes.ENTER) { if (e.key === 'Enter') {
WidgetDiv.hide(); WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation(); dropDownDiv.hideWithoutAnimation();
} else if (e.keyCode === KeyCodes.ESC) { } else if (e.key === 'Esc') {
this.setValue( this.setValue(
this.htmlInput_!.getAttribute('data-untyped-default-value')); this.htmlInput_!.getAttribute('data-untyped-default-value'));
WidgetDiv.hide(); WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation(); dropDownDiv.hideWithoutAnimation();
} else if (e.keyCode === KeyCodes.TAB) { } else if (e.key === 'Tab') {
WidgetDiv.hide(); WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation(); dropDownDiv.hideWithoutAnimation();
(this.sourceBlock_ as BlockSvg).tab(this, !e.shiftKey); (this.sourceBlock_ as BlockSvg).tab(this, !e.shiftKey);
@@ -543,7 +541,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* @returns The text to show on the HTML input. * @returns The text to show on the HTML input.
*/ */
protected getEditorText_(value: AnyDuringMigration): string { protected getEditorText_(value: AnyDuringMigration): string {
return String(value); return `${value}`;
} }
/** /**

View File

@@ -23,7 +23,7 @@ import type {Sentinel} from './utils/sentinel.js';
* Class for a non-editable, non-serializable text field. * Class for a non-editable, non-serializable text field.
*/ */
export class FieldLabel extends Field<string> { export class FieldLabel extends Field<string> {
/** The html class name to use for this field. */ /** The HTML class name to use for this field. */
private class_: string|null = null; private class_: string|null = null;
/** /**
@@ -33,29 +33,28 @@ export class FieldLabel extends Field<string> {
override EDITABLE = false; override EDITABLE = false;
/** /**
* @param opt_value The initial value of the field. Should cast to a string. * @param value The initial value of the field. Should cast to a string.
* Defaults to an empty string if null or undefined. Also accepts * Defaults to an empty string if null or undefined. Also accepts
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses * Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting the field value after * that want to handle configuration and setting the field value after
* their own constructors have run). * their own constructors have run).
* @param opt_class Optional CSS class for the field's text. * @param textClass Optional CSS class for the field's text.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: string|Sentinel, opt_class?: string, value?: string|Sentinel, textClass?: string, config?: FieldLabelConfig) {
opt_config?: FieldLabelConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} else { } else {
this.class_ = opt_class || null; this.class_ = textClass || null;
} }
this.setValue(opt_value); this.setValue(value);
} }
protected override configure_(config: FieldLabelConfig) { protected override configure_(config: FieldLabelConfig) {
@@ -78,15 +77,15 @@ export class FieldLabel extends Field<string> {
/** /**
* Ensure that the input value casts to a valid string. * Ensure that the input value casts to a valid string.
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A valid string, or null if invalid. * @returns A valid string, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: AnyDuringMigration): protected override doClassValidation_(newValue?: AnyDuringMigration): string
string|null { |null {
if (opt_newValue === null || opt_newValue === undefined) { if (newValue === null || newValue === undefined) {
return null; return null;
} }
return String(opt_newValue); return `${newValue}`;
} }
/** /**

View File

@@ -36,17 +36,16 @@ export class FieldLabelSerializable extends FieldLabel {
override SERIALIZABLE = true; override SERIALIZABLE = true;
/** /**
* @param opt_value The initial value of the field. Should cast to a string. * @param value The initial value of the field. Should cast to a string.
* Defaults to an empty string if null or undefined. * Defaults to an empty string if null or undefined.
* @param opt_class Optional CSS class for the field's text. * @param textClass Optional CSS class for the field's text.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(value?: string, textClass?: string, config?: FieldLabelConfig) {
opt_value?: string, opt_class?: string, opt_config?: FieldLabelConfig) { super(String(value ?? ''), textClass, config);
super(String(opt_value ?? ''), opt_class, opt_config);
} }
/** /**

View File

@@ -18,7 +18,6 @@ import * as fieldRegistry from './field_registry.js';
import {FieldTextInput, FieldTextInputConfig, FieldTextInputValidator} from './field_textinput.js'; import {FieldTextInput, FieldTextInputConfig, FieldTextInputValidator} from './field_textinput.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';
import {KeyCodes} from './utils/keycodes.js';
import * as parsing from './utils/parsing.js'; import * as parsing from './utils/parsing.js';
import type {Sentinel} from './utils/sentinel.js'; import type {Sentinel} from './utils/sentinel.js';
import {Svg} from './utils/svg.js'; import {Svg} from './utils/svg.js';
@@ -33,9 +32,7 @@ export class FieldMultilineInput extends FieldTextInput {
* The SVG group element that will contain a text element for each text row * The SVG group element that will contain a text element for each text row
* when initialized. * when initialized.
*/ */
// AnyDuringMigration because: Type 'null' is not assignable to type textGroup: SVGGElement|null = null;
// 'SVGGElement'.
textGroup_: SVGGElement = null as AnyDuringMigration;
/** /**
* Defines the maximum number of lines of field. * Defines the maximum number of lines of field.
@@ -47,35 +44,40 @@ export class FieldMultilineInput extends FieldTextInput {
protected isOverflowedY_ = false; protected isOverflowedY_ = false;
/** /**
* @param opt_value The initial content of the field. Should cast to a string. * @param value The initial content of the field. Should cast to a string.
* Defaults to an empty string if null or undefined. Also accepts * Defaults to an empty string if null or undefined. Also accepts
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses * Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting the field value after * that want to handle configuration and setting the field value after
* their own constructors have run). * their own constructors have run).
* @param opt_validator An optional function that is called to validate any * @param validator An optional function that is called to validate any
* constraints on what the user entered. Takes the new text as an * constraints on what the user entered. Takes the new text as an
* argument and returns either the accepted text, a replacement text, or * argument and returns either the accepted text, a replacement text, or
* null to abort the change. * null to abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: string|Sentinel, opt_validator?: FieldMultilineInputValidator, value?: string|Sentinel, validator?: FieldMultilineInputValidator,
opt_config?: FieldMultilineInputConfig) { config?: FieldMultilineInputConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} }
this.setValue(opt_value); this.setValue(value);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
/**
* Configure the field based on the given map of options.
*
* @param config A map of options to configure the field based on.
*/
protected override configure_(config: FieldMultilineInputConfig) { protected override configure_(config: FieldMultilineInputConfig) {
super.configure_(config); super.configure_(config);
if (config.maxLines) this.setMaxLines(config.maxLines); if (config.maxLines) this.setMaxLines(config.maxLines);
@@ -112,6 +114,8 @@ export class FieldMultilineInput extends FieldTextInput {
/** /**
* Saves this field's value. * Saves this field's value.
* This function only exists for subclasses of FieldMultilineInput which
* predate the load/saveState API and only define to/fromXml.
* *
* @returns The state of this field. * @returns The state of this field.
* @internal * @internal
@@ -126,6 +130,8 @@ export class FieldMultilineInput extends FieldTextInput {
/** /**
* Sets the field's value based on the given state. * Sets the field's value based on the given state.
* This function only exists for subclasses of FieldMultilineInput which
* predate the load/saveState API and only define to/fromXml.
* *
* @param state The state of the variable to assign to this variable field. * @param state The state of the variable to assign to this variable field.
* @internal * @internal
@@ -144,7 +150,7 @@ export class FieldMultilineInput extends FieldTextInput {
*/ */
override initView() { override initView() {
this.createBorderRect_(); this.createBorderRect_();
this.textGroup_ = dom.createSvgElement( this.textGroup = dom.createSvgElement(
Svg.G, { Svg.G, {
'class': 'blocklyEditableText', 'class': 'blocklyEditableText',
}, },
@@ -219,8 +225,9 @@ export class FieldMultilineInput extends FieldTextInput {
} }
// Remove all text group children. // Remove all text group children.
let currentChild; let currentChild;
while (currentChild = this.textGroup_.firstChild) { const textGroup = this.textGroup;
this.textGroup_.removeChild(currentChild); while (currentChild = textGroup!.firstChild) {
textGroup!.removeChild(currentChild);
} }
// Add in text elements into the group. // Add in text elements into the group.
@@ -236,7 +243,7 @@ export class FieldMultilineInput extends FieldTextInput {
'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING, 'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING,
'dy': this.getConstants()!.FIELD_TEXT_BASELINE, 'dy': this.getConstants()!.FIELD_TEXT_BASELINE,
}, },
this.textGroup_); textGroup);
span.appendChild(document.createTextNode(lines[i])); span.appendChild(document.createTextNode(lines[i]));
y += lineHeight; y += lineHeight;
} }
@@ -274,7 +281,7 @@ export class FieldMultilineInput extends FieldTextInput {
/** Updates the size of the field based on the text. */ /** Updates the size of the field based on the text. */
protected override updateSize_() { protected override updateSize_() {
const nodes = this.textGroup_.childNodes; const nodes = (this.textGroup as SVGElement).childNodes;
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE; const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE;
const fontWeight = this.getConstants()!.FIELD_TEXT_FONTWEIGHT; const fontWeight = this.getConstants()!.FIELD_TEXT_FONTWEIGHT;
const fontFamily = this.getConstants()!.FIELD_TEXT_FONTFAMILY; const fontFamily = this.getConstants()!.FIELD_TEXT_FONTFAMILY;
@@ -320,13 +327,8 @@ export class FieldMultilineInput extends FieldTextInput {
if (this.borderRect_) { if (this.borderRect_) {
totalHeight += this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * 2; totalHeight += this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * 2;
totalWidth += this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * 2; totalWidth += this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * 2;
// AnyDuringMigration because: Argument of type 'number' is not this.borderRect_.setAttribute('width', `${totalWidth}`);
// assignable to parameter of type 'string'. this.borderRect_.setAttribute('height', `${totalHeight}`);
this.borderRect_.setAttribute('width', totalWidth as AnyDuringMigration);
// AnyDuringMigration because: Argument of type 'number' is not
// assignable to parameter of type 'string'.
this.borderRect_.setAttribute(
'height', totalHeight as AnyDuringMigration);
} }
this.size_.width = totalWidth; this.size_.width = totalWidth;
this.size_.height = totalHeight; this.size_.height = totalHeight;
@@ -339,13 +341,13 @@ export class FieldMultilineInput extends FieldTextInput {
* Overrides the default behaviour to force rerender in order to * Overrides the default behaviour to force rerender in order to
* correct block size, based on editor text. * correct block size, based on editor text.
* *
* @param _opt_e Optional mouse event that triggered the field to open, or * @param e Optional mouse event that triggered the field to open, or
* undefined if triggered programmatically. * undefined if triggered programmatically.
* @param opt_quietInput True if editor should be created without focus. * @param quietInput True if editor should be created without focus.
* Defaults to false. * Defaults to false.
*/ */
override showEditor_(_opt_e?: Event, opt_quietInput?: boolean) { override showEditor_(e?: Event, quietInput?: boolean) {
super.showEditor_(_opt_e, opt_quietInput); super.showEditor_(e, quietInput);
this.forceRerender(); this.forceRerender();
} }
@@ -360,10 +362,7 @@ export class FieldMultilineInput extends FieldTextInput {
const htmlInput = (document.createElement('textarea')); const htmlInput = (document.createElement('textarea'));
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput'; htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
// AnyDuringMigration because: Argument of type 'boolean' is not assignable htmlInput.setAttribute('spellcheck', String(this.spellcheck_));
// to parameter of type 'string'.
htmlInput.setAttribute(
'spellcheck', this.spellcheck_ as AnyDuringMigration);
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt'; const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt';
div!.style.fontSize = fontSize; div!.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize; htmlInput.style.fontSize = fontSize;
@@ -425,7 +424,7 @@ export class FieldMultilineInput extends FieldTextInput {
* @param e Keyboard event. * @param e Keyboard event.
*/ */
protected override onHtmlInputKeyDown_(e: KeyboardEvent) { protected override onHtmlInputKeyDown_(e: KeyboardEvent) {
if (e.keyCode !== KeyCodes.ENTER) { if (e.key !== 'Enter') {
super.onHtmlInputKeyDown_(e); super.onHtmlInputKeyDown_(e);
} }
} }
@@ -448,7 +447,12 @@ export class FieldMultilineInput extends FieldTextInput {
} }
} }
/** CSS for multiline field. See css.js for use. */ fieldRegistry.register('field_multilinetext', FieldMultilineInput);
/**
* CSS for multiline field.
*/
Css.register(` Css.register(`
.blocklyHtmlTextAreaInput { .blocklyHtmlTextAreaInput {
font-family: monospace; font-family: monospace;
@@ -463,8 +467,6 @@ Css.register(`
} }
`); `);
fieldRegistry.register('field_multilinetext', FieldMultilineInput);
/** /**
* Config options for the multiline input field. * Config options for the multiline input field.
*/ */

View File

@@ -37,51 +37,44 @@ export class FieldNumber extends FieldInput<number> {
*/ */
private decimalPlaces_: number|null = null; private decimalPlaces_: number|null = null;
/**
* Serializable fields are saved by the serializer, non-serializable fields
* are not. Editable fields should also be serializable.
*/
override SERIALIZABLE = true;
/** Don't spellcheck numbers. Our validator does a better job. */ /** Don't spellcheck numbers. Our validator does a better job. */
protected override spellcheck_ = false; protected override spellcheck_ = false;
/** /**
* @param opt_value The initial value of the field. Should cast to a number. * @param value The initial value of the field. Should cast to a number.
* Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup * Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup
* (only used by subclasses that want to handle configuration and setting * (only used by subclasses that want to handle configuration and setting
* the field value after their own constructors have run). * the field value after their own constructors have run).
* @param opt_min Minimum value. Will only be used if opt_config is not * @param min Minimum value. Will only be used if config is not
* provided. * provided.
* @param opt_max Maximum value. Will only be used if opt_config is not * @param max Maximum value. Will only be used if config is not
* provided. * provided.
* @param opt_precision Precision for value. Will only be used if opt_config * @param precision Precision for value. Will only be used if config
* is not provided. * is not provided.
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a number & returns a validated number, or null * field's value. Takes in a number & returns a validated number, or null
* to abort the change. * to abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: string|number|Sentinel, opt_min?: string|number|null, value?: string|number|Sentinel, min?: string|number|null,
opt_max?: string|number|null, opt_precision?: string|number|null, max?: string|number|null, precision?: string|number|null,
opt_validator?: FieldNumberValidator|null, validator?: FieldNumberValidator|null, config?: FieldNumberConfig) {
opt_config?: FieldNumberConfig) {
// Pass SENTINEL so that we can define properties before value validation. // Pass SENTINEL so that we can define properties before value validation.
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return; if (Field.isSentinel(value)) return;
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} else { } else {
this.setConstraints(opt_min, opt_max, opt_precision); this.setConstraints(min, max, precision);
} }
this.setValue(opt_value); this.setValue(value);
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -246,17 +239,17 @@ export class FieldNumber extends FieldInput<number> {
* Ensure that the input value is a valid number (must fulfill the * Ensure that the input value is a valid number (must fulfill the
* constraints placed on the field). * constraints placed on the field).
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A valid number, or null if invalid. * @returns A valid number, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: AnyDuringMigration): protected override doClassValidation_(newValue?: AnyDuringMigration): number
number|null { |null {
if (opt_newValue === null) { if (newValue === null) {
return null; return null;
} }
// Clean up text. // Clean up text.
let newValue = String(opt_newValue); newValue = `${newValue}`;
// TODO: Handle cases like 'ten', '1.203,14', etc. // TODO: Handle cases like 'ten', '1.203,14', etc.
// 'O' is sometimes mistaken for '0' by inexperienced users. // 'O' is sometimes mistaken for '0' by inexperienced users.
newValue = newValue.replace(/O/ig, '0'); newValue = newValue.replace(/O/ig, '0');

View File

@@ -25,37 +25,37 @@ import type {Sentinel} from './utils/sentinel.js';
*/ */
export class FieldTextInput extends FieldInput<string> { export class FieldTextInput extends FieldInput<string> {
/** /**
* @param opt_value The initial value of the field. Should cast to a string. * @param value The initial value of the field. Should cast to a string.
* Defaults to an empty string if null or undefined. Also accepts * Defaults to an empty string if null or undefined. Also accepts
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses * Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting the field value after * that want to handle configuration and setting the field value after
* their own constructors have run). * their own constructors have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a string & returns a validated string, or null * field's value. Takes in a string & returns a validated string, or null
* to abort the change. * to abort the change.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
opt_value?: string|Sentinel, opt_validator?: FieldTextInputValidator|null, value?: string|Sentinel, validator?: FieldTextInputValidator|null,
opt_config?: FieldTextInputConfig) { config?: FieldTextInputConfig) {
super(opt_value, opt_validator, opt_config); super(value, validator, config);
} }
/** /**
* Ensure that the input value casts to a valid string. * Ensure that the input value casts to a valid string.
* *
* @param opt_newValue The input value. * @param newValue The input value.
* @returns A valid string, or null if invalid. * @returns A valid string, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: AnyDuringMigration): protected override doClassValidation_(newValue?: AnyDuringMigration): string
string|null { |null {
if (opt_newValue === undefined) { if (newValue === undefined) {
return null; return null;
} }
return String(opt_newValue); return `${newValue}`;
} }
/** /**

View File

@@ -62,23 +62,23 @@ export class FieldVariable extends FieldDropdown {
* Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
* subclasses that want to handle configuration and setting the field value * subclasses that want to handle configuration and setting the field value
* after their own constructors have run). * after their own constructors have run).
* @param opt_validator A function that is called to validate changes to the * @param validator A function that is called to validate changes to the
* field's value. Takes in a variable ID & returns a validated variable * field's value. Takes in a variable ID & returns a validated variable
* ID, or null to abort the change. * ID, or null to abort the change.
* @param opt_variableTypes A list of the types of variables to include in the * @param variableTypes A list of the types of variables to include in the
* dropdown. Will only be used if opt_config is not provided. * dropdown. Will only be used if config is not provided.
* @param opt_defaultType The type of variable to create if this field's value * @param defaultType The type of variable to create if this field's value
* is not explicitly set. Defaults to ''. Will only be used if opt_config * is not explicitly set. Defaults to ''. Will only be used if config
* is not provided. * is not provided.
* @param opt_config A map of options used to configure the field. * @param config A map of options used to configure the field.
* See the [field creation documentation]{@link * See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/variable#creation} * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/variable#creation}
* for a list of properties this parameter supports. * for a list of properties this parameter supports.
*/ */
constructor( constructor(
varName: string|null|Sentinel, opt_validator?: FieldVariableValidator, varName: string|null|Sentinel, validator?: FieldVariableValidator,
opt_variableTypes?: string[], opt_defaultType?: string, variableTypes?: string[], defaultType?: string,
opt_config?: FieldVariableConfig) { config?: FieldVariableConfig) {
super(Field.SKIP_SETUP); super(Field.SKIP_SETUP);
/** /**
@@ -101,13 +101,13 @@ export class FieldVariable extends FieldDropdown {
return; return;
} }
if (opt_config) { if (config) {
this.configure_(opt_config); this.configure_(config);
} else { } else {
this.setTypes_(opt_variableTypes, opt_defaultType); this.setTypes_(variableTypes, defaultType);
} }
if (opt_validator) { if (validator) {
this.setValidator(opt_validator); this.setValidator(validator);
} }
} }
@@ -315,19 +315,19 @@ export class FieldVariable extends FieldDropdown {
/** /**
* Ensure that the ID belongs to a valid variable of an allowed type. * Ensure that the ID belongs to a valid variable of an allowed type.
* *
* @param opt_newValue The ID of the new variable to set. * @param newValue The ID of the new variable to set.
* @returns The validated ID, or null if invalid. * @returns The validated ID, or null if invalid.
*/ */
protected override doClassValidation_(opt_newValue?: AnyDuringMigration): protected override doClassValidation_(newValue?: AnyDuringMigration): string
string|null { |null {
if (opt_newValue === null) { if (newValue === null) {
return null; return null;
} }
const block = this.getSourceBlock(); const block = this.getSourceBlock();
if (!block) { if (!block) {
throw new UnattachedFieldError(); throw new UnattachedFieldError();
} }
const newId = opt_newValue as string; const newId = newValue as string;
const variable = Variables.getVariable(block.workspace, newId); const variable = Variables.getVariable(block.workspace, newId);
if (!variable) { if (!variable) {
console.warn( console.warn(
@@ -410,23 +410,19 @@ export class FieldVariable extends FieldDropdown {
* Parse the optional arguments representing the allowed variable types and * Parse the optional arguments representing the allowed variable types and
* the default variable type. * the default variable type.
* *
* @param opt_variableTypes A list of the types of variables to include in the * @param variableTypes A list of the types of variables to include in the
* dropdown. If null or undefined, variables of all types will be * dropdown. If null or undefined, variables of all types will be
* displayed in the dropdown. * displayed in the dropdown.
* @param opt_defaultType The type of the variable to create if this field's * @param defaultType The type of the variable to create if this field's
* value is not explicitly set. Defaults to ''. * value is not explicitly set. Defaults to ''.
*/ */
private setTypes_(opt_variableTypes?: string[], opt_defaultType?: string) { private setTypes_(variableTypes: string[]|null = null, defaultType = '') {
// If you expected that the default type would be the same as the only entry // If you expected that the default type would be the same as the only entry
// in the variable types array, tell the Blockly team by commenting on // in the variable types array, tell the Blockly team by commenting on
// #1499. // #1499.
const defaultType = opt_defaultType || '';
let variableTypes;
// Set the allowable variable types. Null means all types on the workspace. // Set the allowable variable types. Null means all types on the workspace.
if (opt_variableTypes === null || opt_variableTypes === undefined) { if (Array.isArray(variableTypes)) {
variableTypes = null; variableTypes = variableTypes;
} else if (Array.isArray(opt_variableTypes)) {
variableTypes = opt_variableTypes;
// Make sure the default type is valid. // Make sure the default type is valid.
let isInArray = false; let isInArray = false;
for (let i = 0; i < variableTypes.length; i++) { for (let i = 0; i < variableTypes.length; i++) {
@@ -439,7 +435,7 @@ export class FieldVariable extends FieldDropdown {
'Invalid default type \'' + defaultType + '\' in ' + 'Invalid default type \'' + defaultType + '\' in ' +
'the definition of a FieldVariable'); 'the definition of a FieldVariable');
} }
} else { } else if (variableTypes !== null) {
throw Error( throw Error(
'\'variableTypes\' was not an array in the definition of ' + '\'variableTypes\' was not an array in the definition of ' +
'a FieldVariable'); 'a FieldVariable');

View File

@@ -74,8 +74,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
/** /**
* Lay out the blocks in the flyout. * Lay out the blocks in the flyout.
* *
* @param contents The blocks and buttons to lay * @param contents The blocks and buttons to lay out.
* out.
* @param gaps The visible gaps between blocks. * @param gaps The visible gaps between blocks.
*/ */
protected abstract layout_(contents: FlyoutItem[], gaps: number[]): void; protected abstract layout_(contents: FlyoutItem[], gaps: number[]): void;
@@ -128,9 +127,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
protected toolboxPosition_: number; protected toolboxPosition_: number;
/** /**
* Opaque data that can be passed to Blockly.unbindEvent_. * Array holding info needed to unbind events.
* Used for disposing.
* Ex: [[node, name, func], [node, name, func]].
*/ */
private eventWrappers_: browserEvents.Data = []; private boundEvents: browserEvents.Data[] = [];
/** /**
* Function that will be registered as a change listener on the workspace * Function that will be registered as a change listener on the workspace
@@ -357,21 +358,17 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.hide(); this.hide();
Array.prototype.push.apply( this.boundEvents.push(browserEvents.conditionalBind(
this.eventWrappers_, (this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_));
browserEvents.conditionalBind(
(this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_));
if (!this.autoClose) { if (!this.autoClose) {
this.filterWrapper_ = this.filterForCapacity_.bind(this); this.filterWrapper_ = this.filterForCapacity_.bind(this);
this.targetWorkspace.addChangeListener(this.filterWrapper_); this.targetWorkspace.addChangeListener(this.filterWrapper_);
} }
// Dragging the flyout up and down. // Dragging the flyout up and down.
Array.prototype.push.apply( this.boundEvents.push(browserEvents.conditionalBind(
this.eventWrappers_, (this.svgBackground_ as SVGPathElement), 'pointerdown', this,
browserEvents.conditionalBind( this.onMouseDown_));
(this.svgBackground_ as SVGPathElement), 'pointerdown', this,
this.onMouseDown_));
// A flyout connected to a workspace doesn't have its own current gesture. // A flyout connected to a workspace doesn't have its own current gesture.
this.workspace_.getGesture = this.workspace_.getGesture =
@@ -401,7 +398,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
dispose() { dispose() {
this.hide(); this.hide();
this.workspace_.getComponentManager().removeComponent(this.id); this.workspace_.getComponentManager().removeComponent(this.id);
browserEvents.unbind(this.eventWrappers_); for (const event of this.boundEvents) {
browserEvents.unbind(event);
}
this.boundEvents.length = 0;
if (this.filterWrapper_) { if (this.filterWrapper_) {
this.targetWorkspace.removeChangeListener(this.filterWrapper_); this.targetWorkspace.removeChangeListener(this.filterWrapper_);
this.filterWrapper_ = null; this.filterWrapper_ = null;
@@ -525,8 +525,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @param y The computed y origin of the flyout's SVG group. * @param y The computed y origin of the flyout's SVG group.
*/ */
protected positionAt_(width: number, height: number, x: number, y: number) { protected positionAt_(width: number, height: number, x: number, y: number) {
this.svgGroup_?.setAttribute('width', width.toString()); this.svgGroup_?.setAttribute('width', `${width}`);
this.svgGroup_?.setAttribute('height', height.toString()); this.svgGroup_?.setAttribute('height', `${height}`);
this.workspace_.setCachedParentSvgSize(width, height); this.workspace_.setCachedParentSvgSize(width, height);
if (this.svgGroup_) { if (this.svgGroup_) {
@@ -562,7 +562,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
} }
this.setVisible(false); this.setVisible(false);
// Delete all the event listeners. // Delete all the event listeners.
for (let i = 0, listen; listen = this.listeners_[i]; i++) { for (const listen of this.listeners_) {
browserEvents.unbind(listen); browserEvents.unbind(listen);
} }
this.listeners_.length = 0; this.listeners_.length = 0;
@@ -781,7 +781,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
blockInfo: toolbox.BlockInfo, gaps: number[], defaultGap: number) { blockInfo: toolbox.BlockInfo, gaps: number[], defaultGap: number) {
let gap; let gap;
if (blockInfo['gap']) { if (blockInfo['gap']) {
gap = parseInt(blockInfo['gap'].toString()); gap = parseInt(String(blockInfo['gap']));
} else if (blockInfo['blockxml']) { } else if (blockInfo['blockxml']) {
const xml = (typeof blockInfo['blockxml'] === 'string' ? const xml = (typeof blockInfo['blockxml'] === 'string' ?
utilsXml.textToDom(blockInfo['blockxml']) : utilsXml.textToDom(blockInfo['blockxml']) :
@@ -806,7 +806,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// <sep gap="36"></sep> // <sep gap="36"></sep>
// The default gap is 24, can be set larger or smaller. // The default gap is 24, can be set larger or smaller.
// This overwrites the gap attribute on the previous element. // This overwrites the gap attribute on the previous element.
const newGap = parseInt(sepInfo['gap']!.toString()); const newGap = parseInt(String(sepInfo['gap']));
// Ignore gaps before the first block. // Ignore gaps before the first block.
if (!isNaN(newGap) && gaps.length > 0) { if (!isNaN(newGap) && gaps.length > 0) {
gaps[gaps.length - 1] = newGap; gaps[gaps.length - 1] = newGap;
@@ -1056,13 +1056,13 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/ */
protected moveRectToBlock_(rect: SVGElement, block: BlockSvg) { protected moveRectToBlock_(rect: SVGElement, block: BlockSvg) {
const blockHW = block.getHeightWidth(); const blockHW = block.getHeightWidth();
rect.setAttribute('width', blockHW.width.toString()); rect.setAttribute('width', String(blockHW.width));
rect.setAttribute('height', blockHW.height.toString()); rect.setAttribute('height', String(blockHW.height));
const blockXY = block.getRelativeToSurfaceXY(); const blockXY = block.getRelativeToSurfaceXY();
rect.setAttribute('y', blockXY.y.toString()); rect.setAttribute('y', String(blockXY.y));
rect.setAttribute( rect.setAttribute(
'x', (this.RTL ? blockXY.x - blockHW.width : blockXY.x).toString()); 'x', String(this.RTL ? blockXY.x - blockHW.width : blockXY.x));
} }
/** /**

View File

@@ -155,17 +155,17 @@ export class FlyoutButton {
if (!this.isLabel_) { if (!this.isLabel_) {
this.width += 2 * FlyoutButton.TEXT_MARGIN_X; this.width += 2 * FlyoutButton.TEXT_MARGIN_X;
this.height += 2 * FlyoutButton.TEXT_MARGIN_Y; this.height += 2 * FlyoutButton.TEXT_MARGIN_Y;
shadow?.setAttribute('width', this.width.toString()); shadow?.setAttribute('width', String(this.width));
shadow?.setAttribute('height', this.height.toString()); shadow?.setAttribute('height', String(this.height));
} }
rect.setAttribute('width', this.width.toString()); rect.setAttribute('width', String(this.width));
rect.setAttribute('height', this.height.toString()); rect.setAttribute('height', String(this.height));
svgText.setAttribute('x', (this.width / 2).toString()); svgText.setAttribute('x', String(this.width / 2));
svgText.setAttribute( svgText.setAttribute(
'y', 'y',
(this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline) String(
.toString()); this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline));
this.updateTransform_(); this.updateTransform_();

View File

@@ -279,7 +279,7 @@ export class CodeGenerator {
if (!Array.isArray(tuple)) { if (!Array.isArray(tuple)) {
throw TypeError( throw TypeError(
`Expecting tuple from value block: ${targetBlock.type} See ` + `Expecting tuple from value block: ${targetBlock.type} See ` +
`https://developers.google.com/blockly/guides/create-custom-blocks/generating-code` + `developers.google.com/blockly/guides/create-custom-blocks/generating-code ` +
`for more information`); `for more information`);
} }
let code = tuple[0]; let code = tuple[0];

View File

@@ -102,16 +102,11 @@ export class Gesture {
private hasExceededDragRadius_ = false; private hasExceededDragRadius_ = false;
/** /**
* A handle to use to unbind a pointermove listener at the end of a drag. * Array holding info needed to unbind events.
* Opaque data returned from Blockly.bindEventWithChecks_. * Used for disposing.
* Ex: [[node, name, func], [node, name, func]].
*/ */
protected onMoveWrapper_: browserEvents.Data|null = null; private boundEvents: browserEvents.Data[] = [];
/**
* A handle to use to unbind a pointerup listener at the end of a drag.
* Opaque data returned from Blockly.bindEventWithChecks_.
*/
protected onUpWrapper_: browserEvents.Data|null = null;
/** The object tracking a bubble drag, or null if none is in progress. */ /** The object tracking a bubble drag, or null if none is in progress. */
private bubbleDragger_: BubbleDragger|null = null; private bubbleDragger_: BubbleDragger|null = null;
@@ -158,13 +153,6 @@ export class Gesture {
/** The starting distance between two touch points. */ /** The starting distance between two touch points. */
private startDistance_ = 0; private startDistance_ = 0;
/**
* A handle to use to unbind the second pointerdown listener
* at the end of a drag.
* Opaque data returned from Blockly.bindEventWithChecks_.
*/
private onStartWrapper_: browserEvents.Data|null = null;
/** Boolean for whether or not the workspace supports pinch-zoom. */ /** Boolean for whether or not the workspace supports pinch-zoom. */
private isPinchZoomEnabled_: boolean|null = null; private isPinchZoomEnabled_: boolean|null = null;
@@ -209,12 +197,10 @@ export class Gesture {
// Clear the owner's reference to this gesture. // Clear the owner's reference to this gesture.
this.creatorWorkspace.clearGesture(); this.creatorWorkspace.clearGesture();
if (this.onMoveWrapper_) { for (const event of this.boundEvents) {
browserEvents.unbind(this.onMoveWrapper_); browserEvents.unbind(event);
}
if (this.onUpWrapper_) {
browserEvents.unbind(this.onUpWrapper_);
} }
this.boundEvents.length = 0;
if (this.blockDragger_) { if (this.blockDragger_) {
this.blockDragger_.dispose(); this.blockDragger_.dispose();
@@ -222,10 +208,6 @@ export class Gesture {
if (this.workspaceDragger_) { if (this.workspaceDragger_) {
this.workspaceDragger_.dispose(); this.workspaceDragger_.dispose();
} }
if (this.onStartWrapper_) {
browserEvents.unbind(this.onStartWrapper_);
}
} }
/** /**
@@ -511,15 +493,15 @@ export class Gesture {
* @internal * @internal
*/ */
bindMouseEvents(e: PointerEvent) { bindMouseEvents(e: PointerEvent) {
this.onStartWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
document, 'pointerdown', null, this.handleStart.bind(this), document, 'pointerdown', null, this.handleStart.bind(this),
/* opt_noCaptureIdentifier */ true); /* opt_noCaptureIdentifier */ true));
this.onMoveWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
document, 'pointermove', null, this.handleMove.bind(this), document, 'pointermove', null, this.handleMove.bind(this),
/* opt_noCaptureIdentifier */ true); /* opt_noCaptureIdentifier */ true));
this.onUpWrapper_ = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
document, 'pointerup', null, this.handleUp.bind(this), document, 'pointerup', null, this.handleUp.bind(this),
/* opt_noCaptureIdentifier */ true); /* opt_noCaptureIdentifier */ true));
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();

View File

@@ -91,8 +91,8 @@ export class Grid {
update(scale: number) { update(scale: number) {
const safeSpacing = this.spacing * scale; const safeSpacing = this.spacing * scale;
this.pattern.setAttribute('width', safeSpacing.toString()); this.pattern.setAttribute('width', `${safeSpacing}`);
this.pattern.setAttribute('height', safeSpacing.toString()); this.pattern.setAttribute('height', `${safeSpacing}`);
let half = Math.floor(this.spacing / 2) + 0.5; let half = Math.floor(this.spacing / 2) + 0.5;
let start = half - this.length / 2; let start = half - this.length / 2;
@@ -121,11 +121,11 @@ export class Grid {
line: SVGElement, width: number, x1: number, x2: number, y1: number, line: SVGElement, width: number, x1: number, x2: number, y1: number,
y2: number) { y2: number) {
if (line) { if (line) {
line.setAttribute('stroke-width', width.toString()); line.setAttribute('stroke-width', `${width}`);
line.setAttribute('x1', x1.toString()); line.setAttribute('x1', `${x1}`);
line.setAttribute('y1', y1.toString()); line.setAttribute('y1', `${y1}`);
line.setAttribute('x2', x2.toString()); line.setAttribute('x2', `${x2}`);
line.setAttribute('y2', y2.toString()); line.setAttribute('y2', `${y2}`);
} }
} }
@@ -138,8 +138,8 @@ export class Grid {
* @internal * @internal
*/ */
moveTo(x: number, y: number) { moveTo(x: number, y: number) {
this.pattern.setAttribute('x', x.toString()); this.pattern.setAttribute('x', `${x}`);
this.pattern.setAttribute('y', y.toString()); this.pattern.setAttribute('y', `${y}`);
} }
/** /**

View File

@@ -177,7 +177,7 @@ export class ASTNode {
throw new Error( throw new Error(
'The current AST location is not associated with a block'); 'The current AST location is not associated with a block');
} }
const curIdx = block.inputList.indexOf((input)); const curIdx = block.inputList.indexOf(input);
let fieldIdx = input.fieldRow.indexOf(location) + 1; let fieldIdx = input.fieldRow.indexOf(location) + 1;
for (let i = curIdx; i < block.inputList.length; i++) { for (let i = curIdx; i < block.inputList.length; i++) {
const newInput = block.inputList[i]; const newInput = block.inputList[i];
@@ -240,7 +240,7 @@ export class ASTNode {
throw new Error( throw new Error(
'The current AST location is not associated with a block'); 'The current AST location is not associated with a block');
} }
const curIdx = block.inputList.indexOf((parentInput)); const curIdx = block.inputList.indexOf(parentInput);
let fieldIdx = parentInput.fieldRow.indexOf(location) - 1; let fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
for (let i = curIdx; i >= 0; i--) { for (let i = curIdx; i >= 0; i--) {
const input = block.inputList[i]; const input = block.inputList[i];

View File

@@ -17,7 +17,6 @@ import type {MenuItem} from './menuitem.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';
import {KeyCodes} from './utils/keycodes.js';
import type {Size} from './utils/size.js'; import type {Size} from './utils/size.js';
import * as style from './utils/style.js'; import * as style from './utils/style.js';
@@ -392,29 +391,29 @@ export class Menu {
} }
const highlighted = this.highlightedItem; const highlighted = this.highlightedItem;
switch (keyboardEvent.keyCode) { switch (keyboardEvent.key) {
case KeyCodes.ENTER: case 'Enter':
case KeyCodes.SPACE: case ' ':
if (highlighted) { if (highlighted) {
highlighted.performAction(); highlighted.performAction();
} }
break; break;
case KeyCodes.UP: case 'ArrowUp':
this.highlightPrevious(); this.highlightPrevious();
break; break;
case KeyCodes.DOWN: case 'ArrowDown':
this.highlightNext(); this.highlightNext();
break; break;
case KeyCodes.PAGE_UP: case 'PageUp':
case KeyCodes.HOME: case 'Home':
this.highlightFirst(); this.highlightFirst();
break; break;
case KeyCodes.PAGE_DOWN: case 'PageDown':
case KeyCodes.END: case 'End':
this.highlightLast(); this.highlightLast();
break; break;

View File

@@ -115,12 +115,12 @@ export function register<T>(
AnyDuringMigration, AnyDuringMigration,
opt_allowOverrides?: boolean): void { opt_allowOverrides?: boolean): void {
if (!(type instanceof Type) && typeof type !== 'string' || if (!(type instanceof Type) && typeof type !== 'string' ||
String(type).trim() === '') { `${type}`.trim() === '') {
throw Error( throw Error(
'Invalid type "' + type + '". The type must be a' + 'Invalid type "' + type + '". The type must be a' +
' non-empty string or a Blockly.registry.Type.'); ' non-empty string or a Blockly.registry.Type.');
} }
type = String(type).toLowerCase(); type = `${type}`.toLowerCase();
if (typeof name !== 'string' || name.trim() === '') { if (typeof name !== 'string' || name.trim() === '') {
throw Error( throw Error(
@@ -178,7 +178,7 @@ function validate(type: string, registryItem: Function|AnyDuringMigration) {
* @param name The plugin's name. (Ex. field_angle, geras) * @param name The plugin's name. (Ex. field_angle, geras)
*/ */
export function unregister<T>(type: string|Type<T>, name: string) { export function unregister<T>(type: string|Type<T>, name: string) {
type = String(type).toLowerCase(); type = `${type}`.toLowerCase();
name = name.toLowerCase(); name = name.toLowerCase();
const typeRegistry = typeMap[type]; const typeRegistry = typeMap[type];
if (!typeRegistry || !typeRegistry[name]) { if (!typeRegistry || !typeRegistry[name]) {
@@ -206,7 +206,7 @@ export function unregister<T>(type: string|Type<T>, name: string) {
function getItem<T>( function getItem<T>(
type: string|Type<T>, name: string, opt_throwIfMissing?: boolean): type: string|Type<T>, name: string, opt_throwIfMissing?: boolean):
(new (...p1: AnyDuringMigration[]) => T)|null|AnyDuringMigration { (new (...p1: AnyDuringMigration[]) => T)|null|AnyDuringMigration {
type = String(type).toLowerCase(); type = `${type}`.toLowerCase();
name = name.toLowerCase(); name = name.toLowerCase();
const typeRegistry = typeMap[type]; const typeRegistry = typeMap[type];
if (!typeRegistry || !typeRegistry[name]) { if (!typeRegistry || !typeRegistry[name]) {
@@ -233,7 +233,7 @@ function getItem<T>(
* otherwise. * otherwise.
*/ */
export function hasItem<T>(type: string|Type<T>, name: string): boolean { export function hasItem<T>(type: string|Type<T>, name: string): boolean {
type = String(type).toLowerCase(); type = `${type}`.toLowerCase();
name = name.toLowerCase(); name = name.toLowerCase();
const typeRegistry = typeMap[type]; const typeRegistry = typeMap[type];
if (!typeRegistry) { if (!typeRegistry) {
@@ -288,7 +288,7 @@ export function getObject<T>(
export function getAllItems<T>( export function getAllItems<T>(
type: string|Type<T>, opt_cased?: boolean, opt_throwIfMissing?: boolean): type: string|Type<T>, opt_cased?: boolean, opt_throwIfMissing?: boolean):
{[key: string]: T|null|(new (...p1: AnyDuringMigration[]) => T)}|null { {[key: string]: T|null|(new (...p1: AnyDuringMigration[]) => T)}|null {
type = String(type).toLowerCase(); type = `${type}`.toLowerCase();
const typeRegistry = typeMap[type]; const typeRegistry = typeMap[type];
if (!typeRegistry) { if (!typeRegistry) {
const msg = `Unable to find [${type}] in the registry.`; const msg = `Unable to find [${type}] in the registry.`;
@@ -304,9 +304,7 @@ export function getAllItems<T>(
} }
const nameRegistry = nameMap[type]; const nameRegistry = nameMap[type];
const casedRegistry = Object.create(null); const casedRegistry = Object.create(null);
const keys = Object.keys(typeRegistry); for (const key of Object.keys(typeRegistry)) {
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
casedRegistry[nameRegistry[key]] = typeRegistry[key]; casedRegistry[nameRegistry[key]] = typeRegistry[key];
} }
return casedRegistry; return casedRegistry;
@@ -325,8 +323,7 @@ export function getAllItems<T>(
export function getClassFromOptions<T>( export function getClassFromOptions<T>(
type: Type<T>, options: Options, opt_throwIfMissing?: boolean): type: Type<T>, options: Options, opt_throwIfMissing?: boolean):
(new (...p1: AnyDuringMigration[]) => T)|null { (new (...p1: AnyDuringMigration[]) => T)|null {
const typeName = type.toString(); const plugin = options.plugins[String(type)] || DEFAULT;
const plugin = options.plugins[typeName] || DEFAULT;
// If the user passed in a plugin class instead of a registered plugin name. // If the user passed in a plugin class instead of a registered plugin name.
if (typeof plugin === 'function') { if (typeof plugin === 'function') {

View File

@@ -87,8 +87,8 @@ export class MarkerSvg extends BaseMarkerSvg {
* @param y The y position of the circle. * @param y The y position of the circle.
*/ */
private positionCircle_(x: number, y: number) { private positionCircle_(x: number, y: number) {
this.markerCircle_?.setAttribute('cx', x.toString()); this.markerCircle_?.setAttribute('cx', `${x}`);
this.markerCircle_?.setAttribute('cy', y.toString()); this.markerCircle_?.setAttribute('cy', `${y}`);
this.currentMarkerSvg = this.markerCircle_; this.currentMarkerSvg = this.markerCircle_;
} }

View File

@@ -223,12 +223,12 @@ export class Scrollbar {
this.svgBackground.setAttribute('height', String(scrollbarThickness)); this.svgBackground.setAttribute('height', String(scrollbarThickness));
this.outerSvg.setAttribute('height', String(scrollbarThickness)); this.outerSvg.setAttribute('height', String(scrollbarThickness));
this.svgHandle.setAttribute('height', String(scrollbarThickness - 5)); this.svgHandle.setAttribute('height', String(scrollbarThickness - 5));
this.svgHandle.setAttribute('y', String(2.5)); this.svgHandle.setAttribute('y', '2.5');
} else { } else {
this.svgBackground.setAttribute('width', String(scrollbarThickness)); this.svgBackground.setAttribute('width', String(scrollbarThickness));
this.outerSvg.setAttribute('width', String(scrollbarThickness)); this.outerSvg.setAttribute('width', String(scrollbarThickness));
this.svgHandle.setAttribute('width', String(scrollbarThickness - 5)); this.svgHandle.setAttribute('width', String(scrollbarThickness - 5));
this.svgHandle.setAttribute('x', String(2.5)); this.svgHandle.setAttribute('x', '2.5');
} }
} }

View File

@@ -103,7 +103,7 @@ export class ShortcutRegistry {
addKeyMapping( addKeyMapping(
keyCode: string|number|KeyCodes, shortcutName: string, keyCode: string|number|KeyCodes, shortcutName: string,
opt_allowCollision?: boolean) { opt_allowCollision?: boolean) {
keyCode = String(keyCode); keyCode = `${keyCode}`;
const shortcutNames = this.keyMap.get(keyCode); const shortcutNames = this.keyMap.get(keyCode);
if (shortcutNames && !opt_allowCollision) { if (shortcutNames && !opt_allowCollision) {
throw new Error(`Shortcut named "${ throw new Error(`Shortcut named "${
@@ -280,7 +280,7 @@ export class ShortcutRegistry {
if (serializedKey !== '' && e.keyCode) { if (serializedKey !== '' && e.keyCode) {
serializedKey = serializedKey + '+' + e.keyCode; serializedKey = serializedKey + '+' + e.keyCode;
} else if (e.keyCode) { } else if (e.keyCode) {
serializedKey = e.keyCode.toString(); serializedKey = String(e.keyCode);
} }
return serializedKey; return serializedKey;
} }
@@ -327,7 +327,7 @@ export class ShortcutRegistry {
if (serializedKey !== '' && keyCode) { if (serializedKey !== '' && keyCode) {
serializedKey = serializedKey + '+' + keyCode; serializedKey = serializedKey + '+' + keyCode;
} else if (keyCode) { } else if (keyCode) {
serializedKey = keyCode.toString(); serializedKey = `${keyCode}`;
} }
return serializedKey; return serializedKey;
} }

View File

@@ -116,10 +116,16 @@ export class Theme implements ITheme {
*/ */
getComponentStyle(componentName: string): string|null { getComponentStyle(componentName: string): string|null {
const style = (this.componentStyles as AnyDuringMigration)[componentName]; const style = (this.componentStyles as AnyDuringMigration)[componentName];
if (style && typeof style === 'string' && this.getComponentStyle((style))) { if (!style) {
return this.getComponentStyle((style)); return null;
} }
return style ? String(style) : null; if (typeof style === 'string') {
const recursiveStyle = this.getComponentStyle(style);
if (recursiveStyle) {
return recursiveStyle;
}
}
return `${style}`;
} }
/** /**

View File

@@ -57,19 +57,19 @@ export class ToolboxCategory extends ToolboxItem implements
/** The colour of the category. */ /** The colour of the category. */
protected colour_ = ''; protected colour_ = '';
/** The html container for the category. */ /** The HTML container for the category. */
protected htmlDiv_: HTMLDivElement|null = null; protected htmlDiv_: HTMLDivElement|null = null;
/** The html element for the category row. */ /** The HTML element for the category row. */
protected rowDiv_: HTMLDivElement|null = null; protected rowDiv_: HTMLDivElement|null = null;
/** The html element that holds children elements of the category row. */ /** The HTML element that holds children elements of the category row. */
protected rowContents_: HTMLDivElement|null = null; protected rowContents_: HTMLDivElement|null = null;
/** The html element for the toolbox icon. */ /** The HTML element for the toolbox icon. */
protected iconDom_: Element|null = null; protected iconDom_: Element|null = null;
/** The html element for the toolbox label. */ /** The HTML element for the toolbox label. */
protected labelDom_: Element|null = null; protected labelDom_: Element|null = null;
protected cssConfig_: CssConfig; protected cssConfig_: CssConfig;

View File

@@ -39,7 +39,6 @@ import type {KeyboardShortcut} from '../shortcut_registry.js';
import * as Touch from '../touch.js'; import * as Touch from '../touch.js';
import * as aria from '../utils/aria.js'; import * as aria from '../utils/aria.js';
import * as dom from '../utils/dom.js'; import * as dom from '../utils/dom.js';
import {KeyCodes} from '../utils/keycodes.js';
import {Rect} from '../utils/rect.js'; import {Rect} from '../utils/rect.js';
import * as toolbox from '../utils/toolbox.js'; import * as toolbox from '../utils/toolbox.js';
import type {WorkspaceSvg} from '../workspace_svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js';
@@ -63,10 +62,10 @@ export class Toolbox extends DeleteArea implements IAutoHideable,
protected toolboxDef_: toolbox.ToolboxInfo; protected toolboxDef_: toolbox.ToolboxInfo;
private readonly horizontalLayout_: boolean; private readonly horizontalLayout_: boolean;
/** The html container for the toolbox. */ /** The HTML container for the toolbox. */
HtmlDiv: HTMLDivElement|null = null; HtmlDiv: HTMLDivElement|null = null;
/** The html container for the contents of a toolbox. */ /** The HTML container for the contents of a toolbox. */
protected contentsDiv_: HTMLDivElement|null = null; protected contentsDiv_: HTMLDivElement|null = null;
/** Whether the Toolbox is visible. */ /** Whether the Toolbox is visible. */
@@ -268,21 +267,21 @@ export class Toolbox extends DeleteArea implements IAutoHideable,
*/ */
protected onKeyDown_(e: KeyboardEvent) { protected onKeyDown_(e: KeyboardEvent) {
let handled = false; let handled = false;
switch (e.keyCode) { switch (e.key) {
case KeyCodes.DOWN: case 'ArrowDown':
handled = this.selectNext_(); handled = this.selectNext_();
break; break;
case KeyCodes.UP: case 'ArrowUp':
handled = this.selectPrevious_(); handled = this.selectPrevious_();
break; break;
case KeyCodes.LEFT: case 'ArrowLeft':
handled = this.selectParent_(); handled = this.selectParent_();
break; break;
case KeyCodes.RIGHT: case 'ArrowRight':
handled = this.selectChild_(); handled = this.selectChild_();
break; break;
case KeyCodes.ENTER: case 'Enter':
case KeyCodes.SPACE: case ' ':
if (this.selectedItem_ && this.selectedItem_.isCollapsible()) { if (this.selectedItem_ && this.selectedItem_.isCollapsible()) {
const collapsibleItem = this.selectedItem_ as ICollapsibleToolboxItem; const collapsibleItem = this.selectedItem_ as ICollapsibleToolboxItem;
collapsibleItem.toggleExpanded(); collapsibleItem.toggleExpanded();

View File

@@ -447,7 +447,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable,
// Linear interpolation between min and max. // Linear interpolation between min and max.
const opacity = OPACITY_MIN + this.lidOpen_ * (OPACITY_MAX - OPACITY_MIN); const opacity = OPACITY_MIN + this.lidOpen_ * (OPACITY_MAX - OPACITY_MIN);
if (this.svgGroup_) { if (this.svgGroup_) {
this.svgGroup_.style.opacity = opacity.toString(); this.svgGroup_.style.opacity = `${opacity}`;
} }
if (this.lidOpen_ > this.minOpenness_ && this.lidOpen_ < 1) { if (this.lidOpen_ > this.minOpenness_ && this.lidOpen_ < 1) {

View File

@@ -80,7 +80,7 @@ export function setHsvValue(newValue: number) {
* can't be parsed. * can't be parsed.
*/ */
export function parse(str: string|number): string|null { export function parse(str: string|number): string|null {
str = String(str).toLowerCase().trim(); str = `${str}`.toLowerCase().trim();
let hex = names[str]; let hex = names[str];
if (hex) { if (hex) {
// e.g. 'red' // e.g. 'red'

View File

@@ -63,7 +63,7 @@ let canvasContext: CanvasRenderingContext2D|null = null;
export function createSvgElement<T extends SVGElement>( export function createSvgElement<T extends SVGElement>(
name: string|Svg<T>, attrs: {[key: string]: string|number}, name: string|Svg<T>, attrs: {[key: string]: string|number},
opt_parent?: Element|null): T { opt_parent?: Element|null): T {
const e = document.createElementNS(SVG_NS, String(name)) as T; const e = document.createElementNS(SVG_NS, `${name}`) as T;
for (const key in attrs) { for (const key in attrs) {
e.setAttribute(key, `${attrs[key]}`); e.setAttribute(key, `${attrs[key]}`);
} }

View File

@@ -109,7 +109,7 @@ function tokenizeInterpolationInternal(
// When parsing interpolation tokens, numbers are special // When parsing interpolation tokens, numbers are special
// placeholders (%1, %2, etc). Make sure all other values are // placeholders (%1, %2, etc). Make sure all other values are
// strings. // strings.
tokens.push(String(rawValue)); tokens.push(`${rawValue}`);
} else { } else {
tokens.push(rawValue); tokens.push(rawValue);
} }

View File

@@ -265,10 +265,10 @@ export class WorkspaceComment {
*/ */
toXmlWithXY(opt_noId?: boolean): Element { toXmlWithXY(opt_noId?: boolean): Element {
const element = this.toXml(opt_noId); const element = this.toXml(opt_noId);
element.setAttribute('x', `${Math.round(this.xy_.x)}`); element.setAttribute('x', String(Math.round(this.xy_.x)));
element.setAttribute('y', `${Math.round(this.xy_.y)}`); element.setAttribute('y', String(Math.round(this.xy_.y)));
element.setAttribute('h', `${this.height_}`); element.setAttribute('h', String(this.height_));
element.setAttribute('w', `${this.width_}`); element.setAttribute('w', String(this.width_));
return element; return element;
} }

View File

@@ -723,8 +723,10 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
Svg.G, {'class': this.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'}, Svg.G, {'class': this.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'},
this.svgGroup_); this.svgGroup_);
dom.createSvgElement( dom.createSvgElement(
Svg.POLYGON, Svg.POLYGON, {
{'points': '0,x x,x x,0'.replace(/x/g, RESIZE_SIZE.toString())}, 'points':
`0,${RESIZE_SIZE} ${RESIZE_SIZE},${RESIZE_SIZE} ${RESIZE_SIZE},0`,
},
this.resizeGroup_); this.resizeGroup_);
dom.createSvgElement( dom.createSvgElement(
Svg.LINE, { Svg.LINE, {
@@ -885,10 +887,11 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
const topOffset = WorkspaceCommentSvg.TOP_OFFSET; const topOffset = WorkspaceCommentSvg.TOP_OFFSET;
const textOffset = TEXTAREA_OFFSET * 2; const textOffset = TEXTAREA_OFFSET * 2;
this.foreignObject_?.setAttribute('width', `${size.width}`); this.foreignObject_?.setAttribute('width', String(size.width));
this.foreignObject_?.setAttribute('height', `${size.height - topOffset}`); this.foreignObject_?.setAttribute(
'height', String(size.height - topOffset));
if (this.RTL) { if (this.RTL) {
this.foreignObject_?.setAttribute('x', `${- size.width}`); this.foreignObject_?.setAttribute('x', String(-size.width));
} }
if (!this.textarea_) return; if (!this.textarea_) return;
@@ -914,7 +917,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
this.svgRectTarget_?.setAttribute('height', `${height}`); this.svgRectTarget_?.setAttribute('height', `${height}`);
this.svgHandleTarget_?.setAttribute('width', `${width}`); this.svgHandleTarget_?.setAttribute('width', `${width}`);
this.svgHandleTarget_?.setAttribute( this.svgHandleTarget_?.setAttribute(
'height', `${WorkspaceCommentSvg.TOP_OFFSET}`); 'height', String(WorkspaceCommentSvg.TOP_OFFSET));
if (this.RTL) { if (this.RTL) {
this.svgRect_.setAttribute('transform', 'scale(-1 1)'); this.svgRect_.setAttribute('transform', 'scale(-1 1)');
this.svgRectTarget_?.setAttribute('transform', 'scale(-1 1)'); this.svgRectTarget_?.setAttribute('transform', 'scale(-1 1)');
@@ -1042,7 +1045,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
eventUtils.enable(); eventUtils.enable();
} }
WorkspaceComment.fireCreateEvent((comment)); WorkspaceComment.fireCreateEvent(comment);
return comment; return comment;
} }
} }

View File

@@ -1074,13 +1074,13 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
this.cachedParentSvgSize.width = width; this.cachedParentSvgSize.width = width;
// This is set to support the public (but deprecated) Blockly.svgSize // This is set to support the public (but deprecated) Blockly.svgSize
// method. // method.
svg.setAttribute('data-cached-width', width.toString()); svg.setAttribute('data-cached-width', `${width}`);
} }
if (height != null) { if (height != null) {
this.cachedParentSvgSize.height = height; this.cachedParentSvgSize.height = height;
// This is set to support the public (but deprecated) Blockly.svgSize // This is set to support the public (but deprecated) Blockly.svgSize
// method. // method.
svg.setAttribute('data-cached-height', height.toString()); svg.setAttribute('data-cached-height', `${height}`);
} }
} }

View File

@@ -106,8 +106,8 @@ export function blockToDomWithXY(block: Block, opt_noId?: boolean): Element|
if (isElement(element)) { if (isElement(element)) {
const xy = block.getRelativeToSurfaceXY(); const xy = block.getRelativeToSurfaceXY();
element.setAttribute( element.setAttribute(
'x', `${Math.round(block.workspace.RTL ? width - xy.x : xy.x)}`); 'x', String(Math.round(block.workspace.RTL ? width - xy.x : xy.x)));
element.setAttribute('y', `${Math.round(xy.y)}`); element.setAttribute('y', String(Math.round(xy.y)));
} }
return element; return element;
} }
@@ -191,8 +191,8 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element|
const commentElement = utilsXml.createElement('comment'); const commentElement = utilsXml.createElement('comment');
commentElement.appendChild(utilsXml.createTextNode(commentText)); commentElement.appendChild(utilsXml.createTextNode(commentText));
commentElement.setAttribute('pinned', `${pinned}`); commentElement.setAttribute('pinned', `${pinned}`);
commentElement.setAttribute('h', `${size.height}`); commentElement.setAttribute('h', String(size.height));
commentElement.setAttribute('w', `${size.width}`); commentElement.setAttribute('w', String(size.width));
element.appendChild(commentElement); element.appendChild(commentElement);
} }
@@ -235,7 +235,7 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element|
} }
if (block.inputsInline !== undefined && if (block.inputsInline !== undefined &&
block.inputsInline !== block.inputsInlineDefault) { block.inputsInline !== block.inputsInlineDefault) {
element.setAttribute('inline', block.inputsInline.toString()); element.setAttribute('inline', String(block.inputsInline));
} }
if (block.isCollapsed()) { if (block.isCollapsed()) {
element.setAttribute('collapsed', 'true'); element.setAttribute('collapsed', 'true');

View File

@@ -42,22 +42,11 @@ export class ZoomControls implements IPositionable {
id = 'zoomControls'; id = 'zoomControls';
/** /**
* A handle to use to unbind the mouse down event handler for zoom reset * Array holding info needed to unbind events.
* button. Opaque data returned from browserEvents.conditionalBind. * Used for disposing.
* Ex: [[node, name, func], [node, name, func]].
*/ */
private onZoomResetWrapper: browserEvents.Data|null = null; private boundEvents: browserEvents.Data[] = [];
/**
* A handle to use to unbind the mouse down event handler for zoom in
* button. Opaque data returned from browserEvents.conditionalBind.
*/
private onZoomInWrapper: browserEvents.Data|null = null;
/**
* A handle to use to unbind the mouse down event handler for zoom out
* button. Opaque data returned from browserEvents.conditionalBind.
*/
private onZoomOutWrapper: browserEvents.Data|null = null;
/** The zoom in svg <g> element. */ /** The zoom in svg <g> element. */
private zoomInGroup: SVGGElement|null = null; private zoomInGroup: SVGGElement|null = null;
@@ -144,15 +133,10 @@ export class ZoomControls implements IPositionable {
if (this.svgGroup) { if (this.svgGroup) {
dom.removeNode(this.svgGroup); dom.removeNode(this.svgGroup);
} }
if (this.onZoomResetWrapper) { for (const event of this.boundEvents) {
browserEvents.unbind(this.onZoomResetWrapper); browserEvents.unbind(event);
}
if (this.onZoomInWrapper) {
browserEvents.unbind(this.onZoomInWrapper);
}
if (this.onZoomOutWrapper) {
browserEvents.unbind(this.onZoomOutWrapper);
} }
this.boundEvents.length = 0;
} }
/** /**
@@ -273,8 +257,8 @@ export class ZoomControls implements IPositionable {
this.workspace.options.pathToMedia + SPRITE.url); this.workspace.options.pathToMedia + SPRITE.url);
// Attach listener. // Attach listener.
this.onZoomOutWrapper = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
this.zoomOutGroup, 'pointerdown', null, this.zoom.bind(this, -1)); this.zoomOutGroup, 'pointerdown', null, this.zoom.bind(this, -1)));
} }
/** /**
@@ -319,8 +303,8 @@ export class ZoomControls implements IPositionable {
this.workspace.options.pathToMedia + SPRITE.url); this.workspace.options.pathToMedia + SPRITE.url);
// Attach listener. // Attach listener.
this.onZoomInWrapper = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
this.zoomInGroup, 'pointerdown', null, this.zoom.bind(this, 1)); this.zoomInGroup, 'pointerdown', null, this.zoom.bind(this, 1)));
} }
/** /**
@@ -377,8 +361,8 @@ export class ZoomControls implements IPositionable {
this.workspace.options.pathToMedia + SPRITE.url); this.workspace.options.pathToMedia + SPRITE.url);
// Attach event listeners. // Attach event listeners.
this.onZoomResetWrapper = browserEvents.conditionalBind( this.boundEvents.push(browserEvents.conditionalBind(
this.zoomResetGroup, 'pointerdown', null, this.resetZoom.bind(this)); this.zoomResetGroup, 'pointerdown', null, this.resetZoom.bind(this)));
} }
/** /**

View File

@@ -171,8 +171,7 @@ BlocklyDevTools.Analytics.onExport = function(typeId, optMetadata) {
*/ */
BlocklyDevTools.Analytics.onError = function(e) { BlocklyDevTools.Analytics.onError = function(e) {
// stub // stub
this.LOG_TO_CONSOLE_ && this.LOG_TO_CONSOLE_ && console.log('Analytics.onError("' + e + '")');
console.log('Analytics.onError("' + e.toString() + '")');
}; };
/** /**

View File

@@ -356,9 +356,9 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(type, direction) {
'NUMERIC': function(a, b) { 'NUMERIC': function(a, b) {
return Number(a) - Number(b); }, return Number(a) - Number(b); },
'TEXT': function(a, b) { 'TEXT': function(a, b) {
return a.toString() > b.toString() ? 1 : -1; }, return String(a) > String(b) ? 1 : -1; },
'IGNORE_CASE': function(a, b) { 'IGNORE_CASE': function(a, b) {
return a.toString().toLowerCase() > b.toString().toLowerCase() ? 1 : -1; }, return String(a).toLowerCase() > String(b).toLowerCase() ? 1 : -1; },
}; };
var compare = compareFuncs[type]; var compare = compareFuncs[type];
return function(a, b) { return compare(a, b) * direction; }; return function(a, b) { return compare(a, b) * direction; };

View File

@@ -59,7 +59,7 @@ function runTestTask(id, task) {
successCount++; successCount++;
if (process.env.CI) console.log('::endgroup::'); if (process.env.CI) console.log('::endgroup::');
console.log(`${BOLD_GREEN}SUCCESS:${ANSI_RESET} ${id}`); console.log(`${BOLD_GREEN}SUCCESS:${ANSI_RESET} ${id}`);
results[id] = {success: true}; results[id] = {success: true};
resolve(result); resolve(result);
}) })
.catch((err) => { .catch((err) => {
@@ -191,8 +191,8 @@ function compareSize(file, expected) {
const message = `Failed: Previous size of ${name} is undefined.`; const message = `Failed: Previous size of ${name} is undefined.`;
console.log(`${BOLD_RED}${message}${ANSI_RESET}`); console.log(`${BOLD_RED}${message}${ANSI_RESET}`);
return 1; return 1;
} }
if (size > compare) { if (size > compare) {
const message = `Failed: ` + const message = `Failed: ` +
`Size of ${name} has grown more than 10%. ${size} vs ${expected}`; `Size of ${name} has grown more than 10%. ${size} vs ${expected}`;

View File

@@ -1338,9 +1338,9 @@ function listsGetSortCompare(type, direction) {
'NUMERIC': function(a, b) { 'NUMERIC': function(a, b) {
return Number(a) - Number(b); }, return Number(a) - Number(b); },
'TEXT': function(a, b) { 'TEXT': function(a, b) {
return a.toString() > b.toString() ? 1 : -1; }, return String(a) > String(b) ? 1 : -1; },
'IGNORE_CASE': function(a, b) { 'IGNORE_CASE': function(a, b) {
return a.toString().toLowerCase() > b.toString().toLowerCase() ? 1 : -1; }, return String(a).toLowerCase() > String(b).toLowerCase() ? 1 : -1; },
}; };
var compare = compareFuncs[type]; var compare = compareFuncs[type];
return function(a, b) { return compare(a, b) * direction; }; return function(a, b) { return compare(a, b) * direction; };

View File

@@ -299,7 +299,7 @@ suite('Blocks', function() {
setup(function() { setup(function() {
this.blocks = createTestBlocks(this.workspace, true); this.blocks = createTestBlocks(this.workspace, true);
}); });
test('Don\'t heal', function() { test('Don\'t heal', function() {
this.blocks.B.dispose(false); this.blocks.B.dispose(false);
assertDisposedNoheal(this.blocks); assertDisposedNoheal(this.blocks);
@@ -313,11 +313,11 @@ suite('Blocks', function() {
test('Heal with bad checks', function() { test('Heal with bad checks', function() {
const blocks = this.blocks; const blocks = this.blocks;
// A and C can't connect, but both can connect to B. // A and C can't connect, but both can connect to B.
blocks.A.inputList[0].connection.setCheck('type1'); blocks.A.inputList[0].connection.setCheck('type1');
blocks.C.outputConnection.setCheck('type2'); blocks.C.outputConnection.setCheck('type2');
// Each block has only one input, but the types don't work. // Each block has only one input, but the types don't work.
blocks.B.dispose(true); blocks.B.dispose(true);
assertDisposedHealFailed(blocks); assertDisposedHealFailed(blocks);
@@ -362,7 +362,7 @@ suite('Blocks', function() {
setup(function() { setup(function() {
this.blocks = createTestBlocks(this.workspace, false); this.blocks = createTestBlocks(this.workspace, false);
}); });
test('Don\'t heal', function() { test('Don\'t heal', function() {
this.blocks.B.dispose(); this.blocks.B.dispose();
assertDisposedNoheal(this.blocks); assertDisposedNoheal(this.blocks);
@@ -378,10 +378,10 @@ suite('Blocks', function() {
// A and C can't connect, but both can connect to B. // A and C can't connect, but both can connect to B.
blocks.A.nextConnection.setCheck('type1'); blocks.A.nextConnection.setCheck('type1');
blocks.C.previousConnection.setCheck('type2'); blocks.C.previousConnection.setCheck('type2');
// The types don't work. // The types don't work.
blocks.B.dispose(true); blocks.B.dispose(true);
assertDisposedHealFailed(blocks); assertDisposedHealFailed(blocks);
}); });

View File

@@ -49,9 +49,9 @@ suite('Procedures', function() {
createProcDefBlock(this.workspace, undefined, undefined, 'procB'); createProcDefBlock(this.workspace, undefined, undefined, 'procB');
const callBlockB = const callBlockB =
createProcCallBlock(this.workspace, undefined, 'procB'); createProcCallBlock(this.workspace, undefined, 'procB');
defBlockB.setFieldValue('procA', 'NAME'); defBlockB.setFieldValue('procA', 'NAME');
chai.assert.notEqual( chai.assert.notEqual(
defBlockB.getFieldValue('NAME'), defBlockB.getFieldValue('NAME'),
'procA', 'procA',
@@ -82,7 +82,7 @@ suite('Procedures', function() {
}, },
mutatorWorkspace); mutatorWorkspace);
this.clock.runAll(); this.clock.runAll();
const newFlyoutParamName = const newFlyoutParamName =
mutatorWorkspace.getFlyout().getWorkspace().getTopBlocks(true)[0] mutatorWorkspace.getFlyout().getWorkspace().getTopBlocks(true)[0]
.getFieldValue('NAME'); .getFieldValue('NAME');
@@ -167,7 +167,7 @@ suite('Procedures', function() {
this.workspace.undo(); this.workspace.undo();
this.workspace.undo(/* redo= */ true); this.workspace.undo(/* redo= */ true);
chai.assert.isNotNull( chai.assert.isNotNull(
defBlock.getField('PARAMS'), defBlock.getField('PARAMS'),
'Expected the params field to exist'); 'Expected the params field to exist');
@@ -252,10 +252,10 @@ suite('Procedures', function() {
this.clock.runAll(); this.clock.runAll();
paramBlock.checkAndDelete(); paramBlock.checkAndDelete();
this.clock.runAll(); this.clock.runAll();
this.workspace.undo(); this.workspace.undo();
this.workspace.undo(/* redo= */ true); this.workspace.undo(/* redo= */ true);
chai.assert.isFalse( chai.assert.isFalse(
defBlock.getFieldValue('PARAMS').includes('param1'), defBlock.getFieldValue('PARAMS').includes('param1'),
'Expected the params field to not contain the name of the new param'); 'Expected the params field to not contain the name of the new param');
@@ -300,10 +300,10 @@ suite('Procedures', function() {
.connect(paramBlock1.previousConnection); .connect(paramBlock1.previousConnection);
paramBlock1.nextConnection.connect(paramBlock2.previousConnection); paramBlock1.nextConnection.connect(paramBlock2.previousConnection);
this.clock.runAll(); this.clock.runAll();
paramBlock1.setFieldValue('new name', 'NAME'); paramBlock1.setFieldValue('new name', 'NAME');
this.clock.runAll(); this.clock.runAll();
chai.assert.isNotNull( chai.assert.isNotNull(
defBlock.getField('PARAMS'), defBlock.getField('PARAMS'),
'Expected the params field to exist'); 'Expected the params field to exist');
@@ -349,10 +349,10 @@ suite('Procedures', function() {
paramBlock.setFieldValue('param1', 'NAME'); paramBlock.setFieldValue('param1', 'NAME');
containerBlock.getInput('STACK').connection.connect(paramBlock.previousConnection); containerBlock.getInput('STACK').connection.connect(paramBlock.previousConnection);
this.clock.runAll(); this.clock.runAll();
paramBlock.setFieldValue('param2', 'NAME'); paramBlock.setFieldValue('param2', 'NAME');
this.clock.runAll(); this.clock.runAll();
chai.assert.isNotNull( chai.assert.isNotNull(
this.workspace.getVariable('param1', ''), this.workspace.getVariable('param1', ''),
'Expected the old variable to continue to exist'); 'Expected the old variable to continue to exist');
@@ -372,10 +372,10 @@ suite('Procedures', function() {
.connect(paramBlock.previousConnection); .connect(paramBlock.previousConnection);
this.clock.runAll(); this.clock.runAll();
defBlock.mutator.setVisible(false); defBlock.mutator.setVisible(false);
const variable = this.workspace.getVariable('param1', ''); const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'new name'); this.workspace.renameVariableById(variable.getId(), 'new name');
chai.assert.isNotNull( chai.assert.isNotNull(
defBlock.getField('PARAMS'), defBlock.getField('PARAMS'),
'Expected the params field to exist'); 'Expected the params field to exist');
@@ -397,10 +397,10 @@ suite('Procedures', function() {
containerBlock.getInput('STACK').connection containerBlock.getInput('STACK').connection
.connect(paramBlock.previousConnection); .connect(paramBlock.previousConnection);
this.clock.runAll(); this.clock.runAll();
const variable = this.workspace.getVariable('param1', ''); const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'new name'); this.workspace.renameVariableById(variable.getId(), 'new name');
chai.assert.equal( chai.assert.equal(
paramBlock.getFieldValue('NAME'), paramBlock.getFieldValue('NAME'),
'new name', 'new name',
@@ -422,7 +422,7 @@ suite('Procedures', function() {
.connect(paramBlock.previousConnection); .connect(paramBlock.previousConnection);
this.clock.runAll(); this.clock.runAll();
defBlock.mutator.setVisible(false); defBlock.mutator.setVisible(false);
const variable = this.workspace.getVariable('param1', ''); const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'new name'); this.workspace.renameVariableById(variable.getId(), 'new name');
@@ -449,10 +449,10 @@ suite('Procedures', function() {
.connect(paramBlock.previousConnection); .connect(paramBlock.previousConnection);
this.clock.runAll(); this.clock.runAll();
defBlock.mutator.setVisible(false); defBlock.mutator.setVisible(false);
const variable = this.workspace.getVariable('param1', ''); const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'preCreatedVar'); this.workspace.renameVariableById(variable.getId(), 'preCreatedVar');
chai.assert.isNotNull( chai.assert.isNotNull(
defBlock.getField('PARAMS'), defBlock.getField('PARAMS'),
'Expected the params field to exist'); 'Expected the params field to exist');
@@ -474,10 +474,10 @@ suite('Procedures', function() {
containerBlock.getInput('STACK').connection containerBlock.getInput('STACK').connection
.connect(paramBlock.previousConnection); .connect(paramBlock.previousConnection);
this.clock.runAll(); this.clock.runAll();
const variable = this.workspace.getVariable('param1', ''); const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'preCreatedVar'); this.workspace.renameVariableById(variable.getId(), 'preCreatedVar');
chai.assert.equal( chai.assert.equal(
paramBlock.getFieldValue('NAME'), paramBlock.getFieldValue('NAME'),
'preCreatedVar', 'preCreatedVar',
@@ -499,7 +499,7 @@ suite('Procedures', function() {
.connect(paramBlock.previousConnection); .connect(paramBlock.previousConnection);
this.clock.runAll(); this.clock.runAll();
defBlock.mutator.setVisible(false); defBlock.mutator.setVisible(false);
const variable = this.workspace.getVariable('param1', ''); const variable = this.workspace.getVariable('param1', '');
this.workspace.renameVariableById(variable.getId(), 'preCreatedVar'); this.workspace.renameVariableById(variable.getId(), 'preCreatedVar');
@@ -571,7 +571,7 @@ suite('Procedures', function() {
this.workspace.undo(); this.workspace.undo();
this.workspace.undo(/* redo= */ true); this.workspace.undo(/* redo= */ true);
chai.assert.isTrue( chai.assert.isTrue(
defBlock.getFieldValue('PARAMS').includes('new'), defBlock.getFieldValue('PARAMS').includes('new'),
'Expected the params field to contain the new name of the param'); 'Expected the params field to contain the new name of the param');
@@ -671,14 +671,14 @@ suite('Procedures', function() {
.connect(block1.outputConnection); .connect(block1.outputConnection);
callBlock.getInput('ARG1').connection callBlock.getInput('ARG1').connection
.connect(block2.outputConnection); .connect(block2.outputConnection);
// Reorder the parameters. // Reorder the parameters.
paramBlock2.previousConnection.disconnect(); paramBlock2.previousConnection.disconnect();
paramBlock1.previousConnection.disconnect(); paramBlock1.previousConnection.disconnect();
containerBlock.getInput('STACK').connection.connect(paramBlock2.previousConnection); containerBlock.getInput('STACK').connection.connect(paramBlock2.previousConnection);
paramBlock2.nextConnection.connect(paramBlock1.previousConnection); paramBlock2.nextConnection.connect(paramBlock1.previousConnection);
this.clock.runAll(); this.clock.runAll();
chai.assert.equal( chai.assert.equal(
callBlock.getInputTargetBlock('ARG0'), callBlock.getInputTargetBlock('ARG0'),
block2, block2,

View File

@@ -161,14 +161,14 @@ suite('Angle Fields', function() {
const field = new Blockly.FieldAngle(0, null, { const field = new Blockly.FieldAngle(0, null, {
clockwise: true, clockwise: true,
}); });
chai.assert.isTrue(field.clockwise_); chai.assert.isTrue(field.clockwise);
}); });
test('JSON Definition', function() { test('JSON Definition', function() {
const field = Blockly.FieldAngle.fromJson({ const field = Blockly.FieldAngle.fromJson({
value: 0, value: 0,
clockwise: true, clockwise: true,
}); });
chai.assert.isTrue(field.clockwise_); chai.assert.isTrue(field.clockwise);
}); });
test('Constant', function() { test('Constant', function() {
// Note: Generally constants should be set at compile time, not // Note: Generally constants should be set at compile time, not
@@ -176,7 +176,7 @@ suite('Angle Fields', function() {
// can do this. // can do this.
Blockly.FieldAngle.CLOCKWISE = true; Blockly.FieldAngle.CLOCKWISE = true;
const field = new Blockly.FieldAngle(); const field = new Blockly.FieldAngle();
chai.assert.isTrue(field.clockwise_); chai.assert.isTrue(field.clockwise);
}); });
}); });
suite('Offset', function() { suite('Offset', function() {
@@ -184,14 +184,14 @@ suite('Angle Fields', function() {
const field = new Blockly.FieldAngle(0, null, { const field = new Blockly.FieldAngle(0, null, {
offset: 90, offset: 90,
}); });
chai.assert.equal(field.offset_, 90); chai.assert.equal(field.offset, 90);
}); });
test('JSON Definition', function() { test('JSON Definition', function() {
const field = Blockly.FieldAngle.fromJson({ const field = Blockly.FieldAngle.fromJson({
value: 0, value: 0,
offset: 90, offset: 90,
}); });
chai.assert.equal(field.offset_, 90); chai.assert.equal(field.offset, 90);
}); });
test('Constant', function() { test('Constant', function() {
// Note: Generally constants should be set at compile time, not // Note: Generally constants should be set at compile time, not
@@ -199,7 +199,7 @@ suite('Angle Fields', function() {
// can do this. // can do this.
Blockly.FieldAngle.OFFSET = 90; Blockly.FieldAngle.OFFSET = 90;
const field = new Blockly.FieldAngle(); const field = new Blockly.FieldAngle();
chai.assert.equal(field.offset_, 90); chai.assert.equal(field.offset, 90);
}); });
test('Null', function() { test('Null', function() {
// Note: Generally constants should be set at compile time, not // Note: Generally constants should be set at compile time, not
@@ -210,7 +210,7 @@ suite('Angle Fields', function() {
value: 0, value: 0,
offset: null, offset: null,
}); });
chai.assert.equal(field.offset_, 90); chai.assert.equal(field.offset, 90);
}); });
}); });
suite('Wrap', function() { suite('Wrap', function() {
@@ -218,14 +218,14 @@ suite('Angle Fields', function() {
const field = new Blockly.FieldAngle(0, null, { const field = new Blockly.FieldAngle(0, null, {
wrap: 180, wrap: 180,
}); });
chai.assert.equal(field.wrap_, 180); chai.assert.equal(field.wrap, 180);
}); });
test('JSON Definition', function() { test('JSON Definition', function() {
const field = Blockly.FieldAngle.fromJson({ const field = Blockly.FieldAngle.fromJson({
value: 0, value: 0,
wrap: 180, wrap: 180,
}); });
chai.assert.equal(field.wrap_, 180); chai.assert.equal(field.wrap, 180);
}); });
test('Constant', function() { test('Constant', function() {
// Note: Generally constants should be set at compile time, not // Note: Generally constants should be set at compile time, not
@@ -233,7 +233,7 @@ suite('Angle Fields', function() {
// can do this. // can do this.
Blockly.FieldAngle.WRAP = 180; Blockly.FieldAngle.WRAP = 180;
const field = new Blockly.FieldAngle(); const field = new Blockly.FieldAngle();
chai.assert.equal(field.wrap_, 180); chai.assert.equal(field.wrap, 180);
}); });
test('Null', function() { test('Null', function() {
// Note: Generally constants should be set at compile time, not // Note: Generally constants should be set at compile time, not
@@ -244,7 +244,7 @@ suite('Angle Fields', function() {
value: 0, value: 0,
wrap: null, wrap: null,
}); });
chai.assert.equal(field.wrap_, 180); chai.assert.equal(field.wrap, 180);
}); });
}); });
suite('Round', function() { suite('Round', function() {
@@ -252,14 +252,14 @@ suite('Angle Fields', function() {
const field = new Blockly.FieldAngle(0, null, { const field = new Blockly.FieldAngle(0, null, {
round: 30, round: 30,
}); });
chai.assert.equal(field.round_, 30); chai.assert.equal(field.round, 30);
}); });
test('JSON Definition', function() { test('JSON Definition', function() {
const field = Blockly.FieldAngle.fromJson({ const field = Blockly.FieldAngle.fromJson({
value: 0, value: 0,
round: 30, round: 30,
}); });
chai.assert.equal(field.round_, 30); chai.assert.equal(field.round, 30);
}); });
test('Constant', function() { test('Constant', function() {
// Note: Generally constants should be set at compile time, not // Note: Generally constants should be set at compile time, not
@@ -267,7 +267,7 @@ suite('Angle Fields', function() {
// can do this. // can do this.
Blockly.FieldAngle.ROUND = 30; Blockly.FieldAngle.ROUND = 30;
const field = new Blockly.FieldAngle(); const field = new Blockly.FieldAngle();
chai.assert.equal(field.round_, 30); chai.assert.equal(field.round, 30);
}); });
test('Null', function() { test('Null', function() {
// Note: Generally constants should be set at compile time, not // Note: Generally constants should be set at compile time, not
@@ -278,7 +278,7 @@ suite('Angle Fields', function() {
value: 0, value: 0,
round: null, round: null,
}); });
chai.assert.equal(field.round_, 30); chai.assert.equal(field.round, 30);
}); });
}); });
suite('Mode', function() { suite('Mode', function() {
@@ -287,16 +287,16 @@ suite('Angle Fields', function() {
const field = new Blockly.FieldAngle(0, null, { const field = new Blockly.FieldAngle(0, null, {
mode: 'compass', mode: 'compass',
}); });
chai.assert.equal(field.offset_, 90); chai.assert.equal(field.offset, 90);
chai.assert.isTrue(field.clockwise_); chai.assert.isTrue(field.clockwise);
}); });
test('JS Configuration', function() { test('JS Configuration', function() {
const field = Blockly.FieldAngle.fromJson({ const field = Blockly.FieldAngle.fromJson({
value: 0, value: 0,
mode: 'compass', mode: 'compass',
}); });
chai.assert.equal(field.offset_, 90); chai.assert.equal(field.offset, 90);
chai.assert.isTrue(field.clockwise_); chai.assert.isTrue(field.clockwise);
}); });
}); });
suite('Protractor', function() { suite('Protractor', function() {
@@ -304,16 +304,16 @@ suite('Angle Fields', function() {
const field = new Blockly.FieldAngle(0, null, { const field = new Blockly.FieldAngle(0, null, {
mode: 'protractor', mode: 'protractor',
}); });
chai.assert.equal(field.offset_, 0); chai.assert.equal(field.offset, 0);
chai.assert.isFalse(field.clockwise_); chai.assert.isFalse(field.clockwise);
}); });
test('JS Configuration', function() { test('JS Configuration', function() {
const field = Blockly.FieldAngle.fromJson({ const field = Blockly.FieldAngle.fromJson({
value: 0, value: 0,
mode: 'protractor', mode: 'protractor',
}); });
chai.assert.equal(field.offset_, 0); chai.assert.equal(field.offset, 0);
chai.assert.isFalse(field.clockwise_); chai.assert.isFalse(field.clockwise);
}); });
}); });
}); });

View File

@@ -173,9 +173,9 @@ suite('Colour Fields', function() {
suite('Customizations', function() { suite('Customizations', function() {
suite('Colours and Titles', function() { suite('Colours and Titles', function() {
function assertColoursAndTitles(field, colours, titles) { function assertColoursAndTitles(field, colours, titles) {
field.dropdownCreate_(); field.dropdownCreate();
let index = 0; let index = 0;
let node = field.picker_.firstChild.firstChild; let node = field.picker.firstChild.firstChild;
while (node) { while (node) {
chai.assert.equal(node.getAttribute('title'), titles[index]); chai.assert.equal(node.getAttribute('title'), titles[index]);
chai.assert.equal( chai.assert.equal(
@@ -251,8 +251,8 @@ suite('Colour Fields', function() {
}); });
suite('Columns', function() { suite('Columns', function() {
function assertColumns(field, columns) { function assertColumns(field, columns) {
field.dropdownCreate_(); field.dropdownCreate();
chai.assert.equal(field.picker_.firstChild.children.length, columns); chai.assert.equal(field.picker.firstChild.children.length, columns);
} }
test('Constants', function() { test('Constants', function() {
const columns = Blockly.FieldColour.COLUMNS; const columns = Blockly.FieldColour.COLUMNS;

View File

@@ -189,15 +189,15 @@ suite('Toolbox', function() {
this.toolbox.dispose(); this.toolbox.dispose();
}); });
function createKeyDownMock(keyCode) { function createKeyDownMock(key) {
return { return {
'keyCode': keyCode, 'key': key,
'preventDefault': function() {}, 'preventDefault': function() {},
}; };
} }
function testCorrectFunctionCalled(toolbox, keyCode, funcName) { function testCorrectFunctionCalled(toolbox, key, funcName) {
const event = createKeyDownMock(keyCode); const event = createKeyDownMock(key);
const preventDefaultEvent = sinon.stub(event, 'preventDefault'); const preventDefaultEvent = sinon.stub(event, 'preventDefault');
const selectMethodStub = sinon.stub(toolbox, funcName); const selectMethodStub = sinon.stub(toolbox, funcName);
selectMethodStub.returns(true); selectMethodStub.returns(true);
@@ -207,21 +207,21 @@ suite('Toolbox', function() {
} }
test('Down button is pushed -> Should call selectNext_', function() { test('Down button is pushed -> Should call selectNext_', function() {
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.DOWN, 'selectNext_', true); testCorrectFunctionCalled(this.toolbox, 'ArrowDown', 'selectNext_', true);
}); });
test('Up button is pushed -> Should call selectPrevious_', function() { test('Up button is pushed -> Should call selectPrevious_', function() {
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.UP, 'selectPrevious_', true); testCorrectFunctionCalled(this.toolbox, 'ArrowUp', 'selectPrevious_', true);
}); });
test('Left button is pushed -> Should call selectParent_', function() { test('Left button is pushed -> Should call selectParent_', function() {
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.LEFT, 'selectParent_', true); testCorrectFunctionCalled(this.toolbox, 'ArrowLeft', 'selectParent_', true);
}); });
test('Right button is pushed -> Should call selectChild_', function() { test('Right button is pushed -> Should call selectChild_', function() {
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.RIGHT, 'selectChild_', true); testCorrectFunctionCalled(this.toolbox, 'ArrowRight', 'selectChild_', true);
}); });
test('Enter button is pushed -> Should toggle expandedd', function() { test('Enter button is pushed -> Should toggle expanded', function() {
this.toolbox.selectedItem_ = getCollapsibleItem(this.toolbox); this.toolbox.selectedItem_ = getCollapsibleItem(this.toolbox);
const toggleExpandedStub = sinon.stub(this.toolbox.selectedItem_, 'toggleExpanded'); const toggleExpandedStub = sinon.stub(this.toolbox.selectedItem_, 'toggleExpanded');
const event = createKeyDownMock(Blockly.utils.KeyCodes.ENTER); const event = createKeyDownMock('Enter');
const preventDefaultEvent = sinon.stub(event, 'preventDefault'); const preventDefaultEvent = sinon.stub(event, 'preventDefault');
this.toolbox.onKeyDown_(event); this.toolbox.onKeyDown_(event);
sinon.assert.called(toggleExpandedStub); sinon.assert.called(toggleExpandedStub);
@@ -229,7 +229,7 @@ suite('Toolbox', function() {
}); });
test('Enter button is pushed when no item is selected -> Should not call prevent default', function() { test('Enter button is pushed when no item is selected -> Should not call prevent default', function() {
this.toolbox.selectedItem_ = null; this.toolbox.selectedItem_ = null;
const event = createKeyDownMock(Blockly.utils.KeyCodes.ENTER); const event = createKeyDownMock('Enter');
const preventDefaultEvent = sinon.stub(event, 'preventDefault'); const preventDefaultEvent = sinon.stub(event, 'preventDefault');
this.toolbox.onKeyDown_(event); this.toolbox.onKeyDown_(event);
sinon.assert.notCalled(preventDefaultEvent); sinon.assert.notCalled(preventDefaultEvent);

View File

@@ -301,7 +301,7 @@ suite('Variable Map', function() {
const variable = const variable =
this.variableMap.createVariable('test name', 'test type', 'test id'); this.variableMap.createVariable('test name', 'test type', 'test id');
this.variableMap.deleteVariable(variable); this.variableMap.deleteVariable(variable);
assertEventFired( assertEventFired(
this.eventSpy, this.eventSpy,
Blockly.Events.VarDelete, Blockly.Events.VarDelete,
@@ -320,7 +320,7 @@ suite('Variable Map', function() {
new Blockly.VariableModel( new Blockly.VariableModel(
this.workspace, 'test name', 'test type', 'test id'); this.workspace, 'test name', 'test type', 'test id');
this.variableMap.deleteVariable(variable); this.variableMap.deleteVariable(variable);
assertEventNotFired( assertEventNotFired(
this.eventSpy, this.eventSpy,
Blockly.Events.VarDelete, Blockly.Events.VarDelete,
@@ -335,7 +335,7 @@ suite('Variable Map', function() {
function() { function() {
this.variableMap.createVariable('test name', 'test type', 'test id'); this.variableMap.createVariable('test name', 'test type', 'test id');
this.variableMap.deleteVariableById('test id'); this.variableMap.deleteVariableById('test id');
assertEventFired( assertEventFired(
this.eventSpy, this.eventSpy,
Blockly.Events.VarDelete, Blockly.Events.VarDelete,
@@ -351,7 +351,7 @@ suite('Variable Map', function() {
'delete events are not fired when a variable does not exist', 'delete events are not fired when a variable does not exist',
function() { function() {
this.variableMap.deleteVariableById('test id'); this.variableMap.deleteVariableById('test id');
assertEventNotFired( assertEventNotFired(
this.eventSpy, this.eventSpy,
Blockly.Events.VarDelete, Blockly.Events.VarDelete,
@@ -379,7 +379,7 @@ suite('Variable Map', function() {
}, },
this.workspace.id); this.workspace.id);
}); });
test( test(
'rename events are not fired if the variable name already matches', 'rename events are not fired if the variable name already matches',
function() { function() {
@@ -387,14 +387,14 @@ suite('Variable Map', function() {
this.variableMap.createVariable( this.variableMap.createVariable(
'test name', 'test type', 'test id'); 'test name', 'test type', 'test id');
this.variableMap.renameVariable(variable, 'test name'); this.variableMap.renameVariable(variable, 'test name');
assertEventNotFired( assertEventNotFired(
this.eventSpy, this.eventSpy,
Blockly.Events.VarRename, Blockly.Events.VarRename,
{}, {},
this.workspace.id); this.workspace.id);
}); });
test( test(
'rename events are not fired if the variable does not exist', 'rename events are not fired if the variable does not exist',
function() { function() {
@@ -402,7 +402,7 @@ suite('Variable Map', function() {
new Blockly.VariableModel( new Blockly.VariableModel(
'test name', 'test type', 'test id'); 'test name', 'test type', 'test id');
this.variableMap.renameVariable(variable, 'test name'); this.variableMap.renameVariable(variable, 'test name');
assertEventNotFired( assertEventNotFired(
this.eventSpy, this.eventSpy,
Blockly.Events.VarRename, Blockly.Events.VarRename,
@@ -426,21 +426,21 @@ suite('Variable Map', function() {
}, },
this.workspace.id); this.workspace.id);
}); });
test( test(
'rename events are not fired if the variable name already matches', 'rename events are not fired if the variable name already matches',
function() { function() {
this.variableMap.createVariable( this.variableMap.createVariable(
'test name', 'test type', 'test id'); 'test name', 'test type', 'test id');
this.variableMap.renameVariableById('test id', 'test name'); this.variableMap.renameVariableById('test id', 'test name');
assertEventNotFired( assertEventNotFired(
this.eventSpy, this.eventSpy,
Blockly.Events.VarRename, Blockly.Events.VarRename,
{}, {},
this.workspace.id); this.workspace.id);
}); });
test( test(
'renaming throws if the variable does not exist', 'renaming throws if the variable does not exist',
function() { function() {

View File

@@ -30,7 +30,7 @@ async function runMochaTestsInBrowser() {
], ],
logLevel: 'warn', logLevel: 'warn',
}; };
// Run in headless mode on Github Actions. // Run in headless mode on Github Actions.
if (process.env.CI) { if (process.env.CI) {
options.capabilities['goog:chromeOptions'].args.push( options.capabilities['goog:chromeOptions'].args.push(

View File

@@ -54,7 +54,7 @@ class FieldMitosis extends Field<CellGroup> {
doMitosis(): void { doMitosis(): void {
const cellGroup = this.getValue(); const cellGroup = this.getValue();
if (!cellGroup) return; if (!cellGroup) return;
const cells = cellGroup.cells.flatMap((cell) => { const cells = cellGroup.cells.flatMap((cell) => {
const leftCell: Cell = {cellId: `${cell.cellId}-left`}; const leftCell: Cell = {cellId: `${cell.cellId}-left`};
const rightCell: Cell = {cellId: `${cell.cellId}-right`}; const rightCell: Cell = {cellId: `${cell.cellId}-right`};