blockly 7.20211209.3 → 8.0.1-beta.0

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 (363) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +582 -546
  3. package/blockly_compressed.js +448 -416
  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 +33 -18
  8. package/blocks/logic.js +24 -8
  9. package/blocks/loops.js +20 -7
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +41 -27
  12. package/blocks/text.js +33 -18
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +129 -126
  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 +20 -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/utils/toolbox.js +1 -1
  193. package/core/variable_map.js +325 -341
  194. package/core/variable_model.js +55 -54
  195. package/core/variables.js +9 -2
  196. package/core/variables_dynamic.js +3 -1
  197. package/core/warning.js +126 -120
  198. package/core/widgetdiv.js +4 -4
  199. package/core/workspace.js +685 -664
  200. package/core/workspace_audio.js +124 -118
  201. package/core/workspace_comment.js +308 -298
  202. package/core/workspace_comment_svg.js +1029 -951
  203. package/core/workspace_drag_surface_svg.js +147 -140
  204. package/core/workspace_dragger.js +70 -71
  205. package/core/workspace_svg.js +2322 -2297
  206. package/core/xml.js +30 -20
  207. package/core/zoom_controls.js +431 -439
  208. package/dart_compressed.js +5 -4
  209. package/dart_compressed.js.map +1 -1
  210. package/generators/dart/colour.js +56 -64
  211. package/generators/dart/lists.js +61 -50
  212. package/generators/dart/math.js +160 -148
  213. package/generators/dart/text.js +83 -61
  214. package/generators/javascript/colour.js +37 -34
  215. package/generators/javascript/lists.js +50 -43
  216. package/generators/javascript/math.js +123 -139
  217. package/generators/javascript/text.js +67 -81
  218. package/generators/lua/colour.js +25 -23
  219. package/generators/lua/lists.js +97 -69
  220. package/generators/lua/logic.js +1 -2
  221. package/generators/lua/math.js +182 -144
  222. package/generators/lua/text.js +116 -99
  223. package/generators/php/colour.js +38 -32
  224. package/generators/php/lists.js +109 -89
  225. package/generators/php/math.js +90 -81
  226. package/generators/php/text.js +63 -61
  227. package/generators/python/colour.js +18 -18
  228. package/generators/python/lists.js +38 -30
  229. package/generators/python/loops.js +12 -8
  230. package/generators/python/math.js +104 -106
  231. package/generators/python/text.js +34 -30
  232. package/javascript_compressed.js +5 -4
  233. package/javascript_compressed.js.map +1 -1
  234. package/lua_compressed.js +5 -4
  235. package/lua_compressed.js.map +1 -1
  236. package/msg/ab.js +1 -2
  237. package/msg/ace.js +1 -2
  238. package/msg/af.js +1 -2
  239. package/msg/am.js +1 -2
  240. package/msg/ar.js +1 -2
  241. package/msg/ast.js +1 -2
  242. package/msg/az.js +3 -4
  243. package/msg/ba.js +1 -2
  244. package/msg/bcc.js +1 -2
  245. package/msg/be-tarask.js +1 -2
  246. package/msg/be.js +5 -6
  247. package/msg/bg.js +1 -2
  248. package/msg/bn.js +1 -2
  249. package/msg/br.js +1 -2
  250. package/msg/bs.js +1 -2
  251. package/msg/ca.js +1 -2
  252. package/msg/cdo.js +1 -2
  253. package/msg/cs.js +16 -17
  254. package/msg/da.js +1 -2
  255. package/msg/de.js +2 -3
  256. package/msg/diq.js +2 -3
  257. package/msg/dty.js +1 -2
  258. package/msg/ee.js +1 -2
  259. package/msg/el.js +1 -2
  260. package/msg/en-gb.js +1 -2
  261. package/msg/en.js +1 -2
  262. package/msg/eo.js +2 -3
  263. package/msg/es.js +2 -3
  264. package/msg/et.js +1 -2
  265. package/msg/eu.js +1 -2
  266. package/msg/fa.js +2 -3
  267. package/msg/fi.js +1 -2
  268. package/msg/fo.js +1 -2
  269. package/msg/fr.js +5 -6
  270. package/msg/frr.js +1 -2
  271. package/msg/gl.js +1 -2
  272. package/msg/gn.js +1 -2
  273. package/msg/gor.js +1 -2
  274. package/msg/ha.js +1 -2
  275. package/msg/hak.js +1 -2
  276. package/msg/he.js +2 -3
  277. package/msg/hi.js +1 -2
  278. package/msg/hr.js +3 -4
  279. package/msg/hrx.js +1 -2
  280. package/msg/hu.js +1 -2
  281. package/msg/hy.js +3 -4
  282. package/msg/ia.js +1 -2
  283. package/msg/id.js +13 -14
  284. package/msg/ig.js +1 -2
  285. package/msg/inh.js +15 -16
  286. package/msg/is.js +1 -2
  287. package/msg/it.js +1 -2
  288. package/msg/ja.js +8 -9
  289. package/msg/ka.js +1 -2
  290. package/msg/kab.js +1 -2
  291. package/msg/kbd-cyrl.js +1 -2
  292. package/msg/km.js +1 -2
  293. package/msg/kn.js +1 -2
  294. package/msg/ko.js +1 -2
  295. package/msg/ksh.js +1 -2
  296. package/msg/ku-latn.js +1 -2
  297. package/msg/ky.js +1 -2
  298. package/msg/la.js +1 -2
  299. package/msg/lb.js +1 -2
  300. package/msg/lki.js +1 -2
  301. package/msg/lo.js +1 -2
  302. package/msg/lrc.js +1 -2
  303. package/msg/lt.js +1 -2
  304. package/msg/lv.js +30 -31
  305. package/msg/mg.js +1 -2
  306. package/msg/mk.js +1 -2
  307. package/msg/ml.js +1 -2
  308. package/msg/mnw.js +1 -2
  309. package/msg/ms.js +1 -2
  310. package/msg/my.js +1 -2
  311. package/msg/mzn.js +1 -2
  312. package/msg/nb.js +1 -2
  313. package/msg/ne.js +1 -2
  314. package/msg/nl.js +1 -2
  315. package/msg/oc.js +1 -2
  316. package/msg/olo.js +1 -2
  317. package/msg/pa.js +4 -5
  318. package/msg/pl.js +1 -2
  319. package/msg/pms.js +1 -2
  320. package/msg/ps.js +1 -2
  321. package/msg/pt-br.js +1 -2
  322. package/msg/pt.js +1 -2
  323. package/msg/ro.js +1 -2
  324. package/msg/ru.js +1 -2
  325. package/msg/sc.js +1 -2
  326. package/msg/sco.js +1 -2
  327. package/msg/sd.js +1 -2
  328. package/msg/shn.js +1 -2
  329. package/msg/si.js +1 -2
  330. package/msg/sk.js +1 -2
  331. package/msg/skr-arab.js +1 -2
  332. package/msg/sl.js +1 -2
  333. package/msg/smn.js +435 -0
  334. package/msg/sq.js +1 -2
  335. package/msg/sr-latn.js +1 -2
  336. package/msg/sr.js +1 -2
  337. package/msg/sv.js +1 -2
  338. package/msg/sw.js +1 -2
  339. package/msg/ta.js +1 -2
  340. package/msg/tcy.js +1 -2
  341. package/msg/te.js +2 -3
  342. package/msg/th.js +1 -2
  343. package/msg/ti.js +1 -2
  344. package/msg/tl.js +1 -2
  345. package/msg/tlh.js +1 -2
  346. package/msg/tr.js +1 -2
  347. package/msg/ug-arab.js +1 -2
  348. package/msg/uk.js +1 -2
  349. package/msg/ur.js +1 -2
  350. package/msg/uz.js +1 -2
  351. package/msg/vi.js +1 -2
  352. package/msg/xmf.js +1 -2
  353. package/msg/yo.js +1 -2
  354. package/msg/yue.js +2 -3
  355. package/msg/zgh.js +1 -2
  356. package/msg/zh-hans.js +4 -5
  357. package/msg/zh-hant.js +4 -5
  358. package/package.json +7 -6
  359. package/php_compressed.js +5 -4
  360. package/php_compressed.js.map +1 -1
  361. package/python_compressed.js +5 -4
  362. package/python_compressed.js.map +1 -1
  363. package/blocks/all.js +0 -36
package/core/field.js CHANGED
@@ -24,6 +24,7 @@ const WidgetDiv = goog.require('Blockly.WidgetDiv');
24
24
  const Xml = goog.require('Blockly.Xml');
25
25
  const browserEvents = goog.require('Blockly.browserEvents');
26
26
  const dom = goog.require('Blockly.utils.dom');
27
+ const dropDownDiv = goog.require('Blockly.dropDownDiv');
27
28
  const eventUtils = goog.require('Blockly.Events.utils');
28
29
  const parsing = goog.require('Blockly.utils.parsing');
29
30
  const style = goog.require('Blockly.utils.style');
@@ -37,7 +38,6 @@ const {Block} = goog.requireType('Blockly.Block');
37
38
  const {ConstantProvider} = goog.requireType('Blockly.blockRendering.ConstantProvider');
38
39
  /* eslint-disable-next-line no-unused-vars */
39
40
  const {Coordinate} = goog.requireType('Blockly.utils.Coordinate');
40
- const {DropDownDiv} = goog.require('Blockly.DropDownDiv');
41
41
  /* eslint-disable-next-line no-unused-vars */
42
42
  const {IASTNodeLocationSvg} = goog.require('Blockly.IASTNodeLocationSvg');
43
43
  /* eslint-disable-next-line no-unused-vars */
@@ -50,6 +50,7 @@ const {IRegistrable} = goog.require('Blockly.IRegistrable');
50
50
  const {Input} = goog.requireType('Blockly.Input');
51
51
  const {MarkerManager} = goog.require('Blockly.MarkerManager');
52
52
  const {Rect} = goog.require('Blockly.utils.Rect');
53
+ const {Sentinel} = goog.require('Blockly.utils.Sentinel');
53
54
  /* eslint-disable-next-line no-unused-vars */
54
55
  const {ShortcutRegistry} = goog.requireType('Blockly.ShortcutRegistry');
55
56
  const {Size} = goog.require('Blockly.utils.Size');
@@ -64,1155 +65,1218 @@ goog.require('Blockly.Gesture');
64
65
 
65
66
  /**
66
67
  * Abstract class for an editable field.
67
- * @param {*} value The initial value of the field.
68
- * @param {?Function=} opt_validator A function that is called to validate
69
- * changes to the field's value. Takes in a value & returns a validated
70
- * value, or null to abort the change.
71
- * @param {Object=} opt_config A map of options used to configure the field. See
72
- * the individual field's documentation for a list of properties this
73
- * parameter supports.
74
- * @constructor
75
- * @abstract
76
68
  * @implements {IASTNodeLocationSvg}
77
69
  * @implements {IASTNodeLocationWithBlock}
78
70
  * @implements {IKeyboardAccessible}
79
71
  * @implements {IRegistrable}
72
+ * @abstract
80
73
  * @alias Blockly.Field
81
74
  */
