blockly 7.20211209.4 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +5 -4
  3. package/blockly_compressed.js +4 -3
  4. package/blockly_compressed.js.map +1 -1
  5. package/blocks/blocks.js +47 -0
  6. package/blocks/colour.js +13 -3
  7. package/blocks/lists.js +22 -13
  8. package/blocks/logic.js +13 -3
  9. package/blocks/loops.js +24 -11
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +45 -32
  12. package/blocks/text.js +22 -13
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +1 -1
  16. package/blocks_compressed.js.map +1 -1
  17. package/core/block.js +1869 -1814
  18. package/core/block_drag_surface.js +201 -200
  19. package/core/block_dragger.js +377 -373
  20. package/core/block_svg.js +1593 -1479
  21. package/core/blockly.js +8 -22
  22. package/core/blocks.js +9 -2
  23. package/core/browser_events.js +22 -5
  24. package/core/bubble.js +841 -797
  25. package/core/bubble_dragger.js +213 -206
  26. package/core/bump_objects.js +2 -2
  27. package/core/clipboard.js +9 -9
  28. package/core/comment.js +353 -332
  29. package/core/common.js +46 -17
  30. package/core/component_manager.js +181 -174
  31. package/core/config.js +87 -0
  32. package/core/connection.js +595 -584
  33. package/core/connection_checker.js +242 -244
  34. package/core/connection_db.js +235 -230
  35. package/core/contextmenu.js +9 -6
  36. package/core/contextmenu_items.js +1 -2
  37. package/core/contextmenu_registry.js +93 -89
  38. package/core/css.js +474 -474
  39. package/core/delete_area.js +45 -42
  40. package/core/drag_target.js +57 -56
  41. package/core/dropdowndiv.js +153 -163
  42. package/core/events/events.js +2 -2
  43. package/core/events/events_abstract.js +89 -77
  44. package/core/events/events_block_base.js +37 -36
  45. package/core/events/events_block_change.js +130 -124
  46. package/core/events/events_block_create.js +73 -71
  47. package/core/events/events_block_delete.js +84 -82
  48. package/core/events/events_block_drag.js +50 -49
  49. package/core/events/events_block_move.js +147 -140
  50. package/core/events/events_bubble_open.js +51 -50
  51. package/core/events/events_click.js +48 -44
  52. package/core/events/events_comment_base.js +72 -69
  53. package/core/events/events_comment_change.js +63 -61
  54. package/core/events/events_comment_create.js +44 -42
  55. package/core/events/events_comment_delete.js +42 -40
  56. package/core/events/events_comment_move.js +106 -104
  57. package/core/events/events_marker_move.js +65 -64
  58. package/core/events/events_selected.js +46 -45
  59. package/core/events/events_theme_change.js +36 -35
  60. package/core/events/events_toolbox_item_select.js +46 -45
  61. package/core/events/events_trashcan_open.js +37 -36
  62. package/core/events/events_ui.js +47 -46
  63. package/core/events/events_ui_base.js +30 -29
  64. package/core/events/events_var_base.js +37 -36
  65. package/core/events/events_var_create.js +50 -48
  66. package/core/events/events_var_delete.js +50 -48
  67. package/core/events/events_var_rename.js +51 -49
  68. package/core/events/events_viewport.js +66 -65
  69. package/core/events/utils.js +29 -14
  70. package/core/events/workspace_events.js +49 -55
  71. package/core/extensions.js +4 -3
  72. package/core/field.js +1061 -997
  73. package/core/field_angle.js +462 -442
  74. package/core/field_checkbox.js +194 -182
  75. package/core/field_colour.js +519 -505
  76. package/core/field_dropdown.js +617 -598
  77. package/core/field_image.js +229 -220
  78. package/core/field_label.js +102 -91
  79. package/core/field_label_serializable.js +42 -41
  80. package/core/field_multilineinput.js +372 -358
  81. package/core/field_number.js +272 -253
  82. package/core/field_textinput.js +499 -467
  83. package/core/field_variable.js +458 -420
  84. package/core/flyout_base.js +1005 -952
  85. package/core/flyout_button.js +277 -260
  86. package/core/flyout_horizontal.js +304 -302
  87. package/core/flyout_metrics_manager.js +64 -64
  88. package/core/flyout_vertical.js +306 -300
  89. package/core/generator.js +459 -446
  90. package/core/gesture.js +829 -813
  91. package/core/grid.js +166 -163
  92. package/core/icon.js +168 -159
  93. package/core/inject.js +7 -5
  94. package/core/input.js +257 -248
  95. package/core/insertion_marker_manager.js +655 -624
  96. package/core/internal_constants.js +0 -129
  97. package/core/keyboard_nav/ast_node.js +605 -596
  98. package/core/keyboard_nav/basic_cursor.js +166 -165
  99. package/core/keyboard_nav/cursor.js +99 -97
  100. package/core/keyboard_nav/marker.js +83 -79
  101. package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
  102. package/core/marker_manager.js +153 -141
  103. package/core/menu.js +377 -372
  104. package/core/menuitem.js +223 -217
  105. package/core/metrics_manager.js +403 -390
  106. package/core/mutator.js +468 -437
  107. package/core/names.js +229 -188
  108. package/core/options.js +290 -284
  109. package/core/procedures.js +29 -17
  110. package/core/registry.js +19 -16
  111. package/core/rendered_connection.js +482 -463
  112. package/core/renderers/common/block_rendering.js +9 -3
  113. package/core/renderers/common/constants.js +1119 -1112
  114. package/core/renderers/common/debug.js +14 -0
  115. package/core/renderers/common/debugger.js +338 -316
  116. package/core/renderers/common/drawer.js +380 -370
  117. package/core/renderers/common/i_path_object.js +2 -2
  118. package/core/renderers/common/info.js +626 -618
  119. package/core/renderers/common/marker_svg.js +579 -541
  120. package/core/renderers/common/path_object.js +203 -200
  121. package/core/renderers/common/renderer.js +220 -218
  122. package/core/renderers/geras/constants.js +36 -36
  123. package/core/renderers/geras/drawer.js +155 -147
  124. package/core/renderers/geras/highlight_constants.js +244 -238
  125. package/core/renderers/geras/highlighter.js +231 -179
  126. package/core/renderers/geras/info.js +392 -369
  127. package/core/renderers/geras/measurables/inline_input.js +25 -19
  128. package/core/renderers/geras/measurables/statement_input.js +23 -17
  129. package/core/renderers/geras/path_object.js +106 -121
  130. package/core/renderers/geras/renderer.js +96 -98
  131. package/core/renderers/measurables/base.js +30 -18
  132. package/core/renderers/measurables/bottom_row.js +83 -80
  133. package/core/renderers/measurables/connection.js +22 -15
  134. package/core/renderers/measurables/external_value_input.js +35 -22
  135. package/core/renderers/measurables/field.js +35 -20
  136. package/core/renderers/measurables/hat.js +18 -13
  137. package/core/renderers/measurables/icon.js +24 -17
  138. package/core/renderers/measurables/in_row_spacer.js +15 -13
  139. package/core/renderers/measurables/inline_input.js +43 -33
  140. package/core/renderers/measurables/input_connection.js +41 -28
  141. package/core/renderers/measurables/input_row.js +50 -44
  142. package/core/renderers/measurables/jagged_edge.js +14 -12
  143. package/core/renderers/measurables/next_connection.js +16 -14
  144. package/core/renderers/measurables/output_connection.js +26 -20
  145. package/core/renderers/measurables/previous_connection.js +16 -15
  146. package/core/renderers/measurables/round_corner.js +20 -18
  147. package/core/renderers/measurables/row.js +184 -168
  148. package/core/renderers/measurables/spacer_row.js +38 -23
  149. package/core/renderers/measurables/square_corner.js +18 -16
  150. package/core/renderers/measurables/statement_input.js +23 -20
  151. package/core/renderers/measurables/top_row.js +88 -85
  152. package/core/renderers/minimalist/constants.js +8 -7
  153. package/core/renderers/minimalist/drawer.js +11 -10
  154. package/core/renderers/minimalist/info.js +18 -18
  155. package/core/renderers/minimalist/renderer.js +40 -39
  156. package/core/renderers/thrasos/info.js +258 -248
  157. package/core/renderers/thrasos/renderer.js +20 -20
  158. package/core/renderers/zelos/constants.js +898 -873
  159. package/core/renderers/zelos/drawer.js +186 -169
  160. package/core/renderers/zelos/info.js +502 -479
  161. package/core/renderers/zelos/marker_svg.js +129 -115
  162. package/core/renderers/zelos/measurables/bottom_row.js +31 -30
  163. package/core/renderers/zelos/measurables/inputs.js +22 -21
  164. package/core/renderers/zelos/measurables/row_elements.js +14 -13
  165. package/core/renderers/zelos/measurables/top_row.js +34 -33
  166. package/core/renderers/zelos/path_object.js +181 -180
  167. package/core/renderers/zelos/renderer.js +91 -92
  168. package/core/scrollbar.js +759 -713
  169. package/core/scrollbar_pair.js +250 -245
  170. package/core/serialization/blocks.js +26 -10
  171. package/core/serialization/workspaces.js +3 -2
  172. package/core/shortcut_registry.js +286 -277
  173. package/core/sprites.js +31 -0
  174. package/core/theme.js +135 -141
  175. package/core/theme_manager.js +147 -143
  176. package/core/toolbox/category.js +602 -576
  177. package/core/toolbox/collapsible_category.js +226 -227
  178. package/core/toolbox/separator.js +70 -61
  179. package/core/toolbox/toolbox.js +934 -927
  180. package/core/toolbox/toolbox_item.js +115 -99
  181. package/core/tooltip.js +108 -35
  182. package/core/touch.js +8 -3
  183. package/core/touch_gesture.js +254 -251
  184. package/core/trashcan.js +606 -595
  185. package/core/utils/coordinate.js +97 -95
  186. package/core/utils/dom.js +2 -2
  187. package/core/utils/global.js +2 -0
  188. package/core/utils/rect.js +41 -37
  189. package/core/utils/sentinel.js +25 -0
  190. package/core/utils/size.js +30 -27
  191. package/core/utils/svg.js +18 -16
  192. package/core/variable_map.js +325 -341
  193. package/core/variable_model.js +55 -54
  194. package/core/variables.js +9 -2
  195. package/core/variables_dynamic.js +3 -1
  196. package/core/warning.js +126 -120
  197. package/core/widgetdiv.js +4 -4
  198. package/core/workspace.js +685 -664
  199. package/core/workspace_audio.js +124 -118
  200. package/core/workspace_comment.js +308 -298
  201. package/core/workspace_comment_svg.js +1029 -951
  202. package/core/workspace_drag_surface_svg.js +147 -140
  203. package/core/workspace_dragger.js +70 -71
  204. package/core/workspace_svg.js +2322 -2297
  205. package/core/xml.js +30 -20
  206. package/core/zoom_controls.js +431 -439
  207. package/generators/dart/colour.js +56 -64
  208. package/generators/dart/lists.js +61 -50
  209. package/generators/dart/math.js +160 -148
  210. package/generators/dart/text.js +83 -61
  211. package/generators/javascript/colour.js +37 -34
  212. package/generators/javascript/lists.js +50 -43
  213. package/generators/javascript/math.js +123 -139
  214. package/generators/javascript/text.js +67 -81
  215. package/generators/lua/colour.js +25 -23
  216. package/generators/lua/lists.js +97 -69
  217. package/generators/lua/logic.js +1 -2
  218. package/generators/lua/math.js +182 -144
  219. package/generators/lua/text.js +116 -99
  220. package/generators/php/colour.js +38 -32
  221. package/generators/php/lists.js +109 -89
  222. package/generators/php/math.js +90 -81
  223. package/generators/php/text.js +63 -61
  224. package/generators/python/colour.js +18 -18
  225. package/generators/python/lists.js +38 -30
  226. package/generators/python/loops.js +12 -8
  227. package/generators/python/math.js +104 -106
  228. package/generators/python/text.js +34 -30
  229. package/msg/smn.js +436 -0
  230. package/package.json +7 -6
  231. package/blocks/all.js +0 -23
