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
package/core/scrollbar.js CHANGED
@@ -36,810 +36,856 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
36
36
  * Class for a pure SVG scrollbar.
37
37
  * This technique offers a scrollbar that is guaranteed to work, but may not
38
38
  * look or behave like the system's scrollbars.
39
- * @param {!WorkspaceSvg} workspace Workspace to bind the scrollbar to.
40
- * @param {boolean} horizontal True if horizontal, false if vertical.
41
- * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair.
42
- * @param {string=} opt_class A class to be applied to this scrollbar.
43
- * @param {number=} opt_margin The margin to apply to this scrollbar.
44
- * @constructor
45
39
  * @alias Blockly.Scrollbar
46
40
  */
47
- const Scrollbar = function(
48
- workspace, horizontal, opt_pair, opt_class, opt_margin) {
41
+ class Scrollbar {
49
42
  /**
50
- * The workspace this scrollbar is bound to.
51
- * @type {!WorkspaceSvg}
52
- * @private
43
+ * @param {!WorkspaceSvg} workspace Workspace to bind the scrollbar to.
44
+ * @param {boolean} horizontal True if horizontal, false if vertical.
45
+ * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair.
46
+ * @param {string=} opt_class A class to be applied to this scrollbar.
47
+ * @param {number=} opt_margin The margin to apply to this scrollbar.
53
48
  */
54
- this.workspace_ = workspace;
49
+ constructor(workspace, horizontal, opt_pair, opt_class, opt_margin) {
50
+ /**
51
+ * The workspace this scrollbar is bound to.
52
+ * @type {!WorkspaceSvg}
53
+ * @private
54
+ */
55
+ this.workspace_ = workspace;
56
+ /**
57
+ * Whether this scrollbar is part of a pair.
58
+ * @type {boolean}
59
+ * @private
60
+ */
61
+ this.pair_ = opt_pair || false;
62
+ /**
63
+ * Whether this is a horizontal scrollbar.
64
+ * @type {boolean}
65
+ * @private
66
+ */
67
+ this.horizontal_ = horizontal;
68
+ /**
69
+ * Margin around the scrollbar (between the scrollbar and the edge of the
70
+ * viewport in pixels).
71
+ * @type {number}
72
+ * @const
73
+ * @private
74
+ */
75
+ this.margin_ = (opt_margin !== undefined) ?
76
+ opt_margin :
77
+ Scrollbar.DEFAULT_SCROLLBAR_MARGIN;
78
+ /**
79
+ * Previously recorded metrics from the workspace.
80
+ * @type {?Metrics}
81
+ * @private
82
+ */
83
+ this.oldHostMetrics_ = null;
84
+ /**
85
+ * The ratio of handle position offset to workspace content displacement.
86
+ * @type {?number}
87
+ * @package
88
+ */
89
+ this.ratio = null;
90
+
91
+ /**
92
+ * The location of the origin of the workspace that the scrollbar is in,
93
+ * measured in CSS pixels relative to the injection div origin. This is
94
+ * usually (0, 0). When the scrollbar is in a flyout it may have a
95
+ * different origin.
96
+ * @type {Coordinate}
97
+ * @private
98
+ */
99
+ this.origin_ = new Coordinate(0, 0);
100
+
101
+ /**
102
+ * The position of the mouse along this scrollbar's major axis at the start
103
+ * of the most recent drag. Units are CSS pixels, with (0, 0) at the top
104
+ * left of the browser window. For a horizontal scrollbar this is the x
105
+ * coordinate of the mouse down event; for a vertical scrollbar it's the y
106
+ * coordinate of the mouse down event.
107
+ * @type {number}
108
+ * @private
109
+ */
110
+ this.startDragMouse_ = 0;
111
+
112
+ /**
113
+ * The length of the scrollbars (including the handle and the background),
114
+ * in CSS pixels. This is equivalent to scrollbar background length and the
115
+ * area within which the scrollbar handle can move.
116
+ * @type {number}
117
+ * @private
118
+ */
119
+ this.scrollbarLength_ = 0;
120
+
121
+ /**
122
+ * The length of the scrollbar handle in CSS pixels.
123
+ * @type {number}
124
+ * @private
125
+ */
126
+ this.handleLength_ = 0;
127
+
128
+ /**
129
+ * The offset of the start of the handle from the scrollbar position, in CSS
130
+ * pixels.
131
+ * @type {number}
132
+ * @private
133
+ */
134
+ this.handlePosition_ = 0;
135
+
136
+ /**
137
+ * @type {number}
138
+ * @private
139
+ */
140
+ this.startDragHandle = 0;
141
+
142
+ /**
143
+ * Whether the scrollbar handle is visible.
144
+ * @type {boolean}
145
+ * @private
146
+ */
147
+ this.isVisible_ = true;
148
+
149
+ /**
150
+ * Whether the workspace containing this scrollbar is visible.
151
+ * @type {boolean}
152
+ * @private
153
+ */
154
+ this.containerVisible_ = true;
155
+
156
+ /**
157
+ * @type {?SVGRectElement}
158
+ * @private
159
+ */
160
+ this.svgBackground_ = null;
161
+
162
+ /**
163
+ * @type {?SVGRectElement}
164
+ * @private
165
+ */
166
+ this.svgHandle_ = null;
167
+
168
+ /**
169
+ * @type {?SVGSVGElement}
170
+ * @private
171
+ */
172
+ this.outerSvg_ = null;
173
+
174
+ /**
175
+ * @type {?SVGGElement}
176
+ * @private
177
+ */
178
+ this.svgGroup_ = null;
179
+
180
+ this.createDom_(opt_class);
181
+
182
+ /**
183
+ * The upper left corner of the scrollbar's SVG group in CSS pixels relative
184
+ * to the scrollbar's origin. This is usually relative to the injection div
185
+ * origin.
186
+ * @type {Coordinate}
187
+ * @package
188
+ */
189
+ this.position = new Coordinate(0, 0);
190
+
191
+ // Store the thickness in a temp variable for readability.
192
+ const scrollbarThickness = Scrollbar.scrollbarThickness;
193
+ if (horizontal) {
194
+ this.svgBackground_.setAttribute('height', scrollbarThickness);
195
+ this.outerSvg_.setAttribute('height', scrollbarThickness);
196
+ this.svgHandle_.setAttribute('height', scrollbarThickness - 5);
197
+ this.svgHandle_.setAttribute('y', 2.5);
198
+
199
+ this.lengthAttribute_ = 'width';
200
+ this.positionAttribute_ = 'x';
201
+ } else {
202
+ this.svgBackground_.setAttribute('width', scrollbarThickness);
203
+ this.outerSvg_.setAttribute('width', scrollbarThickness);
204
+ this.svgHandle_.setAttribute('width', scrollbarThickness - 5);
205
+ this.svgHandle_.setAttribute('x', 2.5);
206
+
207
+ this.lengthAttribute_ = 'height';
208
+ this.positionAttribute_ = 'y';
209
+ }
210
+ const scrollbar = this;
211
+ this.onMouseDownBarWrapper_ = browserEvents.conditionalBind(
212
+ this.svgBackground_, 'mousedown', scrollbar, scrollbar.onMouseDownBar_);
213
+ this.onMouseDownHandleWrapper_ = browserEvents.conditionalBind(
214
+ this.svgHandle_, 'mousedown', scrollbar, scrollbar.onMouseDownHandle_);
215
+ }
216
+
55
217
  /**
56
- * Whether this scrollbar is part of a pair.
57
- * @type {boolean}
58
- * @private
218
+ * Dispose of this scrollbar.
219
+ * Unlink from all DOM elements to prevent memory leaks.
220
+ * @suppress {checkTypes}
59
221
  */
60
- this.pair_ = opt_pair || false;
222
+ dispose() {
223
+ this.cleanUp_();
224
+ browserEvents.unbind(this.onMouseDownBarWrapper_);
225
+ this.onMouseDownBarWrapper_ = null;
226
+ browserEvents.unbind(this.onMouseDownHandleWrapper_);
227
+ this.onMouseDownHandleWrapper_ = null;
228
+
229
+ dom.removeNode(this.outerSvg_);
230
+ this.outerSvg_ = null;
231
+ this.svgGroup_ = null;
232
+ this.svgBackground_ = null;
233
+ if (this.svgHandle_) {
234
+ this.workspace_.getThemeManager().unsubscribe(this.svgHandle_);
235
+ this.svgHandle_ = null;
236
+ }
237
+ this.workspace_ = null;
238
+ }
239
+
61
240
  /**
62
- * Whether this is a horizontal scrollbar.
63
- * @type {boolean}
241
+ * Constrain the handle's length within the minimum (0) and maximum
242
+ * (scrollbar background) values allowed for the scrollbar.
243
+ * @param {number} value Value that is potentially out of bounds, in CSS
244
+ * pixels.
245
+ * @return {number} Constrained value, in CSS pixels.
64
246
  * @private
65
247
  */
66
- this.horizontal_ = horizontal;
248
+ constrainHandleLength_(value) {
249
+ if (value <= 0 || isNaN(value)) {
250
+ value = 0;
251
+ } else {
252
+ value = Math.min(value, this.scrollbarLength_);
253
+ }
254
+ return value;
255
+ }
256
+
67
257
  /**
68
- * Margin around the scrollbar (between the scrollbar and the edge of the
69
- * viewport in pixels).
70
- * @type {number}
71
- * @const
258
+ * Set the length of the scrollbar's handle and change the SVG attribute
259
+ * accordingly.
260
+ * @param {number} newLength The new scrollbar handle length in CSS pixels.
72
261
  * @private
73
262
  */
74
- this.margin_ = (opt_margin !== undefined) ?
75
- opt_margin :
76
- Scrollbar.DEFAULT_SCROLLBAR_MARGIN;
263
+ setHandleLength_(newLength) {
264
+ this.handleLength_ = newLength;
265
+ this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_);
266
+ }
267
+
77
268
  /**
78
- * Previously recorded metrics from the workspace.
79
- * @type {?Metrics}
269
+ * Constrain the handle's position within the minimum (0) and maximum values
270
+ * allowed for the scrollbar.
271
+ * @param {number} value Value that is potentially out of bounds, in CSS
272
+ * pixels.
273
+ * @return {number} Constrained value, in CSS pixels.
80
274
  * @private
81
275
  */
82
- this.oldHostMetrics_ = null;
276
+ constrainHandlePosition_(value) {
277
+ if (value <= 0 || isNaN(value)) {
278
+ value = 0;
279
+ } else {
280
+ // Handle length should never be greater than this.scrollbarLength_.
281
+ // If the viewSize is greater than or equal to the scrollSize, the
282
+ // handleLength will end up equal to this.scrollbarLength_.
283
+ value = Math.min(value, this.scrollbarLength_ - this.handleLength_);
284
+ }
285
+ return value;
286
+ }
287
+
83
288
  /**
84
- * The ratio of handle position offset to workspace content displacement.
85
- * @type {?number}
86
- * @package
289
+ * Set the offset of the scrollbar's handle from the scrollbar's position, and
290
+ * change the SVG attribute accordingly.
291
+ * @param {number} newPosition The new scrollbar handle offset in CSS pixels.
87
292
  */
88
- this.ratio = null;
293
+ setHandlePosition(newPosition) {
294
+ this.handlePosition_ = newPosition;
295
+ this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
296
+ }
89
297
 
90
- this.createDom_(opt_class);
298
+ /**
299
+ * Set the size of the scrollbar's background and change the SVG attribute
300
+ * accordingly.
301
+ * @param {number} newSize The new scrollbar background length in CSS pixels.
302
+ * @private
303
+ */
304
+ setScrollbarLength_(newSize) {
305
+ this.scrollbarLength_ = newSize;
306
+ this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollbarLength_);
307
+ this.svgBackground_.setAttribute(
308
+ this.lengthAttribute_, this.scrollbarLength_);
309
+ }
91
310
 