82
- const Field = function(value, opt_validator, opt_config) {
75
+ class Field {
83
76
  /**
84
- * A generic value possessed by the field.
85
- * Should generally be non-null, only null when the field is created.
86
- * @type {*}
87
- * @protected
77
+ * @param {*} value The initial value of the field.
78
+ * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
79
+ * subclasses that want to handle configuration and setting the field
80
+ * value after their own constructors have run).
81
+ * @param {?Function=} opt_validator A function that is called to validate
82
+ * changes to the field's value. Takes in a value & returns a validated
83
+ * value, or null to abort the change.
84
+ * @param {Object=} opt_config A map of options used to configure the field.
85
+ * Refer to the individual field's documentation for a list of properties
86
+ * this parameter supports.
88
87
  */
89
- this.value_ = this.DEFAULT_VALUE;
88
+ constructor(value, opt_validator, opt_config) {
89
+ /**
90
+ * Name of field. Unique within each block.
91
+ * Static labels are usually unnamed.
92
+ * @type {string|undefined}
93
+ */
94
+ this.name = undefined;
95
+
96
+ /**
97
+ * A generic value possessed by the field.
98
+ * Should generally be non-null, only null when the field is created.
99
+ * @type {*}
100
+ * @protected
101
+ */
102
+ this.value_ =
103
+ /** @type {typeof Field} */ (new.target).prototype.DEFAULT_VALUE;
104
+
105
+ /**
106
+ * Validation function called when user edits an editable field.
107
+ * @type {Function}
108
+ * @protected
109
+ */
110
+ this.validator_ = null;
111
+
112
+ /**
113
+ * Used to cache the field's tooltip value if setTooltip is called when the
114
+ * field is not yet initialized. Is *not* guaranteed to be accurate.
115
+ * @type {?Tooltip.TipInfo}
116
+ * @private
117
+ */
118
+ this.tooltip_ = null;
119
+
120
+ /**
121
+ * The size of the area rendered by the field.
122
+ * @type {!Size}
123
+ * @protected
124
+ */
125
+ this.size_ = new Size(0, 0);
126
+
127
+ /**
128
+ * Holds the cursors svg element when the cursor is attached to the field.
129
+ * This is null if there is no cursor on the field.
130
+ * @type {SVGElement}
131
+ * @private
132
+ */
133
+ this.cursorSvg_ = null;
134
+
135
+ /**
136
+ * Holds the markers svg element when the marker is attached to the field.
137
+ * This is null if there is no marker on the field.
138
+ * @type {SVGElement}
139
+ * @private
140
+ */
141
+ this.markerSvg_ = null;
142
+
143
+ /**
144
+ * The rendered field's SVG group element.
145
+ * @type {SVGGElement}
146
+ * @protected
147
+ */
148
+ this.fieldGroup_ = null;
149
+
150
+ /**
151
+ * The rendered field's SVG border element.
152
+ * @type {SVGRectElement}
153
+ * @protected
154
+ */
155
+ this.borderRect_ = null;
156
+
157
+ /**
158
+ * The rendered field's SVG text element.
159
+ * @type {SVGTextElement}
160
+ * @protected
161
+ */
162
+ this.textElement_ = null;
163
+
164
+ /**
165
+ * The rendered field's text content element.
166
+ * @type {Text}
167
+ * @protected
168
+ */
169
+ this.textContent_ = null;
170
+
171
+ /**
172
+ * Mouse down event listener data.
173
+ * @type {?browserEvents.Data}
174
+ * @private
175
+ */
176
+ this.mouseDownWrapper_ = null;
177
+
178
+ /**
179
+ * Constants associated with the source block's renderer.
180
+ * @type {ConstantProvider}
181
+ * @protected
182
+ */
183
+ this.constants_ = null;
184
+
185
+ /**
186
+ * Has this field been disposed of?
187
+ * @type {boolean}
188
+ * @package
189
+ */
190
+ this.disposed = false;
191
+
192
+ /**
193
+ * Maximum characters of text to display before adding an ellipsis.
194
+ * @type {number}
195
+ */
196
+ this.maxDisplayLength = 50;
197
+
198
+ /**
199
+ * Block this field is attached to. Starts as null, then set in init.
200
+ * @type {Block}
201
+ * @protected
202
+ */
203
+ this.sourceBlock_ = null;
204
+
205
+ /**
206
+ * Does this block need to be re-rendered?
207
+ * @type {boolean}
208
+ * @protected
209
+ */
210
+ this.isDirty_ = true;
211
+
212
+ /**
213
+ * Is the field visible, or hidden due to the block being collapsed?
214
+ * @type {boolean}
215
+ * @protected
216
+ */
217
+ this.visible_ = true;
218
+
219
+ /**
220
+ * Can the field value be changed using the editor on an editable block?
221
+ * @type {boolean}
222
+ * @protected
223
+ */
224
+ this.enabled_ = true;
225
+
226
+ /**
227
+ * The element the click handler is bound to.
228
+ * @type {Element}
229
+ * @protected
230
+ */
231
+ this.clickTarget_ = null;
232
+
233
+ /**
234
+ * The prefix field.
235
+ * @type {?string}
236
+ * @package
237
+ */
238
+ this.prefixField = null;
239
+
240
+ /**
241
+ * The suffix field.
242
+ * @type {?string}
243
+ * @package
244
+ */
245
+ this.suffixField = null;
246
+
247
+ /**
248
+ * Editable fields usually show some sort of UI indicating they are
249
+ * editable. They will also be saved by the serializer.
250
+ * @type {boolean}
251
+ */
252
+ this.EDITABLE = true;
253
+
254
+ /**
255
+ * Serializable fields are saved by the serializer, non-serializable fields
256
+ * are not. Editable fields should also be serializable. This is not the
257
+ * case by default so that SERIALIZABLE is backwards compatible.
258
+ * @type {boolean}
259
+ */
260
+ this.SERIALIZABLE = false;
261
+
262
+ /**
263
+ * Mouse cursor style when over the hotspot that initiates the editor.
264
+ * @type {string}
265
+ */
266
+ this.CURSOR = '';
267
+
268
+ if (value === Field.SKIP_SETUP) return;
269
+ if (opt_config) this.configure_(opt_config);
270
+ this.setValue(value);
271
+ if (opt_validator) this.setValidator(opt_validator);
272
+ }
90
273
 
91
274
  /**
92
- * Validation function called when user edits an editable field.
93
- * @type {Function}
275
+ * Process the configuration map passed to the field.
276
+ * @param {!Object} config A map of options used to configure the field. See
277
+ * the individual field's documentation for a list of properties this
278
+ * parameter supports.
94
279
  * @protected
95
280
  */
96
- this.validator_ = null;
281
+ configure_(config) {
282
+ let tooltip = config['tooltip'];
283
+ if (typeof tooltip === 'string') {
284
+ tooltip = parsing.replaceMessageReferences(config['tooltip']);
285
+ }
286
+ tooltip && this.setTooltip(tooltip);
287
+
288
+ // TODO (#2884): Possibly add CSS class config option.
289
+ // TODO (#2885): Possibly add cursor config option.
290
+ }
97
291
 
98
292
  /**
99
- * Used to cache the field's tooltip value if setTooltip is called when the
100
- * field is not yet initialized. Is *not* guaranteed to be accurate.
101
- * @type {?Tooltip.TipInfo}
102
- * @private
293
+ * Attach this field to a block.
294
+ * @param {!Block} block The block containing this field.
103
295
  */
104
- this.tooltip_ = null;
296
+ setSourceBlock(block) {
297
+ if (this.sourceBlock_) {
298
+ throw Error('Field already bound to a block');
299
+ }
300
+ this.sourceBlock_ = block;
301
+ }
105
302
 
106
303
  /**
107
- * The size of the area rendered by the field.
108
- * @type {!Size}
109
- * @protected
304
+ * Get the renderer constant provider.
305
+ * @return {?ConstantProvider} The renderer constant
306
+ * provider.
110
307
  */
111
- this.size_ = new Size(0, 0);
308
+ getConstants() {
309
+ if (!this.constants_ && this.sourceBlock_ && this.sourceBlock_.workspace &&
310
+ this.sourceBlock_.workspace.rendered) {
311
+ this.constants_ =
312
+ /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace)
313
+ .getRenderer()
314
+ .getConstants();
315
+ }
316
+ return this.constants_;
317
+ }
112
318
 
113
319
  /**
114
- * Holds the cursors svg element when the cursor is attached to the field.
115
- * This is null if there is no cursor on the field.
116
- * @type {SVGElement}
117
- * @private
320
+ * Get the block this field is attached to.
321
+ * @return {Block} The block containing this field.
118
322
  */
119
- this.cursorSvg_ = null;
323
+ getSourceBlock() {
324
+ return this.sourceBlock_;
325
+ }
120
326
 
121
327
  /**
122
- * Holds the markers svg element when the marker is attached to the field.
123
- * This is null if there is no marker on the field.
124
- * @type {SVGElement}
125
- * @private
328
+ * Initialize everything to render this field. Override
329
+ * methods initModel and initView rather than this method.
330
+ * @package
331
+ * @final
126
332
  */
127
- this.markerSvg_ = null;
333
+ init() {
334
+ if (this.fieldGroup_) {
335
+ // Field has already been initialized once.
336
+ return;
337
+ }
338
+ this.fieldGroup_ = dom.createSvgElement(Svg.G, {}, null);
339
+ if (!this.isVisible()) {
340
+ this.fieldGroup_.style.display = 'none';
341
+ }
342
+ const sourceBlockSvg = /** @type {!BlockSvg} **/ (this.sourceBlock_);
343
+ sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_);
344
+ this.initView();
345
+ this.updateEditable();
346
+ this.setTooltip(this.tooltip_);
347
+ this.bindEvents_();
348
+ this.initModel();
349
+ }
128
350
 
129
351
  /**
130
- * The rendered field's SVG group element.
131
- * @type {SVGGElement}
132
- * @protected
352
+ * Create the block UI for this field.
353
+ * @package
133
354
  */
134
- this.fieldGroup_ = null;
355
+ initView() {
356
+ this.createBorderRect_();
357
+ this.createTextElement_();
358
+ }
135
359
 
136
360
  /**
137
- * The rendered field's SVG border element.
138
- * @type {SVGRectElement}
139
- * @protected
361
+ * Initializes the model of the field after it has been installed on a block.
362
+ * No-op by default.
363
+ * @package
140
364
  */
141
- this.borderRect_ = null;
365
+ initModel() {}
142
366
 
143
367
  /**
144
- * The rendered field's SVG text element.
145
- * @type {SVGTextElement}
368
+ * Create a field border rect element. Not to be overridden by subclasses.
369
+ * Instead modify the result of the function inside initView, or create a
370
+ * separate function to call.
146
371
  * @protected
147
372
  */
148
- this.textElement_ = null;
373
+ createBorderRect_() {
374
+ this.borderRect_ = dom.createSvgElement(
375
+ Svg.RECT, {
376
+ 'rx': this.getConstants().FIELD_BORDER_RECT_RADIUS,
377
+ 'ry': this.getConstants().FIELD_BORDER_RECT_RADIUS,
378
+ 'x': 0,
379
+ 'y': 0,
380
+ 'height': this.size_.height,
381
+ 'width': this.size_.width,
382
+ 'class': 'blocklyFieldRect',
383
+ },
384
+ this.fieldGroup_);
385
+ }
149
386
 
150
387
  /**
151
- * The rendered field's text content element.
152
- * @type {Text}
388
+ * Create a field text element. Not to be overridden by subclasses. Instead
389
+ * modify the result of the function inside initView, or create a separate
390
+ * function to call.
153
391
  * @protected
154
392
  */
