blockly 7.20211209.2 → 8.0.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 (262) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +852 -844
  3. package/blockly_compressed.js +669 -664
  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 +41 -27
  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 +146 -141
  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 +19 -9
  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/dart_compressed.js +40 -43
  208. package/dart_compressed.js.map +1 -1
  209. package/generators/dart/colour.js +56 -64
  210. package/generators/dart/lists.js +61 -50
  211. package/generators/dart/math.js +160 -148
  212. package/generators/dart/text.js +83 -61
  213. package/generators/javascript/colour.js +37 -34
  214. package/generators/javascript/lists.js +50 -43
  215. package/generators/javascript/math.js +123 -139
  216. package/generators/javascript/text.js +67 -81
  217. package/generators/lua/colour.js +25 -23
  218. package/generators/lua/lists.js +97 -69
  219. package/generators/lua/logic.js +1 -2
  220. package/generators/lua/math.js +182 -144
  221. package/generators/lua/text.js +116 -99
  222. package/generators/php/colour.js +38 -32
  223. package/generators/php/lists.js +109 -89
  224. package/generators/php/math.js +90 -81
  225. package/generators/php/text.js +63 -61
  226. package/generators/python/colour.js +18 -18
  227. package/generators/python/lists.js +38 -30
  228. package/generators/python/loops.js +12 -8
  229. package/generators/python/math.js +104 -106
  230. package/generators/python/text.js +34 -30
  231. package/javascript_compressed.js +37 -39
  232. package/javascript_compressed.js.map +1 -1
  233. package/lua_compressed.js +39 -42
  234. package/lua_compressed.js.map +1 -1
  235. package/msg/az.js +2 -2
  236. package/msg/be.js +4 -4
  237. package/msg/cs.js +15 -15
  238. package/msg/de.js +1 -1
  239. package/msg/diq.js +1 -1
  240. package/msg/eo.js +1 -1
  241. package/msg/es.js +1 -1
  242. package/msg/fa.js +1 -1
  243. package/msg/fr.js +4 -4
  244. package/msg/he.js +1 -1
  245. package/msg/hr.js +2 -2
  246. package/msg/hy.js +2 -2
  247. package/msg/id.js +12 -12
  248. package/msg/inh.js +14 -14
  249. package/msg/ja.js +7 -7
  250. package/msg/lv.js +29 -29
  251. package/msg/pa.js +3 -3
  252. package/msg/smn.js +436 -0
  253. package/msg/te.js +1 -1
  254. package/msg/yue.js +1 -1
  255. package/msg/zh-hans.js +3 -3
  256. package/msg/zh-hant.js +3 -3
  257. package/package.json +7 -6
  258. package/php_compressed.js +38 -42
  259. package/php_compressed.js.map +1 -1
  260. package/python_compressed.js +26 -25
  261. package/python_compressed.js.map +1 -1
  262. package/blocks/all.js +0 -23
@@ -22,7 +22,6 @@ const browserEvents = goog.require('Blockly.browserEvents');
22
22
  const common = goog.require('Blockly.common');
23
23
  const dom = goog.require('Blockly.utils.dom');
24
24
  const eventUtils = goog.require('Blockly.Events.utils');
25
- const object = goog.require('Blockly.utils.object');
26
25
  const registry = goog.require('Blockly.registry');
27
26
  const toolbox = goog.require('Blockly.utils.toolbox');
28
27
  const {BlockSvg} = goog.require('Blockly.BlockSvg');
@@ -63,9 +62,6 @@ goog.require('Blockly.Events.ToolboxItemSelect');
63
62
  /**
64
63
  * Class for a Toolbox.
65
64
  * Creates the toolbox's DOM.
66
- * @param {!WorkspaceSvg} workspace The workspace in which to create new
67
- * blocks.
68
- * @constructor
69
65
  * @implements {IAutoHideable}
70
66
  * @implements {IKeyboardAccessible}
71
67
  * @implements {IStyleable}
@@ -73,1079 +69,1090 @@ goog.require('Blockly.Events.ToolboxItemSelect');
73
69
  * @extends {DeleteArea}
74
70
  * @alias Blockly.Toolbox
75
71
  */
