Skip to content
This repository was archived by the owner on Jan 27, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion core/ui/fields/field_textinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ Blockly.FieldTextInput.prototype.showEditor_ = function() {
var div = Blockly.WidgetDiv.DIV;
// Create the input.
var htmlInput = goog.dom.createDom('input', 'blocklyHtmlInput');
if (this.changeHandler_ === Blockly.FieldTextInput.numberValidator) {
if (
(this.changeHandler_ === Blockly.FieldTextInput.numberValidator) ||
(this.changeHandler_ && this.changeHandler_.validatorType === 'clampedNumberValidator'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love that we're introducing inconsistency with the way you identify a changeHandler; can we create an issue to track improving this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 That's a good idea. I messed around with identifying these by function name or prototype instead of by identity, but decided in the end it'd be too clever by half and I should just add a property. I'd love to revisit this and make it make sense.

#158

{
htmlInput.setAttribute('type', 'number');
htmlInput.setAttribute('step', 'any');
} else if (this.changeHandler_ === Blockly.FieldTextInput.nonnegativeIntegerValidator) {
Expand Down Expand Up @@ -370,3 +373,30 @@ Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
}
return n;
};

/**
* Create a number validator that limits the number to a configured range.
* @param {?number} min
* @param {?number} max
* @returns {function(*=): string}
*/
Blockly.FieldTextInput.clampedNumberValidator = function(min, max) {
/**
* Ensure that only a number in the configured range may be entered.
* @param {string} text The user's text.
* @returns {?string} A string representing a valid number in the range, or null if invalid.
*/
var validator = function clampedNumberValidator(text) {
var n = Blockly.FieldTextInput.numberValidator(text);
if (!isNaN(parseFloat(min))) {
n = Math.max(min, n);
}
if (!isNaN(parseFloat(max))) {
n = Math.min(max, n);
}
n = String(n);
return n;
};
validator.validatorType = 'clampedNumberValidator';
return validator;
}
47 changes: 47 additions & 0 deletions tests/fields_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,50 @@ function test_dropdown_options() {
dropdown.setConfig('5-7,10');
assert(goog.array.equals(["5", "5", "6", "6", "7", "7", "10", "10"], goog.array.flatten(dropdown.getOptions())));
}

function test_clampedNumberValidator() {
var withoutBounds = Blockly.FieldTextInput.clampedNumberValidator();
var withLowerBound = Blockly.FieldTextInput.clampedNumberValidator(0.5);
var withNegativeLowerBound = Blockly.FieldTextInput.clampedNumberValidator(-0.5);
var withUpperBound = Blockly.FieldTextInput.clampedNumberValidator(undefined, 4.5);
var withBothBounds = Blockly.FieldTextInput.clampedNumberValidator(0.5, 4.5);

[withoutBounds, withLowerBound, withNegativeLowerBound, withUpperBound, withBothBounds].forEach(function (validator) {
// All created validators have validatorType 'clampedNumberValidator' which allows us to customize the
// generated input type appropriately.
assert(validator.validatorType === 'clampedNumberValidator');
// Values in range are returned appropriately
assert(validator('3') === '3');
});

[withLowerBound, withBothBounds].forEach(function (validator) {
// Lower bound is inclusive
assert(validator('0.5') === '0.5');
// Given a value smaller than the lower bound, you get the lower bound
assert(validator('0.2') === '0.5');
});

[withoutBounds, withUpperBound].forEach(function (validator) {
// Without a lower bound, you get the value back
assert(validator('0.2') === '0.2');
});

[withUpperBound, withBothBounds].forEach(function (validator) {
// Upper bound is inclusive
assert(validator('4.5') === '4.5');
// Given a value larger than the upper bound, you get the upper bound
assert(validator('4.7') === '4.5');
});

[withoutBounds, withLowerBound].forEach(function (validator) {
// Without an upper bound, you get the value back
assert(validator('4.7') === '4.7');
});

// Given a non-number value and the lower bound is positive, you get the lower bound
assert(withLowerBound('abc') === '0.5');
// Given a non-number value and the lower bound is negative, you get zero
assert(withNegativeLowerBound('abc') === '0');
// Given a non-number value and the lower bound is not set, you get zero
assert(withUpperBound('abc') === '0');
}