92
311
  /**
93
- * The upper left corner of the scrollbar's SVG group in CSS pixels relative
94
- * to the scrollbar's origin. This is usually relative to the injection div
95
- * origin.
96
- * @type {Coordinate}
312
+ * Set the position of the scrollbar's SVG group in CSS pixels relative to the
313
+ * scrollbar's origin. This sets the scrollbar's location within the
314
+ * workspace.
315
+ * @param {number} x The new x coordinate.
316
+ * @param {number} y The new y coordinate.
97
317
  * @package
98
318
  */
99
- this.position = new Coordinate(0, 0);
100
-
101
- // Store the thickness in a temp variable for readability.
102
- const scrollbarThickness = Scrollbar.scrollbarThickness;
103
- if (horizontal) {
104
- this.svgBackground_.setAttribute('height', scrollbarThickness);
105
- this.outerSvg_.setAttribute('height', scrollbarThickness);
106
- this.svgHandle_.setAttribute('height', scrollbarThickness - 5);
107
- this.svgHandle_.setAttribute('y', 2.5);
108
-
109
- this.lengthAttribute_ = 'width';
110
- this.positionAttribute_ = 'x';
111
- } else {
112
- this.svgBackground_.setAttribute('width', scrollbarThickness);
113
- this.outerSvg_.setAttribute('width', scrollbarThickness);
114
- this.svgHandle_.setAttribute('width', scrollbarThickness - 5);
115
- this.svgHandle_.setAttribute('x', 2.5);
116
-
117
- this.lengthAttribute_ = 'height';
118
- this.positionAttribute_ = 'y';
319
+ setPosition(x, y) {
320
+ this.position.x = x;
321
+ this.position.y = y;
322
+
323
+ const tempX = this.position.x + this.origin_.x;
324
+ const tempY = this.position.y + this.origin_.y;
325
+ const transform = 'translate(' + tempX + 'px,' + tempY + 'px)';
326
+ dom.setCssTransform(/** @type {!Element} */ (this.outerSvg_), transform);
119
327
  }
120
- const scrollbar = this;
121
- this.onMouseDownBarWrapper_ = browserEvents.conditionalBind(
122
- this.svgBackground_, 'mousedown', scrollbar, scrollbar.onMouseDownBar_);
123
- this.onMouseDownHandleWrapper_ = browserEvents.conditionalBind(
124
- this.svgHandle_, 'mousedown', scrollbar, scrollbar.onMouseDownHandle_);
125
- };
126
-
127
- /**
128
- * The location of the origin of the workspace that the scrollbar is in,
129
- * measured in CSS pixels relative to the injection div origin. This is usually
130
- * (0, 0). When the scrollbar is in a flyout it may have a different origin.
131
- * @type {Coordinate}
132
- * @private
133
- */
134
- Scrollbar.prototype.origin_ = new Coordinate(0, 0);
135
-
136
- /**
137
- * The position of the mouse along this scrollbar's major axis at the start of
138
- * the most recent drag.
139
- * Units are CSS pixels, with (0, 0) at the top left of the browser window.
140
- * For a horizontal scrollbar this is the x coordinate of the mouse down event;
141
- * for a vertical scrollbar it's the y coordinate of the mouse down event.
142
- * @type {number}
143
- * @private
144
- */
145
- Scrollbar.prototype.startDragMouse_ = 0;
146
-
147
- /**
148
- * The length of the scrollbars (including the handle and the background), in
149
- * CSS pixels. This is equivalent to scrollbar background length and the area
150
- * within which the scrollbar handle can move.
151
- * @type {number}
152
- * @private
153
- */
154
- Scrollbar.prototype.scrollbarLength_ = 0;
155
-
156
- /**
157
- * The length of the scrollbar handle in CSS pixels.
158
- * @type {number}
159
- * @private
160
- */
161
- Scrollbar.prototype.handleLength_ = 0;
162
-
163
- /**
164
- * The offset of the start of the handle from the scrollbar position, in CSS
165
- * pixels.
166
- * @type {number}
167
- * @private
168
- */
169
- Scrollbar.prototype.handlePosition_ = 0;
170
-
171
- /**
172
- * Whether the scrollbar handle is visible.
173
- * @type {boolean}
174
- * @private
175
- */
176
- Scrollbar.prototype.isVisible_ = true;
177
-
178
- /**
179
- * Whether the workspace containing this scrollbar is visible.
180
- * @type {boolean}
181
- * @private
182
- */
183
- Scrollbar.prototype.containerVisible_ = true;
184
328
 