@@ -20,428 +20,442 @@ const WidgetDiv = goog.require('Blockly.WidgetDiv');
20
20
  const aria = goog.require('Blockly.utils.aria');
21
21
  const dom = goog.require('Blockly.utils.dom');
22
22
  const fieldRegistry = goog.require('Blockly.fieldRegistry');
23
- const object = goog.require('Blockly.utils.object');
24
23
  const parsing = goog.require('Blockly.utils.parsing');
25
24
  const userAgent = goog.require('Blockly.utils.userAgent');
26
25
  const {FieldTextInput} = goog.require('Blockly.FieldTextInput');
27
26
  const {Field} = goog.require('Blockly.Field');
28
27
  const {KeyCodes} = goog.require('Blockly.utils.KeyCodes');
28
+ /* eslint-disable-next-line no-unused-vars */
29
+ const {Sentinel} = goog.requireType('Blockly.utils.Sentinel');
29
30
  const {Svg} = goog.require('Blockly.utils.Svg');
30
31
 
31
32
 
32
33
  /**
33
34
  * Class for an editable text area field.
34
- * @param {string=} opt_value The initial content of the field. Should cast to a
35
- * string. Defaults to an empty string if null or undefined.
36
- * @param {Function=} opt_validator An optional function that is called
37
- * to validate any constraints on what the user entered. Takes the new
38
- * text as an argument and returns either the accepted text, a replacement
39
- * text, or null to abort the change.
40
- * @param {Object=} opt_config A map of options used to configure the field.
41
- * See the [field creation documentation]{@link
42
- * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation}
43
- * for a list of properties this parameter supports.
44
35
  * @extends {FieldTextInput}
45
- * @constructor
46
36
  * @alias Blockly.FieldMultilineInput
47
37
  */