155
- this.textContent_ = null;
393
+ createTextElement_() {
394
+ this.textElement_ = dom.createSvgElement(
395
+ Svg.TEXT, {
396
+ 'class': 'blocklyText',
397
+ },
398
+ this.fieldGroup_);
399
+ if (this.getConstants().FIELD_TEXT_BASELINE_CENTER) {
400
+ this.textElement_.setAttribute('dominant-baseline', 'central');
401
+ }
402
+ this.textContent_ = document.createTextNode('');
403
+ this.textElement_.appendChild(this.textContent_);
404
+ }
156
405
 
157
406
  /**
158
- * Mouse down event listener data.
159
- * @type {?browserEvents.Data}
160
- * @private
407
+ * Bind events to the field. Can be overridden by subclasses if they need to
408
+ * do custom input handling.
409
+ * @protected
161
410
  */
162
- this.mouseDownWrapper_ = null;
411
+ bindEvents_() {
412
+ Tooltip.bindMouseEvents(this.getClickTarget_());
413
+ this.mouseDownWrapper_ = browserEvents.conditionalBind(
414
+ this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
415
+ }
163
416
 
164
417
  /**
165
- * Constants associated with the source block's renderer.
166
- * @type {ConstantProvider}
167
- * @protected
418
+ * Sets the field's value based on the given XML element. Should only be
419
+ * called by Blockly.Xml.
420
+ * @param {!Element} fieldElement The element containing info about the
421
+ * field's state.
422
+ * @package
168
423
  */
169
- this.constants_ = null;
170
-
171
- opt_config && this.configure_(opt_config);
172
- this.setValue(value);
173
- opt_validator && this.setValidator(opt_validator);
174
- };
175
-
176
- /**
177
- * The default value for this field.
178
- * @type {*}
179
- * @protected
180
- */
181
- Field.prototype.DEFAULT_VALUE = null;
182
-
183
- /**
184
- * Name of field. Unique within each block.
185
- * Static labels are usually unnamed.
186
- * @type {string|undefined}
187
- */
188
- Field.prototype.name = undefined;
189
-
190
- /**
191
- * Has this field been disposed of?
192
- * @type {boolean}
193
- * @package
194
- */
195
- Field.prototype.disposed = false;
196
-
197
- /**
198
- * Maximum characters of text to display before adding an ellipsis.
199
- * @type {number}
200
- */
201
- Field.prototype.maxDisplayLength = 50;
202
-
203
- /**
204
- * Block this field is attached to. Starts as null, then set in init.
205
- * @type {Block}
206
- * @protected
207
- */
208
- Field.prototype.sourceBlock_ = null;
209
-
210
- /**
211
- * Does this block need to be re-rendered?
212
- * @type {boolean}
213
- * @protected
214
- */
215
- Field.prototype.isDirty_ = true;
216
-
217
- /**
218
- * Is the field visible, or hidden due to the block being collapsed?
219
- * @type {boolean}
220
- * @protected
221
- */
222
- Field.prototype.visible_ = true;
223
-
224
- /**
225
- * Can the field value be changed using the editor on an editable block?
226
- * @type {boolean}
227
- * @protected
228
- */
229
- Field.prototype.enabled_ = true;
230
-
231
- /**
232
- * The element the click handler is bound to.
233
- * @type {Element}
234
- * @protected
235
- */
236
- Field.prototype.clickTarget_ = null;
237
-
238
- /**
239
- * A developer hook to override the returned text of this field.
240
- * Override if the text representation of the value of this field
241
- * is not just a string cast of its value.
242
- * Return null to resort to a string cast.
243
- * @return {?string} Current text. Return null to resort to a string cast.
244
- * @protected
245
- */
246
- Field.prototype.getText_;
247
-
248
- /**
249
- * An optional method that can be defined to show an editor when the field is
250
- * clicked. Blockly will automatically set the field as clickable if this
251
- * method is defined.
252
- * @param {Event=} opt_e Optional mouse event that triggered the field to open,
253
- * or undefined if triggered programmatically.
254
- * @return {void}
255
- * @protected
256
- */
257
- Field.prototype.showEditor_;
258
-
259
- /**
260
- * Non-breaking space.
261
- * @const
262
- */
263
- Field.NBSP = '\u00A0';
264
-
265
- /**
266
- * Editable fields usually show some sort of UI indicating they are editable.
267
- * They will also be saved by the XML renderer.
268
- * @type {boolean}
269
- */
270
- Field.prototype.EDITABLE = true;
271
-
272
- /**
273
- * Serializable fields are saved by the XML renderer, non-serializable fields
274
- * are not. Editable fields should also be serializable. This is not the
275
- * case by default so that SERIALIZABLE is backwards compatible.
276
- * @type {boolean}
277
- */
278
- Field.prototype.SERIALIZABLE = false;
279
-
280
- /**
281
- * Process the configuration map passed to the field.
282
- * @param {!Object} config A map of options used to configure the field. See
283
- * the individual field's documentation for a list of properties this
284
- * parameter supports.
285
- * @protected
286
- */
287
- Field.prototype.configure_ = function(config) {
288
- let tooltip = config['tooltip'];
289
- if (typeof tooltip === 'string') {
290
- tooltip = parsing.replaceMessageReferences(config['tooltip']);
424
+ fromXml(fieldElement) {
425
+ this.setValue(fieldElement.textContent);
291
426
  }
292
- tooltip && this.setTooltip(tooltip);
293
-
294
- // TODO (#2884): Possibly add CSS class config option.
295
- // TODO (#2885): Possibly add cursor config option.
296
- };
297
427
 
298
- /**
299
- * Attach this field to a block.
300
- * @param {!Block} block The block containing this field.
301
- */
302
- Field.prototype.setSourceBlock = function(block) {
303
- if (this.sourceBlock_) {
304
- throw Error('Field already bound to a block');
428
+ /**
429
+ * Serializes this field's value to XML. Should only be called by Blockly.Xml.
430
+ * @param {!Element} fieldElement The element to populate with info about the
431
+ * field's state.
432
+ * @return {!Element} The element containing info about the field's state.
433
+ * @package
434
+ */
435
+ toXml(fieldElement) {
436
+ fieldElement.textContent = this.getValue();
437
+ return fieldElement;
305
438
  }
306
- this.sourceBlock_ = block;
307
- };
308
439
 
309
- /**
310
- * Get the renderer constant provider.
311
- * @return {?ConstantProvider} The renderer constant
312
- * provider.
313
- */
314
- Field.prototype.getConstants = function() {
315
- if (!this.constants_ && this.sourceBlock_ && this.sourceBlock_.workspace &&
316
- this.sourceBlock_.workspace.rendered) {
317
- this.constants_ = this.sourceBlock_.workspace.getRenderer().getConstants();
440
+ /**
441
+ * Saves this fields value as something which can be serialized to JSON.
442
+ * Should only be called by the serialization system.
443
+ * @param {boolean=} _doFullSerialization If true, this signals to the field
444
+ * that if it normally just saves a reference to some state (eg variable
445
+ * fields) it should instead serialize the full state of the thing being
446
+ * referenced.
447
+ * @return {*} JSON serializable state.
448
+ * @package
449
+ */
450
+ saveState(_doFullSerialization) {
451
+ const legacyState = this.saveLegacyState(Field);
452
+ if (legacyState !== null) {
453
+ return legacyState;
454
+ }
455
+ return this.getValue();
318
456
  }
319
- return this.constants_;
320
- };
321
457
 
322
- /**
323
- * Get the block this field is attached to.
324
- * @return {Block} The block containing this field.
325
- */
326
- Field.prototype.getSourceBlock = function() {
327
- return this.sourceBlock_;
328
- };
329
-
330
- /**
331
- * Initialize everything to render this field. Override
332
- * methods initModel and initView rather than this method.
333
- * @package
334
- */
335
- Field.prototype.init = function() {
336
- if (this.fieldGroup_) {
337
- // Field has already been initialized once.
338
- return;
339
- }
340
- this.fieldGroup_ = dom.createSvgElement(Svg.G, {}, null);
341
- if (!this.isVisible()) {
342
- this.fieldGroup_.style.display = 'none';
458
+ /**
459
+ * Sets the field's state based on the given state value. Should only be
460
+ * called by the serialization system.
461
+ * @param {*} state The state we want to apply to the field.
462
+ * @package
463
+ */
464
+ loadState(state) {
465
+ if (this.loadLegacyState(Field, state)) {
466
+ return;
467
+ }
468
+ this.setValue(state);
343
469
  }
344
- const sourceBlockSvg = /** @type {!BlockSvg} **/ (this.sourceBlock_);
345
- sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_);
346
- this.initView();
347
- this.updateEditable();
348
- this.setTooltip(this.tooltip_);
349
- this.bindEvents_();
350
- this.initModel();
351
- };
352
-
353
- /**
354
- * Create the block UI for this field.
355
- * @package
356
- */
357
- Field.prototype.initView = function() {
358
- this.createBorderRect_();
359
- this.createTextElement_();
360
- };
361
-
362
- /**
363
- * Initializes the model of the field after it has been installed on a block.
364
- * No-op by default.
365
- * @package
366
- */
367
- Field.prototype.initModel = function() {};
368
470
 
369
- /**
370
- * Create a field border rect element. Not to be overridden by subclasses.
371
- * Instead modify the result of the function inside initView, or create a
372
- * separate function to call.
373
- * @protected
374
- */
375
- Field.prototype.createBorderRect_ = function() {
376
- this.borderRect_ = dom.createSvgElement(
377
- Svg.RECT, {
378
- 'rx': this.getConstants().FIELD_BORDER_RECT_RADIUS,
379
- 'ry': this.getConstants().FIELD_BORDER_RECT_RADIUS,
380
- 'x': 0,
381
- 'y': 0,
382
- 'height': this.size_.height,
383
- 'width': this.size_.width,
384
- 'class': 'blocklyFieldRect',
385
- },
386
- this.fieldGroup_);
387
- };
471
+ /**
472
+ * Returns a stringified version of the XML state, if it should be used.
473
+ * Otherwise this returns null, to signal the field should use its own
474
+ * serialization.
475
+ * @param {*} callingClass The class calling this method.
476
+ * Used to see if `this` has overridden any relevant hooks.
477
+ * @return {?string} The stringified version of the XML state, or null.
478
+ * @protected
479
+ */
480
+ saveLegacyState(callingClass) {
481
+ if (callingClass.prototype.saveState === this.saveState &&
482
+ callingClass.prototype.toXml !== this.toXml) {
483
+ const elem = utilsXml.createElement('field');
484
+ elem.setAttribute('name', this.name || '');
485
+ const text = Xml.domToText(this.toXml(elem));
486
+ return text.replace(
487
+ ' xmlns="https://developers.google.com/blockly/xml"', '');
488
+ }
489
+ // Either they called this on purpose from their saveState, or they have
490
+ // no implementations of either hook. Just do our thing.
491
+ return null;
492
+ }
388
493
 
389
- /**
390
- * Create a field text element. Not to be overridden by subclasses. Instead
391
- * modify the result of the function inside initView, or create a separate
392
- * function to call.
393
- * @protected
394
- */
395
- Field.prototype.createTextElement_ = function() {
396
- this.textElement_ = dom.createSvgElement(
397
- Svg.TEXT, {
398
- 'class': 'blocklyText',
399
- },
400
- this.fieldGroup_);
401
- if (this.getConstants().FIELD_TEXT_BASELINE_CENTER) {
402
- this.textElement_.setAttribute('dominant-baseline', 'central');
494
+ /**
495
+ * Loads the given state using either the old XML hoooks, if they should be
496
+ * used. Returns true to indicate loading has been handled, false otherwise.
497
+ * @param {*} callingClass The class calling this method.
498
+ * Used to see if `this` has overridden any relevant hooks.
499
+ * @param {*} state The state to apply to the field.
500
+ * @return {boolean} Whether the state was applied or not.
501
+ */
502
+ loadLegacyState(callingClass, state) {
503
+ if (callingClass.prototype.loadState === this.loadState &&
504
+ callingClass.prototype.fromXml !== this.fromXml) {
505
+ this.fromXml(Xml.textToDom(/** @type {string} */ (state)));
506
+ return true;
507
+ }
508
+ // Either they called this on purpose from their loadState, or they have
509
+ // no implementations of either hook. Just do our thing.
510
+ return false;
403
511
  }
404
- this.textContent_ = document.createTextNode('');
405
- this.textElement_.appendChild(this.textContent_);
406
- };
407
512
 
408
- /**
409
- * Bind events to the field. Can be overridden by subclasses if they need to do
410
- * custom input handling.
411
- * @protected
412
- */
413
- Field.prototype.bindEvents_ = function() {
414
- Tooltip.bindMouseEvents(this.getClickTarget_());
415
- this.mouseDownWrapper_ = browserEvents.conditionalBind(
416
- this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
417
- };
513
+ /**
514
+ * Dispose of all DOM objects and events belonging to this editable field.
515
+ * @package
516
+ */
517
+ dispose() {
518
+ dropDownDiv.hideIfOwner(this);
519
+ WidgetDiv.hideIfOwner(this);
520
+ Tooltip.unbindMouseEvents(this.getClickTarget_());
418
521
 
419
- /**
420
- * Sets the field's value based on the given XML element. Should only be called
421
- * by Blockly.Xml.
422
- * @param {!Element} fieldElement The element containing info about the
423
- * field's state.
424
- * @package
425
- */
426
- Field.prototype.fromXml = function(fieldElement) {
427
- this.setValue(fieldElement.textContent);
428
- };
522
+ if (this.mouseDownWrapper_) {
523
+ browserEvents.unbind(this.mouseDownWrapper_);
524
+ }
429
525
 
430
- /**
431
- * Serializes this field's value to XML. Should only be called by Blockly.Xml.
432
- * @param {!Element} fieldElement The element to populate with info about the
433
- * field's state.
434
- * @return {!Element} The element containing info about the field's state.
435
- * @package
436
- */
437
- Field.prototype.toXml = function(fieldElement) {
438
- fieldElement.textContent = this.getValue();
439
- return fieldElement;
440
- };
526
+ dom.removeNode(this.fieldGroup_);
441
527
 
442
- /**
443
- * Saves this fields value as something which can be serialized to JSON. Should
444
- * only be called by the serialization system.
445
- * @param {boolean=} _doFullSerialization If true, this signals to the field
446
- * that if it normally just saves a reference to some state (eg variable
447
- * fields) it should instead serialize the full state of the thing being
448
- * referenced.
449
- * @return {*} JSON serializable state.
450
- * @package
451
- */
452
- Field.prototype.saveState = function(_doFullSerialization) {
453
- const legacyState = this.saveLegacyState(Field);
454
- if (legacyState !== null) {
455
- return legacyState;
528
+ this.disposed = true;
456
529
  }
457
- return this.getValue();
458
- };
459
530
 
460
- /**
461
- * Sets the field's state based on the given state value. Should only be called
462
- * by the serialization system.
463
- * @param {*} state The state we want to apply to the field.
464
- * @package
465
- */
466
- Field.prototype.loadState = function(state) {
467
- if (this.loadLegacyState(Field, state)) {
468
- return;
531
+ /**
532
+ * Add or remove the UI indicating if this field is editable or not.
533
+ */
534
+ updateEditable() {
535
+ const group = this.fieldGroup_;
536
+ if (!this.EDITABLE || !group) {
537
+ return;
538
+ }
539
+ if (this.enabled_ && this.sourceBlock_.isEditable()) {
540
+ dom.addClass(group, 'blocklyEditableText');
541
+ dom.removeClass(group, 'blocklyNonEditableText');
542
+ group.style.cursor = this.CURSOR;
543
+ } else {
544
+ dom.addClass(group, 'blocklyNonEditableText');
545
+ dom.removeClass(group, 'blocklyEditableText');
546
+ group.style.cursor = '';
547
+ }
469
548
  }
470
- this.setValue(state);
471
- };
472
549
 
473
- /**
474
- * Returns a stringified version of the XML state, if it should be used.
475
- * Otherwise this returns null, to signal the field should use its own
476
- * serialization.
477
- * @param {*} callingClass The class calling this method.
478
- * Used to see if `this` has overridden any relevant hooks.
479
- * @return {?string} The stringified version of the XML state, or null.
480
- * @protected
481
- */
482
- Field.prototype.saveLegacyState = function(callingClass) {
483
- if (callingClass.prototype.saveState === this.saveState &&
484
- callingClass.prototype.toXml !== this.toXml) {
485
- const elem = utilsXml.createElement('field');
486
- elem.setAttribute('name', this.name || '');
487
- const text = Xml.domToText(this.toXml(elem));
488
- return text.replace(
489
- ' xmlns="https://developers.google.com/blockly/xml"', '');
550
+ /**
551
+ * Set whether this field's value can be changed using the editor when the
552
+ * source block is editable.
553
+ * @param {boolean} enabled True if enabled.
554
+ */
555
+ setEnabled(enabled) {
556
+ this.enabled_ = enabled;
557
+ this.updateEditable();
490
558
  }
491
- // Either they called this on purpose from their saveState, or they have
492
- // no implementations of either hook. Just do our thing.
493
- return null;
494
- };
495
559
 
496
- /**
497
- * Loads the given state using either the old XML hoooks, if they should be
498
- * used. Returns true to indicate loading has been handled, false otherwise.
499
- * @param {*} callingClass The class calling this method.
500
- * Used to see if `this` has overridden any relevant hooks.
501
- * @param {*} state The state to apply to the field.
502
- * @return {boolean} Whether the state was applied or not.
503
- */
504
- Field.prototype.loadLegacyState = function(callingClass, state) {
505
- if (callingClass.prototype.loadState === this.loadState &&
506
- callingClass.prototype.fromXml !== this.fromXml) {
507
- this.fromXml(Xml.textToDom(/** @type {string} */ (state)));
508
- return true;
560
+ /**
561
+ * Check whether this field's value can be changed using the editor when the
562
+ * source block is editable.
563
+ * @return {boolean} Whether this field is enabled.
564
+ */
565
+ isEnabled() {
566
+ return this.enabled_;
509
567
  }
510
- // Either they called this on purpose from their loadState, or they have
511
- // no implementations of either hook. Just do our thing.
512
- return false;
513
- };
514
568
 
515
- /**
516
- * Dispose of all DOM objects and events belonging to this editable field.
517
- * @package
518
- */
519
- Field.prototype.dispose = function() {
520
- DropDownDiv.hideIfOwner(this);
521
- WidgetDiv.hideIfOwner(this);
522
- Tooltip.unbindMouseEvents(this.getClickTarget_());
523
-
524
- if (this.mouseDownWrapper_) {
525
- browserEvents.unbind(this.mouseDownWrapper_);
569
+ /**
570
+ * Check whether this field defines the showEditor_ function.
571
+ * @return {boolean} Whether this field is clickable.
572
+ */
573
+ isClickable() {
574
+ return this.enabled_ && !!this.sourceBlock_ &&
575
+ this.sourceBlock_.isEditable() &&
576
+ this.showEditor_ !== Field.prototype.showEditor_;
526
577
  }
527
578
 
528
- dom.removeNode(this.fieldGroup_);
529
-
530
- this.disposed = true;
531
- };
532
-
533
- /**
534
- * Add or remove the UI indicating if this field is editable or not.
535
- */
536
- Field.prototype.updateEditable = function() {
537
- const group = this.fieldGroup_;
538
- if (!this.EDITABLE || !group) {
539
- return;
540
- }
541
- if (this.enabled_ && this.sourceBlock_.isEditable()) {
542
- dom.addClass(group, 'blocklyEditableText');
543
- dom.removeClass(group, 'blocklyNonEditableText');
544
- group.style.cursor = this.CURSOR;
545
- } else {
546
- dom.addClass(group, 'blocklyNonEditableText');
547
- dom.removeClass(group, 'blocklyEditableText');
548
- group.style.cursor = '';
579
+ /**
580
+ * Check whether this field is currently editable. Some fields are never
581
+ * EDITABLE (e.g. text labels). Other fields may be EDITABLE but may exist on
582
+ * non-editable blocks or be currently disabled.
583
+ * @return {boolean} Whether this field is currently enabled, editable and on
584
+ * an editable block.
585
+ */
586
+ isCurrentlyEditable() {
587
+ return this.enabled_ && this.EDITABLE && !!this.sourceBlock_ &&
588
+ this.sourceBlock_.isEditable();
549
589
  }
550
- };
551
-
552
- /**
553
- * Set whether this field's value can be changed using the editor when the
554
- * source block is editable.
555
- * @param {boolean} enabled True if enabled.
556
- */
557
- Field.prototype.setEnabled = function(enabled) {
558
- this.enabled_ = enabled;
559
- this.updateEditable();
560
- };
561
590
 
562
- /**
563
- * Check whether this field's value can be changed using the editor when the
564
- * source block is editable.
565
- * @return {boolean} Whether this field is enabled.
566
- */
567
- Field.prototype.isEnabled = function() {
568
- return this.enabled_;
569
- };
570
-
571
- /**
572
- * Check whether this field defines the showEditor_ function.
573
- * @return {boolean} Whether this field is clickable.
574
- */
575
- Field.prototype.isClickable = function() {
576
- return this.enabled_ && !!this.sourceBlock_ &&
577
- this.sourceBlock_.isEditable() && !!this.showEditor_ &&
578
- (typeof this.showEditor_ === 'function');
579
- };
580
-
581
- /**
582
- * Check whether this field is currently editable. Some fields are never
583
- * EDITABLE (e.g. text labels). Other fields may be EDITABLE but may exist on
584
- * non-editable blocks or be currently disabled.
585
- * @return {boolean} Whether this field is currently enabled, editable and on
586
- * an editable block.
587
- */
588
- Field.prototype.isCurrentlyEditable = function() {
589
- return this.enabled_ && this.EDITABLE && !!this.sourceBlock_ &&
590
- this.sourceBlock_.isEditable();
591
- };
592
-
593
- /**
594
- * Check whether this field should be serialized by the XML renderer.
595
- * Handles the logic for backwards compatibility and incongruous states.
596
- * @return {boolean} Whether this field should be serialized or not.
597
- */
598
- Field.prototype.isSerializable = function() {
599
- let isSerializable = false;
600
- if (this.name) {
601
- if (this.SERIALIZABLE) {
602
- isSerializable = true;
603
- } else if (this.EDITABLE) {
604
- console.warn(
605
- 'Detected an editable field that was not serializable.' +
606
- ' Please define SERIALIZABLE property as true on all editable custom' +
607
- ' fields. Proceeding with serialization.');
608
- isSerializable = true;
591
+ /**
592
+ * Check whether this field should be serialized by the XML renderer.
593
+ * Handles the logic for backwards compatibility and incongruous states.
594
+ * @return {boolean} Whether this field should be serialized or not.
595
+ */
596
+ isSerializable() {
597
+ let isSerializable = false;
598
+ if (this.name) {
599
+ if (this.SERIALIZABLE) {
600
+ isSerializable = true;
601
+ } else if (this.EDITABLE) {
602
+ console.warn(
603
+ 'Detected an editable field that was not serializable.' +
604
+ ' Please define SERIALIZABLE property as true on all editable custom' +
605
+ ' fields. Proceeding with serialization.');
606
+ isSerializable = true;
607
+ }
609
608
  }
609
+ return isSerializable;
610
610
  }
611
- return isSerializable;
612
- };
613
-
614
- /**
615
- * Gets whether this editable field is visible or not.
616
- * @return {boolean} True if visible.
617
- */
618
- Field.prototype.isVisible = function() {
619
- return this.visible_;
620
- };
621
611
 
622
- /**
623
- * Sets whether this editable field is visible or not. Should only be called
624
- * by input.setVisible.
625
- * @param {boolean} visible True if visible.
626
- * @package
627
- */
628
- Field.prototype.setVisible = function(visible) {
629
- if (this.visible_ === visible) {
630
- return;
631
- }
632
- this.visible_ = visible;
633
- const root = this.getSvgRoot();
634
- if (root) {
635
- root.style.display = visible ? 'block' : 'none';
612
+ /**
613
+ * Gets whether this editable field is visible or not.
614
+ * @return {boolean} True if visible.
615
+ */
616
+ isVisible() {
617
+ return this.visible_;
636
618
  }
637
- };
638
619
 
639
- /**
640
- * Sets a new validation function for editable fields, or clears a previously
641
- * set validator.
642
- *
643
- * The validator function takes in the new field value, and returns
644
- * validated value. The validated value could be the input value, a modified
645
- * version of the input value, or null to abort the change.
646
- *
647
- * If the function does not return anything (or returns undefined) the new
648
- * value is accepted as valid. This is to allow for fields using the
649
- * validated function as a field-level change event notification.
650
- *
651
- * @param {Function} handler The validator function
652
- * or null to clear a previous validator.
653
- */
654
- Field.prototype.setValidator = function(handler) {
655
- this.validator_ = handler;
656
- };
620
+ /**
621
+ * Sets whether this editable field is visible or not. Should only be called
622
+ * by input.setVisible.
623
+ * @param {boolean} visible True if visible.
624
+ * @package
625
+ */
626
+ setVisible(visible) {
627
+ if (this.visible_ === visible) {
628
+ return;
629
+ }
630
+ this.visible_ = visible;
631
+ const root = this.getSvgRoot();
632
+ if (root) {
633
+ root.style.display = visible ? 'block' : 'none';
634
+ }
635
+ }
657
636
 
658
- /**
659
- * Gets the validation function for editable fields, or null if not set.
660
- * @return {?Function} Validation function, or null.
661
- */
662
- Field.prototype.getValidator = function() {
663
- return this.validator_;
664
- };
637
+ /**
638
+ * Sets a new validation function for editable fields, or clears a previously
639
+ * set validator.
640
+ *
641
+ * The validator function takes in the new field value, and returns
642
+ * validated value. The validated value could be the input value, a modified
643
+ * version of the input value, or null to abort the change.
644
+ *
645
+ * If the function does not return anything (or returns undefined) the new
646
+ * value is accepted as valid. This is to allow for fields using the
647
+ * validated function as a field-level change event notification.
648
+ *
649
+ * @param {Function} handler The validator function
650
+ * or null to clear a previous validator.
651
+ */
652
+ setValidator(handler) {
653
+ this.validator_ = handler;
654
+ }
665
655
 
666
- /**
667
- * Gets the group element for this editable field.
668
- * Used for measuring the size and for positioning.
669
- * @return {!SVGGElement} The group element.
670
- */
671
- Field.prototype.getSvgRoot = function() {
672
- return /** @type {!SVGGElement} */ (this.fieldGroup_);
673
- };
656
+ /**
657
+ * Gets the validation function for editable fields, or null if not set.
658
+ * @return {?Function} Validation function, or null.
659
+ */
660
+ getValidator() {
661
+ return this.validator_;
662
+ }
674
663
 
675
- /**
676
- * Updates the field to match the colour/style of the block. Should only be
677
- * called by BlockSvg.applyColour().
678
- * @package
679
- */
680
- Field.prototype.applyColour = function() {
681
- // Non-abstract sub-classes may wish to implement this. See FieldDropdown.
682
- };
664
+ /**
665
+ * Gets the group element for this editable field.
666
+ * Used for measuring the size and for positioning.
667
+ * @return {!SVGGElement} The group element.
668
+ */
669
+ getSvgRoot() {
670
+ return /** @type {!SVGGElement} */ (this.fieldGroup_);
671
+ }
683
672
 
684
- /**
685
- * Used by getSize() to move/resize any DOM elements, and get the new size.
686
- *
687
- * All rendering that has an effect on the size/shape of the block should be
688
- * done here, and should be triggered by getSize().
689
- * @protected
690
- */
691
- Field.prototype.render_ = function() {
692
- if (this.textContent_) {
693
- this.textContent_.nodeValue = this.getDisplayText_();
673
+ /**
674
+ * Updates the field to match the colour/style of the block. Should only be
675
+ * called by BlockSvg.applyColour().
676
+ * @package
677
+ */
678
+ applyColour() {
679
+ // Non-abstract sub-classes may wish to implement this. See FieldDropdown.
694
680
  }
695
- this.updateSize_();
696
- };
697
681
 
698
- /**
699
- * Show an editor when the field is clicked only if the field is clickable.
700
- * @param {Event=} opt_e Optional mouse event that triggered the field to open,
701
- * or undefined if triggered programmatically.
702
- * @package
703
- */
704
- Field.prototype.showEditor = function(opt_e) {
705
- if (this.isClickable()) {
706
- this.showEditor_(opt_e);
682
+ /**
683
+ * Used by getSize() to move/resize any DOM elements, and get the new size.
684
+ *
685
+ * All rendering that has an effect on the size/shape of the block should be
686
+ * done here, and should be triggered by getSize().
687
+ * @protected
688
+ */
689
+ render_() {
690
+ if (this.textContent_) {
691
+ this.textContent_.nodeValue = this.getDisplayText_();
692
+ }
693
+ this.updateSize_();
707
694
  }
708
- };
709
695
 
710
- /**
711
- * Updates the size of the field based on the text.
712
- * @param {number=} opt_margin margin to use when positioning the text element.
713
- * @protected
714
- */
715
- Field.prototype.updateSize_ = function(opt_margin) {
716
- const constants = this.getConstants();
717
- const xOffset = opt_margin !== undefined ?
718
- opt_margin :
719
- (this.borderRect_ ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0);
720
- let totalWidth = xOffset * 2;
721
- let totalHeight = constants.FIELD_TEXT_HEIGHT;
722
-
723
- let contentWidth = 0;
724
- if (this.textElement_) {
725
- contentWidth = dom.getFastTextWidth(
726
- this.textElement_, constants.FIELD_TEXT_FONTSIZE,
727
- constants.FIELD_TEXT_FONTWEIGHT, constants.FIELD_TEXT_FONTFAMILY);
728
- totalWidth += contentWidth;
696
+ /**
697
+ * Calls showEditor_ when the field is clicked if the field is clickable.
698
+ * Do not override.
699
+ * @param {Event=} opt_e Optional mouse event that triggered the field to
700
+ * open, or undefined if triggered programmatically.
701
+ * @package
702
+ * @final
703
+ */
704
+ showEditor(opt_e) {
705
+ if (this.isClickable()) {
706
+ this.showEditor_(opt_e);
707
+ }
729
708
  }
730
- if (this.borderRect_) {
731
- totalHeight = Math.max(totalHeight, constants.FIELD_BORDER_RECT_HEIGHT);
709
+
710
+ /**
711
+ * A developer hook to create an editor for the field. This is no-op by
712
+ * default, and must be overriden to create an editor.
713
+ * @param {Event=} _e Optional mouse event that triggered the field to
714
+ * open, or undefined if triggered programmatically.
715
+ * @return {void}
716
+ * @protected
717
+ */
718
+ showEditor_(_e) {
719
+ // NOP
732
720
  }
733
721
 
734
- this.size_.height = totalHeight;
735
- this.size_.width = totalWidth;
722
+ /**
723
+ * Updates the size of the field based on the text.
724
+ * @param {number=} opt_margin margin to use when positioning the text
725
+ * element.
726
+ * @protected
727
+ */
728
+ updateSize_(opt_margin) {
729
+ const constants = this.getConstants();
730
+ const xOffset = opt_margin !== undefined ?
731
+ opt_margin :
732
+ (this.borderRect_ ? this.getConstants().FIELD_BORDER_RECT_X_PADDING :
733
+ 0);
734
+ let totalWidth = xOffset * 2;
735
+ let totalHeight = constants.FIELD_TEXT_HEIGHT;
736
+
737
+ let contentWidth = 0;
738
+ if (this.textElement_) {
739
+ contentWidth = dom.getFastTextWidth(
740
+ this.textElement_, constants.FIELD_TEXT_FONTSIZE,
741
+ constants.FIELD_TEXT_FONTWEIGHT, constants.FIELD_TEXT_FONTFAMILY);
742
+ totalWidth += contentWidth;
743
+ }
744
+ if (this.borderRect_) {
745
+ totalHeight = Math.max(totalHeight, constants.FIELD_BORDER_RECT_HEIGHT);
746
+ }
736
747
 
737
- this.positionTextElement_(xOffset, contentWidth);
738
- this.positionBorderRect_();
739
- };
748
+ this.size_.height = totalHeight;
749
+ this.size_.width = totalWidth;
740
750
 
741
- /**
742
- * Position a field's text element after a size change. This handles both LTR
743
- * and RTL positioning.
744
- * @param {number} xOffset x offset to use when positioning the text element.
745
- * @param {number} contentWidth The content width.
746
- * @protected
747
- */
748
- Field.prototype.positionTextElement_ = function(xOffset, contentWidth) {
749
- if (!this.textElement_) {
750
- return;
751
+ this.positionTextElement_(xOffset, contentWidth);
752
+ this.positionBorderRect_();
751
753
  }
752
- const constants = this.getConstants();
753
- const halfHeight = this.size_.height / 2;
754
-
755
- this.textElement_.setAttribute(
756
- 'x',
757
- this.sourceBlock_.RTL ? this.size_.width - contentWidth - xOffset :
758
- xOffset);
759
- this.textElement_.setAttribute(
760
- 'y',
761
- constants.FIELD_TEXT_BASELINE_CENTER ? halfHeight :
762
- halfHeight -
763
- constants.FIELD_TEXT_HEIGHT / 2 + constants.FIELD_TEXT_BASELINE);
764
- };
765
754
 
766
- /**
767
- * Position a field's border rect after a size change.
768
- * @protected
769
- */
770
- Field.prototype.positionBorderRect_ = function() {
771
- if (!this.borderRect_) {
772
- return;
755
+ /**
756
+ * Position a field's text element after a size change. This handles both LTR
757
+ * and RTL positioning.
758
+ * @param {number} xOffset x offset to use when positioning the text element.
759
+ * @param {number} contentWidth The content width.
760
+ * @protected
761
+ */
762
+ positionTextElement_(xOffset, contentWidth) {
763
+ if (!this.textElement_) {
764
+ return;
765
+ }
766
+ const constants = this.getConstants();
767
+ const halfHeight = this.size_.height / 2;
768
+
769
+ this.textElement_.setAttribute(
770
+ 'x',
771
+ this.sourceBlock_.RTL ? this.size_.width - contentWidth - xOffset :
772
+ xOffset);
773
+ this.textElement_.setAttribute(
774
+ 'y',
775
+ constants.FIELD_TEXT_BASELINE_CENTER ?
776
+ halfHeight :
777
+ halfHeight - constants.FIELD_TEXT_HEIGHT / 2 +
778
+ constants.FIELD_TEXT_BASELINE);
773
779
  }
774
- this.borderRect_.setAttribute('width', this.size_.width);
775
- this.borderRect_.setAttribute('height', this.size_.height);
776
- this.borderRect_.setAttribute(
777
- 'rx', this.getConstants().FIELD_BORDER_RECT_RADIUS);
778
- this.borderRect_.setAttribute(
779
- 'ry', this.getConstants().FIELD_BORDER_RECT_RADIUS);
780
- };
781
-
782
780
 
783
- /**
784
- * Returns the height and width of the field.
785
- *
786
- * This should *in general* be the only place render_ gets called from.
787
- * @return {!Size} Height and width.
788
- */
789
- Field.prototype.getSize = function() {
790
- if (!this.isVisible()) {
791
- return new Size(0, 0);
781
+ /**
782
+ * Position a field's border rect after a size change.
783
+ * @protected
784
+ */
785
+ positionBorderRect_() {
786
+ if (!this.borderRect_) {
787
+ return;
788
+ }
789
+ this.borderRect_.setAttribute('width', this.size_.width);
790
+ this.borderRect_.setAttribute('height', this.size_.height);
791
+ this.borderRect_.setAttribute(
792
+ 'rx', this.getConstants().FIELD_BORDER_RECT_RADIUS);
793
+ this.borderRect_.setAttribute(
794
+ 'ry', this.getConstants().FIELD_BORDER_RECT_RADIUS);
792
795
  }
793
796
 
794
- if (this.isDirty_) {
795
- this.render_();
796
- this.isDirty_ = false;
797
- } else if (this.visible_ && this.size_.width === 0) {
798
- // If the field is not visible the width will be 0 as well, one of the
799
- // problems with the old system.
800
- console.warn(
801
- 'Deprecated use of setting size_.width to 0 to rerender a' +
802
- ' field. Set field.isDirty_ to true instead.');
803
- this.render_();
804
- }
805
- return this.size_;
806
- };
797
+ /**
798
+ * Returns the height and width of the field.
799
+ *
800
+ * This should *in general* be the only place render_ gets called from.
801
+ * @return {!Size} Height and width.
802
+ */
803
+ getSize() {
804
+ if (!this.isVisible()) {
805
+ return new Size(0, 0);
806
+ }
807
807
 
808
- /**
809
- * Returns the bounding box of the rendered field, accounting for workspace
810
- * scaling.
811
- * @return {!Rect} An object with top, bottom, left, and right in
812
- * pixels relative to the top left corner of the page (window coordinates).
813
- * @package
814
- */
815
- Field.prototype.getScaledBBox = function() {
816
- let scaledWidth;
817
- let scaledHeight;
818
- let xy;
819
- if (!this.borderRect_) {
820
- // Browsers are inconsistent in what they return for a bounding box.
821
- // - Webkit / Blink: fill-box / object bounding box
822
- // - Gecko / Triden / EdgeHTML: stroke-box
823
- const bBox = this.sourceBlock_.getHeightWidth();
824
- const scale = this.sourceBlock_.workspace.scale;
825
- xy = this.getAbsoluteXY_();
826
- scaledWidth = bBox.width * scale;
827
- scaledHeight = bBox.height * scale;
828
-
829
- if (userAgent.GECKO) {
830
- xy.x += 1.5 * scale;
831
- xy.y += 1.5 * scale;
832
- scaledWidth += 1 * scale;
833
- scaledHeight += 1 * scale;
834
- } else {
835
- if (!userAgent.EDGE && !userAgent.IE) {
836
- xy.x -= 0.5 * scale;
837
- xy.y -= 0.5 * scale;
838
- }
839
- scaledWidth += 1 * scale;
840
- scaledHeight += 1 * scale;
808
+ if (this.isDirty_) {
809
+ this.render_();
810
+ this.isDirty_ = false;
811
+ } else if (this.visible_ && this.size_.width === 0) {
812
+ // If the field is not visible the width will be 0 as well, one of the
813
+ // problems with the old system.
814
+ console.warn(
815
+ 'Deprecated use of setting size_.width to 0 to rerender a' +
816
+ ' field. Set field.isDirty_ to true instead.');
817
+ this.render_();
841
818
  }
842
- } else {
843
- const bBox = this.borderRect_.getBoundingClientRect();
844
- xy = style.getPageOffset(this.borderRect_);
845
- scaledWidth = bBox.width;
846
- scaledHeight = bBox.height;
819
+ return this.size_;
847
820
  }
848
- return new Rect(xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth);
849
- };
850
821
 
851
- /**
852
- * Get the text from this field to display on the block. May differ from
853
- * ``getText`` due to ellipsis, and other formatting.
854
- * @return {string} Text to display.
855
- * @protected
856
- */
857
- Field.prototype.getDisplayText_ = function() {
858
- let text = this.getText();
859
- if (!text) {
860
- // Prevent the field from disappearing if empty.
861
- return Field.NBSP;
862
- }
863
- if (text.length > this.maxDisplayLength) {
864
- // Truncate displayed string and add an ellipsis ('...').
865
- text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
866
- }
867
- // Replace whitespace with non-breaking spaces so the text doesn't collapse.
868
- text = text.replace(/\s/g, Field.NBSP);
869
- if (this.sourceBlock_ && this.sourceBlock_.RTL) {
870
- // The SVG is LTR, force text to be RTL.
871
- text += '\u200F';
822
+ /**
823
+ * Returns the bounding box of the rendered field, accounting for workspace
824
+ * scaling.
825
+ * @return {!Rect} An object with top, bottom, left, and right in
826
+ * pixels relative to the top left corner of the page (window
827
+ * coordinates).
828
+ * @package
829
+ */
830
+ getScaledBBox() {
831
+ let scaledWidth;
832
+ let scaledHeight;
833
+ let xy;
834
+ if (!this.borderRect_) {
835
+ // Browsers are inconsistent in what they return for a bounding box.
836
+ // - Webkit / Blink: fill-box / object bounding box
837
+ // - Gecko / Triden / EdgeHTML: stroke-box
838
+ const bBox = this.sourceBlock_.getHeightWidth();
839
+ const scale =
840
+ /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace).scale;
841
+ xy = this.getAbsoluteXY_();
842
+ scaledWidth = bBox.width * scale;
843
+ scaledHeight = bBox.height * scale;
844
+
845
+ if (userAgent.GECKO) {
846
+ xy.x += 1.5 * scale;
847
+ xy.y += 1.5 * scale;
848
+ scaledWidth += 1 * scale;
849
+ scaledHeight += 1 * scale;
850
+ } else {
851
+ if (!userAgent.EDGE && !userAgent.IE) {
852
+ xy.x -= 0.5 * scale;
853
+ xy.y -= 0.5 * scale;
854
+ }
855
+ scaledWidth += 1 * scale;
856
+ scaledHeight += 1 * scale;
857
+ }
858
+ } else {
859
+ const bBox = this.borderRect_.getBoundingClientRect();
860
+ xy = style.getPageOffset(this.borderRect_);
861
+ scaledWidth = bBox.width;
862
+ scaledHeight = bBox.height;
863
+ }
864
+ return new Rect(xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth);
872
865
  }
873
- return text;
874
- };
875
866
 
876
- /**
877
- * Get the text from this field.
878
- * @return {string} Current text.
879
- */
880
- Field.prototype.getText = function() {
881
- if (this.getText_) {
882
- const text = this.getText_.call(this);
883
- if (text !== null) {
884
- return String(text);
867
+ /**
868
+ * Get the text from this field to display on the block. May differ from
869
+ * ``getText`` due to ellipsis, and other formatting.
870
+ * @return {string} Text to display.
871
+ * @protected
872
+ */
873
+ getDisplayText_() {
874
+ let text = this.getText();
875
+ if (!text) {
876
+ // Prevent the field from disappearing if empty.
877
+ return Field.NBSP;
878
+ }
879
+ if (text.length > this.maxDisplayLength) {
880
+ // Truncate displayed string and add an ellipsis ('...').
881
+ text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
885
882
  }
883
+ // Replace whitespace with non-breaking spaces so the text doesn't collapse.
884
+ text = text.replace(/\s/g, Field.NBSP);
885
+ if (this.sourceBlock_ && this.sourceBlock_.RTL) {
886
+ // The SVG is LTR, force text to be RTL.
887
+ text += '\u200F';
888
+ }
889
+ return text;
886
890
  }
887
- return String(this.getValue());
888
- };
889
891
 
890
- /**
891
- * Force a rerender of the block that this field is installed on, which will
892
- * rerender this field and adjust for any sizing changes.
893
- * Other fields on the same block will not rerender, because their sizes have
894
- * already been recorded.
895
- * @package
896
- */
897
- Field.prototype.markDirty = function() {
898
- this.isDirty_ = true;
899
- this.constants_ = null;
900
- };
892
+ /**
893
+ * Get the text from this field.
894
+ * Override getText_ to provide a different behavior than simply casting the
895
+ * value to a string.
896
+ * @return {string} Current text.
897
+ * @final
898
+ */
899
+ getText() {
900
+ // this.getText_ was intended so that devs don't have to remember to call
901
+ // super when overriding how the text of the field is generated. (#2910)
902
+ const text = this.getText_();
903
+ if (text !== null) return String(text);
904
+ return String(this.getValue());
905
+ }
901
906
 
902
- /**
903
- * Force a rerender of the block that this field is installed on, which will
904
- * rerender this field and adjust for any sizing changes.
905
- * Other fields on the same block will not rerender, because their sizes have
906
- * already been recorded.
907
- * @package
908
- */
909
- Field.prototype.forceRerender = function() {
910
- this.isDirty_ = true;
911
- if (this.sourceBlock_ && this.sourceBlock_.rendered) {
912
- this.sourceBlock_.render();
913
- this.sourceBlock_.bumpNeighbours();
914
- this.updateMarkers_();
907
+ /**
908
+ * A developer hook to override the returned text of this field.
909
+ * Override if the text representation of the value of this field
910
+ * is not just a string cast of its value.
911
+ * Return null to resort to a string cast.
912
+ * @return {?string} Current text or null.
913
+ * @protected
914
+ */
915
+ getText_() {
916
+ return null;
915
917
  }
916
- };
917
918
 
918
- /**
919
- * Used to change the value of the field. Handles validation and events.
920
- * Subclasses should override doClassValidation_ and doValueUpdate_ rather
921
- * than this method.
922
- * @param {*} newValue New value.
923
- */
924
- Field.prototype.setValue = function(newValue) {
925
- const doLogging = false;
926
- if (newValue === null) {
927
- doLogging && console.log('null, return');
928
- // Not a valid value to check.
929
- return;
919
+ /**
920
+ * Force a rerender of the block that this field is installed on, which will
921
+ * rerender this field and adjust for any sizing changes.
922
+ * Other fields on the same block will not rerender, because their sizes have
923
+ * already been recorded.
924
+ * @package
925
+ */
926
+ markDirty() {
927
+ this.isDirty_ = true;
928
+ this.constants_ = null;
930
929
  }
931
930
 
932
- let validatedValue = this.doClassValidation_(newValue);
933
- // Class validators might accidentally forget to return, we'll ignore that.
934
- newValue = this.processValidation_(newValue, validatedValue);
935
- if (newValue instanceof Error) {
936
- doLogging && console.log('invalid class validation, return');
937
- return;
931
+ /**
932
+ * Force a rerender of the block that this field is installed on, which will
933
+ * rerender this field and adjust for any sizing changes.
934
+ * Other fields on the same block will not rerender, because their sizes have
935
+ * already been recorded.
936
+ * @package
937
+ */
938
+ forceRerender() {
939
+ this.isDirty_ = true;
940
+ if (this.sourceBlock_ && this.sourceBlock_.rendered) {
941
+ this.sourceBlock_.render();
942
+ this.sourceBlock_.bumpNeighbours();
943
+ this.updateMarkers_();
944
+ }
938
945
  }
939
946
 
940
- const localValidator = this.getValidator();
941
- if (localValidator) {
942
- validatedValue = localValidator.call(this, newValue);
943
- // Local validators might accidentally forget to return, we'll ignore that.
947
+ /**
948
+ * Used to change the value of the field. Handles validation and events.
949
+ * Subclasses should override doClassValidation_ and doValueUpdate_ rather
950
+ * than this method.
951
+ * @param {*} newValue New value.
952
+ * @final
953
+ */
954
+ setValue(newValue) {
955
+ const doLogging = false;
956
+ if (newValue === null) {
957
+ doLogging && console.log('null, return');
958
+ // Not a valid value to check.
959
+ return;
960
+ }
961
+
962
+ let validatedValue = this.doClassValidation_(newValue);
963
+ // Class validators might accidentally forget to return, we'll ignore that.
944
964
  newValue = this.processValidation_(newValue, validatedValue);
945
965
  if (newValue instanceof Error) {
946
- doLogging && console.log('invalid local validation, return');
966
+ doLogging && console.log('invalid class validation, return');
947
967
  return;
948
968
  }
949
- }
950
- const source = this.sourceBlock_;
951
- if (source && source.disposed) {
952
- doLogging && console.log('source disposed, return');
953
- return;
954
- }
955
- const oldValue = this.getValue();
956
- if (oldValue === newValue) {
957
- doLogging && console.log('same, doValueUpdate_, return');
969
+
970
+ const localValidator = this.getValidator();
971
+ if (localValidator) {
972
+ validatedValue = localValidator.call(this, newValue);
973
+ // Local validators might accidentally forget to return, we'll ignore
974
+ // that.
975
+ newValue = this.processValidation_(newValue, validatedValue);
976
+ if (newValue instanceof Error) {
977
+ doLogging && console.log('invalid local validation, return');
978
+ return;
979
+ }
980
+ }
981
+ const source = this.sourceBlock_;
982
+ if (source && source.disposed) {
983
+ doLogging && console.log('source disposed, return');
984
+ return;
985
+ }
986
+ const oldValue = this.getValue();
987
+ if (oldValue === newValue) {
988
+ doLogging && console.log('same, doValueUpdate_, return');
989
+ this.doValueUpdate_(newValue);
990
+ return;
991
+ }
992
+
958
993
  this.doValueUpdate_(newValue);
959
- return;
994
+ if (source && eventUtils.isEnabled()) {
995
+ eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
996
+ source, 'field', this.name || null, oldValue, newValue));
997
+ }
998
+ if (this.isDirty_) {
999
+ this.forceRerender();
1000
+ }
1001
+ doLogging && console.log(this.value_);
960
1002
  }
961
1003
 
962
- this.doValueUpdate_(newValue);
963
- if (source && eventUtils.isEnabled()) {
964
- eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
965
- source, 'field', this.name || null, oldValue, newValue));
1004
+ /**
1005
+ * Process the result of validation.
1006
+ * @param {*} newValue New value.
1007
+ * @param {*} validatedValue Validated value.
1008
+ * @return {*} New value, or an Error object.
1009
+ * @private
1010
+ */
1011
+ processValidation_(newValue, validatedValue) {
1012
+ if (validatedValue === null) {
1013
+ this.doValueInvalid_(newValue);
1014
+ if (this.isDirty_) {
1015
+ this.forceRerender();
1016
+ }
1017
+ return Error();
1018
+ }
1019
+ if (validatedValue !== undefined) {
1020
+ newValue = validatedValue;
1021
+ }
1022
+ return newValue;
966
1023
  }
967
- if (this.isDirty_) {
968
- this.forceRerender();
1024
+
1025
+ /**
1026
+ * Get the current value of the field.
1027
+ * @return {*} Current value.
1028
+ */
1029
+ getValue() {
1030
+ return this.value_;
969
1031
  }
970
- doLogging && console.log(this.value_);
971
- };
972
1032
 
973
- /**
974
- * Process the result of validation.
975
- * @param {*} newValue New value.
976
- * @param {*} validatedValue Validated value.
977
- * @return {*} New value, or an Error object.
978
- * @private
979
- */
980
- Field.prototype.processValidation_ = function(newValue, validatedValue) {
981
- if (validatedValue === null) {
982
- this.doValueInvalid_(newValue);
983
- if (this.isDirty_) {
984
- this.forceRerender();
1033
+ /**
1034
+ * Used to validate a value. Returns input by default. Can be overridden by
1035
+ * subclasses, see FieldDropdown.
1036
+ * @param {*=} opt_newValue The value to be validated.
1037
+ * @return {*} The validated value, same as input by default.
1038
+ * @protected
1039
+ */
1040
+ doClassValidation_(opt_newValue) {
1041
+ if (opt_newValue === null || opt_newValue === undefined) {
1042
+ return null;
985
1043
  }
986
- return Error();
1044
+ return opt_newValue;
987
1045
  }
988
- if (validatedValue !== undefined) {
989
- newValue = validatedValue;
1046
+
1047
+ /**
1048
+ * Used to update the value of a field. Can be overridden by subclasses to do
1049
+ * custom storage of values/updating of external things.
1050
+ * @param {*} newValue The value to be saved.
1051
+ * @protected
1052
+ */
1053
+ doValueUpdate_(newValue) {
1054
+ this.value_ = newValue;
1055
+ this.isDirty_ = true;
990
1056
  }
991
- return newValue;
992
- };
993
1057
 
994
- /**
995
- * Get the current value of the field.
996
- * @return {*} Current value.
997
- */
998
- Field.prototype.getValue = function() {
999
- return this.value_;
1000
- };
1058
+ /**
1059
+ * Used to notify the field an invalid value was input. Can be overridden by
1060
+ * subclasses, see FieldTextInput.
1061
+ * No-op by default.
1062
+ * @param {*} _invalidValue The input value that was determined to be invalid.
1063
+ * @protected
1064
+ */
1065
+ doValueInvalid_(_invalidValue) {
1066
+ // NOP
1067
+ }
1001
1068
 
1002
- /**
1003
- * Used to validate a value. Returns input by default. Can be overridden by
1004
- * subclasses, see FieldDropdown.
1005
- * @param {*=} opt_newValue The value to be validated.
1006
- * @return {*} The validated value, same as input by default.
1007
- * @protected
1008
- */
1009
- Field.prototype.doClassValidation_ = function(opt_newValue) {
1010
- if (opt_newValue === null || opt_newValue === undefined) {
1011
- return null;
1069
+ /**
1070
+ * Handle a mouse down event on a field.
1071
+ * @param {!Event} e Mouse down event.
1072
+ * @protected
1073
+ */
1074
+ onMouseDown_(e) {
1075
+ if (!this.sourceBlock_ || !this.sourceBlock_.workspace) {
1076
+ return;
1077
+ }
1078
+ const gesture =
1079
+ /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace)
1080
+ .getGesture(e);
1081
+ if (gesture) {
1082
+ gesture.setStartField(this);
1083
+ }
1012
1084
  }
1013
- return opt_newValue;
1014
- };
1015
1085
 
1016
- /**
1017
- * Used to update the value of a field. Can be overridden by subclasses to do
1018
- * custom storage of values/updating of external things.
1019
- * @param {*} newValue The value to be saved.
1020
- * @protected
1021
- */
1022
- Field.prototype.doValueUpdate_ = function(newValue) {
1023
- this.value_ = newValue;
1024
- this.isDirty_ = true;
1025
- };
1086
+ /**
1087
+ * Sets the tooltip for this field.
1088
+ * @param {?Tooltip.TipInfo} newTip The
1089
+ * text for the tooltip, a function that returns the text for the tooltip,
1090
+ * a parent object whose tooltip will be used, or null to display the tooltip
1091
+ * of the parent block. To not display a tooltip pass the empty string.
1092
+ */
1093
+ setTooltip(newTip) {
1094
+ if (!newTip && newTip !== '') { // If null or undefined.
1095
+ newTip = this.sourceBlock_;
1096
+ }
1097
+ const clickTarget = this.getClickTarget_();
1098
+ if (clickTarget) {
1099
+ clickTarget.tooltip = newTip;
1100
+ } else {
1101
+ // Field has not been initialized yet.
1102
+ this.tooltip_ = newTip;
1103
+ }
1104
+ }
1026
1105
 
1027
- /**
1028
- * Used to notify the field an invalid value was input. Can be overridden by
1029
- * subclasses, see FieldTextInput.
1030
- * No-op by default.
1031
- * @param {*} _invalidValue The input value that was determined to be invalid.
1032
- * @protected
1033
- */
1034
- Field.prototype.doValueInvalid_ = function(_invalidValue) {
1035
- // NOP
1036
- };
1106
+ /**
1107
+ * Returns the tooltip text for this field.
1108
+ * @return {string} The tooltip text for this field.
1109
+ */
1110
+ getTooltip() {
1111
+ const clickTarget = this.getClickTarget_();
1112
+ if (clickTarget) {
1113
+ return Tooltip.getTooltipOfObject(clickTarget);
1114
+ }
1115
+ // Field has not been initialized yet. Return stashed this.tooltip_ value.
1116
+ return Tooltip.getTooltipOfObject({tooltip: this.tooltip_});
1117
+ }
1037
1118
 
1038
- /**
1039
- * Handle a mouse down event on a field.
1040
- * @param {!Event} e Mouse down event.
1041
- * @protected
1042
- */
1043
- Field.prototype.onMouseDown_ = function(e) {
1044
- if (!this.sourceBlock_ || !this.sourceBlock_.workspace) {
1045
- return;
1119
+ /**
1120
+ * The element to bind the click handler to. If not set explicitly, defaults
1121
+ * to the SVG root of the field. When this element is
1122
+ * clicked on an editable field, the editor will open.
1123
+ * @return {!Element} Element to bind click handler to.
1124
+ * @protected
1125
+ */
1126
+ getClickTarget_() {
1127
+ return this.clickTarget_ || this.getSvgRoot();
1046
1128
  }
1047
- const gesture = this.sourceBlock_.workspace.getGesture(e);
1048
- if (gesture) {
1049
- gesture.setStartField(this);
1129
+
1130
+ /**
1131
+ * Return the absolute coordinates of the top-left corner of this field.
1132
+ * The origin (0,0) is the top-left corner of the page body.
1133
+ * @return {!Coordinate} Object with .x and .y properties.
1134
+ * @protected
1135
+ */
1136
+ getAbsoluteXY_() {
1137
+ return style.getPageOffset(
1138
+ /** @type {!SVGRectElement} */ (this.getClickTarget_()));
1050
1139
  }
1051
- };
1052
1140
 
1053
- /**
1054
- * Sets the tooltip for this field.
1055
- * @param {?Tooltip.TipInfo} newTip The
1056
- * text for the tooltip, a function that returns the text for the tooltip, a
1057
- * parent object whose tooltip will be used, or null to display the tooltip
1058
- * of the parent block. To not display a tooltip pass the empty string.
1059
- */
1060
- Field.prototype.setTooltip = function(newTip) {
1061
- if (!newTip && newTip !== '') { // If null or undefined.
1062
- newTip = this.sourceBlock_;
1141
+ /**
1142
+ * Whether this field references any Blockly variables. If true it may need
1143
+ * to be handled differently during serialization and deserialization.
1144
+ * Subclasses may override this.
1145
+ * @return {boolean} True if this field has any variable references.
1146
+ * @package
1147
+ */
1148
+ referencesVariables() {
1149
+ return false;
1063
1150
  }
1064
- const clickTarget = this.getClickTarget_();
1065
- if (clickTarget) {
1066
- clickTarget.tooltip = newTip;
1067
- } else {
1068
- // Field has not been initialized yet.
1069
- this.tooltip_ = newTip;
1151
+
1152
+ /**
1153
+ * Refresh the variable name referenced by this field if this field references
1154
+ * variables.
1155
+ * @package
1156
+ */
1157
+ refreshVariableName() {
1158
+ // NOP
1070
1159
  }
1071
- };
1072
1160
 
1073
- /**
1074
- * Returns the tooltip text for this field.
1075
- * @return {string} The tooltip text for this field.
1076
- */
1077
- Field.prototype.getTooltip = function() {
1078
- const clickTarget = this.getClickTarget_();
1079
- if (clickTarget) {
1080
- return Tooltip.getTooltipOfObject(clickTarget);
1161
+ /**
1162
+ * Search through the list of inputs and their fields in order to find the
1163
+ * parent input of a field.
1164
+ * @return {Input} The input that the field belongs to.
1165
+ * @package
1166
+ */
1167
+ getParentInput() {
1168
+ let parentInput = null;
1169
+ const block = this.sourceBlock_;
1170
+ const inputs = block.inputList;
1171
+
1172
+ for (let idx = 0; idx < block.inputList.length; idx++) {
1173
+ const input = inputs[idx];
1174
+ const fieldRows = input.fieldRow;
1175
+ for (let j = 0; j < fieldRows.length; j++) {
1176
+ if (fieldRows[j] === this) {
1177
+ parentInput = input;
1178
+ break;
1179
+ }
1180
+ }
1181
+ }
1182
+ return parentInput;
1081
1183
  }
1082
- // Field has not been initialized yet. Return stashed this.tooltip_ value.
1083
- return Tooltip.getTooltipOfObject({tooltip: this.tooltip_});
1084
- };
1085
1184
 
1086
- /**
1087
- * The element to bind the click handler to. If not set explicitly, defaults
1088
- * to the SVG root of the field. When this element is
1089
- * clicked on an editable field, the editor will open.
1090
- * @return {!Element} Element to bind click handler to.
1091
- * @protected
1092
- */
1093
- Field.prototype.getClickTarget_ = function() {
1094
- return this.clickTarget_ || this.getSvgRoot();
1095
- };
1185
+ /**
1186
+ * Returns whether or not we should flip the field in RTL.
1187
+ * @return {boolean} True if we should flip in RTL.
1188
+ */
1189
+ getFlipRtl() {
1190
+ return false;
1191
+ }
1096
1192
 
1097
- /**
1098
- * Return the absolute coordinates of the top-left corner of this field.
1099
- * The origin (0,0) is the top-left corner of the page body.
1100
- * @return {!Coordinate} Object with .x and .y properties.
1101
- * @protected
1102
- */
1103
- Field.prototype.getAbsoluteXY_ = function() {
1104
- return style.getPageOffset(
1105
- /** @type {!SVGRectElement} */ (this.getClickTarget_()));
1106
- };
1193
+ /**
1194
+ * Returns whether or not the field is tab navigable.
1195
+ * @return {boolean} True if the field is tab navigable.
1196
+ */
1197
+ isTabNavigable() {
1198
+ return false;
1199
+ }
1107
1200
 
1108
- /**
1109
- * Whether this field references any Blockly variables. If true it may need to
1110
- * be handled differently during serialization and deserialization. Subclasses
1111
- * may override this.
1112
- * @return {boolean} True if this field has any variable references.
1113
- * @package
1114
- */
1115
- Field.prototype.referencesVariables = function() {
1116
- return false;
1117
- };
1201
+ /**
1202
+ * Handles the given keyboard shortcut.
1203
+ * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be
1204
+ * handled.
1205
+ * @return {boolean} True if the shortcut has been handled, false otherwise.
1206
+ * @public
1207
+ */
1208
+ onShortcut(_shortcut) {
1209
+ return false;
1210
+ }
1118
1211
 
1119
- /**
1120
- * Search through the list of inputs and their fields in order to find the
1121
- * parent input of a field.
1122
- * @return {Input} The input that the field belongs to.
1123
- * @package
1124
- */
1125
- Field.prototype.getParentInput = function() {
1126
- let parentInput = null;
1127
- const block = this.sourceBlock_;
1128
- const inputs = block.inputList;
1129
-
1130
- for (let idx = 0; idx < block.inputList.length; idx++) {
1131
- const input = inputs[idx];
1132
- const fieldRows = input.fieldRow;
1133
- for (let j = 0; j < fieldRows.length; j++) {
1134
- if (fieldRows[j] === this) {
1135
- parentInput = input;
1136
- break;
1137
- }
1212
+ /**
1213
+ * Add the cursor SVG to this fields SVG group.
1214
+ * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the
1215
+ * field group.
1216
+ * @package
1217
+ */
1218
+ setCursorSvg(cursorSvg) {
1219
+ if (!cursorSvg) {
1220
+ this.cursorSvg_ = null;
1221
+ return;
1138
1222
  }
1223
+
1224
+ this.fieldGroup_.appendChild(cursorSvg);
1225
+ this.cursorSvg_ = cursorSvg;
1139
1226
  }
1140
- return parentInput;
1141
- };
1142
1227
 
1143
- /**
1144
- * Returns whether or not we should flip the field in RTL.
1145
- * @return {boolean} True if we should flip in RTL.
1146
- */
1147
- Field.prototype.getFlipRtl = function() {
1148
- return false;
1149
- };
1228
+ /**
1229
+ * Add the marker SVG to this fields SVG group.
1230
+ * @param {SVGElement} markerSvg The SVG root of the marker to be added to the
1231
+ * field group.
1232
+ * @package
1233
+ */
1234
+ setMarkerSvg(markerSvg) {
1235
+ if (!markerSvg) {
1236
+ this.markerSvg_ = null;
1237
+ return;
1238
+ }
1150
1239
 
1151
- /**
1152
- * Returns whether or not the field is tab navigable.
1153
- * @return {boolean} True if the field is tab navigable.
1154
- */
1155
- Field.prototype.isTabNavigable = function() {
1156
- return false;
1157
- };
1240
+ this.fieldGroup_.appendChild(markerSvg);
1241
+ this.markerSvg_ = markerSvg;
1242
+ }
1158
1243
 
1159
- /**
1160
- * Handles the given keyboard shortcut.
1161
- * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be
1162
- * handled.
1163
- * @return {boolean} True if the shortcut has been handled, false otherwise.
1164
- * @public
1165
- */
1166
- Field.prototype.onShortcut = function(_shortcut) {
1167
- return false;
1168
- };
1244
+ /**
1245
+ * Redraw any attached marker or cursor svgs if needed.
1246
+ * @protected
1247
+ */
1248
+ updateMarkers_() {
1249
+ const workspace =
1250
+ /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace);
1251
+ if (workspace.keyboardAccessibilityMode && this.cursorSvg_) {
1252
+ workspace.getCursor().draw();
1253
+ }
1254
+ if (workspace.keyboardAccessibilityMode && this.markerSvg_) {
1255
+ // TODO(#4592): Update all markers on the field.
1256
+ workspace.getMarker(MarkerManager.LOCAL_MARKER).draw();
1257
+ }
1258
+ }
1259
+ }
1169
1260
 
1170
1261
  /**
1171
- * Add the cursor SVG to this fields SVG group.
1172
- * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the
1173
- * field group.
1174
- * @package
1262
+ * The default value for this field.
1263
+ * @type {*}
1264
+ * @protected
1175
1265
  */
1176
- Field.prototype.setCursorSvg = function(cursorSvg) {
1177
- if (!cursorSvg) {
1178
- this.cursorSvg_ = null;
1179
- return;
1180
- }
1181
-
1182
- this.fieldGroup_.appendChild(cursorSvg);
1183
- this.cursorSvg_ = cursorSvg;
1184
- };
1266
+ Field.prototype.DEFAULT_VALUE = null;
1185
1267
 
1186
1268
  /**
1187
- * Add the marker SVG to this fields SVG group.
1188
- * @param {SVGElement} markerSvg The SVG root of the marker to be added to the
1189
- * field group.
1190
- * @package
1269
+ * Non-breaking space.
1270
+ * @const
1191
1271
  */
1192
- Field.prototype.setMarkerSvg = function(markerSvg) {
1193
- if (!markerSvg) {
1194
- this.markerSvg_ = null;
1195
- return;
1196
- }
1197
-
1198
- this.fieldGroup_.appendChild(markerSvg);
1199
- this.markerSvg_ = markerSvg;
1200
- };
1272
+ Field.NBSP = '\u00A0';
1201
1273
 
1202
1274
  /**
1203
- * Redraw any attached marker or cursor svgs if needed.
1204
- * @protected
1275
+ * A value used to signal when a field's constructor should *not* set the
1276
+ * field's value or run configure_, and should allow a subclass to do that
1277
+ * instead.
1278
+ * @const
1205
1279
  */
1206
- Field.prototype.updateMarkers_ = function() {
1207
- const workspace =
1208
- /** @type {!WorkspaceSvg} */ (this.sourceBlock_.workspace);
1209
- if (workspace.keyboardAccessibilityMode && this.cursorSvg_) {
1210
- workspace.getCursor().draw();
1211
- }
1212
- if (workspace.keyboardAccessibilityMode && this.markerSvg_) {
1213
- // TODO(#4592): Update all markers on the field.
1214
- workspace.getMarker(MarkerManager.LOCAL_MARKER).draw();
1215
- }
1216
- };
1280
+ Field.SKIP_SETUP = new Sentinel();
1217
1281
 
1218
1282
  exports.Field = Field;