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,19 +20,20 @@ const aria = goog.require('Blockly.utils.aria');
20
20
  const browserEvents = goog.require('Blockly.browserEvents');
21
21
  const dialog = goog.require('Blockly.dialog');
22
22
  const dom = goog.require('Blockly.utils.dom');
23
+ const dropDownDiv = goog.require('Blockly.dropDownDiv');
23
24
  const eventUtils = goog.require('Blockly.Events.utils');
24
25
  const fieldRegistry = goog.require('Blockly.fieldRegistry');
25
- const object = goog.require('Blockly.utils.object');
26
26
  const parsing = goog.require('Blockly.utils.parsing');
27
27
  const userAgent = goog.require('Blockly.utils.userAgent');
28
28
  /* eslint-disable-next-line no-unused-vars */
29
29
  const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
30
30
  const {Coordinate} = goog.require('Blockly.utils.Coordinate');
31
- const {DropDownDiv} = goog.require('Blockly.DropDownDiv');
32
31
  const {Field} = goog.require('Blockly.Field');
33
32
  const {KeyCodes} = goog.require('Blockly.utils.KeyCodes');
34
33
  const {Msg} = goog.require('Blockly.Msg');
35
34
  /* eslint-disable-next-line no-unused-vars */
35
+ const {Sentinel} = goog.requireType('Blockly.utils.Sentinel');
36
+ /* eslint-disable-next-line no-unused-vars */
36
37
  const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
37
38
  /** @suppress {extraRequire} */
38
39
  goog.require('Blockly.Events.BlockChange');
@@ -40,547 +41,578 @@ goog.require('Blockly.Events.BlockChange');
40
41
 
41
42
  /**
42
43
  * Class for an editable text field.
43
- * @param {string=} opt_value The initial value of the field. Should cast to a
44
- * string. Defaults to an empty string if null or undefined.
45
- * @param {?Function=} opt_validator A function that is called to validate
46
- * changes to the field's value. Takes in a string & returns a validated
47
- * string, or null to abort the change.
48
- * @param {Object=} opt_config A map of options used to configure the field.
49
- * See the [field creation documentation]{@link
50
- * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
51
- * for a list of properties this parameter supports.
52
- * @extends {Field}
53
- * @constructor
54
44
  * @alias Blockly.FieldTextInput
55
45
  */