185
- /**
186
- * Width of vertical scrollbar or height of horizontal scrollbar in CSS pixels.
187
- * Scrollbars should be larger on touch devices.
188
- */
189
- Scrollbar.scrollbarThickness = 15;
190
- if (Touch.TOUCH_ENABLED) {
191
- Scrollbar.scrollbarThickness = 25;
192
- }
329
+ /**
330
+ * Recalculate the scrollbar's location and its length.
331
+ * @param {Metrics=} opt_metrics A data structure of from the
332
+ * describing all the required dimensions. If not provided, it will be
333
+ * fetched from the host object.
334
+ */
335
+ resize(opt_metrics) {
336
+ // Determine the location, height and width of the host element.
337
+ let hostMetrics = opt_metrics;
338
+ if (!hostMetrics) {
339
+ hostMetrics = this.workspace_.getMetrics();
340
+ if (!hostMetrics) {
341
+ // Host element is likely not visible.
342
+ return;
343
+ }
344
+ }
193
345
 
194
- /**
195
- * Default margin around the scrollbar (between the scrollbar and the edge of
196
- * the viewport in pixels).
197
- * @type {number}
198
- * @const
199
- * @package
200
- */
201
- Scrollbar.DEFAULT_SCROLLBAR_MARGIN = 0.5;
346
+ if (this.oldHostMetrics_ &&
347
+ Scrollbar.metricsAreEquivalent_(hostMetrics, this.oldHostMetrics_)) {
348
+ return;
349
+ }
202
350
 
351
+ if (this.horizontal_) {
352
+ this.resizeHorizontal_(hostMetrics);
353
+ } else {
354
+ this.resizeVertical_(hostMetrics);
355
+ }
203
356
 
204
- /**
205
- * @param {!Metrics} first An object containing computed
206
- * measurements of a workspace.
207
- * @param {!Metrics} second Another object containing computed
208
- * measurements of a workspace.
209
- * @return {boolean} Whether the two sets of metrics are equivalent.
210
- * @private
211
- */
212
- Scrollbar.metricsAreEquivalent_ = function(first, second) {
213
- return (
214
- first.viewWidth === second.viewWidth &&
215
- first.viewHeight === second.viewHeight &&
216
- first.viewLeft === second.viewLeft && first.viewTop === second.viewTop &&
217
- first.absoluteTop === second.absoluteTop &&
218
- first.absoluteLeft === second.absoluteLeft &&
219
- first.scrollWidth === second.scrollWidth &&
220
- first.scrollHeight === second.scrollHeight &&
221
- first.scrollLeft === second.scrollLeft &&
222
- first.scrollTop === second.scrollTop);
223
- };
357
+ this.oldHostMetrics_ = hostMetrics;
224
358
 
225
- /**
226
- * Dispose of this scrollbar.
227
- * Unlink from all DOM elements to prevent memory leaks.
228
- * @suppress {checkTypes}
229
- */
230
- Scrollbar.prototype.dispose = function() {
231
- this.cleanUp_();
232
- browserEvents.unbind(this.onMouseDownBarWrapper_);
233
- this.onMouseDownBarWrapper_ = null;
234
- browserEvents.unbind(this.onMouseDownHandleWrapper_);
235
- this.onMouseDownHandleWrapper_ = null;
236
-
237
- dom.removeNode(this.outerSvg_);
238
- this.outerSvg_ = null;
239
- this.svgGroup_ = null;
240
- this.svgBackground_ = null;
241
- if (this.svgHandle_) {
242
- this.workspace_.getThemeManager().unsubscribe(this.svgHandle_);
243
- this.svgHandle_ = null;
359
+ // Resizing may have caused some scrolling.
360
+ this.updateMetrics_();
244
361
  }
245
- this.workspace_ = null;
246
- };
247
362
 
248
- /**
249
- * Constrain the handle's length within the minimum (0) and maximum
250
- * (scrollbar background) values allowed for the scrollbar.
251
- * @param {number} value Value that is potentially out of bounds, in CSS pixels.
252
- * @return {number} Constrained value, in CSS pixels.
253
- * @private
254
- */
255
- Scrollbar.prototype.constrainHandleLength_ = function(value) {
256
- if (value <= 0 || isNaN(value)) {
257
- value = 0;
258
- } else {
259
- value = Math.min(value, this.scrollbarLength_);
363
+ /**
364
+ * Returns whether the a resizeView is necessary by comparing the passed
365
+ * hostMetrics with cached old host metrics.
366
+ * @param {!Metrics} hostMetrics A data structure describing all
367
+ * the required dimensions, possibly fetched from the host object.
368
+ * @return {boolean} Whether a resizeView is necessary.
369
+ * @private
370
+ */
371
+ requiresViewResize_(hostMetrics) {
372
+ if (!this.oldHostMetrics_) {
373
+ return true;
374
+ }
375
+ return this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth ||
376
+ this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight ||
377
+ this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft ||
378
+ this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop;
260
379
  }
261
- return value;
262
- };
263
380
 
264
- /**
265
- * Set the length of the scrollbar's handle and change the SVG attribute
266
- * accordingly.
267
- * @param {number} newLength The new scrollbar handle length in CSS pixels.
268
- * @private
269
- */
270
- Scrollbar.prototype.setHandleLength_ = function(newLength) {
271
- this.handleLength_ = newLength;
272
- this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_);
273
- };
274
-
275
- /**
276
- * Constrain the handle's position within the minimum (0) and maximum values
277
- * allowed for the scrollbar.
278
- * @param {number} value Value that is potentially out of bounds, in CSS pixels.
279
- * @return {number} Constrained value, in CSS pixels.
280
- * @private
281
- */
282
- Scrollbar.prototype.constrainHandlePosition_ = function(value) {
283
- if (value <= 0 || isNaN(value)) {
284
- value = 0;
285
- } else {
286
- // Handle length should never be greater than this.scrollbarLength_.
287
- // If the viewSize is greater than or equal to the scrollSize, the
288
- // handleLength will end up equal to this.scrollbarLength_.
289
- value = Math.min(value, this.scrollbarLength_ - this.handleLength_);
381
+ /**
382
+ * Recalculate a horizontal scrollbar's location and length.
383
+ * @param {!Metrics} hostMetrics A data structure describing all
384
+ * the required dimensions, possibly fetched from the host object.
385
+ * @private
386
+ */
387
+ resizeHorizontal_(hostMetrics) {
388
+ if (this.requiresViewResize_(hostMetrics)) {
389
+ this.resizeViewHorizontal(hostMetrics);
390
+ } else {
391
+ this.resizeContentHorizontal(hostMetrics);
392
+ }
290
393
  }
291
- return value;
292
- };
293
394
 
294
- /**
295
- * Set the offset of the scrollbar's handle from the scrollbar's position, and
296
- * change the SVG attribute accordingly.
297
- * @param {number} newPosition The new scrollbar handle offset in CSS pixels.
298
- */
299
- Scrollbar.prototype.setHandlePosition = function(newPosition) {
300
- this.handlePosition_ = newPosition;
301
- this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
302
- };
395
+ /**
396
+ * Recalculate a horizontal scrollbar's location on the screen and path
397
+ * length. This should be called when the layout or size of the window has
398
+ * changed.
399
+ * @param {!Metrics} hostMetrics A data structure describing all
400
+ * the required dimensions, possibly fetched from the host object.
401
+ */
402
+ resizeViewHorizontal(hostMetrics) {
403
+ let viewSize = hostMetrics.viewWidth - this.margin_ * 2;
404
+ if (this.pair_) {
405
+ // Shorten the scrollbar to make room for the corner square.
406
+ viewSize -= Scrollbar.scrollbarThickness;
407
+ }
408
+ this.setScrollbarLength_(Math.max(0, viewSize));
303
409
 
304
- /**
305
- * Set the size of the scrollbar's background and change the SVG attribute
306
- * accordingly.
307
- * @param {number} newSize The new scrollbar background length in CSS pixels.
308
- * @private
309
- */
310
- Scrollbar.prototype.setScrollbarLength_ = function(newSize) {
311
- this.scrollbarLength_ = newSize;
312
- this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollbarLength_);
313
- this.svgBackground_.setAttribute(
314
- this.lengthAttribute_, this.scrollbarLength_);
315
- };
410
+ let xCoordinate = hostMetrics.absoluteLeft + this.margin_;
411
+ if (this.pair_ && this.workspace_.RTL) {
412
+ xCoordinate += Scrollbar.scrollbarThickness;
413
+ }
316
414
 
317
- /**
318
- * Set the position of the scrollbar's SVG group in CSS pixels relative to the
319
- * scrollbar's origin. This sets the scrollbar's location within the workspace.
320
- * @param {number} x The new x coordinate.
321
- * @param {number} y The new y coordinate.
322
- * @package
323
- */
324
- Scrollbar.prototype.setPosition = function(x, y) {
325
- this.position.x = x;
326
- this.position.y = y;
415
+ // Horizontal toolbar should always be just above the bottom of the
416
+ // workspace.
417
+ const yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight -
418
+ Scrollbar.scrollbarThickness - this.margin_;
419
+ this.setPosition(xCoordinate, yCoordinate);
327
420
 
328
- const tempX = this.position.x + this.origin_.x;
329
- const tempY = this.position.y + this.origin_.y;
330
- const transform = 'translate(' + tempX + 'px,' + tempY + 'px)';
331
- dom.setCssTransform(this.outerSvg_, transform);
332
- };
421
+ // If the view has been resized, a content resize will also be necessary.
422
+ // The reverse is not true.
423
+ this.resizeContentHorizontal(hostMetrics);
424
+ }
333
425
 
334
- /**
335
- * Recalculate the scrollbar's location and its length.
336
- * @param {Metrics=} opt_metrics A data structure of from the
337
- * describing all the required dimensions. If not provided, it will be
338
- * fetched from the host object.
339
- */
340
- Scrollbar.prototype.resize = function(opt_metrics) {
341
- // Determine the location, height and width of the host element.
342
- let hostMetrics = opt_metrics;
343
- if (!hostMetrics) {
344
- hostMetrics = this.workspace_.getMetrics();
345
- if (!hostMetrics) {
346
- // Host element is likely not visible.
426
+ /**
427
+ * Recalculate a horizontal scrollbar's location within its path and length.
428
+ * This should be called when the contents of the workspace have changed.
429
+ * @param {!Metrics} hostMetrics A data structure describing all
430
+ * the required dimensions, possibly fetched from the host object.
431
+ */
432
+ resizeContentHorizontal(hostMetrics) {
433
+ if (hostMetrics.viewWidth >= hostMetrics.scrollWidth) {
434
+ // viewWidth is often greater than scrollWidth in flyouts and
435
+ // non-scrollable workspaces.
436
+ this.setHandleLength_(this.scrollbarLength_);
437
+ this.setHandlePosition(0);
438
+ if (!this.pair_) {
439
+ // The scrollbar isn't needed.
440
+ // This doesn't apply to scrollbar pairs because interactions with the
441
+ // corner square aren't handled.
442
+ this.setVisible(false);
443
+ }
347
444
  return;
445
+ } else if (!this.pair_) {
446
+ // The scrollbar is needed. Only non-paired scrollbars are hidden/shown.
447
+ this.setVisible(true);
348
448
  }
349
- }
350
449
 
351
- if (this.oldHostMetrics_ &&
352
- Scrollbar.metricsAreEquivalent_(hostMetrics, this.oldHostMetrics_)) {
353
- return;
450
+ // Resize the handle.
451
+ let handleLength =
452
+ this.scrollbarLength_ * hostMetrics.viewWidth / hostMetrics.scrollWidth;
453
+ handleLength = this.constrainHandleLength_(handleLength);
454
+ this.setHandleLength_(handleLength);
455
+
456
+ // Compute the handle offset.
457
+ // The position of the handle can be between:
458
+ // 0 and this.scrollbarLength_ - handleLength
459
+ // If viewLeft === scrollLeft
460
+ // then the offset should be 0
461
+ // If viewRight === scrollRight
462
+ // then viewLeft = scrollLeft + scrollWidth - viewWidth
463
+ // then the offset should be max offset
464
+
465
+ const maxScrollDistance = hostMetrics.scrollWidth - hostMetrics.viewWidth;
466
+ const contentDisplacement = hostMetrics.viewLeft - hostMetrics.scrollLeft;
467
+ // Percent of content to the left of our current position.
468
+ const offsetRatio = contentDisplacement / maxScrollDistance;
469
+ // Area available to scroll * percent to the left
470
+ const maxHandleOffset = this.scrollbarLength_ - this.handleLength_;
471
+ let handleOffset = maxHandleOffset * offsetRatio;
472
+ handleOffset = this.constrainHandlePosition_(handleOffset);
473
+ this.setHandlePosition(handleOffset);
474
+
475
+ // Compute ratio (for use with set calls, which pass in content
476
+ // displacement).
477
+ this.ratio = maxHandleOffset / maxScrollDistance;
354
478
  }
355
479
 
356
- if (this.horizontal_) {
357
- this.resizeHorizontal_(hostMetrics);
358
- } else {
359
- this.resizeVertical_(hostMetrics);
480
+ /**
481
+ * Recalculate a vertical scrollbar's location and length.
482
+ * @param {!Metrics} hostMetrics A data structure describing all
483
+ * the required dimensions, possibly fetched from the host object.
484
+ * @private
485
+ */
486
+ resizeVertical_(hostMetrics) {
487
+ if (this.requiresViewResize_(hostMetrics)) {
488
+ this.resizeViewVertical(hostMetrics);
489
+ } else {
490
+ this.resizeContentVertical(hostMetrics);
491
+ }
360
492
  }
361
493
 
362
- this.oldHostMetrics_ = hostMetrics;
494
+ /**
495
+ * Recalculate a vertical scrollbar's location on the screen and path length.
496
+ * This should be called when the layout or size of the window has changed.
497
+ * @param {!Metrics} hostMetrics A data structure describing all
498
+ * the required dimensions, possibly fetched from the host object.
499
+ */
500
+ resizeViewVertical(hostMetrics) {
501
+ let viewSize = hostMetrics.viewHeight - this.margin_ * 2;
502
+ if (this.pair_) {
503
+ // Shorten the scrollbar to make room for the corner square.
504
+ viewSize -= Scrollbar.scrollbarThickness;
505
+ }
506
+ this.setScrollbarLength_(Math.max(0, viewSize));
363
507
 
364
- // Resizing may have caused some scrolling.
365
- this.updateMetrics_();
366
- };
508
+ const xCoordinate = this.workspace_.RTL ?
509
+ hostMetrics.absoluteLeft + this.margin_ :
510
+ hostMetrics.absoluteLeft + hostMetrics.viewWidth -
511
+ Scrollbar.scrollbarThickness - this.margin_;
367
512
 
368
- /**
369
- * Returns whether the a resizeView is necessary by comparing the passed
370
- * hostMetrics with cached old host metrics.
371
- * @param {!Metrics} hostMetrics A data structure describing all
372
- * the required dimensions, possibly fetched from the host object.
373
- * @return {boolean} Whether a resizeView is necessary.
374
- * @private
375
- */
376
- Scrollbar.prototype.requiresViewResize_ = function(hostMetrics) {
377
- if (!this.oldHostMetrics_) {
378
- return true;
379
- }
380
- return this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth ||
381
- this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight ||
382
- this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft ||
383
- this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop;
384
- };
513
+ const yCoordinate = hostMetrics.absoluteTop + this.margin_;
514
+ this.setPosition(xCoordinate, yCoordinate);
385
515
 
386
- /**
387
- * Recalculate a horizontal scrollbar's location and length.
388
- * @param {!Metrics} hostMetrics A data structure describing all
389
- * the required dimensions, possibly fetched from the host object.
390
- * @private
391
- */
392
- Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) {
393
- if (this.requiresViewResize_(hostMetrics)) {
394
- this.resizeViewHorizontal(hostMetrics);
395
- } else {
396
- this.resizeContentHorizontal(hostMetrics);
516
+ // If the view has been resized, a content resize will also be necessary.
517
+ // The reverse is not true.
518
+ this.resizeContentVertical(hostMetrics);
397
519
  }
398
- };
399
520
 
400
- /**
401
- * Recalculate a horizontal scrollbar's location on the screen and path length.
402
- * This should be called when the layout or size of the window has changed.
403
- * @param {!Metrics} hostMetrics A data structure describing all
404
- * the required dimensions, possibly fetched from the host object.
405
- */
406
- Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
407
- let viewSize = hostMetrics.viewWidth - this.margin_ * 2;
408
- if (this.pair_) {
409
- // Shorten the scrollbar to make room for the corner square.
410
- viewSize -= Scrollbar.scrollbarThickness;
521
+ /**
522
+ * Recalculate a vertical scrollbar's location within its path and length.
523
+ * This should be called when the contents of the workspace have changed.
524
+ * @param {!Metrics} hostMetrics A data structure describing all
525
+ * the required dimensions, possibly fetched from the host object.
526
+ */
527
+ resizeContentVertical(hostMetrics) {
528
+ if (hostMetrics.viewHeight >= hostMetrics.scrollHeight) {
529
+ // viewHeight is often greater than scrollHeight in flyouts and
530
+ // non-scrollable workspaces.
531
+ this.setHandleLength_(this.scrollbarLength_);
532
+ this.setHandlePosition(0);
533
+ if (!this.pair_) {
534
+ // The scrollbar isn't needed.
535
+ // This doesn't apply to scrollbar pairs because interactions with the
536
+ // corner square aren't handled.
537
+ this.setVisible(false);
538
+ }
539
+ return;
540
+ } else if (!this.pair_) {
541
+ // The scrollbar is needed. Only non-paired scrollbars are hidden/shown.
542
+ this.setVisible(true);
543
+ }
544
+
545
+ // Resize the handle.
546
+ let handleLength = this.scrollbarLength_ * hostMetrics.viewHeight /
547
+ hostMetrics.scrollHeight;
548
+ handleLength = this.constrainHandleLength_(handleLength);
549
+ this.setHandleLength_(handleLength);
550
+
551
+ // Compute the handle offset.
552
+ // The position of the handle can be between:
553
+ // 0 and this.scrollbarLength_ - handleLength
554
+ // If viewTop === scrollTop
555
+ // then the offset should be 0
556
+ // If viewBottom === scrollBottom
557
+ // then viewTop = scrollTop + scrollHeight - viewHeight
558
+ // then the offset should be max offset
559
+
560
+ const maxScrollDistance = hostMetrics.scrollHeight - hostMetrics.viewHeight;
561
+ const contentDisplacement = hostMetrics.viewTop - hostMetrics.scrollTop;
562
+ // Percent of content to the left of our current position.
563
+ const offsetRatio = contentDisplacement / maxScrollDistance;
564
+ // Area available to scroll * percent to the left
565
+ const maxHandleOffset = this.scrollbarLength_ - this.handleLength_;
566
+ let handleOffset = maxHandleOffset * offsetRatio;
567
+ handleOffset = this.constrainHandlePosition_(handleOffset);
568
+ this.setHandlePosition(handleOffset);
569
+
570
+ // Compute ratio (for use with set calls, which pass in content
571
+ // displacement).
572
+ this.ratio = maxHandleOffset / maxScrollDistance;
411
573
  }
412
- this.setScrollbarLength_(Math.max(0, viewSize));
413
574
 
414
- let xCoordinate = hostMetrics.absoluteLeft + this.margin_;
415
- if (this.pair_ && this.workspace_.RTL) {
416
- xCoordinate += Scrollbar.scrollbarThickness;
575
+ /**
576
+ * Create all the DOM elements required for a scrollbar.
577
+ * The resulting widget is not sized.
578
+ * @param {string=} opt_class A class to be applied to this scrollbar.
579
+ * @private
580
+ */
581
+ createDom_(opt_class) {
582
+ /* Create the following DOM:
583
+ <svg class="blocklyScrollbarHorizontal optionalClass">
584
+ <g>
585
+ <rect class="blocklyScrollbarBackground" />
586
+ <rect class="blocklyScrollbarHandle" rx="8" ry="8" />
587
+ </g>
588
+ </svg>
589
+ */
590
+ let className =
591
+ 'blocklyScrollbar' + (this.horizontal_ ? 'Horizontal' : 'Vertical');
592
+ if (opt_class) {
593
+ className += ' ' + opt_class;
594
+ }
595
+ this.outerSvg_ = dom.createSvgElement(Svg.SVG, {'class': className}, null);
596
+ this.svgGroup_ = dom.createSvgElement(Svg.G, {}, this.outerSvg_);
597
+ this.svgBackground_ = dom.createSvgElement(
598
+ Svg.RECT, {'class': 'blocklyScrollbarBackground'}, this.svgGroup_);
599
+ const radius = Math.floor((Scrollbar.scrollbarThickness - 5) / 2);
600
+ this.svgHandle_ = dom.createSvgElement(
601
+ Svg.RECT,
602
+ {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius},
603
+ this.svgGroup_);
604
+ this.workspace_.getThemeManager().subscribe(
605
+ this.svgHandle_, 'scrollbarColour', 'fill');
606
+ this.workspace_.getThemeManager().subscribe(
607
+ this.svgHandle_, 'scrollbarOpacity', 'fill-opacity');
608
+ dom.insertAfter(this.outerSvg_, this.workspace_.getParentSvg());
417
609
  }
418
610
 
419
- // Horizontal toolbar should always be just above the bottom of the workspace.
420
- const yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight -
421
- Scrollbar.scrollbarThickness - this.margin_;
422
- this.setPosition(xCoordinate, yCoordinate);
611
+ /**
612
+ * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't
613
+ * needed.
614
+ * @return {boolean} True if visible.
615
+ */
616
+ isVisible() {
617
+ return this.isVisible_;
618
+ }
423
619
 
424
- // If the view has been resized, a content resize will also be necessary.
425
- // The reverse is not true.
426
- this.resizeContentHorizontal(hostMetrics);
427
- };
620
+ /**
621
+ * Set whether the scrollbar's container is visible and update
622
+ * display accordingly if visibility has changed.
623
+ * @param {boolean} visible Whether the container is visible
624
+ */
625
+ setContainerVisible(visible) {
626
+ const visibilityChanged = (visible !== this.containerVisible_);
428
627
 
429
- /**
430
- * Recalculate a horizontal scrollbar's location within its path and length.
431
- * This should be called when the contents of the workspace have changed.
432
- * @param {!Metrics} hostMetrics A data structure describing all
433
- * the required dimensions, possibly fetched from the host object.
434
- */
435
- Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
436
- if (hostMetrics.viewWidth >= hostMetrics.scrollWidth) {
437
- // viewWidth is often greater than scrollWidth in flyouts and
438
- // non-scrollable workspaces.
439
- this.setHandleLength_(this.scrollbarLength_);
440
- this.setHandlePosition(0);
441
- if (!this.pair_) {
442
- // The scrollbar isn't needed.
443
- // This doesn't apply to scrollbar pairs because interactions with the
444
- // corner square aren't handled.
445
- this.setVisible(false);
628
+ this.containerVisible_ = visible;
629
+ if (visibilityChanged) {
630
+ this.updateDisplay_();
446
631
  }
447
- return;
448
- } else if (!this.pair_) {
449
- // The scrollbar is needed. Only non-paired scrollbars are hidden/shown.
450
- this.setVisible(true);
451
632
  }
452
633
 
453
- // Resize the handle.
454
- let handleLength =
455
- this.scrollbarLength_ * hostMetrics.viewWidth / hostMetrics.scrollWidth;
456
- handleLength = this.constrainHandleLength_(handleLength);
457
- this.setHandleLength_(handleLength);
458
-
459
- // Compute the handle offset.
460
- // The position of the handle can be between:
461
- // 0 and this.scrollbarLength_ - handleLength
462
- // If viewLeft === scrollLeft
463
- // then the offset should be 0
464
- // If viewRight === scrollRight
465
- // then viewLeft = scrollLeft + scrollWidth - viewWidth
466
- // then the offset should be max offset
467
-
468
- const maxScrollDistance = hostMetrics.scrollWidth - hostMetrics.viewWidth;
469
- const contentDisplacement = hostMetrics.viewLeft - hostMetrics.scrollLeft;
470
- // Percent of content to the left of our current position.
471
- const offsetRatio = contentDisplacement / maxScrollDistance;
472
- // Area available to scroll * percent to the left
473
- const maxHandleOffset = this.scrollbarLength_ - this.handleLength_;
474
- let handleOffset = maxHandleOffset * offsetRatio;
475
- handleOffset = this.constrainHandlePosition_(handleOffset);
476
- this.setHandlePosition(handleOffset);
477
-
478
- // Compute ratio (for use with set calls, which pass in content displacement).
479
- this.ratio = maxHandleOffset / maxScrollDistance;
480
- };
634
+ /**
635
+ * Set whether the scrollbar is visible.
636
+ * Only applies to non-paired scrollbars.
637
+ * @param {boolean} visible True if visible.
638
+ */
639
+ setVisible(visible) {
640
+ const visibilityChanged = (visible !== this.isVisible());
481
641
 
482
- /**
483
- * Recalculate a vertical scrollbar's location and length.
484
- * @param {!Metrics} hostMetrics A data structure describing all
485
- * the required dimensions, possibly fetched from the host object.
486
- * @private
487
- */
488
- Scrollbar.prototype.resizeVertical_ = function(hostMetrics) {
489
- if (this.requiresViewResize_(hostMetrics)) {
490
- this.resizeViewVertical(hostMetrics);
491
- } else {
492
- this.resizeContentVertical(hostMetrics);
642
+ // Ideally this would also apply to scrollbar pairs, but that's a bigger
643
+ // headache (due to interactions with the corner square).
644
+ if (this.pair_) {
645
+ throw Error('Unable to toggle visibility of paired scrollbars.');
646
+ }
647
+ this.isVisible_ = visible;
648
+ if (visibilityChanged) {
649
+ this.updateDisplay_();
650
+ }
493
651
  }
494
- };
495
652
 
496
- /**
497
- * Recalculate a vertical scrollbar's location on the screen and path length.
498
- * This should be called when the layout or size of the window has changed.
499
- * @param {!Metrics} hostMetrics A data structure describing all
500
- * the required dimensions, possibly fetched from the host object.
501
- */
502
- Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
503
- let viewSize = hostMetrics.viewHeight - this.margin_ * 2;
504
- if (this.pair_) {
505
- // Shorten the scrollbar to make room for the corner square.
506
- viewSize -= Scrollbar.scrollbarThickness;
653
+ /**
654
+ * Update visibility of scrollbar based on whether it thinks it should
655
+ * be visible and whether its containing workspace is visible.
656
+ * We cannot rely on the containing workspace being hidden to hide us
657
+ * because it is not necessarily our parent in the DOM.
658
+ */
659
+ updateDisplay_() {
660
+ let show = true;
661
+ // Check whether our parent/container is visible.
662
+ if (!this.containerVisible_) {
663
+ show = false;
664
+ } else {
665
+ show = this.isVisible();
666
+ }
667
+ if (show) {
668
+ this.outerSvg_.setAttribute('display', 'block');
669
+ } else {
670
+ this.outerSvg_.setAttribute('display', 'none');
671
+ }
507
672
  }
508
- this.setScrollbarLength_(Math.max(0, viewSize));
509
673
 
510
- const xCoordinate = this.workspace_.RTL ?
511
- hostMetrics.absoluteLeft + this.margin_ :
512
- hostMetrics.absoluteLeft + hostMetrics.viewWidth -
513
- Scrollbar.scrollbarThickness - this.margin_;
674
+ /**
675
+ * Scroll by one pageful.
676
+ * Called when scrollbar background is clicked.
677
+ * @param {!Event} e Mouse down event.
678
+ * @private
679
+ */
680
+ onMouseDownBar_(e) {
681
+ this.workspace_.markFocused();
682
+ Touch.clearTouchIdentifier(); // This is really a click.
683
+ this.cleanUp_();
684
+ if (browserEvents.isRightButton(e)) {
685
+ // Right-click.
686
+ // Scrollbars have no context menu.
687
+ e.stopPropagation();
688
+ return;
689
+ }
690
+ const mouseXY = browserEvents.mouseToSvg(
691
+ e, this.workspace_.getParentSvg(),
692
+ this.workspace_.getInverseScreenCTM());
693
+ const mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
694
+
695
+ const handleXY =
696
+ svgMath.getInjectionDivXY(/** @type {!Element} */ (this.svgHandle_));
697
+ const handleStart = this.horizontal_ ? handleXY.x : handleXY.y;
698
+ let handlePosition = this.handlePosition_;
699
+
700
+ const pageLength = this.handleLength_ * 0.95;
701
+ if (mouseLocation <= handleStart) {
702
+ // Decrease the scrollbar's value by a page.
703
+ handlePosition -= pageLength;
704
+ } else if (mouseLocation >= handleStart + this.handleLength_) {
705
+ // Increase the scrollbar's value by a page.
706
+ handlePosition += pageLength;
707
+ }
514
708
 
515
- const yCoordinate = hostMetrics.absoluteTop + this.margin_;
516
- this.setPosition(xCoordinate, yCoordinate);
709
+ this.setHandlePosition(this.constrainHandlePosition_(handlePosition));
517
710
 
518
- // If the view has been resized, a content resize will also be necessary. The
519
- // reverse is not true.
520
- this.resizeContentVertical(hostMetrics);
521
- };
711
+ this.updateMetrics_();
712
+ e.stopPropagation();
713
+ e.preventDefault();
714
+ }
522
715
 
523
- /**
524
- * Recalculate a vertical scrollbar's location within its path and length.
525
- * This should be called when the contents of the workspace have changed.
526
- * @param {!Metrics} hostMetrics A data structure describing all
527
- * the required dimensions, possibly fetched from the host object.
528
- */
529
- Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
530
- if (hostMetrics.viewHeight >= hostMetrics.scrollHeight) {
531
- // viewHeight is often greater than scrollHeight in flyouts and
532
- // non-scrollable workspaces.
533
- this.setHandleLength_(this.scrollbarLength_);
534
- this.setHandlePosition(0);
535
- if (!this.pair_) {
536
- // The scrollbar isn't needed.
537
- // This doesn't apply to scrollbar pairs because interactions with the
538
- // corner square aren't handled.
539
- this.setVisible(false);
716
+ /**
717
+ * Start a dragging operation.
718
+ * Called when scrollbar handle is clicked.
719
+ * @param {!Event} e Mouse down event.
720
+ * @private
721
+ */
722
+ onMouseDownHandle_(e) {
723
+ this.workspace_.markFocused();
724
+ this.cleanUp_();
725
+ if (browserEvents.isRightButton(e)) {
726
+ // Right-click.
727
+ // Scrollbars have no context menu.
728
+ e.stopPropagation();
729
+ return;
540
730
  }
541
- return;
542
- } else if (!this.pair_) {
543
- // The scrollbar is needed. Only non-paired scrollbars are hidden/shown.
544
- this.setVisible(true);
731
+ // Look up the current translation and record it.
732
+ this.startDragHandle = this.handlePosition_;
733
+
734
+ // Tell the workspace to setup its drag surface since it is about to move.
735
+ // onMouseMoveHandle will call onScroll which actually tells the workspace
736
+ // to move.
737
+ this.workspace_.setupDragSurface();
738
+
739
+ // Record the current mouse position.
740
+ this.startDragMouse_ = this.horizontal_ ? e.clientX : e.clientY;
741
+ Scrollbar.onMouseUpWrapper_ = browserEvents.conditionalBind(
742
+ document, 'mouseup', this, this.onMouseUpHandle_);
743
+ Scrollbar.onMouseMoveWrapper_ = browserEvents.conditionalBind(
744
+ document, 'mousemove', this, this.onMouseMoveHandle_);
745
+ e.stopPropagation();
746
+ e.preventDefault();
545
747
  }
546
748
 
547
- // Resize the handle.
548
- let handleLength =
549
- this.scrollbarLength_ * hostMetrics.viewHeight / hostMetrics.scrollHeight;
550
- handleLength = this.constrainHandleLength_(handleLength);
551
- this.setHandleLength_(handleLength);
552
-
553
- // Compute the handle offset.
554
- // The position of the handle can be between:
555
- // 0 and this.scrollbarLength_ - handleLength
556
- // If viewTop === scrollTop
557
- // then the offset should be 0
558
- // If viewBottom === scrollBottom
559
- // then viewTop = scrollTop + scrollHeight - viewHeight
560
- // then the offset should be max offset
561
-
562
- const maxScrollDistance = hostMetrics.scrollHeight - hostMetrics.viewHeight;
563
- const contentDisplacement = hostMetrics.viewTop - hostMetrics.scrollTop;
564
- // Percent of content to the left of our current position.
565
- const offsetRatio = contentDisplacement / maxScrollDistance;
566
- // Area available to scroll * percent to the left
567
- const maxHandleOffset = this.scrollbarLength_ - this.handleLength_;
568
- let handleOffset = maxHandleOffset * offsetRatio;
569
- handleOffset = this.constrainHandlePosition_(handleOffset);
570
- this.setHandlePosition(handleOffset);
571
-
572
- // Compute ratio (for use with set calls, which pass in content displacement).
573
- this.ratio = maxHandleOffset / maxScrollDistance;
574
- };
575
-
576
- /**
577
- * Create all the DOM elements required for a scrollbar.
578
- * The resulting widget is not sized.
579
- * @param {string=} opt_class A class to be applied to this scrollbar.
580
- * @private
581
- */
582
- Scrollbar.prototype.createDom_ = function(opt_class) {
583
- /* Create the following DOM:
584
- <svg class="blocklyScrollbarHorizontal optionalClass">
585
- <g>
586
- <rect class="blocklyScrollbarBackground" />
587
- <rect class="blocklyScrollbarHandle" rx="8" ry="8" />
588
- </g>
589
- </svg>
590
- */
591
- let className =
592
- 'blocklyScrollbar' + (this.horizontal_ ? 'Horizontal' : 'Vertical');
593
- if (opt_class) {
594
- className += ' ' + opt_class;
749
+ /**
750
+ * Drag the scrollbar's handle.
751
+ * @param {!Event} e Mouse up event.
752
+ * @private
753
+ */
754
+ onMouseMoveHandle_(e) {
755
+ const currentMouse = this.horizontal_ ? e.clientX : e.clientY;
756
+ const mouseDelta = currentMouse - this.startDragMouse_;
757
+ const handlePosition = this.startDragHandle + mouseDelta;
758
+ // Position the bar.
759
+ this.setHandlePosition(this.constrainHandlePosition_(handlePosition));
760
+ this.updateMetrics_();
595
761
  }
596
- this.outerSvg_ = dom.createSvgElement(Svg.SVG, {'class': className}, null);
597
- this.svgGroup_ = dom.createSvgElement(Svg.G, {}, this.outerSvg_);
598
- this.svgBackground_ = dom.createSvgElement(
599
- Svg.RECT, {'class': 'blocklyScrollbarBackground'}, this.svgGroup_);
600
- const radius = Math.floor((Scrollbar.scrollbarThickness - 5) / 2);
601
- this.svgHandle_ = dom.createSvgElement(
602
- Svg.RECT, {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius},
603
- this.svgGroup_);
604
- this.workspace_.getThemeManager().subscribe(
605
- this.svgHandle_, 'scrollbarColour', 'fill');
606
- this.workspace_.getThemeManager().subscribe(
607
- this.svgHandle_, 'scrollbarOpacity', 'fill-opacity');
608
- dom.insertAfter(this.outerSvg_, this.workspace_.getParentSvg());
609
- };
610
-
611
- /**
612
- * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't
613
- * needed.
614
- * @return {boolean} True if visible.
615
- */
616
- Scrollbar.prototype.isVisible = function() {
617
- return this.isVisible_;
618
- };
619
-
620
- /**
621
- * Set whether the scrollbar's container is visible and update
622
- * display accordingly if visibility has changed.
623
- * @param {boolean} visible Whether the container is visible
624
- */
625
- Scrollbar.prototype.setContainerVisible = function(visible) {
626
- const visibilityChanged = (visible !== this.containerVisible_);
627
762
 
628
- this.containerVisible_ = visible;
629
- if (visibilityChanged) {
630
- this.updateDisplay_();
763
+ /**
764
+ * Release the scrollbar handle and reset state accordingly.
765
+ * @private
766
+ */
767
+ onMouseUpHandle_() {
768
+ // Tell the workspace to clean up now that the workspace is done moving.
769
+ this.workspace_.resetDragSurface();
770
+ Touch.clearTouchIdentifier();
771
+ this.cleanUp_();
631
772
  }
632
- };
633
-
634
- /**
635
- * Set whether the scrollbar is visible.
636
- * Only applies to non-paired scrollbars.
637
- * @param {boolean} visible True if visible.
638
- */
639
- Scrollbar.prototype.setVisible = function(visible) {
640
- const visibilityChanged = (visible !== this.isVisible());
641
773
 
642
- // Ideally this would also apply to scrollbar pairs, but that's a bigger
643
- // headache (due to interactions with the corner square).
644
- if (this.pair_) {
645
- throw Error('Unable to toggle visibility of paired scrollbars.');
646
- }
647
- this.isVisible_ = visible;
648
- if (visibilityChanged) {
649
- this.updateDisplay_();
774
+ /**
775
+ * Hide chaff and stop binding to mouseup and mousemove events. Call this to
776
+ * wrap up loose ends associated with the scrollbar.
777
+ * @private
778
+ */
779
+ cleanUp_() {
780
+ this.workspace_.hideChaff(true);
781
+ if (Scrollbar.onMouseUpWrapper_) {
782
+ browserEvents.unbind(Scrollbar.onMouseUpWrapper_);
783
+ Scrollbar.onMouseUpWrapper_ = null;
784
+ }
785
+ if (Scrollbar.onMouseMoveWrapper_) {
786
+ browserEvents.unbind(Scrollbar.onMouseMoveWrapper_);
787
+ Scrollbar.onMouseMoveWrapper_ = null;
788
+ }
650
789
  }
651
- };
652
790
 
653
- /**
654
- * Update visibility of scrollbar based on whether it thinks it should
655
- * be visible and whether its containing workspace is visible.
656
- * We cannot rely on the containing workspace being hidden to hide us
657
- * because it is not necessarily our parent in the DOM.
658
- */
659
- Scrollbar.prototype.updateDisplay_ = function() {
660
- let show = true;
661
- // Check whether our parent/container is visible.
662
- if (!this.containerVisible_) {
663
- show = false;
664
- } else {
665
- show = this.isVisible();
666
- }
667
- if (show) {
668
- this.outerSvg_.setAttribute('display', 'block');
669
- } else {
670
- this.outerSvg_.setAttribute('display', 'none');
791
+ /**
792
+ * Helper to calculate the ratio of handle position to scrollbar view size.
793
+ * @return {number} Ratio.
794
+ * @package
795
+ */
796
+ getRatio_() {
797
+ const scrollHandleRange = this.scrollbarLength_ - this.handleLength_;
798
+ let ratio = this.handlePosition_ / scrollHandleRange;
799
+ if (isNaN(ratio)) {
800
+ ratio = 0;
801
+ }
802
+ return ratio;
671
803
  }
672
- };
673
804
 
674
- /**
675
- * Scroll by one pageful.
676
- * Called when scrollbar background is clicked.
677
- * @param {!Event} e Mouse down event.
678
- * @private
679
- */
680
- Scrollbar.prototype.onMouseDownBar_ = function(e) {
681
- this.workspace_.markFocused();
682
- Touch.clearTouchIdentifier(); // This is really a click.
683
- this.cleanUp_();
684
- if (browserEvents.isRightButton(e)) {
685
- // Right-click.
686
- // Scrollbars have no context menu.
687
- e.stopPropagation();
688
- return;
689
- }
690
- const mouseXY = browserEvents.mouseToSvg(
691
- e, this.workspace_.getParentSvg(), this.workspace_.getInverseScreenCTM());
692
- const mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
693
-
694
- const handleXY = svgMath.getInjectionDivXY(this.svgHandle_);
695
- const handleStart = this.horizontal_ ? handleXY.x : handleXY.y;
696
- let handlePosition = this.handlePosition_;
697
-
698
- const pageLength = this.handleLength_ * 0.95;
699
- if (mouseLocation <= handleStart) {
700
- // Decrease the scrollbar's value by a page.
701
- handlePosition -= pageLength;
702
- } else if (mouseLocation >= handleStart + this.handleLength_) {
703
- // Increase the scrollbar's value by a page.
704
- handlePosition += pageLength;
805
+ /**
806
+ * Updates workspace metrics based on new scroll ratio. Called when scrollbar
807
+ * is moved.
808
+ * @private
809
+ */
810
+ updateMetrics_() {
811
+ const ratio = this.getRatio_();
812
+ const xyRatio = {};
813
+ if (this.horizontal_) {
814
+ xyRatio.x = ratio;
815
+ } else {
816
+ xyRatio.y = ratio;
817
+ }
818
+ this.workspace_.setMetrics(xyRatio);
705
819
  }
706
820
 
707
- this.setHandlePosition(this.constrainHandlePosition_(handlePosition));
708
-
709
- this.updateMetrics_();
710
- e.stopPropagation();
711
- e.preventDefault();
712
- };
713
-
714
- /**
715
- * Start a dragging operation.
716
- * Called when scrollbar handle is clicked.
717
- * @param {!Event} e Mouse down event.
718
- * @private
719
- */
720
- Scrollbar.prototype.onMouseDownHandle_ = function(e) {
721
- this.workspace_.markFocused();
722
- this.cleanUp_();
723
- if (browserEvents.isRightButton(e)) {
724
- // Right-click.
725
- // Scrollbars have no context menu.
726
- e.stopPropagation();
727
- return;
821
+ /**
822
+ * Set the scrollbar handle's position.
823
+ * @param {number} value The content displacement, relative to the view in
824
+ * pixels.
825
+ * @param {boolean=} updateMetrics Whether to update metrics on this set call.
826
+ * Defaults to true.
827
+ */
828
+ set(value, updateMetrics) {
829
+ this.setHandlePosition(this.constrainHandlePosition_(value * this.ratio));
830
+ if (updateMetrics || updateMetrics === undefined) {
831
+ this.updateMetrics_();
832
+ }
728
833
  }
729
- // Look up the current translation and record it.
730
- this.startDragHandle = this.handlePosition_;
731
-
732
- // Tell the workspace to setup its drag surface since it is about to move.
733
- // onMouseMoveHandle will call onScroll which actually tells the workspace
734
- // to move.
735
- this.workspace_.setupDragSurface();
736
-
737
- // Record the current mouse position.
738
- this.startDragMouse_ = this.horizontal_ ? e.clientX : e.clientY;
739
- Scrollbar.onMouseUpWrapper_ = browserEvents.conditionalBind(
740
- document, 'mouseup', this, this.onMouseUpHandle_);
741
- Scrollbar.onMouseMoveWrapper_ = browserEvents.conditionalBind(
742
- document, 'mousemove', this, this.onMouseMoveHandle_);
743
- e.stopPropagation();
744
- e.preventDefault();
745
- };
746
-
747
- /**
748
- * Drag the scrollbar's handle.
749
- * @param {!Event} e Mouse up event.
750
- * @private
751
- */
752
- Scrollbar.prototype.onMouseMoveHandle_ = function(e) {
753
- const currentMouse = this.horizontal_ ? e.clientX : e.clientY;
754
- const mouseDelta = currentMouse - this.startDragMouse_;
755
- const handlePosition = this.startDragHandle + mouseDelta;
756
- // Position the bar.
757
- this.setHandlePosition(this.constrainHandlePosition_(handlePosition));
758
- this.updateMetrics_();
759
- };
760
834
 
761
- /**
762
- * Release the scrollbar handle and reset state accordingly.
763
- * @private
764
- */
765
- Scrollbar.prototype.onMouseUpHandle_ = function() {
766
- // Tell the workspace to clean up now that the workspace is done moving.
767
- this.workspace_.resetDragSurface();
768
- Touch.clearTouchIdentifier();
769
- this.cleanUp_();
770
- };
771
-
772
- /**
773
- * Hide chaff and stop binding to mouseup and mousemove events. Call this to
774
- * wrap up loose ends associated with the scrollbar.
775
- * @private
776
- */
777
- Scrollbar.prototype.cleanUp_ = function() {
778
- this.workspace_.hideChaff(true);
779
- if (Scrollbar.onMouseUpWrapper_) {
780
- browserEvents.unbind(Scrollbar.onMouseUpWrapper_);
781
- Scrollbar.onMouseUpWrapper_ = null;
782
- }
783
- if (Scrollbar.onMouseMoveWrapper_) {
784
- browserEvents.unbind(Scrollbar.onMouseMoveWrapper_);
785
- Scrollbar.onMouseMoveWrapper_ = null;
835
+ /**
836
+ * Record the origin of the workspace that the scrollbar is in, in pixels
837
+ * relative to the injection div origin. This is for times when the scrollbar
838
+ * is used in an object whose origin isn't the same as the main workspace
839
+ * (e.g. in a flyout.)
840
+ * @param {number} x The x coordinate of the scrollbar's origin, in CSS
841
+ * pixels.
842
+ * @param {number} y The y coordinate of the scrollbar's origin, in CSS
843
+ * pixels.
844
+ */
845
+ setOrigin(x, y) {
846
+ this.origin_ = new Coordinate(x, y);
786
847
  }
787
- };
788
848
 
789
- /**
790
- * Helper to calculate the ratio of handle position to scrollbar view size.
791
- * @return {number} Ratio.
792
- * @package
793
- */
794
- Scrollbar.prototype.getRatio_ = function() {
795
- const scrollHandleRange = this.scrollbarLength_ - this.handleLength_;
796
- let ratio = this.handlePosition_ / scrollHandleRange;
797
- if (isNaN(ratio)) {
798
- ratio = 0;
849
+ /**
850
+ * @param {!Metrics} first An object containing computed
851
+ * measurements of a workspace.
852
+ * @param {!Metrics} second Another object containing computed
853
+ * measurements of a workspace.
854
+ * @return {boolean} Whether the two sets of metrics are equivalent.
855
+ * @private
856
+ */
857
+ static metricsAreEquivalent_(first, second) {
858
+ return (
859
+ first.viewWidth === second.viewWidth &&
860
+ first.viewHeight === second.viewHeight &&
861
+ first.viewLeft === second.viewLeft &&
862
+ first.viewTop === second.viewTop &&
863
+ first.absoluteTop === second.absoluteTop &&
864
+ first.absoluteLeft === second.absoluteLeft &&
865
+ first.scrollWidth === second.scrollWidth &&
866
+ first.scrollHeight === second.scrollHeight &&
867
+ first.scrollLeft === second.scrollLeft &&
868
+ first.scrollTop === second.scrollTop);
799
869
  }
800
- return ratio;
801
- };
870
+ }
802
871
 
803
872
  /**
804
- * Updates workspace metrics based on new scroll ratio. Called when scrollbar is
805
- * moved.
806
- * @private
873
+ * Width of vertical scrollbar or height of horizontal scrollbar in CSS pixels.
874
+ * Scrollbars should be larger on touch devices.
807
875
  */
808
- Scrollbar.prototype.updateMetrics_ = function() {
809
- const ratio = this.getRatio_();
810
- const xyRatio = {};
811
- if (this.horizontal_) {
812
- xyRatio.x = ratio;
813
- } else {
814
- xyRatio.y = ratio;
815
- }
816
- this.workspace_.setMetrics(xyRatio);
817
- };
876
+ Scrollbar.scrollbarThickness = 15;
818
877
 
819
- /**
820
- * Set the scrollbar handle's position.
821
- * @param {number} value The content displacement, relative to the view in
822
- * pixels.
823
- * @param {boolean=} updateMetrics Whether to update metrics on this set call.
824
- * Defaults to true.
825
- */
826
- Scrollbar.prototype.set = function(value, updateMetrics) {
827
- this.setHandlePosition(this.constrainHandlePosition_(value * this.ratio));
828
- if (updateMetrics || updateMetrics === undefined) {
829
- this.updateMetrics_();
830
- }
831
- };
878
+ if (Touch.TOUCH_ENABLED) {
879
+ Scrollbar.scrollbarThickness = 25;
880
+ }
832
881
 
833
882
  /**
834
- * Record the origin of the workspace that the scrollbar is in, in pixels
835
- * relative to the injection div origin. This is for times when the scrollbar is
836
- * used in an object whose origin isn't the same as the main workspace
837
- * (e.g. in a flyout.)
838
- * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels.
839
- * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels.
883
+ * Default margin around the scrollbar (between the scrollbar and the edge of
884
+ * the viewport in pixels).
885
+ * @type {number}
886
+ * @const
887
+ * @package
840
888
  */
841
- Scrollbar.prototype.setOrigin = function(x, y) {
842
- this.origin_ = new Coordinate(x, y);
843
- };
889
+ Scrollbar.DEFAULT_SCROLLBAR_MARGIN = 0.5;
844
890
 
845
891
  exports.Scrollbar = Scrollbar;