From 42fde0f81b5d89c989741c6db41726bce7d2d781 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 15 Mar 2023 21:28:57 +0100 Subject: [PATCH] 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. --- .github/ISSUE_TEMPLATE/bug_report.yaml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yaml | 2 +- core/block_animations.ts | 4 +- core/browser_events.ts | 5 +- core/bubble.ts | 28 +- core/comment.ts | 51 +-- core/component_manager.ts | 8 +- core/contextmenu_items.ts | 6 +- core/field.ts | 44 +-- core/field_angle.ts | 264 ++++++++-------- core/field_checkbox.ts | 30 +- core/field_colour.ts | 294 +++++++++--------- core/field_dropdown.ts | 55 ++-- core/field_image.ts | 36 +-- core/field_input.ts | 40 ++- core/field_label.ts | 31 +- core/field_label_serializable.ts | 11 +- core/field_multilineinput.ts | 80 ++--- core/field_number.ts | 49 ++- core/field_textinput.ts | 22 +- core/field_variable.ts | 54 ++-- core/flyout_base.ts | 46 +-- core/flyout_button.ts | 14 +- core/generator.ts | 2 +- core/gesture.ts | 44 +-- core/grid.ts | 18 +- core/keyboard_nav/ast_node.ts | 4 +- core/menu.ts | 19 +- core/registry.ts | 19 +- core/renderers/zelos/marker_svg.ts | 4 +- core/scrollbar.ts | 4 +- core/shortcut_registry.ts | 6 +- core/theme.ts | 12 +- core/toolbox/category.ts | 10 +- core/toolbox/toolbox.ts | 19 +- core/trashcan.ts | 2 +- core/utils/colour.ts | 2 +- core/utils/dom.ts | 2 +- core/utils/parsing.ts | 2 +- core/workspace_comment.ts | 8 +- core/workspace_comment_svg.ts | 17 +- core/workspace_svg.ts | 4 +- core/xml.ts | 10 +- core/zoom_controls.ts | 42 +-- demos/blockfactory/analytics.js | 3 +- generators/javascript/lists.js | 4 +- scripts/gulpfiles/test_tasks.js | 6 +- tests/generators/golden/generated.js | 4 +- tests/mocha/block_test.js | 12 +- tests/mocha/blocks/procedures_test.js | 46 +-- tests/mocha/field_angle_test.js | 46 +-- tests/mocha/field_colour_test.js | 8 +- tests/mocha/toolbox_test.js | 22 +- tests/mocha/variable_map_test.js | 22 +- tests/mocha/webdriver.js | 2 +- .../src/field/different_user_input.ts | 2 +- 56 files changed, 756 insertions(+), 847 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 1b3af57bd..c0097d770 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -5,7 +5,7 @@ body: - type: markdown attributes: value: > - Thank you for taking the time to fill out a bug report! + Thank you for taking the time to fill out a bug report! If you have a question about how to use Blockly in your application, please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue. - type: checkboxes diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index b5c17620f..f64640789 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: > - Thank you for taking the time to fill out a feature request! + Thank you for taking the time to fill out a feature request! If you have a question about how to use Blockly in your application, please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue. - type: checkboxes diff --git a/core/block_animations.ts b/core/block_animations.ts index d9d0c5bef..9378498a9 100644 --- a/core/block_animations.ts +++ b/core/block_animations.ts @@ -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); } } diff --git a/core/browser_events.ts b/core/browser_events.ts index c87655bda..2d389415a 100644 --- a/core/browser_events.ts +++ b/core/browser_events.ts @@ -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; diff --git a/core/bubble.ts b/core/bubble.ts index 5036b4b25..c8fa2b0f3 100644 --- a/core/bubble.ts +++ b/core/bubble.ts @@ -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; diff --git a/core/comment.ts b/core/comment.ts index 3dc4e31ef..b60d7547e 100644 --- a/core/comment.ts +++ b/core/comment.ts @@ -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; diff --git a/core/component_manager.ts b/core/component_manager.ts index 426860a74..6b014baa4 100644 --- a/core/component_manager.ts +++ b/core/component_manager.ts @@ -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(id: string, capability: string|Capability): 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( capability: string|Capability, sorted: boolean): T[] { - capability = String(capability).toLowerCase(); + capability = `${capability}`.toLowerCase(); const componentIds = this.capabilityToComponentIds.get(capability); if (!componentIds) { return []; diff --git a/core/contextmenu_items.ts b/core/contextmenu_items.ts index 706ba4fbe..667e0896f 100644 --- a/core/contextmenu_items.ts +++ b/core/contextmenu_items.ts @@ -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()) { diff --git a/core/field.ts b/core/field.ts index 209dc59e3..0c163e038 100644 --- a/core/field.ts +++ b/core/field.ts @@ -203,16 +203,16 @@ export abstract class Field 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|null, - opt_config?: FieldConfig) { + value: T|Sentinel, validator?: FieldValidator|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 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 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 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 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 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)); } /** diff --git a/core/field_angle.ts b/core/field_angle.ts index 971126cf9..c2f9318e7 100644 --- a/core/field_angle.ts +++ b/core/field_angle.ts @@ -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 { - /** - * 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 { 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 { 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 { */ 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 { 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 { '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 { // 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 { */ 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 { } // 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 { * * @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 { ' 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 { throw new UnattachedFieldError(); } - let multiplier; - if (e.keyCode === KeyCodes.LEFT) { - // decrement (increment in RTL) - multiplier = block.RTL ? 1 : -1; - } else if (e.keyCode === KeyCodes.RIGHT) { - // increment (decrement in RTL) - multiplier = block.RTL ? -1 : 1; - } else if (e.keyCode === KeyCodes.DOWN) { - // decrement - multiplier = -1; - } else if (e.keyCode === KeyCodes.UP) { - // increment - multiplier = 1; + let multiplier = 0; + switch (e.key) { + case 'ArrowLeft': + // decrement (increment in RTL) + multiplier = block.RTL ? 1 : -1; + break; + case 'ArrowRight': + // increment (decrement in RTL) + multiplier = block.RTL ? -1 : 1; + break; + case 'ArrowDown': + // decrement + multiplier = -1; + 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 { /** * 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 { * @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 { } } -/** 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: diff --git a/core/field_checkbox.ts b/core/field_checkbox.ts index f884bc47c..31cc50134 100644 --- a/core/field_checkbox.ts +++ b/core/field_checkbox.ts @@ -49,22 +49,22 @@ export class FieldCheckbox extends Field { 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 { */ 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 { /** * 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; diff --git a/core/field_colour.ts b/core/field_colour.ts index cc703a8ed..63fd1a752 100644 --- a/core/field_colour.ts +++ b/core/field_colour.ts @@ -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 { 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 { 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 { */ 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 { } } + /** + * 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 { /** * 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 { * * @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 { * @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 { * * @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 { * * @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) { - // Select the highlighted colour. - const highlighted = this.getHighlighted_(); - if (highlighted) { - const colour = highlighted && highlighted.getAttribute('data-colour'); - if (colour !== null) { - this.setValue(colour); + 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. + highlighted = this.getHighlighted(); + if (highlighted) { + const colour = highlighted.getAttribute('data-colour'); + if (colour !== null) { + this.setValue(colour); + } } - } - dropDownDiv.hideWithoutAnimation(); - handled = true; + dropDownDiv.hideWithoutAnimation(); + break; + default: + handled = false; } if (handled) { e.stopPropagation(); @@ -336,22 +336,22 @@ export class FieldColour extends Field { /** * 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 { } // 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 { * * @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 { * * @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 { 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 { /** 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. */ diff --git a/core/field_dropdown.ts b/core/field_dropdown.ts index 04d9d7580..90cd9117a 100644 --- a/core/field_dropdown.ts +++ b/core/field_dropdown.ts @@ -97,11 +97,11 @@ export class FieldDropdown extends Field { * 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 { */ 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 { */ 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 { /** * 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 { /** * 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 { /** * 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 { 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 { 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); } diff --git a/core/field_image.ts b/core/field_image.ts index 173a3dc63..60ddac433 100644 --- a/core/field_image.ts +++ b/core/field_image.ts @@ -64,19 +64,19 @@ export class FieldImage extends Field { * 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 { */ 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 { /** * 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 { this.value_ = newValue; if (this.imageElement_) { this.imageElement_.setAttributeNS( - dom.XLINK_NS, 'xlink:href', String(this.value_)); + dom.XLINK_NS, 'xlink:href', this.value_); } } diff --git a/core/field_input.ts b/core/field_input.ts index 3d3f4302a..756103d16 100644 --- a/core/field_input.ts +++ b/core/field_input.ts @@ -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 extends Field { 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|null, - opt_config?: FieldInputConfig) { + value?: string|Sentinel, validator?: FieldInputValidator|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 extends Field { * 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 extends Field { 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 extends Field { * @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 extends Field { * @returns The text to show on the HTML input. */ protected getEditorText_(value: AnyDuringMigration): string { - return String(value); + return `${value}`; } /** diff --git a/core/field_label.ts b/core/field_label.ts index 1c6daea50..4cb293ef6 100644 --- a/core/field_label.ts +++ b/core/field_label.ts @@ -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 { - /** 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 { 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 { /** * 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}`; } /** diff --git a/core/field_label_serializable.ts b/core/field_label_serializable.ts index 79c4c8e06..55ef8a1fb 100644 --- a/core/field_label_serializable.ts +++ b/core/field_label_serializable.ts @@ -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); } /** diff --git a/core/field_multilineinput.ts b/core/field_multilineinput.ts index 467ace17c..40f82de23 100644 --- a/core/field_multilineinput.ts +++ b/core/field_multilineinput.ts @@ -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. */ diff --git a/core/field_number.ts b/core/field_number.ts index 5be44816d..262373050 100644 --- a/core/field_number.ts +++ b/core/field_number.ts @@ -37,51 +37,44 @@ export class FieldNumber extends FieldInput { */ 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 { * 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'); diff --git a/core/field_textinput.ts b/core/field_textinput.ts index 5b6a3878e..f519f98bb 100644 --- a/core/field_textinput.ts +++ b/core/field_textinput.ts @@ -25,37 +25,37 @@ import type {Sentinel} from './utils/sentinel.js'; */ export class FieldTextInput extends FieldInput { /** - * @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}`; } /** diff --git a/core/field_variable.ts b/core/field_variable.ts index 091a01a4a..61159463e 100644 --- a/core/field_variable.ts +++ b/core/field_variable.ts @@ -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'); diff --git a/core/flyout_base.ts b/core/flyout_base.ts index f5a59e425..154e32f07 100644 --- a/core/flyout_base.ts +++ b/core/flyout_base.ts @@ -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,21 +358,17 @@ export abstract class Flyout extends DeleteArea implements IFlyout { this.hide(); - Array.prototype.push.apply( - this.eventWrappers_, - browserEvents.conditionalBind( - (this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_)); + this.boundEvents.push(browserEvents.conditionalBind( + (this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_)); if (!this.autoClose) { this.filterWrapper_ = this.filterForCapacity_.bind(this); this.targetWorkspace.addChangeListener(this.filterWrapper_); } // Dragging the flyout up and down. - Array.prototype.push.apply( - this.eventWrappers_, - browserEvents.conditionalBind( - (this.svgBackground_ as SVGPathElement), 'pointerdown', this, - this.onMouseDown_)); + this.boundEvents.push(browserEvents.conditionalBind( + (this.svgBackground_ as SVGPathElement), 'pointerdown', this, + this.onMouseDown_)); // A flyout connected to a workspace doesn't have its own current gesture. this.workspace_.getGesture = @@ -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 { // // 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)); } /** diff --git a/core/flyout_button.ts b/core/flyout_button.ts index c3ad6cdaa..8a91d1c63 100644 --- a/core/flyout_button.ts +++ b/core/flyout_button.ts @@ -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_(); diff --git a/core/generator.ts b/core/generator.ts index d51081a6d..e3261dfa5 100644 --- a/core/generator.ts +++ b/core/generator.ts @@ -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]; diff --git a/core/gesture.ts b/core/gesture.ts index c3d00ea3f..279d3527c 100644 --- a/core/gesture.ts +++ b/core/gesture.ts @@ -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(); diff --git a/core/grid.ts b/core/grid.ts index 8b88fa169..079ae5d19 100644 --- a/core/grid.ts +++ b/core/grid.ts @@ -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}`); } /** diff --git a/core/keyboard_nav/ast_node.ts b/core/keyboard_nav/ast_node.ts index 1319c4b57..624dc6a45 100644 --- a/core/keyboard_nav/ast_node.ts +++ b/core/keyboard_nav/ast_node.ts @@ -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]; diff --git a/core/menu.ts b/core/menu.ts index 0b800d6ff..0ad63cf26 100644 --- a/core/menu.ts +++ b/core/menu.ts @@ -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; diff --git a/core/registry.ts b/core/registry.ts index c3219934b..f99ded7bf 100644 --- a/core/registry.ts +++ b/core/registry.ts @@ -115,12 +115,12 @@ export function register( 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(type: string|Type, 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(type: string|Type, name: string) { function getItem( type: string|Type, 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( * otherwise. */ export function hasItem(type: string|Type, 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( export function getAllItems( type: string|Type, 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( } 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( export function getClassFromOptions( type: Type, 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') { diff --git a/core/renderers/zelos/marker_svg.ts b/core/renderers/zelos/marker_svg.ts index 49e35a873..7e8ce2522 100644 --- a/core/renderers/zelos/marker_svg.ts +++ b/core/renderers/zelos/marker_svg.ts @@ -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_; } diff --git a/core/scrollbar.ts b/core/scrollbar.ts index 707b6d2b3..f01bccf5f 100644 --- a/core/scrollbar.ts +++ b/core/scrollbar.ts @@ -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'); } } diff --git a/core/shortcut_registry.ts b/core/shortcut_registry.ts index 191913941..8f03c5ca8 100644 --- a/core/shortcut_registry.ts +++ b/core/shortcut_registry.ts @@ -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; } diff --git a/core/theme.ts b/core/theme.ts index c79bc8a33..c9fb2981c 100644 --- a/core/theme.ts +++ b/core/theme.ts @@ -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}`; } /** diff --git a/core/toolbox/category.ts b/core/toolbox/category.ts index a3a685ec8..16f814f26 100644 --- a/core/toolbox/category.ts +++ b/core/toolbox/category.ts @@ -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; diff --git a/core/toolbox/toolbox.ts b/core/toolbox/toolbox.ts index 91cf9d6d9..469515639 100644 --- a/core/toolbox/toolbox.ts +++ b/core/toolbox/toolbox.ts @@ -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(); diff --git a/core/trashcan.ts b/core/trashcan.ts index 820a0caed..5d4d87f58 100644 --- a/core/trashcan.ts +++ b/core/trashcan.ts @@ -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) { diff --git a/core/utils/colour.ts b/core/utils/colour.ts index 6f234d308..d98c1fed7 100644 --- a/core/utils/colour.ts +++ b/core/utils/colour.ts @@ -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' diff --git a/core/utils/dom.ts b/core/utils/dom.ts index 383934f47..b6ca84eda 100644 --- a/core/utils/dom.ts +++ b/core/utils/dom.ts @@ -63,7 +63,7 @@ let canvasContext: CanvasRenderingContext2D|null = null; export function createSvgElement( name: string|Svg, 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]}`); } diff --git a/core/utils/parsing.ts b/core/utils/parsing.ts index 79c3784fc..4badd77ff 100644 --- a/core/utils/parsing.ts +++ b/core/utils/parsing.ts @@ -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); } diff --git a/core/workspace_comment.ts b/core/workspace_comment.ts index 4a4de35ff..6745a1bb2 100644 --- a/core/workspace_comment.ts +++ b/core/workspace_comment.ts @@ -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; } diff --git a/core/workspace_comment_svg.ts b/core/workspace_comment_svg.ts index 2d45de046..b440b0679 100644 --- a/core/workspace_comment_svg.ts +++ b/core/workspace_comment_svg.ts @@ -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; } } diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 5acdf2235..d186772bc 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -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}`); } } diff --git a/core/xml.ts b/core/xml.ts index 534b32536..473ffdb51 100644 --- a/core/xml.ts +++ b/core/xml.ts @@ -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'); diff --git a/core/zoom_controls.ts b/core/zoom_controls.ts index 128ad7fa6..5ae63f8a5 100644 --- a/core/zoom_controls.ts +++ b/core/zoom_controls.ts @@ -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 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))); } /** diff --git a/demos/blockfactory/analytics.js b/demos/blockfactory/analytics.js index 6febb8858..5611c09a9 100644 --- a/demos/blockfactory/analytics.js +++ b/demos/blockfactory/analytics.js @@ -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 + '")'); }; /** diff --git a/generators/javascript/lists.js b/generators/javascript/lists.js index a614f2a0c..c2845d439 100644 --- a/generators/javascript/lists.js +++ b/generators/javascript/lists.js @@ -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; }; diff --git a/scripts/gulpfiles/test_tasks.js b/scripts/gulpfiles/test_tasks.js index 0354977bf..921be874d 100644 --- a/scripts/gulpfiles/test_tasks.js +++ b/scripts/gulpfiles/test_tasks.js @@ -59,7 +59,7 @@ function runTestTask(id, task) { successCount++; if (process.env.CI) console.log('::endgroup::'); console.log(`${BOLD_GREEN}SUCCESS:${ANSI_RESET} ${id}`); - results[id] = {success: true}; + results[id] = {success: true}; resolve(result); }) .catch((err) => { @@ -191,8 +191,8 @@ function compareSize(file, expected) { const message = `Failed: Previous size of ${name} is undefined.`; console.log(`${BOLD_RED}${message}${ANSI_RESET}`); return 1; - } - + } + if (size > compare) { const message = `Failed: ` + `Size of ${name} has grown more than 10%. ${size} vs ${expected}`; diff --git a/tests/generators/golden/generated.js b/tests/generators/golden/generated.js index 2778376c2..f8ac5651a 100644 --- a/tests/generators/golden/generated.js +++ b/tests/generators/golden/generated.js @@ -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; }; diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index b9eadedfa..88ffc5100 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -299,7 +299,7 @@ suite('Blocks', function() { setup(function() { this.blocks = createTestBlocks(this.workspace, true); }); - + test('Don\'t heal', function() { this.blocks.B.dispose(false); assertDisposedNoheal(this.blocks); @@ -313,11 +313,11 @@ suite('Blocks', function() { test('Heal with bad checks', function() { const blocks = this.blocks; - + // A and C can't connect, but both can connect to B. blocks.A.inputList[0].connection.setCheck('type1'); blocks.C.outputConnection.setCheck('type2'); - + // Each block has only one input, but the types don't work. blocks.B.dispose(true); assertDisposedHealFailed(blocks); @@ -362,7 +362,7 @@ suite('Blocks', function() { setup(function() { this.blocks = createTestBlocks(this.workspace, false); }); - + test('Don\'t heal', function() { this.blocks.B.dispose(); assertDisposedNoheal(this.blocks); @@ -378,10 +378,10 @@ suite('Blocks', function() { // A and C can't connect, but both can connect to B. blocks.A.nextConnection.setCheck('type1'); blocks.C.previousConnection.setCheck('type2'); - + // The types don't work. blocks.B.dispose(true); - + assertDisposedHealFailed(blocks); }); diff --git a/tests/mocha/blocks/procedures_test.js b/tests/mocha/blocks/procedures_test.js index 0f17772cd..e792e0e81 100644 --- a/tests/mocha/blocks/procedures_test.js +++ b/tests/mocha/blocks/procedures_test.js @@ -49,9 +49,9 @@ suite('Procedures', function() { createProcDefBlock(this.workspace, undefined, undefined, 'procB'); const callBlockB = createProcCallBlock(this.workspace, undefined, 'procB'); - + defBlockB.setFieldValue('procA', 'NAME'); - + chai.assert.notEqual( defBlockB.getFieldValue('NAME'), 'procA', @@ -82,7 +82,7 @@ suite('Procedures', function() { }, mutatorWorkspace); this.clock.runAll(); - + const newFlyoutParamName = mutatorWorkspace.getFlyout().getWorkspace().getTopBlocks(true)[0] .getFieldValue('NAME'); @@ -167,7 +167,7 @@ suite('Procedures', function() { this.workspace.undo(); this.workspace.undo(/* redo= */ true); - + chai.assert.isNotNull( defBlock.getField('PARAMS'), 'Expected the params field to exist'); @@ -252,10 +252,10 @@ suite('Procedures', function() { this.clock.runAll(); paramBlock.checkAndDelete(); this.clock.runAll(); - + this.workspace.undo(); this.workspace.undo(/* redo= */ true); - + chai.assert.isFalse( defBlock.getFieldValue('PARAMS').includes('param1'), 'Expected the params field to not contain the name of the new param'); @@ -300,10 +300,10 @@ suite('Procedures', function() { .connect(paramBlock1.previousConnection); paramBlock1.nextConnection.connect(paramBlock2.previousConnection); this.clock.runAll(); - + paramBlock1.setFieldValue('new name', 'NAME'); this.clock.runAll(); - + chai.assert.isNotNull( defBlock.getField('PARAMS'), 'Expected the params field to exist'); @@ -349,10 +349,10 @@ suite('Procedures', function() { paramBlock.setFieldValue('param1', 'NAME'); containerBlock.getInput('STACK').connection.connect(paramBlock.previousConnection); this.clock.runAll(); - + paramBlock.setFieldValue('param2', 'NAME'); this.clock.runAll(); - + chai.assert.isNotNull( this.workspace.getVariable('param1', ''), 'Expected the old variable to continue to exist'); @@ -372,10 +372,10 @@ suite('Procedures', function() { .connect(paramBlock.previousConnection); this.clock.runAll(); defBlock.mutator.setVisible(false); - + const variable = this.workspace.getVariable('param1', ''); this.workspace.renameVariableById(variable.getId(), 'new name'); - + chai.assert.isNotNull( defBlock.getField('PARAMS'), 'Expected the params field to exist'); @@ -397,10 +397,10 @@ suite('Procedures', function() { containerBlock.getInput('STACK').connection .connect(paramBlock.previousConnection); this.clock.runAll(); - + const variable = this.workspace.getVariable('param1', ''); this.workspace.renameVariableById(variable.getId(), 'new name'); - + chai.assert.equal( paramBlock.getFieldValue('NAME'), 'new name', @@ -422,7 +422,7 @@ suite('Procedures', function() { .connect(paramBlock.previousConnection); this.clock.runAll(); defBlock.mutator.setVisible(false); - + const variable = this.workspace.getVariable('param1', ''); this.workspace.renameVariableById(variable.getId(), 'new name'); @@ -449,10 +449,10 @@ suite('Procedures', function() { .connect(paramBlock.previousConnection); this.clock.runAll(); defBlock.mutator.setVisible(false); - + const variable = this.workspace.getVariable('param1', ''); this.workspace.renameVariableById(variable.getId(), 'preCreatedVar'); - + chai.assert.isNotNull( defBlock.getField('PARAMS'), 'Expected the params field to exist'); @@ -474,10 +474,10 @@ suite('Procedures', function() { containerBlock.getInput('STACK').connection .connect(paramBlock.previousConnection); this.clock.runAll(); - + const variable = this.workspace.getVariable('param1', ''); this.workspace.renameVariableById(variable.getId(), 'preCreatedVar'); - + chai.assert.equal( paramBlock.getFieldValue('NAME'), 'preCreatedVar', @@ -499,7 +499,7 @@ suite('Procedures', function() { .connect(paramBlock.previousConnection); this.clock.runAll(); defBlock.mutator.setVisible(false); - + const variable = this.workspace.getVariable('param1', ''); this.workspace.renameVariableById(variable.getId(), 'preCreatedVar'); @@ -571,7 +571,7 @@ suite('Procedures', function() { this.workspace.undo(); this.workspace.undo(/* redo= */ true); - + chai.assert.isTrue( defBlock.getFieldValue('PARAMS').includes('new'), 'Expected the params field to contain the new name of the param'); @@ -671,14 +671,14 @@ suite('Procedures', function() { .connect(block1.outputConnection); callBlock.getInput('ARG1').connection .connect(block2.outputConnection); - + // Reorder the parameters. paramBlock2.previousConnection.disconnect(); paramBlock1.previousConnection.disconnect(); containerBlock.getInput('STACK').connection.connect(paramBlock2.previousConnection); paramBlock2.nextConnection.connect(paramBlock1.previousConnection); this.clock.runAll(); - + chai.assert.equal( callBlock.getInputTargetBlock('ARG0'), block2, diff --git a/tests/mocha/field_angle_test.js b/tests/mocha/field_angle_test.js index 30af8340d..8cdff6de8 100644 --- a/tests/mocha/field_angle_test.js +++ b/tests/mocha/field_angle_test.js @@ -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); }); }); }); diff --git a/tests/mocha/field_colour_test.js b/tests/mocha/field_colour_test.js index 65d4a9d2c..f1a37d9e3 100644 --- a/tests/mocha/field_colour_test.js +++ b/tests/mocha/field_colour_test.js @@ -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; diff --git a/tests/mocha/toolbox_test.js b/tests/mocha/toolbox_test.js index 067ff4275..bc9bd9706 100644 --- a/tests/mocha/toolbox_test.js +++ b/tests/mocha/toolbox_test.js @@ -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); diff --git a/tests/mocha/variable_map_test.js b/tests/mocha/variable_map_test.js index 2efb3afbd..ceef57088 100644 --- a/tests/mocha/variable_map_test.js +++ b/tests/mocha/variable_map_test.js @@ -301,7 +301,7 @@ suite('Variable Map', function() { const variable = this.variableMap.createVariable('test name', 'test type', 'test id'); this.variableMap.deleteVariable(variable); - + assertEventFired( this.eventSpy, Blockly.Events.VarDelete, @@ -320,7 +320,7 @@ suite('Variable Map', function() { new Blockly.VariableModel( this.workspace, 'test name', 'test type', 'test id'); this.variableMap.deleteVariable(variable); - + assertEventNotFired( this.eventSpy, Blockly.Events.VarDelete, @@ -335,7 +335,7 @@ suite('Variable Map', function() { function() { this.variableMap.createVariable('test name', 'test type', 'test id'); this.variableMap.deleteVariableById('test id'); - + assertEventFired( this.eventSpy, Blockly.Events.VarDelete, @@ -351,7 +351,7 @@ suite('Variable Map', function() { 'delete events are not fired when a variable does not exist', function() { this.variableMap.deleteVariableById('test id'); - + assertEventNotFired( this.eventSpy, Blockly.Events.VarDelete, @@ -379,7 +379,7 @@ suite('Variable Map', function() { }, this.workspace.id); }); - + test( 'rename events are not fired if the variable name already matches', function() { @@ -387,14 +387,14 @@ suite('Variable Map', function() { this.variableMap.createVariable( 'test name', 'test type', 'test id'); this.variableMap.renameVariable(variable, 'test name'); - + assertEventNotFired( this.eventSpy, Blockly.Events.VarRename, {}, this.workspace.id); }); - + test( 'rename events are not fired if the variable does not exist', function() { @@ -402,7 +402,7 @@ suite('Variable Map', function() { new Blockly.VariableModel( 'test name', 'test type', 'test id'); this.variableMap.renameVariable(variable, 'test name'); - + assertEventNotFired( this.eventSpy, Blockly.Events.VarRename, @@ -426,21 +426,21 @@ suite('Variable Map', function() { }, this.workspace.id); }); - + test( 'rename events are not fired if the variable name already matches', function() { this.variableMap.createVariable( 'test name', 'test type', 'test id'); this.variableMap.renameVariableById('test id', 'test name'); - + assertEventNotFired( this.eventSpy, Blockly.Events.VarRename, {}, this.workspace.id); }); - + test( 'renaming throws if the variable does not exist', function() { diff --git a/tests/mocha/webdriver.js b/tests/mocha/webdriver.js index e46921234..d64024a9f 100644 --- a/tests/mocha/webdriver.js +++ b/tests/mocha/webdriver.js @@ -30,7 +30,7 @@ async function runMochaTestsInBrowser() { ], logLevel: 'warn', }; - + // Run in headless mode on Github Actions. if (process.env.CI) { options.capabilities['goog:chromeOptions'].args.push( diff --git a/tests/typescript/src/field/different_user_input.ts b/tests/typescript/src/field/different_user_input.ts index f77094ea8..b4fa93731 100644 --- a/tests/typescript/src/field/different_user_input.ts +++ b/tests/typescript/src/field/different_user_input.ts @@ -54,7 +54,7 @@ class FieldMitosis extends Field { doMitosis(): void { const cellGroup = this.getValue(); if (!cellGroup) return; - + const cells = cellGroup.cells.flatMap((cell) => { const leftCell: Cell = {cellId: `${cell.cellId}-left`}; const rightCell: Cell = {cellId: `${cell.cellId}-right`};