blockly 7.20211209.4 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +5 -4
  3. package/blockly_compressed.js +4 -3
  4. package/blockly_compressed.js.map +1 -1
  5. package/blocks/blocks.js +47 -0
  6. package/blocks/colour.js +13 -3
  7. package/blocks/lists.js +22 -13
  8. package/blocks/logic.js +13 -3
  9. package/blocks/loops.js +24 -11
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +45 -32
  12. package/blocks/text.js +22 -13
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +1 -1
  16. package/blocks_compressed.js.map +1 -1
  17. package/core/block.js +1869 -1814
  18. package/core/block_drag_surface.js +201 -200
  19. package/core/block_dragger.js +377 -373
  20. package/core/block_svg.js +1593 -1479
  21. package/core/blockly.js +8 -22
  22. package/core/blocks.js +9 -2
  23. package/core/browser_events.js +22 -5
  24. package/core/bubble.js +841 -797
  25. package/core/bubble_dragger.js +213 -206
  26. package/core/bump_objects.js +2 -2
  27. package/core/clipboard.js +9 -9
  28. package/core/comment.js +353 -332
  29. package/core/common.js +46 -17
  30. package/core/component_manager.js +181 -174
  31. package/core/config.js +87 -0
  32. package/core/connection.js +595 -584
  33. package/core/connection_checker.js +242 -244
  34. package/core/connection_db.js +235 -230
  35. package/core/contextmenu.js +9 -6
  36. package/core/contextmenu_items.js +1 -2
  37. package/core/contextmenu_registry.js +93 -89
  38. package/core/css.js +474 -474
  39. package/core/delete_area.js +45 -42
  40. package/core/drag_target.js +57 -56
  41. package/core/dropdowndiv.js +153 -163
  42. package/core/events/events.js +2 -2
  43. package/core/events/events_abstract.js +89 -77
  44. package/core/events/events_block_base.js +37 -36
  45. package/core/events/events_block_change.js +130 -124
  46. package/core/events/events_block_create.js +73 -71
  47. package/core/events/events_block_delete.js +84 -82
  48. package/core/events/events_block_drag.js +50 -49
  49. package/core/events/events_block_move.js +147 -140
  50. package/core/events/events_bubble_open.js +51 -50
  51. package/core/events/events_click.js +48 -44
  52. package/core/events/events_comment_base.js +72 -69
  53. package/core/events/events_comment_change.js +63 -61
  54. package/core/events/events_comment_create.js +44 -42
  55. package/core/events/events_comment_delete.js +42 -40
  56. package/core/events/events_comment_move.js +106 -104
  57. package/core/events/events_marker_move.js +65 -64
  58. package/core/events/events_selected.js +46 -45
  59. package/core/events/events_theme_change.js +36 -35
  60. package/core/events/events_toolbox_item_select.js +46 -45
  61. package/core/events/events_trashcan_open.js +37 -36
  62. package/core/events/events_ui.js +47 -46
  63. package/core/events/events_ui_base.js +30 -29
  64. package/core/events/events_var_base.js +37 -36
  65. package/core/events/events_var_create.js +50 -48
  66. package/core/events/events_var_delete.js +50 -48
  67. package/core/events/events_var_rename.js +51 -49
  68. package/core/events/events_viewport.js +66 -65
  69. package/core/events/utils.js +29 -14
  70. package/core/events/workspace_events.js +49 -55
  71. package/core/extensions.js +4 -3
  72. package/core/field.js +1061 -997
  73. package/core/field_angle.js +462 -442
  74. package/core/field_checkbox.js +194 -182
  75. package/core/field_colour.js +519 -505
  76. package/core/field_dropdown.js +617 -598
  77. package/core/field_image.js +229 -220
  78. package/core/field_label.js +102 -91
  79. package/core/field_label_serializable.js +42 -41
  80. package/core/field_multilineinput.js +372 -358
  81. package/core/field_number.js +272 -253
  82. package/core/field_textinput.js +499 -467
  83. package/core/field_variable.js +458 -420
  84. package/core/flyout_base.js +1005 -952
  85. package/core/flyout_button.js +277 -260
  86. package/core/flyout_horizontal.js +304 -302
  87. package/core/flyout_metrics_manager.js +64 -64
  88. package/core/flyout_vertical.js +306 -300
  89. package/core/generator.js +459 -446
  90. package/core/gesture.js +829 -813
  91. package/core/grid.js +166 -163
  92. package/core/icon.js +168 -159
  93. package/core/inject.js +7 -5
  94. package/core/input.js +257 -248
  95. package/core/insertion_marker_manager.js +655 -624
  96. package/core/internal_constants.js +0 -129
  97. package/core/keyboard_nav/ast_node.js +605 -596
  98. package/core/keyboard_nav/basic_cursor.js +166 -165
  99. package/core/keyboard_nav/cursor.js +99 -97
  100. package/core/keyboard_nav/marker.js +83 -79
  101. package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
  102. package/core/marker_manager.js +153 -141
  103. package/core/menu.js +377 -372
  104. package/core/menuitem.js +223 -217
  105. package/core/metrics_manager.js +403 -390
  106. package/core/mutator.js +468 -437
  107. package/core/names.js +229 -188
  108. package/core/options.js +290 -284
  109. package/core/procedures.js +29 -17
  110. package/core/registry.js +19 -16
  111. package/core/rendered_connection.js +482 -463
  112. package/core/renderers/common/block_rendering.js +9 -3
  113. package/core/renderers/common/constants.js +1119 -1112
  114. package/core/renderers/common/debug.js +14 -0
  115. package/core/renderers/common/debugger.js +338 -316
  116. package/core/renderers/common/drawer.js +380 -370
  117. package/core/renderers/common/i_path_object.js +2 -2
  118. package/core/renderers/common/info.js +626 -618
  119. package/core/renderers/common/marker_svg.js +579 -541
  120. package/core/renderers/common/path_object.js +203 -200
  121. package/core/renderers/common/renderer.js +220 -218
  122. package/core/renderers/geras/constants.js +36 -36
  123. package/core/renderers/geras/drawer.js +155 -147
  124. package/core/renderers/geras/highlight_constants.js +244 -238
  125. package/core/renderers/geras/highlighter.js +231 -179
  126. package/core/renderers/geras/info.js +392 -369
  127. package/core/renderers/geras/measurables/inline_input.js +25 -19
  128. package/core/renderers/geras/measurables/statement_input.js +23 -17
  129. package/core/renderers/geras/path_object.js +106 -121
  130. package/core/renderers/geras/renderer.js +96 -98
  131. package/core/renderers/measurables/base.js +30 -18
  132. package/core/renderers/measurables/bottom_row.js +83 -80
  133. package/core/renderers/measurables/connection.js +22 -15
  134. package/core/renderers/measurables/external_value_input.js +35 -22
  135. package/core/renderers/measurables/field.js +35 -20
  136. package/core/renderers/measurables/hat.js +18 -13
  137. package/core/renderers/measurables/icon.js +24 -17
  138. package/core/renderers/measurables/in_row_spacer.js +15 -13
  139. package/core/renderers/measurables/inline_input.js +43 -33
  140. package/core/renderers/measurables/input_connection.js +41 -28
  141. package/core/renderers/measurables/input_row.js +50 -44
  142. package/core/renderers/measurables/jagged_edge.js +14 -12
  143. package/core/renderers/measurables/next_connection.js +16 -14
  144. package/core/renderers/measurables/output_connection.js +26 -20
  145. package/core/renderers/measurables/previous_connection.js +16 -15
  146. package/core/renderers/measurables/round_corner.js +20 -18
  147. package/core/renderers/measurables/row.js +184 -168
  148. package/core/renderers/measurables/spacer_row.js +38 -23
  149. package/core/renderers/measurables/square_corner.js +18 -16
  150. package/core/renderers/measurables/statement_input.js +23 -20
  151. package/core/renderers/measurables/top_row.js +88 -85
  152. package/core/renderers/minimalist/constants.js +8 -7
  153. package/core/renderers/minimalist/drawer.js +11 -10
  154. package/core/renderers/minimalist/info.js +18 -18
  155. package/core/renderers/minimalist/renderer.js +40 -39
  156. package/core/renderers/thrasos/info.js +258 -248
  157. package/core/renderers/thrasos/renderer.js +20 -20
  158. package/core/renderers/zelos/constants.js +898 -873
  159. package/core/renderers/zelos/drawer.js +186 -169
  160. package/core/renderers/zelos/info.js +502 -479
  161. package/core/renderers/zelos/marker_svg.js +129 -115
  162. package/core/renderers/zelos/measurables/bottom_row.js +31 -30
  163. package/core/renderers/zelos/measurables/inputs.js +22 -21
  164. package/core/renderers/zelos/measurables/row_elements.js +14 -13
  165. package/core/renderers/zelos/measurables/top_row.js +34 -33
  166. package/core/renderers/zelos/path_object.js +181 -180
  167. package/core/renderers/zelos/renderer.js +91 -92
  168. package/core/scrollbar.js +759 -713
  169. package/core/scrollbar_pair.js +250 -245
  170. package/core/serialization/blocks.js +26 -10
  171. package/core/serialization/workspaces.js +3 -2
  172. package/core/shortcut_registry.js +286 -277
  173. package/core/sprites.js +31 -0
  174. package/core/theme.js +135 -141
  175. package/core/theme_manager.js +147 -143
  176. package/core/toolbox/category.js +602 -576
  177. package/core/toolbox/collapsible_category.js +226 -227
  178. package/core/toolbox/separator.js +70 -61
  179. package/core/toolbox/toolbox.js +934 -927
  180. package/core/toolbox/toolbox_item.js +115 -99
  181. package/core/tooltip.js +108 -35
  182. package/core/touch.js +8 -3
  183. package/core/touch_gesture.js +254 -251
  184. package/core/trashcan.js +606 -595
  185. package/core/utils/coordinate.js +97 -95
  186. package/core/utils/dom.js +2 -2
  187. package/core/utils/global.js +2 -0
  188. package/core/utils/rect.js +41 -37
  189. package/core/utils/sentinel.js +25 -0
  190. package/core/utils/size.js +30 -27
  191. package/core/utils/svg.js +18 -16
  192. package/core/variable_map.js +325 -341
  193. package/core/variable_model.js +55 -54
  194. package/core/variables.js +9 -2
  195. package/core/variables_dynamic.js +3 -1
  196. package/core/warning.js +126 -120
  197. package/core/widgetdiv.js +4 -4
  198. package/core/workspace.js +685 -664
  199. package/core/workspace_audio.js +124 -118
  200. package/core/workspace_comment.js +308 -298
  201. package/core/workspace_comment_svg.js +1029 -951
  202. package/core/workspace_drag_surface_svg.js +147 -140
  203. package/core/workspace_dragger.js +70 -71
  204. package/core/workspace_svg.js +2322 -2297
  205. package/core/xml.js +30 -20
  206. package/core/zoom_controls.js +431 -439
  207. package/generators/dart/colour.js +56 -64
  208. package/generators/dart/lists.js +61 -50
  209. package/generators/dart/math.js +160 -148
  210. package/generators/dart/text.js +83 -61
  211. package/generators/javascript/colour.js +37 -34
  212. package/generators/javascript/lists.js +50 -43
  213. package/generators/javascript/math.js +123 -139
  214. package/generators/javascript/text.js +67 -81
  215. package/generators/lua/colour.js +25 -23
  216. package/generators/lua/lists.js +97 -69
  217. package/generators/lua/logic.js +1 -2
  218. package/generators/lua/math.js +182 -144
  219. package/generators/lua/text.js +116 -99
  220. package/generators/php/colour.js +38 -32
  221. package/generators/php/lists.js +109 -89
  222. package/generators/php/math.js +90 -81
  223. package/generators/php/text.js +63 -61
  224. package/generators/python/colour.js +18 -18
  225. package/generators/python/lists.js +38 -30
  226. package/generators/python/loops.js +12 -8
  227. package/generators/python/math.js +104 -106
  228. package/generators/python/text.js +34 -30
  229. package/msg/smn.js +436 -0
  230. package/package.json +7 -6
  231. package/blocks/all.js +0 -23
