mirror of
https://github.com/google/blockly.git
synced 2025-12-16 06:10:12 +01:00
chore: Reduce delta on ports to blockly-samples (#6886)
* Reduce usage of obsolete .keyCode property.
* Rename private properties/methods which violate eslint rules.
* Use arrays of bound events rather than individual properties.
* Improve typing info.
* Also fix O(n^2) recursive performance issue in theme's getComponentStyle function.
* And replace String(...) with '${...}' (smaller, faster).
* .toString() is considered harmful.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -118,7 +118,7 @@ export class ComponentManager {
|
||||
'Plugin "' + id + 'already has capability "' + capability + '"');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
this.componentData.get(id)?.capabilities.push(capability);
|
||||
this.capabilityToComponentIds.get(capability)?.push(id);
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export class ComponentManager {
|
||||
'" to remove');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
arrayUtils.removeElem(this.componentData.get(id)!.capabilities, capability);
|
||||
arrayUtils.removeElem(this.capabilityToComponentIds.get(capability)!, id);
|
||||
}
|
||||
@@ -154,7 +154,7 @@ export class ComponentManager {
|
||||
* @returns Whether the component has the capability.
|
||||
*/
|
||||
hasCapability<T>(id: string, capability: string|Capability<T>): boolean {
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
return this.componentData.has(id) &&
|
||||
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ export class ComponentManager {
|
||||
*/
|
||||
getComponents<T extends IComponent>(
|
||||
capability: string|Capability<T>, sorted: boolean): T[] {
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
const componentIds = this.capabilityToComponentIds.get(capability);
|
||||
if (!componentIds) {
|
||||
return [];
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -203,16 +203,16 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
|
||||
* subclasses that want to handle configuration and setting the field value
|
||||
* after their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a value & returns a validated value, or null to
|
||||
* abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* Refer to the individual field's documentation for a list of properties
|
||||
* this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
value: T|Sentinel, opt_validator?: FieldValidator<T>|null,
|
||||
opt_config?: FieldConfig) {
|
||||
value: T|Sentinel, validator?: FieldValidator<T>|null,
|
||||
config?: FieldConfig) {
|
||||
/**
|
||||
* A generic value possessed by the field.
|
||||
* Should generally be non-null, only null when the field is created.
|
||||
@@ -225,12 +225,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
this.size_ = new Size(0, 0);
|
||||
|
||||
if (Field.isSentinel(value)) return;
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,14 +715,14 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* Calls showEditor_ when the field is clicked if the field is clickable.
|
||||
* Do not override.
|
||||
*
|
||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||
* @param e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
* @sealed
|
||||
* @internal
|
||||
*/
|
||||
showEditor(opt_e?: Event) {
|
||||
showEditor(e?: Event) {
|
||||
if (this.isClickable()) {
|
||||
this.showEditor_(opt_e);
|
||||
this.showEditor_(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,11 +739,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
/**
|
||||
* Updates the size of the field based on the text.
|
||||
*
|
||||
* @param opt_margin margin to use when positioning the text element.
|
||||
* @param margin margin to use when positioning the text element.
|
||||
*/
|
||||
protected updateSize_(opt_margin?: number) {
|
||||
protected updateSize_(margin?: number) {
|
||||
const constants = this.getConstants();
|
||||
const xOffset = opt_margin !== undefined ? opt_margin :
|
||||
const xOffset = margin !== undefined ? margin :
|
||||
this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING :
|
||||
0;
|
||||
let totalWidth = xOffset * 2;
|
||||
@@ -783,17 +783,17 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
|
||||
this.textElement_.setAttribute(
|
||||
'x',
|
||||
`${
|
||||
String(
|
||||
this.getSourceBlock()?.RTL ?
|
||||
this.size_.width - contentWidth - xOffset :
|
||||
xOffset}`);
|
||||
xOffset));
|
||||
this.textElement_.setAttribute(
|
||||
'y',
|
||||
`${
|
||||
String(
|
||||
constants!.FIELD_TEXT_BASELINE_CENTER ?
|
||||
halfHeight :
|
||||
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
|
||||
constants!.FIELD_TEXT_BASELINE}`);
|
||||
constants!.FIELD_TEXT_BASELINE));
|
||||
}
|
||||
|
||||
/** Position a field's border rect after a size change. */
|
||||
@@ -801,12 +801,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
if (!this.borderRect_) {
|
||||
return;
|
||||
}
|
||||
this.borderRect_.setAttribute('width', `${this.size_.width}`);
|
||||
this.borderRect_.setAttribute('height', `${this.size_.height}`);
|
||||
this.borderRect_.setAttribute('width', String(this.size_.width));
|
||||
this.borderRect_.setAttribute('height', String(this.size_.height));
|
||||
this.borderRect_.setAttribute(
|
||||
'rx', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||
'rx', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
|
||||
this.borderRect_.setAttribute(
|
||||
'ry', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||
'ry', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,6 @@ import {Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import * as math from './utils/math.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
@@ -31,15 +30,15 @@ import * as WidgetDiv from './widgetdiv.js';
|
||||
* Class for an editable angle field.
|
||||
*/
|
||||
export class FieldAngle extends FieldInput<number> {
|
||||
/**
|
||||
* The default amount to round angles to when using a mouse or keyboard nav
|
||||
* input. Must be a positive integer to support keyboard navigation.
|
||||
*/
|
||||
static readonly ROUND = 15;
|
||||
|
||||
/** Half the width of protractor image. */
|
||||
static readonly HALF = 100 / 2;
|
||||
|
||||
/**
|
||||
* Radius of protractor circle. Slightly smaller than protractor size since
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
*/
|
||||
static readonly RADIUS: number = FieldAngle.HALF - 1;
|
||||
|
||||
/**
|
||||
* Default property describing which direction makes an angle field's value
|
||||
* increase. Angle increases clockwise (true) or counterclockwise (false).
|
||||
@@ -60,84 +59,73 @@ export class FieldAngle extends FieldInput<number> {
|
||||
static readonly WRAP = 360;
|
||||
|
||||
/**
|
||||
* Radius of protractor circle. Slightly smaller than protractor size since
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
* The default amount to round angles to when using a mouse or keyboard nav
|
||||
* input. Must be a positive integer to support keyboard navigation.
|
||||
*/
|
||||
static readonly RADIUS: number = FieldAngle.HALF - 1;
|
||||
static readonly ROUND = 15;
|
||||
|
||||
/**
|
||||
* Whether the angle should increase as the angle picker is moved clockwise
|
||||
* (true) or counterclockwise (false).
|
||||
*/
|
||||
private clockwise_ = FieldAngle.CLOCKWISE;
|
||||
private clockwise = FieldAngle.CLOCKWISE;
|
||||
|
||||
/**
|
||||
* The offset of zero degrees (and all other angles).
|
||||
*/
|
||||
private offset_ = FieldAngle.OFFSET;
|
||||
private offset = FieldAngle.OFFSET;
|
||||
|
||||
/**
|
||||
* The maximum angle to allow before wrapping.
|
||||
*/
|
||||
private wrap_ = FieldAngle.WRAP;
|
||||
private wrap = FieldAngle.WRAP;
|
||||
|
||||
/**
|
||||
* The amount to round angles to when using a mouse or keyboard nav input.
|
||||
*/
|
||||
private round_ = FieldAngle.ROUND;
|
||||
private round = FieldAngle.ROUND;
|
||||
|
||||
/** The angle picker's SVG element. */
|
||||
private editor_: SVGSVGElement|null = null;
|
||||
/**
|
||||
* Array holding info needed to unbind events.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
*/
|
||||
private boundEvents: browserEvents.Data[] = [];
|
||||
|
||||
/** The angle picker's gauge path depending on the value. */
|
||||
gauge_: SVGPathElement|null = null;
|
||||
/** Dynamic red line pointing at the value's angle. */
|
||||
private line: SVGLineElement|null = null;
|
||||
|
||||
/** The angle picker's line drawn representing the value's angle. */
|
||||
line_: SVGLineElement|null = null;
|
||||
/** Dynamic pink area extending from 0 to the value's angle. */
|
||||
private gauge: SVGPathElement|null = null;
|
||||
|
||||
/** The degree symbol for this field. */
|
||||
protected symbol_: SVGTSpanElement|null = null;
|
||||
|
||||
/** Wrapper click event data. */
|
||||
private clickWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Surface click event data. */
|
||||
private clickSurfaceWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Surface mouse move event data. */
|
||||
private moveSurfaceWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the serializer, non-serializable fields
|
||||
* are not. Editable fields should also be serializable.
|
||||
*/
|
||||
override SERIALIZABLE = true;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a number.
|
||||
* @param value The initial value of the field. Should cast to a number.
|
||||
* Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup
|
||||
* (only used by subclasses that want to handle configuration and setting
|
||||
* the field value after their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a number & returns a validated number, or null
|
||||
* to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|number|Sentinel, opt_validator?: FieldAngleValidator,
|
||||
opt_config?: FieldAngleConfig) {
|
||||
value?: string|number|Sentinel, validator?: FieldAngleValidator,
|
||||
config?: FieldAngleConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (Field.isSentinel(opt_value)) return;
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (Field.isSentinel(value)) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,22 +139,22 @@ export class FieldAngle extends FieldInput<number> {
|
||||
|
||||
switch (config.mode) {
|
||||
case Mode.COMPASS:
|
||||
this.clockwise_ = true;
|
||||
this.offset_ = 90;
|
||||
this.clockwise = true;
|
||||
this.offset = 90;
|
||||
break;
|
||||
case Mode.PROTRACTOR:
|
||||
// This is the default mode, so we could do nothing. But just to
|
||||
// future-proof, we'll set it anyway.
|
||||
this.clockwise_ = false;
|
||||
this.offset_ = 0;
|
||||
this.clockwise = false;
|
||||
this.offset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Allow individual settings to override the mode setting.
|
||||
if (config.clockwise) this.clockwise_ = config.clockwise;
|
||||
if (config.offset) this.offset_ = config.offset;
|
||||
if (config.wrap) this.wrap_ = config.wrap;
|
||||
if (config.round) this.round_ = config.round;
|
||||
if (config.clockwise) this.clockwise = config.clockwise;
|
||||
if (config.offset) this.offset = config.offset;
|
||||
if (config.wrap) this.wrap = config.wrap;
|
||||
if (config.round) this.round = config.round;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,32 +164,32 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*/
|
||||
override initView() {
|
||||
super.initView();
|
||||
// Add the degree symbol to the left of the number, even in RTL (issue
|
||||
// #2380)
|
||||
// Add the degree symbol to the left of the number,
|
||||
// even in RTL (issue #2380).
|
||||
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {});
|
||||
this.symbol_.appendChild(document.createTextNode('°'));
|
||||
this.getTextElement().appendChild(this.symbol_);
|
||||
}
|
||||
|
||||
/** Updates the graph when the field rerenders. */
|
||||
/** Updates the angle when the field rerenders. */
|
||||
protected override render_() {
|
||||
super.render_();
|
||||
this.updateGraph_();
|
||||
this.updateGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and show the angle field's editor.
|
||||
*
|
||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
* @param e Optional mouse event that triggered the field to open,
|
||||
* or undefined if triggered programmatically.
|
||||
*/
|
||||
protected override showEditor_(opt_e?: Event) {
|
||||
protected override showEditor_(e?: Event) {
|
||||
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
||||
const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
|
||||
super.showEditor_(opt_e, noFocus);
|
||||
super.showEditor_(e, noFocus);
|
||||
|
||||
this.dropdownCreate_();
|
||||
dropDownDiv.getContentDiv().appendChild(this.editor_!);
|
||||
const editor = this.dropdownCreate();
|
||||
dropDownDiv.getContentDiv().appendChild(editor);
|
||||
|
||||
if (this.sourceBlock_ instanceof BlockSvg) {
|
||||
dropDownDiv.setColour(
|
||||
@@ -209,13 +197,17 @@ export class FieldAngle extends FieldInput<number> {
|
||||
this.sourceBlock_.style.colourTertiary);
|
||||
}
|
||||
|
||||
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
|
||||
dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
|
||||
|
||||
this.updateGraph_();
|
||||
this.updateGraph();
|
||||
}
|
||||
|
||||
/** Create the angle dropdown editor. */
|
||||
private dropdownCreate_() {
|
||||
/**
|
||||
* Creates the angle dropdown editor.
|
||||
*
|
||||
* @returns The newly created slider.
|
||||
*/
|
||||
private dropdownCreate(): SVGSVGElement {
|
||||
const svg = dom.createSvgElement(Svg.SVG, {
|
||||
'xmlns': dom.SVG_NS,
|
||||
'xmlns:html': dom.HTML_NS,
|
||||
@@ -233,9 +225,9 @@ export class FieldAngle extends FieldInput<number> {
|
||||
'class': 'blocklyAngleCircle',
|
||||
},
|
||||
svg);
|
||||
this.gauge_ =
|
||||
this.gauge =
|
||||
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
|
||||
this.line_ = dom.createSvgElement(
|
||||
this.line = dom.createSvgElement(
|
||||
Svg.LINE, {
|
||||
'x1': FieldAngle.HALF,
|
||||
'y1': FieldAngle.HALF,
|
||||
@@ -261,38 +253,30 @@ export class FieldAngle extends FieldInput<number> {
|
||||
// The angle picker is different from other fields in that it updates on
|
||||
// mousemove even if it's not in the middle of a drag. In future we may
|
||||
// change this behaviour.
|
||||
this.clickWrapper_ =
|
||||
browserEvents.conditionalBind(svg, 'click', this, this.hide_);
|
||||
this.boundEvents.push(
|
||||
browserEvents.conditionalBind(svg, 'click', this, this.hide));
|
||||
// On touch devices, the picker's value is only updated with a drag. Add
|
||||
// a click handler on the drag surface to update the value if the surface
|
||||
// is clicked.
|
||||
this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
|
||||
circle, 'pointerdown', this, this.onMouseMove_, true);
|
||||
this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
|
||||
circle, 'pointermove', this, this.onMouseMove_, true);
|
||||
this.editor_ = svg;
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
circle, 'pointerdown', this, this.onMouseMove_, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
circle, 'pointermove', this, this.onMouseMove_, true));
|
||||
return svg;
|
||||
}
|
||||
|
||||
/** Disposes of events and DOM-references belonging to the angle editor. */
|
||||
private dropdownDispose_() {
|
||||
if (this.clickWrapper_) {
|
||||
browserEvents.unbind(this.clickWrapper_);
|
||||
this.clickWrapper_ = null;
|
||||
private dropdownDispose() {
|
||||
for (const event of this.boundEvents) {
|
||||
browserEvents.unbind(event);
|
||||
}
|
||||
if (this.clickSurfaceWrapper_) {
|
||||
browserEvents.unbind(this.clickSurfaceWrapper_);
|
||||
this.clickSurfaceWrapper_ = null;
|
||||
}
|
||||
if (this.moveSurfaceWrapper_) {
|
||||
browserEvents.unbind(this.moveSurfaceWrapper_);
|
||||
this.moveSurfaceWrapper_ = null;
|
||||
}
|
||||
this.gauge_ = null;
|
||||
this.line_ = null;
|
||||
this.boundEvents.length = 0;
|
||||
this.gauge = null;
|
||||
this.line = null;
|
||||
}
|
||||
|
||||
/** Hide the editor. */
|
||||
private hide_() {
|
||||
private hide() {
|
||||
dropDownDiv.hideIfOwner(this);
|
||||
WidgetDiv.hide();
|
||||
}
|
||||
@@ -304,7 +288,7 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*/
|
||||
protected onMouseMove_(e: PointerEvent) {
|
||||
// Calculate angle.
|
||||
const bBox = this.gauge_!.ownerSVGElement!.getBoundingClientRect();
|
||||
const bBox = this.gauge!.ownerSVGElement!.getBoundingClientRect();
|
||||
const dx = e.clientX - bBox.left - FieldAngle.HALF;
|
||||
const dy = e.clientY - bBox.top - FieldAngle.HALF;
|
||||
let angle = Math.atan(-dy / dx);
|
||||
@@ -321,13 +305,13 @@ export class FieldAngle extends FieldInput<number> {
|
||||
}
|
||||
|
||||
// Do offsetting.
|
||||
if (this.clockwise_) {
|
||||
angle = this.offset_ + 360 - angle;
|
||||
if (this.clockwise) {
|
||||
angle = this.offset + 360 - angle;
|
||||
} else {
|
||||
angle = 360 - (this.offset_ - angle);
|
||||
angle = 360 - (this.offset - angle);
|
||||
}
|
||||
|
||||
this.displayMouseOrKeyboardValue_(angle);
|
||||
this.displayMouseOrKeyboardValue(angle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,31 +321,31 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*
|
||||
* @param angle New angle.
|
||||
*/
|
||||
private displayMouseOrKeyboardValue_(angle: number) {
|
||||
if (this.round_) {
|
||||
angle = Math.round(angle / this.round_) * this.round_;
|
||||
private displayMouseOrKeyboardValue(angle: number) {
|
||||
if (this.round) {
|
||||
angle = Math.round(angle / this.round) * this.round;
|
||||
}
|
||||
angle = this.wrapValue_(angle);
|
||||
angle = this.wrapValue(angle);
|
||||
if (angle !== this.value_) {
|
||||
this.setEditorValue_(angle);
|
||||
}
|
||||
}
|
||||
|
||||
/** Redraw the graph with the current angle. */
|
||||
private updateGraph_() {
|
||||
if (!this.gauge_) {
|
||||
private updateGraph() {
|
||||
if (!this.gauge || !this.line) {
|
||||
return;
|
||||
}
|
||||
// Always display the input (i.e. getText) even if it is invalid.
|
||||
let angleDegrees = Number(this.getText()) + this.offset_;
|
||||
let angleDegrees = Number(this.getText()) + this.offset;
|
||||
angleDegrees %= 360;
|
||||
let angleRadians = math.toRadians(angleDegrees);
|
||||
const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
|
||||
let x2 = FieldAngle.HALF;
|
||||
let y2 = FieldAngle.HALF;
|
||||
if (!isNaN(angleRadians)) {
|
||||
const clockwiseFlag = Number(this.clockwise_);
|
||||
const angle1 = math.toRadians(this.offset_);
|
||||
const clockwiseFlag = Number(this.clockwise);
|
||||
const angle1 = math.toRadians(this.offset);
|
||||
const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
|
||||
const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
|
||||
if (clockwiseFlag) {
|
||||
@@ -379,9 +363,9 @@ export class FieldAngle extends FieldInput<number> {
|
||||
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
|
||||
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
|
||||
}
|
||||
this.gauge_.setAttribute('d', path.join(''));
|
||||
this.line_?.setAttribute('x2', `${x2}`);
|
||||
this.line_?.setAttribute('y2', `${y2}`);
|
||||
this.gauge.setAttribute('d', path.join(''));
|
||||
this.line.setAttribute('x2', `${x2}`);
|
||||
this.line.setAttribute('y2', `${y2}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,23 +380,28 @@ export class FieldAngle extends FieldInput<number> {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
let multiplier;
|
||||
if (e.keyCode === KeyCodes.LEFT) {
|
||||
let multiplier = 0;
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
// decrement (increment in RTL)
|
||||
multiplier = block.RTL ? 1 : -1;
|
||||
} else if (e.keyCode === KeyCodes.RIGHT) {
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
// increment (decrement in RTL)
|
||||
multiplier = block.RTL ? -1 : 1;
|
||||
} else if (e.keyCode === KeyCodes.DOWN) {
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
// decrement
|
||||
multiplier = -1;
|
||||
} else if (e.keyCode === KeyCodes.UP) {
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
// increment
|
||||
multiplier = 1;
|
||||
break;
|
||||
}
|
||||
if (multiplier) {
|
||||
const value = this.getValue() as number;
|
||||
this.displayMouseOrKeyboardValue_(value + multiplier * this.round_);
|
||||
this.displayMouseOrKeyboardValue(value + multiplier * this.round);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -421,15 +410,15 @@ export class FieldAngle extends FieldInput<number> {
|
||||
/**
|
||||
* Ensure that the input value is a valid angle.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid angle, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: any): number|null {
|
||||
const value = Number(opt_newValue);
|
||||
protected override doClassValidation_(newValue?: any): number|null {
|
||||
const value = Number(newValue);
|
||||
if (isNaN(value) || !isFinite(value)) {
|
||||
return null;
|
||||
}
|
||||
return this.wrapValue_(value);
|
||||
return this.wrapValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,12 +427,12 @@ export class FieldAngle extends FieldInput<number> {
|
||||
* @param value The value to wrap.
|
||||
* @returns The wrapped value.
|
||||
*/
|
||||
private wrapValue_(value: number): number {
|
||||
private wrapValue(value: number): number {
|
||||
value %= 360;
|
||||
if (value < 0) {
|
||||
value += 360;
|
||||
}
|
||||
if (value > this.wrap_) {
|
||||
if (value > this.wrap) {
|
||||
value -= 360;
|
||||
}
|
||||
return value;
|
||||
@@ -464,13 +453,20 @@ export class FieldAngle extends FieldInput<number> {
|
||||
}
|
||||
}
|
||||
|
||||
/** CSS for angle field. See css.js for use. */
|
||||
fieldRegistry.register('field_angle', FieldAngle);
|
||||
|
||||
FieldAngle.prototype.DEFAULT_VALUE = 0;
|
||||
|
||||
|
||||
/**
|
||||
* CSS for angle field.
|
||||
*/
|
||||
Css.register(`
|
||||
.blocklyAngleCircle {
|
||||
stroke: #444;
|
||||
stroke-width: 1;
|
||||
fill: #ddd;
|
||||
fill-opacity: .8;
|
||||
fill-opacity: 0.8;
|
||||
}
|
||||
|
||||
.blocklyAngleMarks {
|
||||
@@ -480,7 +476,7 @@ Css.register(`
|
||||
|
||||
.blocklyAngleGauge {
|
||||
fill: #f88;
|
||||
fill-opacity: .8;
|
||||
fill-opacity: 0.8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -492,10 +488,6 @@ Css.register(`
|
||||
}
|
||||
`);
|
||||
|
||||
fieldRegistry.register('field_angle', FieldAngle);
|
||||
|
||||
FieldAngle.prototype.DEFAULT_VALUE = 0;
|
||||
|
||||
/**
|
||||
* The two main modes of the angle field.
|
||||
* Compass specifies:
|
||||
|
||||
@@ -49,22 +49,22 @@ export class FieldCheckbox extends Field<CheckboxBool> {
|
||||
override value_: boolean|null = this.value_;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should either be 'TRUE',
|
||||
* @param value The initial value of the field. Should either be 'TRUE',
|
||||
* 'FALSE' or a boolean. Defaults to 'FALSE'. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a value ('TRUE' or 'FALSE') & returns a
|
||||
* validated value ('TRUE' or 'FALSE'), or null to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: CheckboxBool|Sentinel, opt_validator?: FieldCheckboxValidator,
|
||||
opt_config?: FieldCheckboxConfig) {
|
||||
value?: CheckboxBool|Sentinel, validator?: FieldCheckboxValidator,
|
||||
config?: FieldCheckboxConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
/**
|
||||
@@ -73,13 +73,13 @@ export class FieldCheckbox extends Field<CheckboxBool> {
|
||||
*/
|
||||
this.checkChar_ = FieldCheckbox.CHECK_CHAR;
|
||||
|
||||
if (Field.isSentinel(opt_value)) return;
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (Field.isSentinel(value)) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,15 +150,15 @@ export class FieldCheckbox extends Field<CheckboxBool> {
|
||||
/**
|
||||
* Ensure that the input value is valid ('TRUE' or 'FALSE').
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid value ('TRUE' or 'FALSE), or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
protected override doClassValidation_(newValue?: AnyDuringMigration):
|
||||
BoolString|null {
|
||||
if (opt_newValue === true || opt_newValue === 'TRUE') {
|
||||
if (newValue === true || newValue === 'TRUE') {
|
||||
return 'TRUE';
|
||||
}
|
||||
if (opt_newValue === false || opt_newValue === 'FALSE') {
|
||||
if (newValue === false || newValue === 'FALSE') {
|
||||
return 'FALSE';
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -25,7 +25,6 @@ import * as fieldRegistry from './field_registry.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import * as colour from './utils/colour.js';
|
||||
import * as idGenerator from './utils/idgenerator.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import {Size} from './utils/size.js';
|
||||
|
||||
@@ -76,25 +75,17 @@ export class FieldColour extends Field<string> {
|
||||
static COLUMNS = 7;
|
||||
|
||||
/** The field's colour picker element. */
|
||||
private picker_: HTMLElement|null = null;
|
||||
private picker: HTMLElement|null = null;
|
||||
|
||||
/** Index of the currently highlighted element. */
|
||||
private highlightedIndex_: number|null = null;
|
||||
private highlightedIndex: number|null = null;
|
||||
|
||||
/** Mouse click event data. */
|
||||
private onClickWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse move event data. */
|
||||
private onMouseMoveWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse enter event data. */
|
||||
private onMouseEnterWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse leave event data. */
|
||||
private onMouseLeaveWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Key down event data. */
|
||||
private onKeyDownWrapper_: browserEvents.Data|null = null;
|
||||
/**
|
||||
* Array holding info needed to unbind events.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
*/
|
||||
private boundEvents: browserEvents.Data[] = [];
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the serializer, non-serializable fields
|
||||
@@ -113,46 +104,46 @@ export class FieldColour extends Field<string> {
|
||||
protected override isDirty_ = false;
|
||||
|
||||
/** Array of colours used by this field. If null, use the global list. */
|
||||
private colours_: string[]|null = null;
|
||||
private colours: string[]|null = null;
|
||||
|
||||
/**
|
||||
* Array of colour tooltips used by this field. If null, use the global
|
||||
* list.
|
||||
*/
|
||||
private titles_: string[]|null = null;
|
||||
private titles: string[]|null = null;
|
||||
|
||||
/**
|
||||
* Number of colour columns used by this field. If 0, use the global
|
||||
* setting. By default use the global constants for columns.
|
||||
*/
|
||||
private columns_ = 0;
|
||||
private columns = 0;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should be in '#rrggbb'
|
||||
* @param value The initial value of the field. Should be in '#rrggbb'
|
||||
* format. Defaults to the first value in the default colour array. Also
|
||||
* accepts Field.SKIP_SETUP if you wish to skip setup (only used by
|
||||
* subclasses that want to handle configuration and setting the field
|
||||
* value after their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a colour string & returns a validated colour
|
||||
* string ('#rrggbb' format), or null to abort the change.Blockly.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* string ('#rrggbb' format), or null to abort the change.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_validator?: FieldColourValidator,
|
||||
opt_config?: FieldColourConfig) {
|
||||
value?: string|Sentinel, validator?: FieldColourValidator,
|
||||
config?: FieldColourConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (Field.isSentinel(opt_value)) return;
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (Field.isSentinel(value)) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,9 +154,9 @@ export class FieldColour extends Field<string> {
|
||||
*/
|
||||
protected override configure_(config: FieldColourConfig) {
|
||||
super.configure_(config);
|
||||
if (config.colourOptions) this.colours_ = config.colourOptions;
|
||||
if (config.colourTitles) this.titles_ = config.colourTitles;
|
||||
if (config.columns) this.columns_ = config.columns;
|
||||
if (config.colourOptions) this.colours = config.colourOptions;
|
||||
if (config.colourTitles) this.titles = config.colourTitles;
|
||||
if (config.columns) this.columns = config.columns;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,6 +176,11 @@ export class FieldColour extends Field<string> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates text field to match the colour/style of the block.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
override applyColour() {
|
||||
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
|
||||
if (this.borderRect_) {
|
||||
@@ -200,14 +196,14 @@ export class FieldColour extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value is a valid colour.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid colour, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: any): string|null {
|
||||
if (typeof opt_newValue !== 'string') {
|
||||
protected override doClassValidation_(newValue?: any): string|null {
|
||||
if (typeof newValue !== 'string') {
|
||||
return null;
|
||||
}
|
||||
return colour.parse(opt_newValue);
|
||||
return colour.parse(newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,14 +243,14 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param colours Array of colours for this block, or null to use default
|
||||
* (FieldColour.COLOURS).
|
||||
* @param opt_titles Optional array of colour tooltips, or null to use default
|
||||
* @param titles Optional array of colour tooltips, or null to use default
|
||||
* (FieldColour.TITLES).
|
||||
* @returns Returns itself (for method chaining).
|
||||
*/
|
||||
setColours(colours: string[], opt_titles?: string[]): FieldColour {
|
||||
this.colours_ = colours;
|
||||
if (opt_titles) {
|
||||
this.titles_ = opt_titles;
|
||||
setColours(colours: string[], titles?: string[]): FieldColour {
|
||||
this.colours = colours;
|
||||
if (titles) {
|
||||
this.titles = titles;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -267,19 +263,19 @@ export class FieldColour extends Field<string> {
|
||||
* @returns Returns itself (for method chaining).
|
||||
*/
|
||||
setColumns(columns: number): FieldColour {
|
||||
this.columns_ = columns;
|
||||
this.columns = columns;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Create and show the colour field's editor. */
|
||||
protected override showEditor_() {
|
||||
this.dropdownCreate_();
|
||||
dropDownDiv.getContentDiv().appendChild(this.picker_!);
|
||||
this.dropdownCreate();
|
||||
dropDownDiv.getContentDiv().appendChild(this.picker!);
|
||||
|
||||
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
|
||||
dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
|
||||
|
||||
// Focus so we can start receiving keyboard events.
|
||||
this.picker_!.focus({preventScroll: true});
|
||||
this.picker!.focus({preventScroll: true});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +283,7 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
*/
|
||||
private onClick_(e: PointerEvent) {
|
||||
private onClick(e: PointerEvent) {
|
||||
const cell = e.target as Element;
|
||||
const colour = cell && cell.getAttribute('data-colour');
|
||||
if (colour !== null) {
|
||||
@@ -302,31 +298,35 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Keyboard event.
|
||||
*/
|
||||
private onKeyDown_(e: KeyboardEvent) {
|
||||
let handled = false;
|
||||
if (e.keyCode === KeyCodes.UP) {
|
||||
this.moveHighlightBy_(0, -1);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.DOWN) {
|
||||
this.moveHighlightBy_(0, 1);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.LEFT) {
|
||||
this.moveHighlightBy_(-1, 0);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.RIGHT) {
|
||||
this.moveHighlightBy_(1, 0);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.ENTER) {
|
||||
private onKeyDown(e: KeyboardEvent) {
|
||||
let handled = true;
|
||||
let highlighted: HTMLElement|null;
|
||||
switch (e.key) {
|
||||
case 'ArrowUp':
|
||||
this.moveHighlightBy(0, -1);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
this.moveHighlightBy(0, 1);
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
this.moveHighlightBy(-1, 0);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.moveHighlightBy(1, 0);
|
||||
break;
|
||||
case 'Enter':
|
||||
// Select the highlighted colour.
|
||||
const highlighted = this.getHighlighted_();
|
||||
highlighted = this.getHighlighted();
|
||||
if (highlighted) {
|
||||
const colour = highlighted && highlighted.getAttribute('data-colour');
|
||||
const colour = highlighted.getAttribute('data-colour');
|
||||
if (colour !== null) {
|
||||
this.setValue(colour);
|
||||
}
|
||||
}
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
handled = true;
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
if (handled) {
|
||||
e.stopPropagation();
|
||||
@@ -336,22 +336,22 @@ export class FieldColour extends Field<string> {
|
||||
/**
|
||||
* Move the currently highlighted position by dx and dy.
|
||||
*
|
||||
* @param dx Change of x
|
||||
* @param dy Change of y
|
||||
* @param dx Change of x.
|
||||
* @param dy Change of y.
|
||||
*/
|
||||
private moveHighlightBy_(dx: number, dy: number) {
|
||||
if (!this.highlightedIndex_) {
|
||||
private moveHighlightBy(dx: number, dy: number) {
|
||||
if (!this.highlightedIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const colours = this.colours_ || FieldColour.COLOURS;
|
||||
const columns = this.columns_ || FieldColour.COLUMNS;
|
||||
const colours = this.colours || FieldColour.COLOURS;
|
||||
const columns = this.columns || FieldColour.COLUMNS;
|
||||
|
||||
// Get the current x and y coordinates
|
||||
let x = this.highlightedIndex_ % columns;
|
||||
let y = Math.floor(this.highlightedIndex_ / columns);
|
||||
// Get the current x and y coordinates.
|
||||
let x = this.highlightedIndex % columns;
|
||||
let y = Math.floor(this.highlightedIndex / columns);
|
||||
|
||||
// Add the offset
|
||||
// Add the offset.
|
||||
x += dx;
|
||||
y += dy;
|
||||
|
||||
@@ -386,9 +386,9 @@ export class FieldColour extends Field<string> {
|
||||
}
|
||||
|
||||
// Move the highlight to the new coordinates.
|
||||
const cell = this.picker_!.childNodes[y].childNodes[x] as Element;
|
||||
const cell = this.picker!.childNodes[y].childNodes[x] as Element;
|
||||
const index = y * columns + x;
|
||||
this.setHighlightedCell_(cell, index);
|
||||
this.setHighlightedCell(cell, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,26 +396,26 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
*/
|
||||
private onMouseMove_(e: PointerEvent) {
|
||||
private onMouseMove(e: PointerEvent) {
|
||||
const cell = e.target as Element;
|
||||
const index = cell && Number(cell.getAttribute('data-index'));
|
||||
if (index !== null && index !== this.highlightedIndex_) {
|
||||
this.setHighlightedCell_(cell, index);
|
||||
if (index !== null && index !== this.highlightedIndex) {
|
||||
this.setHighlightedCell(cell, index);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle a mouse enter event. Focus the picker. */
|
||||
private onMouseEnter_() {
|
||||
this.picker_?.focus({preventScroll: true});
|
||||
private onMouseEnter() {
|
||||
this.picker?.focus({preventScroll: true});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse leave event. Blur the picker and unhighlight
|
||||
* the currently highlighted colour.
|
||||
*/
|
||||
private onMouseLeave_() {
|
||||
this.picker_?.blur();
|
||||
const highlighted = this.getHighlighted_();
|
||||
private onMouseLeave() {
|
||||
this.picker?.blur();
|
||||
const highlighted = this.getHighlighted();
|
||||
if (highlighted) {
|
||||
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||
}
|
||||
@@ -426,54 +426,53 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @returns Highlighted item (null if none).
|
||||
*/
|
||||
private getHighlighted_(): HTMLElement|null {
|
||||
if (!this.highlightedIndex_) {
|
||||
private getHighlighted(): HTMLElement|null {
|
||||
if (!this.highlightedIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const columns = this.columns_ || FieldColour.COLUMNS;
|
||||
const x = this.highlightedIndex_ % columns;
|
||||
const y = Math.floor(this.highlightedIndex_ / columns);
|
||||
const row = this.picker_!.childNodes[y];
|
||||
const columns = this.columns || FieldColour.COLUMNS;
|
||||
const x = this.highlightedIndex % columns;
|
||||
const y = Math.floor(this.highlightedIndex / columns);
|
||||
const row = this.picker!.childNodes[y];
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
const col = row.childNodes[x] as HTMLElement;
|
||||
return col;
|
||||
return row.childNodes[x] as HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the currently highlighted cell.
|
||||
*
|
||||
* @param cell the new cell to highlight
|
||||
* @param index the index of the new cell
|
||||
* @param cell The new cell to highlight.
|
||||
* @param index The index of the new cell.
|
||||
*/
|
||||
private setHighlightedCell_(cell: Element, index: number) {
|
||||
private setHighlightedCell(cell: Element, index: number) {
|
||||
// Unhighlight the current item.
|
||||
const highlighted = this.getHighlighted_();
|
||||
const highlighted = this.getHighlighted();
|
||||
if (highlighted) {
|
||||
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||
}
|
||||
// Highlight new item.
|
||||
dom.addClass(cell, 'blocklyColourHighlighted');
|
||||
// Set new highlighted index.
|
||||
this.highlightedIndex_ = index;
|
||||
this.highlightedIndex = index;
|
||||
|
||||
// Update accessibility roles.
|
||||
const cellId = cell.getAttribute('id');
|
||||
if (cellId && this.picker_) {
|
||||
aria.setState(this.picker_, aria.State.ACTIVEDESCENDANT, cellId);
|
||||
if (cellId && this.picker) {
|
||||
aria.setState(this.picker, aria.State.ACTIVEDESCENDANT, cellId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a colour picker dropdown editor. */
|
||||
private dropdownCreate_() {
|
||||
const columns = this.columns_ || FieldColour.COLUMNS;
|
||||
const colours = this.colours_ || FieldColour.COLOURS;
|
||||
const titles = this.titles_ || FieldColour.TITLES;
|
||||
private dropdownCreate() {
|
||||
const columns = this.columns || FieldColour.COLUMNS;
|
||||
const colours = this.colours || FieldColour.COLOURS;
|
||||
const titles = this.titles || FieldColour.TITLES;
|
||||
const selectedColour = this.getValue();
|
||||
// Create the palette.
|
||||
const table = (document.createElement('table'));
|
||||
const table = document.createElement('table');
|
||||
table.className = 'blocklyColourTable';
|
||||
table.tabIndex = 0;
|
||||
table.dir = 'ltr';
|
||||
@@ -495,56 +494,40 @@ export class FieldColour extends Field<string> {
|
||||
cell.setAttribute('data-colour', colours[i]);
|
||||
cell.title = titles[i] || colours[i];
|
||||
cell.id = idGenerator.getNextUniqueId();
|
||||
cell.setAttribute('data-index', String(i));
|
||||
cell.setAttribute('data-index', `${i}`);
|
||||
aria.setRole(cell, aria.Role.GRIDCELL);
|
||||
aria.setState(cell, aria.State.LABEL, colours[i]);
|
||||
aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour);
|
||||
cell.style.backgroundColor = colours[i];
|
||||
if (colours[i] === selectedColour) {
|
||||
cell.className = 'blocklyColourSelected';
|
||||
this.highlightedIndex_ = i;
|
||||
this.highlightedIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure event handler on the table to listen for any event in a cell.
|
||||
this.onClickWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointerdown', this, this.onClick_, true);
|
||||
this.onMouseMoveWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointermove', this, this.onMouseMove_, true);
|
||||
this.onMouseEnterWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointerenter', this, this.onMouseEnter_, true);
|
||||
this.onMouseLeaveWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointerleave', this, this.onMouseLeave_, true);
|
||||
this.onKeyDownWrapper_ =
|
||||
browserEvents.conditionalBind(table, 'keydown', this, this.onKeyDown_);
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointerdown', this, this.onClick, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointermove', this, this.onMouseMove, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointerenter', this, this.onMouseEnter, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointerleave', this, this.onMouseLeave, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'keydown', this, this.onKeyDown, false));
|
||||
|
||||
this.picker_ = table;
|
||||
this.picker = table;
|
||||
}
|
||||
|
||||
/** Disposes of events and DOM-references belonging to the colour editor. */
|
||||
private dropdownDispose_() {
|
||||
if (this.onClickWrapper_) {
|
||||
browserEvents.unbind(this.onClickWrapper_);
|
||||
this.onClickWrapper_ = null;
|
||||
private dropdownDispose() {
|
||||
for (const event of this.boundEvents) {
|
||||
browserEvents.unbind(event);
|
||||
}
|
||||
if (this.onMouseMoveWrapper_) {
|
||||
browserEvents.unbind(this.onMouseMoveWrapper_);
|
||||
this.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
if (this.onMouseEnterWrapper_) {
|
||||
browserEvents.unbind(this.onMouseEnterWrapper_);
|
||||
this.onMouseEnterWrapper_ = null;
|
||||
}
|
||||
if (this.onMouseLeaveWrapper_) {
|
||||
browserEvents.unbind(this.onMouseLeaveWrapper_);
|
||||
this.onMouseLeaveWrapper_ = null;
|
||||
}
|
||||
if (this.onKeyDownWrapper_) {
|
||||
browserEvents.unbind(this.onKeyDownWrapper_);
|
||||
this.onKeyDownWrapper_ = null;
|
||||
}
|
||||
this.picker_ = null;
|
||||
this.highlightedIndex_ = null;
|
||||
this.boundEvents.length = 0;
|
||||
this.picker = null;
|
||||
this.highlightedIndex = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,7 +548,12 @@ export class FieldColour extends Field<string> {
|
||||
/** The default value for this field. */
|
||||
FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0];
|
||||
|
||||
/** CSS for colour picker. See css.js for use. */
|
||||
fieldRegistry.register('field_colour', FieldColour);
|
||||
|
||||
|
||||
/**
|
||||
* CSS for colour picker.
|
||||
*/
|
||||
Css.register(`
|
||||
.blocklyColourTable {
|
||||
border-collapse: collapse;
|
||||
@@ -575,7 +563,7 @@ Css.register(`
|
||||
}
|
||||
|
||||
.blocklyColourTable>tr>td {
|
||||
border: .5px solid #888;
|
||||
border: 0.5px solid #888;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
@@ -586,7 +574,7 @@ Css.register(`
|
||||
|
||||
.blocklyColourTable>tr>td.blocklyColourHighlighted {
|
||||
border-color: #eee;
|
||||
box-shadow: 2px 2px 7px 2px rgba(0,0,0,.3);
|
||||
box-shadow: 2px 2px 7px 2px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -597,8 +585,6 @@ Css.register(`
|
||||
}
|
||||
`);
|
||||
|
||||
fieldRegistry.register('field_colour', FieldColour);
|
||||
|
||||
/**
|
||||
* Config options for the colour field.
|
||||
*/
|
||||
|
||||
@@ -97,11 +97,11 @@ export class FieldDropdown extends Field<string> {
|
||||
* if you wish to skip setup (only used by subclasses that want to handle
|
||||
* configuration and setting the field value after their own constructors
|
||||
* have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a language-neutral dropdown option & returns a
|
||||
* validated language-neutral dropdown option, or null to abort the
|
||||
* change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
@@ -109,14 +109,14 @@ export class FieldDropdown extends Field<string> {
|
||||
*/
|
||||
constructor(
|
||||
menuGenerator: MenuGenerator,
|
||||
opt_validator?: FieldDropdownValidator,
|
||||
opt_config?: FieldDropdownConfig,
|
||||
validator?: FieldDropdownValidator,
|
||||
config?: FieldDropdownConfig,
|
||||
);
|
||||
constructor(menuGenerator: Sentinel);
|
||||
constructor(
|
||||
menuGenerator: MenuGenerator|Sentinel,
|
||||
opt_validator?: FieldDropdownValidator,
|
||||
opt_config?: FieldDropdownConfig,
|
||||
validator?: FieldDropdownValidator,
|
||||
config?: FieldDropdownConfig,
|
||||
) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
@@ -139,12 +139,12 @@ export class FieldDropdown extends Field<string> {
|
||||
*/
|
||||
this.selectedOption_ = this.getOptions(false)[0];
|
||||
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(this.selectedOption_[1]);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,17 +244,17 @@ export class FieldDropdown extends Field<string> {
|
||||
/**
|
||||
* Create a dropdown menu under the text.
|
||||
*
|
||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||
* @param e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
*/
|
||||
protected override showEditor_(opt_e?: MouseEvent) {
|
||||
protected override showEditor_(e?: MouseEvent) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.dropdownCreate_();
|
||||
if (opt_e && typeof opt_e.clientX === 'number') {
|
||||
this.menu_!.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY);
|
||||
if (e && typeof e.clientX === 'number') {
|
||||
this.menu_!.openingCoords = new Coordinate(e.clientX, e.clientY);
|
||||
} else {
|
||||
this.menu_!.openingCoords = null;
|
||||
}
|
||||
@@ -368,20 +368,20 @@ export class FieldDropdown extends Field<string> {
|
||||
/**
|
||||
* Return a list of the options for this dropdown.
|
||||
*
|
||||
* @param opt_useCache For dynamic options, whether or not to use the cached
|
||||
* @param useCache For dynamic options, whether or not to use the cached
|
||||
* options or to re-generate them.
|
||||
* @returns A non-empty array of option tuples:
|
||||
* (human-readable text or image, language-neutral name).
|
||||
* @throws {TypeError} If generated options are incorrectly structured.
|
||||
*/
|
||||
getOptions(opt_useCache?: boolean): MenuOption[] {
|
||||
getOptions(useCache?: boolean): MenuOption[] {
|
||||
if (!this.menuGenerator_) {
|
||||
// A subclass improperly skipped setup without defining the menu
|
||||
// generator.
|
||||
throw TypeError('A menu generator was never defined.');
|
||||
}
|
||||
if (Array.isArray(this.menuGenerator_)) return this.menuGenerator_;
|
||||
if (opt_useCache && this.generatedOptions_) return this.generatedOptions_;
|
||||
if (useCache && this.generatedOptions_) return this.generatedOptions_;
|
||||
|
||||
this.generatedOptions_ = this.menuGenerator_();
|
||||
validateOptions(this.generatedOptions_);
|
||||
@@ -391,23 +391,23 @@ export class FieldDropdown extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value is a valid language-neutral option.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid language-neutral option, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: string): string|null {
|
||||
protected override doClassValidation_(newValue?: string): string|null {
|
||||
const options = this.getOptions(true);
|
||||
const isValueValid = options.some((option) => option[1] === opt_newValue);
|
||||
const isValueValid = options.some((option) => option[1] === newValue);
|
||||
|
||||
if (!isValueValid) {
|
||||
if (this.sourceBlock_) {
|
||||
console.warn(
|
||||
'Cannot set the dropdown\'s value to an unavailable option.' +
|
||||
' Block type: ' + this.sourceBlock_.type +
|
||||
', Field name: ' + this.name + ', Value: ' + opt_newValue);
|
||||
', Field name: ' + this.name + ', Value: ' + newValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return opt_newValue as string;
|
||||
return newValue as string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,8 +481,8 @@ export class FieldDropdown extends Field<string> {
|
||||
this.imageElement_!.style.display = '';
|
||||
this.imageElement_!.setAttributeNS(
|
||||
dom.XLINK_NS, 'xlink:href', imageJson.src);
|
||||
this.imageElement_!.setAttribute('height', `${imageJson.height}`);
|
||||
this.imageElement_!.setAttribute('width', `${imageJson.width}`);
|
||||
this.imageElement_!.setAttribute('height', String(imageJson.height));
|
||||
this.imageElement_!.setAttribute('width', String(imageJson.width));
|
||||
|
||||
const imageHeight = Number(imageJson.height);
|
||||
const imageWidth = Number(imageJson.width);
|
||||
@@ -512,14 +512,13 @@ export class FieldDropdown extends Field<string> {
|
||||
let arrowX = 0;
|
||||
if (block.RTL) {
|
||||
const imageX = xPadding + arrowWidth;
|
||||
this.imageElement_!.setAttribute('x', imageX.toString());
|
||||
this.imageElement_!.setAttribute('x', `${imageX}`);
|
||||
} else {
|
||||
arrowX = imageWidth + arrowWidth;
|
||||
this.getTextElement().setAttribute('text-anchor', 'end');
|
||||
this.imageElement_!.setAttribute('x', xPadding.toString());
|
||||
this.imageElement_!.setAttribute('x', `${xPadding}`);
|
||||
}
|
||||
this.imageElement_!.setAttribute(
|
||||
'y', (height / 2 - imageHeight / 2).toString());
|
||||
this.imageElement_!.setAttribute('y', String(height / 2 - imageHeight / 2));
|
||||
|
||||
this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth);
|
||||
}
|
||||
|
||||
@@ -64,19 +64,19 @@ export class FieldImage extends Field<string> {
|
||||
* after their own constructors have run).
|
||||
* @param width Width of the image.
|
||||
* @param height Height of the image.
|
||||
* @param opt_alt Optional alt text for when block is collapsed.
|
||||
* @param opt_onClick Optional function to be called when the image is
|
||||
* clicked. If opt_onClick is defined, opt_alt must also be defined.
|
||||
* @param opt_flipRtl Whether to flip the icon in RTL.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param alt Optional alt text for when block is collapsed.
|
||||
* @param onClick Optional function to be called when the image is
|
||||
* clicked. If onClick is defined, alt must also be defined.
|
||||
* @param flipRtl Whether to flip the icon in RTL.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
src: string|Sentinel, width: string|number, height: string|number,
|
||||
opt_alt?: string, opt_onClick?: (p1: FieldImage) => void,
|
||||
opt_flipRtl?: boolean, opt_config?: FieldImageConfig) {
|
||||
alt?: string, onClick?: (p1: FieldImage) => void, flipRtl?: boolean,
|
||||
config?: FieldImageConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
const imageHeight = Number(parsing.replaceMessageReferences(height));
|
||||
@@ -100,19 +100,19 @@ export class FieldImage extends Field<string> {
|
||||
*/
|
||||
this.imageHeight_ = imageHeight;
|
||||
|
||||
if (typeof opt_onClick === 'function') {
|
||||
this.clickHandler_ = opt_onClick;
|
||||
if (typeof onClick === 'function') {
|
||||
this.clickHandler_ = onClick;
|
||||
}
|
||||
|
||||
if (src === Field.SKIP_SETUP) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
} else {
|
||||
this.flipRtl_ = !!opt_flipRtl;
|
||||
this.altText_ = parsing.replaceMessageReferences(opt_alt) || '';
|
||||
this.flipRtl_ = !!flipRtl;
|
||||
this.altText_ = parsing.replaceMessageReferences(alt) || '';
|
||||
}
|
||||
this.setValue(parsing.replaceMessageReferences(src));
|
||||
}
|
||||
@@ -157,14 +157,14 @@ export class FieldImage extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value (the source URL) is a string.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A string, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: any): string|null {
|
||||
if (typeof opt_newValue !== 'string') {
|
||||
protected override doClassValidation_(newValue?: any): string|null {
|
||||
if (typeof newValue !== 'string') {
|
||||
return null;
|
||||
}
|
||||
return opt_newValue;
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,7 +177,7 @@ export class FieldImage extends Field<string> {
|
||||
this.value_ = newValue;
|
||||
if (this.imageElement_) {
|
||||
this.imageElement_.setAttributeNS(
|
||||
dom.XLINK_NS, 'xlink:href', String(this.value_));
|
||||
dom.XLINK_NS, 'xlink:href', this.value_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.
|
||||
import {Msg} from './msg.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
@@ -90,31 +89,31 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
|
||||
override CURSOR = 'text';
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a string.
|
||||
* @param value The initial value of the field. Should cast to a string.
|
||||
* Defaults to an empty string if null or undefined. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a string & returns a validated string, or null
|
||||
* to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_validator?: FieldInputValidator<T>|null,
|
||||
opt_config?: FieldInputConfig) {
|
||||
value?: string|Sentinel, validator?: FieldInputValidator<T>|null,
|
||||
config?: FieldInputConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (Field.isSentinel(opt_value)) return;
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (Field.isSentinel(value)) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,14 +261,13 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
|
||||
* Shows a prompt editor for mobile browsers if the modalInputs option is
|
||||
* enabled.
|
||||
*
|
||||
* @param _opt_e Optional mouse event that triggered the field to open, or
|
||||
* @param _e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
* @param opt_quietInput True if editor should be created without focus.
|
||||
* @param quietInput True if editor should be created without focus.
|
||||
* Defaults to false.
|
||||
*/
|
||||
protected override showEditor_(_opt_e?: Event, opt_quietInput?: boolean) {
|
||||
protected override showEditor_(_e?: Event, quietInput = false) {
|
||||
this.workspace_ = (this.sourceBlock_ as BlockSvg).workspace;
|
||||
const quietInput = opt_quietInput || false;
|
||||
if (!quietInput && this.workspace_.options.modalInputs &&
|
||||
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
|
||||
this.showPromptEditor_();
|
||||
@@ -358,7 +356,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
|
||||
div!.style.transition = 'box-shadow 0.25s ease 0s';
|
||||
if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) {
|
||||
div!.style.boxShadow =
|
||||
'rgba(255, 255, 255, 0.3) 0 0 0 ' + 4 * scale + 'px';
|
||||
'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px';
|
||||
}
|
||||
}
|
||||
htmlInput.style.borderRadius = borderRadius;
|
||||
@@ -444,15 +442,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
|
||||
* @param e Keyboard event.
|
||||
*/
|
||||
protected onHtmlInputKeyDown_(e: KeyboardEvent) {
|
||||
if (e.keyCode === KeyCodes.ENTER) {
|
||||
if (e.key === 'Enter') {
|
||||
WidgetDiv.hide();
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
} else if (e.keyCode === KeyCodes.ESC) {
|
||||
} else if (e.key === 'Esc') {
|
||||
this.setValue(
|
||||
this.htmlInput_!.getAttribute('data-untyped-default-value'));
|
||||
WidgetDiv.hide();
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
} else if (e.keyCode === KeyCodes.TAB) {
|
||||
} else if (e.key === 'Tab') {
|
||||
WidgetDiv.hide();
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
(this.sourceBlock_ as BlockSvg).tab(this, !e.shiftKey);
|
||||
@@ -543,7 +541,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
|
||||
* @returns The text to show on the HTML input.
|
||||
*/
|
||||
protected getEditorText_(value: AnyDuringMigration): string {
|
||||
return String(value);
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ import type {Sentinel} from './utils/sentinel.js';
|
||||
* Class for a non-editable, non-serializable text field.
|
||||
*/
|
||||
export class FieldLabel extends Field<string> {
|
||||
/** The html class name to use for this field. */
|
||||
/** The HTML class name to use for this field. */
|
||||
private class_: string|null = null;
|
||||
|
||||
/**
|
||||
@@ -33,29 +33,28 @@ export class FieldLabel extends Field<string> {
|
||||
override EDITABLE = false;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a string.
|
||||
* @param value The initial value of the field. Should cast to a string.
|
||||
* Defaults to an empty string if null or undefined. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_class Optional CSS class for the field's text.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param textClass Optional CSS class for the field's text.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_class?: string,
|
||||
opt_config?: FieldLabelConfig) {
|
||||
value?: string|Sentinel, textClass?: string, config?: FieldLabelConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (Field.isSentinel(opt_value)) return;
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (Field.isSentinel(value)) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
} else {
|
||||
this.class_ = opt_class || null;
|
||||
this.class_ = textClass || null;
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
this.setValue(value);
|
||||
}
|
||||
|
||||
protected override configure_(config: FieldLabelConfig) {
|
||||
@@ -78,15 +77,15 @@ export class FieldLabel extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value casts to a valid string.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid string, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
string|null {
|
||||
if (opt_newValue === null || opt_newValue === undefined) {
|
||||
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|
||||
|null {
|
||||
if (newValue === null || newValue === undefined) {
|
||||
return null;
|
||||
}
|
||||
return String(opt_newValue);
|
||||
return `${newValue}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -37,51 +37,44 @@ export class FieldNumber extends FieldInput<number> {
|
||||
*/
|
||||
private decimalPlaces_: number|null = null;
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the serializer, non-serializable fields
|
||||
* are not. Editable fields should also be serializable.
|
||||
*/
|
||||
override SERIALIZABLE = true;
|
||||
|
||||
/** Don't spellcheck numbers. Our validator does a better job. */
|
||||
protected override spellcheck_ = false;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a number.
|
||||
* @param value The initial value of the field. Should cast to a number.
|
||||
* Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup
|
||||
* (only used by subclasses that want to handle configuration and setting
|
||||
* the field value after their own constructors have run).
|
||||
* @param opt_min Minimum value. Will only be used if opt_config is not
|
||||
* @param min Minimum value. Will only be used if config is not
|
||||
* provided.
|
||||
* @param opt_max Maximum value. Will only be used if opt_config is not
|
||||
* @param max Maximum value. Will only be used if config is not
|
||||
* provided.
|
||||
* @param opt_precision Precision for value. Will only be used if opt_config
|
||||
* @param precision Precision for value. Will only be used if config
|
||||
* is not provided.
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a number & returns a validated number, or null
|
||||
* to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|number|Sentinel, opt_min?: string|number|null,
|
||||
opt_max?: string|number|null, opt_precision?: string|number|null,
|
||||
opt_validator?: FieldNumberValidator|null,
|
||||
opt_config?: FieldNumberConfig) {
|
||||
value?: string|number|Sentinel, min?: string|number|null,
|
||||
max?: string|number|null, precision?: string|number|null,
|
||||
validator?: FieldNumberValidator|null, config?: FieldNumberConfig) {
|
||||
// Pass SENTINEL so that we can define properties before value validation.
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (Field.isSentinel(opt_value)) return;
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (Field.isSentinel(value)) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
} else {
|
||||
this.setConstraints(opt_min, opt_max, opt_precision);
|
||||
this.setConstraints(min, max, precision);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,17 +239,17 @@ export class FieldNumber extends FieldInput<number> {
|
||||
* Ensure that the input value is a valid number (must fulfill the
|
||||
* constraints placed on the field).
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid number, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
number|null {
|
||||
if (opt_newValue === null) {
|
||||
protected override doClassValidation_(newValue?: AnyDuringMigration): number
|
||||
|null {
|
||||
if (newValue === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clean up text.
|
||||
let newValue = String(opt_newValue);
|
||||
newValue = `${newValue}`;
|
||||
// TODO: Handle cases like 'ten', '1.203,14', etc.
|
||||
// 'O' is sometimes mistaken for '0' by inexperienced users.
|
||||
newValue = newValue.replace(/O/ig, '0');
|
||||
|
||||
@@ -25,37 +25,37 @@ import type {Sentinel} from './utils/sentinel.js';
|
||||
*/
|
||||
export class FieldTextInput extends FieldInput<string> {
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a string.
|
||||
* @param value The initial value of the field. Should cast to a string.
|
||||
* Defaults to an empty string if null or undefined. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a string & returns a validated string, or null
|
||||
* to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_validator?: FieldTextInputValidator|null,
|
||||
opt_config?: FieldTextInputConfig) {
|
||||
super(opt_value, opt_validator, opt_config);
|
||||
value?: string|Sentinel, validator?: FieldTextInputValidator|null,
|
||||
config?: FieldTextInputConfig) {
|
||||
super(value, validator, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the input value casts to a valid string.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid string, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
string|null {
|
||||
if (opt_newValue === undefined) {
|
||||
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|
||||
|null {
|
||||
if (newValue === undefined) {
|
||||
return null;
|
||||
}
|
||||
return String(opt_newValue);
|
||||
return `${newValue}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -74,8 +74,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
/**
|
||||
* Lay out the blocks in the flyout.
|
||||
*
|
||||
* @param contents The blocks and buttons to lay
|
||||
* out.
|
||||
* @param contents The blocks and buttons to lay out.
|
||||
* @param gaps The visible gaps between blocks.
|
||||
*/
|
||||
protected abstract layout_(contents: FlyoutItem[], gaps: number[]): void;
|
||||
@@ -128,9 +127,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
protected toolboxPosition_: number;
|
||||
|
||||
/**
|
||||
* Opaque data that can be passed to Blockly.unbindEvent_.
|
||||
* Array holding info needed to unbind events.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
*/
|
||||
private eventWrappers_: browserEvents.Data = [];
|
||||
private boundEvents: browserEvents.Data[] = [];
|
||||
|
||||
/**
|
||||
* Function that will be registered as a change listener on the workspace
|
||||
@@ -357,9 +358,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
|
||||
this.hide();
|
||||
|
||||
Array.prototype.push.apply(
|
||||
this.eventWrappers_,
|
||||
browserEvents.conditionalBind(
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
(this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_));
|
||||
if (!this.autoClose) {
|
||||
this.filterWrapper_ = this.filterForCapacity_.bind(this);
|
||||
@@ -367,9 +366,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
}
|
||||
|
||||
// Dragging the flyout up and down.
|
||||
Array.prototype.push.apply(
|
||||
this.eventWrappers_,
|
||||
browserEvents.conditionalBind(
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
(this.svgBackground_ as SVGPathElement), 'pointerdown', this,
|
||||
this.onMouseDown_));
|
||||
|
||||
@@ -401,7 +398,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
dispose() {
|
||||
this.hide();
|
||||
this.workspace_.getComponentManager().removeComponent(this.id);
|
||||
browserEvents.unbind(this.eventWrappers_);
|
||||
for (const event of this.boundEvents) {
|
||||
browserEvents.unbind(event);
|
||||
}
|
||||
this.boundEvents.length = 0;
|
||||
if (this.filterWrapper_) {
|
||||
this.targetWorkspace.removeChangeListener(this.filterWrapper_);
|
||||
this.filterWrapper_ = null;
|
||||
@@ -525,8 +525,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
* @param y The computed y origin of the flyout's SVG group.
|
||||
*/
|
||||
protected positionAt_(width: number, height: number, x: number, y: number) {
|
||||
this.svgGroup_?.setAttribute('width', width.toString());
|
||||
this.svgGroup_?.setAttribute('height', height.toString());
|
||||
this.svgGroup_?.setAttribute('width', `${width}`);
|
||||
this.svgGroup_?.setAttribute('height', `${height}`);
|
||||
this.workspace_.setCachedParentSvgSize(width, height);
|
||||
|
||||
if (this.svgGroup_) {
|
||||
@@ -562,7 +562,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
}
|
||||
this.setVisible(false);
|
||||
// Delete all the event listeners.
|
||||
for (let i = 0, listen; listen = this.listeners_[i]; i++) {
|
||||
for (const listen of this.listeners_) {
|
||||
browserEvents.unbind(listen);
|
||||
}
|
||||
this.listeners_.length = 0;
|
||||
@@ -781,7 +781,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
blockInfo: toolbox.BlockInfo, gaps: number[], defaultGap: number) {
|
||||
let gap;
|
||||
if (blockInfo['gap']) {
|
||||
gap = parseInt(blockInfo['gap'].toString());
|
||||
gap = parseInt(String(blockInfo['gap']));
|
||||
} else if (blockInfo['blockxml']) {
|
||||
const xml = (typeof blockInfo['blockxml'] === 'string' ?
|
||||
utilsXml.textToDom(blockInfo['blockxml']) :
|
||||
@@ -806,7 +806,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
// <sep gap="36"></sep>
|
||||
// The default gap is 24, can be set larger or smaller.
|
||||
// This overwrites the gap attribute on the previous element.
|
||||
const newGap = parseInt(sepInfo['gap']!.toString());
|
||||
const newGap = parseInt(String(sepInfo['gap']));
|
||||
// Ignore gaps before the first block.
|
||||
if (!isNaN(newGap) && gaps.length > 0) {
|
||||
gaps[gaps.length - 1] = newGap;
|
||||
@@ -1056,13 +1056,13 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
*/
|
||||
protected moveRectToBlock_(rect: SVGElement, block: BlockSvg) {
|
||||
const blockHW = block.getHeightWidth();
|
||||
rect.setAttribute('width', blockHW.width.toString());
|
||||
rect.setAttribute('height', blockHW.height.toString());
|
||||
rect.setAttribute('width', String(blockHW.width));
|
||||
rect.setAttribute('height', String(blockHW.height));
|
||||
|
||||
const blockXY = block.getRelativeToSurfaceXY();
|
||||
rect.setAttribute('y', blockXY.y.toString());
|
||||
rect.setAttribute('y', String(blockXY.y));
|
||||
rect.setAttribute(
|
||||
'x', (this.RTL ? blockXY.x - blockHW.width : blockXY.x).toString());
|
||||
'x', String(this.RTL ? blockXY.x - blockHW.width : blockXY.x));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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_();
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
18
core/grid.ts
18
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}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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];
|
||||
|
||||
19
core/menu.ts
19
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;
|
||||
|
||||
|
||||
@@ -115,12 +115,12 @@ export function register<T>(
|
||||
AnyDuringMigration,
|
||||
opt_allowOverrides?: boolean): void {
|
||||
if (!(type instanceof Type) && typeof type !== 'string' ||
|
||||
String(type).trim() === '') {
|
||||
`${type}`.trim() === '') {
|
||||
throw Error(
|
||||
'Invalid type "' + type + '". The type must be a' +
|
||||
' non-empty string or a Blockly.registry.Type.');
|
||||
}
|
||||
type = String(type).toLowerCase();
|
||||
type = `${type}`.toLowerCase();
|
||||
|
||||
if (typeof name !== 'string' || name.trim() === '') {
|
||||
throw Error(
|
||||
@@ -178,7 +178,7 @@ function validate(type: string, registryItem: Function|AnyDuringMigration) {
|
||||
* @param name The plugin's name. (Ex. field_angle, geras)
|
||||
*/
|
||||
export function unregister<T>(type: string|Type<T>, name: string) {
|
||||
type = String(type).toLowerCase();
|
||||
type = `${type}`.toLowerCase();
|
||||
name = name.toLowerCase();
|
||||
const typeRegistry = typeMap[type];
|
||||
if (!typeRegistry || !typeRegistry[name]) {
|
||||
@@ -206,7 +206,7 @@ export function unregister<T>(type: string|Type<T>, name: string) {
|
||||
function getItem<T>(
|
||||
type: string|Type<T>, name: string, opt_throwIfMissing?: boolean):
|
||||
(new (...p1: AnyDuringMigration[]) => T)|null|AnyDuringMigration {
|
||||
type = String(type).toLowerCase();
|
||||
type = `${type}`.toLowerCase();
|
||||
name = name.toLowerCase();
|
||||
const typeRegistry = typeMap[type];
|
||||
if (!typeRegistry || !typeRegistry[name]) {
|
||||
@@ -233,7 +233,7 @@ function getItem<T>(
|
||||
* otherwise.
|
||||
*/
|
||||
export function hasItem<T>(type: string|Type<T>, name: string): boolean {
|
||||
type = String(type).toLowerCase();
|
||||
type = `${type}`.toLowerCase();
|
||||
name = name.toLowerCase();
|
||||
const typeRegistry = typeMap[type];
|
||||
if (!typeRegistry) {
|
||||
@@ -288,7 +288,7 @@ export function getObject<T>(
|
||||
export function getAllItems<T>(
|
||||
type: string|Type<T>, opt_cased?: boolean, opt_throwIfMissing?: boolean):
|
||||
{[key: string]: T|null|(new (...p1: AnyDuringMigration[]) => T)}|null {
|
||||
type = String(type).toLowerCase();
|
||||
type = `${type}`.toLowerCase();
|
||||
const typeRegistry = typeMap[type];
|
||||
if (!typeRegistry) {
|
||||
const msg = `Unable to find [${type}] in the registry.`;
|
||||
@@ -304,9 +304,7 @@ export function getAllItems<T>(
|
||||
}
|
||||
const nameRegistry = nameMap[type];
|
||||
const casedRegistry = Object.create(null);
|
||||
const keys = Object.keys(typeRegistry);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
for (const key of Object.keys(typeRegistry)) {
|
||||
casedRegistry[nameRegistry[key]] = typeRegistry[key];
|
||||
}
|
||||
return casedRegistry;
|
||||
@@ -325,8 +323,7 @@ export function getAllItems<T>(
|
||||
export function getClassFromOptions<T>(
|
||||
type: Type<T>, options: Options, opt_throwIfMissing?: boolean):
|
||||
(new (...p1: AnyDuringMigration[]) => T)|null {
|
||||
const typeName = type.toString();
|
||||
const plugin = options.plugins[typeName] || DEFAULT;
|
||||
const plugin = options.plugins[String(type)] || DEFAULT;
|
||||
|
||||
// If the user passed in a plugin class instead of a registered plugin name.
|
||||
if (typeof plugin === 'function') {
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -63,7 +63,7 @@ let canvasContext: CanvasRenderingContext2D|null = null;
|
||||
export function createSvgElement<T extends SVGElement>(
|
||||
name: string|Svg<T>, attrs: {[key: string]: string|number},
|
||||
opt_parent?: Element|null): T {
|
||||
const e = document.createElementNS(SVG_NS, String(name)) as T;
|
||||
const e = document.createElementNS(SVG_NS, `${name}`) as T;
|
||||
for (const key in attrs) {
|
||||
e.setAttribute(key, `${attrs[key]}`);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
core/xml.ts
10
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');
|
||||
|
||||
@@ -42,22 +42,11 @@ export class ZoomControls implements IPositionable {
|
||||
id = 'zoomControls';
|
||||
|
||||
/**
|
||||
* A handle to use to unbind the mouse down event handler for zoom reset
|
||||
* button. Opaque data returned from browserEvents.conditionalBind.
|
||||
* Array holding info needed to unbind events.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
*/
|
||||
private onZoomResetWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind the mouse down event handler for zoom in
|
||||
* button. Opaque data returned from browserEvents.conditionalBind.
|
||||
*/
|
||||
private onZoomInWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind the mouse down event handler for zoom out
|
||||
* button. Opaque data returned from browserEvents.conditionalBind.
|
||||
*/
|
||||
private onZoomOutWrapper: browserEvents.Data|null = null;
|
||||
private boundEvents: browserEvents.Data[] = [];
|
||||
|
||||
/** The zoom in svg <g> element. */
|
||||
private zoomInGroup: SVGGElement|null = null;
|
||||
@@ -144,15 +133,10 @@ export class ZoomControls implements IPositionable {
|
||||
if (this.svgGroup) {
|
||||
dom.removeNode(this.svgGroup);
|
||||
}
|
||||
if (this.onZoomResetWrapper) {
|
||||
browserEvents.unbind(this.onZoomResetWrapper);
|
||||
}
|
||||
if (this.onZoomInWrapper) {
|
||||
browserEvents.unbind(this.onZoomInWrapper);
|
||||
}
|
||||
if (this.onZoomOutWrapper) {
|
||||
browserEvents.unbind(this.onZoomOutWrapper);
|
||||
for (const event of this.boundEvents) {
|
||||
browserEvents.unbind(event);
|
||||
}
|
||||
this.boundEvents.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,8 +257,8 @@ export class ZoomControls implements IPositionable {
|
||||
this.workspace.options.pathToMedia + SPRITE.url);
|
||||
|
||||
// Attach listener.
|
||||
this.onZoomOutWrapper = browserEvents.conditionalBind(
|
||||
this.zoomOutGroup, 'pointerdown', null, this.zoom.bind(this, -1));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
this.zoomOutGroup, 'pointerdown', null, this.zoom.bind(this, -1)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,8 +303,8 @@ export class ZoomControls implements IPositionable {
|
||||
this.workspace.options.pathToMedia + SPRITE.url);
|
||||
|
||||
// Attach listener.
|
||||
this.onZoomInWrapper = browserEvents.conditionalBind(
|
||||
this.zoomInGroup, 'pointerdown', null, this.zoom.bind(this, 1));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
this.zoomInGroup, 'pointerdown', null, this.zoom.bind(this, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,8 +361,8 @@ export class ZoomControls implements IPositionable {
|
||||
this.workspace.options.pathToMedia + SPRITE.url);
|
||||
|
||||
// Attach event listeners.
|
||||
this.onZoomResetWrapper = browserEvents.conditionalBind(
|
||||
this.zoomResetGroup, 'pointerdown', null, this.resetZoom.bind(this));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
this.zoomResetGroup, 'pointerdown', null, this.resetZoom.bind(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 + '")');
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user