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.
- package/blockly.d.ts +18963 -18432
- package/blockly.min.js +852 -844
- package/blockly_compressed.js +669 -664
- package/blockly_compressed.js.map +1 -1
- package/blocks/blocks.js +47 -0
- package/blocks/colour.js +13 -3
- package/blocks/lists.js +22 -13
- package/blocks/logic.js +13 -3
- package/blocks/loops.js +24 -11
- package/blocks/math.js +12 -3
- package/blocks/procedures.js +41 -27
- package/blocks/text.js +22 -13
- package/blocks/variables.js +14 -3
- package/blocks/variables_dynamic.js +13 -3
- package/blocks_compressed.js +146 -141
- package/blocks_compressed.js.map +1 -1
- package/core/block.js +1869 -1814
- package/core/block_drag_surface.js +201 -200
- package/core/block_dragger.js +377 -373
- package/core/block_svg.js +1593 -1479
- package/core/blockly.js +8 -22
- package/core/blocks.js +9 -2
- package/core/browser_events.js +22 -5
- package/core/bubble.js +841 -797
- package/core/bubble_dragger.js +213 -206
- package/core/bump_objects.js +2 -2
- package/core/clipboard.js +9 -9
- package/core/comment.js +353 -332
- package/core/common.js +46 -17
- package/core/component_manager.js +181 -174
- package/core/config.js +87 -0
- package/core/connection.js +595 -584
- package/core/connection_checker.js +242 -244
- package/core/connection_db.js +235 -230
- package/core/contextmenu.js +9 -6
- package/core/contextmenu_items.js +1 -2
- package/core/contextmenu_registry.js +93 -89
- package/core/css.js +474 -474
- package/core/delete_area.js +45 -42
- package/core/drag_target.js +57 -56
- package/core/dropdowndiv.js +153 -163
- package/core/events/events.js +2 -2
- package/core/events/events_abstract.js +89 -77
- package/core/events/events_block_base.js +37 -36
- package/core/events/events_block_change.js +130 -124
- package/core/events/events_block_create.js +73 -71
- package/core/events/events_block_delete.js +84 -82
- package/core/events/events_block_drag.js +50 -49
- package/core/events/events_block_move.js +147 -140
- package/core/events/events_bubble_open.js +51 -50
- package/core/events/events_click.js +48 -44
- package/core/events/events_comment_base.js +72 -69
- package/core/events/events_comment_change.js +63 -61
- package/core/events/events_comment_create.js +44 -42
- package/core/events/events_comment_delete.js +42 -40
- package/core/events/events_comment_move.js +106 -104
- package/core/events/events_marker_move.js +65 -64
- package/core/events/events_selected.js +46 -45
- package/core/events/events_theme_change.js +36 -35
- package/core/events/events_toolbox_item_select.js +46 -45
- package/core/events/events_trashcan_open.js +37 -36
- package/core/events/events_ui.js +47 -46
- package/core/events/events_ui_base.js +30 -29
- package/core/events/events_var_base.js +37 -36
- package/core/events/events_var_create.js +50 -48
- package/core/events/events_var_delete.js +50 -48
- package/core/events/events_var_rename.js +51 -49
- package/core/events/events_viewport.js +66 -65
- package/core/events/utils.js +29 -14
- package/core/events/workspace_events.js +49 -55
- package/core/extensions.js +4 -3
- package/core/field.js +1061 -997
- package/core/field_angle.js +462 -442
- package/core/field_checkbox.js +194 -182
- package/core/field_colour.js +519 -505
- package/core/field_dropdown.js +617 -598
- package/core/field_image.js +229 -220
- package/core/field_label.js +102 -91
- package/core/field_label_serializable.js +42 -41
- package/core/field_multilineinput.js +372 -358
- package/core/field_number.js +272 -253
- package/core/field_textinput.js +499 -467
- package/core/field_variable.js +458 -420
- package/core/flyout_base.js +1005 -952
- package/core/flyout_button.js +277 -260
- package/core/flyout_horizontal.js +304 -302
- package/core/flyout_metrics_manager.js +64 -64
- package/core/flyout_vertical.js +306 -300
- package/core/generator.js +459 -446
- package/core/gesture.js +829 -813
- package/core/grid.js +166 -163
- package/core/icon.js +168 -159
- package/core/inject.js +7 -5
- package/core/input.js +257 -248
- package/core/insertion_marker_manager.js +655 -624
- package/core/internal_constants.js +0 -129
- package/core/keyboard_nav/ast_node.js +605 -596
- package/core/keyboard_nav/basic_cursor.js +166 -165
- package/core/keyboard_nav/cursor.js +99 -97
- package/core/keyboard_nav/marker.js +83 -79
- package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
- package/core/marker_manager.js +153 -141
- package/core/menu.js +377 -372
- package/core/menuitem.js +223 -217
- package/core/metrics_manager.js +403 -390
- package/core/mutator.js +468 -437
- package/core/names.js +229 -188
- package/core/options.js +290 -284
- package/core/procedures.js +29 -17
- package/core/registry.js +19 -16
- package/core/rendered_connection.js +482 -463
- package/core/renderers/common/block_rendering.js +9 -3
- package/core/renderers/common/constants.js +1119 -1112
- package/core/renderers/common/debug.js +14 -0
- package/core/renderers/common/debugger.js +338 -316
- package/core/renderers/common/drawer.js +380 -370
- package/core/renderers/common/i_path_object.js +2 -2
- package/core/renderers/common/info.js +626 -618
- package/core/renderers/common/marker_svg.js +579 -541
- package/core/renderers/common/path_object.js +203 -200
- package/core/renderers/common/renderer.js +220 -218
- package/core/renderers/geras/constants.js +36 -36
- package/core/renderers/geras/drawer.js +155 -147
- package/core/renderers/geras/highlight_constants.js +244 -238
- package/core/renderers/geras/highlighter.js +231 -179
- package/core/renderers/geras/info.js +392 -369
- package/core/renderers/geras/measurables/inline_input.js +25 -19
- package/core/renderers/geras/measurables/statement_input.js +23 -17
- package/core/renderers/geras/path_object.js +106 -121
- package/core/renderers/geras/renderer.js +96 -98
- package/core/renderers/measurables/base.js +30 -18
- package/core/renderers/measurables/bottom_row.js +83 -80
- package/core/renderers/measurables/connection.js +22 -15
- package/core/renderers/measurables/external_value_input.js +35 -22
- package/core/renderers/measurables/field.js +35 -20
- package/core/renderers/measurables/hat.js +18 -13
- package/core/renderers/measurables/icon.js +24 -17
- package/core/renderers/measurables/in_row_spacer.js +15 -13
- package/core/renderers/measurables/inline_input.js +43 -33
- package/core/renderers/measurables/input_connection.js +41 -28
- package/core/renderers/measurables/input_row.js +50 -44
- package/core/renderers/measurables/jagged_edge.js +14 -12
- package/core/renderers/measurables/next_connection.js +16 -14
- package/core/renderers/measurables/output_connection.js +26 -20
- package/core/renderers/measurables/previous_connection.js +16 -15
- package/core/renderers/measurables/round_corner.js +20 -18
- package/core/renderers/measurables/row.js +184 -168
- package/core/renderers/measurables/spacer_row.js +38 -23
- package/core/renderers/measurables/square_corner.js +18 -16
- package/core/renderers/measurables/statement_input.js +23 -20
- package/core/renderers/measurables/top_row.js +88 -85
- package/core/renderers/minimalist/constants.js +8 -7
- package/core/renderers/minimalist/drawer.js +11 -10
- package/core/renderers/minimalist/info.js +18 -18
- package/core/renderers/minimalist/renderer.js +40 -39
- package/core/renderers/thrasos/info.js +258 -248
- package/core/renderers/thrasos/renderer.js +20 -20
- package/core/renderers/zelos/constants.js +898 -873
- package/core/renderers/zelos/drawer.js +186 -169
- package/core/renderers/zelos/info.js +502 -479
- package/core/renderers/zelos/marker_svg.js +129 -115
- package/core/renderers/zelos/measurables/bottom_row.js +31 -30
- package/core/renderers/zelos/measurables/inputs.js +22 -21
- package/core/renderers/zelos/measurables/row_elements.js +14 -13
- package/core/renderers/zelos/measurables/top_row.js +34 -33
- package/core/renderers/zelos/path_object.js +181 -180
- package/core/renderers/zelos/renderer.js +91 -92
- package/core/scrollbar.js +759 -713
- package/core/scrollbar_pair.js +250 -245
- package/core/serialization/blocks.js +19 -9
- package/core/serialization/workspaces.js +3 -2
- package/core/shortcut_registry.js +286 -277
- package/core/sprites.js +31 -0
- package/core/theme.js +135 -141
- package/core/theme_manager.js +147 -143
- package/core/toolbox/category.js +602 -576
- package/core/toolbox/collapsible_category.js +226 -227
- package/core/toolbox/separator.js +70 -61
- package/core/toolbox/toolbox.js +934 -927
- package/core/toolbox/toolbox_item.js +115 -99
- package/core/tooltip.js +108 -35
- package/core/touch.js +8 -3
- package/core/touch_gesture.js +254 -251
- package/core/trashcan.js +606 -595
- package/core/utils/coordinate.js +97 -95
- package/core/utils/dom.js +2 -2
- package/core/utils/global.js +2 -0
- package/core/utils/rect.js +41 -37
- package/core/utils/sentinel.js +25 -0
- package/core/utils/size.js +30 -27
- package/core/utils/svg.js +18 -16
- package/core/variable_map.js +325 -341
- package/core/variable_model.js +55 -54
- package/core/variables.js +9 -2
- package/core/variables_dynamic.js +3 -1
- package/core/warning.js +126 -120
- package/core/widgetdiv.js +4 -4
- package/core/workspace.js +685 -664
- package/core/workspace_audio.js +124 -118
- package/core/workspace_comment.js +308 -298
- package/core/workspace_comment_svg.js +1029 -951
- package/core/workspace_drag_surface_svg.js +147 -140
- package/core/workspace_dragger.js +70 -71
- package/core/workspace_svg.js +2322 -2297
- package/core/xml.js +30 -20
- package/core/zoom_controls.js +431 -439
- package/dart_compressed.js +40 -43
- package/dart_compressed.js.map +1 -1
- package/generators/dart/colour.js +56 -64
- package/generators/dart/lists.js +61 -50
- package/generators/dart/math.js +160 -148
- package/generators/dart/text.js +83 -61
- package/generators/javascript/colour.js +37 -34
- package/generators/javascript/lists.js +50 -43
- package/generators/javascript/math.js +123 -139
- package/generators/javascript/text.js +67 -81
- package/generators/lua/colour.js +25 -23
- package/generators/lua/lists.js +97 -69
- package/generators/lua/logic.js +1 -2
- package/generators/lua/math.js +182 -144
- package/generators/lua/text.js +116 -99
- package/generators/php/colour.js +38 -32
- package/generators/php/lists.js +109 -89
- package/generators/php/math.js +90 -81
- package/generators/php/text.js +63 -61
- package/generators/python/colour.js +18 -18
- package/generators/python/lists.js +38 -30
- package/generators/python/loops.js +12 -8
- package/generators/python/math.js +104 -106
- package/generators/python/text.js +34 -30
- package/javascript_compressed.js +37 -39
- package/javascript_compressed.js.map +1 -1
- package/lua_compressed.js +39 -42
- package/lua_compressed.js.map +1 -1
- package/msg/az.js +2 -2
- package/msg/be.js +4 -4
- package/msg/cs.js +15 -15
- package/msg/de.js +1 -1
- package/msg/diq.js +1 -1
- package/msg/eo.js +1 -1
- package/msg/es.js +1 -1
- package/msg/fa.js +1 -1
- package/msg/fr.js +4 -4
- package/msg/he.js +1 -1
- package/msg/hr.js +2 -2
- package/msg/hy.js +2 -2
- package/msg/id.js +12 -12
- package/msg/inh.js +14 -14
- package/msg/ja.js +7 -7
- package/msg/lv.js +29 -29
- package/msg/pa.js +3 -3
- package/msg/smn.js +436 -0
- package/msg/te.js +1 -1
- package/msg/yue.js +1 -1
- package/msg/zh-hans.js +3 -3
- package/msg/zh-hant.js +3 -3
- package/package.json +7 -6
- package/php_compressed.js +38 -42
- package/php_compressed.js.map +1 -1
- package/python_compressed.js +26 -25
- package/python_compressed.js.map +1 -1
- package/blocks/all.js +0 -23
package/core/field_dropdown.js
CHANGED
|
@@ -21,713 +21,732 @@ goog.module('Blockly.FieldDropdown');
|
|
|
21
21
|
|
|
22
22
|
const aria = goog.require('Blockly.utils.aria');
|
|
23
23
|
const dom = goog.require('Blockly.utils.dom');
|
|
24
|
+
const dropDownDiv = goog.require('Blockly.dropDownDiv');
|
|
24
25
|
const fieldRegistry = goog.require('Blockly.fieldRegistry');
|
|
25
|
-
const object = goog.require('Blockly.utils.object');
|
|
26
26
|
const parsing = goog.require('Blockly.utils.parsing');
|
|
27
27
|
const userAgent = goog.require('Blockly.utils.userAgent');
|
|
28
28
|
const utilsString = goog.require('Blockly.utils.string');
|
|
29
29
|
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
|
30
|
-
const {DropDownDiv} = goog.require('Blockly.DropDownDiv');
|
|
31
30
|
const {Field} = goog.require('Blockly.Field');
|
|
32
31
|
const {MenuItem} = goog.require('Blockly.MenuItem');
|
|
33
32
|
const {Menu} = goog.require('Blockly.Menu');
|
|
33
|
+
/* eslint-disable-next-line no-unused-vars */
|
|
34
|
+
const {Sentinel} = goog.requireType('Blockly.utils.Sentinel');
|
|
34
35
|
const {Svg} = goog.require('Blockly.utils.Svg');
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Class for an editable dropdown field.
|
|
39
|
-
* @param {(!Array<!Array>|!Function)} menuGenerator A non-empty array of
|
|
40
|
-
* options for a dropdown list, or a function which generates these options.
|
|
41
|
-
* @param {Function=} opt_validator A function that is called to validate
|
|
42
|
-
* changes to the field's value. Takes in a language-neutral dropdown
|
|
43
|
-
* option & returns a validated language-neutral dropdown option, or null to
|
|
44
|
-
* abort the change.
|
|
45
|
-
* @param {Object=} opt_config A map of options used to configure the field.
|
|
46
|
-
* See the [field creation documentation]{@link
|
|
47
|
-
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation}
|
|
48
|
-
* for a list of properties this parameter supports.
|
|
49
40
|
* @extends {Field}
|
|
50
|
-
* @constructor
|
|
51
|
-
* @throws {TypeError} If `menuGenerator` options are incorrectly structured.
|
|
52
41
|
* @alias Blockly.FieldDropdown
|
|
53
42
|
*/
|
|
54
|
-
|
|
55
|
-
if (typeof menuGenerator !== 'function') {
|
|
56
|
-
validateOptions(menuGenerator);
|
|
57
|
-
}
|
|
58
|
-
|
|
43
|
+
class FieldDropdown extends Field {
|
|
59
44
|
/**
|
|
60
|
-
*
|
|
61
|
-
* or a function which
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
45
|
+
* @param {(!Array<!Array>|!Function|!Sentinel)} menuGenerator
|
|
46
|
+
* A non-empty array of options for a dropdown list, or a function which
|
|
47
|
+
* generates these options.
|
|
48
|
+
* Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
|
|
49
|
+
* subclasses that want to handle configuration and setting the field
|
|
50
|
+
* value after their own constructors have run).
|
|
51
|
+
* @param {Function=} opt_validator A function that is called to validate
|
|
52
|
+
* changes to the field's value. Takes in a language-neutral dropdown
|
|
53
|
+
* option & returns a validated language-neutral dropdown option, or null
|
|
54
|
+
* to abort the change.
|
|
55
|
+
* @param {Object=} opt_config A map of options used to configure the field.
|
|
56
|
+
* See the [field creation documentation]{@link
|
|
57
|
+
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation}
|
|
58
|
+
* for a list of properties this parameter supports.
|
|
59
|
+
* @throws {TypeError} If `menuGenerator` options are incorrectly structured.
|
|
65
60
|
*/
|
|
66
|
-
|
|
61
|
+
constructor(menuGenerator, opt_validator, opt_config) {
|
|
62
|
+
super(Field.SKIP_SETUP);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A reference to the currently selected menu item.
|
|
66
|
+
* @type {?MenuItem}
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
this.selectedMenuItem_ = null;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The dropdown menu.
|
|
73
|
+
* @type {?Menu}
|
|
74
|
+
* @protected
|
|
75
|
+
*/
|
|
76
|
+
this.menu_ = null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* SVG image element if currently selected option is an image, or null.
|
|
80
|
+
* @type {?SVGImageElement}
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
this.imageElement_ = null;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Tspan based arrow element.
|
|
87
|
+
* @type {?SVGTSpanElement}
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
this.arrow_ = null;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* SVG based arrow element.
|
|
94
|
+
* @type {?SVGElement}
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
this.svgArrow_ = null;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Serializable fields are saved by the serializer, non-serializable fields
|
|
101
|
+
* are not. Editable fields should also be serializable.
|
|
102
|
+
* @type {boolean}
|
|
103
|
+
*/
|
|
104
|
+
this.SERIALIZABLE = true;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Mouse cursor style when over the hotspot that initiates the editor.
|
|
108
|
+
* @type {string}
|
|
109
|
+
*/
|
|
110
|
+
this.CURSOR = 'default';
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// If we pass SKIP_SETUP, don't do *anything* with the menu generator.
|
|
114
|
+
if (menuGenerator === Field.SKIP_SETUP) return;
|
|
115
|
+
|
|
116
|
+
if (Array.isArray(menuGenerator)) {
|
|
117
|
+
validateOptions(menuGenerator);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* An array of options for a dropdown list,
|
|
122
|
+
* or a function which generates these options.
|
|
123
|
+
* @type {(!Array<!Array>|!function(this:FieldDropdown): !Array<!Array>)}
|
|
124
|
+
* @protected
|
|
125
|
+
*/
|
|
126
|
+
this.menuGenerator_ =
|
|
127
|
+
/**
|
|
128
|
+
* @type {(!Array<!Array>|
|
|
129
|
+
* !function(this:FieldDropdown):!Array<!Array>)}
|
|
130
|
+
*/
|
|
131
|
+
(menuGenerator);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A cache of the most recently generated options.
|
|
135
|
+
* @type {Array<!Array<string>>}
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
this.generatedOptions_ = null;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The prefix field label, of common words set after options are trimmed.
|
|
142
|
+
* @type {?string}
|
|
143
|
+
* @package
|
|
144
|
+
*/
|
|
145
|
+
this.prefixField = null;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The suffix field label, of common words set after options are trimmed.
|
|
149
|
+
* @type {?string}
|
|
150
|
+
* @package
|
|
151
|
+
*/
|
|
152
|
+
this.suffixField = null;
|
|
153
|
+
|
|
154
|
+
this.trimOptions_();
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The currently selected option. The field is initialized with the
|
|
158
|
+
* first option selected.
|
|
159
|
+
* @type {!Array<string|!ImageProperties>}
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
this.selectedOption_ = this.getOptions(false)[0];
|
|
163
|
+
|
|
164
|
+
if (opt_config) this.configure_(opt_config);
|
|
165
|
+
this.setValue(this.selectedOption_[1]);
|
|
166
|
+
if (opt_validator) this.setValidator(opt_validator);
|
|
167
|
+
}
|
|
67
168
|
|
|
68
169
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* @
|
|
170
|
+
* Sets the field's value based on the given XML element. Should only be
|
|
171
|
+
* called by Blockly.Xml.
|
|
172
|
+
* @param {!Element} fieldElement The element containing info about the
|
|
173
|
+
* field's state.
|
|
174
|
+
* @package
|
|
72
175
|
*/
|
|
73
|
-
|
|
176
|
+
fromXml(fieldElement) {
|
|
177
|
+
if (this.isOptionListDynamic()) {
|
|
178
|
+
this.getOptions(false);
|
|
179
|
+
}
|
|
180
|
+
this.setValue(fieldElement.textContent);
|
|
181
|
+
}
|
|
74
182
|
|
|
75
183
|
/**
|
|
76
|
-
*
|
|
77
|
-
* @
|
|
184
|
+
* Sets the field's value based on the given state.
|
|
185
|
+
* @param {*} state The state to apply to the dropdown field.
|
|
186
|
+
* @override
|
|
78
187
|
* @package
|
|
79
188
|
*/
|
|
80
|
-
|
|
189
|
+
loadState(state) {
|
|
190
|
+
if (this.loadLegacyState(FieldDropdown, state)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (this.isOptionListDynamic()) {
|
|
194
|
+
this.getOptions(false);
|
|
195
|
+
}
|
|
196
|
+
this.setValue(state);
|
|
197
|
+
}
|
|
81
198
|
|
|
82
199
|
/**
|
|
83
|
-
*
|
|
84
|
-
* @type {?string}
|
|
200
|
+
* Create the block UI for this dropdown.
|
|
85
201
|
* @package
|
|
86
202
|
*/
|
|
87
|
-
|
|
203
|
+
initView() {
|
|
204
|
+
if (this.shouldAddBorderRect_()) {
|
|
205
|
+
this.createBorderRect_();
|
|
206
|
+
} else {
|
|
207
|
+
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
|
|
208
|
+
}
|
|
209
|
+
this.createTextElement_();
|
|
88
210
|
|
|
89
|
-
|
|
211
|
+
this.imageElement_ = dom.createSvgElement(Svg.IMAGE, {}, this.fieldGroup_);
|
|
90
212
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
*/
|
|
97
|
-
this.selectedOption_ = this.getOptions(false)[0];
|
|
213
|
+
if (this.getConstants().FIELD_DROPDOWN_SVG_ARROW) {
|
|
214
|
+
this.createSVGArrow_();
|
|
215
|
+
} else {
|
|
216
|
+
this.createTextArrow_();
|
|
217
|
+
}
|
|
98
218
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
219
|
+
if (this.borderRect_) {
|
|
220
|
+
dom.addClass(this.borderRect_, 'blocklyDropdownRect');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
102
223
|
|
|
103
224
|
/**
|
|
104
|
-
*
|
|
105
|
-
* @
|
|
106
|
-
* @
|
|
225
|
+
* Whether or not the dropdown should add a border rect.
|
|
226
|
+
* @return {boolean} True if the dropdown field should add a border rect.
|
|
227
|
+
* @protected
|
|
107
228
|
*/
|
|
108
|
-
|
|
229
|
+
shouldAddBorderRect_() {
|
|
230
|
+
return !this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
|
231
|
+
(this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
|
|
232
|
+
!this.sourceBlock_.isShadow());
|
|
233
|
+
}
|
|
109
234
|
|
|
110
235
|
/**
|
|
111
|
-
*
|
|
112
|
-
* @type {?Menu}
|
|
236
|
+
* Create a tspan based arrow.
|
|
113
237
|
* @protected
|
|
114
238
|
*/
|
|
115
|
-
|
|
239
|
+
createTextArrow_() {
|
|
240
|
+
this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_);
|
|
241
|
+
this.arrow_.appendChild(document.createTextNode(
|
|
242
|
+
this.sourceBlock_.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
|
|
243
|
+
' ' + FieldDropdown.ARROW_CHAR));
|
|
244
|
+
if (this.sourceBlock_.RTL) {
|
|
245
|
+
this.textElement_.insertBefore(this.arrow_, this.textContent_);
|
|
246
|
+
} else {
|
|
247
|
+
this.textElement_.appendChild(this.arrow_);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
116
250
|
|
|
117
251
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @
|
|
120
|
-
* @private
|
|
252
|
+
* Create an SVG based arrow.
|
|
253
|
+
* @protected
|
|
121
254
|
*/
|
|
122
|
-
|
|
255
|
+
createSVGArrow_() {
|
|
256
|
+
this.svgArrow_ = dom.createSvgElement(
|
|
257
|
+
Svg.IMAGE, {
|
|
258
|
+
'height': this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
|
|
259
|
+
'width': this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
|
|
260
|
+
},
|
|
261
|
+
this.fieldGroup_);
|
|
262
|
+
this.svgArrow_.setAttributeNS(
|
|
263
|
+
dom.XLINK_NS, 'xlink:href',
|
|
264
|
+
this.getConstants().FIELD_DROPDOWN_SVG_ARROW_DATAURI);
|
|
265
|
+
}
|
|
123
266
|
|
|
124
267
|
/**
|
|
125
|
-
*
|
|
126
|
-
* @
|
|
127
|
-
*
|
|
268
|
+
* Create a dropdown menu under the text.
|
|
269
|
+
* @param {Event=} opt_e Optional mouse event that triggered the field to
|
|
270
|
+
* open, or undefined if triggered programmatically.
|
|
271
|
+
* @protected
|
|
128
272
|
*/
|
|
129
|
-
|
|
273
|
+
showEditor_(opt_e) {
|
|
274
|
+
this.dropdownCreate_();
|
|
275
|
+
if (opt_e && typeof opt_e.clientX === 'number') {
|
|
276
|
+
this.menu_.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY);
|
|
277
|
+
} else {
|
|
278
|
+
this.menu_.openingCoords = null;
|
|
279
|
+
}
|
|
130
280
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
281
|
+
// Remove any pre-existing elements in the dropdown.
|
|
282
|
+
dropDownDiv.clearContent();
|
|
283
|
+
// Element gets created in render.
|
|
284
|
+
this.menu_.render(dropDownDiv.getContentDiv());
|
|
285
|
+
const menuElement = /** @type {!Element} */ (this.menu_.getElement());
|
|
286
|
+
dom.addClass(menuElement, 'blocklyDropdownMenu');
|
|
287
|
+
|
|
288
|
+
if (this.getConstants().FIELD_DROPDOWN_COLOURED_DIV) {
|
|
289
|
+
const primaryColour = (this.sourceBlock_.isShadow()) ?
|
|
290
|
+
this.sourceBlock_.getParent().getColour() :
|
|
291
|
+
this.sourceBlock_.getColour();
|
|
292
|
+
const borderColour = (this.sourceBlock_.isShadow()) ?
|
|
293
|
+
this.sourceBlock_.getParent().style.colourTertiary :
|
|
294
|
+
this.sourceBlock_.style.colourTertiary;
|
|
295
|
+
dropDownDiv.setColour(primaryColour, borderColour);
|
|
296
|
+
}
|
|
139
297
|
|
|
140
|
-
|
|
141
|
-
* Dropdown image properties.
|
|
142
|
-
* @typedef {{
|
|
143
|
-
* src:string,
|
|
144
|
-
* alt:string,
|
|
145
|
-
* width:number,
|
|
146
|
-
* height:number
|
|
147
|
-
* }}
|
|
148
|
-
*/
|
|
149
|
-
FieldDropdown.ImageProperties;
|
|
298
|
+
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
|
|
150
299
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
* @package
|
|
156
|
-
* @nocollapse
|
|
157
|
-
*/
|
|
158
|
-
FieldDropdown.fromJson = function(options) {
|
|
159
|
-
// `this` might be a subclass of FieldDropdown if that class doesn't override
|
|
160
|
-
// the static fromJson method.
|
|
161
|
-
return new this(options['options'], undefined, options);
|
|
162
|
-
};
|
|
300
|
+
// Focusing needs to be handled after the menu is rendered and positioned.
|
|
301
|
+
// Otherwise it will cause a page scroll to get the misplaced menu in
|
|
302
|
+
// view. See issue #1329.
|
|
303
|
+
this.menu_.focus();
|
|
163
304
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
* @param {!Element} fieldElement The element containing info about the
|
|
168
|
-
* field's state.
|
|
169
|
-
* @package
|
|
170
|
-
*/
|
|
171
|
-
FieldDropdown.prototype.fromXml = function(fieldElement) {
|
|
172
|
-
if (this.isOptionListDynamic()) {
|
|
173
|
-
this.getOptions(false);
|
|
174
|
-
}
|
|
175
|
-
this.setValue(fieldElement.textContent);
|
|
176
|
-
};
|
|
305
|
+
if (this.selectedMenuItem_) {
|
|
306
|
+
this.menu_.setHighlighted(this.selectedMenuItem_);
|
|
307
|
+
}
|
|
177
308
|
|
|
178
|
-
|
|
179
|
-
* Sets the field's value based on the given state.
|
|
180
|
-
* @param {*} state The state to apply to the dropdown field.
|
|
181
|
-
* @override
|
|
182
|
-
* @package
|
|
183
|
-
*/
|
|
184
|
-
FieldDropdown.prototype.loadState = function(state) {
|
|
185
|
-
if (this.loadLegacyState(FieldDropdown, state)) {
|
|
186
|
-
return;
|
|
309
|
+
this.applyColour();
|
|
187
310
|
}
|
|
188
|
-
if (this.isOptionListDynamic()) {
|
|
189
|
-
this.getOptions(false);
|
|
190
|
-
}
|
|
191
|
-
this.setValue(state);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Serializable fields are saved by the XML renderer, non-serializable fields
|
|
196
|
-
* are not. Editable fields should also be serializable.
|
|
197
|
-
* @type {boolean}
|
|
198
|
-
*/
|
|
199
|
-
FieldDropdown.prototype.SERIALIZABLE = true;
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Horizontal distance that a checkmark overhangs the dropdown.
|
|
203
|
-
*/
|
|
204
|
-
FieldDropdown.CHECKMARK_OVERHANG = 25;
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Maximum height of the dropdown menu, as a percentage of the viewport height.
|
|
208
|
-
*/
|
|
209
|
-
FieldDropdown.MAX_MENU_HEIGHT_VH = 0.45;
|
|
210
311
|
|
|
211
|
-
/**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
} else {
|
|
244
|
-
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
|
|
312
|
+
/**
|
|
313
|
+
* Create the dropdown editor.
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
dropdownCreate_() {
|
|
317
|
+
const menu = new Menu();
|
|
318
|
+
menu.setRole(aria.Role.LISTBOX);
|
|
319
|
+
this.menu_ = menu;
|
|
320
|
+
|
|
321
|
+
const options = this.getOptions(false);
|
|
322
|
+
this.selectedMenuItem_ = null;
|
|
323
|
+
for (let i = 0; i < options.length; i++) {
|
|
324
|
+
let content = options[i][0]; // Human-readable text or image.
|
|
325
|
+
const value = options[i][1]; // Language-neutral value.
|
|
326
|
+
if (typeof content === 'object') {
|
|
327
|
+
// An image, not text.
|
|
328
|
+
const image = new Image(content['width'], content['height']);
|
|
329
|
+
image.src = content['src'];
|
|
330
|
+
image.alt = content['alt'] || '';
|
|
331
|
+
content = image;
|
|
332
|
+
}
|
|
333
|
+
const menuItem = new MenuItem(content, value);
|
|
334
|
+
menuItem.setRole(aria.Role.OPTION);
|
|
335
|
+
menuItem.setRightToLeft(this.sourceBlock_.RTL);
|
|
336
|
+
menuItem.setCheckable(true);
|
|
337
|
+
menu.addChild(menuItem);
|
|
338
|
+
menuItem.setChecked(value === this.value_);
|
|
339
|
+
if (value === this.value_) {
|
|
340
|
+
this.selectedMenuItem_ = menuItem;
|
|
341
|
+
}
|
|
342
|
+
menuItem.onAction(this.handleMenuActionEvent_, this);
|
|
343
|
+
}
|
|
245
344
|
}
|
|
246
|
-
this.createTextElement_();
|
|
247
345
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.
|
|
346
|
+
/**
|
|
347
|
+
* Disposes of events and DOM-references belonging to the dropdown editor.
|
|
348
|
+
* @private
|
|
349
|
+
*/
|
|
350
|
+
dropdownDispose_() {
|
|
351
|
+
if (this.menu_) {
|
|
352
|
+
this.menu_.dispose();
|
|
353
|
+
}
|
|
354
|
+
this.menu_ = null;
|
|
355
|
+
this.selectedMenuItem_ = null;
|
|
356
|
+
this.applyColour();
|
|
254
357
|
}
|
|
255
358
|
|
|
256
|
-
|
|
257
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Handle an action in the dropdown menu.
|
|
361
|
+
* @param {!MenuItem} menuItem The MenuItem selected within menu.
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
handleMenuActionEvent_(menuItem) {
|
|
365
|
+
dropDownDiv.hideIfOwner(this, true);
|
|
366
|
+
this.onItemSelected_(/** @type {!Menu} */ (this.menu_), menuItem);
|
|
258
367
|
}
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Whether or not the dropdown should add a border rect.
|
|
263
|
-
* @return {boolean} True if the dropdown field should add a border rect.
|
|
264
|
-
* @protected
|
|
265
|
-
*/
|
|
266
|
-
FieldDropdown.prototype.shouldAddBorderRect_ = function() {
|
|
267
|
-
return !this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
|
268
|
-
(this.getConstants().FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
|
|
269
|
-
!this.sourceBlock_.isShadow());
|
|
270
|
-
};
|
|
271
368
|
|
|
272
|
-
/**
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
' ' + FieldDropdown.ARROW_CHAR));
|
|
281
|
-
if (this.sourceBlock_.RTL) {
|
|
282
|
-
this.textElement_.insertBefore(this.arrow_, this.textContent_);
|
|
283
|
-
} else {
|
|
284
|
-
this.textElement_.appendChild(this.arrow_);
|
|
369
|
+
/**
|
|
370
|
+
* Handle the selection of an item in the dropdown menu.
|
|
371
|
+
* @param {!Menu} menu The Menu component clicked.
|
|
372
|
+
* @param {!MenuItem} menuItem The MenuItem selected within menu.
|
|
373
|
+
* @protected
|
|
374
|
+
*/
|
|
375
|
+
onItemSelected_(menu, menuItem) {
|
|
376
|
+
this.setValue(menuItem.getValue());
|
|
285
377
|
}
|
|
286
|
-
};
|
|
287
378
|
|
|
288
|
-
/**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Factor out common words in statically defined options.
|
|
381
|
+
* Create prefix and/or suffix labels.
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
trimOptions_() {
|
|
385
|
+
const options = this.menuGenerator_;
|
|
386
|
+
if (!Array.isArray(options)) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
let hasImages = false;
|
|
390
|
+
|
|
391
|
+
// Localize label text and image alt text.
|
|
392
|
+
for (let i = 0; i < options.length; i++) {
|
|
393
|
+
const label = options[i][0];
|
|
394
|
+
if (typeof label === 'string') {
|
|
395
|
+
options[i][0] = parsing.replaceMessageReferences(label);
|
|
396
|
+
} else {
|
|
397
|
+
if (label.alt !== null) {
|
|
398
|
+
options[i][0].alt = parsing.replaceMessageReferences(label.alt);
|
|
399
|
+
}
|
|
400
|
+
hasImages = true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (hasImages || options.length < 2) {
|
|
404
|
+
return; // Do nothing if too few items or at least one label is an image.
|
|
405
|
+
}
|
|
406
|
+
const strings = [];
|
|
407
|
+
for (let i = 0; i < options.length; i++) {
|
|
408
|
+
strings.push(options[i][0]);
|
|
409
|
+
}
|
|
410
|
+
const shortest = utilsString.shortestStringLength(strings);
|
|
411
|
+
const prefixLength = utilsString.commonWordPrefix(strings, shortest);
|
|
412
|
+
const suffixLength = utilsString.commonWordSuffix(strings, shortest);
|
|
413
|
+
if (!prefixLength && !suffixLength) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (shortest <= prefixLength + suffixLength) {
|
|
417
|
+
// One or more strings will entirely vanish if we proceed. Abort.
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (prefixLength) {
|
|
421
|
+
this.prefixField = strings[0].substring(0, prefixLength - 1);
|
|
422
|
+
}
|
|
423
|
+
if (suffixLength) {
|
|
424
|
+
this.suffixField = strings[0].substr(1 - suffixLength);
|
|
425
|
+
}
|
|
303
426
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
|
|
307
|
-
* or undefined if triggered programmatically.
|
|
308
|
-
* @protected
|
|
309
|
-
*/
|
|
310
|
-
FieldDropdown.prototype.showEditor_ = function(opt_e) {
|
|
311
|
-
this.dropdownCreate_();
|
|
312
|
-
if (opt_e && typeof opt_e.clientX === 'number') {
|
|
313
|
-
this.menu_.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY);
|
|
314
|
-
} else {
|
|
315
|
-
this.menu_.openingCoords = null;
|
|
427
|
+
this.menuGenerator_ =
|
|
428
|
+
FieldDropdown.applyTrim_(options, prefixLength, suffixLength);
|
|
316
429
|
}
|
|
317
430
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (this.getConstants().FIELD_DROPDOWN_COLOURED_DIV) {
|
|
326
|
-
const primaryColour = (this.sourceBlock_.isShadow()) ?
|
|
327
|
-
this.sourceBlock_.getParent().getColour() :
|
|
328
|
-
this.sourceBlock_.getColour();
|
|
329
|
-
const borderColour = (this.sourceBlock_.isShadow()) ?
|
|
330
|
-
this.sourceBlock_.getParent().style.colourTertiary :
|
|
331
|
-
this.sourceBlock_.style.colourTertiary;
|
|
332
|
-
DropDownDiv.setColour(primaryColour, borderColour);
|
|
431
|
+
/**
|
|
432
|
+
* @return {boolean} True if the option list is generated by a function.
|
|
433
|
+
* Otherwise false.
|
|
434
|
+
*/
|
|
435
|
+
isOptionListDynamic() {
|
|
436
|
+
return typeof this.menuGenerator_ === 'function';
|
|
333
437
|
}
|
|
334
438
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
439
|
+
/**
|
|
440
|
+
* Return a list of the options for this dropdown.
|
|
441
|
+
* @param {boolean=} opt_useCache For dynamic options, whether or not to use
|
|
442
|
+
* the cached options or to re-generate them.
|
|
443
|
+
* @return {!Array<!Array>} A non-empty array of option tuples:
|
|
444
|
+
* (human-readable text or image, language-neutral name).
|
|
445
|
+
* @throws {TypeError} If generated options are incorrectly structured.
|
|
446
|
+
*/
|
|
447
|
+
getOptions(opt_useCache) {
|
|
448
|
+
if (this.isOptionListDynamic()) {
|
|
449
|
+
if (!this.generatedOptions_ || !opt_useCache) {
|
|
450
|
+
this.generatedOptions_ = this.menuGenerator_.call(this);
|
|
451
|
+
validateOptions(this.generatedOptions_);
|
|
452
|
+
}
|
|
453
|
+
return this.generatedOptions_;
|
|
454
|
+
}
|
|
455
|
+
return /** @type {!Array<!Array<string>>} */ (this.menuGenerator_);
|
|
344
456
|
}
|
|
345
457
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
menuItem.setRightToLeft(this.sourceBlock_.RTL);
|
|
373
|
-
menuItem.setCheckable(true);
|
|
374
|
-
menu.addChild(menuItem);
|
|
375
|
-
menuItem.setChecked(value === this.value_);
|
|
376
|
-
if (value === this.value_) {
|
|
377
|
-
this.selectedMenuItem_ = menuItem;
|
|
378
|
-
}
|
|
379
|
-
menuItem.onAction(this.handleMenuActionEvent_, this);
|
|
458
|
+
/**
|
|
459
|
+
* Ensure that the input value is a valid language-neutral option.
|
|
460
|
+
* @param {*=} opt_newValue The input value.
|
|
461
|
+
* @return {?string} A valid language-neutral option, or null if invalid.
|
|
462
|
+
* @protected
|
|
463
|
+
*/
|
|
464
|
+
doClassValidation_(opt_newValue) {
|
|
465
|
+
let isValueValid = false;
|
|
466
|
+
const options = this.getOptions(true);
|
|
467
|
+
for (let i = 0, option; (option = options[i]); i++) {
|
|
468
|
+
// Options are tuples of human-readable text and language-neutral values.
|
|
469
|
+
if (option[1] === opt_newValue) {
|
|
470
|
+
isValueValid = true;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (!isValueValid) {
|
|
475
|
+
if (this.sourceBlock_) {
|
|
476
|
+
console.warn(
|
|
477
|
+
'Cannot set the dropdown\'s value to an unavailable option.' +
|
|
478
|
+
' Block type: ' + this.sourceBlock_.type +
|
|
479
|
+
', Field name: ' + this.name + ', Value: ' + opt_newValue);
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
return /** @type {string} */ (opt_newValue);
|
|
380
484
|
}
|
|
381
|
-
};
|
|
382
485
|
|
|
383
|
-
/**
|
|
384
|
-
|
|
385
|
-
*
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
486
|
+
/**
|
|
487
|
+
* Update the value of this dropdown field.
|
|
488
|
+
* @param {*} newValue The value to be saved. The default validator guarantees
|
|
489
|
+
* that this is one of the valid dropdown options.
|
|
490
|
+
* @protected
|
|
491
|
+
*/
|
|
492
|
+
doValueUpdate_(newValue) {
|
|
493
|
+
super.doValueUpdate_(newValue);
|
|
494
|
+
const options = this.getOptions(true);
|
|
495
|
+
for (let i = 0, option; (option = options[i]); i++) {
|
|
496
|
+
if (option[1] === this.value_) {
|
|
497
|
+
this.selectedOption_ = option;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
390
500
|
}
|
|
391
|
-
this.menu_ = null;
|
|
392
|
-
this.selectedMenuItem_ = null;
|
|
393
|
-
this.applyColour();
|
|
394
|
-
};
|
|
395
501
|
|
|
396
|
-
/**
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
502
|
+
/**
|
|
503
|
+
* Updates the dropdown arrow to match the colour/style of the block.
|
|
504
|
+
* @package
|
|
505
|
+
*/
|
|
506
|
+
applyColour() {
|
|
507
|
+
if (this.borderRect_) {
|
|
508
|
+
this.borderRect_.setAttribute(
|
|
509
|
+
'stroke', this.sourceBlock_.style.colourTertiary);
|
|
510
|
+
if (this.menu_) {
|
|
511
|
+
this.borderRect_.setAttribute(
|
|
512
|
+
'fill', this.sourceBlock_.style.colourTertiary);
|
|
513
|
+
} else {
|
|
514
|
+
this.borderRect_.setAttribute('fill', 'transparent');
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Update arrow's colour.
|
|
518
|
+
if (this.sourceBlock_ && this.arrow_) {
|
|
519
|
+
if (this.sourceBlock_.isShadow()) {
|
|
520
|
+
this.arrow_.style.fill = this.sourceBlock_.style.colourSecondary;
|
|
521
|
+
} else {
|
|
522
|
+
this.arrow_.style.fill = this.sourceBlock_.style.colourPrimary;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
405
526
|
|
|
406
|
-
/**
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
527
|
+
/**
|
|
528
|
+
* Draws the border with the correct width.
|
|
529
|
+
* @protected
|
|
530
|
+
*/
|
|
531
|
+
render_() {
|
|
532
|
+
// Hide both elements.
|
|
533
|
+
this.textContent_.nodeValue = '';
|
|
534
|
+
this.imageElement_.style.display = 'none';
|
|
535
|
+
|
|
536
|
+
// Show correct element.
|
|
537
|
+
const option = this.selectedOption_ && this.selectedOption_[0];
|
|
538
|
+
if (option && typeof option === 'object') {
|
|
539
|
+
this.renderSelectedImage_(
|
|
540
|
+
/** @type {!ImageProperties} */ (option));
|
|
541
|
+
} else {
|
|
542
|
+
this.renderSelectedText_();
|
|
543
|
+
}
|
|
415
544
|
|
|
416
|
-
|
|
417
|
-
* Factor out common words in statically defined options.
|
|
418
|
-
* Create prefix and/or suffix labels.
|
|
419
|
-
* @private
|
|
420
|
-
*/
|
|
421
|
-
FieldDropdown.prototype.trimOptions_ = function() {
|
|
422
|
-
const options = this.menuGenerator_;
|
|
423
|
-
if (!Array.isArray(options)) {
|
|
424
|
-
return;
|
|
545
|
+
this.positionBorderRect_();
|
|
425
546
|
}
|
|
426
|
-
let hasImages = false;
|
|
427
547
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
548
|
+
/**
|
|
549
|
+
* Renders the selected option, which must be an image.
|
|
550
|
+
* @param {!ImageProperties} imageJson Selected
|
|
551
|
+
* option that must be an image.
|
|
552
|
+
* @private
|
|
553
|
+
*/
|
|
554
|
+
renderSelectedImage_(imageJson) {
|
|
555
|
+
this.imageElement_.style.display = '';
|
|
556
|
+
this.imageElement_.setAttributeNS(
|
|
557
|
+
dom.XLINK_NS, 'xlink:href', imageJson.src);
|
|
558
|
+
this.imageElement_.setAttribute('height', imageJson.height);
|
|
559
|
+
this.imageElement_.setAttribute('width', imageJson.width);
|
|
560
|
+
|
|
561
|
+
const imageHeight = Number(imageJson.height);
|
|
562
|
+
const imageWidth = Number(imageJson.width);
|
|
563
|
+
|
|
564
|
+
// Height and width include the border rect.
|
|
565
|
+
const hasBorder = !!this.borderRect_;
|
|
566
|
+
const height = Math.max(
|
|
567
|
+
hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
|
|
568
|
+
imageHeight + IMAGE_Y_PADDING);
|
|
569
|
+
const xPadding =
|
|
570
|
+
hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0;
|
|
571
|
+
let arrowWidth = 0;
|
|
572
|
+
if (this.svgArrow_) {
|
|
573
|
+
arrowWidth = this.positionSVGArrow_(
|
|
574
|
+
imageWidth + xPadding,
|
|
575
|
+
height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
|
|
433
576
|
} else {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
577
|
+
arrowWidth = dom.getFastTextWidth(
|
|
578
|
+
/** @type {!SVGTSpanElement} */ (this.arrow_),
|
|
579
|
+
this.getConstants().FIELD_TEXT_FONTSIZE,
|
|
580
|
+
this.getConstants().FIELD_TEXT_FONTWEIGHT,
|
|
581
|
+
this.getConstants().FIELD_TEXT_FONTFAMILY);
|
|
438
582
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return; // Do nothing if too few items or at least one label is an image.
|
|
442
|
-
}
|
|
443
|
-
const strings = [];
|
|
444
|
-
for (let i = 0; i < options.length; i++) {
|
|
445
|
-
strings.push(options[i][0]);
|
|
446
|
-
}
|
|
447
|
-
const shortest = utilsString.shortestStringLength(strings);
|
|
448
|
-
const prefixLength = utilsString.commonWordPrefix(strings, shortest);
|
|
449
|
-
const suffixLength = utilsString.commonWordSuffix(strings, shortest);
|
|
450
|
-
if (!prefixLength && !suffixLength) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (shortest <= prefixLength + suffixLength) {
|
|
454
|
-
// One or more strings will entirely vanish if we proceed. Abort.
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
if (prefixLength) {
|
|
458
|
-
this.prefixField = strings[0].substring(0, prefixLength - 1);
|
|
459
|
-
}
|
|
460
|
-
if (suffixLength) {
|
|
461
|
-
this.suffixField = strings[0].substr(1 - suffixLength);
|
|
462
|
-
}
|
|
583
|
+
this.size_.width = imageWidth + arrowWidth + xPadding * 2;
|
|
584
|
+
this.size_.height = height;
|
|
463
585
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
586
|
+
let arrowX = 0;
|
|
587
|
+
if (this.sourceBlock_.RTL) {
|
|
588
|
+
const imageX = xPadding + arrowWidth;
|
|
589
|
+
this.imageElement_.setAttribute('x', imageX);
|
|
590
|
+
} else {
|
|
591
|
+
arrowX = imageWidth + arrowWidth;
|
|
592
|
+
this.textElement_.setAttribute('text-anchor', 'end');
|
|
593
|
+
this.imageElement_.setAttribute('x', xPadding);
|
|
594
|
+
}
|
|
595
|
+
this.imageElement_.setAttribute('y', height / 2 - imageHeight / 2);
|
|
467
596
|
|
|
468
|
-
|
|
469
|
-
* Use the calculated prefix and suffix lengths to trim all of the options in
|
|
470
|
-
* the given array.
|
|
471
|
-
* @param {!Array<!Array>} options Array of option tuples:
|
|
472
|
-
* (human-readable text or image, language-neutral name).
|
|
473
|
-
* @param {number} prefixLength The length of the common prefix.
|
|
474
|
-
* @param {number} suffixLength The length of the common suffix
|
|
475
|
-
* @return {!Array<!Array>} A new array with all of the option text trimmed.
|
|
476
|
-
*/
|
|
477
|
-
FieldDropdown.applyTrim_ = function(options, prefixLength, suffixLength) {
|
|
478
|
-
const newOptions = [];
|
|
479
|
-
// Remove the prefix and suffix from the options.
|
|
480
|
-
for (let i = 0; i < options.length; i++) {
|
|
481
|
-
let text = options[i][0];
|
|
482
|
-
const value = options[i][1];
|
|
483
|
-
text = text.substring(prefixLength, text.length - suffixLength);
|
|
484
|
-
newOptions[i] = [text, value];
|
|
597
|
+
this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth);
|
|
485
598
|
}
|
|
486
|
-
return newOptions;
|
|
487
|
-
};
|
|
488
599
|
|
|
489
|
-
/**
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
600
|
+
/**
|
|
601
|
+
* Renders the selected option, which must be text.
|
|
602
|
+
* @private
|
|
603
|
+
*/
|
|
604
|
+
renderSelectedText_() {
|
|
605
|
+
// Retrieves the selected option to display through getText_.
|
|
606
|
+
this.textContent_.nodeValue = this.getDisplayText_();
|
|
607
|
+
dom.addClass(
|
|
608
|
+
/** @type {!Element} */ (this.textElement_), 'blocklyDropdownText');
|
|
609
|
+
this.textElement_.setAttribute('text-anchor', 'start');
|
|
610
|
+
|
|
611
|
+
// Height and width include the border rect.
|
|
612
|
+
const hasBorder = !!this.borderRect_;
|
|
613
|
+
const height = Math.max(
|
|
614
|
+
hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
|
|
615
|
+
this.getConstants().FIELD_TEXT_HEIGHT);
|
|
616
|
+
const textWidth = dom.getFastTextWidth(
|
|
617
|
+
this.textElement_, this.getConstants().FIELD_TEXT_FONTSIZE,
|
|
618
|
+
this.getConstants().FIELD_TEXT_FONTWEIGHT,
|
|
619
|
+
this.getConstants().FIELD_TEXT_FONTFAMILY);
|
|
620
|
+
const xPadding =
|
|
621
|
+
hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0;
|
|
622
|
+
let arrowWidth = 0;
|
|
623
|
+
if (this.svgArrow_) {
|
|
624
|
+
arrowWidth = this.positionSVGArrow_(
|
|
625
|
+
textWidth + xPadding,
|
|
626
|
+
height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
|
|
510
627
|
}
|
|
511
|
-
|
|
628
|
+
this.size_.width = textWidth + arrowWidth + xPadding * 2;
|
|
629
|
+
this.size_.height = height;
|
|
630
|
+
|
|
631
|
+
this.positionTextElement_(xPadding, textWidth);
|
|
512
632
|
}
|
|
513
|
-
return /** @type {!Array<!Array<string>>} */ (this.menuGenerator_);
|
|
514
|
-
};
|
|
515
633
|
|
|
516
|
-
/**
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
// Options are tuples of human-readable text and language-neutral values.
|
|
527
|
-
if (option[1] === opt_newValue) {
|
|
528
|
-
isValueValid = true;
|
|
529
|
-
break;
|
|
634
|
+
/**
|
|
635
|
+
* Position a drop-down arrow at the appropriate location at render-time.
|
|
636
|
+
* @param {number} x X position the arrow is being rendered at, in px.
|
|
637
|
+
* @param {number} y Y position the arrow is being rendered at, in px.
|
|
638
|
+
* @return {number} Amount of space the arrow is taking up, in px.
|
|
639
|
+
* @private
|
|
640
|
+
*/
|
|
641
|
+
positionSVGArrow_(x, y) {
|
|
642
|
+
if (!this.svgArrow_) {
|
|
643
|
+
return 0;
|
|
530
644
|
}
|
|
645
|
+
const hasBorder = !!this.borderRect_;
|
|
646
|
+
const xPadding =
|
|
647
|
+
hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0;
|
|
648
|
+
const textPadding = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_PADDING;
|
|
649
|
+
const svgArrowSize = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE;
|
|
650
|
+
const arrowX = this.sourceBlock_.RTL ? xPadding : x + textPadding;
|
|
651
|
+
this.svgArrow_.setAttribute(
|
|
652
|
+
'transform', 'translate(' + arrowX + ',' + y + ')');
|
|
653
|
+
return svgArrowSize + textPadding;
|
|
531
654
|
}
|
|
532
|
-
if (!isValueValid) {
|
|
533
|
-
if (this.sourceBlock_) {
|
|
534
|
-
console.warn(
|
|
535
|
-
'Cannot set the dropdown\'s value to an unavailable option.' +
|
|
536
|
-
' Block type: ' + this.sourceBlock_.type +
|
|
537
|
-
', Field name: ' + this.name + ', Value: ' + opt_newValue);
|
|
538
|
-
}
|
|
539
|
-
return null;
|
|
540
|
-
}
|
|
541
|
-
return /** @type {string} */ (opt_newValue);
|
|
542
|
-
};
|
|
543
655
|
|
|
544
|
-
/**
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
656
|
+
/**
|
|
657
|
+
* Use the `getText_` developer hook to override the field's text
|
|
658
|
+
* representation. Get the selected option text. If the selected option is an
|
|
659
|
+
* image we return the image alt text.
|
|
660
|
+
* @return {?string} Selected option text.
|
|
661
|
+
* @protected
|
|
662
|
+
* @override
|
|
663
|
+
*/
|
|
664
|
+
getText_() {
|
|
665
|
+
if (!this.selectedOption_) {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
const option = this.selectedOption_[0];
|
|
669
|
+
if (typeof option === 'object') {
|
|
670
|
+
return option['alt'];
|
|
556
671
|
}
|
|
672
|
+
return option;
|
|
557
673
|
}
|
|
558
|
-
};
|
|
559
674
|
|
|
560
|
-
/**
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
} else {
|
|
572
|
-
this.borderRect_.setAttribute('fill', 'transparent');
|
|
573
|
-
}
|
|
675
|
+
/**
|
|
676
|
+
* Construct a FieldDropdown from a JSON arg object.
|
|
677
|
+
* @param {!Object} options A JSON object with options (options).
|
|
678
|
+
* @return {!FieldDropdown} The new field instance.
|
|
679
|
+
* @package
|
|
680
|
+
* @nocollapse
|
|
681
|
+
*/
|
|
682
|
+
static fromJson(options) {
|
|
683
|
+
// `this` might be a subclass of FieldDropdown if that class doesn't
|
|
684
|
+
// override the static fromJson method.
|
|
685
|
+
return new this(options['options'], undefined, options);
|
|
574
686
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Use the calculated prefix and suffix lengths to trim all of the options in
|
|
690
|
+
* the given array.
|
|
691
|
+
* @param {!Array<!Array>} options Array of option tuples:
|
|
692
|
+
* (human-readable text or image, language-neutral name).
|
|
693
|
+
* @param {number} prefixLength The length of the common prefix.
|
|
694
|
+
* @param {number} suffixLength The length of the common suffix
|
|
695
|
+
* @return {!Array<!Array>} A new array with all of the option text trimmed.
|
|
696
|
+
*/
|
|
697
|
+
static applyTrim_(options, prefixLength, suffixLength) {
|
|
698
|
+
const newOptions = [];
|
|
699
|
+
// Remove the prefix and suffix from the options.
|
|
700
|
+
for (let i = 0; i < options.length; i++) {
|
|
701
|
+
let text = options[i][0];
|
|
702
|
+
const value = options[i][1];
|
|
703
|
+
text = text.substring(prefixLength, text.length - suffixLength);
|
|
704
|
+
newOptions[i] = [text, value];
|
|
581
705
|
}
|
|
706
|
+
return newOptions;
|
|
582
707
|
}
|
|
583
|
-
}
|
|
708
|
+
}
|
|
584
709
|
|
|
585
710
|
/**
|
|
586
|
-
*
|
|
587
|
-
* @
|
|
711
|
+
* Dropdown image properties.
|
|
712
|
+
* @typedef {{
|
|
713
|
+
* src:string,
|
|
714
|
+
* alt:string,
|
|
715
|
+
* width:number,
|
|
716
|
+
* height:number
|
|
717
|
+
* }}
|
|
588
718
|
*/
|
|
589
|
-
|
|
590
|
-
// Hide both elements.
|
|
591
|
-
this.textContent_.nodeValue = '';
|
|
592
|
-
this.imageElement_.style.display = 'none';
|
|
593
|
-
|
|
594
|
-
// Show correct element.
|
|
595
|
-
const option = this.selectedOption_ && this.selectedOption_[0];
|
|
596
|
-
if (option && typeof option === 'object') {
|
|
597
|
-
this.renderSelectedImage_(
|
|
598
|
-
/** @type {!FieldDropdown.ImageProperties} */ (option));
|
|
599
|
-
} else {
|
|
600
|
-
this.renderSelectedText_();
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
this.positionBorderRect_();
|
|
604
|
-
};
|
|
719
|
+
let ImageProperties; // eslint-disable-line no-unused-vars
|
|
605
720
|
|
|
606
721
|
/**
|
|
607
|
-
*
|
|
608
|
-
* @param {!FieldDropdown.ImageProperties} imageJson Selected
|
|
609
|
-
* option that must be an image.
|
|
610
|
-
* @private
|
|
722
|
+
* Horizontal distance that a checkmark overhangs the dropdown.
|
|
611
723
|
*/
|
|
612
|
-
FieldDropdown.
|
|
613
|
-
this.imageElement_.style.display = '';
|
|
614
|
-
this.imageElement_.setAttributeNS(dom.XLINK_NS, 'xlink:href', imageJson.src);
|
|
615
|
-
this.imageElement_.setAttribute('height', imageJson.height);
|
|
616
|
-
this.imageElement_.setAttribute('width', imageJson.width);
|
|
617
|
-
|
|
618
|
-
const imageHeight = Number(imageJson.height);
|
|
619
|
-
const imageWidth = Number(imageJson.width);
|
|
620
|
-
|
|
621
|
-
// Height and width include the border rect.
|
|
622
|
-
const hasBorder = !!this.borderRect_;
|
|
623
|
-
const height = Math.max(
|
|
624
|
-
hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
|
|
625
|
-
imageHeight + IMAGE_Y_PADDING);
|
|
626
|
-
const xPadding =
|
|
627
|
-
hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0;
|
|
628
|
-
let arrowWidth = 0;
|
|
629
|
-
if (this.svgArrow_) {
|
|
630
|
-
arrowWidth = this.positionSVGArrow_(
|
|
631
|
-
imageWidth + xPadding,
|
|
632
|
-
height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
|
|
633
|
-
} else {
|
|
634
|
-
arrowWidth = dom.getFastTextWidth(
|
|
635
|
-
/** @type {!SVGTSpanElement} */ (this.arrow_),
|
|
636
|
-
this.getConstants().FIELD_TEXT_FONTSIZE,
|
|
637
|
-
this.getConstants().FIELD_TEXT_FONTWEIGHT,
|
|
638
|
-
this.getConstants().FIELD_TEXT_FONTFAMILY);
|
|
639
|
-
}
|
|
640
|
-
this.size_.width = imageWidth + arrowWidth + xPadding * 2;
|
|
641
|
-
this.size_.height = height;
|
|
642
|
-
|
|
643
|
-
let arrowX = 0;
|
|
644
|
-
if (this.sourceBlock_.RTL) {
|
|
645
|
-
const imageX = xPadding + arrowWidth;
|
|
646
|
-
this.imageElement_.setAttribute('x', imageX);
|
|
647
|
-
} else {
|
|
648
|
-
arrowX = imageWidth + arrowWidth;
|
|
649
|
-
this.textElement_.setAttribute('text-anchor', 'end');
|
|
650
|
-
this.imageElement_.setAttribute('x', xPadding);
|
|
651
|
-
}
|
|
652
|
-
this.imageElement_.setAttribute('y', height / 2 - imageHeight / 2);
|
|
653
|
-
|
|
654
|
-
this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth);
|
|
655
|
-
};
|
|
724
|
+
FieldDropdown.CHECKMARK_OVERHANG = 25;
|
|
656
725
|
|
|
657
726
|
/**
|
|
658
|
-
*
|
|
659
|
-
* @private
|
|
727
|
+
* Maximum height of the dropdown menu, as a percentage of the viewport height.
|
|
660
728
|
*/
|
|
661
|
-
FieldDropdown.
|
|
662
|
-
// Retrieves the selected option to display through getText_.
|
|
663
|
-
this.textContent_.nodeValue = this.getDisplayText_();
|
|
664
|
-
dom.addClass(
|
|
665
|
-
/** @type {!Element} */ (this.textElement_), 'blocklyDropdownText');
|
|
666
|
-
this.textElement_.setAttribute('text-anchor', 'start');
|
|
667
|
-
|
|
668
|
-
// Height and width include the border rect.
|
|
669
|
-
const hasBorder = !!this.borderRect_;
|
|
670
|
-
const height = Math.max(
|
|
671
|
-
hasBorder ? this.getConstants().FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
|
|
672
|
-
this.getConstants().FIELD_TEXT_HEIGHT);
|
|
673
|
-
const textWidth = dom.getFastTextWidth(
|
|
674
|
-
this.textElement_, this.getConstants().FIELD_TEXT_FONTSIZE,
|
|
675
|
-
this.getConstants().FIELD_TEXT_FONTWEIGHT,
|
|
676
|
-
this.getConstants().FIELD_TEXT_FONTFAMILY);
|
|
677
|
-
const xPadding =
|
|
678
|
-
hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0;
|
|
679
|
-
let arrowWidth = 0;
|
|
680
|
-
if (this.svgArrow_) {
|
|
681
|
-
arrowWidth = this.positionSVGArrow_(
|
|
682
|
-
textWidth + xPadding,
|
|
683
|
-
height / 2 - this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
|
|
684
|
-
}
|
|
685
|
-
this.size_.width = textWidth + arrowWidth + xPadding * 2;
|
|
686
|
-
this.size_.height = height;
|
|
729
|
+
FieldDropdown.MAX_MENU_HEIGHT_VH = 0.45;
|
|
687
730
|
|
|
688
|
-
|
|
689
|
-
|
|
731
|
+
/**
|
|
732
|
+
* The y offset from the top of the field to the top of the image, if an image
|
|
733
|
+
* is selected.
|
|
734
|
+
* @type {number}
|
|
735
|
+
* @const
|
|
736
|
+
*/
|
|
737
|
+
const IMAGE_Y_OFFSET = 5;
|
|
690
738
|
|
|
691
739
|
/**
|
|
692
|
-
*
|
|
693
|
-
* @
|
|
694
|
-
* @
|
|
695
|
-
* @return {number} Amount of space the arrow is taking up, in px.
|
|
696
|
-
* @private
|
|
740
|
+
* The total vertical padding above and below an image.
|
|
741
|
+
* @type {number}
|
|
742
|
+
* @const
|
|
697
743
|
*/
|
|
698
|
-
|
|
699
|
-
if (!this.svgArrow_) {
|
|
700
|
-
return 0;
|
|
701
|
-
}
|
|
702
|
-
const hasBorder = !!this.borderRect_;
|
|
703
|
-
const xPadding =
|
|
704
|
-
hasBorder ? this.getConstants().FIELD_BORDER_RECT_X_PADDING : 0;
|
|
705
|
-
const textPadding = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_PADDING;
|
|
706
|
-
const svgArrowSize = this.getConstants().FIELD_DROPDOWN_SVG_ARROW_SIZE;
|
|
707
|
-
const arrowX = this.sourceBlock_.RTL ? xPadding : x + textPadding;
|
|
708
|
-
this.svgArrow_.setAttribute(
|
|
709
|
-
'transform', 'translate(' + arrowX + ',' + y + ')');
|
|
710
|
-
return svgArrowSize + textPadding;
|
|
711
|
-
};
|
|
744
|
+
const IMAGE_Y_PADDING = IMAGE_Y_OFFSET * 2;
|
|
712
745
|
|
|
713
746
|
/**
|
|
714
|
-
*
|
|
715
|
-
* representation. Get the selected option text. If the selected option is an
|
|
716
|
-
* image we return the image alt text.
|
|
717
|
-
* @return {?string} Selected option text.
|
|
718
|
-
* @protected
|
|
719
|
-
* @override
|
|
747
|
+
* Android can't (in 2014) display "▾", so use "▼" instead.
|
|
720
748
|
*/
|
|
721
|
-
FieldDropdown.
|
|
722
|
-
if (!this.selectedOption_) {
|
|
723
|
-
return null;
|
|
724
|
-
}
|
|
725
|
-
const option = this.selectedOption_[0];
|
|
726
|
-
if (typeof option === 'object') {
|
|
727
|
-
return option['alt'];
|
|
728
|
-
}
|
|
729
|
-
return option;
|
|
730
|
-
};
|
|
749
|
+
FieldDropdown.ARROW_CHAR = userAgent.ANDROID ? '\u25BC' : '\u25BE';
|
|
731
750
|
|
|
732
751
|
/**
|
|
733
752
|
* Validates the data structure to be processed as an options list.
|