@@ -19,110 +19,493 @@ const Css = goog.require('Blockly.Css');
19
19
  const WidgetDiv = goog.require('Blockly.WidgetDiv');
20
20
  const browserEvents = goog.require('Blockly.browserEvents');
21
21
  const dom = goog.require('Blockly.utils.dom');
22
+ const dropDownDiv = goog.require('Blockly.dropDownDiv');
22
23
  const fieldRegistry = goog.require('Blockly.fieldRegistry');
23
24
  const math = goog.require('Blockly.utils.math');
24
- const object = goog.require('Blockly.utils.object');
25
25
  const userAgent = goog.require('Blockly.utils.userAgent');
26
- const {DropDownDiv} = goog.require('Blockly.DropDownDiv');
26
+ const {Field} = goog.require('Blockly.Field');
27
27
  const {FieldTextInput} = goog.require('Blockly.FieldTextInput');
28
28
  const {KeyCodes} = goog.require('Blockly.utils.KeyCodes');
29
+ /* eslint-disable-next-line no-unused-vars */
30
+ const {Sentinel} = goog.requireType('Blockly.utils.Sentinel');
29
31
  const {Svg} = goog.require('Blockly.utils.Svg');
30
32
 
31
33
 
32
34
  /**
33
35
  * Class for an editable angle field.
34
- * @param {string|number=} opt_value The initial value of the field. Should cast
35
- * to a number. Defaults to 0.
36
- * @param {Function=} opt_validator A function that is called to validate
37
- * changes to the field's value. Takes in a number & returns a
38
- * validated number, or null to abort the change.
39
- * @param {Object=} opt_config A map of options used to configure the field.
40
- * See the [field creation documentation]{@link
41
- * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
42
- * for a list of properties this parameter supports.
43
36
  * @extends {FieldTextInput}
44
- * @constructor
45
37
  * @alias Blockly.FieldAngle
46
38
  */
