diff --git a/blocks/math.js b/blocks/math.js index d7950f94da..04db90ee10 100644 --- a/blocks/math.js +++ b/blocks/math.js @@ -38,15 +38,14 @@ Blockly.Blocks.math.HUE = Blockly.Colours.textField; Blockly.Blocks['math_number'] = { /** - * Block for numeric value. + * Block for generic numeric value. * @this Blockly.Block */ init: function() { this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL); this.setColour(Blockly.Blocks.math.HUE); this.appendDummyInput() - .appendField(new Blockly.FieldNumber('0', - Blockly.FieldTextInput.numberValidator, 20, -Infinity, Infinity), 'NUM'); + .appendField(new Blockly.FieldNumber('0', true, true), 'NUM'); this.setOutput(true, 'Number'); // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; @@ -61,30 +60,28 @@ Blockly.Blocks['math_number'] = { Blockly.Blocks['math_whole_number'] = { /** - * Block for whole number value, with min of 0 and max Infinity, precision 0. + * Block for whole number value, no negatives or decimals. * @this Blockly.Block */ init: function() { this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL); this.setColour(Blockly.Blocks.math.HUE); this.appendDummyInput() - .appendField(new Blockly.FieldNumber('0', - Blockly.FieldNumber.numberValidator, 0, 0, Infinity), 'NUM'); + .appendField(new Blockly.FieldNumber('0', false, false), 'NUM'); this.setOutput(true, 'Number'); } }; Blockly.Blocks['math_positive_number'] = { /** - * Block for positive number value, with min of 0 and max Infinity, precision 20. + * Block for positive number value, with decimal. * @this Blockly.Block */ init: function() { this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL); this.setColour(Blockly.Blocks.math.HUE); this.appendDummyInput() - .appendField(new Blockly.FieldNumber('0', - Blockly.FieldNumber.numberValidator, 20, 0, Infinity), 'NUM'); + .appendField(new Blockly.FieldNumber('0', true, false), 'NUM'); this.setOutput(true, 'Number'); } }; diff --git a/core/block_render_svg_vertical.js b/core/block_render_svg_vertical.js index 40b65ac330..61950e281c 100644 --- a/core/block_render_svg_vertical.js +++ b/core/block_render_svg_vertical.js @@ -324,7 +324,7 @@ Blockly.BlockSvg.FIELD_WIDTH_MIN_EDIT = 8 * Blockly.BlockSvg.GRID_UNIT; * Maximum width of user inputs during editing * @const */ -Blockly.BlockSvg.FIELD_WIDTH_MAX_EDIT = 52 * Blockly.BlockSvg.GRID_UNIT; +Blockly.BlockSvg.FIELD_WIDTH_MAX_EDIT = Infinity; /** * Maximum height of user inputs during editing diff --git a/core/field_number.js b/core/field_number.js index e1d62ee3dd..e82b1e2d1b 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -31,24 +31,41 @@ goog.require('goog.math'); goog.require('goog.userAgent'); +/** + * Return an appropriate restrictor, depending on whether this FieldNumber + * allows decimal or negative numbers. + * @param {boolean} decimalAllowed Whether number may have decimal/float component. + * @param {boolean} negativeAllowed Whether number may be negative. + * @return {!RegExp} Regular expression for this FieldNumber's restrictor. + */ +var getNumRestrictor = function(decimalAllowed, negativeAllowed) { + var pattern = "[\\d]|[e]"; // Always allow digits and e, from Scratch 2.0. + if (decimalAllowed) { + pattern += "|[\\.]"; + } + if (negativeAllowed) { + pattern += "|[-]"; + } + return new RegExp(pattern); +}; + /** * Class for an editable number field. * @param {string} text The initial content of the field. + * @param {boolean} decimalAllowed Whether number may have decimal/float component. + * @param {boolean} negativeAllowed Whether number may be negative. * @param {Function=} opt_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 the accepted text or null to abort * the change. - * @param {number} precision Precision of the decimal value (negative power of 10). - * @param {number} min Minimum value of the number. - * @param {number} max Maximum value of the number. * @extends {Blockly.FieldTextInput} * @constructor */ -Blockly.FieldNumber = function(text, opt_validator, precision, min, max) { - this.precision_ = precision; - this.min_ = min; - this.max_ = max; - Blockly.FieldNumber.superClass_.constructor.call(this, text, opt_validator); +Blockly.FieldNumber = function(text, decimalAllowed, negativeAllowed, opt_validator) { + this.decimalAllowed_ = decimalAllowed; + this.negativeAllowed_ = negativeAllowed; + var numRestrictor = getNumRestrictor(decimalAllowed, negativeAllowed); + Blockly.FieldNumber.superClass_.constructor.call(this, text, opt_validator, numRestrictor); }; goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); @@ -101,37 +118,6 @@ Blockly.FieldNumber.NUMPAD_DELETE_ICON = 'data:image/svg+xml;utf8,' + */ Blockly.FieldNumber.activeField_ = null; -/** - * Sets a new change handler for angle field. - * @param {Function} handler New change handler, or null. - */ -Blockly.FieldNumber.prototype.setValidator = function(handler) { - var wrappedHandler; - if (handler) { - // Wrap the user's change handler together with the number validator. - // This is copied entirely from FieldAngle. - wrappedHandler = function(value) { - var v1 = handler.call(this, value); - var v2; - if (v1 === null) { - v2 = v1; - } else { - if (v1 === undefined) { - v1 = value; - } - v2 = Blockly.FieldNumber.numberValidator.call(this, v1); - if (v2 === undefined) { - v2 = v1; - } - } - return v2 === value ? undefined : v2; - }; - } else { - wrappedHandler = Blockly.FieldNumber.numberValidator; - } - Blockly.FieldNumber.superClass_.setValidator.call(this, wrappedHandler); -}; - /** * Show the inline free-text editor on top of the text and the num-pad if appropriate. * @private @@ -170,7 +156,7 @@ Blockly.FieldNumber.prototype.showNumPad_ = function() { button.innerHTML = buttonText; Blockly.bindEvent_(button, 'mousedown', button, Blockly.FieldNumber.numPadButtonTouch_); - if (buttonText == '.' && this.precision_ == 0) { + if (buttonText == '.' && !this.decimalAllowed) { // Don't show the decimal point for inputs that must be round numbers button.setAttribute('style', 'visibility: hidden'); } @@ -267,27 +253,6 @@ Blockly.FieldNumber.prototype.onHide_ = function() { Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup'); }; -/** - * Ensure that only a number may be entered with the properties of this field. - * @param {string} text The user's text. - * @return {?string} A string representing a valid angle, or null if invalid. - */ -Blockly.FieldNumber.numberValidator = function(text) { - var n = Blockly.FieldTextInput.numberValidator(text); - if (n !== null) { - // string -> float - n = parseFloat(n); - // Keep within min and max - n = Math.min(Math.max(n, this.min_), this.max_); - // Update float precision (returns a string) - n = n.toFixed(this.precision_); - // Parse to a float and back to string to remove trailing decimals - n = parseFloat(n); - n = String(n); - } - return n; -}; - /** * Border radius for drawing this field, called when rendering the owning shadow block. * @return {Number} Border radius in px. diff --git a/core/field_textinput.js b/core/field_textinput.js index 6f0b416510..3dea4dfdcc 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -43,12 +43,16 @@ goog.require('goog.userAgent'); * 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 {RegExp=} opt_restrictor An optional regular expression to restrict + * typed text to. Text that doesn't match the restrictor will never show + * in the text field. * @extends {Blockly.Field} * @constructor */ -Blockly.FieldTextInput = function(text, opt_validator) { +Blockly.FieldTextInput = function(text, opt_validator, opt_restrictor) { Blockly.FieldTextInput.superClass_.constructor.call(this, text, opt_validator); + this.setRestrictor(opt_restrictor); }; goog.inherits(Blockly.FieldTextInput, Blockly.Field); @@ -109,6 +113,15 @@ Blockly.FieldTextInput.prototype.setSpellcheck = function(check) { this.spellcheck_ = check; }; +/** + * Set the restrictor regex for this text input. + * Text that doesn't match the restrictor will never show in the text field. + * @param {?RegExp} restrictor Regular expression to restrict text. + */ +Blockly.FieldTextInput.prototype.setRestrictor = function(restrictor) { + this.restrictor_ = restrictor; +}; + /** * Show the inline free-text editor on top of the text. * @param {boolean=} opt_quietInput True if editor should be created without @@ -208,6 +221,15 @@ Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) { * @private */ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(e) { + // Check if the key matches the restrictor. + if (e.type === 'keypress' && this.restrictor_) { + var charCode = String.fromCharCode(e.keyCode); + if (!this.restrictor_.test(charCode) && e.preventDefault) { + // Failed to pass restrictor. + e.preventDefault(); + return; + } + } var htmlInput = Blockly.FieldTextInput.htmlInput_; // Update source block. var text = htmlInput.value; diff --git a/tests/vertical_playground.html b/tests/vertical_playground.html index 2f77647957..e33847df62 100644 --- a/tests/vertical_playground.html +++ b/tests/vertical_playground.html @@ -318,15 +318,15 @@ - + 1 - - 4 + + 10 @@ -468,7 +468,7 @@ - + 1