mirror of
https://github.com/google/blockly.git
synced 2026-01-07 17:10:11 +01:00
304 lines
9.8 KiB
JavaScript
304 lines
9.8 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2019 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Text Area field.
|
|
* @author fraser@google.com (Neil Fraser)
|
|
* @author Andrew Mee
|
|
* @author acbart@udel.edu (Austin Cory Bart)
|
|
*/
|
|
'use strict';
|
|
|
|
goog.provide('Blockly.FieldMultilineInput');
|
|
|
|
goog.require('Blockly.Css');
|
|
goog.require('Blockly.DropDownDiv');
|
|
goog.require('Blockly.FieldTextInput');
|
|
goog.require('Blockly.utils');
|
|
goog.require('Blockly.utils.aria');
|
|
goog.require('Blockly.utils.Coordinate');
|
|
goog.require('Blockly.utils.dom');
|
|
goog.require('Blockly.utils.KeyCodes');
|
|
goog.require('Blockly.utils.object');
|
|
goog.require('Blockly.utils.userAgent');
|
|
|
|
|
|
/**
|
|
* Class for an editable text area field.
|
|
* @param {string=} opt_value The initial content of the field. Should cast to a
|
|
* string. Defaults to an empty string if null or undefined.
|
|
* @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 either the accepted text, a replacement
|
|
* text, or null to abort the change.
|
|
* @param {Object=} opt_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.
|
|
* @extends {Blockly.FieldTextInput}
|
|
* @constructor
|
|
*/
|
|
Blockly.FieldMultilineInput = function(opt_value, opt_validator, opt_config) {
|
|
// TODO: Once this field is documented the opt_config link should point to its
|
|
// creation documentation, rather than the text input field's.
|
|
if (opt_value == null) {
|
|
opt_value = '';
|
|
}
|
|
Blockly.FieldMultilineInput.superClass_.constructor.call(this,
|
|
opt_value, opt_validator, opt_config);
|
|
|
|
/**
|
|
* The SVG group element that will contain a text element for each text row
|
|
* when initialized.
|
|
* @type {SVGGElement}
|
|
*/
|
|
this.textGroup_ = null;
|
|
};
|
|
Blockly.utils.object.inherits(Blockly.FieldMultilineInput,
|
|
Blockly.FieldTextInput);
|
|
|
|
|
|
/**
|
|
* The default height of a single line of text.
|
|
* @type {number}
|
|
* @const
|
|
*/
|
|
Blockly.FieldMultilineInput.LINE_HEIGHT = 20;
|
|
|
|
/**
|
|
* Construct a FieldMultilineInput from a JSON arg object,
|
|
* dereferencing any string table references.
|
|
* @param {!Object} options A JSON object with options (text, and spellcheck).
|
|
* @return {!Blockly.FieldMultilineInput} The new field instance.
|
|
* @package
|
|
* @nocollapse
|
|
*/
|
|
Blockly.FieldMultilineInput.fromJson = function(options) {
|
|
var text = Blockly.utils.replaceMessageReferences(options['text']);
|
|
return new Blockly.FieldMultilineInput(text, undefined, options);
|
|
};
|
|
|
|
/**
|
|
* Create the block UI for this field.
|
|
* @package
|
|
*/
|
|
Blockly.FieldMultilineInput.prototype.initView = function() {
|
|
this.createBorderRect_();
|
|
this.textGroup_ = /** @type {!SVGGElement} **/
|
|
(Blockly.utils.dom.createSvgElement('g',
|
|
{
|
|
'class': 'blocklyEditableText',
|
|
}, this.fieldGroup_));
|
|
};
|
|
|
|
/**
|
|
* Get the text from this field as displayed on screen. May differ from getText
|
|
* due to ellipsis, and other formatting.
|
|
* @return {string} Currently displayed text.
|
|
* @private
|
|
*/
|
|
Blockly.FieldMultilineInput.prototype.getDisplayText_ = function() {
|
|
var value = this.value_;
|
|
if (!value) {
|
|
// Prevent the field from disappearing if empty.
|
|
return Blockly.Field.NBSP;
|
|
}
|
|
var lines = value.split('\n');
|
|
value = '';
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var text = lines[i];
|
|
if (text.length > this.maxDisplayLength) {
|
|
// Truncate displayed string and add an ellipsis ('...').
|
|
text = text.substring(0, this.maxDisplayLength - 4) + '...';
|
|
}
|
|
// Replace whitespace with non-breaking spaces so the text doesn't collapse.
|
|
text = text.replace(/\s/g, Blockly.Field.NBSP);
|
|
|
|
value += text;
|
|
if (i !== lines.length - 1) {
|
|
value += '\n';
|
|
}
|
|
}
|
|
if (this.sourceBlock_.RTL) {
|
|
// The SVG is LTR, force value to be RTL.
|
|
value += '\u200F';
|
|
}
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* Updates the text of the textElement.
|
|
* @protected
|
|
*/
|
|
Blockly.FieldMultilineInput.prototype.render_ = function() {
|
|
// Remove all text group children.
|
|
var currentChild;
|
|
while ((currentChild = this.textGroup_.firstChild)) {
|
|
this.textGroup_.removeChild(currentChild);
|
|
}
|
|
|
|
// Add in text elements into the group.
|
|
var lines = this.getDisplayText_().split('\n');
|
|
var y = 0;
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var span = Blockly.utils.dom.createSvgElement('text', {
|
|
'class': 'blocklyText blocklyMultilineText',
|
|
x: this.constants_.FIELD_BORDER_RECT_X_PADDING,
|
|
y: y + this.constants_.FIELD_BORDER_RECT_Y_PADDING,
|
|
dy: Blockly.FieldMultilineInput.LINE_HEIGHT / 2
|
|
}, this.textGroup_);
|
|
span.appendChild(document.createTextNode(lines[i]));
|
|
y += Blockly.FieldMultilineInput.LINE_HEIGHT;
|
|
}
|
|
|
|
this.updateSize_();
|
|
|
|
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_();
|
|
}
|
|
var htmlInput = /** @type {!HTMLElement} */(this.htmlInput_);
|
|
if (!this.isTextValid_) {
|
|
Blockly.utils.dom.addClass(htmlInput, 'blocklyInvalidInput');
|
|
Blockly.utils.aria.setState(htmlInput,
|
|
Blockly.utils.aria.State.INVALID, true);
|
|
} else {
|
|
Blockly.utils.dom.removeClass(htmlInput, 'blocklyInvalidInput');
|
|
Blockly.utils.aria.setState(htmlInput,
|
|
Blockly.utils.aria.State.INVALID, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Updates the size of the field based on the text.
|
|
* @protected
|
|
*/
|
|
Blockly.FieldMultilineInput.prototype.updateSize_ = function() {
|
|
var nodes = this.textGroup_.childNodes;
|
|
var totalWidth = 0;
|
|
var totalHeight = 0;
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
var tspan = /** @type {!Element} */ (nodes[i]);
|
|
var textWidth = Blockly.utils.dom.getTextWidth(tspan);
|
|
if (textWidth > totalWidth) {
|
|
totalWidth = textWidth;
|
|
}
|
|
totalHeight += Blockly.FieldMultilineInput.LINE_HEIGHT;
|
|
}
|
|
if (this.borderRect_) {
|
|
totalWidth += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
|
|
this.borderRect_.setAttribute('width', totalWidth);
|
|
this.borderRect_.setAttribute('height', totalHeight);
|
|
}
|
|
this.size_.width = totalWidth;
|
|
this.size_.height = totalHeight;
|
|
};
|
|
|
|
/**
|
|
* Resize the editor to fit the text.
|
|
* @protected
|
|
*/
|
|
Blockly.FieldMultilineInput.prototype.resizeEditor_ = function() {
|
|
var div = Blockly.WidgetDiv.DIV;
|
|
var bBox = this.getScaledBBox();
|
|
div.style.width = bBox.right - bBox.left + 'px';
|
|
div.style.height = bBox.bottom - bBox.top + 'px';
|
|
|
|
// In RTL mode block fields and LTR input fields the left edge moves,
|
|
// whereas the right edge is fixed. Reposition the editor.
|
|
var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
|
|
var xy = new Blockly.utils.Coordinate(x, bBox.top);
|
|
|
|
div.style.left = xy.x + 'px';
|
|
div.style.top = xy.y + 'px';
|
|
};
|
|
|
|
/**
|
|
* Create the text input editor widget.
|
|
* @return {!HTMLTextAreaElement} The newly created text input editor.
|
|
* @protected
|
|
*/
|
|
Blockly.FieldMultilineInput.prototype.widgetCreate_ = function() {
|
|
var div = Blockly.WidgetDiv.DIV;
|
|
var scale = this.workspace_.scale;
|
|
|
|
var htmlInput = /** @type {HTMLTextAreaElement} */ (document.createElement('textarea'));
|
|
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
|
|
htmlInput.setAttribute('spellcheck', this.spellcheck_);
|
|
var fontSize = (this.constants_.FIELD_TEXT_FONTSIZE * scale) + 'pt';
|
|
div.style.fontSize = fontSize;
|
|
htmlInput.style.fontSize = fontSize;
|
|
var borderRadius = (Blockly.FieldTextInput.BORDERRADIUS * scale) + 'px';
|
|
htmlInput.style.borderRadius = borderRadius;
|
|
var padding = this.constants_.FIELD_BORDER_RECT_X_PADDING * scale;
|
|
htmlInput.style.paddingLeft = padding + 'px';
|
|
htmlInput.style.width = 'calc(100% - ' + padding + 'px)';
|
|
htmlInput.style.lineHeight =
|
|
(Blockly.FieldMultilineInput.LINE_HEIGHT * scale) + 'px';
|
|
|
|
div.appendChild(htmlInput);
|
|
|
|
htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
|
|
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.bindInputEvents_(htmlInput);
|
|
|
|
return htmlInput;
|
|
};
|
|
|
|
/**
|
|
* Handle key down to the editor. Override the text input definition of this
|
|
* so as to not close the editor when enter is typed in.
|
|
* @param {!Event} e Keyboard event.
|
|
* @protected
|
|
*/
|
|
Blockly.FieldMultilineInput.prototype.onHtmlInputKeyDown_ = function(e) {
|
|
if (e.keyCode !== Blockly.utils.KeyCodes.ENTER) {
|
|
Blockly.FieldMultilineInput.superClass_.onHtmlInputKeyDown_.call(this, e);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* CSS for multiline field. See css.js for use.
|
|
*/
|
|
Blockly.Css.register([
|
|
/* eslint-disable indent */
|
|
'.blocklyHtmlTextAreaInput {',
|
|
'font-family: monospace;',
|
|
'resize: none;',
|
|
'overflow: hidden;',
|
|
'height: 100%;',
|
|
'text-align: left;',
|
|
'}'
|
|
/* eslint-enable indent */
|
|
]);
|
|
|
|
|
|
Blockly.fieldRegistry.register('field_multilinetext', Blockly.FieldMultilineInput);
|