76
- const Toolbox = function(workspace) {
77
- Toolbox.superClass_.constructor.call(this);
72
+ class Toolbox extends DeleteArea {
78
73
  /**
79
- * The workspace this toolbox is on.
80
- * @type {!WorkspaceSvg}
81
- * @protected
74
+ * @param {!WorkspaceSvg} workspace The workspace in which to create new
75
+ * blocks.
82
76
  */
83
- this.workspace_ = workspace;
77
+ constructor(workspace) {
78
+ super();
79
+
80
+ /**
81
+ * The workspace this toolbox is on.
82
+ * @type {!WorkspaceSvg}
83
+ * @protected
84
+ */
85
+ this.workspace_ = workspace;
86
+
87
+ /**
88
+ * The unique id for this component that is used to register with the
89
+ * ComponentManager.
90
+ * @type {string}
91
+ */
92
+ this.id = 'toolbox';
93
+
94
+ /**
95
+ * The JSON describing the contents of this toolbox.
96
+ * @type {!toolbox.ToolboxInfo}
97
+ * @protected
98
+ */
99
+ this.toolboxDef_ = workspace.options.languageTree || {'contents': []};
100
+
101
+ /**
102
+ * Whether the toolbox should be laid out horizontally.
103
+ * @type {boolean}
104
+ * @private
105
+ */
106
+ this.horizontalLayout_ = workspace.options.horizontalLayout;
107
+
108
+ /**
109
+ * The html container for the toolbox.
110
+ * @type {?HTMLDivElement}
111
+ */
112
+ this.HtmlDiv = null;
113
+
114
+ /**
115
+ * The html container for the contents of a toolbox.
116
+ * @type {?HTMLDivElement}
117
+ * @protected
118
+ */
119
+ this.contentsDiv_ = null;
120
+
121
+ /**
122
+ * Whether the Toolbox is visible.
123
+ * @type {boolean}
124
+ * @protected
125
+ */
126
+ this.isVisible_ = false;
127
+
128
+ /**
129
+ * The list of items in the toolbox.
130
+ * @type {!Array<!IToolboxItem>}
131
+ * @protected
132
+ */
133
+ this.contents_ = [];
134
+
135
+ /**
136
+ * The width of the toolbox.
137
+ * @type {number}
138
+ * @protected
139
+ */
140
+ this.width_ = 0;
141
+
142
+ /**
143
+ * The height of the toolbox.
144
+ * @type {number}
145
+ * @protected
146
+ */
147
+ this.height_ = 0;
148
+
149
+ /**
150
+ * Is RTL vs LTR.
151
+ * @type {boolean}
152
+ */
153
+ this.RTL = workspace.options.RTL;
154
+
155
+ /**
156
+ * The flyout for the toolbox.
157
+ * @type {?IFlyout}
158
+ * @private
159
+ */
160
+ this.flyout_ = null;
161
+
162
+ /**
163
+ * A map from toolbox item IDs to toolbox items.
164
+ * @type {!Object<string, !IToolboxItem>}
165
+ * @protected
166
+ */
167
+ this.contentMap_ = Object.create(null);
168
+
169
+ /**
170
+ * Position of the toolbox and flyout relative to the workspace.
171
+ * @type {!toolbox.Position}
172
+ */
173
+ this.toolboxPosition = workspace.options.toolboxPosition;
174
+
175
+ /**
176
+ * The currently selected item.
177
+ * @type {?ISelectableToolboxItem}
178
+ * @protected
179
+ */
180
+ this.selectedItem_ = null;
181
+
182
+ /**
183
+ * The previously selected item.
184
+ * @type {?ISelectableToolboxItem}
185
+ * @protected
186
+ */
187
+ this.previouslySelectedItem_ = null;
188
+
189
+ /**
190
+ * Array holding info needed to unbind event handlers.
191
+ * Used for disposing.
192
+ * Ex: [[node, name, func], [node, name, func]].
193
+ * @type {!Array<!browserEvents.Data>}
194
+ * @protected
195
+ */
196
+ this.boundEvents_ = [];
197
+ }
84
198
 
85
199
  /**
86
- * The unique id for this component that is used to register with the
87
- * ComponentManager.
88
- * @type {string}
200
+ * Handles the given keyboard shortcut.
201
+ * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be
202
+ * handled.
203
+ * @return {boolean} True if the shortcut has been handled, false otherwise.
204
+ * @public
89
205
  */
90
- this.id = 'toolbox';
206
+ onShortcut(_shortcut) {
207
+ return false;
208
+ }
91
209
 
92
210
  /**
93
- * The JSON describing the contents of this toolbox.
94
- * @type {!toolbox.ToolboxInfo}
95
- * @protected
211
+ * Initializes the toolbox
212
+ * @public
96
213
  */
97
- this.toolboxDef_ = workspace.options.languageTree || {'contents': []};
214
+ init() {
215
+ const workspace = this.workspace_;
216
+ const svg = workspace.getParentSvg();
217
+
218
+ this.flyout_ = this.createFlyout_();
219
+
220
+ this.HtmlDiv = this.createDom_(this.workspace_);
221
+ dom.insertAfter(this.flyout_.createDom('svg'), svg);
222
+ this.setVisible(true);
223
+ this.flyout_.init(workspace);
224
+
225
+ this.render(this.toolboxDef_);
226
+ const themeManager = workspace.getThemeManager();
227
+ themeManager.subscribe(
228
+ this.HtmlDiv, 'toolboxBackgroundColour', 'background-color');
229
+ themeManager.subscribe(this.HtmlDiv, 'toolboxForegroundColour', 'color');
230
+ this.workspace_.getComponentManager().addComponent({
231
+ component: this,
232
+ weight: 1,
233
+ capabilities: [
234
+ ComponentManager.Capability.AUTOHIDEABLE,
235
+ ComponentManager.Capability.DELETE_AREA,
236
+ ComponentManager.Capability.DRAG_TARGET,
237
+ ],
238
+ });
239
+ }
98
240
 
99
241
  /**
100
- * Whether the toolbox should be laid out horizontally.
101
- * @type {boolean}
102
- * @private
242
+ * Creates the DOM for the toolbox.
243
+ * @param {!WorkspaceSvg} workspace The workspace this toolbox is on.
244
+ * @return {!HTMLDivElement} The HTML container for the toolbox.
245
+ * @protected
103
246
  */
104
- this.horizontalLayout_ = workspace.options.horizontalLayout;
247
+ createDom_(workspace) {
248
+ const svg = workspace.getParentSvg();
105
249
 
106
- /**
107
- * The html container for the toolbox.
108
- * @type {?Element}
109
- */
110
- this.HtmlDiv = null;
250
+ const container = this.createContainer_();
251
+
252
+ this.contentsDiv_ = this.createContentsContainer_();
253
+ this.contentsDiv_.tabIndex = 0;
254
+ aria.setRole(this.contentsDiv_, aria.Role.TREE);
255
+ container.appendChild(this.contentsDiv_);
256
+
257
+ svg.parentNode.insertBefore(container, svg);
258
+
259
+ this.attachEvents_(container, this.contentsDiv_);
260
+ return container;
261
+ }
111
262
 
112
263
  /**
113
- * The html container for the contents of a toolbox.
114
- * @type {?Element}
264
+ * Creates the container div for the toolbox.
265
+ * @return {!HTMLDivElement} The HTML container for the toolbox.
115
266
  * @protected
116
267
  */
117
- this.contentsDiv_ = null;
268
+ createContainer_() {
269
+ const toolboxContainer =
270
+ /** @type {!HTMLDivElement} */ (document.createElement('div'));
271
+ toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v');
272
+ dom.addClass(toolboxContainer, 'blocklyToolboxDiv');
273
+ dom.addClass(toolboxContainer, 'blocklyNonSelectable');
274
+ toolboxContainer.setAttribute('dir', this.RTL ? 'RTL' : 'LTR');
275
+ return toolboxContainer;
276
+ }
118
277
 
119
278
  /**
120
- * Whether the Toolbox is visible.
121
- * @type {boolean}
279
+ * Creates the container for all the contents in the toolbox.
280
+ * @return {!HTMLDivElement} The HTML container for the toolbox contents.
122
281
  * @protected
123
282
  */
124
- this.isVisible_ = false;
283
+ createContentsContainer_() {
284
+ const contentsContainer =
285
+ /** @type {!HTMLDivElement} */ (document.createElement('div'));
286
+ dom.addClass(contentsContainer, 'blocklyToolboxContents');
287
+ if (this.isHorizontal()) {
288
+ contentsContainer.style.flexDirection = 'row';
289
+ }
290
+ return contentsContainer;
291
+ }
125
292
 
126
293
  /**
127
- * The list of items in the toolbox.
128
- * @type {!Array<!IToolboxItem>}
294
+ * Adds event listeners to the toolbox container div.
295
+ * @param {!HTMLDivElement} container The HTML container for the toolbox.
296
+ * @param {!HTMLDivElement} contentsContainer The HTML container for the
297
+ * contents of the toolbox.
129
298
  * @protected
130
299
  */
131
- this.contents_ = [];
300
+ attachEvents_(container, contentsContainer) {
301
+ // Clicking on toolbox closes popups.
302
+ const clickEvent = browserEvents.conditionalBind(
303
+ container, 'click', this, this.onClick_,
304
+ /* opt_noCaptureIdentifier */ false,
305
+ /* opt_noPreventDefault */ true);
306
+ this.boundEvents_.push(clickEvent);
307
+
308
+ const keyDownEvent = browserEvents.conditionalBind(
309
+ contentsContainer, 'keydown', this, this.onKeyDown_,
310
+ /* opt_noCaptureIdentifier */ false,
311
+ /* opt_noPreventDefault */ true);
312
+ this.boundEvents_.push(keyDownEvent);
313
+ }
132
314
 
133
315
  /**
134
- * The width of the toolbox.
135
- * @type {number}
316
+ * Handles on click events for when the toolbox or toolbox items are clicked.
317
+ * @param {!Event} e Click event to handle.
136
318
  * @protected
137
319
  */
138
- this.width_ = 0;
320
+ onClick_(e) {
321
+ if (browserEvents.isRightButton(e) || e.target === this.HtmlDiv) {
322
+ // Close flyout.
323
+ /** @type {!WorkspaceSvg} */ (common.getMainWorkspace()).hideChaff(false);
324
+ } else {
325
+ const targetElement = e.target;
326
+ const itemId = targetElement.getAttribute('id');
327
+ if (itemId) {
328
+ const item = this.getToolboxItemById(itemId);
329
+ if (item.isSelectable()) {
330
+ this.setSelectedItem(item);
331
+ item.onClick(e);
332
+ }
333
+ }
334
+ // Just close popups.
335
+ /** @type {!WorkspaceSvg} */ (common.getMainWorkspace()).hideChaff(true);
336
+ }
337
+ Touch.clearTouchIdentifier(); // Don't block future drags.
338
+ }
139
339
 
140
340
  /**
141
- * The height of the toolbox.
142
- * @type {number}
341
+ * Handles key down events for the toolbox.
342
+ * @param {!KeyboardEvent} e The key down event.
143
343
  * @protected
144
344
  */
145
- this.height_ = 0;
345
+ onKeyDown_(e) {
346
+ let handled = false;
347
+ switch (e.keyCode) {
348
+ case KeyCodes.DOWN:
349
+ handled = this.selectNext_();
350
+ break;
351
+ case KeyCodes.UP:
352
+ handled = this.selectPrevious_();
353
+ break;
354
+ case KeyCodes.LEFT:
355
+ handled = this.selectParent_();
356
+ break;
357
+ case KeyCodes.RIGHT:
358
+ handled = this.selectChild_();
359
+ break;
360
+ case KeyCodes.ENTER:
361
+ case KeyCodes.SPACE:
362
+ if (this.selectedItem_ && this.selectedItem_.isCollapsible()) {
363
+ const collapsibleItem =
364
+ /** @type {!ICollapsibleToolboxItem} */ (this.selectedItem_);
365
+ collapsibleItem.toggleExpanded();
366
+ handled = true;
367
+ }
368
+ break;
369
+ default:
370
+ handled = false;
371
+ break;
372
+ }
373
+ if (!handled && this.selectedItem_ && this.selectedItem_.onKeyDown) {
374
+ handled = this.selectedItem_.onKeyDown(e);
375
+ }
376
+
377
+ if (handled) {
378
+ e.preventDefault();
379
+ }
380
+ }
146
381
 
147
382
  /**
148
- * Is RTL vs LTR.
149
- * @type {boolean}
383
+ * Creates the flyout based on the toolbox layout.
384
+ * @return {!IFlyout} The flyout for the toolbox.
385
+ * @throws {Error} If missing a require for `Blockly.HorizontalFlyout`,
386
+ * `Blockly.VerticalFlyout`, and no flyout plugin is specified.
387
+ * @protected
150
388
  */
151
- this.RTL = workspace.options.RTL;
389
+ createFlyout_() {
390
+ const workspace = this.workspace_;
391
+ // TODO (#4247): Look into adding a makeFlyout method to Blockly Options.
392
+ const workspaceOptions = new Options(
393
+ /** @type {!BlocklyOptions} */
394
+ ({
395
+ 'parentWorkspace': workspace,
396
+ 'rtl': workspace.RTL,
397
+ 'oneBasedIndex': workspace.options.oneBasedIndex,
398
+ 'horizontalLayout': workspace.horizontalLayout,
399
+ 'renderer': workspace.options.renderer,
400
+ 'rendererOverrides': workspace.options.rendererOverrides,
401
+ 'move': {
402
+ 'scrollbars': true,
403
+ },
404
+ }));
405
+ // Options takes in either 'end' or 'start'. This has already been parsed to
406
+ // be either 0 or 1, so set it after.
407
+ workspaceOptions.toolboxPosition = workspace.options.toolboxPosition;
408
+ let FlyoutClass = null;
409
+ if (workspace.horizontalLayout) {
410
+ FlyoutClass = registry.getClassFromOptions(
411
+ registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, workspace.options, true);
412
+ } else {
413
+ FlyoutClass = registry.getClassFromOptions(
414
+ registry.Type.FLYOUTS_VERTICAL_TOOLBOX, workspace.options, true);
415
+ }
416
+ return new FlyoutClass(workspaceOptions);
417
+ }
152
418
 
153
419
  /**
154
- * The flyout for the toolbox.
155
- * @type {?IFlyout}
156
- * @private
420
+ * Fills the toolbox with new toolbox items and removes any old contents.
421
+ * @param {!toolbox.ToolboxInfo} toolboxDef Object holding information
422
+ * for creating a toolbox.
423
+ * @package
157
424
  */
158
- this.flyout_ = null;
425
+ render(toolboxDef) {
426
+ this.toolboxDef_ = toolboxDef;
427
+ for (let i = 0; i < this.contents_.length; i++) {
428
+ const toolboxItem = this.contents_[i];
429
+ if (toolboxItem) {
430
+ toolboxItem.dispose();
431
+ }
432
+ }
433
+ this.contents_ = [];
434
+ this.contentMap_ = Object.create(null);
435
+ this.renderContents_(toolboxDef['contents']);
436
+ this.position();
437
+ this.handleToolboxItemResize();
438
+ }
159
439
 
160
440
  /**
161
- * A map from toolbox item IDs to toolbox items.
162
- * @type {!Object<string, !IToolboxItem>}
441
+ * Adds all the toolbox items to the toolbox.
442
+ * @param {!Array<!toolbox.ToolboxItemInfo>} toolboxDef Array
443
+ * holding objects containing information on the contents of the toolbox.
163
444
  * @protected
164
445
  */
165
- this.contentMap_ = Object.create(null);
446
+ renderContents_(toolboxDef) {
447
+ // This is for performance reasons. By using document fragment we only have
448
+ // to add to the DOM once.
449
+ const fragment = document.createDocumentFragment();
450
+ for (let i = 0; i < toolboxDef.length; i++) {
451
+ const toolboxItemDef = toolboxDef[i];
452
+ this.createToolboxItem_(toolboxItemDef, fragment);
453
+ }
454
+ this.contentsDiv_.appendChild(fragment);
455
+ }
166
456
 
167
457
  /**
168
- * Position of the toolbox and flyout relative to the workspace.
169
- * @type {!toolbox.Position}
458
+ * Creates and renders the toolbox item.
459
+ * @param {!toolbox.ToolboxItemInfo} toolboxItemDef Any information
460
+ * that can be used to create an item in the toolbox.
461
+ * @param {!DocumentFragment} fragment The document fragment to add the child
462
+ * toolbox elements to.
463
+ * @private
170
464
  */
171
- this.toolboxPosition = workspace.options.toolboxPosition;
465
+ createToolboxItem_(toolboxItemDef, fragment) {
466
+ let registryName = toolboxItemDef['kind'];
467
+
468
+ // Categories that are collapsible are created using a class registered
469
+ // under a different name.
470
+ if (registryName.toUpperCase() === 'CATEGORY' &&
471
+ toolbox.isCategoryCollapsible(
472
+ /** @type {!toolbox.CategoryInfo} */ (toolboxItemDef))) {
473
+ registryName = CollapsibleToolboxCategory.registrationName;
474
+ }
475
+
476
+ const ToolboxItemClass = registry.getClass(
477
+ registry.Type.TOOLBOX_ITEM, registryName.toLowerCase());
478
+ if (ToolboxItemClass) {
479
+ const toolboxItem = new ToolboxItemClass(toolboxItemDef, this);
480
+ toolboxItem.init();
481
+ this.addToolboxItem_(toolboxItem);
482
+ const toolboxItemDom = toolboxItem.getDiv();
483
+ if (toolboxItemDom) {
484
+ fragment.appendChild(toolboxItemDom);
485
+ }
486
+ // Adds the ID to the HTML element that can receive a click.
487
+ // This is used in onClick_ to find the toolboxItem that was clicked.
488
+ if (toolboxItem.getClickTarget()) {
489
+ toolboxItem.getClickTarget().setAttribute('id', toolboxItem.getId());
490
+ }
491
+ }
492
+ }
172
493
 
173
494
  /**
174
- * The currently selected item.
175
- * @type {?ISelectableToolboxItem}
495
+ * Adds an item to the toolbox.
496
+ * @param {!IToolboxItem} toolboxItem The item in the toolbox.
176
497
  * @protected
177
498
  */
178
- this.selectedItem_ = null;
499
+ addToolboxItem_(toolboxItem) {
500
+ this.contents_.push(toolboxItem);
501
+ this.contentMap_[toolboxItem.getId()] = toolboxItem;
502
+ if (toolboxItem.isCollapsible()) {
503
+ const collapsibleItem = /** @type {ICollapsibleToolboxItem} */
504
+ (toolboxItem);
505
+ const childToolboxItems = collapsibleItem.getChildToolboxItems();
506
+ for (let i = 0; i < childToolboxItems.length; i++) {
507
+ const child = childToolboxItems[i];
508
+ this.addToolboxItem_(child);
509
+ }
510
+ }
511
+ }
179
512
 
180
513
  /**
181
- * The previously selected item.
182
- * @type {?ISelectableToolboxItem}
183
- * @protected
514
+ * Gets the items in the toolbox.
515
+ * @return {!Array<!IToolboxItem>} The list of items in the toolbox.
516
+ * @public
184
517
  */
185
- this.previouslySelectedItem_ = null;
518
+ getToolboxItems() {
519
+ return this.contents_;
520
+ }
186
521
 
187
522
  /**
188
- * Array holding info needed to unbind event handlers.
189
- * Used for disposing.
190
- * Ex: [[node, name, func], [node, name, func]].
191
- * @type {!Array<!browserEvents.Data>}
192
- * @protected
523
+ * Adds a style on the toolbox. Usually used to change the cursor.
524
+ * @param {string} style The name of the class to add.
525
+ * @package
193
526
  */
194
- this.boundEvents_ = [];
195
- };
196
- object.inherits(Toolbox, DeleteArea);
197
-
198
- /**
199
- * Handles the given keyboard shortcut.
200
- * @param {!ShortcutRegistry.KeyboardShortcut} _shortcut The shortcut to be
201
- * handled.
202
- * @return {boolean} True if the shortcut has been handled, false otherwise.
203
- * @public
204
- */
205
- Toolbox.prototype.onShortcut = function(_shortcut) {
206
- return false;
207
- };
208
-
209
- /**
210
- * Initializes the toolbox
211
- * @public
212
- */
213
- Toolbox.prototype.init = function() {
214
- const workspace = this.workspace_;
215
- const svg = workspace.getParentSvg();
216
-
217
- this.flyout_ = this.createFlyout_();
218
-
219
- this.HtmlDiv = this.createDom_(this.workspace_);
220
- dom.insertAfter(this.flyout_.createDom('svg'), svg);
221
- this.setVisible(true);
222
- this.flyout_.init(workspace);
223
-
224
- this.render(this.toolboxDef_);
225
- const themeManager = workspace.getThemeManager();
226
- themeManager.subscribe(
227
- this.HtmlDiv, 'toolboxBackgroundColour', 'background-color');
228
- themeManager.subscribe(this.HtmlDiv, 'toolboxForegroundColour', 'color');
229
- this.workspace_.getComponentManager().addComponent({
230
- component: this,
231
- weight: 1,
232
- capabilities: [
233
- ComponentManager.Capability.AUTOHIDEABLE,
234
- ComponentManager.Capability.DELETE_AREA,
235
- ComponentManager.Capability.DRAG_TARGET,
236
- ],
237
- });
238
- };
239
-
240
- /**
241
- * Creates the DOM for the toolbox.
242
- * @param {!WorkspaceSvg} workspace The workspace this toolbox is on.
243
- * @return {!Element} The HTML container for the toolbox.
244
- * @protected
245
- */
246
- Toolbox.prototype.createDom_ = function(workspace) {
247
- const svg = workspace.getParentSvg();
248
-
249
- const container = this.createContainer_();
527
+ addStyle(style) {
528
+ dom.addClass(/** @type {!Element} */ (this.HtmlDiv), style);
529
+ }
250
530
 
251
- this.contentsDiv_ = this.createContentsContainer_();
252
- this.contentsDiv_.tabIndex = 0;
253
- aria.setRole(this.contentsDiv_, aria.Role.TREE);
254
- container.appendChild(this.contentsDiv_);
531
+ /**
532
+ * Removes a style from the toolbox. Usually used to change the cursor.
533
+ * @param {string} style The name of the class to remove.
534
+ * @package
535
+ */
536
+ removeStyle(style) {
537
+ dom.removeClass(/** @type {!Element} */ (this.HtmlDiv), style);
538
+ }
255
539
 
256
- svg.parentNode.insertBefore(container, svg);
540
+ /**
541
+ * Returns the bounding rectangle of the drag target area in pixel units
542
+ * relative to viewport.
543
+ * @return {?Rect} The component's bounding box. Null if drag
544
+ * target area should be ignored.
545
+ */
546
+ getClientRect() {
547
+ if (!this.HtmlDiv || !this.isVisible_) {
548
+ return null;
549
+ }
257
550
 
258
- this.attachEvents_(container, this.contentsDiv_);
259
- return container;
260
- };
551
+ // BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox
552
+ // area are still deleted. Must be smaller than Infinity, but larger than
553
+ // the largest screen size.
554
+ const BIG_NUM = 10000000;
555
+ const toolboxRect = this.HtmlDiv.getBoundingClientRect();
261
556
 
262
- /**
263
- * Creates the container div for the toolbox.
264
- * @return {!Element} The HTML container for the toolbox.
265
- * @protected
266
- */
267
- Toolbox.prototype.createContainer_ = function() {
268
- const toolboxContainer = document.createElement('div');
269
- toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v');
270
- dom.addClass(toolboxContainer, 'blocklyToolboxDiv');
271
- dom.addClass(toolboxContainer, 'blocklyNonSelectable');
272
- toolboxContainer.setAttribute('dir', this.RTL ? 'RTL' : 'LTR');
273
- return toolboxContainer;
274
- };
557
+ const top = toolboxRect.top;
558
+ const bottom = top + toolboxRect.height;
559
+ const left = toolboxRect.left;
560
+ const right = left + toolboxRect.width;
275
561
 
276
- /**
277
- * Creates the container for all the contents in the toolbox.
278
- * @return {!Element} The HTML container for the toolbox contents.
279
- * @protected
280
- */
281
- Toolbox.prototype.createContentsContainer_ = function() {
282
- const contentsContainer = document.createElement('div');
283
- dom.addClass(contentsContainer, 'blocklyToolboxContents');
284
- if (this.isHorizontal()) {
285
- contentsContainer.style.flexDirection = 'row';
562
+ // Assumes that the toolbox is on the SVG edge. If this changes
563
+ // (e.g. toolboxes in mutators) then this code will need to be more complex.
564
+ if (this.toolboxPosition === toolbox.Position.TOP) {
565
+ return new Rect(-BIG_NUM, bottom, -BIG_NUM, BIG_NUM);
566
+ } else if (this.toolboxPosition === toolbox.Position.BOTTOM) {
567
+ return new Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM);
568
+ } else if (this.toolboxPosition === toolbox.Position.LEFT) {
569
+ return new Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, right);
570
+ } else { // Right
571
+ return new Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM);
572
+ }
286
573
  }
