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

@@ -133,8 +133,8 @@ function connectionUiStep(ripple: SVGElement, start: Date, scale: number) {
if (percent > 1) {
dom.removeNode(ripple);
} else {
ripple.setAttribute('r', (percent * 25 * scale).toString());
ripple.style.opacity = (1 - percent).toString();
ripple.setAttribute('r', String(percent * 25 * scale));
ripple.style.opacity = String(1 - percent);
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.
const callback = bindData[bindData.length - 1][2];
while (bindData.length) {
const bindDatum = bindData.pop();
const node = bindDatum![0];
const name = bindDatum![1];
const func = bindDatum![2];
const [node, name, func] = bindData.pop()!;
node.removeEventListener(name, func, false);
}
return callback;

View File

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

View File

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

View File

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

View File

@@ -261,10 +261,8 @@ export function registerDeleteAll() {
const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length;
if (deletableBlocksLength === 1) {
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) {
if (!scope.workspace) {
@@ -489,7 +487,7 @@ export function registerDelete() {
}
return descendantCount === 1 ?
Msg['DELETE_BLOCK'] :
Msg['DELETE_X_BLOCKS'].replace('%1', String(descendantCount));
Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`);
},
preconditionFn(scope: Scope) {
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
* subclasses that want to handle configuration and setting 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 value & returns a validated value, 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.
* Refer to the individual field's documentation for a list of properties
* this parameter supports.
*/
constructor(
value: T|Sentinel, opt_validator?: FieldValidator<T>|null,
opt_config?: FieldConfig) {
value: T|Sentinel, validator?: FieldValidator<T>|null,
config?: FieldConfig) {
/**
* A generic value possessed by the field.
* 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);
if (Field.isSentinel(value)) return;
if (opt_config) {
this.configure_(opt_config);
if (config) {
this.configure_(config);
}
this.setValue(value);
if (opt_validator) {
this.setValidator(opt_validator);
if (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.
* 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.
* @sealed
* @internal
*/
showEditor(opt_e?: Event) {
showEditor(e?: Event) {
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.
*
* @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 xOffset = opt_margin !== undefined ? opt_margin :
const xOffset = margin !== undefined ? margin :
this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING :
0;
let totalWidth = xOffset * 2;
@@ -783,17 +783,17 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
this.textElement_.setAttribute(
'x',
`${
String(
this.getSourceBlock()?.RTL ?
this.size_.width - contentWidth - xOffset :
xOffset}`);
xOffset));
this.textElement_.setAttribute(
'y',
`${
String(
constants!.FIELD_TEXT_BASELINE_CENTER ?
halfHeight :
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
constants!.FIELD_TEXT_BASELINE}`);
constants!.FIELD_TEXT_BASELINE));
}
/** 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_) {
return;
}
this.borderRect_.setAttribute('width', `${this.size_.width}`);
this.borderRect_.setAttribute('height', `${this.size_.height}`);
this.borderRect_.setAttribute('width', String(this.size_.width));
this.borderRect_.setAttribute('height', String(this.size_.height));
this.borderRect_.setAttribute(
'rx', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
'rx', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
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 {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import * as dom from './utils/dom.js';
import {KeyCodes} from './utils/keycodes.js';
import * as math from './utils/math.js';
import type {Sentinel} from './utils/sentinel.js';
import {Svg} from './utils/svg.js';
@@ -31,15 +30,15 @@ import * as WidgetDiv from './widgetdiv.js';
* Class for an editable angle field.
*/
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. */
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
* increase. Angle increases clockwise (true) or counterclockwise (false).
@@ -60,84 +59,73 @@ export class FieldAngle extends FieldInput<number> {
static readonly WRAP = 360;
/**
* Radius of protractor circle. Slightly smaller than protractor size since
* otherwise SVG crops off half the border at the edges.
* 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 RADIUS: number = FieldAngle.HALF - 1;
static readonly ROUND = 15;
/**
* Whether the angle should increase as the angle picker is moved clockwise
* (true) or counterclockwise (false).
*/
private clockwise_ = FieldAngle.CLOCKWISE;
private clockwise = FieldAngle.CLOCKWISE;
/**
* The offset of zero degrees (and all other angles).
*/
private offset_ = FieldAngle.OFFSET;
private offset = FieldAngle.OFFSET;
/**
* 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.
*/
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. */
gauge_: SVGPathElement|null = null;
/** Dynamic red line pointing at the value's angle. */
private line: SVGLineElement|null = null;
/** The angle picker's line drawn representing the value's angle. */
line_: SVGLineElement|null = null;
/** Dynamic pink area extending from 0 to the value's angle. */
private gauge: SVGPathElement|null = null;
/** The degree symbol for this field. */
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
* 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.
* @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
* (only used by subclasses that want to handle configuration and setting
* 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
* 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
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string|number|Sentinel, opt_validator?: FieldAngleValidator,
opt_config?: FieldAngleConfig) {
value?: string|number|Sentinel, validator?: FieldAngleValidator,
config?: FieldAngleConfig) {
super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
if (Field.isSentinel(value)) return;
if (config) {
this.configure_(config);
}
this.setValue(opt_value);
if (opt_validator) {
this.setValidator(opt_validator);
this.setValue(value);
if (validator) {
this.setValidator(validator);
}
}
@@ -151,22 +139,22 @@ export class FieldAngle extends FieldInput<number> {
switch (config.mode) {
case Mode.COMPASS:
this.clockwise_ = true;
this.offset_ = 90;
this.clockwise = true;
this.offset = 90;
break;
case Mode.PROTRACTOR:
// This is the default mode, so we could do nothing. But just to
// future-proof, we'll set it anyway.
this.clockwise_ = false;
this.offset_ = 0;
this.clockwise = false;
this.offset = 0;
break;
}
// Allow individual settings to override the mode setting.
if (config.clockwise) this.clockwise_ = config.clockwise;
if (config.offset) this.offset_ = config.offset;
if (config.wrap) this.wrap_ = config.wrap;
if (config.round) this.round_ = config.round;
if (config.clockwise) this.clockwise = config.clockwise;
if (config.offset) this.offset = config.offset;
if (config.wrap) this.wrap = config.wrap;
if (config.round) this.round = config.round;
}
/**
@@ -176,32 +164,32 @@ export class FieldAngle extends FieldInput<number> {
*/
override initView() {
super.initView();
// Add the degree symbol to the left of the number, even in RTL (issue
// #2380)
// Add the degree symbol to the left of the number,
// even in RTL (issue #2380).
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {});
this.symbol_.appendChild(document.createTextNode('°'));
this.getTextElement().appendChild(this.symbol_);
}
/** Updates the graph when the field rerenders. */
/** Updates the angle when the field rerenders. */
protected override render_() {
super.render_();
this.updateGraph_();
this.updateGraph();
}
/**
* Create and show the angle field's editor.
*
* @param opt_e Optional mouse event that triggered the field to open, or
* undefined if triggered programmatically.
* @param e Optional mouse event that triggered the field to open,
* 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).
const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
super.showEditor_(opt_e, noFocus);
super.showEditor_(e, noFocus);
this.dropdownCreate_();
dropDownDiv.getContentDiv().appendChild(this.editor_!);
const editor = this.dropdownCreate();
dropDownDiv.getContentDiv().appendChild(editor);
if (this.sourceBlock_ instanceof BlockSvg) {
dropDownDiv.setColour(
@@ -209,13 +197,17 @@ export class FieldAngle extends FieldInput<number> {
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, {
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
@@ -233,9 +225,9 @@ export class FieldAngle extends FieldInput<number> {
'class': 'blocklyAngleCircle',
},
svg);
this.gauge_ =
this.gauge =
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
this.line_ = dom.createSvgElement(
this.line = dom.createSvgElement(
Svg.LINE, {
'x1': 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
// mousemove even if it's not in the middle of a drag. In future we may
// change this behaviour.
this.clickWrapper_ =
browserEvents.conditionalBind(svg, 'click', this, this.hide_);
this.boundEvents.push(
browserEvents.conditionalBind(svg, 'click', this, this.hide));
// 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
// is clicked.
this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
circle, 'pointerdown', this, this.onMouseMove_, true);
this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
circle, 'pointermove', this, this.onMouseMove_, true);
this.editor_ = svg;
this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointerdown', this, this.onMouseMove_, true));
this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointermove', this, this.onMouseMove_, true));
return svg;
}
/** Disposes of events and DOM-references belonging to the angle editor. */
private dropdownDispose_() {
if (this.clickWrapper_) {
browserEvents.unbind(this.clickWrapper_);
this.clickWrapper_ = null;
private dropdownDispose() {
for (const event of this.boundEvents) {
browserEvents.unbind(event);
}
if (this.clickSurfaceWrapper_) {
browserEvents.unbind(this.clickSurfaceWrapper_);
this.clickSurfaceWrapper_ = null;
}
if (this.moveSurfaceWrapper_) {
browserEvents.unbind(this.moveSurfaceWrapper_);
this.moveSurfaceWrapper_ = null;
}
this.gauge_ = null;
this.line_ = null;
this.boundEvents.length = 0;
this.gauge = null;
this.line = null;
}
/** Hide the editor. */
private hide_() {
private hide() {
dropDownDiv.hideIfOwner(this);
WidgetDiv.hide();
}
@@ -304,7 +288,7 @@ export class FieldAngle extends FieldInput<number> {
*/
protected onMouseMove_(e: PointerEvent) {
// Calculate angle.
const bBox = this.gauge_!.ownerSVGElement!.getBoundingClientRect();
const bBox = this.gauge!.ownerSVGElement!.getBoundingClientRect();
const dx = e.clientX - bBox.left - FieldAngle.HALF;
const dy = e.clientY - bBox.top - FieldAngle.HALF;
let angle = Math.atan(-dy / dx);
@@ -321,13 +305,13 @@ export class FieldAngle extends FieldInput<number> {
}
// Do offsetting.
if (this.clockwise_) {
angle = this.offset_ + 360 - angle;
if (this.clockwise) {
angle = this.offset + 360 - angle;
} 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.
*/
private displayMouseOrKeyboardValue_(angle: number) {
if (this.round_) {
angle = Math.round(angle / this.round_) * this.round_;
private displayMouseOrKeyboardValue(angle: number) {
if (this.round) {
angle = Math.round(angle / this.round) * this.round;
}
angle = this.wrapValue_(angle);
angle = this.wrapValue(angle);
if (angle !== this.value_) {
this.setEditorValue_(angle);
}
}
/** Redraw the graph with the current angle. */
private updateGraph_() {
if (!this.gauge_) {
private updateGraph() {
if (!this.gauge || !this.line) {
return;
}
// 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;
let angleRadians = math.toRadians(angleDegrees);
const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
let x2 = FieldAngle.HALF;
let y2 = FieldAngle.HALF;
if (!isNaN(angleRadians)) {
const clockwiseFlag = Number(this.clockwise_);
const angle1 = math.toRadians(this.offset_);
const clockwiseFlag = Number(this.clockwise);
const angle1 = math.toRadians(this.offset);
const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
if (clockwiseFlag) {
@@ -379,9 +363,9 @@ export class FieldAngle extends FieldInput<number> {
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
}
this.gauge_.setAttribute('d', path.join(''));
this.line_?.setAttribute('x2', `${x2}`);
this.line_?.setAttribute('y2', `${y2}`);
this.gauge.setAttribute('d', path.join(''));
this.line.setAttribute('x2', `${x2}`);
this.line.setAttribute('y2', `${y2}`);
}
/**
@@ -396,23 +380,28 @@ export class FieldAngle extends FieldInput<number> {
throw new UnattachedFieldError();
}
let multiplier;
if (e.keyCode === KeyCodes.LEFT) {
let multiplier = 0;
switch (e.key) {
case 'ArrowLeft':
// decrement (increment in RTL)
multiplier = block.RTL ? 1 : -1;
} else if (e.keyCode === KeyCodes.RIGHT) {
break;
case 'ArrowRight':
// increment (decrement in RTL)
multiplier = block.RTL ? -1 : 1;
} else if (e.keyCode === KeyCodes.DOWN) {
break;
case 'ArrowDown':
// decrement
multiplier = -1;
} else if (e.keyCode === KeyCodes.UP) {
break;
case 'ArrowUp':
// increment
multiplier = 1;
break;
}
if (multiplier) {
const value = this.getValue() as number;
this.displayMouseOrKeyboardValue_(value + multiplier * this.round_);
this.displayMouseOrKeyboardValue(value + multiplier * this.round);
e.preventDefault();
e.stopPropagation();
}
@@ -421,15 +410,15 @@ export class FieldAngle extends FieldInput<number> {
/**
* 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.
*/
protected override doClassValidation_(opt_newValue?: any): number|null {
const value = Number(opt_newValue);
protected override doClassValidation_(newValue?: any): number|null {
const value = Number(newValue);
if (isNaN(value) || !isFinite(value)) {
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.
* @returns The wrapped value.
*/
private wrapValue_(value: number): number {
private wrapValue(value: number): number {
value %= 360;
if (value < 0) {
value += 360;
}
if (value > this.wrap_) {
if (value > this.wrap) {
value -= 360;
}
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(`
.blocklyAngleCircle {
stroke: #444;
stroke-width: 1;
fill: #ddd;
fill-opacity: .8;
fill-opacity: 0.8;
}
.blocklyAngleMarks {
@@ -480,7 +476,7 @@ Css.register(`
.blocklyAngleGauge {
fill: #f88;
fill-opacity: .8;
fill-opacity: 0.8;
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.
* Compass specifies:

View File

@@ -49,22 +49,22 @@ export class FieldCheckbox extends Field<CheckboxBool> {
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
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting 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 value ('TRUE' or 'FALSE') & returns a
* 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
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: CheckboxBool|Sentinel, opt_validator?: FieldCheckboxValidator,
opt_config?: FieldCheckboxConfig) {
value?: CheckboxBool|Sentinel, validator?: FieldCheckboxValidator,
config?: FieldCheckboxConfig) {
super(Field.SKIP_SETUP);
/**
@@ -73,13 +73,13 @@ export class FieldCheckbox extends Field<CheckboxBool> {
*/
this.checkChar_ = FieldCheckbox.CHECK_CHAR;
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
if (Field.isSentinel(value)) return;
if (config) {
this.configure_(config);
}
this.setValue(opt_value);
if (opt_validator) {
this.setValidator(opt_validator);
this.setValue(value);
if (validator) {
this.setValidator(validator);
}
}
@@ -150,15 +150,15 @@ export class FieldCheckbox extends Field<CheckboxBool> {
/**
* 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.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
protected override doClassValidation_(newValue?: AnyDuringMigration):
BoolString|null {
if (opt_newValue === true || opt_newValue === 'TRUE') {
if (newValue === true || newValue === 'TRUE') {
return 'TRUE';
}
if (opt_newValue === false || opt_newValue === 'FALSE') {
if (newValue === false || newValue === 'FALSE') {
return 'FALSE';
}
return null;

View File

@@ -25,7 +25,6 @@ import * as fieldRegistry from './field_registry.js';
import * as aria from './utils/aria.js';
import * as colour from './utils/colour.js';
import * as idGenerator from './utils/idgenerator.js';
import {KeyCodes} from './utils/keycodes.js';
import type {Sentinel} from './utils/sentinel.js';
import {Size} from './utils/size.js';
@@ -76,25 +75,17 @@ export class FieldColour extends Field<string> {
static COLUMNS = 7;
/** The field's colour picker element. */
private picker_: HTMLElement|null = null;
private picker: HTMLElement|null = null;
/** 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;
/** Mouse move event data. */
private onMouseMoveWrapper_: browserEvents.Data|null = null;
/** 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;
/**
* Array holding info needed to unbind events.
* Used for disposing.
* Ex: [[node, name, func], [node, name, func]].
*/
private boundEvents: browserEvents.Data[] = [];
/**
* Serializable fields are saved by the serializer, non-serializable fields
@@ -113,46 +104,46 @@ export class FieldColour extends Field<string> {
protected override isDirty_ = false;
/** 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
* list.
*/
private titles_: string[]|null = null;
private titles: string[]|null = null;
/**
* Number of colour columns used by this field. If 0, use the global
* 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
* accepts Field.SKIP_SETUP if you wish to skip setup (only used by
* subclasses that want to handle configuration and setting 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 colour string & returns a validated colour
* string ('#rrggbb' format), or null to abort the change.Blockly.
* @param opt_config A map of options used to configure the field.
* string ('#rrggbb' format), or null to abort the change.
* @param config A map of options used to configure the field.
* See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string|Sentinel, opt_validator?: FieldColourValidator,
opt_config?: FieldColourConfig) {
value?: string|Sentinel, validator?: FieldColourValidator,
config?: FieldColourConfig) {
super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
if (Field.isSentinel(value)) return;
if (config) {
this.configure_(config);
}
this.setValue(opt_value);
if (opt_validator) {
this.setValidator(opt_validator);
this.setValue(value);
if (validator) {
this.setValidator(validator);
}
}
@@ -163,9 +154,9 @@ export class FieldColour extends Field<string> {
*/
protected override configure_(config: FieldColourConfig) {
super.configure_(config);
if (config.colourOptions) this.colours_ = config.colourOptions;
if (config.colourTitles) this.titles_ = config.colourTitles;
if (config.columns) this.columns_ = config.columns;
if (config.colourOptions) this.colours = config.colourOptions;
if (config.colourTitles) this.titles = config.colourTitles;
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() {
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
if (this.borderRect_) {
@@ -200,14 +196,14 @@ export class FieldColour extends Field<string> {
/**
* 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.
*/
protected override doClassValidation_(opt_newValue?: any): string|null {
if (typeof opt_newValue !== 'string') {
protected override doClassValidation_(newValue?: any): string|null {
if (typeof newValue !== 'string') {
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
* (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).
* @returns Returns itself (for method chaining).
*/
setColours(colours: string[], opt_titles?: string[]): FieldColour {
this.colours_ = colours;
if (opt_titles) {
this.titles_ = opt_titles;
setColours(colours: string[], titles?: string[]): FieldColour {
this.colours = colours;
if (titles) {
this.titles = titles;
}
return this;
}
@@ -267,19 +263,19 @@ export class FieldColour extends Field<string> {
* @returns Returns itself (for method chaining).
*/
setColumns(columns: number): FieldColour {
this.columns_ = columns;
this.columns = columns;
return this;
}
/** Create and show the colour field's editor. */
protected override showEditor_() {
this.dropdownCreate_();
dropDownDiv.getContentDiv().appendChild(this.picker_!);
this.dropdownCreate();
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.
this.picker_!.focus({preventScroll: true});
this.picker!.focus({preventScroll: true});
}
/**
@@ -287,7 +283,7 @@ export class FieldColour extends Field<string> {
*
* @param e Mouse event.
*/
private onClick_(e: PointerEvent) {
private onClick(e: PointerEvent) {
const cell = e.target as Element;
const colour = cell && cell.getAttribute('data-colour');
if (colour !== null) {
@@ -302,31 +298,35 @@ export class FieldColour extends Field<string> {
*
* @param e Keyboard event.
*/
private onKeyDown_(e: KeyboardEvent) {
let handled = false;
if (e.keyCode === KeyCodes.UP) {
this.moveHighlightBy_(0, -1);
handled = true;
} else if (e.keyCode === KeyCodes.DOWN) {
this.moveHighlightBy_(0, 1);
handled = true;
} else if (e.keyCode === KeyCodes.LEFT) {
this.moveHighlightBy_(-1, 0);
handled = true;
} else if (e.keyCode === KeyCodes.RIGHT) {
this.moveHighlightBy_(1, 0);
handled = true;
} else if (e.keyCode === KeyCodes.ENTER) {
private onKeyDown(e: KeyboardEvent) {
let handled = true;
let highlighted: HTMLElement|null;
switch (e.key) {
case 'ArrowUp':
this.moveHighlightBy(0, -1);
break;
case 'ArrowDown':
this.moveHighlightBy(0, 1);
break;
case 'ArrowLeft':
this.moveHighlightBy(-1, 0);
break;
case 'ArrowRight':
this.moveHighlightBy(1, 0);
break;
case 'Enter':
// Select the highlighted colour.
const highlighted = this.getHighlighted_();
highlighted = this.getHighlighted();
if (highlighted) {
const colour = highlighted && highlighted.getAttribute('data-colour');
const colour = highlighted.getAttribute('data-colour');
if (colour !== null) {
this.setValue(colour);
}
}
dropDownDiv.hideWithoutAnimation();
handled = true;
break;
default:
handled = false;
}
if (handled) {
e.stopPropagation();
@@ -336,22 +336,22 @@ export class FieldColour extends Field<string> {
/**
* Move the currently highlighted position by dx and dy.
*
* @param dx Change of x
* @param dy Change of y
* @param dx Change of x.
* @param dy Change of y.
*/
private moveHighlightBy_(dx: number, dy: number) {
if (!this.highlightedIndex_) {
private moveHighlightBy(dx: number, dy: number) {
if (!this.highlightedIndex) {
return;
}
const colours = this.colours_ || FieldColour.COLOURS;
const columns = this.columns_ || FieldColour.COLUMNS;
const colours = this.colours || FieldColour.COLOURS;
const columns = this.columns || FieldColour.COLUMNS;
// Get the current x and y coordinates
let x = this.highlightedIndex_ % columns;
let y = Math.floor(this.highlightedIndex_ / columns);
// Get the current x and y coordinates.
let x = this.highlightedIndex % columns;
let y = Math.floor(this.highlightedIndex / columns);
// Add the offset
// Add the offset.
x += dx;
y += dy;
@@ -386,9 +386,9 @@ export class FieldColour extends Field<string> {
}
// 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;
this.setHighlightedCell_(cell, index);
this.setHighlightedCell(cell, index);
}
/**
@@ -396,26 +396,26 @@ export class FieldColour extends Field<string> {
*
* @param e Mouse event.
*/
private onMouseMove_(e: PointerEvent) {
private onMouseMove(e: PointerEvent) {
const cell = e.target as Element;
const index = cell && Number(cell.getAttribute('data-index'));
if (index !== null && index !== this.highlightedIndex_) {
this.setHighlightedCell_(cell, index);
if (index !== null && index !== this.highlightedIndex) {
this.setHighlightedCell(cell, index);
}
}
/** Handle a mouse enter event. Focus the picker. */
private onMouseEnter_() {
this.picker_?.focus({preventScroll: true});
private onMouseEnter() {
this.picker?.focus({preventScroll: true});
}
/**
* Handle a mouse leave event. Blur the picker and unhighlight
* the currently highlighted colour.
*/
private onMouseLeave_() {
this.picker_?.blur();
const highlighted = this.getHighlighted_();
private onMouseLeave() {
this.picker?.blur();
const highlighted = this.getHighlighted();
if (highlighted) {
dom.removeClass(highlighted, 'blocklyColourHighlighted');
}
@@ -426,54 +426,53 @@ export class FieldColour extends Field<string> {
*
* @returns Highlighted item (null if none).
*/
private getHighlighted_(): HTMLElement|null {
if (!this.highlightedIndex_) {
private getHighlighted(): HTMLElement|null {
if (!this.highlightedIndex) {
return null;
}
const columns = this.columns_ || FieldColour.COLUMNS;
const x = this.highlightedIndex_ % columns;
const y = Math.floor(this.highlightedIndex_ / columns);
const row = this.picker_!.childNodes[y];
const columns = this.columns || FieldColour.COLUMNS;
const x = this.highlightedIndex % columns;
const y = Math.floor(this.highlightedIndex / columns);
const row = this.picker!.childNodes[y];
if (!row) {
return null;
}
const col = row.childNodes[x] as HTMLElement;
return col;
return row.childNodes[x] as HTMLElement;
}
/**
* Update the currently highlighted cell.
*
* @param cell the new cell to highlight
* @param index the index of the new cell
* @param cell The new cell to highlight.
* @param index The index of the new cell.
*/
private setHighlightedCell_(cell: Element, index: number) {
private setHighlightedCell(cell: Element, index: number) {
// Unhighlight the current item.
const highlighted = this.getHighlighted_();
const highlighted = this.getHighlighted();
if (highlighted) {
dom.removeClass(highlighted, 'blocklyColourHighlighted');
}
// Highlight new item.
dom.addClass(cell, 'blocklyColourHighlighted');
// Set new highlighted index.
this.highlightedIndex_ = index;
this.highlightedIndex = index;
// Update accessibility roles.
const cellId = cell.getAttribute('id');
if (cellId && this.picker_) {
aria.setState(this.picker_, aria.State.ACTIVEDESCENDANT, cellId);
if (cellId && this.picker) {
aria.setState(this.picker, aria.State.ACTIVEDESCENDANT, cellId);
}
}
/** Create a colour picker dropdown editor. */
private dropdownCreate_() {
const columns = this.columns_ || FieldColour.COLUMNS;
const colours = this.colours_ || FieldColour.COLOURS;
const titles = this.titles_ || FieldColour.TITLES;
private dropdownCreate() {
const columns = this.columns || FieldColour.COLUMNS;
const colours = this.colours || FieldColour.COLOURS;
const titles = this.titles || FieldColour.TITLES;
const selectedColour = this.getValue();
// Create the palette.
const table = (document.createElement('table'));
const table = document.createElement('table');
table.className = 'blocklyColourTable';
table.tabIndex = 0;
table.dir = 'ltr';
@@ -495,56 +494,40 @@ export class FieldColour extends Field<string> {
cell.setAttribute('data-colour', colours[i]);
cell.title = titles[i] || colours[i];
cell.id = idGenerator.getNextUniqueId();
cell.setAttribute('data-index', String(i));
cell.setAttribute('data-index', `${i}`);
aria.setRole(cell, aria.Role.GRIDCELL);
aria.setState(cell, aria.State.LABEL, colours[i]);
aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour);
cell.style.backgroundColor = colours[i];
if (colours[i] === selectedColour) {
cell.className = 'blocklyColourSelected';
this.highlightedIndex_ = i;
this.highlightedIndex = i;
}
}
// Configure event handler on the table to listen for any event in a cell.
this.onClickWrapper_ = browserEvents.conditionalBind(
table, 'pointerdown', this, this.onClick_, true);
this.onMouseMoveWrapper_ = browserEvents.conditionalBind(
table, 'pointermove', this, this.onMouseMove_, true);
this.onMouseEnterWrapper_ = browserEvents.conditionalBind(
table, 'pointerenter', this, this.onMouseEnter_, true);
this.onMouseLeaveWrapper_ = browserEvents.conditionalBind(
table, 'pointerleave', this, this.onMouseLeave_, true);
this.onKeyDownWrapper_ =
browserEvents.conditionalBind(table, 'keydown', this, this.onKeyDown_);
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerdown', this, this.onClick, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointermove', this, this.onMouseMove, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerenter', this, this.onMouseEnter, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerleave', this, this.onMouseLeave, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'keydown', this, this.onKeyDown, false));
this.picker_ = table;
this.picker = table;
}
/** Disposes of events and DOM-references belonging to the colour editor. */
private dropdownDispose_() {
if (this.onClickWrapper_) {
browserEvents.unbind(this.onClickWrapper_);
this.onClickWrapper_ = null;
private dropdownDispose() {
for (const event of this.boundEvents) {
browserEvents.unbind(event);
}
if (this.onMouseMoveWrapper_) {
browserEvents.unbind(this.onMouseMoveWrapper_);
this.onMouseMoveWrapper_ = 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;
this.boundEvents.length = 0;
this.picker = null;
this.highlightedIndex = null;
}
/**
@@ -565,7 +548,12 @@ export class FieldColour extends Field<string> {
/** The default value for this field. */
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(`
.blocklyColourTable {
border-collapse: collapse;
@@ -575,7 +563,7 @@ Css.register(`
}
.blocklyColourTable>tr>td {
border: .5px solid #888;
border: 0.5px solid #888;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
@@ -586,7 +574,7 @@ Css.register(`
.blocklyColourTable>tr>td.blocklyColourHighlighted {
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;
}
@@ -597,8 +585,6 @@ Css.register(`
}
`);
fieldRegistry.register('field_colour', FieldColour);
/**
* 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
* configuration and setting 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 language-neutral dropdown option & returns a
* validated language-neutral dropdown option, 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
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation}
* for a list of properties this parameter supports.
@@ -109,14 +109,14 @@ export class FieldDropdown extends Field<string> {
*/
constructor(
menuGenerator: MenuGenerator,
opt_validator?: FieldDropdownValidator,
opt_config?: FieldDropdownConfig,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig,
);
constructor(menuGenerator: Sentinel);
constructor(
menuGenerator: MenuGenerator|Sentinel,
opt_validator?: FieldDropdownValidator,
opt_config?: FieldDropdownConfig,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig,
) {
super(Field.SKIP_SETUP);
@@ -139,12 +139,12 @@ export class FieldDropdown extends Field<string> {
*/
this.selectedOption_ = this.getOptions(false)[0];
if (opt_config) {
this.configure_(opt_config);
if (config) {
this.configure_(config);
}
this.setValue(this.selectedOption_[1]);
if (opt_validator) {
this.setValidator(opt_validator);
if (validator) {
this.setValidator(validator);
}
}
@@ -244,17 +244,17 @@ export class FieldDropdown extends Field<string> {
/**
* 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.
*/
protected override showEditor_(opt_e?: MouseEvent) {
protected override showEditor_(e?: MouseEvent) {
const block = this.getSourceBlock();
if (!block) {
throw new UnattachedFieldError();
}
this.dropdownCreate_();
if (opt_e && typeof opt_e.clientX === 'number') {
this.menu_!.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY);
if (e && typeof e.clientX === 'number') {
this.menu_!.openingCoords = new Coordinate(e.clientX, e.clientY);
} else {
this.menu_!.openingCoords = null;
}
@@ -368,20 +368,20 @@ export class FieldDropdown extends Field<string> {
/**
* 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.
* @returns A non-empty array of option tuples:
* (human-readable text or image, language-neutral name).
* @throws {TypeError} If generated options are incorrectly structured.
*/
getOptions(opt_useCache?: boolean): MenuOption[] {
getOptions(useCache?: boolean): MenuOption[] {
if (!this.menuGenerator_) {
// A subclass improperly skipped setup without defining the menu
// generator.
throw TypeError('A menu generator was never defined.');
}
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_();
validateOptions(this.generatedOptions_);
@@ -391,23 +391,23 @@ export class FieldDropdown extends Field<string> {
/**
* 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.
*/
protected override doClassValidation_(opt_newValue?: string): string|null {
protected override doClassValidation_(newValue?: string): string|null {
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 (this.sourceBlock_) {
console.warn(
'Cannot set the dropdown\'s value to an unavailable option.' +
' Block type: ' + this.sourceBlock_.type +
', Field name: ' + this.name + ', Value: ' + opt_newValue);
', Field name: ' + this.name + ', Value: ' + newValue);
}
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_!.setAttributeNS(
dom.XLINK_NS, 'xlink:href', imageJson.src);
this.imageElement_!.setAttribute('height', `${imageJson.height}`);
this.imageElement_!.setAttribute('width', `${imageJson.width}`);
this.imageElement_!.setAttribute('height', String(imageJson.height));
this.imageElement_!.setAttribute('width', String(imageJson.width));
const imageHeight = Number(imageJson.height);
const imageWidth = Number(imageJson.width);
@@ -512,14 +512,13 @@ export class FieldDropdown extends Field<string> {
let arrowX = 0;
if (block.RTL) {
const imageX = xPadding + arrowWidth;
this.imageElement_!.setAttribute('x', imageX.toString());
this.imageElement_!.setAttribute('x', `${imageX}`);
} else {
arrowX = imageWidth + arrowWidth;
this.getTextElement().setAttribute('text-anchor', 'end');
this.imageElement_!.setAttribute('x', xPadding.toString());
this.imageElement_!.setAttribute('x', `${xPadding}`);
}
this.imageElement_!.setAttribute(
'y', (height / 2 - imageHeight / 2).toString());
this.imageElement_!.setAttribute('y', String(height / 2 - imageHeight / 2));
this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth);
}

View File

@@ -64,19 +64,19 @@ export class FieldImage extends Field<string> {
* after their own constructors have run).
* @param width Width of the image.
* @param height Height of the image.
* @param opt_alt Optional alt text for when block is collapsed.
* @param opt_onClick Optional function to be called when the image is
* clicked. If opt_onClick is defined, opt_alt must also be defined.
* @param opt_flipRtl Whether to flip the icon in RTL.
* @param opt_config A map of options used to configure the field.
* @param alt Optional alt text for when block is collapsed.
* @param onClick Optional function to be called when the image is
* clicked. If onClick is defined, alt must also be defined.
* @param flipRtl Whether to flip the icon in RTL.
* @param config A map of options used to configure the field.
* See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation}
* for a list of properties this parameter supports.
*/
constructor(
src: string|Sentinel, width: string|number, height: string|number,
opt_alt?: string, opt_onClick?: (p1: FieldImage) => void,
opt_flipRtl?: boolean, opt_config?: FieldImageConfig) {
alt?: string, onClick?: (p1: FieldImage) => void, flipRtl?: boolean,
config?: FieldImageConfig) {
super(Field.SKIP_SETUP);
const imageHeight = Number(parsing.replaceMessageReferences(height));
@@ -100,19 +100,19 @@ export class FieldImage extends Field<string> {
*/
this.imageHeight_ = imageHeight;
if (typeof opt_onClick === 'function') {
this.clickHandler_ = opt_onClick;
if (typeof onClick === 'function') {
this.clickHandler_ = onClick;
}
if (src === Field.SKIP_SETUP) {
return;
}
if (opt_config) {
this.configure_(opt_config);
if (config) {
this.configure_(config);
} else {
this.flipRtl_ = !!opt_flipRtl;
this.altText_ = parsing.replaceMessageReferences(opt_alt) || '';
this.flipRtl_ = !!flipRtl;
this.altText_ = parsing.replaceMessageReferences(alt) || '';
}
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.
*
* @param opt_newValue The input value.
* @param newValue The input value.
* @returns A string, or null if invalid.
*/
protected override doClassValidation_(opt_newValue?: any): string|null {
if (typeof opt_newValue !== 'string') {
protected override doClassValidation_(newValue?: any): string|null {
if (typeof newValue !== 'string') {
return null;
}
return opt_newValue;
return newValue;
}
/**
@@ -177,7 +177,7 @@ export class FieldImage extends Field<string> {
this.value_ = newValue;
if (this.imageElement_) {
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 * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js';
import {KeyCodes} from './utils/keycodes.js';
import type {Sentinel} from './utils/sentinel.js';
import * as userAgent from './utils/useragent.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';
/**
* @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
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting 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 string & returns a validated string, 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
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string|Sentinel, opt_validator?: FieldInputValidator<T>|null,
opt_config?: FieldInputConfig) {
value?: string|Sentinel, validator?: FieldInputValidator<T>|null,
config?: FieldInputConfig) {
super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
if (Field.isSentinel(value)) return;
if (config) {
this.configure_(config);
}
this.setValue(opt_value);
if (opt_validator) {
this.setValidator(opt_validator);
this.setValue(value);
if (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
* 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.
* @param opt_quietInput True if editor should be created without focus.
* @param quietInput True if editor should be created without focus.
* 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;
const quietInput = opt_quietInput || false;
if (!quietInput && this.workspace_.options.modalInputs &&
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
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';
if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) {
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;
@@ -444,15 +442,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* @param e Keyboard event.
*/
protected onHtmlInputKeyDown_(e: KeyboardEvent) {
if (e.keyCode === KeyCodes.ENTER) {
if (e.key === 'Enter') {
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
} else if (e.keyCode === KeyCodes.ESC) {
} else if (e.key === 'Esc') {
this.setValue(
this.htmlInput_!.getAttribute('data-untyped-default-value'));
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
} else if (e.keyCode === KeyCodes.TAB) {
} else if (e.key === 'Tab') {
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
(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.
*/
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.
*/
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;
/**
@@ -33,29 +33,28 @@ export class FieldLabel extends Field<string> {
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
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting the field value after
* their own constructors have run).
* @param opt_class Optional CSS class for the field's text.
* @param opt_config A map of options used to configure the field.
* @param textClass Optional CSS class for the field's text.
* @param config A map of options used to configure the field.
* See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string|Sentinel, opt_class?: string,
opt_config?: FieldLabelConfig) {
value?: string|Sentinel, textClass?: string, config?: FieldLabelConfig) {
super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
if (Field.isSentinel(value)) return;
if (config) {
this.configure_(config);
} else {
this.class_ = opt_class || null;
this.class_ = textClass || null;
}
this.setValue(opt_value);
this.setValue(value);
}
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.
*
* @param opt_newValue The input value.
* @param newValue The input value.
* @returns A valid string, or null if invalid.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
string|null {
if (opt_newValue === null || opt_newValue === undefined) {
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
if (newValue === null || newValue === undefined) {
return null;
}
return String(opt_newValue);
return `${newValue}`;
}
/**

View File

@@ -36,17 +36,16 @@ export class FieldLabelSerializable extends FieldLabel {
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.
* @param opt_class Optional CSS class for the field's text.
* @param opt_config A map of options used to configure the field.
* @param textClass Optional CSS class for the field's text.
* @param config A map of options used to configure the field.
* See the [field creation documentation]{@link
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string, opt_class?: string, opt_config?: FieldLabelConfig) {
super(String(opt_value ?? ''), opt_class, opt_config);
constructor(value?: string, textClass?: string, config?: FieldLabelConfig) {
super(String(value ?? ''), textClass, config);
}
/**

View File

@@ -18,7 +18,6 @@ import * as fieldRegistry from './field_registry.js';
import {FieldTextInput, FieldTextInputConfig, FieldTextInputValidator} from './field_textinput.js';
import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js';
import {KeyCodes} from './utils/keycodes.js';
import * as parsing from './utils/parsing.js';
import type {Sentinel} from './utils/sentinel.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
* when initialized.
*/
// AnyDuringMigration because: Type 'null' is not assignable to type
// 'SVGGElement'.
textGroup_: SVGGElement = null as AnyDuringMigration;
textGroup: SVGGElement|null = null;
/**
* Defines the maximum number of lines of field.
@@ -47,35 +44,40 @@ export class FieldMultilineInput extends FieldTextInput {
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
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting the field value after
* 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
* argument and returns either the accepted text, a replacement text, 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
* 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.
*/
constructor(
opt_value?: string|Sentinel, opt_validator?: FieldMultilineInputValidator,
opt_config?: FieldMultilineInputConfig) {
value?: string|Sentinel, validator?: FieldMultilineInputValidator,
config?: FieldMultilineInputConfig) {
super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
if (Field.isSentinel(value)) return;
if (config) {
this.configure_(config);
}
this.setValue(opt_value);
if (opt_validator) {
this.setValidator(opt_validator);
this.setValue(value);
if (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) {
super.configure_(config);
if (config.maxLines) this.setMaxLines(config.maxLines);
@@ -112,6 +114,8 @@ export class FieldMultilineInput extends FieldTextInput {
/**
* 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.
* @internal
@@ -126,6 +130,8 @@ export class FieldMultilineInput extends FieldTextInput {
/**
* 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.
* @internal
@@ -144,7 +150,7 @@ export class FieldMultilineInput extends FieldTextInput {
*/
override initView() {
this.createBorderRect_();
this.textGroup_ = dom.createSvgElement(
this.textGroup = dom.createSvgElement(
Svg.G, {
'class': 'blocklyEditableText',
},
@@ -219,8 +225,9 @@ export class FieldMultilineInput extends FieldTextInput {
}
// Remove all text group children.
let currentChild;
while (currentChild = this.textGroup_.firstChild) {
this.textGroup_.removeChild(currentChild);
const textGroup = this.textGroup;
while (currentChild = textGroup!.firstChild) {
textGroup!.removeChild(currentChild);
}
// 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,
'dy': this.getConstants()!.FIELD_TEXT_BASELINE,
},
this.textGroup_);
textGroup);
span.appendChild(document.createTextNode(lines[i]));
y += lineHeight;
}
@@ -274,7 +281,7 @@ export class FieldMultilineInput extends FieldTextInput {
/** Updates the size of the field based on the text. */
protected override updateSize_() {
const nodes = this.textGroup_.childNodes;
const nodes = (this.textGroup as SVGElement).childNodes;
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE;
const fontWeight = this.getConstants()!.FIELD_TEXT_FONTWEIGHT;
const fontFamily = this.getConstants()!.FIELD_TEXT_FONTFAMILY;
@@ -320,13 +327,8 @@ export class FieldMultilineInput extends FieldTextInput {
if (this.borderRect_) {
totalHeight += this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * 2;
totalWidth += this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * 2;
// AnyDuringMigration because: Argument of type 'number' is not
// assignable to parameter of type 'string'.
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.borderRect_.setAttribute('width', `${totalWidth}`);
this.borderRect_.setAttribute('height', `${totalHeight}`);
}
this.size_.width = totalWidth;
this.size_.height = totalHeight;
@@ -339,13 +341,13 @@ export class FieldMultilineInput extends FieldTextInput {
* Overrides the default behaviour to force rerender in order to
* 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.
* @param opt_quietInput True if editor should be created without focus.
* @param quietInput True if editor should be created without focus.
* Defaults to false.
*/
override showEditor_(_opt_e?: Event, opt_quietInput?: boolean) {
super.showEditor_(_opt_e, opt_quietInput);
override showEditor_(e?: Event, quietInput?: boolean) {
super.showEditor_(e, quietInput);
this.forceRerender();
}
@@ -360,10 +362,7 @@ export class FieldMultilineInput extends FieldTextInput {
const htmlInput = (document.createElement('textarea'));
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
// AnyDuringMigration because: Argument of type 'boolean' is not assignable
// to parameter of type 'string'.
htmlInput.setAttribute(
'spellcheck', this.spellcheck_ as AnyDuringMigration);
htmlInput.setAttribute('spellcheck', String(this.spellcheck_));
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt';
div!.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize;
@@ -425,7 +424,7 @@ export class FieldMultilineInput extends FieldTextInput {
* @param e Keyboard event.
*/
protected override onHtmlInputKeyDown_(e: KeyboardEvent) {
if (e.keyCode !== KeyCodes.ENTER) {
if (e.key !== 'Enter') {
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(`
.blocklyHtmlTextAreaInput {
font-family: monospace;
@@ -463,8 +467,6 @@ Css.register(`
}
`);
fieldRegistry.register('field_multilinetext', FieldMultilineInput);
/**
* Config options for the multiline input field.
*/

View File

@@ -37,51 +37,44 @@ export class FieldNumber extends FieldInput<number> {
*/
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. */
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
* (only used by subclasses that want to handle configuration and setting
* 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.
* @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.
* @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.
* @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
* 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
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string|number|Sentinel, opt_min?: string|number|null,
opt_max?: string|number|null, opt_precision?: string|number|null,
opt_validator?: FieldNumberValidator|null,
opt_config?: FieldNumberConfig) {
value?: string|number|Sentinel, min?: string|number|null,
max?: string|number|null, precision?: string|number|null,
validator?: FieldNumberValidator|null, config?: FieldNumberConfig) {
// Pass SENTINEL so that we can define properties before value validation.
super(Field.SKIP_SETUP);
if (Field.isSentinel(opt_value)) return;
if (opt_config) {
this.configure_(opt_config);
if (Field.isSentinel(value)) return;
if (config) {
this.configure_(config);
} else {
this.setConstraints(opt_min, opt_max, opt_precision);
this.setConstraints(min, max, precision);
}
this.setValue(opt_value);
if (opt_validator) {
this.setValidator(opt_validator);
this.setValue(value);
if (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
* constraints placed on the field).
*
* @param opt_newValue The input value.
* @param newValue The input value.
* @returns A valid number, or null if invalid.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
number|null {
if (opt_newValue === null) {
protected override doClassValidation_(newValue?: AnyDuringMigration): number
|null {
if (newValue === null) {
return null;
}
// Clean up text.
let newValue = String(opt_newValue);
newValue = `${newValue}`;
// TODO: Handle cases like 'ten', '1.203,14', etc.
// 'O' is sometimes mistaken for '0' by inexperienced users.
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> {
/**
* @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
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
* that want to handle configuration and setting 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 string & returns a validated string, 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
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
* for a list of properties this parameter supports.
*/
constructor(
opt_value?: string|Sentinel, opt_validator?: FieldTextInputValidator|null,
opt_config?: FieldTextInputConfig) {
super(opt_value, opt_validator, opt_config);
value?: string|Sentinel, validator?: FieldTextInputValidator|null,
config?: FieldTextInputConfig) {
super(value, validator, config);
}
/**
* 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.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
string|null {
if (opt_newValue === undefined) {
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
if (newValue === undefined) {
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
* subclasses that want to handle configuration and setting 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 variable ID & returns a validated variable
* ID, or null to abort the change.
* @param opt_variableTypes A list of the types of variables to include in the
* dropdown. Will only be used if opt_config is not provided.
* @param opt_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
* @param variableTypes A list of the types of variables to include in the
* dropdown. Will only be used if config is not provided.
* @param defaultType The type of variable to create if this field's value
* is not explicitly set. Defaults to ''. Will only be used if config
* 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
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/variable#creation}
* for a list of properties this parameter supports.
*/
constructor(
varName: string|null|Sentinel, opt_validator?: FieldVariableValidator,
opt_variableTypes?: string[], opt_defaultType?: string,
opt_config?: FieldVariableConfig) {
varName: string|null|Sentinel, validator?: FieldVariableValidator,
variableTypes?: string[], defaultType?: string,
config?: FieldVariableConfig) {
super(Field.SKIP_SETUP);
/**
@@ -101,13 +101,13 @@ export class FieldVariable extends FieldDropdown {
return;
}
if (opt_config) {
this.configure_(opt_config);
if (config) {
this.configure_(config);
} else {
this.setTypes_(opt_variableTypes, opt_defaultType);
this.setTypes_(variableTypes, defaultType);
}
if (opt_validator) {
this.setValidator(opt_validator);
if (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.
*
* @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.
*/
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
string|null {
if (opt_newValue === null) {
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
if (newValue === null) {
return null;
}
const block = this.getSourceBlock();
if (!block) {
throw new UnattachedFieldError();
}
const newId = opt_newValue as string;
const newId = newValue as string;
const variable = Variables.getVariable(block.workspace, newId);
if (!variable) {
console.warn(
@@ -410,23 +410,19 @@ export class FieldVariable extends FieldDropdown {
* Parse the optional arguments representing the allowed variable types and
* 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
* 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 ''.
*/
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
// in the variable types array, tell the Blockly team by commenting on
// #1499.
const defaultType = opt_defaultType || '';
let variableTypes;
// Set the allowable variable types. Null means all types on the workspace.
if (opt_variableTypes === null || opt_variableTypes === undefined) {
variableTypes = null;
} else if (Array.isArray(opt_variableTypes)) {
variableTypes = opt_variableTypes;
if (Array.isArray(variableTypes)) {
variableTypes = variableTypes;
// Make sure the default type is valid.
let isInArray = false;
for (let i = 0; i < variableTypes.length; i++) {
@@ -439,7 +435,7 @@ export class FieldVariable extends FieldDropdown {
'Invalid default type \'' + defaultType + '\' in ' +
'the definition of a FieldVariable');
}
} else {
} else if (variableTypes !== null) {
throw Error(
'\'variableTypes\' was not an array in the definition of ' +
'a FieldVariable');

View File

@@ -74,8 +74,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
/**
* Lay out the blocks in the flyout.
*
* @param contents The blocks and buttons to lay
* out.
* @param contents The blocks and buttons to lay out.
* @param gaps The visible gaps between blocks.
*/
protected abstract layout_(contents: FlyoutItem[], gaps: number[]): void;
@@ -128,9 +127,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
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
@@ -357,9 +358,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.hide();
Array.prototype.push.apply(
this.eventWrappers_,
browserEvents.conditionalBind(
this.boundEvents.push(browserEvents.conditionalBind(
(this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_));
if (!this.autoClose) {
this.filterWrapper_ = this.filterForCapacity_.bind(this);
@@ -367,9 +366,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
}
// Dragging the flyout up and down.
Array.prototype.push.apply(
this.eventWrappers_,
browserEvents.conditionalBind(
this.boundEvents.push(browserEvents.conditionalBind(
(this.svgBackground_ as SVGPathElement), 'pointerdown', this,
this.onMouseDown_));
@@ -401,7 +398,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
dispose() {
this.hide();
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_) {
this.targetWorkspace.removeChangeListener(this.filterWrapper_);
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.
*/
protected positionAt_(width: number, height: number, x: number, y: number) {
this.svgGroup_?.setAttribute('width', width.toString());
this.svgGroup_?.setAttribute('height', height.toString());
this.svgGroup_?.setAttribute('width', `${width}`);
this.svgGroup_?.setAttribute('height', `${height}`);
this.workspace_.setCachedParentSvgSize(width, height);
if (this.svgGroup_) {
@@ -562,7 +562,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
}
this.setVisible(false);
// Delete all the event listeners.
for (let i = 0, listen; listen = this.listeners_[i]; i++) {
for (const listen of this.listeners_) {
browserEvents.unbind(listen);
}
this.listeners_.length = 0;
@@ -781,7 +781,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
blockInfo: toolbox.BlockInfo, gaps: number[], defaultGap: number) {
let gap;
if (blockInfo['gap']) {
gap = parseInt(blockInfo['gap'].toString());
gap = parseInt(String(blockInfo['gap']));
} else if (blockInfo['blockxml']) {
const xml = (typeof blockInfo['blockxml'] === 'string' ?
utilsXml.textToDom(blockInfo['blockxml']) :
@@ -806,7 +806,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// <sep gap="36"></sep>
// The default gap is 24, can be set larger or smaller.
// 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.
if (!isNaN(newGap) && gaps.length > 0) {
gaps[gaps.length - 1] = newGap;
@@ -1056,13 +1056,13 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/
protected moveRectToBlock_(rect: SVGElement, block: BlockSvg) {
const blockHW = block.getHeightWidth();
rect.setAttribute('width', blockHW.width.toString());
rect.setAttribute('height', blockHW.height.toString());
rect.setAttribute('width', String(blockHW.width));
rect.setAttribute('height', String(blockHW.height));
const blockXY = block.getRelativeToSurfaceXY();
rect.setAttribute('y', blockXY.y.toString());
rect.setAttribute('y', String(blockXY.y));
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_) {
this.width += 2 * FlyoutButton.TEXT_MARGIN_X;
this.height += 2 * FlyoutButton.TEXT_MARGIN_Y;
shadow?.setAttribute('width', this.width.toString());
shadow?.setAttribute('height', this.height.toString());
shadow?.setAttribute('width', String(this.width));
shadow?.setAttribute('height', String(this.height));
}
rect.setAttribute('width', this.width.toString());
rect.setAttribute('height', this.height.toString());
rect.setAttribute('width', String(this.width));
rect.setAttribute('height', String(this.height));
svgText.setAttribute('x', (this.width / 2).toString());
svgText.setAttribute('x', String(this.width / 2));
svgText.setAttribute(
'y',
(this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline)
.toString());
String(
this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline));
this.updateTransform_();

View File

@@ -279,7 +279,7 @@ export class CodeGenerator {
if (!Array.isArray(tuple)) {
throw TypeError(
`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`);
}
let code = tuple[0];

View File

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

View File

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

View File

@@ -177,7 +177,7 @@ export class ASTNode {
throw new Error(
'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;
for (let i = curIdx; i < block.inputList.length; i++) {
const newInput = block.inputList[i];
@@ -240,7 +240,7 @@ export class ASTNode {
throw new Error(
'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;
for (let i = curIdx; i >= 0; 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 {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import {KeyCodes} from './utils/keycodes.js';
import type {Size} from './utils/size.js';
import * as style from './utils/style.js';
@@ -392,29 +391,29 @@ export class Menu {
}
const highlighted = this.highlightedItem;
switch (keyboardEvent.keyCode) {
case KeyCodes.ENTER:
case KeyCodes.SPACE:
switch (keyboardEvent.key) {
case 'Enter':
case ' ':
if (highlighted) {
highlighted.performAction();
}
break;
case KeyCodes.UP:
case 'ArrowUp':
this.highlightPrevious();
break;
case KeyCodes.DOWN:
case 'ArrowDown':
this.highlightNext();
break;
case KeyCodes.PAGE_UP:
case KeyCodes.HOME:
case 'PageUp':
case 'Home':
this.highlightFirst();
break;
case KeyCodes.PAGE_DOWN:
case KeyCodes.END:
case 'PageDown':
case 'End':
this.highlightLast();
break;

View File

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

View File

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

View File

@@ -223,12 +223,12 @@ export class Scrollbar {
this.svgBackground.setAttribute('height', String(scrollbarThickness));
this.outerSvg.setAttribute('height', String(scrollbarThickness));
this.svgHandle.setAttribute('height', String(scrollbarThickness - 5));
this.svgHandle.setAttribute('y', String(2.5));
this.svgHandle.setAttribute('y', '2.5');
} else {
this.svgBackground.setAttribute('width', String(scrollbarThickness));
this.outerSvg.setAttribute('width', String(scrollbarThickness));
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(
keyCode: string|number|KeyCodes, shortcutName: string,
opt_allowCollision?: boolean) {
keyCode = String(keyCode);
keyCode = `${keyCode}`;
const shortcutNames = this.keyMap.get(keyCode);
if (shortcutNames && !opt_allowCollision) {
throw new Error(`Shortcut named "${
@@ -280,7 +280,7 @@ export class ShortcutRegistry {
if (serializedKey !== '' && e.keyCode) {
serializedKey = serializedKey + '+' + e.keyCode;
} else if (e.keyCode) {
serializedKey = e.keyCode.toString();
serializedKey = String(e.keyCode);
}
return serializedKey;
}
@@ -327,7 +327,7 @@ export class ShortcutRegistry {
if (serializedKey !== '' && keyCode) {
serializedKey = serializedKey + '+' + keyCode;
} else if (keyCode) {
serializedKey = keyCode.toString();
serializedKey = `${keyCode}`;
}
return serializedKey;
}

View File

@@ -116,10 +116,16 @@ export class Theme implements ITheme {
*/
getComponentStyle(componentName: string): string|null {
const style = (this.componentStyles as AnyDuringMigration)[componentName];
if (style && typeof style === 'string' && this.getComponentStyle((style))) {
return this.getComponentStyle((style));
if (!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. */
protected colour_ = '';
/** The html container for the category. */
/** The HTML container for the category. */
protected htmlDiv_: HTMLDivElement|null = null;
/** The html element for the category row. */
/** The HTML element for the category row. */
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;
/** The html element for the toolbox icon. */
/** The HTML element for the toolbox icon. */
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 cssConfig_: CssConfig;

View File

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

View File

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

View File

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

View File

@@ -63,7 +63,7 @@ let canvasContext: CanvasRenderingContext2D|null = null;
export function createSvgElement<T extends SVGElement>(
name: string|Svg<T>, attrs: {[key: string]: string|number},
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) {
e.setAttribute(key, `${attrs[key]}`);
}

View File

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

View File

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

View File

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

View File

@@ -1074,13 +1074,13 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
this.cachedParentSvgSize.width = width;
// This is set to support the public (but deprecated) Blockly.svgSize
// method.
svg.setAttribute('data-cached-width', width.toString());
svg.setAttribute('data-cached-width', `${width}`);
}
if (height != null) {
this.cachedParentSvgSize.height = height;
// This is set to support the public (but deprecated) Blockly.svgSize
// 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)) {
const xy = block.getRelativeToSurfaceXY();
element.setAttribute(
'x', `${Math.round(block.workspace.RTL ? width - xy.x : xy.x)}`);
element.setAttribute('y', `${Math.round(xy.y)}`);
'x', String(Math.round(block.workspace.RTL ? width - xy.x : xy.x)));
element.setAttribute('y', String(Math.round(xy.y)));
}
return element;
}
@@ -191,8 +191,8 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element|
const commentElement = utilsXml.createElement('comment');
commentElement.appendChild(utilsXml.createTextNode(commentText));
commentElement.setAttribute('pinned', `${pinned}`);
commentElement.setAttribute('h', `${size.height}`);
commentElement.setAttribute('w', `${size.width}`);
commentElement.setAttribute('h', String(size.height));
commentElement.setAttribute('w', String(size.width));
element.appendChild(commentElement);
}
@@ -235,7 +235,7 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element|
}
if (block.inputsInline !== undefined &&
block.inputsInline !== block.inputsInlineDefault) {
element.setAttribute('inline', block.inputsInline.toString());
element.setAttribute('inline', String(block.inputsInline));
}
if (block.isCollapsed()) {
element.setAttribute('collapsed', 'true');

View File

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

View File

@@ -356,9 +356,9 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(type, direction) {
'NUMERIC': function(a, b) {
return Number(a) - Number(b); },
'TEXT': function(a, b) {
return a.toString() > b.toString() ? 1 : -1; },
return String(a) > String(b) ? 1 : -1; },
'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];
return function(a, b) { return compare(a, b) * direction; };

View File

@@ -1338,9 +1338,9 @@ function listsGetSortCompare(type, direction) {
'NUMERIC': function(a, b) {
return Number(a) - Number(b); },
'TEXT': function(a, b) {
return a.toString() > b.toString() ? 1 : -1; },
return String(a) > String(b) ? 1 : -1; },
'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];
return function(a, b) { return compare(a, b) * direction; };

View File

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

View File

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

View File

@@ -189,15 +189,15 @@ suite('Toolbox', function() {
this.toolbox.dispose();
});
function createKeyDownMock(keyCode) {
function createKeyDownMock(key) {
return {
'keyCode': keyCode,
'key': key,
'preventDefault': function() {},
};
}
function testCorrectFunctionCalled(toolbox, keyCode, funcName) {
const event = createKeyDownMock(keyCode);
function testCorrectFunctionCalled(toolbox, key, funcName) {
const event = createKeyDownMock(key);
const preventDefaultEvent = sinon.stub(event, 'preventDefault');
const selectMethodStub = sinon.stub(toolbox, funcName);
selectMethodStub.returns(true);
@@ -207,21 +207,21 @@ suite('Toolbox', 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() {
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.UP, 'selectPrevious_', true);
testCorrectFunctionCalled(this.toolbox, 'ArrowUp', 'selectPrevious_', true);
});
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() {
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);
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');
this.toolbox.onKeyDown_(event);
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() {
this.toolbox.selectedItem_ = null;
const event = createKeyDownMock(Blockly.utils.KeyCodes.ENTER);
const event = createKeyDownMock('Enter');
const preventDefaultEvent = sinon.stub(event, 'preventDefault');
this.toolbox.onKeyDown_(event);
sinon.assert.notCalled(preventDefaultEvent);