From 139382e009f6e03498f60dc1d32d7fd94dab7658 Mon Sep 17 00:00:00 2001 From: Sam El-Husseini Date: Thu, 14 Nov 2019 16:13:22 -0800 Subject: [PATCH] Field textinput pixel perfect input (#3443) * Fix sizing of text input field in zelos, pixel perfect input in other browsers --- core/css.js | 3 +- core/field.js | 2 +- core/field_textinput.js | 47 +++++++---------------------- core/renderers/common/constants.js | 2 +- tests/mocha/field_angle_test.js | 4 +++ tests/mocha/field_number_test.js | 4 +++ tests/mocha/field_textinput_test.js | 5 +++ tests/mocha/procedures_test.js | 3 ++ 8 files changed, 31 insertions(+), 39 deletions(-) diff --git a/core/css.js b/core/css.js index 568adc530..f2af021bf 100644 --- a/core/css.js +++ b/core/css.js @@ -360,7 +360,7 @@ Blockly.Css.CONTENT = [ 'fill: #000;', '}', - '.blocklyEditableText:hover>rect {', + '.blocklyEditableText:not(.editing):hover>rect {', 'stroke: #fff;', 'stroke-width: 2;', '}', @@ -427,6 +427,7 @@ Blockly.Css.CONTENT = [ 'padding: 0;', 'width: 100%;', 'text-align: center;', + 'display: block;', '}', /* Edge and IE introduce a close icon when the input value is longer than a diff --git a/core/field.js b/core/field.js index 6b68324b7..903911458 100644 --- a/core/field.js +++ b/core/field.js @@ -917,7 +917,7 @@ Blockly.Field.prototype.setTooltip = function(newTip) { * to the SVG root of the field. When this element is * clicked on an editable field, the editor will open. * @return {!Element} Element to bind click handler to. - * @private + * @protected */ Blockly.Field.prototype.getClickTarget_ = function() { return this.clickTarget_ || this.getSvgRoot(); diff --git a/core/field_textinput.js b/core/field_textinput.js index 1cd030af5..e898351f1 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -248,14 +248,7 @@ Blockly.FieldTextInput.prototype.render_ = function() { // This logic is done in render_ rather than doValueInvalid_ or // doValueUpdate_ so that the code is more centralized. if (this.isBeingEdited_) { - if (this.sourceBlock_.RTL) { - // in RTL, we need to let the browser reflow before resizing - // in order to get the correct bounding box of the borderRect - // avoiding issue #2777. - setTimeout(this.resizeEditor_.bind(this), 0); - } else { - this.resizeEditor_(); - } + this.resizeEditor_(); var htmlInput = /** @type {!HTMLElement} */(this.htmlInput_); if (!this.isTextValid_) { Blockly.utils.dom.addClass(htmlInput, 'blocklyInvalidInput'); @@ -340,6 +333,8 @@ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { Blockly.FieldTextInput.prototype.widgetCreate_ = function() { var div = Blockly.WidgetDiv.DIV; + Blockly.utils.dom.addClass(this.getClickTarget_(), 'editing'); + var htmlInput = /** @type {HTMLInputElement} */ (document.createElement('input')); htmlInput.className = 'blocklyHtmlInput'; htmlInput.setAttribute('spellcheck', this.spellcheck_); @@ -366,13 +361,7 @@ Blockly.FieldTextInput.prototype.widgetCreate_ = function() { htmlInput.untypedDefaultValue_ = this.value_; htmlInput.oldValue_ = null; - - if (Blockly.utils.userAgent.GECKO) { - // In FF, ensure the browser reflows before resizing to avoid issue #2777. - setTimeout(this.resizeEditor_.bind(this), 0); - } else { - this.resizeEditor_(); - } + this.resizeEditor_(); this.bindInputEvents_(htmlInput); @@ -402,6 +391,8 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { style.height = 'auto'; style.fontSize = ''; this.htmlInput_ = null; + + Blockly.utils.dom.removeClass(this.getClickTarget_(), 'editing'); }; /** @@ -481,6 +472,7 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) { var value = this.getValueFromEditorText_(text); this.setValue(value); this.forceRerender(); + this.resizeEditor_(); Blockly.Events.setGroup(false); } }; @@ -529,17 +521,6 @@ Blockly.FieldTextInput.prototype.resizeEditor_ = function() { var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left; var xy = new Blockly.utils.Coordinate(x, bBox.top); - // Shift by a few pixels to line up exactly. - xy.y += 1; - if (Blockly.utils.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) { - // Firefox mis-reports the location of the border by a pixel - // once the WidgetDiv is moved into position. - xy.x -= 1; - xy.y -= 1; - } - if (Blockly.utils.userAgent.WEBKIT) { - xy.y -= 3; - } div.style.left = xy.x + 'px'; div.style.top = xy.y + 'px'; }; @@ -638,21 +619,15 @@ Blockly.FieldTextInput.prototype.getValueFromEditorText_ = function(text) { */ Blockly.FieldTextInput.prototype.getScaledBBox = function() { if (this.fullBlockClickTarget_) { - var heightWidth = this.sourceBlock_.getHeightWidth(); + var xy = this.getClickTarget_().getBoundingClientRect(); } else { - var heightWidth = this.borderRect_.getBBox(); + var xy = this.borderRect_.getBoundingClientRect(); } - - var scale = this.sourceBlock_.workspace.scale; - var xy = this.getAbsoluteXY_(); - var scaledHeight = heightWidth.height * scale; - var scaledWidth = heightWidth.width * scale; - return { top: xy.y, - bottom: xy.y + scaledHeight, + bottom: xy.y + xy.height, left: xy.x, - right: xy.x + scaledWidth + right: xy.x + xy.width }; }; diff --git a/core/renderers/common/constants.js b/core/renderers/common/constants.js index 9bd0f20a4..940a80564 100644 --- a/core/renderers/common/constants.js +++ b/core/renderers/common/constants.js @@ -214,7 +214,7 @@ Blockly.blockRendering.ConstantProvider = function() { * set to false. * @type {number} */ - this.FIELD_TEXT_BASELINE_Y = 13; + this.FIELD_TEXT_BASELINE_Y = Blockly.utils.userAgent.GECKO ? 12 : 13.09; /** * A field's text element's dominant baseline. diff --git a/tests/mocha/field_angle_test.js b/tests/mocha/field_angle_test.js index aeb1f8930..08141c428 100644 --- a/tests/mocha/field_angle_test.js +++ b/tests/mocha/field_angle_test.js @@ -201,10 +201,14 @@ suite('Angle Fields', function() { this.angleField.htmlInput_ = Object.create(null); this.angleField.htmlInput_.oldValue_ = '1'; this.angleField.htmlInput_.untypedDefaultValue_ = 1; + this.stub = sinon.stub(this.angleField, 'resizeEditor_'); }); teardown(function() { this.angleField.setValidator(null); this.angleField.htmlInput_ = null; + if (this.stub) { + this.stub.restore(); + } }); suite('Null Validator', function() { setup(function() { diff --git a/tests/mocha/field_number_test.js b/tests/mocha/field_number_test.js index 56d8aaa0d..081cfa737 100644 --- a/tests/mocha/field_number_test.js +++ b/tests/mocha/field_number_test.js @@ -329,10 +329,14 @@ suite('Number Fields', function() { this.numberField.htmlInput_ = Object.create(null); this.numberField.htmlInput_.oldValue_ = '1'; this.numberField.htmlInput_.untypedDefaultValue_ = 1; + this.stub = sinon.stub(this.numberField, 'resizeEditor_'); }); teardown(function() { this.numberField.setValidator(null); this.numberField.htmlInput_ = null; + if (this.stub) { + this.stub.restore(); + } }); suite('Null Validator', function() { setup(function() { diff --git a/tests/mocha/field_textinput_test.js b/tests/mocha/field_textinput_test.js index e1c9b0206..76df1748b 100644 --- a/tests/mocha/field_textinput_test.js +++ b/tests/mocha/field_textinput_test.js @@ -161,10 +161,14 @@ suite('Text Input Fields', function() { this.textInputField.htmlInput_ = Object.create(null); this.textInputField.htmlInput_.oldValue_ = 'value'; this.textInputField.htmlInput_.untypedDefaultValue_ = 'value'; + this.stub = sinon.stub(this.textInputField, 'resizeEditor_'); }); teardown(function() { this.textInputField.setValidator(null); Blockly.FieldTextInput.htmlInput_ = null; + if (this.stub) { + this.stub.restore(); + } }); suite('Null Validator', function() { setup(function() { @@ -233,6 +237,7 @@ suite('Text Input Fields', function() { FIELD_TEXT_FONTWEIGHT: 'normal', FIELD_TEXT_FONTFAMILY: 'sans-serif' }; + field.clickTarget_ = document.createElement('div'); Blockly.WidgetDiv.DIV = document.createElement('div'); this.stub = sinon.stub(field, 'resizeEditor_'); }; diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js index 99b20e6ba..a9453dc59 100644 --- a/tests/mocha/procedures_test.js +++ b/tests/mocha/procedures_test.js @@ -38,9 +38,12 @@ suite('Procedures', function() { context.defBlock.setFieldValue(startName, 'NAME'); context.callBlock = new Blockly.Block(this.workspace, context.callType); context.callBlock.setFieldValue(startName, 'NAME'); + context.stub = sinon.stub( + context.defBlock.getField('NAME'), 'resizeEditor_'); func.call(context); context.defBlock.dispose(); context.callBlock.dispose(); + context.stub.restore(); } }; });