56
- const FieldTextInput = function(opt_value, opt_validator, opt_config) {
46
+ class FieldTextInput extends Field {
57
47
  /**
58
- * Allow browser to spellcheck this field.
59
- * @type {boolean}
60
- * @protected
48
+ * @param {(string|!Sentinel)=} opt_value The initial value of the
49
+ * field. Should cast to a string. Defaults to an empty string if null or
50
+ * undefined.
51
+ * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
52
+ * subclasses that want to handle configuration and setting the field
53
+ * value after their own constructors have run).
54
+ * @param {?Function=} opt_validator A function that is called to validate
55
+ * changes to the field's value. Takes in a string & returns a validated
56
+ * string, or null to abort the change.
57
+ * @param {Object=} opt_config A map of options used to configure the field.
58
+ * See the [field creation documentation]{@link
59
+ * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
60
+ * for a list of properties this parameter supports.
61
61
  */
62
- this.spellcheck_ = true;
62
+ constructor(opt_value, opt_validator, opt_config) {
63
+ super(Field.SKIP_SETUP);
64
+
65
+ /**
66
+ * Allow browser to spellcheck this field.
67
+ * @type {boolean}
68
+ * @protected
69
+ */
70
+ this.spellcheck_ = true;
71
+
72
+ /**
73
+ * The HTML input element.
74
+ * @type {HTMLElement}
75
+ * @protected
76
+ */
77
+ this.htmlInput_ = null;
78
+
79
+ /**
80
+ * True if the field's value is currently being edited via the UI.
81
+ * @type {boolean}
82
+ * @private
83
+ */
84
+ this.isBeingEdited_ = false;
85
+
86
+ /**
87
+ * True if the value currently displayed in the field's editory UI is valid.
88
+ * @type {boolean}
89
+ * @private
90
+ */
91
+ this.isTextValid_ = false;
63
92
 
64
- FieldTextInput.superClass_.constructor.call(
65
- this, opt_value, opt_validator, opt_config);
93
+ /**
94
+ * Key down event data.
95
+ * @type {?browserEvents.Data}
96
+ * @private
97
+ */
98
+ this.onKeyDownWrapper_ = null;
66
99
 
67
- /**
68
- * The HTML input element.
69
- * @type {HTMLElement}
70
- */
71
- this.htmlInput_ = null;
100
+ /**
101
+ * Key input event data.
102
+ * @type {?browserEvents.Data}
103
+ * @private
104
+ */
105
+ this.onKeyInputWrapper_ = null;
72
106
 
73
- /**
74
- * Key down event data.
75
- * @type {?browserEvents.Data}
76
- * @private
77
- */
78
- this.onKeyDownWrapper_ = null;
107
+ /**
108
+ * Whether the field should consider the whole parent block to be its click
109
+ * target.
110
+ * @type {?boolean}
111
+ */
112
+ this.fullBlockClickTarget_ = false;
113
+
114
+ /**
115
+ * The workspace that this field belongs to.
116
+ * @type {?WorkspaceSvg}
117
+ * @protected
118
+ */
119
+ this.workspace_ = null;
120
+
121
+ /**
122
+ * Serializable fields are saved by the serializer, non-serializable fields
123
+ * are not. Editable fields should also be serializable.
124
+ * @type {boolean}
125
+ */
126
+ this.SERIALIZABLE = true;
127
+
128
+ /**
129
+ * Mouse cursor style when over the hotspot that initiates the editor.
130
+ * @type {string}
131
+ */
132
+ this.CURSOR = 'text';
133
+
134
+ if (opt_value === Field.SKIP_SETUP) return;
135
+ if (opt_config) this.configure_(opt_config);
136
+ this.setValue(opt_value);
137
+ if (opt_validator) this.setValidator(opt_validator);
138
+ }
79
139
 
80
140
  /**
81
- * Key input event data.
82
- * @type {?browserEvents.Data}
83
- * @private
141
+ * @override
84
142
  */
85
- this.onKeyInputWrapper_ = null;
143
+ configure_(config) {
144
+ super.configure_(config);
145
+ if (typeof config['spellcheck'] === 'boolean') {
146
+ this.spellcheck_ = config['spellcheck'];
147
+ }
148
+ }
86
149
 
87
150
  /**
88
- * Whether the field should consider the whole parent block to be its click
89
- * target.
90
- * @type {?boolean}
151
+ * @override
91
152
  */
92
- this.fullBlockClickTarget_ = false;
153
+ initView() {
154
+ if (this.getConstants().FULL_BLOCK_FIELDS) {
155
+ // Step one: figure out if this is the only field on this block.
156
+ // Rendering is quite different in that case.
157
+ let nFields = 0;
158
+ let nConnections = 0;
159
+
160
+ // Count the number of fields, excluding text fields
161
+ for (let i = 0, input; (input = this.sourceBlock_.inputList[i]); i++) {
162
+ for (let j = 0; (input.fieldRow[j]); j++) {
163
+ nFields++;
164
+ }
165
+ if (input.connection) {
166
+ nConnections++;
167
+ }
168
+ }
169
+ // The special case is when this is the only non-label field on the block
170
+ // and it has an output but no inputs.
171
+ this.fullBlockClickTarget_ =
172
+ nFields <= 1 && this.sourceBlock_.outputConnection && !nConnections;
173
+ } else {
174
+ this.fullBlockClickTarget_ = false;
175
+ }
176
+
177
+ if (this.fullBlockClickTarget_) {
178
+ this.clickTarget_ = this.sourceBlock_.getSvgRoot();
179
+ } else {
180
+ this.createBorderRect_();
181
+ }
182
+ this.createTextElement_();
183
+ }
93
184
 
94
185
  /**
95
- * The workspace that this field belongs to.
96
- * @type {?WorkspaceSvg}
186
+ * Ensure that the input value casts to a valid string.
187
+ * @param {*=} opt_newValue The input value.
188
+ * @return {*} A valid string, or null if invalid.
97
189
  * @protected
98
190
  */
99
- this.workspace_ = null;
100
- };
101
- object.inherits(FieldTextInput, Field);
102
-
103
- /**
104
- * The default value for this field.
105
- * @type {*}
106
- * @protected
107
- */
108
- FieldTextInput.prototype.DEFAULT_VALUE = '';
109
-
110
- /**
111
- * Construct a FieldTextInput from a JSON arg object,
112
- * dereferencing any string table references.
113
- * @param {!Object} options A JSON object with options (text, and spellcheck).
114
- * @return {!FieldTextInput} The new field instance.
115
- * @package
116
- * @nocollapse
117
- */
118
- FieldTextInput.fromJson = function(options) {
119
- const text = parsing.replaceMessageReferences(options['text']);
120
- // `this` might be a subclass of FieldTextInput if that class doesn't override
121
- // the static fromJson method.
122
- return new this(text, undefined, options);
123
- };
124
-
125
- /**
126
- * Serializable fields are saved by the XML renderer, non-serializable fields
127
- * are not. Editable fields should also be serializable.
128
- * @type {boolean}
129
- */
130
- FieldTextInput.prototype.SERIALIZABLE = true;
131
-
132
- /**
133
- * Pixel size of input border radius.
134
- * Should match blocklyText's border-radius in CSS.
135
- */
136
- FieldTextInput.BORDERRADIUS = 4;
137
-
138
- /**
139
- * Mouse cursor style when over the hotspot that initiates the editor.
140
- */
141
- FieldTextInput.prototype.CURSOR = 'text';
142
-
143
- /**
144
- * @override
145
- */
146
- FieldTextInput.prototype.configure_ = function(config) {
147
- FieldTextInput.superClass_.configure_.call(this, config);
148
- if (typeof config['spellcheck'] === 'boolean') {
149
- this.spellcheck_ = config['spellcheck'];
191
+ doClassValidation_(opt_newValue) {
192
+ if (opt_newValue === null || opt_newValue === undefined) {
193
+ return null;
194
+ }
195
+ return String(opt_newValue);
150
196
  }
151
- };
152
197
 
153
- /**
154
- * @override
155
- */
156
- FieldTextInput.prototype.initView = function() {
157
- if (this.getConstants().FULL_BLOCK_FIELDS) {
158
- // Step one: figure out if this is the only field on this block.
159
- // Rendering is quite different in that case.
160
- let nFields = 0;
161
- let nConnections = 0;
162
-
163
- // Count the number of fields, excluding text fields
164
- for (let i = 0, input; (input = this.sourceBlock_.inputList[i]); i++) {
165
- for (let j = 0; (input.fieldRow[j]); j++) {
166
- nFields++;
167
- }
168
- if (input.connection) {
169
- nConnections++;
198
+ /**
199
+ * Called by setValue if the text input is not valid. If the field is
200
+ * currently being edited it reverts value of the field to the previous
201
+ * value while allowing the display text to be handled by the htmlInput_.
202
+ * @param {*} _invalidValue The input value that was determined to be invalid.
203
+ * This is not used by the text input because its display value is stored
204
+ * on the htmlInput_.
205
+ * @protected
206
+ */
207
+ doValueInvalid_(_invalidValue) {
208
+ if (this.isBeingEdited_) {
209
+ this.isTextValid_ = false;
210
+ const oldValue = this.value_;
211
+ // Revert value when the text becomes invalid.
212
+ this.value_ = this.htmlInput_.untypedDefaultValue_;
213
+ if (this.sourceBlock_ && eventUtils.isEnabled()) {
214
+ eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
215
+ this.sourceBlock_, 'field', this.name || null, oldValue,
216
+ this.value_));
170
217
  }
171
218
  }
172
- // The special case is when this is the only non-label field on the block
173
- // and it has an output but no inputs.
174
- this.fullBlockClickTarget_ =
175
- nFields <= 1 && this.sourceBlock_.outputConnection && !nConnections;
176
- } else {
177
- this.fullBlockClickTarget_ = false;
178
- }
179
-
180
- if (this.fullBlockClickTarget_) {
181
- this.clickTarget_ = this.sourceBlock_.getSvgRoot();
182
- } else {
183
- this.createBorderRect_();
184
219
  }
185
- this.createTextElement_();
186
- };
187
220
 
188
- /**
189
- * Ensure that the input value casts to a valid string.
190
- * @param {*=} opt_newValue The input value.
191
- * @return {*} A valid string, or null if invalid.
192
- * @protected
193
- */
194
- FieldTextInput.prototype.doClassValidation_ = function(opt_newValue) {
195
- if (opt_newValue === null || opt_newValue === undefined) {
196
- return null;
221
+ /**
222
+ * Called by setValue if the text input is valid. Updates the value of the
223
+ * field, and updates the text of the field if it is not currently being
224
+ * edited (i.e. handled by the htmlInput_).
225
+ * @param {*} newValue The value to be saved. The default validator guarantees
226
+ * that this is a string.
227
+ * @protected
228
+ */
229
+ doValueUpdate_(newValue) {
230
+ this.isTextValid_ = true;
231
+ this.value_ = newValue;
232
+ if (!this.isBeingEdited_) {
233
+ // This should only occur if setValue is triggered programmatically.
234
+ this.isDirty_ = true;
235
+ }
197
236
  }
198
- return String(opt_newValue);
199
- };
200
237
 
201
- /**
202
- * Called by setValue if the text input is not valid. If the field is
203
- * currently being edited it reverts value of the field to the previous
204
- * value while allowing the display text to be handled by the htmlInput_.
205
- * @param {*} _invalidValue The input value that was determined to be invalid.
206
- * This is not used by the text input because its display value is stored on
207
- * the htmlInput_.
208
- * @protected
209
- */
210
- FieldTextInput.prototype.doValueInvalid_ = function(_invalidValue) {
211
- if (this.isBeingEdited_) {
212
- this.isTextValid_ = false;
213
- const oldValue = this.value_;
214
- // Revert value when the text becomes invalid.
215
- this.value_ = this.htmlInput_.untypedDefaultValue_;
216
- if (this.sourceBlock_ && eventUtils.isEnabled()) {
217
- eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
218
- this.sourceBlock_, 'field', this.name || null, oldValue,
219
- this.value_));
238
+ /**
239
+ * Updates text field to match the colour/style of the block.
240
+ * @package
241
+ */
242
+ applyColour() {
243
+ if (this.sourceBlock_ && this.getConstants().FULL_BLOCK_FIELDS) {
244
+ if (this.borderRect_) {
245
+ this.borderRect_.setAttribute(
246
+ 'stroke', this.sourceBlock_.style.colourTertiary);
247
+ } else {
248
+ this.sourceBlock_.pathObject.svgPath.setAttribute(
249
+ 'fill', this.getConstants().FIELD_BORDER_RECT_COLOUR);
250
+ }
220
251
  }
221
252
  }
222
- };
223
253
 
224
- /**
225
- * Called by setValue if the text input is valid. Updates the value of the
226
- * field, and updates the text of the field if it is not currently being
227
- * edited (i.e. handled by the htmlInput_).
228
- * @param {*} newValue The value to be saved. The default validator guarantees
229
- * that this is a string.
230
- * @protected
231
- */
232
- FieldTextInput.prototype.doValueUpdate_ = function(newValue) {
233
- this.isTextValid_ = true;
234
- this.value_ = newValue;
235
- if (!this.isBeingEdited_) {
236
- // This should only occur if setValue is triggered programmatically.
237
- this.isDirty_ = true;
254
+ /**
255
+ * Updates the colour of the htmlInput given the current validity of the
256
+ * field's value.
257
+ * @protected
258
+ */
259
+ render_() {
260
+ super.render_();
261
+ // This logic is done in render_ rather than doValueInvalid_ or
262
+ // doValueUpdate_ so that the code is more centralized.
263
+ if (this.isBeingEdited_) {
264
+ this.resizeEditor_();
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
+ }
273
+ }
238
274
  }
239
- };
240
275
 
241
- /**
242
- * Updates text field to match the colour/style of the block.
243
- * @package
244
- */
245
- FieldTextInput.prototype.applyColour = function() {
246
- if (this.sourceBlock_ && this.getConstants().FULL_BLOCK_FIELDS) {
247
- if (this.borderRect_) {
248
- this.borderRect_.setAttribute(
249
- 'stroke', this.sourceBlock_.style.colourTertiary);
250
- } else {
251
- this.sourceBlock_.pathObject.svgPath.setAttribute(
252
- 'fill', this.getConstants().FIELD_BORDER_RECT_COLOUR);
276
+ /**
277
+ * Set whether this field is spellchecked by the browser.
278
+ * @param {boolean} check True if checked.
279
+ */
280
+ setSpellcheck(check) {
281
+ if (check === this.spellcheck_) {
282
+ return;
283
+ }
284
+ this.spellcheck_ = check;
285
+ if (this.htmlInput_) {
286
+ this.htmlInput_.setAttribute('spellcheck', this.spellcheck_);
253
287
  }
254
288
  }
255
- };
256
289
 
257
- /**
258
- * Updates the colour of the htmlInput given the current validity of the
259
- * field's value.
260
- * @protected
261
- */
262
- FieldTextInput.prototype.render_ = function() {
263
- FieldTextInput.superClass_.render_.call(this);
264
- // This logic is done in render_ rather than doValueInvalid_ or
265
- // doValueUpdate_ so that the code is more centralized.
266
- if (this.isBeingEdited_) {
267
- this.resizeEditor_();
268
- const htmlInput = /** @type {!HTMLElement} */ (this.htmlInput_);
269
- if (!this.isTextValid_) {
270
- dom.addClass(htmlInput, 'blocklyInvalidInput');
271
- aria.setState(htmlInput, aria.State.INVALID, true);
290
+ /**
291
+ * Show the inline free-text editor on top of the text.
292
+ * @param {Event=} _opt_e Optional mouse event that triggered the field to
293
+ * open, or undefined if triggered programmatically.
294
+ * @param {boolean=} opt_quietInput True if editor should be created without
295
+ * focus. Defaults to false.
296
+ * @protected
297
+ */
298
+ showEditor_(_opt_e, opt_quietInput) {
299
+ this.workspace_ = (/** @type {!BlockSvg} */ (this.sourceBlock_)).workspace;
300
+ const quietInput = opt_quietInput || false;
301
+ if (!quietInput &&
302
+ (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
303
+ this.showPromptEditor_();
272
304
  } else {
273
- dom.removeClass(htmlInput, 'blocklyInvalidInput');
274
- aria.setState(htmlInput, aria.State.INVALID, false);
305
+ this.showInlineEditor_(quietInput);
275
306
  }
276
307
  }
277
- };
278
308
 
279
- /**
280
- * Set whether this field is spellchecked by the browser.
281
- * @param {boolean} check True if checked.
282
- */
283
- FieldTextInput.prototype.setSpellcheck = function(check) {
284
- if (check === this.spellcheck_) {
285
- return;
286
- }
287
- this.spellcheck_ = check;
288
- if (this.htmlInput_) {
289
- this.htmlInput_.setAttribute('spellcheck', this.spellcheck_);
290
- }
291
- };
292
-
293
- /**
294
- * Show the inline free-text editor on top of the text.
295
- * @param {Event=} _opt_e Optional mouse event that triggered the field to open,
296
- * or undefined if triggered programmatically.
297
- * @param {boolean=} opt_quietInput True if editor should be created without
298
- * focus. Defaults to false.
299
- * @protected
300
- */
301
- FieldTextInput.prototype.showEditor_ = function(_opt_e, opt_quietInput) {
302
- this.workspace_ = (/** @type {!BlockSvg} */ (this.sourceBlock_)).workspace;
303
- const quietInput = opt_quietInput || false;
304
- if (!quietInput &&
305
- (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
306
- this.showPromptEditor_();
307
- } else {
308
- this.showInlineEditor_(quietInput);
309
+ /**
310
+ * Create and show a text input editor that is a prompt (usually a popup).
311
+ * Mobile browsers have issues with in-line textareas (focus and keyboards).
312
+ * @private
313
+ */
314
+ showPromptEditor_() {
315
+ dialog.prompt(Msg['CHANGE_VALUE_TITLE'], this.getText(), function(text) {
316
+ // Text is null if user pressed cancel button.
317
+ if (text !== null) {
318
+ this.setValue(this.getValueFromEditorText_(text));
319
+ }
320
+ }.bind(this));
309
321
  }
310
- };
311
322
 
312
- /**
313
- * Create and show a text input editor that is a prompt (usually a popup).
314
- * Mobile browsers have issues with in-line textareas (focus and keyboards).
315
- * @private
316
- */
317
- FieldTextInput.prototype.showPromptEditor_ = function() {
318
- dialog.prompt(Msg['CHANGE_VALUE_TITLE'], this.getText(), function(text) {
319
- // Text is null if user pressed cancel button.
320
- if (text !== null) {
321
- this.setValue(this.getValueFromEditorText_(text));
323
+ /**
324
+ * Create and show a text input editor that sits directly over the text input.
325
+ * @param {boolean} quietInput True if editor should be created without
326
+ * focus.
327
+ * @private
328
+ */
329
+ showInlineEditor_(quietInput) {
330
+ WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
331
+ this.htmlInput_ = this.widgetCreate_();
332
+ this.isBeingEdited_ = true;
333
+
334
+ if (!quietInput) {
335
+ this.htmlInput_.focus({preventScroll: true});
336
+ this.htmlInput_.select();
322
337
  }
323
- }.bind(this));
324
- };
325
-
326
- /**
327
- * Create and show a text input editor that sits directly over the text input.
328
- * @param {boolean} quietInput True if editor should be created without
329
- * focus.
330
- * @private
331
- */
332
- FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
333
- WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
334
- this.htmlInput_ = this.widgetCreate_();
335
- this.isBeingEdited_ = true;
336
-
337
- if (!quietInput) {
338
- this.htmlInput_.focus({preventScroll: true});
339
- this.htmlInput_.select();
340
338
  }
341
- };
342
-
343
- /**
344
- * Create the text input editor widget.
345
- * @return {!HTMLElement} The newly created text input editor.
346
- * @protected
347
- */
348
- FieldTextInput.prototype.widgetCreate_ = function() {
349
- eventUtils.setGroup(true);
350
- const div = WidgetDiv.getDiv();
351
-
352
- dom.addClass(this.getClickTarget_(), 'editing');
353
-
354
- const htmlInput =
355
- /** @type {HTMLInputElement} */ (document.createElement('input'));
356
- htmlInput.className = 'blocklyHtmlInput';
357
- htmlInput.setAttribute('spellcheck', this.spellcheck_);
358
- const scale = this.workspace_.getScale();
359
- const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt';
360
- div.style.fontSize = fontSize;
361
- htmlInput.style.fontSize = fontSize;
362
- let borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px';
363
-
364
- if (this.fullBlockClickTarget_) {
365
- const bBox = this.getScaledBBox();
366
339
 
367
- // Override border radius.
368
- borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
369
- // Pull stroke colour from the existing shadow block
370
- const strokeColour = this.sourceBlock_.getParent() ?
371
- this.sourceBlock_.getParent().style.colourTertiary :
372
- this.sourceBlock_.style.colourTertiary;
373
- htmlInput.style.border = (1 * scale) + 'px solid ' + strokeColour;
374
- div.style.borderRadius = borderRadius;
375
- div.style.transition = 'box-shadow 0.25s ease 0s';
376
- if (this.getConstants().FIELD_TEXTINPUT_BOX_SHADOW) {
377
- div.style.boxShadow =
378
- 'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px';
340
+ /**
341
+ * Create the text input editor widget.
342
+ * @return {!HTMLElement} The newly created text input editor.
343
+ * @protected
344
+ */
345
+ widgetCreate_() {
346
+ eventUtils.setGroup(true);
347
+ const div = WidgetDiv.getDiv();
348
+
349
+ dom.addClass(this.getClickTarget_(), 'editing');
350
+
351
+ const htmlInput =
352
+ /** @type {HTMLInputElement} */ (document.createElement('input'));
353
+ htmlInput.className = 'blocklyHtmlInput';
354
+ htmlInput.setAttribute('spellcheck', this.spellcheck_);
355
+ const scale = this.workspace_.getScale();
356
+ const fontSize = (this.getConstants().FIELD_TEXT_FONTSIZE * scale) + 'pt';
357
+ div.style.fontSize = fontSize;
358
+ htmlInput.style.fontSize = fontSize;
359
+ let borderRadius = (FieldTextInput.BORDERRADIUS * scale) + 'px';
360
+
361
+ if (this.fullBlockClickTarget_) {
362
+ const bBox = this.getScaledBBox();
363
+
364
+ // Override border radius.
365
+ borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
366
+ // Pull stroke colour from the existing shadow block
367
+ const strokeColour = this.sourceBlock_.getParent() ?
368
+ this.sourceBlock_.getParent().style.colourTertiary :
369
+ this.sourceBlock_.style.colourTertiary;
370
+ htmlInput.style.border = (1 * scale) + 'px solid ' + strokeColour;
371
+ div.style.borderRadius = borderRadius;
372
+ div.style.transition = 'box-shadow 0.25s ease 0s';
373
+ if (this.getConstants().FIELD_TEXTINPUT_BOX_SHADOW) {
374
+ div.style.boxShadow =
375
+ 'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px';
376
+ }
379
377
  }
380
- }
381
- htmlInput.style.borderRadius = borderRadius;
378
+ htmlInput.style.borderRadius = borderRadius;
382
379
 
383
- div.appendChild(htmlInput);
380
+ div.appendChild(htmlInput);
384
381
 
385
- htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
386
- htmlInput.untypedDefaultValue_ = this.value_;
387
- htmlInput.oldValue_ = null;
382
+ htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
383
+ htmlInput.untypedDefaultValue_ = this.value_;
384
+ htmlInput.oldValue_ = null;
388
385
 
389
- this.resizeEditor_();
386
+ this.resizeEditor_();
390
387
 
391
- this.bindInputEvents_(htmlInput);
388
+ this.bindInputEvents_(htmlInput);
392
389
 
393
- return htmlInput;
394
- };
390
+ return htmlInput;
391
+ }
395
392
 
396
- /**
397
- * Closes the editor, saves the results, and disposes of any events or
398
- * DOM-references belonging to the editor.
399
- * @protected
400
- */
401
- FieldTextInput.prototype.widgetDispose_ = function() {
402
- // Non-disposal related things that we do when the editor closes.
403
- this.isBeingEdited_ = false;
404
- this.isTextValid_ = true;
405
- // Make sure the field's node matches the field's internal value.
406
- this.forceRerender();
407
- // TODO(#2496): Make this less of a hack.
408
- if (this.onFinishEditing_) {
393
+ /**
394
+ * Closes the editor, saves the results, and disposes of any events or
395
+ * DOM-references belonging to the editor.
396
+ * @protected
397
+ */
398
+ widgetDispose_() {
399
+ // Non-disposal related things that we do when the editor closes.
400
+ this.isBeingEdited_ = false;
401
+ this.isTextValid_ = true;
402
+ // Make sure the field's node matches the field's internal value.
403
+ this.forceRerender();
409
404
  this.onFinishEditing_(this.value_);
405
+ eventUtils.setGroup(false);
406
+
407
+ // Actual disposal.
408
+ this.unbindInputEvents_();
409
+ const style = WidgetDiv.getDiv().style;
410
+ style.width = 'auto';
411
+ style.height = 'auto';
412
+ style.fontSize = '';
413
+ style.transition = '';
414
+ style.boxShadow = '';
415
+ this.htmlInput_ = null;
416
+
417
+ dom.removeClass(this.getClickTarget_(), 'editing');
410
418
  }
411
- eventUtils.setGroup(false);
412
419
 
413
- // Actual disposal.
414
- this.unbindInputEvents_();
415
- const style = WidgetDiv.getDiv().style;
416
- style.width = 'auto';
417
- style.height = 'auto';
418
- style.fontSize = '';
419
- style.transition = '';
420
- style.boxShadow = '';
421
- this.htmlInput_ = null;
420
+ /**
421
+ * A callback triggered when the user is done editing the field via the UI.
422
+ * @param {*} _value The new value of the field.
423
+ */
424
+ onFinishEditing_(_value) {
425
+ // NOP by default.
426
+ // TODO(#2496): Support people passing a func into the field.
427
+ }
422
428
 
423
- dom.removeClass(this.getClickTarget_(), 'editing');
424
- };
429
+ /**
430
+ * Bind handlers for user input on the text input field's editor.
431
+ * @param {!HTMLElement} htmlInput The htmlInput to which event
432
+ * handlers will be bound.
433
+ * @protected
434
+ */
435
+ bindInputEvents_(htmlInput) {
436
+ // Trap Enter without IME and Esc to hide.
437
+ this.onKeyDownWrapper_ = browserEvents.conditionalBind(
438
+ htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
439
+ // Resize after every input change.
440
+ this.onKeyInputWrapper_ = browserEvents.conditionalBind(
441
+ htmlInput, 'input', this, this.onHtmlInputChange_);
442
+ }
425
443
 
426
- /**
427
- * Bind handlers for user input on the text input field's editor.
428
- * @param {!HTMLElement} htmlInput The htmlInput to which event
429
- * handlers will be bound.
430
- * @protected
431
- */
432
- FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) {
433
- // Trap Enter without IME and Esc to hide.
434
- this.onKeyDownWrapper_ = browserEvents.conditionalBind(
435
- htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
436
- // Resize after every input change.
437
- this.onKeyInputWrapper_ = browserEvents.conditionalBind(
438
- htmlInput, 'input', this, this.onHtmlInputChange_);
439
- };
444
+ /**
445
+ * Unbind handlers for user input and workspace size changes.
446
+ * @protected
447
+ */
448
+ unbindInputEvents_() {
449
+ if (this.onKeyDownWrapper_) {
450
+ browserEvents.unbind(this.onKeyDownWrapper_);
451
+ this.onKeyDownWrapper_ = null;
452
+ }
453
+ if (this.onKeyInputWrapper_) {
454
+ browserEvents.unbind(this.onKeyInputWrapper_);
455
+ this.onKeyInputWrapper_ = null;
456
+ }
457
+ }
440
458
 
441
- /**
442
- * Unbind handlers for user input and workspace size changes.
443
- * @protected
444
- */
445
- FieldTextInput.prototype.unbindInputEvents_ = function() {
446
- if (this.onKeyDownWrapper_) {
447
- browserEvents.unbind(this.onKeyDownWrapper_);
448
- this.onKeyDownWrapper_ = null;
459
+ /**
460
+ * Handle key down to the editor.
461
+ * @param {!Event} e Keyboard event.
462
+ * @protected
463
+ */
464
+ onHtmlInputKeyDown_(e) {
465
+ if (e.keyCode === KeyCodes.ENTER) {
466
+ WidgetDiv.hide();
467
+ dropDownDiv.hideWithoutAnimation();
468
+ } else if (e.keyCode === KeyCodes.ESC) {
469
+ this.setValue(this.htmlInput_.untypedDefaultValue_);
470
+ WidgetDiv.hide();
471
+ dropDownDiv.hideWithoutAnimation();
472
+ } else if (e.keyCode === KeyCodes.TAB) {
473
+ WidgetDiv.hide();
474
+ dropDownDiv.hideWithoutAnimation();
475
+ this.sourceBlock_.tab(this, !e.shiftKey);
476
+ e.preventDefault();
477
+ }
449
478
  }
450
- if (this.onKeyInputWrapper_) {
451
- browserEvents.unbind(this.onKeyInputWrapper_);
452
- this.onKeyInputWrapper_ = null;
479
+
480
+ /**
481
+ * Handle a change to the editor.
482
+ * @param {!Event} _e Keyboard event.
483
+ * @private
484
+ */
485
+ onHtmlInputChange_(_e) {
486
+ const text = this.htmlInput_.value;
487
+ if (text !== this.htmlInput_.oldValue_) {
488
+ this.htmlInput_.oldValue_ = text;
489
+
490
+ const value = this.getValueFromEditorText_(text);
491
+ this.setValue(value);
492
+ this.forceRerender();
493
+ this.resizeEditor_();
494
+ }
453
495
  }
454
- };
455
496
 
456
- /**
457
- * Handle key down to the editor.
458
- * @param {!Event} e Keyboard event.
459
- * @protected
460
- */
461
- FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
462
- if (e.keyCode === KeyCodes.ENTER) {
463
- WidgetDiv.hide();
464
- DropDownDiv.hideWithoutAnimation();
465
- } else if (e.keyCode === KeyCodes.ESC) {
466
- this.setValue(this.htmlInput_.untypedDefaultValue_);
467
- WidgetDiv.hide();
468
- DropDownDiv.hideWithoutAnimation();
469
- } else if (e.keyCode === KeyCodes.TAB) {
470
- WidgetDiv.hide();
471
- DropDownDiv.hideWithoutAnimation();
472
- this.sourceBlock_.tab(this, !e.shiftKey);
473
- e.preventDefault();
497
+ /**
498
+ * Set the HTML input value and the field's internal value. The difference
499
+ * between this and ``setValue`` is that this also updates the HTML input
500
+ * value whilst editing.
501
+ * @param {*} newValue New value.
502
+ * @protected
503
+ */
504
+ setEditorValue_(newValue) {
505
+ this.isDirty_ = true;
506
+ if (this.isBeingEdited_) {
507
+ // In the case this method is passed an invalid value, we still
508
+ // pass it through the transformation method `getEditorText` to deal
509
+ // with. Otherwise, the internal field's state will be inconsistent
510
+ // with what's shown to the user.
511
+ this.htmlInput_.value = this.getEditorText_(newValue);
512
+ }
513
+ this.setValue(newValue);
474
514
  }
475
- };
476
515
 
477
- /**
478
- * Handle a change to the editor.
479
- * @param {!Event} _e Keyboard event.
480
- * @private
481
- */
482
- FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
483
- const text = this.htmlInput_.value;
484
- if (text !== this.htmlInput_.oldValue_) {
485
- this.htmlInput_.oldValue_ = text;
516
+ /**
517
+ * Resize the editor to fit the text.
518
+ * @protected
519
+ */
520
+ resizeEditor_() {
521
+ const div = WidgetDiv.getDiv();
522
+ const bBox = this.getScaledBBox();
523
+ div.style.width = bBox.right - bBox.left + 'px';
524
+ div.style.height = bBox.bottom - bBox.top + 'px';
486
525
 
487
- const value = this.getValueFromEditorText_(text);
488
- this.setValue(value);
489
- this.forceRerender();
490
- this.resizeEditor_();
491
- }
492
- };
526
+ // In RTL mode block fields and LTR input fields the left edge moves,
527
+ // whereas the right edge is fixed. Reposition the editor.
528
+ const x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
529
+ const xy = new Coordinate(x, bBox.top);
493
530
 
494
- /**
495
- * Set the HTML input value and the field's internal value. The difference
496
- * between this and ``setValue`` is that this also updates the HTML input
497
- * value whilst editing.
498
- * @param {*} newValue New value.
499
- * @protected
500
- */
501
- FieldTextInput.prototype.setEditorValue_ = function(newValue) {
502
- this.isDirty_ = true;
503
- if (this.isBeingEdited_) {
504
- // In the case this method is passed an invalid value, we still
505
- // pass it through the transformation method `getEditorText` to deal
506
- // with. Otherwise, the internal field's state will be inconsistent
507
- // with what's shown to the user.
508
- this.htmlInput_.value = this.getEditorText_(newValue);
531
+ div.style.left = xy.x + 'px';
532
+ div.style.top = xy.y + 'px';
509
533
  }
510
- this.setValue(newValue);
511
- };
512
534
 
513
- /**
514
- * Resize the editor to fit the text.
515
- * @protected
516
- */
517
- FieldTextInput.prototype.resizeEditor_ = function() {
518
- const div = WidgetDiv.getDiv();
519
- const bBox = this.getScaledBBox();
520
- div.style.width = bBox.right - bBox.left + 'px';
521
- div.style.height = bBox.bottom - bBox.top + 'px';
535
+ /**
536
+ * Returns whether or not the field is tab navigable.
537
+ * @return {boolean} True if the field is tab navigable.
538
+ * @override
539
+ */
540
+ isTabNavigable() {
541
+ return true;
542
+ }
522
543
 
523
- // In RTL mode block fields and LTR input fields the left edge moves,
524
- // whereas the right edge is fixed. Reposition the editor.
525
- const x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
526
- const xy = new Coordinate(x, bBox.top);
544
+ /**
545
+ * Use the `getText_` developer hook to override the field's text
546
+ * representation. When we're currently editing, return the current HTML value
547
+ * instead. Otherwise, return null which tells the field to use the default
548
+ * behaviour (which is a string cast of the field's value).
549
+ * @return {?string} The HTML value if we're editing, otherwise null.
550
+ * @protected
551
+ * @override
552
+ */
553
+ getText_() {
554
+ if (this.isBeingEdited_ && this.htmlInput_) {
555
+ // We are currently editing, return the HTML input value instead.
556
+ return this.htmlInput_.value;
557
+ }
558
+ return null;
559
+ }
527
560
 
528
- div.style.left = xy.x + 'px';
529
- div.style.top = xy.y + 'px';
530
- };
561
+ /**
562
+ * Transform the provided value into a text to show in the HTML input.
563
+ * Override this method if the field's HTML input representation is different
564
+ * than the field's value. This should be coupled with an override of
565
+ * `getValueFromEditorText_`.
566
+ * @param {*} value The value stored in this field.
567
+ * @return {string} The text to show on the HTML input.
568
+ * @protected
569
+ */
570
+ getEditorText_(value) {
571
+ return String(value);
572
+ }
531
573
 
532
- /**
533
- * Returns whether or not the field is tab navigable.
534
- * @return {boolean} True if the field is tab navigable.
535
- * @override
536
- */
537
- FieldTextInput.prototype.isTabNavigable = function() {
538
- return true;
539
- };
574
+ /**
575
+ * Transform the text received from the HTML input into a value to store
576
+ * in this field.
577
+ * Override this method if the field's HTML input representation is different
578
+ * than the field's value. This should be coupled with an override of
579
+ * `getEditorText_`.
580
+ * @param {string} text Text received from the HTML input.
581
+ * @return {*} The value to store.
582
+ * @protected
583
+ */
584
+ getValueFromEditorText_(text) {
585
+ return text;
586
+ }
540
587
 
541
- /**
542
- * Use the `getText_` developer hook to override the field's text
543
- * representation. When we're currently editing, return the current HTML value
544
- * instead. Otherwise, return null which tells the field to use the default
545
- * behaviour (which is a string cast of the field's value).
546
- * @return {?string} The HTML value if we're editing, otherwise null.
547
- * @protected
548
- * @override
549
- */
550
- FieldTextInput.prototype.getText_ = function() {
551
- if (this.isBeingEdited_ && this.htmlInput_) {
552
- // We are currently editing, return the HTML input value instead.
553
- return this.htmlInput_.value;
588
+ /**
589
+ * Construct a FieldTextInput from a JSON arg object,
590
+ * dereferencing any string table references.
591
+ * @param {!Object} options A JSON object with options (text, and spellcheck).
592
+ * @return {!FieldTextInput} The new field instance.
593
+ * @package
594
+ * @nocollapse
595
+ */
596
+ static fromJson(options) {
597
+ const text = parsing.replaceMessageReferences(options['text']);
598
+ // `this` might be a subclass of FieldTextInput if that class doesn't
599
+ // override the static fromJson method.
600
+ return new this(text, undefined, options);
554
601
  }
555
- return null;
556
- };
602
+ }
557
603
 
558
604
  /**
559
- * Transform the provided value into a text to show in the HTML input.
560
- * Override this method if the field's HTML input representation is different
561
- * than the field's value. This should be coupled with an override of
562
- * `getValueFromEditorText_`.
563
- * @param {*} value The value stored in this field.
564
- * @return {string} The text to show on the HTML input.
605
+ * The default value for this field.
606
+ * @type {*}
565
607
  * @protected
566
608
  */
567
- FieldTextInput.prototype.getEditorText_ = function(value) {
568
- return String(value);
569
- };
609
+ FieldTextInput.prototype.DEFAULT_VALUE = '';
570
610
 
571
611
  /**
572
- * Transform the text received from the HTML input into a value to store
573
- * in this field.
574
- * Override this method if the field's HTML input representation is different
575
- * than the field's value. This should be coupled with an override of
576
- * `getEditorText_`.
577
- * @param {string} text Text received from the HTML input.
578
- * @return {*} The value to store.
579
- * @protected
612
+ * Pixel size of input border radius.
613
+ * Should match blocklyText's border-radius in CSS.
580
614
  */
581
- FieldTextInput.prototype.getValueFromEditorText_ = function(text) {
582
- return text;
583
- };
615
+ FieldTextInput.BORDERRADIUS = 4;
584
616
 
585
617
  fieldRegistry.register('field_input', FieldTextInput);
586
618