287
- return contentsContainer;
288
- };
289
-
290
- /**
291
- * Adds event listeners to the toolbox container div.
292
- * @param {!Element} container The HTML container for the toolbox.
293
- * @param {!Element} contentsContainer The HTML container for the contents
294
- * of the toolbox.
295
- * @protected
296
- */
297
- Toolbox.prototype.attachEvents_ = function(container, contentsContainer) {
298
- // Clicking on toolbox closes popups.
299
- const clickEvent = browserEvents.conditionalBind(
300
- container, 'click', this, this.onClick_,
301
- /* opt_noCaptureIdentifier */ false,
302
- /* opt_noPreventDefault */ true);
303
- this.boundEvents_.push(clickEvent);
304
-
305
- const keyDownEvent = browserEvents.conditionalBind(
306
- contentsContainer, 'keydown', this, this.onKeyDown_,
307
- /* opt_noCaptureIdentifier */ false,
308
- /* opt_noPreventDefault */ true);
309
- this.boundEvents_.push(keyDownEvent);
310
- };
311
574
 
312
- /**
313
- * Handles on click events for when the toolbox or toolbox items are clicked.
314
- * @param {!Event} e Click event to handle.
315
- * @protected
316
- */
317
- Toolbox.prototype.onClick_ = function(e) {
318
- if (browserEvents.isRightButton(e) || e.target === this.HtmlDiv) {
319
- // Close flyout.
320
- common.getMainWorkspace().hideChaff(false);
321
- } else {
322
- const targetElement = e.target;
323
- const itemId = targetElement.getAttribute('id');
324
- if (itemId) {
325
- const item = this.getToolboxItemById(itemId);
326
- if (item.isSelectable()) {
327
- this.setSelectedItem(item);
328
- item.onClick(e);
329
- }
575
+ /**
576
+ * Returns whether the provided block or bubble would be deleted if dropped on
577
+ * this area.
578
+ * This method should check if the element is deletable and is always called
579
+ * before onDragEnter/onDragOver/onDragExit.
580
+ * @param {!IDraggable} element The block or bubble currently being
581
+ * dragged.
582
+ * @param {boolean} _couldConnect Whether the element could could connect to
583
+ * another.
584
+ * @return {boolean} Whether the element provided would be deleted if dropped
585
+ * on this area.
586
+ * @override
587
+ */
588
+ wouldDelete(element, _couldConnect) {
589
+ if (element instanceof BlockSvg) {
590
+ const block = /** @type {BlockSvg} */ (element);
591
+ // Prefer dragging to the toolbox over connecting to other blocks.
592
+ this.updateWouldDelete_(!block.getParent() && block.isDeletable());
593
+ } else {
594
+ this.updateWouldDelete_(element.isDeletable());
330
595
  }
331
- // Just close popups.
332
- common.getMainWorkspace().hideChaff(true);
596
+ return this.wouldDelete_;
333
597
  }
334
- Touch.clearTouchIdentifier(); // Don't block future drags.
335
- };
336
598
 
337
- /**
338
- * Handles key down events for the toolbox.
339
- * @param {!KeyboardEvent} e The key down event.
340
- * @protected
341
- */
342
- Toolbox.prototype.onKeyDown_ = function(e) {
343
- let handled = false;
344
- switch (e.keyCode) {
345
- case KeyCodes.DOWN:
346
- handled = this.selectNext_();
347
- break;
348
- case KeyCodes.UP:
349
- handled = this.selectPrevious_();
350
- break;
351
- case KeyCodes.LEFT:
352
- handled = this.selectParent_();
353
- break;
354
- case KeyCodes.RIGHT:
355
- handled = this.selectChild_();
356
- break;
357
- case KeyCodes.ENTER:
358
- case KeyCodes.SPACE:
359
- if (this.selectedItem_ && this.selectedItem_.isCollapsible()) {
360
- const collapsibleItem =
361
- /** @type {!ICollapsibleToolboxItem} */ (this.selectedItem_);
362
- collapsibleItem.toggleExpanded();
363
- handled = true;
364
- }
365
- break;
366
- default:
367
- handled = false;
368
- break;
369
- }
370
- if (!handled && this.selectedItem_ && this.selectedItem_.onKeyDown) {
371
- handled = this.selectedItem_.onKeyDown(e);
599
+ /**
600
+ * Handles when a cursor with a block or bubble enters this drag target.
601
+ * @param {!IDraggable} _dragElement The block or bubble currently being
602
+ * dragged.
603
+ * @override
604
+ */
605
+ onDragEnter(_dragElement) {
606
+ this.updateCursorDeleteStyle_(true);
372
607
  }
373
608
 
374
- if (handled) {
375
- e.preventDefault();
609
+ /**
610
+ * Handles when a cursor with a block or bubble exits this drag target.
611
+ * @param {!IDraggable} _dragElement The block or bubble currently being
612
+ * dragged.
613
+ * @override
614
+ */
615
+ onDragExit(_dragElement) {
616
+ this.updateCursorDeleteStyle_(false);
376
617
  }
377
- };
378
618
 
379
- /**
380
- * Creates the flyout based on the toolbox layout.
381
- * @return {!IFlyout} The flyout for the toolbox.
382
- * @throws {Error} If missing a require for `Blockly.HorizontalFlyout`,
383
- * `Blockly.VerticalFlyout`, and no flyout plugin is specified.
384
- * @protected
385
- */
386
- Toolbox.prototype.createFlyout_ = function() {
387
- const workspace = this.workspace_;
388
- // TODO (#4247): Look into adding a makeFlyout method to Blockly Options.
389
- const workspaceOptions = new Options(
390
- /** @type {!BlocklyOptions} */
391
- ({
392
- 'parentWorkspace': workspace,
393
- 'rtl': workspace.RTL,
394
- 'oneBasedIndex': workspace.options.oneBasedIndex,
395
- 'horizontalLayout': workspace.horizontalLayout,
396
- 'renderer': workspace.options.renderer,
397
- 'rendererOverrides': workspace.options.rendererOverrides,
398
- 'move': {
399
- 'scrollbars': true,
400
- },
401
- }));
402
- // Options takes in either 'end' or 'start'. This has already been parsed to
403
- // be either 0 or 1, so set it after.
404
- workspaceOptions.toolboxPosition = workspace.options.toolboxPosition;
405
- let FlyoutClass = null;
406
- if (workspace.horizontalLayout) {
407
- FlyoutClass = registry.getClassFromOptions(
408
- registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, workspace.options, true);
409
- } else {
410
- FlyoutClass = registry.getClassFromOptions(
411
- registry.Type.FLYOUTS_VERTICAL_TOOLBOX, workspace.options, true);
619
+ /**
620
+ * Handles when a block or bubble is dropped on this component.
621
+ * Should not handle delete here.
622
+ * @param {!IDraggable} _dragElement The block or bubble currently being
623
+ * dragged.
624
+ * @override
625
+ */
626
+ onDrop(_dragElement) {
627
+ this.updateCursorDeleteStyle_(false);
412
628
  }
413
- return new FlyoutClass(workspaceOptions);
414
- };
415
629
 
416
- /**
417
- * Fills the toolbox with new toolbox items and removes any old contents.
418
- * @param {!toolbox.ToolboxInfo} toolboxDef Object holding information
419
- * for creating a toolbox.
420
- * @package
421
- */
422
- Toolbox.prototype.render = function(toolboxDef) {
423
- this.toolboxDef_ = toolboxDef;
424
- for (let i = 0; i < this.contents_.length; i++) {
425
- const toolboxItem = this.contents_[i];
426
- if (toolboxItem) {
427
- toolboxItem.dispose();
630
+ /**
631
+ * Updates the internal wouldDelete_ state.
632
+ * @param {boolean} wouldDelete The new value for the wouldDelete state.
633
+ * @protected
634
+ * @override
635
+ */
636
+ updateWouldDelete_(wouldDelete) {
637
+ if (wouldDelete === this.wouldDelete_) {
638
+ return;
428
639
  }
429
- }
430
- this.contents_ = [];
431
- this.contentMap_ = Object.create(null);
432
- this.renderContents_(toolboxDef['contents']);
433
- this.position();
434
- this.handleToolboxItemResize();
435
- };
436
-
437
- /**
438
- * Adds all the toolbox items to the toolbox.
439
- * @param {!Array<!toolbox.ToolboxItemInfo>} toolboxDef Array
440
- * holding objects containing information on the contents of the toolbox.
441
- * @protected
442
- */
443
- Toolbox.prototype.renderContents_ = function(toolboxDef) {
444
- // This is for performance reasons. By using document fragment we only have to
445
- // add to the DOM once.
446
- const fragment = document.createDocumentFragment();
447
- for (let i = 0; i < toolboxDef.length; i++) {
448
- const toolboxItemDef = toolboxDef[i];
449
- this.createToolboxItem_(toolboxItemDef, fragment);
450
- }
451
- this.contentsDiv_.appendChild(fragment);
452
- };
453
-
454
- /**
455
- * Creates and renders the toolbox item.
456
- * @param {!toolbox.ToolboxItemInfo} toolboxItemDef Any information
457
- * that can be used to create an item in the toolbox.
458
- * @param {!DocumentFragment} fragment The document fragment to add the child
459
- * toolbox elements to.
460
- * @private
461
- */
462
- Toolbox.prototype.createToolboxItem_ = function(toolboxItemDef, fragment) {
463
- let registryName = toolboxItemDef['kind'];
464
-
465
- // Categories that are collapsible are created using a class registered under
466
- // a different name.
467
- if (registryName.toUpperCase() === 'CATEGORY' &&
468
- toolbox.isCategoryCollapsible(
469
- /** @type {!toolbox.CategoryInfo} */ (toolboxItemDef))) {
470
- registryName = CollapsibleToolboxCategory.registrationName;
640
+ // This logic handles updating the deleteStyle properly if the delete state
641
+ // changes while the block is over the Toolbox. This could happen if the
642
+ // implementation of wouldDeleteBlock depends on the couldConnect parameter
643
+ // or if the isDeletable property of the block currently being dragged
644
+ // changes during the drag.
645
+ this.updateCursorDeleteStyle_(false);
646
+ this.wouldDelete_ = wouldDelete;
647
+ this.updateCursorDeleteStyle_(true);
471
648
  }
472
649
 
473
- const ToolboxItemClass =
474
- registry.getClass(registry.Type.TOOLBOX_ITEM, registryName.toLowerCase());
475
- if (ToolboxItemClass) {
476
- const toolboxItem = new ToolboxItemClass(toolboxItemDef, this);
477
- this.addToolboxItem_(toolboxItem);
478
- toolboxItem.init();
479
- const toolboxItemDom = toolboxItem.getDiv();
480
- if (toolboxItemDom) {
481
- fragment.appendChild(toolboxItemDom);
482
- }
483
- // Adds the ID to the HTML element that can receive a click.
484
- // This is used in onClick_ to find the toolboxItem that was clicked.
485
- if (toolboxItem.getClickTarget) {
486
- toolboxItem.getClickTarget().setAttribute('id', toolboxItem.getId());
650
+ /**
651
+ * Adds or removes the CSS style of the cursor over the toolbox based whether
652
+ * the block or bubble over it is expected to be deleted if dropped (using the
653
+ * internal this.wouldDelete_ property).
654
+ * @param {boolean} addStyle Whether the style should be added or removed.
655
+ * @protected
656
+ */
657
+ updateCursorDeleteStyle_(addStyle) {
658
+ const style =
659
+ this.wouldDelete_ ? 'blocklyToolboxDelete' : 'blocklyToolboxGrab';
660
+ if (addStyle) {
661
+ this.addStyle(style);
662
+ } else {
663
+ this.removeStyle(style);
487
664
  }
488
665
  }
489
- };
490
666
 
491
- /**
492
- * Adds an item to the toolbox.
493
- * @param {!IToolboxItem} toolboxItem The item in the toolbox.
494
- * @protected
495
- */
496
- Toolbox.prototype.addToolboxItem_ = function(toolboxItem) {
497
- this.contents_.push(toolboxItem);
498
- this.contentMap_[toolboxItem.getId()] = toolboxItem;
499
- if (toolboxItem.isCollapsible()) {
500
- const collapsibleItem = /** @type {ICollapsibleToolboxItem} */
501
- (toolboxItem);
502
- const childToolboxItems = collapsibleItem.getChildToolboxItems();
503
- for (let i = 0; i < childToolboxItems.length; i++) {
504
- const child = childToolboxItems[i];
505
- this.addToolboxItem_(child);
506
- }
667
+ /**
668
+ * Gets the toolbox item with the given ID.
669
+ * @param {string} id The ID of the toolbox item.
670
+ * @return {?IToolboxItem} The toolbox item with the given ID, or null
671
+ * if no item exists.
672
+ * @public
673
+ */
674
+ getToolboxItemById(id) {
675
+ return this.contentMap_[id] || null;
507
676
  }
508
- };
509
-
510
- /**
511
- * Gets the items in the toolbox.
512
- * @return {!Array<!IToolboxItem>} The list of items in the toolbox.
513
- * @public
514
- */
515
- Toolbox.prototype.getToolboxItems = function() {
516
- return this.contents_;
517
- };
518
677
 
519
- /**
520
- * Adds a style on the toolbox. Usually used to change the cursor.
521
- * @param {string} style The name of the class to add.
522
- * @package
523
- */
524
- Toolbox.prototype.addStyle = function(style) {
525
- dom.addClass(/** @type {!Element} */ (this.HtmlDiv), style);
526
- };
527
-
528
- /**
529
- * Removes a style from the toolbox. Usually used to change the cursor.
530
- * @param {string} style The name of the class to remove.
531
- * @package
532
- */
533
- Toolbox.prototype.removeStyle = function(style) {
534
- dom.removeClass(/** @type {!Element} */ (this.HtmlDiv), style);
535
- };
536
-
537
- /**
538
- * Returns the bounding rectangle of the drag target area in pixel units
539
- * relative to viewport.
540
- * @return {?Rect} The component's bounding box. Null if drag
541
- * target area should be ignored.
542
- */
543
- Toolbox.prototype.getClientRect = function() {
544
- if (!this.HtmlDiv || !this.isVisible_) {
545
- return null;
678
+ /**
679
+ * Gets the width of the toolbox.
680
+ * @return {number} The width of the toolbox.
681
+ * @public
682
+ */
683
+ getWidth() {
684
+ return this.width_;
546
685
  }
547
686
 
548
- // BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox
549
- // area are still deleted. Must be smaller than Infinity, but larger than
550
- // the largest screen size.
551
- const BIG_NUM = 10000000;
552
- const toolboxRect = this.HtmlDiv.getBoundingClientRect();
553
-
554
- const top = toolboxRect.top;
555
- const bottom = top + toolboxRect.height;
556
- const left = toolboxRect.left;
557
- const right = left + toolboxRect.width;
558
-
559
- // Assumes that the toolbox is on the SVG edge. If this changes
560
- // (e.g. toolboxes in mutators) then this code will need to be more complex.
561
- if (this.toolboxPosition === toolbox.Position.TOP) {
562
- return new Rect(-BIG_NUM, bottom, -BIG_NUM, BIG_NUM);
563
- } else if (this.toolboxPosition === toolbox.Position.BOTTOM) {
564
- return new Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM);
565
- } else if (this.toolboxPosition === toolbox.Position.LEFT) {
566
- return new Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, right);
567
- } else { // Right
568
- return new Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM);
687
+ /**
688
+ * Gets the height of the toolbox.
689
+ * @return {number} The width of the toolbox.
690
+ * @public
691
+ */
692
+ getHeight() {
693
+ return this.height_;
569
694
  }
570
- };
571
695
 
572
- /**
573
- * Returns whether the provided block or bubble would be deleted if dropped on
574
- * this area.
575
- * This method should check if the element is deletable and is always called
576
- * before onDragEnter/onDragOver/onDragExit.
577
- * @param {!IDraggable} element The block or bubble currently being
578
- * dragged.
579
- * @param {boolean} _couldConnect Whether the element could could connect to
580
- * another.
581
- * @return {boolean} Whether the element provided would be deleted if dropped on
582
- * this area.
583
- * @override
584
- */
585
- Toolbox.prototype.wouldDelete = function(element, _couldConnect) {
586
- if (element instanceof BlockSvg) {
587
- const block = /** @type {BlockSvg} */ (element);
588
- // Prefer dragging to the toolbox over connecting to other blocks.
589
- this.updateWouldDelete_(!block.getParent() && block.isDeletable());
590
- } else {
591
- this.updateWouldDelete_(element.isDeletable());
696
+ /**
697
+ * Gets the toolbox flyout.
698
+ * @return {?IFlyout} The toolbox flyout.
699
+ * @public
700
+ */
701
+ getFlyout() {
702
+ return this.flyout_;
592
703
  }
593
- return this.wouldDelete_;
594
- };
595
-
596
- /**
597
- * Handles when a cursor with a block or bubble enters this drag target.
598
- * @param {!IDraggable} _dragElement The block or bubble currently being
599
- * dragged.
600
- * @override
601
- */
602
- Toolbox.prototype.onDragEnter = function(_dragElement) {
603
- this.updateCursorDeleteStyle_(true);
604
- };
605
-
606
- /**
607
- * Handles when a cursor with a block or bubble exits this drag target.
608
- * @param {!IDraggable} _dragElement The block or bubble currently being
609
- * dragged.
610
- * @override
611
- */
612
- Toolbox.prototype.onDragExit = function(_dragElement) {
613
- this.updateCursorDeleteStyle_(false);
614
- };
615
-
616
-
617
- /**
618
- * Handles when a block or bubble is dropped on this component.
619
- * Should not handle delete here.
620
- * @param {!IDraggable} _dragElement The block or bubble currently being
621
- * dragged.
622
- * @override
623
- */
624
- Toolbox.prototype.onDrop = function(_dragElement) {
625
- this.updateCursorDeleteStyle_(false);
626
- };
627
704
 
628
- /**
629
- * Updates the internal wouldDelete_ state.
630
- * @param {boolean} wouldDelete The new value for the wouldDelete state.
631
- * @protected
632
- * @override
633
- */
634
- Toolbox.prototype.updateWouldDelete_ = function(wouldDelete) {
635
- if (wouldDelete === this.wouldDelete_) {
636
- return;
705
+ /**
706
+ * Gets the workspace for the toolbox.
707
+ * @return {!WorkspaceSvg} The parent workspace for the toolbox.
708
+ * @public
709
+ */
710
+ getWorkspace() {
711
+ return this.workspace_;
637
712
  }
638
- // This logic handles updating the deleteStyle properly if the delete state
639
- // changes while the block is over the Toolbox. This could happen if the
640
- // implementation of wouldDeleteBlock depends on the couldConnect parameter
641
- // or if the isDeletable property of the block currently being dragged
642
- // changes during the drag.
643
- this.updateCursorDeleteStyle_(false);
644
- this.wouldDelete_ = wouldDelete;
645
- this.updateCursorDeleteStyle_(true);
646
- };
647
713
 
648
- /**
649
- * Adds or removes the CSS style of the cursor over the toolbox based whether
650
- * the block or bubble over it is expected to be deleted if dropped (using the
651
- * internal this.wouldDelete_ property).
652
- * @param {boolean} addStyle Whether the style should be added or removed.
653
- * @protected
654
- */
655
- Toolbox.prototype.updateCursorDeleteStyle_ = function(addStyle) {
656
- const style =
657
- this.wouldDelete_ ? 'blocklyToolboxDelete' : 'blocklyToolboxGrab';
658
- if (addStyle) {
659
- this.addStyle(style);
660
- } else {
661
- this.removeStyle(style);
714
+ /**
715
+ * Gets the selected item.
716
+ * @return {?ISelectableToolboxItem} The selected item, or null if no item is
717
+ * currently selected.
718
+ * @public
719
+ */
720
+ getSelectedItem() {
721
+ return this.selectedItem_;
662
722
  }
663
- };
664
-
665
- /**
666
- * Gets the toolbox item with the given ID.
667
- * @param {string} id The ID of the toolbox item.
668
- * @return {?IToolboxItem} The toolbox item with the given ID, or null
669
- * if no item exists.
670
- * @public
671
- */
672
- Toolbox.prototype.getToolboxItemById = function(id) {
673
- return this.contentMap_[id] || null;
674
- };
675
-
676
- /**
677
- * Gets the width of the toolbox.
678
- * @return {number} The width of the toolbox.
679
- * @public
680
- */
681
- Toolbox.prototype.getWidth = function() {
682
- return this.width_;
683
- };
684
-
685
- /**
686
- * Gets the height of the toolbox.
687
- * @return {number} The width of the toolbox.
688
- * @public
689
- */
690
- Toolbox.prototype.getHeight = function() {
691
- return this.height_;
692
- };
693
-
694
- /**
695
- * Gets the toolbox flyout.
696
- * @return {?IFlyout} The toolbox flyout.
697
- * @public
698
- */
699
- Toolbox.prototype.getFlyout = function() {
700
- return this.flyout_;
701
- };
702
723
 
703
- /**
704
- * Gets the workspace for the toolbox.
705
- * @return {!WorkspaceSvg} The parent workspace for the toolbox.
706
- * @public
707
- */
708
- Toolbox.prototype.getWorkspace = function() {
709
- return this.workspace_;
710
- };
711
-
712
- /**
713
- * Gets the selected item.
714
- * @return {?ISelectableToolboxItem} The selected item, or null if no item is
715
- * currently selected.
716
- * @public
717
- */
718
- Toolbox.prototype.getSelectedItem = function() {
719
- return this.selectedItem_;
720
- };
721
-
722
- /**
723
- * Gets the previously selected item.
724
- * @return {?ISelectableToolboxItem} The previously selected item, or null if no
725
- * item was previously selected.
726
- * @public
727
- */
728
- Toolbox.prototype.getPreviouslySelectedItem = function() {
729
- return this.previouslySelectedItem_;
730
- };
731
-
732
- /**
733
- * Gets whether or not the toolbox is horizontal.
734
- * @return {boolean} True if the toolbox is horizontal, false if the toolbox is
735
- * vertical.
736
- * @public
737
- */
738
- Toolbox.prototype.isHorizontal = function() {
739
- return this.horizontalLayout_;
740
- };
724
+ /**
725
+ * Gets the previously selected item.
726
+ * @return {?ISelectableToolboxItem} The previously selected item, or null if
727
+ * no item was previously selected.
728
+ * @public
729
+ */
730
+ getPreviouslySelectedItem() {
731
+ return this.previouslySelectedItem_;
732
+ }
741
733
 
742
- /**
743
- * Positions the toolbox based on whether it is a horizontal toolbox and whether
744
- * the workspace is in rtl.
745
- * @public
746
- */
747
- Toolbox.prototype.position = function() {
748
- const workspaceMetrics = this.workspace_.getMetrics();
749
- const toolboxDiv = this.HtmlDiv;
750
- if (!toolboxDiv) {
751
- // Not initialized yet.
752
- return;
734
+ /**
735
+ * Gets whether or not the toolbox is horizontal.
736
+ * @return {boolean} True if the toolbox is horizontal, false if the toolbox
737
+ * is vertical.
738
+ * @public
739
+ */
740
+ isHorizontal() {
741
+ return this.horizontalLayout_;
753
742
  }
754
743
 
755
- if (this.horizontalLayout_) {
756
- toolboxDiv.style.left = '0';
757
- toolboxDiv.style.height = 'auto';
758
- toolboxDiv.style.width = '100%';
759
- this.height_ = toolboxDiv.offsetHeight;
760
- this.width_ = workspaceMetrics.viewWidth;
761
- if (this.toolboxPosition === toolbox.Position.TOP) {
762
- toolboxDiv.style.top = '0';
763
- } else { // Bottom
764
- toolboxDiv.style.bottom = '0';
744
+ /**
745
+ * Positions the toolbox based on whether it is a horizontal toolbox and
746
+ * whether the workspace is in rtl.
747
+ * @public
748
+ */
749
+ position() {
750
+ const workspaceMetrics = this.workspace_.getMetrics();
751
+ const toolboxDiv = this.HtmlDiv;
752
+ if (!toolboxDiv) {
753
+ // Not initialized yet.
754
+ return;
765
755
  }
766
- } else {
767
- if (this.toolboxPosition === toolbox.Position.RIGHT) {
768
- toolboxDiv.style.right = '0';
769
- } else { // Left
756
+
757
+ if (this.horizontalLayout_) {
770
758
  toolboxDiv.style.left = '0';
759
+ toolboxDiv.style.height = 'auto';
760
+ toolboxDiv.style.width = '100%';
761
+ this.height_ = toolboxDiv.offsetHeight;
762
+ this.width_ = workspaceMetrics.viewWidth;
763
+ if (this.toolboxPosition === toolbox.Position.TOP) {
764
+ toolboxDiv.style.top = '0';
765
+ } else { // Bottom
766
+ toolboxDiv.style.bottom = '0';
767
+ }
768
+ } else {
769
+ if (this.toolboxPosition === toolbox.Position.RIGHT) {
770
+ toolboxDiv.style.right = '0';
771
+ } else { // Left
772
+ toolboxDiv.style.left = '0';
773
+ }
774
+ toolboxDiv.style.height = '100%';
775
+ this.width_ = toolboxDiv.offsetWidth;
776
+ this.height_ = workspaceMetrics.viewHeight;
771
777
  }
772
- toolboxDiv.style.height = '100%';
773
- this.width_ = toolboxDiv.offsetWidth;
774
- this.height_ = workspaceMetrics.viewHeight;
778
+ this.flyout_.position();
775
779
  }
776
- this.flyout_.position();
777
- };
778
- /**
779
- * Handles resizing the toolbox when a toolbox item resizes.
780
- * @package
781
- */
782
- Toolbox.prototype.handleToolboxItemResize = function() {
783
- // Reposition the workspace so that (0,0) is in the correct position relative
784
- // to the new absolute edge (ie toolbox edge).
785
- const workspace = this.workspace_;
786
- const rect = this.HtmlDiv.getBoundingClientRect();
787
- const newX = this.toolboxPosition === toolbox.Position.LEFT ?
788
- workspace.scrollX + rect.width :
789
- workspace.scrollX;
790
- const newY = this.toolboxPosition === toolbox.Position.TOP ?
791
- workspace.scrollY + rect.height :
792
- workspace.scrollY;
793
- workspace.translate(newX, newY);
794
-
795
- // Even though the div hasn't changed size, the visible workspace
796
- // surface of the workspace has, so we may need to reposition everything.
797
- common.svgResize(workspace);
798
- };
799
780
 
800
- /**
801
- * Unhighlights any previously selected item.
802
- * @public
803
- */
804
- Toolbox.prototype.clearSelection = function() {
805
- this.setSelectedItem(null);
806
- };
807
-
808
- /**
809
- * Updates the category colours and background colour of selected categories.
810
- * @package
811
- */
812
- Toolbox.prototype.refreshTheme = function() {
813
- for (let i = 0; i < this.contents_.length; i++) {
814
- const child = this.contents_[i];
815
- if (child.refreshTheme) {
816
- child.refreshTheme();
817
- }
781
+ /**
782
+ * Handles resizing the toolbox when a toolbox item resizes.
783
+ * @package
784
+ */
785
+ handleToolboxItemResize() {
786
+ // Reposition the workspace so that (0,0) is in the correct position
787
+ // relative to the new absolute edge (ie toolbox edge).
788
+ const workspace = this.workspace_;
789
+ const rect = this.HtmlDiv.getBoundingClientRect();
790
+ const newX = this.toolboxPosition === toolbox.Position.LEFT ?
791
+ workspace.scrollX + rect.width :
792
+ workspace.scrollX;
793
+ const newY = this.toolboxPosition === toolbox.Position.TOP ?
794
+ workspace.scrollY + rect.height :
795
+ workspace.scrollY;
796
+ workspace.translate(newX, newY);
797
+
798
+ // Even though the div hasn't changed size, the visible workspace
799
+ // surface of the workspace has, so we may need to reposition everything.
800
+ common.svgResize(workspace);
818
801
  }
819
- };
820
802
 
821
- /**
822
- * Updates the flyout's content without closing it. Should be used in response
823
- * to a change in one of the dynamic categories, such as variables or
824
- * procedures.
825
- * @public
826
- */
827
- Toolbox.prototype.refreshSelection = function() {
828
- if (this.selectedItem_ && this.selectedItem_.isSelectable() &&
829
- this.selectedItem_.getContents().length) {
830
- this.flyout_.show(this.selectedItem_.getContents());
803
+ /**
804
+ * Unhighlights any previously selected item.
805
+ * @public
806
+ */
807
+ clearSelection() {
808
+ this.setSelectedItem(null);
831
809
  }
832
- };
833
810
 
834
- /**
835
- * Shows or hides the toolbox.
836
- * @param {boolean} isVisible True if toolbox should be visible.
837
- * @public
838
- */
839
- Toolbox.prototype.setVisible = function(isVisible) {
840
- if (this.isVisible_ === isVisible) {
841
- return;
811
+ /**
812
+ * Updates the category colours and background colour of selected categories.
813
+ * @package
814
+ */
815
+ refreshTheme() {
816
+ for (let i = 0; i < this.contents_.length; i++) {
817
+ const child = this.contents_[i];
818
+ if (child.refreshTheme) {
819
+ child.refreshTheme();
820
+ }
821
+ }
842
822
  }
843
823
 
844
- this.HtmlDiv.style.display = isVisible ? 'block' : 'none';
845
- this.isVisible_ = isVisible;
846
- // Invisible toolbox is ignored as drag targets and must have the drag target
847
- // updated.
848
- this.workspace_.recordDragTargets();
849
- };
850
-
851
- /**
852
- * Hides the component. Called in WorkspaceSvg.hideChaff.
853
- * @param {boolean} onlyClosePopups Whether only popups should be closed.
854
- * Flyouts should not be closed if this is true.
855
- */
856
- Toolbox.prototype.autoHide = function(onlyClosePopups) {
857
- if (!onlyClosePopups && this.flyout_ && this.flyout_.autoClose) {
858
- this.clearSelection();
824
+ /**
825
+ * Updates the flyout's content without closing it. Should be used in
826
+ * response to a change in one of the dynamic categories, such as variables or
827
+ * procedures.
828
+ * @public
829
+ */
830
+ refreshSelection() {
831
+ if (this.selectedItem_ && this.selectedItem_.isSelectable() &&
832
+ this.selectedItem_.getContents().length) {
833
+ this.flyout_.show(this.selectedItem_.getContents());
834
+ }
859
835
  }
860
- };
861
-
862
- /**
863
- * Sets the given item as selected.
864
- * No-op if the item is not selectable.
865
- * @param {?IToolboxItem} newItem The toolbox item to select.
866
- * @public
867
- */
868
- Toolbox.prototype.setSelectedItem = function(newItem) {
869
- const oldItem = this.selectedItem_;
870
836
 
871
- if ((!newItem && !oldItem) || (newItem && !newItem.isSelectable())) {
872
- return;
873
- }
874
- newItem = /** @type {ISelectableToolboxItem} */ (newItem);
837
+ /**
838
+ * Shows or hides the toolbox.
839
+ * @param {boolean} isVisible True if toolbox should be visible.
840
+ * @public
841
+ */
842
+ setVisible(isVisible) {
843
+ if (this.isVisible_ === isVisible) {
844
+ return;
845
+ }
875
846
 
876
- if (this.shouldDeselectItem_(oldItem, newItem) && oldItem !== null) {
877
- this.deselectItem_(oldItem);
847
+ this.HtmlDiv.style.display = isVisible ? 'block' : 'none';
848
+ this.isVisible_ = isVisible;
849
+ // Invisible toolbox is ignored as drag targets and must have the drag
850
+ // target updated.
851
+ this.workspace_.recordDragTargets();
878
852
  }
879
853
 
880
- if (this.shouldSelectItem_(oldItem, newItem) && newItem !== null) {
881
- this.selectItem_(oldItem, newItem);
854
+ /**
855
+ * Hides the component. Called in WorkspaceSvg.hideChaff.
856
+ * @param {boolean} onlyClosePopups Whether only popups should be closed.
857
+ * Flyouts should not be closed if this is true.
858
+ */
859
+ autoHide(onlyClosePopups) {
860
+ if (!onlyClosePopups && this.flyout_ && this.flyout_.autoClose) {
861
+ this.clearSelection();
862
+ }
882
863
  }
883
864
 
884
- this.updateFlyout_(oldItem, newItem);
885
- this.fireSelectEvent_(oldItem, newItem);
886
- };
887
-
888
- /**
889
- * Decides whether the old item should be deselected.
890
- * @param {?ISelectableToolboxItem} oldItem The previously selected
891
- * toolbox item.
892
- * @param {?ISelectableToolboxItem} newItem The newly selected toolbox
893
- * item.
894
- * @return {boolean} True if the old item should be deselected, false otherwise.
895
- * @protected
896
- */
897
- Toolbox.prototype.shouldDeselectItem_ = function(oldItem, newItem) {
898
- // Deselect the old item unless the old item is collapsible and has been
899
- // previously clicked on.
900
- return oldItem !== null && (!oldItem.isCollapsible() || oldItem !== newItem);
901
- };
902
-
903
- /**
904
- * Decides whether the new item should be selected.
905
- * @param {?ISelectableToolboxItem} oldItem The previously selected
906
- * toolbox item.
907
- * @param {?ISelectableToolboxItem} newItem The newly selected toolbox
908
- * item.
909
- * @return {boolean} True if the new item should be selected, false otherwise.
910
- * @protected
911
- */
912
- Toolbox.prototype.shouldSelectItem_ = function(oldItem, newItem) {
913
- // Select the new item unless the old item equals the new item.
914
- return newItem !== null && newItem !== oldItem;
915
- };
865
+ /**
866
+ * Sets the given item as selected.
867
+ * No-op if the item is not selectable.
868
+ * @param {?IToolboxItem} newItem The toolbox item to select.
869
+ * @public
870
+ */
871
+ setSelectedItem(newItem) {
872
+ const oldItem = this.selectedItem_;
916
873
 
917
- /**
918
- * Deselects the given item, marks it as unselected, and updates aria state.
919
- * @param {!ISelectableToolboxItem} item The previously selected
920
- * toolbox item which should be deselected.
921
- * @protected
922
- */
923
- Toolbox.prototype.deselectItem_ = function(item) {
924
- this.selectedItem_ = null;
925
- this.previouslySelectedItem_ = item;
926
- item.setSelected(false);
927
- aria.setState(
928
- /** @type {!Element} */ (this.contentsDiv_), aria.State.ACTIVEDESCENDANT,
929
- '');
930
- };
874
+ if ((!newItem && !oldItem) || (newItem && !newItem.isSelectable())) {
875
+ return;
876
+ }
877
+ newItem = /** @type {ISelectableToolboxItem} */ (newItem);
931
878
 
932
- /**
933
- * Selects the given item, marks it selected, and updates aria state.
934
- * @param {?ISelectableToolboxItem} oldItem The previously selected
935
- * toolbox item.
936
- * @param {!ISelectableToolboxItem} newItem The newly selected toolbox
937
- * item.
938
- * @protected
939
- */
940
- Toolbox.prototype.selectItem_ = function(oldItem, newItem) {
941
- this.selectedItem_ = newItem;
942
- this.previouslySelectedItem_ = oldItem;
943
- newItem.setSelected(true);
944
- aria.setState(
945
- /** @type {!Element} */ (this.contentsDiv_), aria.State.ACTIVEDESCENDANT,
946
- newItem.getId());
947
- };
879
+ if (this.shouldDeselectItem_(oldItem, newItem) && oldItem !== null) {
880
+ this.deselectItem_(oldItem);
881
+ }
948
882
 
949
- /**
950
- * Selects the toolbox item by its position in the list of toolbox items.
951
- * @param {number} position The position of the item to select.
952
- * @public
953
- */
954
- Toolbox.prototype.selectItemByPosition = function(position) {
955
- if (position > -1 && position < this.contents_.length) {
956
- const item = this.contents_[position];
957
- if (item.isSelectable()) {
958
- this.setSelectedItem(item);
883
+ if (this.shouldSelectItem_(oldItem, newItem) && newItem !== null) {
884
+ this.selectItem_(oldItem, newItem);
959
885
  }
886
+
887
+ this.updateFlyout_(oldItem, newItem);
888
+ this.fireSelectEvent_(oldItem, newItem);
960
889
  }
961
- };
962
890
 
963
- /**
964
- * Decides whether to hide or show the flyout depending on the selected item.
965
- * @param {?ISelectableToolboxItem} oldItem The previously selected toolbox
966
- * item.
967
- * @param {?ISelectableToolboxItem} newItem The newly selected toolbox item.
968
- * @protected
969
- */
970
- Toolbox.prototype.updateFlyout_ = function(oldItem, newItem) {
971
- if (!newItem || (oldItem === newItem && !newItem.isCollapsible()) ||
972
- !newItem.getContents().length) {
973
- this.flyout_.hide();
974
- } else {
975
- this.flyout_.show(newItem.getContents());
976
- this.flyout_.scrollToStart();
891
+ /**
892
+ * Decides whether the old item should be deselected.
893
+ * @param {?ISelectableToolboxItem} oldItem The previously selected
894
+ * toolbox item.
895
+ * @param {?ISelectableToolboxItem} newItem The newly selected toolbox
896
+ * item.
897
+ * @return {boolean} True if the old item should be deselected, false
898
+ * otherwise.
899
+ * @protected
900
+ */
901
+ shouldDeselectItem_(oldItem, newItem) {
902
+ // Deselect the old item unless the old item is collapsible and has been
903
+ // previously clicked on.
904
+ return oldItem !== null &&
905
+ (!oldItem.isCollapsible() || oldItem !== newItem);
977
906
  }
978
- };
979
907
 
980
- /**
981
- * Emits an event when a new toolbox item is selected.
982
- * @param {?ISelectableToolboxItem} oldItem The previously selected
983
- * toolbox item.
984
- * @param {?ISelectableToolboxItem} newItem The newly selected toolbox
985
- * item.
986
- * @private
987
- */
988
- Toolbox.prototype.fireSelectEvent_ = function(oldItem, newItem) {
989
- const oldElement = oldItem && oldItem.getName();
990
- let newElement = newItem && newItem.getName();
991
- // In this case the toolbox closes, so the newElement should be null.
992
- if (oldItem === newItem) {
993
- newElement = null;
908
+ /**
909
+ * Decides whether the new item should be selected.
910
+ * @param {?ISelectableToolboxItem} oldItem The previously selected
911
+ * toolbox item.
912
+ * @param {?ISelectableToolboxItem} newItem The newly selected toolbox
913
+ * item.
914
+ * @return {boolean} True if the new item should be selected, false otherwise.
915
+ * @protected
916
+ */
917
+ shouldSelectItem_(oldItem, newItem) {
918
+ // Select the new item unless the old item equals the new item.
919
+ return newItem !== null && newItem !== oldItem;
994
920
  }
995
- const event = new (eventUtils.get(eventUtils.TOOLBOX_ITEM_SELECT))(
996
- oldElement, newElement, this.workspace_.id);
997
- eventUtils.fire(event);
998
- };
999
921
 
1000
- /**
1001
- * Closes the current item if it is expanded, or selects the parent.
1002
- * @return {boolean} True if a parent category was selected, false otherwise.
1003
- * @private
1004
- */
1005
- Toolbox.prototype.selectParent_ = function() {
1006
- if (!this.selectedItem_) {
1007
- return false;
922
+ /**
923
+ * Deselects the given item, marks it as unselected, and updates aria state.
924
+ * @param {!ISelectableToolboxItem} item The previously selected
925
+ * toolbox item which should be deselected.
926
+ * @protected
927
+ */
928
+ deselectItem_(item) {
929
+ this.selectedItem_ = null;
930
+ this.previouslySelectedItem_ = item;
931
+ item.setSelected(false);
932
+ aria.setState(
933
+ /** @type {!Element} */ (this.contentsDiv_),
934
+ aria.State.ACTIVEDESCENDANT, '');
1008
935
  }
1009
936
 
1010
- if (this.selectedItem_.isCollapsible() && this.selectedItem_.isExpanded()) {
1011
- const collapsibleItem =
1012
- /** @type {!ICollapsibleToolboxItem} */ (this.selectedItem_);
1013
- collapsibleItem.setExpanded(false);
1014
- return true;
1015
- } else if (
1016
- this.selectedItem_.getParent() &&
1017
- this.selectedItem_.getParent().isSelectable()) {
1018
- this.setSelectedItem(this.selectedItem_.getParent());
1019
- return true;
937
+ /**
938
+ * Selects the given item, marks it selected, and updates aria state.
939
+ * @param {?ISelectableToolboxItem} oldItem The previously selected
940
+ * toolbox item.
941
+ * @param {!ISelectableToolboxItem} newItem The newly selected toolbox
942
+ * item.
943
+ * @protected
944
+ */
945
+ selectItem_(oldItem, newItem) {
946
+ this.selectedItem_ = newItem;
947
+ this.previouslySelectedItem_ = oldItem;
948
+ newItem.setSelected(true);
949
+ aria.setState(
950
+ /** @type {!Element} */ (this.contentsDiv_),
951
+ aria.State.ACTIVEDESCENDANT, newItem.getId());
1020
952
  }
1021
- return false;
1022
- };
1023
953
 
1024
- /**
1025
- * Selects the first child of the currently selected item, or nothing if the
1026
- * toolbox item has no children.
1027
- * @return {boolean} True if a child category was selected, false otherwise.
1028
- * @private
1029
- */
1030
- Toolbox.prototype.selectChild_ = function() {
1031
- if (!this.selectedItem_ || !this.selectedItem_.isCollapsible()) {
1032
- return false;
954
+ /**
955
+ * Selects the toolbox item by its position in the list of toolbox items.
956
+ * @param {number} position The position of the item to select.
957
+ * @public
958
+ */
959
+ selectItemByPosition(position) {
960
+ if (position > -1 && position < this.contents_.length) {
961
+ const item = this.contents_[position];
962
+ if (item.isSelectable()) {
963
+ this.setSelectedItem(item);
964
+ }
965
+ }
1033
966
  }
1034
- const collapsibleItem = /** @type {ICollapsibleToolboxItem} */
1035
- (this.selectedItem_);
1036
- if (!collapsibleItem.isExpanded()) {
1037
- collapsibleItem.setExpanded(true);
1038
- return true;
1039
- } else {
1040
- this.selectNext_();
1041
- return true;
967
+
968
+ /**
969
+ * Decides whether to hide or show the flyout depending on the selected item.
970
+ * @param {?ISelectableToolboxItem} oldItem The previously selected toolbox
971
+ * item.
972
+ * @param {?ISelectableToolboxItem} newItem The newly selected toolbox item.
973
+ * @protected
974
+ */
975
+ updateFlyout_(oldItem, newItem) {
976
+ if (!newItem || (oldItem === newItem && !newItem.isCollapsible()) ||
977
+ !newItem.getContents().length) {
978
+ this.flyout_.hide();
979
+ } else {
980
+ this.flyout_.show(newItem.getContents());
981
+ this.flyout_.scrollToStart();
982
+ }
1042
983
  }
1043
- };
1044
984
 
1045
- /**
1046
- * Selects the next visible toolbox item.
1047
- * @return {boolean} True if a next category was selected, false otherwise.
1048
- * @private
1049
- */
1050
- Toolbox.prototype.selectNext_ = function() {
1051
- if (!this.selectedItem_) {
1052
- return false;
985
+ /**
986
+ * Emits an event when a new toolbox item is selected.
987
+ * @param {?ISelectableToolboxItem} oldItem The previously selected
988
+ * toolbox item.
989
+ * @param {?ISelectableToolboxItem} newItem The newly selected toolbox
990
+ * item.
991
+ * @private
992
+ */
993
+ fireSelectEvent_(oldItem, newItem) {
994
+ const oldElement = oldItem && oldItem.getName();
995
+ let newElement = newItem && newItem.getName();
996
+ // In this case the toolbox closes, so the newElement should be null.
997
+ if (oldItem === newItem) {
998
+ newElement = null;
999
+ }
1000
+ const event = new (eventUtils.get(eventUtils.TOOLBOX_ITEM_SELECT))(
1001
+ oldElement, newElement, this.workspace_.id);
1002
+ eventUtils.fire(event);
1053
1003
  }
1054
1004
 
1055
- let nextItemIdx = this.contents_.indexOf(this.selectedItem_) + 1;
1056
- if (nextItemIdx > -1 && nextItemIdx < this.contents_.length) {
1057
- let nextItem = this.contents_[nextItemIdx];
1058
- while (nextItem && !nextItem.isSelectable()) {
1059
- nextItem = this.contents_[++nextItemIdx];
1005
+ /**
1006
+ * Closes the current item if it is expanded, or selects the parent.
1007
+ * @return {boolean} True if a parent category was selected, false otherwise.
1008
+ * @private
1009
+ */
1010
+ selectParent_() {
1011
+ if (!this.selectedItem_) {
1012
+ return false;
1060
1013
  }
1061
- if (nextItem && nextItem.isSelectable()) {
1062
- this.setSelectedItem(nextItem);
1014
+
1015
+ if (this.selectedItem_.isCollapsible() && this.selectedItem_.isExpanded()) {
1016
+ const collapsibleItem =
1017
+ /** @type {!ICollapsibleToolboxItem} */ (this.selectedItem_);
1018
+ collapsibleItem.setExpanded(false);
1019
+ return true;
1020
+ } else if (
1021
+ this.selectedItem_.getParent() &&
1022
+ this.selectedItem_.getParent().isSelectable()) {
1023
+ this.setSelectedItem(this.selectedItem_.getParent());
1063
1024
  return true;
1064
1025
  }
1065
- }
1066
- return false;
1067
- };
1068
-
1069
- /**
1070
- * Selects the previous visible toolbox item.
1071
- * @return {boolean} True if a previous category was selected, false otherwise.
1072
- * @private
1073
- */
1074
- Toolbox.prototype.selectPrevious_ = function() {
1075
- if (!this.selectedItem_) {
1076
1026
  return false;
1077
1027
  }
1078
1028
 
1079
- let prevItemIdx = this.contents_.indexOf(this.selectedItem_) - 1;
1080
- if (prevItemIdx > -1 && prevItemIdx < this.contents_.length) {
1081
- let prevItem = this.contents_[prevItemIdx];
1082
- while (prevItem && !prevItem.isSelectable()) {
1083
- prevItem = this.contents_[--prevItemIdx];
1029
+ /**
1030
+ * Selects the first child of the currently selected item, or nothing if the
1031
+ * toolbox item has no children.
1032
+ * @return {boolean} True if a child category was selected, false otherwise.
1033
+ * @private
1034
+ */
1035
+ selectChild_() {
1036
+ if (!this.selectedItem_ || !this.selectedItem_.isCollapsible()) {
1037
+ return false;
1084
1038
  }
1085
- if (prevItem && prevItem.isSelectable()) {
1086
- this.setSelectedItem(prevItem);
1039
+ const collapsibleItem = /** @type {ICollapsibleToolboxItem} */
1040
+ (this.selectedItem_);
1041
+ if (!collapsibleItem.isExpanded()) {
1042
+ collapsibleItem.setExpanded(true);
1043
+ return true;
1044
+ } else {
1045
+ this.selectNext_();
1087
1046
  return true;
1088
1047
  }
1089
1048
  }
1090
- return false;
1091
- };
1092
1049
 
1093
- /**
1094
- * Disposes of this toolbox.
1095
- * @public
1096
- */
1097
- Toolbox.prototype.dispose = function() {
1098
- this.workspace_.getComponentManager().removeComponent('toolbox');
1099
- this.flyout_.dispose();
1100
- for (let i = 0; i < this.contents_.length; i++) {
1101
- const toolboxItem = this.contents_[i];
1102
- toolboxItem.dispose();
1103
- }
1050
+ /**
1051
+ * Selects the next visible toolbox item.
1052
+ * @return {boolean} True if a next category was selected, false otherwise.
1053
+ * @private
1054
+ */
1055
+ selectNext_() {
1056
+ if (!this.selectedItem_) {
1057
+ return false;
1058
+ }
1104
1059
 
1105
- for (let j = 0; j < this.boundEvents_.length; j++) {
1106
- browserEvents.unbind(this.boundEvents_[j]);
1060
+ let nextItemIdx = this.contents_.indexOf(this.selectedItem_) + 1;
1061
+ if (nextItemIdx > -1 && nextItemIdx < this.contents_.length) {
1062
+ let nextItem = this.contents_[nextItemIdx];
1063
+ while (nextItem && !nextItem.isSelectable()) {
1064
+ nextItem = this.contents_[++nextItemIdx];
1065
+ }
1066
+ if (nextItem && nextItem.isSelectable()) {
1067
+ this.setSelectedItem(nextItem);
1068
+ return true;
1069
+ }
1070
+ }
1071
+ return false;
1107
1072
  }
1108
- this.boundEvents_ = [];
1109
- this.contents_ = [];
1110
1073
 
1111
- this.workspace_.getThemeManager().unsubscribe(this.HtmlDiv);
1112
- dom.removeNode(this.HtmlDiv);
1113
- };
1074
+ /**
1075
+ * Selects the previous visible toolbox item.
1076
+ * @return {boolean} True if a previous category was selected, false
1077
+ * otherwise.
1078
+ * @private
1079
+ */
1080
+ selectPrevious_() {
1081
+ if (!this.selectedItem_) {
1082
+ return false;
1083
+ }
1114
1084
 
1115
- /**
1116
- * CSS for Toolbox. See css.js for use.
1117
- */
1118
- Css.register(`
1119
- .blocklyToolboxDelete {
1120
- cursor: url("<<<PATH>>>/handdelete.cur"), auto;
1085
+ let prevItemIdx = this.contents_.indexOf(this.selectedItem_) - 1;
1086
+ if (prevItemIdx > -1 && prevItemIdx < this.contents_.length) {
1087
+ let prevItem = this.contents_[prevItemIdx];
1088
+ while (prevItem && !prevItem.isSelectable()) {
1089
+ prevItem = this.contents_[--prevItemIdx];
1090
+ }
1091
+ if (prevItem && prevItem.isSelectable()) {
1092
+ this.setSelectedItem(prevItem);
1093
+ return true;
1094
+ }
1095
+ }
1096
+ return false;
1121
1097
  }
1122
1098
 
1123
- .blocklyToolboxGrab {
1124
- cursor: url("<<<PATH>>>/handclosed.cur"), auto;
1125
- cursor: grabbing;
1126
- cursor: -webkit-grabbing;
1127
- }
1099
+ /**
1100
+ * Disposes of this toolbox.
1101
+ * @public
1102
+ */
1103
+ dispose() {
1104
+ this.workspace_.getComponentManager().removeComponent('toolbox');
1105
+ this.flyout_.dispose();
1106
+ for (let i = 0; i < this.contents_.length; i++) {
1107
+ const toolboxItem = this.contents_[i];
1108
+ toolboxItem.dispose();
1109
+ }
1128
1110
 
1129
- /* Category tree in Toolbox. */
1130
- .blocklyToolboxDiv {
1131
- background-color: #ddd;
1132
- overflow-x: visible;
1133
- overflow-y: auto;
1134
- padding: 4px 0 4px 0;
1135
- position: absolute;
1136
- z-index: 70; /* so blocks go under toolbox when dragging */
1137
- -webkit-tap-highlight-color: transparent; /* issue #1345 */
1138
- }
1111
+ for (let j = 0; j < this.boundEvents_.length; j++) {
1112
+ browserEvents.unbind(this.boundEvents_[j]);
1113
+ }
1114
+ this.boundEvents_ = [];
1115
+ this.contents_ = [];
1139
1116
 
1140
- .blocklyToolboxContents {
1141
- display: flex;
1142
- flex-wrap: wrap;
1143
- flex-direction: column;
1117
+ this.workspace_.getThemeManager().unsubscribe(this.HtmlDiv);
1118
+ dom.removeNode(this.HtmlDiv);
1144
1119
  }
1120
+ }
1145
1121
 
1146
- .blocklyToolboxContents:focus {
1147
- outline: none;
1148
- }
1122
+ /**
1123
+ * CSS for Toolbox. See css.js for use.
1124
+ */
1125
+ Css.register(`
1126
+ .blocklyToolboxDelete {
1127
+ cursor: url("<<<PATH>>>/handdelete.cur"), auto;
1128
+ }
1129
+
1130
+ .blocklyToolboxGrab {
1131
+ cursor: url("<<<PATH>>>/handclosed.cur"), auto;
1132
+ cursor: grabbing;
1133
+ cursor: -webkit-grabbing;
1134
+ }
1135
+
1136
+ /* Category tree in Toolbox. */
1137
+ .blocklyToolboxDiv {
1138
+ background-color: #ddd;
1139
+ overflow-x: visible;
1140
+ overflow-y: auto;
1141
+ padding: 4px 0 4px 0;
1142
+ position: absolute;
1143
+ z-index: 70; /* so blocks go under toolbox when dragging */
1144
+ -webkit-tap-highlight-color: transparent; /* issue #1345 */
1145
+ }
1146
+
1147
+ .blocklyToolboxContents {
1148
+ display: flex;
1149
+ flex-wrap: wrap;
1150
+ flex-direction: column;
1151
+ }
1152
+
1153
+ .blocklyToolboxContents:focus {
1154
+ outline: none;
1155
+ }
1149
1156
  `);
1150
1157
 
1151
1158
  registry.register(registry.Type.TOOLBOX, registry.DEFAULT, Toolbox);