48
- const FieldMultilineInput = function(opt_value, opt_validator, opt_config) {
49
- FieldMultilineInput.superClass_.constructor.call(
50
- this, opt_value, opt_validator, opt_config);
51
-
38
+ class FieldMultilineInput extends FieldTextInput {
52
39
  /**
53
- * The SVG group element that will contain a text element for each text row
54
- * when initialized.
55
- * @type {SVGGElement}
40
+ * @param {(string|!Sentinel)=} opt_value The initial content of the
41
+ * field. Should cast to a string. Defaults to an empty string if null or
42
+ * undefined.
43
+ * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
44
+ * subclasses that want to handle configuration and setting the field
45
+ * value after their own constructors have run).
46
+ * @param {Function=} opt_validator An optional function that is called
47
+ * to validate any constraints on what the user entered. Takes the new
48
+ * text as an argument and returns either the accepted text, a replacement
49
+ * text, or null to abort the change.
50
+ * @param {Object=} opt_config A map of options used to configure the field.
51
+ * See the [field creation documentation]{@link
52
+ * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation}
53
+ * for a list of properties this parameter supports.
56
54
  */
57
- this.textGroup_ = null;
55
+ constructor(opt_value, opt_validator, opt_config) {
56
+ super(Field.SKIP_SETUP);
57
+
58
+ /**
59
+ * The SVG group element that will contain a text element for each text row
60
+ * when initialized.
61
+ * @type {SVGGElement}
62
+ */
63
+ this.textGroup_ = null;
64
+
65
+ /**
66
+ * Defines the maximum number of lines of field.
67
+ * If exceeded, scrolling functionality is enabled.
68
+ * @type {number}
69
+ * @protected
70
+ */
71
+ this.maxLines_ = Infinity;
72
+
73
+ /**
74
+ * Whether Y overflow is currently occurring.
75
+ * @type {boolean}
76
+ * @protected
77
+ */
78
+ this.isOverflowedY_ = false;
79
+
80
+ if (opt_value === Field.SKIP_SETUP) return;
81
+ if (opt_config) this.configure_(opt_config);
82
+ this.setValue(opt_value);
83
+ if (opt_validator) this.setValidator(opt_validator);
84
+ }
58
85
 
59
86
  /**
60
- * Defines the maximum number of lines of field.
61
- * If exceeded, scrolling functionality is enabled.
62
- * @type {number}
63
- * @protected
87
+ * @override
64
88
  */
65
- this.maxLines_ = Infinity;
89
+ configure_(config) {
90
+ super.configure_(config);
91
+ config.maxLines && this.setMaxLines(config.maxLines);
92
+ }
66
93
 
67
94
  /**
68
- * Whether Y overflow is currently occurring.
69
- * @type {boolean}
70
- * @protected
95
+ * Serializes this field's value to XML. Should only be called by Blockly.Xml.
96
+ * @param {!Element} fieldElement The element to populate with info about the
97
+ * field's state.
98
+ * @return {!Element} The element containing info about the field's state.
99
+ * @package
71
100
  */
72
- this.isOverflowedY_ = false;
73
- };
74
- object.inherits(FieldMultilineInput, FieldTextInput);
75
-
76
- /**
77
- * @override
78
- */
79
- FieldMultilineInput.prototype.configure_ = function(config) {
80
- FieldMultilineInput.superClass_.configure_.call(this, config);
81
- config.maxLines && this.setMaxLines(config.maxLines);
82
- };
83
-
84
- /**
85
- * Construct a FieldMultilineInput from a JSON arg object,
86
- * dereferencing any string table references.
87
- * @param {!Object} options A JSON object with options (text, and spellcheck).
88
- * @return {!FieldMultilineInput} The new field instance.
89
- * @package
90
- * @nocollapse
91
- */
92
- FieldMultilineInput.fromJson = function(options) {
93
- const text = parsing.replaceMessageReferences(options['text']);
94
- // `this` might be a subclass of FieldMultilineInput if that class doesn't
95
- // override the static fromJson method.
96
- return new this(text, undefined, options);
97
- };
98
-
99
- /**
100
- * Serializes this field's value to XML. Should only be called by Blockly.Xml.
101
- * @param {!Element} fieldElement The element to populate with info about the
102
- * field's state.
103
- * @return {!Element} The element containing info about the field's state.
104
- * @package
105
- */
106
- FieldMultilineInput.prototype.toXml = function(fieldElement) {
107
- // Replace '\n' characters with HTML-escaped equivalent '&#10'. This is
108
- // needed so the plain-text representation of the XML produced by
109
- // `Blockly.Xml.domToText` will appear on a single line (this is a limitation
110
- // of the plain-text format).
111
- fieldElement.textContent = this.getValue().replace(/\n/g, '
');
112
- return fieldElement;
113
- };
114
-
115
- /**
116
- * Sets the field's value based on the given XML element. Should only be
117
- * called by Blockly.Xml.
118
- * @param {!Element} fieldElement The element containing info about the
119
- * field's state.
120
- * @package
121
- */
122
- FieldMultilineInput.prototype.fromXml = function(fieldElement) {
123
- this.setValue(fieldElement.textContent.replace(/
/g, '\n'));
124
- };
125
-
126
- /**
127
- * Saves this field's value.
128
- * @return {*} The state of this field.
129
- * @package
130
- */
131
- FieldMultilineInput.prototype.saveState = function() {
132
- const legacyState = this.saveLegacyState(FieldMultilineInput);
133
- if (legacyState !== null) {
134
- return legacyState;
101
+ toXml(fieldElement) {
102
+ // Replace '\n' characters with HTML-escaped equivalent '&#10'. This is
103
+ // needed so the plain-text representation of the XML produced by
104
+ // `Blockly.Xml.domToText` will appear on a single line (this is a
105
+ // limitation of the plain-text format).
106
+ fieldElement.textContent = this.getValue().replace(/\n/g, '
');
107
+ return fieldElement;
135
108
  }
136
- return this.getValue();
137
- };
138
109
 
139
- /**
140
- * Sets the field's value based on the given state.
141
- * @param {*} state The state of the variable to assign to this variable field.
142
- * @override
143
- * @package
144
- */
145
- FieldMultilineInput.prototype.loadState = function(state) {
146
- if (this.loadLegacyState(Field, state)) {
147
- return;
110
+ /**
111
+ * Sets the field's value based on the given XML element. Should only be
112
+ * called by Blockly.Xml.
113
+ * @param {!Element} fieldElement The element containing info about the
114
+ * field's state.
115
+ * @package
116
+ */
117
+ fromXml(fieldElement) {
118
+ this.setValue(fieldElement.textContent.replace(/
/g, '\n'));
148
119
  }
149
- this.setValue(state);
150
- };
151
120
 
152
- /**
153
- * Create the block UI for this field.
154
- * @package
155
- */
156
- FieldMultilineInput.prototype.initView = function() {
157
- this.createBorderRect_();
158
- this.textGroup_ = dom.createSvgElement(
159
- Svg.G, {
160
- 'class': 'blocklyEditableText',
161
- },
162
- this.fieldGroup_);
163
- };
164
-
165
- /**
166
- * Get the text from this field as displayed on screen. May differ from getText
167
- * due to ellipsis, and other formatting.
168
- * @return {string} Currently displayed text.
169
- * @protected
170
- * @override
171
- */
172
- FieldMultilineInput.prototype.getDisplayText_ = function() {
173
- let textLines = this.getText();
174
- if (!textLines) {
175
- // Prevent the field from disappearing if empty.
176
- return Field.NBSP;
177
- }
178
- const lines = textLines.split('\n');
179
- textLines = '';
180
- const displayLinesNumber =
181
- this.isOverflowedY_ ? this.maxLines_ : lines.length;
182
- for (let i = 0; i < displayLinesNumber; i++) {
183
- let text = lines[i];
184
- if (text.length > this.maxDisplayLength) {
185
- // Truncate displayed string and add an ellipsis ('...').
186
- text = text.substring(0, this.maxDisplayLength - 4) + '...';
187
- } else if (this.isOverflowedY_ && i === displayLinesNumber - 1) {
188
- text = text.substring(0, text.length - 3) + '...';
121
+ /**
122
+ * Saves this field's value.
123
+ * @return {*} The state of this field.
124
+ * @package
125
+ */
126
+ saveState() {
127
+ const legacyState = this.saveLegacyState(FieldMultilineInput);
128
+ if (legacyState !== null) {
129
+ return legacyState;
189
130
  }
190
- // Replace whitespace with non-breaking spaces so the text doesn't collapse.
191
- text = text.replace(/\s/g, Field.NBSP);
131
+ return this.getValue();
132
+ }
192
133
 
193
- textLines += text;
194
- if (i !== displayLinesNumber - 1) {
195
- textLines += '\n';
134
+ /**
135
+ * Sets the field's value based on the given state.
136
+ * @param {*} state The state of the variable to assign to this variable
137
+ * field.
138
+ * @override
139
+ * @package
140
+ */
141
+ loadState(state) {
142
+ if (this.loadLegacyState(Field, state)) {
143
+ return;
196
144
  }
145
+ this.setValue(state);
197
146
  }
198
- if (this.sourceBlock_.RTL) {
199
- // The SVG is LTR, force value to be RTL.
200
- textLines += '\u200F';
147
+
148
+ /**
149
+ * Create the block UI for this field.
150
+ * @package
151
+ */
152
+ initView() {
153
+ this.createBorderRect_();
154
+ this.textGroup_ = dom.createSvgElement(
155
+ Svg.G, {
156
+ 'class': 'blocklyEditableText',
157
+ },
158
+ this.fieldGroup_);
201
159
  }
202
- return textLines;
203
- };
204
160
 
205
- /**
206
- * Called by setValue if the text input is valid. Updates the value of the
207
- * field, and updates the text of the field if it is not currently being
208
- * edited (i.e. handled by the htmlInput_). Is being redefined here to update
209
- * overflow state of the field.
210
- * @param {*} newValue The value to be saved. The default validator guarantees
211
- * that this is a string.
212
- * @protected
213
- */
214
- FieldMultilineInput.prototype.doValueUpdate_ = function(newValue) {
215
- FieldMultilineInput.superClass_.doValueUpdate_.call(this, newValue);
216
- this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_;
217
- };
161
+ /**
162
+ * Get the text from this field as displayed on screen. May differ from
163
+ * getText due to ellipsis, and other formatting.
164
+ * @return {string} Currently displayed text.
165
+ * @protected
166
+ * @override
167
+ */
168
+ getDisplayText_() {
169
+ let textLines = this.getText();
170
+ if (!textLines) {
171
+ // Prevent the field from disappearing if empty.
172
+ return Field.NBSP;
173
+ }
174
+ const lines = textLines.split('\n');
175
+ textLines = '';
176
+ const displayLinesNumber =
177
+ this.isOverflowedY_ ? this.maxLines_ : lines.length;
178
+ for (let i = 0; i < displayLinesNumber; i++) {
179
+ let text = lines[i];
180
+ if (text.length > this.maxDisplayLength) {
181
+ // Truncate displayed string and add an ellipsis ('...').
182
+ text = text.substring(0, this.maxDisplayLength - 4) + '...';
183
+ } else if (this.isOverflowedY_ && i === displayLinesNumber - 1) {
184
+ text = text.substring(0, text.length - 3) + '...';
185
+ }
186
+ // Replace whitespace with non-breaking spaces so the text doesn't
187
+ // collapse.
188
+ text = text.replace(/\s/g, Field.NBSP);
218
189
 
219
- /**
220
- * Updates the text of the textElement.
221
- * @protected
222
- */
223
- FieldMultilineInput.prototype.render_ = function() {
224
- // Remove all text group children.
225
- let currentChild;
226
- while ((currentChild = this.textGroup_.firstChild)) {
227
- this.textGroup_.removeChild(currentChild);
190
+ textLines += text;
191
+ if (i !== displayLinesNumber - 1) {
192
+ textLines += '\n';
193
+ }
194
+ }
195
+ if (this.sourceBlock_.RTL) {
196
+ // The SVG is LTR, force value to be RTL.
197
+ textLines += '\u200F';
198
+ }
199
+ return textLines;
228
200
  }
229
201
 
230
- // Add in text elements into the group.
231
- const lines = this.getDisplayText_().split('\n');
232
- let y = 0;
233
- for (let i = 0; i < lines.length; i++) {
234
- const lineHeight = this.getConstants().FIELD_TEXT_HEIGHT +
235
- this.getConstants().FIELD_BORDER_RECT_Y_PADDING;
236
- const span = dom.createSvgElement(
237
- Svg.TEXT, {
238
- 'class': 'blocklyText blocklyMultilineText',
239
- 'x': this.getConstants().FIELD_BORDER_RECT_X_PADDING,
240
- 'y': y + this.getConstants().FIELD_BORDER_RECT_Y_PADDING,
241
- 'dy': this.getConstants().FIELD_TEXT_BASELINE,
242
- },
243
- this.textGroup_);
244
- span.appendChild(document.createTextNode(lines[i]));
245
- y += lineHeight;
202
+ /**
203
+ * Called by setValue if the text input is valid. Updates the value of the
204
+ * field, and updates the text of the field if it is not currently being
205
+ * edited (i.e. handled by the htmlInput_). Is being redefined here to update
206
+ * overflow state of the field.
207
+ * @param {*} newValue The value to be saved. The default validator guarantees
208
+ * that this is a string.
209
+ * @protected
210
+ */
211
+ doValueUpdate_(newValue) {
212
+ super.doValueUpdate_(newValue);
213
+ this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_;
246
214
  }
247
215
 
248
- if (this.isBeingEdited_) {
249
- const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_);
250
- if (this.isOverflowedY_) {
251
- dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
252
- } else {
253
- dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
216
+ /**
217
+ * Updates the text of the textElement.
218
+ * @protected
219
+ */
220
+ render_() {
221
+ // Remove all text group children.
222
+ let currentChild;
223
+ while ((currentChild = this.textGroup_.firstChild)) {
224
+ this.textGroup_.removeChild(currentChild);
254
225
  }
255
- }
256
-
257
- this.updateSize_();
258
226
 
259
- if (this.isBeingEdited_) {
260
- if (this.sourceBlock_.RTL) {
261
- // in RTL, we need to let the browser reflow before resizing
262
- // in order to get the correct bounding box of the borderRect
263
- // avoiding issue #2777.
264
- setTimeout(this.resizeEditor_.bind(this), 0);
265
- } else {
266
- this.resizeEditor_();
227
+ // Add in text elements into the group.
228
+ const lines = this.getDisplayText_().split('\n');
229
+ let y = 0;
230
+ for (let i = 0; i < lines.length; i++) {
231
+ const lineHeight = this.getConstants().FIELD_TEXT_HEIGHT +
232
+ this.getConstants().FIELD_BORDER_RECT_Y_PADDING;
233
+ const span = dom.createSvgElement(
234
+ Svg.TEXT, {
235
+ 'class': 'blocklyText blocklyMultilineText',
236
+ 'x': this.getConstants().FIELD_BORDER_RECT_X_PADDING,
237
+ 'y': y + this.getConstants().FIELD_BORDER_RECT_Y_PADDING,
238
+ 'dy': this.getConstants().FIELD_TEXT_BASELINE,
239
+ },
240
+ this.textGroup_);
241
+ span.appendChild(document.createTextNode(lines[i]));
242
+ y += lineHeight;
267
243
  }
268
- const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_);
269
- if (!this.isTextValid_) {
270
- dom.addClass(htmlInput, 'blocklyInvalidInput');
271
- aria.setState(htmlInput, aria.State.INVALID, true);
272
- } else {
273
- dom.removeClass(htmlInput, 'blocklyInvalidInput');
274
- aria.setState(htmlInput, aria.State.INVALID, false);
244
+
245
+ if (this.isBeingEdited_) {
246
+ const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_);
247
+ if (this.isOverflowedY_) {
248
+ dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
249
+ } else {
250
+ dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
251
+ }
275
252
  }
276
- }
277
- };
278
253
 
279
- /**
280
- * Updates the size of the field based on the text.
281
- * @protected
282
- */
283
- FieldMultilineInput.prototype.updateSize_ = function() {
284
- const nodes = this.textGroup_.childNodes;
285
- let totalWidth = 0;
286
- let totalHeight = 0;
287
- for (let i = 0; i < nodes.length; i++) {
288
- const tspan = /** @type {!Element} */ (nodes[i]);
289
- const textWidth = dom.getTextWidth(tspan);
290
- if (textWidth > totalWidth) {
291
- totalWidth = textWidth;
254
+ this.updateSize_();
255
+
256
+ if (this.isBeingEdited_) {
257
+ if (this.sourceBlock_.RTL) {
258
+ // in RTL, we need to let the browser reflow before resizing
259
+ // in order to get the correct bounding box of the borderRect
260
+ // avoiding issue #2777.
261
+ setTimeout(this.resizeEditor_.bind(this), 0);
262
+ } else {
263
+ this.resizeEditor_();
264
+ }
265
+ const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_);
266
+ if (!this.isTextValid_) {
267
+ dom.addClass(htmlInput, 'blocklyInvalidInput');
268
+ aria.setState(htmlInput, aria.State.INVALID, true);
269
+ } else {
270
+ dom.removeClass(htmlInput, 'blocklyInvalidInput');
271
+ aria.setState(htmlInput, aria.State.INVALID, false);
272
+ }
292
273
  }
293
- totalHeight += this.getConstants().FIELD_TEXT_HEIGHT +
294
- (i > 0 ? this.getConstants().FIELD_BORDER_RECT_Y_PADDING : 0);
295
274
  }
296
- if (this.isBeingEdited_) {
297
- // The default width is based on the longest line in the display text,
298
- // but when it's being edited, width should be calculated based on the
299
- // absolute longest line, even if it would be truncated after editing.
300
- // Otherwise we would get wrong editor width when there are more
301
- // lines than this.maxLines_.
302
- const actualEditorLines = this.value_.split('\n');
303
- const dummyTextElement = dom.createSvgElement(
304
- Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
305
- const fontSize = this.getConstants().FIELD_TEXT_FONTSIZE;
306
- const fontWeight = this.getConstants().FIELD_TEXT_FONTWEIGHT;
307
- const fontFamily = this.getConstants().FIELD_TEXT_FONTFAMILY;
308
-
309
- for (let i = 0; i < actualEditorLines.length; i++) {
310
- if (actualEditorLines[i].length > this.maxDisplayLength) {
311
- actualEditorLines[i] =
312
- actualEditorLines[i].substring(0, this.maxDisplayLength);
275
+
276
+ /**
277
+ * Updates the size of the field based on the text.
278
+ * @protected
279
+ */
280
+ updateSize_() {
281
+ const nodes = this.textGroup_.childNodes;
282
+ let totalWidth = 0;
283
+ let totalHeight = 0;
284
+ for (let i = 0; i < nodes.length; i++) {
285
+ const tspan = /** @type {!Element} */ (nodes[i]);
286
+ const textWidth = dom.getTextWidth(tspan);
287
+ if (textWidth > totalWidth) {
288
+ totalWidth = textWidth;
313
289
  }
314
- dummyTextElement.textContent = actualEditorLines[i];
315
- const lineWidth = dom.getFastTextWidth(
316
- dummyTextElement, fontSize, fontWeight, fontFamily);
317
- if (lineWidth > totalWidth) {
318
- totalWidth = lineWidth;
290
+ totalHeight += this.getConstants().FIELD_TEXT_HEIGHT +
291
+ (i > 0 ? this.getConstants().FIELD_BORDER_RECT_Y_PADDING : 0);
292
+ }
293
+ if (this.isBeingEdited_) {
294
+ // The default width is based on the longest line in the display text,
295
+ // but when it's being edited, width should be calculated based on the
296
+ // absolute longest line, even if it would be truncated after editing.
297
+ // Otherwise we would get wrong editor width when there are more
298
+ // lines than this.maxLines_.
299
+ const actualEditorLines = this.value_.split('\n');
300
+ const dummyTextElement = dom.createSvgElement(
301
+ Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
302
+ const fontSize = this.getConstants().FIELD_TEXT_FONTSIZE;
303
+ const fontWeight = this.getConstants().FIELD_TEXT_FONTWEIGHT;
304
+ const fontFamily = this.getConstants().FIELD_TEXT_FONTFAMILY;
305
+
306
+ for (let i = 0; i < actualEditorLines.length; i++) {
307
+ if (actualEditorLines[i].length > this.maxDisplayLength) {
308
+ actualEditorLines[i] =
309
+ actualEditorLines[i].substring(0, this.maxDisplayLength);
310
+ }
311
+ dummyTextElement.textContent = actualEditorLines[i];
312
+ const lineWidth = dom.getFastTextWidth(
313
+ dummyTextElement, fontSize, fontWeight, fontFamily);
314
+ if (lineWidth > totalWidth) {
315
+ totalWidth = lineWidth;
316
+ }
319
317
  }
318
+
319
+ const scrollbarWidth =
320
+ this.htmlInput_.offsetWidth - this.htmlInput_.clientWidth;
321
+ totalWidth += scrollbarWidth;
322
+ }
323
+ if (this.borderRect_) {
324
+ totalHeight += this.getConstants().FIELD_BORDER_RECT_Y_PADDING * 2;
325
+ totalWidth += this.getConstants().FIELD_BORDER_RECT_X_PADDING * 2;
326
+ this.borderRect_.setAttribute('width', totalWidth);
327
+ this.borderRect_.setAttribute('height', totalHeight);
320
328
  }
329
+ this.size_.width = totalWidth;
330
+ this.size_.height = totalHeight;
321
331
 
322
- const scrollbarWidth =
323
- this.htmlInput_.offsetWidth - this.htmlInput_.clientWidth;
324
- totalWidth += scrollbarWidth;
332
+ this.positionBorderRect_();
325
333
  }
326
- if (this.borderRect_) {
327
- totalHeight += this.getConstants().FIELD_BORDER_RECT_Y_PADDING * 2;
328
- totalWidth += this.getConstants().FIELD_BORDER_RECT_X_PADDING * 2;
329
- this.borderRect_.setAttribute('width', totalWidth);
330
- this.borderRect_.setAttribute('height', totalHeight);
334
+
335
+ /**
336
+ * Show the inline free-text editor on top of the text.
337
+ * Overrides the default behaviour to force rerender in order to
338
+ * correct block size, based on editor text.
339
+ * @param {Event=} _opt_e Optional mouse event that triggered the field to
340
+ * open, or undefined if triggered programmatically.
341
+ * @param {boolean=} opt_quietInput True if editor should be created without
342
+ * focus. Defaults to false.
343
+ * @override
344
+ */
345
+ showEditor_(_opt_e, opt_quietInput) {
346
+ super.showEditor_(_opt_e, opt_quietInput);
347
+ this.forceRerender();
331
348
  }
332
- this.size_.width = totalWidth;
333
- this.size_.height = totalHeight;
334
349
 
335
- this.positionBorderRect_();
336
- };
350
+ /**
351
+ * Create the text input editor widget.
352
+ * @return {!HTMLTextAreaElement} The newly created text input editor.
353
+ * @protected
354
+ */
355
+ widgetCreate_() {
356
+ const div = WidgetDiv.getDiv();
357
+ const scale = this.workspace_.getScale();
358
+
359
+ const htmlInput =
360
+ /** @type {HTMLTextAreaElement} */ (document.createElement('textarea'));
361
+ htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
362
+ htmlInput.setAttribute('spellcheck', this.spellcheck_);
363
+ const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt';
364
+ div.style.fontSize = fontSize;
365
+ htmlInput.style.fontSize = fontSize;
366
+ const borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px';
367
+ htmlInput.style.borderRadius = borderRadius;
368
+ const paddingX = this.getConstants().FIELD_BORDER_RECT_X_PADDING * scale;
369
+ const paddingY =
370
+ this.getConstants().FIELD_BORDER_RECT_Y_PADDING * scale / 2;
371
+ htmlInput.style.padding = paddingY + 'px ' + paddingX + 'px ' + paddingY +
372
+ 'px ' + paddingX + 'px';
373
+ const lineHeight = this.getConstants().FIELD_TEXT_HEIGHT +
374
+ this.getConstants().FIELD_BORDER_RECT_Y_PADDING;
375
+ htmlInput.style.lineHeight = (lineHeight * scale) + 'px';
337
376
 
338
- /**
339
- * Show the inline free-text editor on top of the text.
340
- * Overrides the default behaviour to force rerender in order to
341
- * correct block size, based on editor text.
342
- * @param {Event=} _opt_e Optional mouse event that triggered the field to open,
343
- * or undefined if triggered programmatically.
344
- * @param {boolean=} opt_quietInput True if editor should be created without
345
- * focus. Defaults to false.
346
- * @override
347
- */
348
- FieldMultilineInput.prototype.showEditor_ = function(_opt_e, opt_quietInput) {
349
- FieldMultilineInput.superClass_.showEditor_.call(
350
- this, _opt_e, opt_quietInput);
351
- this.forceRerender();
352
- };
377
+ div.appendChild(htmlInput);
353
378
 
354
- /**
355
- * Create the text input editor widget.
356
- * @return {!HTMLTextAreaElement} The newly created text input editor.
357
- * @protected
358
- */
359
- FieldMultilineInput.prototype.widgetCreate_ = function() {
360
- const div = WidgetDiv.getDiv();
361
- const scale = this.workspace_.getScale();
362
-
363
- const htmlInput =
364
- /** @type {HTMLTextAreaElement} */ (document.createElement('textarea'));
365
- htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
366
- htmlInput.setAttribute('spellcheck', this.spellcheck_);
367
- const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt';
368
- div.style.fontSize = fontSize;
369
- htmlInput.style.fontSize = fontSize;
370
- const borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px';
371
- htmlInput.style.borderRadius = borderRadius;
372
- const paddingX = this.getConstants().FIELD_BORDER_RECT_X_PADDING * scale;
373
- const paddingY = this.getConstants().FIELD_BORDER_RECT_Y_PADDING * scale / 2;
374
- htmlInput.style.padding =
375
- paddingY + 'px ' + paddingX + 'px ' + paddingY + 'px ' + paddingX + 'px';
376
- const lineHeight = this.getConstants().FIELD_TEXT_HEIGHT +
377
- this.getConstants().FIELD_BORDER_RECT_Y_PADDING;
378
- htmlInput.style.lineHeight = (lineHeight * scale) + 'px';
379
-
380
- div.appendChild(htmlInput);
381
-
382
- htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
383
- htmlInput.untypedDefaultValue_ = this.value_;
384
- htmlInput.oldValue_ = null;
385
- if (userAgent.GECKO) {
386
- // In FF, ensure the browser reflows before resizing to avoid issue #2777.
387
- setTimeout(this.resizeEditor_.bind(this), 0);
388
- } else {
389
- this.resizeEditor_();
390
- }
379
+ htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
380
+ htmlInput.untypedDefaultValue_ = this.value_;
381
+ htmlInput.oldValue_ = null;
382
+ if (userAgent.GECKO) {
383
+ // In FF, ensure the browser reflows before resizing to avoid issue #2777.
384
+ setTimeout(this.resizeEditor_.bind(this), 0);
385
+ } else {
386
+ this.resizeEditor_();
387
+ }
391
388
 
392
- this.bindInputEvents_(htmlInput);
389
+ this.bindInputEvents_(htmlInput);
393
390
 
394
- return htmlInput;
395
- };
391
+ return htmlInput;
392
+ }
396
393
 
397
- /**
398
- * Sets the maxLines config for this field.
399
- * @param {number} maxLines Defines the maximum number of lines allowed,
400
- * before scrolling functionality is enabled.
401
- */
402
- FieldMultilineInput.prototype.setMaxLines = function(maxLines) {
403
- if (typeof maxLines === 'number' && maxLines > 0 &&
404
- maxLines !== this.maxLines_) {
405
- this.maxLines_ = maxLines;
406
- this.forceRerender();
394
+ /**
395
+ * Sets the maxLines config for this field.
396
+ * @param {number} maxLines Defines the maximum number of lines allowed,
397
+ * before scrolling functionality is enabled.
398
+ */
399
+ setMaxLines(maxLines) {
400
+ if (typeof maxLines === 'number' && maxLines > 0 &&
401
+ maxLines !== this.maxLines_) {
402
+ this.maxLines_ = maxLines;
403
+ this.forceRerender();
404
+ }
407
405
  }
408
- };
409
406
 
410
- /**
411
- * Returns the maxLines config of this field.
412
- * @return {number} The maxLines config value.
413
- */
414
- FieldMultilineInput.prototype.getMaxLines = function() {
415
- return this.maxLines_;
416
- };
407
+ /**
408
+ * Returns the maxLines config of this field.
409
+ * @return {number} The maxLines config value.
410
+ */
411
+ getMaxLines() {
412
+ return this.maxLines_;
413
+ }
417
414
 
418
- /**
419
- * Handle key down to the editor. Override the text input definition of this
420
- * so as to not close the editor when enter is typed in.
421
- * @param {!Event} e Keyboard event.
422
- * @protected
423
- */
424
- FieldMultilineInput.prototype.onHtmlInputKeyDown_ = function(e) {
425
- if (e.keyCode !== KeyCodes.ENTER) {
426
- FieldMultilineInput.superClass_.onHtmlInputKeyDown_.call(this, e);
415
+ /**
416
+ * Handle key down to the editor. Override the text input definition of this
417
+ * so as to not close the editor when enter is typed in.
418
+ * @param {!Event} e Keyboard event.
419
+ * @protected
420
+ */
421
+ onHtmlInputKeyDown_(e) {
422
+ if (e.keyCode !== KeyCodes.ENTER) {
423
+ super.onHtmlInputKeyDown_(e);
424
+ }
427
425
  }
428
- };
426
+
427
+ /**
428
+ * Construct a FieldMultilineInput from a JSON arg object,
429
+ * dereferencing any string table references.
430
+ * @param {!Object} options A JSON object with options (text, and spellcheck).
431
+ * @return {!FieldMultilineInput} The new field instance.
432
+ * @package
433
+ * @nocollapse
434
+ * @override
435
+ */
436
+ static fromJson(options) {
437
+ const text = parsing.replaceMessageReferences(options['text']);
438
+ // `this` might be a subclass of FieldMultilineInput if that class doesn't
439
+ // override the static fromJson method.
440
+ return new this(text, undefined, options);
441
+ }
442
+ }
429
443
 
430
444
  /**
431
445
  * CSS for multiline field. See css.js for use.
432
446
  */
433
447
  Css.register(`
434
- .blocklyHtmlTextAreaInput {
435
- font-family: monospace;
436
- resize: none;
437
- overflow: hidden;
438
- height: 100%;
439
- text-align: left;
440
- }
441
-
442
- .blocklyHtmlTextAreaInputOverflowedY {
443
- overflow-y: scroll;
444
- }
448
+ .blocklyHtmlTextAreaInput {
449
+ font-family: monospace;
450
+ resize: none;
451
+ overflow: hidden;
452
+ height: 100%;
453
+ text-align: left;
454
+ }
455
+
456
+ .blocklyHtmlTextAreaInputOverflowedY {
457
+ overflow-y: scroll;
458
+ }
445
459
  `);
446
460
 
447
461
  fieldRegistry.register('field_multilinetext', FieldMultilineInput);