47
- const FieldAngle = function(opt_value, opt_validator, opt_config) {
39
+ class FieldAngle extends FieldTextInput {
48
40
  /**
49
- * Should the angle increase as the angle picker is moved clockwise (true)
50
- * or counterclockwise (false)
51
- * @see FieldAngle.CLOCKWISE
52
- * @type {boolean}
53
- * @private
41
+ * @param {(string|number|!Sentinel)=} opt_value The initial value of
42
+ * the field. Should cast to a number. Defaults to 0.
43
+ * Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
44
+ * subclasses that want to handle configuration and setting the field
45
+ * value after their own constructors have run).
46
+ * @param {Function=} opt_validator A function that is called to validate
47
+ * changes to the field's value. Takes in a number & returns a
48
+ * validated number, or null to abort the change.
49
+ * @param {Object=} opt_config A map of options used to configure the field.
50
+ * See the [field creation documentation]{@link
51
+ * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
52
+ * for a list of properties this parameter supports.
54
53
  */
55
- this.clockwise_ = FieldAngle.CLOCKWISE;
54
+ constructor(opt_value, opt_validator, opt_config) {
55
+ super(Field.SKIP_SETUP);
56
+
57
+ /**
58
+ * Should the angle increase as the angle picker is moved clockwise (true)
59
+ * or counterclockwise (false)
60
+ * @see FieldAngle.CLOCKWISE
61
+ * @type {boolean}
62
+ * @private
63
+ */
64
+ this.clockwise_ = FieldAngle.CLOCKWISE;
65
+
66
+ /**
67
+ * The offset of zero degrees (and all other angles).
68
+ * @see FieldAngle.OFFSET
69
+ * @type {number}
70
+ * @private
71
+ */
72
+ this.offset_ = FieldAngle.OFFSET;
73
+
74
+ /**
75
+ * The maximum angle to allow before wrapping.
76
+ * @see FieldAngle.WRAP
77
+ * @type {number}
78
+ * @private
79
+ */
80
+ this.wrap_ = FieldAngle.WRAP;
81
+
82
+ /**
83
+ * The amount to round angles to when using a mouse or keyboard nav input.
84
+ * @see FieldAngle.ROUND
85
+ * @type {number}
86
+ * @private
87
+ */
88
+ this.round_ = FieldAngle.ROUND;
89
+
90
+ /**
91
+ * The angle picker's SVG element.
92
+ * @type {?SVGElement}
93
+ * @private
94
+ */
95
+ this.editor_ = null;
96
+
97
+ /**
98
+ * The angle picker's gauge path depending on the value.
99
+ * @type {?SVGElement}
100
+ */
101
+ this.gauge_ = null;
102
+
103
+ /**
104
+ * The angle picker's line drawn representing the value's angle.
105
+ * @type {?SVGElement}
106
+ */
107
+ this.line_ = null;
108
+
109
+ /**
110
+ * The degree symbol for this field.
111
+ * @type {SVGTSpanElement}
112
+ * @protected
113
+ */
114
+ this.symbol_ = null;
115
+
116
+ /**
117
+ * Wrapper click event data.
118
+ * @type {?browserEvents.Data}
119
+ * @private
120
+ */
121
+ this.clickWrapper_ = null;
122
+
123
+ /**
124
+ * Surface click event data.
125
+ * @type {?browserEvents.Data}
126
+ * @private
127
+ */
128
+ this.clickSurfaceWrapper_ = null;
129
+
130
+ /**
131
+ * Surface mouse move event data.
132
+ * @type {?browserEvents.Data}
133
+ * @private
134
+ */
135
+ this.moveSurfaceWrapper_ = null;
136
+
137
+ /**
138
+ * Serializable fields are saved by the serializer, non-serializable fields
139
+ * are not. Editable fields should also be serializable.
140
+ * @type {boolean}
141
+ */
142
+ this.SERIALIZABLE = true;
143
+
144
+ if (opt_value === Field.SKIP_SETUP) return;
145
+ if (opt_config) this.configure_(opt_config);
146
+ this.setValue(opt_value);
147
+ if (opt_validator) this.setValidator(opt_validator);
148
+ }
56
149
 
57
150
  /**
58
- * The offset of zero degrees (and all other angles).
59
- * @see FieldAngle.OFFSET
60
- * @type {number}
61
- * @private
151
+ * Configure the field based on the given map of options.
152
+ * @param {!Object} config A map of options to configure the field based on.
153
+ * @protected
154
+ * @override
155
+ */
156
+ configure_(config) {
157
+ super.configure_(config);
158
+
159
+ switch (config['mode']) {
160
+ case 'compass':
161
+ this.clockwise_ = true;
162
+ this.offset_ = 90;
163
+ break;
164
+ case 'protractor':
165
+ // This is the default mode, so we could do nothing. But just to
166
+ // future-proof, we'll set it anyway.
167
+ this.clockwise_ = false;
168
+ this.offset_ = 0;
169
+ break;
170
+ }
171
+
172
+ // Allow individual settings to override the mode setting.
173
+ const clockwise = config['clockwise'];
174
+ if (typeof clockwise === 'boolean') {
175
+ this.clockwise_ = clockwise;
176
+ }
177
+
178
+ // If these are passed as null then we should leave them on the default.
179
+ let offset = config['offset'];
180
+ if (offset !== null) {
181
+ offset = Number(offset);
182
+ if (!isNaN(offset)) {
183
+ this.offset_ = offset;
184
+ }
185
+ }
186
+ let wrap = config['wrap'];
187
+ if (wrap !== null) {
188
+ wrap = Number(wrap);
189
+ if (!isNaN(wrap)) {
190
+ this.wrap_ = wrap;
191
+ }
192
+ }
193
+ let round = config['round'];
194
+ if (round !== null) {
195
+ round = Number(round);
196
+ if (!isNaN(round)) {
197
+ this.round_ = round;
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Create the block UI for this field.
204
+ * @package
62
205
  */
63
- this.offset_ = FieldAngle.OFFSET;
206
+ initView() {
207
+ super.initView();
208
+ // Add the degree symbol to the left of the number, even in RTL (issue
209
+ // #2380)
210
+ this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}, null);
211
+ this.symbol_.appendChild(document.createTextNode('\u00B0'));
212
+ this.textElement_.appendChild(this.symbol_);
213
+ }
64
214
 
65
215
  /**
66
- * The maximum angle to allow before wrapping.
67
- * @see FieldAngle.WRAP
68
- * @type {number}
69
- * @private
216
+ * Updates the graph when the field rerenders.
217
+ * @protected
218
+ * @override
70
219
  */
71
- this.wrap_ = FieldAngle.WRAP;
220
+ render_() {
221
+ super.render_();
222
+ this.updateGraph_();
223
+ }
72
224
 
73
225
  /**
74
- * The amount to round angles to when using a mouse or keyboard nav input.
75
- * @see FieldAngle.ROUND
76
- * @type {number}
226
+ * Create and show the angle field's editor.
227
+ * @param {Event=} opt_e Optional mouse event that triggered the field to
228
+ * open, or undefined if triggered programmatically.
229
+ * @protected
230
+ */
231
+ showEditor_(opt_e) {
232
+ // Mobile browsers have issues with in-line textareas (focus & keyboards).
233
+ const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
234
+ super.showEditor_(opt_e, noFocus);
235
+
236
+ this.dropdownCreate_();
237
+ dropDownDiv.getContentDiv().appendChild(this.editor_);
238
+
239
+ dropDownDiv.setColour(
240
+ this.sourceBlock_.style.colourPrimary,
241
+ this.sourceBlock_.style.colourTertiary);
242
+
243
+ dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
244
+
245
+ this.updateGraph_();
246
+ }
247
+
248
+ /**
249
+ * Create the angle dropdown editor.
77
250
  * @private
78
251
  */
79
- this.round_ = FieldAngle.ROUND;
252
+ dropdownCreate_() {
253
+ const svg = dom.createSvgElement(
254
+ Svg.SVG, {
255
+ 'xmlns': dom.SVG_NS,
256
+ 'xmlns:html': dom.HTML_NS,
257
+ 'xmlns:xlink': dom.XLINK_NS,
258
+ 'version': '1.1',
259
+ 'height': (FieldAngle.HALF * 2) + 'px',
260
+ 'width': (FieldAngle.HALF * 2) + 'px',
261
+ 'style': 'touch-action: none',
262
+ },
263
+ null);
264
+ const circle = dom.createSvgElement(
265
+ Svg.CIRCLE, {
266
+ 'cx': FieldAngle.HALF,
267
+ 'cy': FieldAngle.HALF,
268
+ 'r': FieldAngle.RADIUS,
269
+ 'class': 'blocklyAngleCircle',
270
+ },
271
+ svg);
272
+ this.gauge_ =
273
+ dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
274
+ this.line_ = dom.createSvgElement(
275
+ Svg.LINE, {
276
+ 'x1': FieldAngle.HALF,
277
+ 'y1': FieldAngle.HALF,
278
+ 'class': 'blocklyAngleLine',
279
+ },
280
+ svg);
281
+ // Draw markers around the edge.
282
+ for (let angle = 0; angle < 360; angle += 15) {
283
+ dom.createSvgElement(
284
+ Svg.LINE, {
285
+ 'x1': FieldAngle.HALF + FieldAngle.RADIUS,
286
+ 'y1': FieldAngle.HALF,
287
+ 'x2': FieldAngle.HALF + FieldAngle.RADIUS -
288
+ (angle % 45 === 0 ? 10 : 5),
289
+ 'y2': FieldAngle.HALF,
290
+ 'class': 'blocklyAngleMarks',
291
+ 'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' +
292
+ FieldAngle.HALF + ')',
293
+ },
294
+ svg);
295
+ }
80
296
 
81
- FieldAngle.superClass_.constructor.call(
82
- this, opt_value, opt_validator, opt_config);
297
+ // The angle picker is different from other fields in that it updates on
298
+ // mousemove even if it's not in the middle of a drag. In future we may
299
+ // change this behaviour.
300
+ this.clickWrapper_ =
301
+ browserEvents.conditionalBind(svg, 'click', this, this.hide_);
302
+ // On touch devices, the picker's value is only updated with a drag. Add
303
+ // a click handler on the drag surface to update the value if the surface
304
+ // is clicked.
305
+ this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
306
+ circle, 'click', this, this.onMouseMove_, true, true);
307
+ this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
308
+ circle, 'mousemove', this, this.onMouseMove_, true, true);
309
+ this.editor_ = svg;
310
+ }
83
311
 
84
312
  /**
85
- * The angle picker's SVG element.
86
- * @type {?SVGElement}
313
+ * Disposes of events and DOM-references belonging to the angle editor.
87
314
  * @private
88
315
  */
89
- this.editor_ = null;
316
+ dropdownDispose_() {
317
+ if (this.clickWrapper_) {
318
+ browserEvents.unbind(this.clickWrapper_);
319
+ this.clickWrapper_ = null;
320
+ }
321
+ if (this.clickSurfaceWrapper_) {
322
+ browserEvents.unbind(this.clickSurfaceWrapper_);
323
+ this.clickSurfaceWrapper_ = null;
324
+ }
325
+ if (this.moveSurfaceWrapper_) {
326
+ browserEvents.unbind(this.moveSurfaceWrapper_);
327
+ this.moveSurfaceWrapper_ = null;
328
+ }
329
+ this.gauge_ = null;
330
+ this.line_ = null;
331
+ }
90
332
 
91
333
  /**
92
- * The angle picker's gauge path depending on the value.
93
- * @type {?SVGElement}
334
+ * Hide the editor.
335
+ * @private
94
336
  */
95
- this.gauge_ = null;
337
+ hide_() {
338
+ dropDownDiv.hideIfOwner(this);
339
+ WidgetDiv.hide();
340
+ }
96
341
 
97
342
  /**
98
- * The angle picker's line drawn representing the value's angle.
99
- * @type {?SVGElement}
343
+ * Set the angle to match the mouse's position.
344
+ * @param {!Event} e Mouse move event.
345
+ * @protected
100
346
  */
101
- this.line_ = null;
347
+ onMouseMove_(e) {
348
+ // Calculate angle.
349
+ const bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
350
+ const dx = e.clientX - bBox.left - FieldAngle.HALF;
351
+ const dy = e.clientY - bBox.top - FieldAngle.HALF;
352
+ let angle = Math.atan(-dy / dx);
353
+ if (isNaN(angle)) {
354
+ // This shouldn't happen, but let's not let this error propagate further.
355
+ return;
356
+ }
357
+ angle = math.toDegrees(angle);
358
+ // 0: East, 90: North, 180: West, 270: South.
359
+ if (dx < 0) {
360
+ angle += 180;
361
+ } else if (dy > 0) {
362
+ angle += 360;
363
+ }
364
+
365
+ // Do offsetting.
366
+ if (this.clockwise_) {
367
+ angle = this.offset_ + 360 - angle;
368
+ } else {
369
+ angle = 360 - (this.offset_ - angle);
370
+ }
371
+
372
+ this.displayMouseOrKeyboardValue_(angle);
373
+ }
102
374
 
103
375
  /**
104
- * Wrapper click event data.
105
- * @type {?browserEvents.Data}
376
+ * Handles and displays values that are input via mouse or arrow key input.
377
+ * These values need to be rounded and wrapped before being displayed so
378
+ * that the text input's value is appropriate.
379
+ * @param {number} angle New angle.
106
380
  * @private
107
381
  */
108
- this.clickWrapper_ = null;
382
+ displayMouseOrKeyboardValue_(angle) {
383
+ if (this.round_) {
384
+ angle = Math.round(angle / this.round_) * this.round_;
385
+ }
386
+ angle = this.wrapValue_(angle);
387
+ if (angle !== this.value_) {
388
+ this.setEditorValue_(angle);
389
+ }
390
+ }
109
391
 
110
392
  /**
111
- * Surface click event data.
112
- * @type {?browserEvents.Data}
393
+ * Redraw the graph with the current angle.
113
394
  * @private
114
395
  */
115
- this.clickSurfaceWrapper_ = null;
396
+ updateGraph_() {
397
+ if (!this.gauge_) {
398
+ return;
399
+ }
400
+ // Always display the input (i.e. getText) even if it is invalid.
401
+ let angleDegrees = Number(this.getText()) + this.offset_;
402
+ angleDegrees %= 360;
403
+ let angleRadians = math.toRadians(angleDegrees);
404
+ const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
405
+ let x2 = FieldAngle.HALF;
406
+ let y2 = FieldAngle.HALF;
407
+ if (!isNaN(angleRadians)) {
408
+ const clockwiseFlag = Number(this.clockwise_);
409
+ const angle1 = math.toRadians(this.offset_);
410
+ const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
411
+ const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
412
+ if (clockwiseFlag) {
413
+ angleRadians = 2 * angle1 - angleRadians;
414
+ }
415
+ x2 += Math.cos(angleRadians) * FieldAngle.RADIUS;
416
+ y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS;
417
+ // Don't ask how the flag calculations work. They just do.
418
+ let largeFlag =
419
+ Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2);
420
+ if (clockwiseFlag) {
421
+ largeFlag = 1 - largeFlag;
422
+ }
423
+ path.push(
424
+ ' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
425
+ ' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
426
+ }
427
+ this.gauge_.setAttribute('d', path.join(''));
428
+ this.line_.setAttribute('x2', x2);
429
+ this.line_.setAttribute('y2', y2);
430
+ }
431
+
432
+ /**
433
+ * Handle key down to the editor.
434
+ * @param {!Event} e Keyboard event.
435
+ * @protected
436
+ * @override
437
+ */
438
+ onHtmlInputKeyDown_(e) {
439
+ super.onHtmlInputKeyDown_(e);
440
+
441
+ let multiplier;
442
+ if (e.keyCode === KeyCodes.LEFT) {
443
+ // decrement (increment in RTL)
444
+ multiplier = this.sourceBlock_.RTL ? 1 : -1;
445
+ } else if (e.keyCode === KeyCodes.RIGHT) {
446
+ // increment (decrement in RTL)
447
+ multiplier = this.sourceBlock_.RTL ? -1 : 1;
448
+ } else if (e.keyCode === KeyCodes.DOWN) {
449
+ // decrement
450
+ multiplier = -1;
451
+ } else if (e.keyCode === KeyCodes.UP) {
452
+ // increment
453
+ multiplier = 1;
454
+ }
455
+ if (multiplier) {
456
+ const value = /** @type {number} */ (this.getValue());
457
+ this.displayMouseOrKeyboardValue_(value + (multiplier * this.round_));
458
+ e.preventDefault();
459
+ e.stopPropagation();
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Ensure that the input value is a valid angle.
465
+ * @param {*=} opt_newValue The input value.
466
+ * @return {?number} A valid angle, or null if invalid.
467
+ * @protected
468
+ * @override
469
+ */
470
+ doClassValidation_(opt_newValue) {
471
+ const value = Number(opt_newValue);
472
+ if (isNaN(value) || !isFinite(value)) {
473
+ return null;
474
+ }
475
+ return this.wrapValue_(value);
476
+ }
116
477
 
117
478
  /**
118
- * Surface mouse move event data.
119
- * @type {?browserEvents.Data}
479
+ * Wraps the value so that it is in the range (-360 + wrap, wrap).
480
+ * @param {number} value The value to wrap.
481
+ * @return {number} The wrapped value.
120
482
  * @private
121
483
  */
122
- this.moveSurfaceWrapper_ = null;
123
- };
124
- object.inherits(FieldAngle, FieldTextInput);
484
+ wrapValue_(value) {
485
+ value %= 360;
486
+ if (value < 0) {
487
+ value += 360;
488
+ }
489
+ if (value > this.wrap_) {
490
+ value -= 360;
491
+ }
492
+ return value;
493
+ }
125
494
 
495
+ /**
496
+ * Construct a FieldAngle from a JSON arg object.
497
+ * @param {!Object} options A JSON object with options (angle).
498
+ * @return {!FieldAngle} The new field instance.
499
+ * @package
500
+ * @nocollapse
501
+ * @override
502
+ */
503
+ static fromJson(options) {
504
+ // `this` might be a subclass of FieldAngle if that class doesn't override
505
+ // the static fromJson method.
506
+ return new this(options['angle'], undefined, options);
507
+ }
508
+ }
126
509
 
127
510
  /**
128
511
  * The default value for this field.
@@ -131,26 +514,6 @@ object.inherits(FieldAngle, FieldTextInput);
131
514
  */
132
515
  FieldAngle.prototype.DEFAULT_VALUE = 0;
133
516
 
134
- /**
135
- * Construct a FieldAngle from a JSON arg object.
136
- * @param {!Object} options A JSON object with options (angle).
137
- * @return {!FieldAngle} The new field instance.
138
- * @package
139
- * @nocollapse
140
- */
141
- FieldAngle.fromJson = function(options) {
142
- // `this` might be a subclass of FieldAngle if that class doesn't override
143
- // the static fromJson method.
144
- return new this(options['angle'], undefined, options);
145
- };
146
-
147
- /**
148
- * Serializable fields are saved by the XML renderer, non-serializable fields
149
- * are not. Editable fields should also be serializable.
150
- * @type {boolean}
151
- */
152
- FieldAngle.prototype.SERIALIZABLE = true;
153
-
154
517
  /**
155
518
  * The default amount to round angles to when using a mouse or keyboard nav
156
519
  * input. Must be a positive integer to support keyboard navigation.
@@ -193,377 +556,34 @@ FieldAngle.WRAP = 360;
193
556
  */
194
557
  FieldAngle.RADIUS = FieldAngle.HALF - 1;
195
558
 
196
- /**
197
- * Configure the field based on the given map of options.
198
- * @param {!Object} config A map of options to configure the field based on.
199
- * @protected
200
- * @override
201
- */
202
- FieldAngle.prototype.configure_ = function(config) {
203
- FieldAngle.superClass_.configure_.call(this, config);
204
-
205
- switch (config['mode']) {
206
- case 'compass':
207
- this.clockwise_ = true;
208
- this.offset_ = 90;
209
- break;
210
- case 'protractor':
211
- // This is the default mode, so we could do nothing. But just to
212
- // future-proof, we'll set it anyway.
213
- this.clockwise_ = false;
214
- this.offset_ = 0;
215
- break;
216
- }
217
-
218
- // Allow individual settings to override the mode setting.
219
- const clockwise = config['clockwise'];
220
- if (typeof clockwise === 'boolean') {
221
- this.clockwise_ = clockwise;
222
- }
223
-
224
- // If these are passed as null then we should leave them on the default.
225
- let offset = config['offset'];
226
- if (offset !== null) {
227
- offset = Number(offset);
228
- if (!isNaN(offset)) {
229
- this.offset_ = offset;
230
- }
231
- }
232
- let wrap = config['wrap'];
233
- if (wrap !== null) {
234
- wrap = Number(wrap);
235
- if (!isNaN(wrap)) {
236
- this.wrap_ = wrap;
237
- }
238
- }
239
- let round = config['round'];
240
- if (round !== null) {
241
- round = Number(round);
242
- if (!isNaN(round)) {
243
- this.round_ = round;
244
- }
245
- }
246
- };
247
-
248
- /**
249
- * Create the block UI for this field.
250
- * @package
251
- */
252
- FieldAngle.prototype.initView = function() {
253
- FieldAngle.superClass_.initView.call(this);
254
- // Add the degree symbol to the left of the number, even in RTL (issue #2380)
255
- this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}, null);
256
- this.symbol_.appendChild(document.createTextNode('\u00B0'));
257
- this.textElement_.appendChild(this.symbol_);
258
- };
259
-
260
- /**
261
- * Updates the graph when the field rerenders.
262
- * @protected
263
- * @override
264
- */
265
- FieldAngle.prototype.render_ = function() {
266
- FieldAngle.superClass_.render_.call(this);
267
- this.updateGraph_();
268
- };
269
-
270
- /**
271
- * Create and show the angle field's editor.
272
- * @param {Event=} opt_e Optional mouse event that triggered the field to open,
273
- * or undefined if triggered programmatically.
274
- * @protected
275
- */
276
- FieldAngle.prototype.showEditor_ = function(opt_e) {
277
- // Mobile browsers have issues with in-line textareas (focus & keyboards).
278
- const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
279
- FieldAngle.superClass_.showEditor_.call(this, opt_e, noFocus);
280
-
281
- this.dropdownCreate_();
282
- DropDownDiv.getContentDiv().appendChild(this.editor_);
283
-
284
- DropDownDiv.setColour(
285
- this.sourceBlock_.style.colourPrimary,
286
- this.sourceBlock_.style.colourTertiary);
287
-
288
- DropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
289
-
290
- this.updateGraph_();
291
- };
292
-
293
- /**
294
- * Create the angle dropdown editor.
295
- * @private
296
- */
297
- FieldAngle.prototype.dropdownCreate_ = function() {
298
- const svg = dom.createSvgElement(
299
- Svg.SVG, {
300
- 'xmlns': dom.SVG_NS,
301
- 'xmlns:html': dom.HTML_NS,
302
- 'xmlns:xlink': dom.XLINK_NS,
303
- 'version': '1.1',
304
- 'height': (FieldAngle.HALF * 2) + 'px',
305
- 'width': (FieldAngle.HALF * 2) + 'px',
306
- 'style': 'touch-action: none',
307
- },
308
- null);
309
- const circle = dom.createSvgElement(
310
- Svg.CIRCLE, {
311
- 'cx': FieldAngle.HALF,
312
- 'cy': FieldAngle.HALF,
313
- 'r': FieldAngle.RADIUS,
314
- 'class': 'blocklyAngleCircle',
315
- },
316
- svg);
317
- this.gauge_ =
318
- dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
319
- this.line_ = dom.createSvgElement(
320
- Svg.LINE, {
321
- 'x1': FieldAngle.HALF,
322
- 'y1': FieldAngle.HALF,
323
- 'class': 'blocklyAngleLine',
324
- },
325
- svg);
326
- // Draw markers around the edge.
327
- for (let angle = 0; angle < 360; angle += 15) {
328
- dom.createSvgElement(
329
- Svg.LINE, {
330
- 'x1': FieldAngle.HALF + FieldAngle.RADIUS,
331
- 'y1': FieldAngle.HALF,
332
- 'x2':
333
- FieldAngle.HALF + FieldAngle.RADIUS - (angle % 45 === 0 ? 10 : 5),
334
- 'y2': FieldAngle.HALF,
335
- 'class': 'blocklyAngleMarks',
336
- 'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' +
337
- FieldAngle.HALF + ')',
338
- },
339
- svg);
340
- }
341
-
342
- // The angle picker is different from other fields in that it updates on
343
- // mousemove even if it's not in the middle of a drag. In future we may
344
- // change this behaviour.
345
- this.clickWrapper_ =
346
- browserEvents.conditionalBind(svg, 'click', this, this.hide_);
347
- // On touch devices, the picker's value is only updated with a drag. Add
348
- // a click handler on the drag surface to update the value if the surface
349
- // is clicked.
350
- this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
351
- circle, 'click', this, this.onMouseMove_, true, true);
352
- this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
353
- circle, 'mousemove', this, this.onMouseMove_, true, true);
354
- this.editor_ = svg;
355
- };
356
-
357
- /**
358
- * Disposes of events and DOM-references belonging to the angle editor.
359
- * @private
360
- */
361
- FieldAngle.prototype.dropdownDispose_ = function() {
362
- if (this.clickWrapper_) {
363
- browserEvents.unbind(this.clickWrapper_);
364
- this.clickWrapper_ = null;
365
- }
366
- if (this.clickSurfaceWrapper_) {
367
- browserEvents.unbind(this.clickSurfaceWrapper_);
368
- this.clickSurfaceWrapper_ = null;
369
- }
370
- if (this.moveSurfaceWrapper_) {
371
- browserEvents.unbind(this.moveSurfaceWrapper_);
372
- this.moveSurfaceWrapper_ = null;
373
- }
374
- this.gauge_ = null;
375
- this.line_ = null;
376
- };
377
-
378
- /**
379
- * Hide the editor.
380
- * @private
381
- */
382
- FieldAngle.prototype.hide_ = function() {
383
- DropDownDiv.hideIfOwner(this);
384
- WidgetDiv.hide();
385
- };
386
-
387
- /**
388
- * Set the angle to match the mouse's position.
389
- * @param {!Event} e Mouse move event.
390
- * @protected
391
- */
392
- FieldAngle.prototype.onMouseMove_ = function(e) {
393
- // Calculate angle.
394
- const bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
395
- const dx = e.clientX - bBox.left - FieldAngle.HALF;
396
- const dy = e.clientY - bBox.top - FieldAngle.HALF;
397
- let angle = Math.atan(-dy / dx);
398
- if (isNaN(angle)) {
399
- // This shouldn't happen, but let's not let this error propagate further.
400
- return;
401
- }
402
- angle = math.toDegrees(angle);
403
- // 0: East, 90: North, 180: West, 270: South.
404
- if (dx < 0) {
405
- angle += 180;
406
- } else if (dy > 0) {
407
- angle += 360;
408
- }
409
-
410
- // Do offsetting.
411
- if (this.clockwise_) {
412
- angle = this.offset_ + 360 - angle;
413
- } else {
414
- angle = 360 - (this.offset_ - angle);
415
- }
416
-
417
- this.displayMouseOrKeyboardValue_(angle);
418
- };
419
-
420
- /**
421
- * Handles and displays values that are input via mouse or arrow key input.
422
- * These values need to be rounded and wrapped before being displayed so
423
- * that the text input's value is appropriate.
424
- * @param {number} angle New angle.
425
- * @private
426
- */
427
- FieldAngle.prototype.displayMouseOrKeyboardValue_ = function(angle) {
428
- if (this.round_) {
429
- angle = Math.round(angle / this.round_) * this.round_;
430
- }
431
- angle = this.wrapValue_(angle);
432
- if (angle !== this.value_) {
433
- this.setEditorValue_(angle);
434
- }
435
- };
436
-
437
- /**
438
- * Redraw the graph with the current angle.
439
- * @private
440
- */
441
- FieldAngle.prototype.updateGraph_ = function() {
442
- if (!this.gauge_) {
443
- return;
444
- }
445
- // Always display the input (i.e. getText) even if it is invalid.
446
- let angleDegrees = Number(this.getText()) + this.offset_;
447
- angleDegrees %= 360;
448
- let angleRadians = math.toRadians(angleDegrees);
449
- const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
450
- let x2 = FieldAngle.HALF;
451
- let y2 = FieldAngle.HALF;
452
- if (!isNaN(angleRadians)) {
453
- const clockwiseFlag = Number(this.clockwise_);
454
- const angle1 = math.toRadians(this.offset_);
455
- const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
456
- const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
457
- if (clockwiseFlag) {
458
- angleRadians = 2 * angle1 - angleRadians;
459
- }
460
- x2 += Math.cos(angleRadians) * FieldAngle.RADIUS;
461
- y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS;
462
- // Don't ask how the flag calculations work. They just do.
463
- let largeFlag = Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2);
464
- if (clockwiseFlag) {
465
- largeFlag = 1 - largeFlag;
466
- }
467
- path.push(
468
- ' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
469
- ' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
470
- }
471
- this.gauge_.setAttribute('d', path.join(''));
472
- this.line_.setAttribute('x2', x2);
473
- this.line_.setAttribute('y2', y2);
474
- };
475
-
476
- /**
477
- * Handle key down to the editor.
478
- * @param {!Event} e Keyboard event.
479
- * @protected
480
- * @override
481
- */
482
- FieldAngle.prototype.onHtmlInputKeyDown_ = function(e) {
483
- FieldAngle.superClass_.onHtmlInputKeyDown_.call(this, e);
484
-
485
- let multiplier;
486
- if (e.keyCode === KeyCodes.LEFT) {
487
- // decrement (increment in RTL)
488
- multiplier = this.sourceBlock_.RTL ? 1 : -1;
489
- } else if (e.keyCode === KeyCodes.RIGHT) {
490
- // increment (decrement in RTL)
491
- multiplier = this.sourceBlock_.RTL ? -1 : 1;
492
- } else if (e.keyCode === KeyCodes.DOWN) {
493
- // decrement
494
- multiplier = -1;
495
- } else if (e.keyCode === KeyCodes.UP) {
496
- // increment
497
- multiplier = 1;
498
- }
499
- if (multiplier) {
500
- const value = /** @type {number} */ (this.getValue());
501
- this.displayMouseOrKeyboardValue_(value + (multiplier * this.round_));
502
- e.preventDefault();
503
- e.stopPropagation();
504
- }
505
- };
506
-
507
- /**
508
- * Ensure that the input value is a valid angle.
509
- * @param {*=} opt_newValue The input value.
510
- * @return {?number} A valid angle, or null if invalid.
511
- * @protected
512
- * @override
513
- */
514
- FieldAngle.prototype.doClassValidation_ = function(opt_newValue) {
515
- const value = Number(opt_newValue);
516
- if (isNaN(value) || !isFinite(value)) {
517
- return null;
518
- }
519
- return this.wrapValue_(value);
520
- };
521
-
522
- /**
523
- * Wraps the value so that it is in the range (-360 + wrap, wrap).
524
- * @param {number} value The value to wrap.
525
- * @return {number} The wrapped value.
526
- * @private
527
- */
528
- FieldAngle.prototype.wrapValue_ = function(value) {
529
- value %= 360;
530
- if (value < 0) {
531
- value += 360;
532
- }
533
- if (value > this.wrap_) {
534
- value -= 360;
535
- }
536
- return value;
537
- };
538
-
539
559
  /**
540
560
  * CSS for angle field. See css.js for use.
541
561
  */
542
562
  Css.register(`
543
- .blocklyAngleCircle {
544
- stroke: #444;
545
- stroke-width: 1;
546
- fill: #ddd;
547
- fill-opacity: .8;
548
- }
549
-
550
- .blocklyAngleMarks {
551
- stroke: #444;
552
- stroke-width: 1;
553
- }
554
-
555
- .blocklyAngleGauge {
556
- fill: #f88;
557
- fill-opacity: .8;
558
- pointer-events: none;
559
- }
560
-
561
- .blocklyAngleLine {
562
- stroke: #f00;
563
- stroke-width: 2;
564
- stroke-linecap: round;
565
- pointer-events: none;
566
- }
563
+ .blocklyAngleCircle {
564
+ stroke: #444;
565
+ stroke-width: 1;
566
+ fill: #ddd;
567
+ fill-opacity: .8;
568
+ }
569
+
570
+ .blocklyAngleMarks {
571
+ stroke: #444;
572
+ stroke-width: 1;
573
+ }
574
+
575
+ .blocklyAngleGauge {
576
+ fill: #f88;
577
+ fill-opacity: .8;
578
+ pointer-events: none;
579
+ }
580
+
581
+ .blocklyAngleLine {
582
+ stroke: #f00;
583
+ stroke-width: 2;
584
+ stroke-linecap: round;
585
+ pointer-events: none;
586
+ }
567
587
  `);
568
588
 
569
589
  fieldRegistry.register('field_angle', FieldAngle);