@khanacademy/wonder-blocks-dropdown 2.3.19

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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/dist/es/index.js +3403 -0
  3. package/dist/index.js +3966 -0
  4. package/dist/index.js.flow +2 -0
  5. package/docs.md +12 -0
  6. package/package.json +44 -0
  7. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +4054 -0
  8. package/src/__tests__/generated-snapshot.test.js +1612 -0
  9. package/src/__tests__/index.test.js +23 -0
  10. package/src/components/__mocks__/dropdown-core-virtualized.js +40 -0
  11. package/src/components/__tests__/__snapshots__/action-item.test.js.snap +63 -0
  12. package/src/components/__tests__/action-item.test.js +43 -0
  13. package/src/components/__tests__/action-menu.test.js +544 -0
  14. package/src/components/__tests__/dropdown-core-virtualized.test.js +119 -0
  15. package/src/components/__tests__/dropdown-core.test.js +659 -0
  16. package/src/components/__tests__/multi-select.test.js +982 -0
  17. package/src/components/__tests__/search-text-input.test.js +144 -0
  18. package/src/components/__tests__/single-select.test.js +588 -0
  19. package/src/components/action-item.js +270 -0
  20. package/src/components/action-menu-opener-core.js +203 -0
  21. package/src/components/action-menu.js +300 -0
  22. package/src/components/action-menu.md +338 -0
  23. package/src/components/check.js +59 -0
  24. package/src/components/checkbox.js +111 -0
  25. package/src/components/dropdown-core-virtualized-item.js +62 -0
  26. package/src/components/dropdown-core-virtualized.js +246 -0
  27. package/src/components/dropdown-core.js +770 -0
  28. package/src/components/dropdown-opener.js +101 -0
  29. package/src/components/multi-select.js +597 -0
  30. package/src/components/multi-select.md +718 -0
  31. package/src/components/multi-select.stories.js +111 -0
  32. package/src/components/option-item.js +239 -0
  33. package/src/components/search-text-input.js +227 -0
  34. package/src/components/select-opener.js +297 -0
  35. package/src/components/separator-item.js +50 -0
  36. package/src/components/single-select.js +418 -0
  37. package/src/components/single-select.md +520 -0
  38. package/src/components/single-select.stories.js +107 -0
  39. package/src/index.js +20 -0
  40. package/src/util/constants.js +50 -0
  41. package/src/util/types.js +32 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Khan Academy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3403 @@
1
+ import { createElement, Fragment, Component, cloneElement, Children, createRef } from 'react';
2
+ import { StyleSheet, css } from 'aphrodite';
3
+ import { Link } from 'react-router-dom';
4
+ import { any } from 'prop-types';
5
+ import Color, { mix, fade, SemanticColor } from '@khanacademy/wonder-blocks-color';
6
+ import Spacing from '@khanacademy/wonder-blocks-spacing';
7
+ import { LabelMedium, styles as styles$a, LabelLarge } from '@khanacademy/wonder-blocks-typography';
8
+ import { getClickableBehavior, isClientSideUrl, ClickableBehavior } from '@khanacademy/wonder-blocks-clickable';
9
+ import { addStyle, View, getElementIntersection } from '@khanacademy/wonder-blocks-core';
10
+ import Icon, { icons } from '@khanacademy/wonder-blocks-icon';
11
+ import ReactDOM from 'react-dom';
12
+ import { Popper } from 'react-popper';
13
+ import { VariableSizeList } from 'react-window';
14
+ import { maybeGetPortalMountedModalHostElement } from '@khanacademy/wonder-blocks-modal';
15
+ import { withActionScheduler } from '@khanacademy/wonder-blocks-timing';
16
+ import PopperJS from 'popper.js';
17
+ import IconButton from '@khanacademy/wonder-blocks-icon-button';
18
+ import { Strut } from '@khanacademy/wonder-blocks-layout';
19
+
20
+ function _classCallCheck(instance, Constructor) {
21
+ if (!(instance instanceof Constructor)) {
22
+ throw new TypeError("Cannot call a class as a function");
23
+ }
24
+ }
25
+
26
+ function _defineProperties(target, props) {
27
+ for (var i = 0; i < props.length; i++) {
28
+ var descriptor = props[i];
29
+ descriptor.enumerable = descriptor.enumerable || false;
30
+ descriptor.configurable = true;
31
+ if ("value" in descriptor) descriptor.writable = true;
32
+ Object.defineProperty(target, descriptor.key, descriptor);
33
+ }
34
+ }
35
+
36
+ function _createClass(Constructor, protoProps, staticProps) {
37
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
38
+ if (staticProps) _defineProperties(Constructor, staticProps);
39
+ return Constructor;
40
+ }
41
+
42
+ function _defineProperty(obj, key, value) {
43
+ if (key in obj) {
44
+ Object.defineProperty(obj, key, {
45
+ value: value,
46
+ enumerable: true,
47
+ configurable: true,
48
+ writable: true
49
+ });
50
+ } else {
51
+ obj[key] = value;
52
+ }
53
+
54
+ return obj;
55
+ }
56
+
57
+ function _extends() {
58
+ _extends = Object.assign || function (target) {
59
+ for (var i = 1; i < arguments.length; i++) {
60
+ var source = arguments[i];
61
+
62
+ for (var key in source) {
63
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
64
+ target[key] = source[key];
65
+ }
66
+ }
67
+ }
68
+
69
+ return target;
70
+ };
71
+
72
+ return _extends.apply(this, arguments);
73
+ }
74
+
75
+ function ownKeys(object, enumerableOnly) {
76
+ var keys = Object.keys(object);
77
+
78
+ if (Object.getOwnPropertySymbols) {
79
+ var symbols = Object.getOwnPropertySymbols(object);
80
+ if (enumerableOnly) symbols = symbols.filter(function (sym) {
81
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
82
+ });
83
+ keys.push.apply(keys, symbols);
84
+ }
85
+
86
+ return keys;
87
+ }
88
+
89
+ function _objectSpread2(target) {
90
+ for (var i = 1; i < arguments.length; i++) {
91
+ var source = arguments[i] != null ? arguments[i] : {};
92
+
93
+ if (i % 2) {
94
+ ownKeys(Object(source), true).forEach(function (key) {
95
+ _defineProperty(target, key, source[key]);
96
+ });
97
+ } else if (Object.getOwnPropertyDescriptors) {
98
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
99
+ } else {
100
+ ownKeys(Object(source)).forEach(function (key) {
101
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
102
+ });
103
+ }
104
+ }
105
+
106
+ return target;
107
+ }
108
+
109
+ function _inherits(subClass, superClass) {
110
+ if (typeof superClass !== "function" && superClass !== null) {
111
+ throw new TypeError("Super expression must either be null or a function");
112
+ }
113
+
114
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
115
+ constructor: {
116
+ value: subClass,
117
+ writable: true,
118
+ configurable: true
119
+ }
120
+ });
121
+ if (superClass) _setPrototypeOf(subClass, superClass);
122
+ }
123
+
124
+ function _getPrototypeOf(o) {
125
+ _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
126
+ return o.__proto__ || Object.getPrototypeOf(o);
127
+ };
128
+ return _getPrototypeOf(o);
129
+ }
130
+
131
+ function _setPrototypeOf(o, p) {
132
+ _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
133
+ o.__proto__ = p;
134
+ return o;
135
+ };
136
+
137
+ return _setPrototypeOf(o, p);
138
+ }
139
+
140
+ function _isNativeReflectConstruct() {
141
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
142
+ if (Reflect.construct.sham) return false;
143
+ if (typeof Proxy === "function") return true;
144
+
145
+ try {
146
+ Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
147
+ return true;
148
+ } catch (e) {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ function _objectWithoutPropertiesLoose(source, excluded) {
154
+ if (source == null) return {};
155
+ var target = {};
156
+ var sourceKeys = Object.keys(source);
157
+ var key, i;
158
+
159
+ for (i = 0; i < sourceKeys.length; i++) {
160
+ key = sourceKeys[i];
161
+ if (excluded.indexOf(key) >= 0) continue;
162
+ target[key] = source[key];
163
+ }
164
+
165
+ return target;
166
+ }
167
+
168
+ function _objectWithoutProperties(source, excluded) {
169
+ if (source == null) return {};
170
+
171
+ var target = _objectWithoutPropertiesLoose(source, excluded);
172
+
173
+ var key, i;
174
+
175
+ if (Object.getOwnPropertySymbols) {
176
+ var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
177
+
178
+ for (i = 0; i < sourceSymbolKeys.length; i++) {
179
+ key = sourceSymbolKeys[i];
180
+ if (excluded.indexOf(key) >= 0) continue;
181
+ if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
182
+ target[key] = source[key];
183
+ }
184
+ }
185
+
186
+ return target;
187
+ }
188
+
189
+ function _assertThisInitialized(self) {
190
+ if (self === void 0) {
191
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
192
+ }
193
+
194
+ return self;
195
+ }
196
+
197
+ function _possibleConstructorReturn(self, call) {
198
+ if (call && (typeof call === "object" || typeof call === "function")) {
199
+ return call;
200
+ }
201
+
202
+ return _assertThisInitialized(self);
203
+ }
204
+
205
+ function _createSuper(Derived) {
206
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
207
+
208
+ return function _createSuperInternal() {
209
+ var Super = _getPrototypeOf(Derived),
210
+ result;
211
+
212
+ if (hasNativeReflectConstruct) {
213
+ var NewTarget = _getPrototypeOf(this).constructor;
214
+
215
+ result = Reflect.construct(Super, arguments, NewTarget);
216
+ } else {
217
+ result = Super.apply(this, arguments);
218
+ }
219
+
220
+ return _possibleConstructorReturn(this, result);
221
+ };
222
+ }
223
+
224
+ function _toConsumableArray(arr) {
225
+ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
226
+ }
227
+
228
+ function _arrayWithoutHoles(arr) {
229
+ if (Array.isArray(arr)) return _arrayLikeToArray(arr);
230
+ }
231
+
232
+ function _iterableToArray(iter) {
233
+ if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
234
+ }
235
+
236
+ function _unsupportedIterableToArray(o, minLen) {
237
+ if (!o) return;
238
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
239
+ var n = Object.prototype.toString.call(o).slice(8, -1);
240
+ if (n === "Object" && o.constructor) n = o.constructor.name;
241
+ if (n === "Map" || n === "Set") return Array.from(o);
242
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
243
+ }
244
+
245
+ function _arrayLikeToArray(arr, len) {
246
+ if (len == null || len > arr.length) len = arr.length;
247
+
248
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
249
+
250
+ return arr2;
251
+ }
252
+
253
+ function _nonIterableSpread() {
254
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
255
+ }
256
+
257
+ function _createForOfIteratorHelper(o, allowArrayLike) {
258
+ var it;
259
+
260
+ if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
261
+ if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
262
+ if (it) o = it;
263
+ var i = 0;
264
+
265
+ var F = function () {};
266
+
267
+ return {
268
+ s: F,
269
+ n: function () {
270
+ if (i >= o.length) return {
271
+ done: true
272
+ };
273
+ return {
274
+ done: false,
275
+ value: o[i++]
276
+ };
277
+ },
278
+ e: function (e) {
279
+ throw e;
280
+ },
281
+ f: F
282
+ };
283
+ }
284
+
285
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
286
+ }
287
+
288
+ var normalCompletion = true,
289
+ didErr = false,
290
+ err;
291
+ return {
292
+ s: function () {
293
+ it = o[Symbol.iterator]();
294
+ },
295
+ n: function () {
296
+ var step = it.next();
297
+ normalCompletion = step.done;
298
+ return step;
299
+ },
300
+ e: function (e) {
301
+ didErr = true;
302
+ err = e;
303
+ },
304
+ f: function () {
305
+ try {
306
+ if (!normalCompletion && it.return != null) it.return();
307
+ } finally {
308
+ if (didErr) throw err;
309
+ }
310
+ }
311
+ };
312
+ }
313
+
314
+ var keyCodes = {
315
+ tab: 9,
316
+ enter: 13,
317
+ escape: 27,
318
+ space: 32,
319
+ up: 38,
320
+ down: 40
321
+ };
322
+ var selectDropdownStyle = {
323
+ marginTop: Spacing.xSmall_8,
324
+ marginBottom: Spacing.xSmall_8
325
+ }; // Filterable dropdown has minimum dimensions requested from Design.
326
+ // Note that these can be overridden by the provided style if needed.
327
+
328
+ var filterableDropdownStyle = {
329
+ minHeight: 100,
330
+ maxHeight: 384
331
+ };
332
+ var searchInputStyle = {
333
+ margin: Spacing.xSmall_8,
334
+ marginTop: Spacing.xxxSmall_4
335
+ }; // The default item height
336
+
337
+ var DROPDOWN_ITEM_HEIGHT = 40;
338
+ var SEPARATOR_ITEM_HEIGHT = 9;
339
+ var SEARCH_ITEM_HEIGHT = DROPDOWN_ITEM_HEIGHT + searchInputStyle.margin + searchInputStyle.marginTop; // The default labels that will be used by different components
340
+
341
+ var defaultLabels = {
342
+ clearSearch: "Clear search",
343
+ filter: "Filter",
344
+ noResults: "No results",
345
+ selectNoneLabel: "Select none",
346
+ selectAllLabel: function selectAllLabel(numOptions) {
347
+ return "Select all (".concat(numOptions, ")");
348
+ },
349
+ noneSelected: "0 items",
350
+ someSelected: function someSelected(numSelectedValues) {
351
+ return "".concat(numSelectedValues, " items");
352
+ },
353
+ allSelected: "All items"
354
+ };
355
+
356
+ var blue = Color.blue,
357
+ white = Color.white,
358
+ offBlack = Color.offBlack,
359
+ offBlack32 = Color.offBlack32;
360
+ var StyledAnchor = addStyle("a");
361
+ var StyledButton = addStyle("button");
362
+ var StyledLink = addStyle(Link);
363
+ /**
364
+ * The action item trigger actions, such as navigating to a different page or
365
+ * opening a modal. Supply the href and/or onClick props. Used as a child of
366
+ * ActionMenu.
367
+ */
368
+
369
+ var ActionItem = /*#__PURE__*/function (_React$Component) {
370
+ _inherits(ActionItem, _React$Component);
371
+
372
+ var _super = _createSuper(ActionItem);
373
+
374
+ function ActionItem() {
375
+ _classCallCheck(this, ActionItem);
376
+
377
+ return _super.apply(this, arguments);
378
+ }
379
+
380
+ _createClass(ActionItem, [{
381
+ key: "render",
382
+ value: function render() {
383
+ var _this$props = this.props,
384
+ skipClientNav = _this$props.skipClientNav,
385
+ disabled = _this$props.disabled,
386
+ href = _this$props.href,
387
+ target = _this$props.target,
388
+ indent = _this$props.indent,
389
+ label = _this$props.label,
390
+ onClick = _this$props.onClick,
391
+ role = _this$props.role,
392
+ style = _this$props.style,
393
+ testId = _this$props.testId;
394
+ var router = this.context.router;
395
+ var ClickableBehavior = getClickableBehavior(href, skipClientNav, router);
396
+ return /*#__PURE__*/createElement(ClickableBehavior, {
397
+ disabled: disabled,
398
+ onClick: onClick,
399
+ href: href,
400
+ role: role,
401
+ target: target
402
+ }, function (state, childrenProps) {
403
+ var pressed = state.pressed,
404
+ hovered = state.hovered,
405
+ focused = state.focused;
406
+ var defaultStyle = [styles.shared, disabled && styles.disabled, !disabled && (pressed ? styles.active : (hovered || focused) && styles.focus), // pass optional styles from react-window (if applies)
407
+ style];
408
+
409
+ var props = _objectSpread2({
410
+ "data-test-id": testId,
411
+ disabled: disabled,
412
+ role: role,
413
+ style: [defaultStyle]
414
+ }, childrenProps);
415
+
416
+ var children = /*#__PURE__*/createElement(Fragment, null, /*#__PURE__*/createElement(LabelMedium, {
417
+ style: [indent && styles.indent, styles.label]
418
+ }, label));
419
+
420
+ if (href && !disabled) {
421
+ return router && !skipClientNav && isClientSideUrl(href) ? /*#__PURE__*/createElement(StyledLink, _extends({}, props, {
422
+ to: href
423
+ }), children) : /*#__PURE__*/createElement(StyledAnchor, _extends({}, props, {
424
+ href: href,
425
+ target: target
426
+ }), children);
427
+ } else {
428
+ return /*#__PURE__*/createElement(StyledButton, _extends({
429
+ type: "button"
430
+ }, props, {
431
+ disabled: disabled
432
+ }), children);
433
+ }
434
+ });
435
+ }
436
+ }], [{
437
+ key: "isClassOf",
438
+ value: function isClassOf(instance) {
439
+ return instance && instance.type && instance.type.__IS_ACTION_ITEM__;
440
+ }
441
+ }]);
442
+
443
+ return ActionItem;
444
+ }(Component);
445
+
446
+ _defineProperty(ActionItem, "contextTypes", {
447
+ router: any
448
+ });
449
+
450
+ _defineProperty(ActionItem, "defaultProps", {
451
+ disabled: false,
452
+ indent: false,
453
+ role: "menuitem"
454
+ });
455
+
456
+ _defineProperty(ActionItem, "__IS_ACTION_ITEM__", true);
457
+ var styles = StyleSheet.create({
458
+ shared: {
459
+ background: white,
460
+ color: offBlack,
461
+ textDecoration: "none",
462
+ border: "none",
463
+ outline: "none",
464
+ flexDirection: "row",
465
+ alignItems: "center",
466
+ display: "flex",
467
+ height: DROPDOWN_ITEM_HEIGHT,
468
+ minHeight: DROPDOWN_ITEM_HEIGHT,
469
+ paddingLeft: Spacing.medium_16,
470
+ paddingRight: Spacing.medium_16,
471
+ // This removes the 300ms click delay on mobile browsers by indicating that
472
+ // "double-tap to zoom" shouldn't be used on this element.
473
+ touchAction: "manipulation"
474
+ },
475
+ label: {
476
+ whiteSpace: "nowrap",
477
+ userSelect: "none"
478
+ },
479
+ indent: {
480
+ marginLeft: Spacing.medium_16
481
+ },
482
+ // hover and focus states
483
+ focus: {
484
+ color: white,
485
+ background: blue
486
+ },
487
+ // active and pressed states
488
+ active: {
489
+ color: mix(fade(blue, 0.32), white),
490
+ background: mix(offBlack32, blue)
491
+ },
492
+ // disabled state
493
+ disabled: {
494
+ color: offBlack32,
495
+ cursor: "default"
496
+ }
497
+ });
498
+
499
+ var offBlack$1 = Color.offBlack,
500
+ offBlack32$1 = Color.offBlack32,
501
+ white$1 = Color.white;
502
+ /**
503
+ * Props describing the state of the OptionItem, shared by the checkbox
504
+ * component,
505
+ */
506
+
507
+ /**
508
+ * The check component used by OptionItem.
509
+ */
510
+ function Check(props) {
511
+ var disabled = props.disabled,
512
+ selected = props.selected,
513
+ pressed = props.pressed,
514
+ hovered = props.hovered,
515
+ focused = props.focused;
516
+ return /*#__PURE__*/createElement(Icon, {
517
+ icon: icons.check,
518
+ size: "small",
519
+ color: disabled ? offBlack32$1 : pressed || hovered || focused ? white$1 : offBlack$1,
520
+ style: [styles$1.bounds, !selected && styles$1.hide]
521
+ });
522
+ }
523
+ var styles$1 = StyleSheet.create({
524
+ bounds: {
525
+ // Semantically, this are the constants for a small-sized icon
526
+ minHeight: 16,
527
+ minWidth: 16
528
+ },
529
+ hide: {
530
+ visibility: "hidden"
531
+ }
532
+ });
533
+
534
+ // NOTE(sophie): This is a smaller check specifically for use in checkboxes.
535
+ // Please don't copy it automatically and check with designers before using.
536
+ // If the intended icon is a check without a checkbox, you should be using
537
+ // icons.check from the Wonder Blocks Icon package.
538
+ var checkboxCheck = {
539
+ small: "M11.263 4.324a1 1 0 1 1 1.474 1.352l-5.5 6a1 1 0 0 1-1.505-.036l-2.5-3a1 1 0 1 1 1.536-1.28L6.536 9.48l4.727-5.157z"
540
+ };
541
+ var blue$1 = Color.blue,
542
+ white$2 = Color.white,
543
+ offBlack16 = Color.offBlack16,
544
+ offBlack32$2 = Color.offBlack32,
545
+ offBlack50 = Color.offBlack50,
546
+ offWhite = Color.offWhite;
547
+ /**
548
+ * Props describing the state of the OptionItem, shared by the check
549
+ * component,
550
+ */
551
+
552
+ /**
553
+ * The checkbox component used by OptionItem.
554
+ */
555
+ function Checkbox(props) {
556
+ var disabled = props.disabled,
557
+ selected = props.selected,
558
+ pressed = props.pressed,
559
+ hovered = props.hovered,
560
+ focused = props.focused;
561
+ var activeBlue = mix(offBlack32$2, blue$1);
562
+ var clickInteraction = pressed || hovered || focused;
563
+ var bgColor = disabled ? offWhite : selected && !clickInteraction ? blue$1 : white$2;
564
+ var checkColor = disabled ? offBlack32$2 : clickInteraction ? pressed ? activeBlue : blue$1 : white$2;
565
+ return /*#__PURE__*/createElement(View, {
566
+ style: [styles$2.checkbox, (clickInteraction || selected && !disabled) && styles$2.noBorder, disabled && styles$2.disabledCheckbox, {
567
+ backgroundColor: bgColor
568
+ }]
569
+ }, selected && /*#__PURE__*/createElement(Icon, {
570
+ icon: checkboxCheck,
571
+ size: "small",
572
+ color: checkColor,
573
+ style: [disabled && selected && styles$2.disabledCheckFormatting]
574
+ }));
575
+ }
576
+ var styles$2 = StyleSheet.create({
577
+ checkbox: {
578
+ // Semantically, this are the constants for a small-sized icon
579
+ minHeight: 16,
580
+ minWidth: 16,
581
+ borderRadius: 3,
582
+ borderWidth: 1,
583
+ borderStyle: "solid",
584
+ borderColor: offBlack50
585
+ },
586
+ noBorder: {
587
+ borderWidth: 0
588
+ },
589
+ disabledCheckbox: {
590
+ borderColor: offBlack16,
591
+ backgroundColor: offWhite
592
+ },
593
+ // The border of 1px on the selected, disabled checkbox pushes the check out
594
+ // of place. Move it back.
595
+ disabledCheckFormatting: {
596
+ position: "absolute",
597
+ top: -1,
598
+ left: -1
599
+ }
600
+ });
601
+
602
+ /**
603
+ * For option items that can be selected in a dropdown, selection denoted either
604
+ * with a check ✔️ or a checkbox ☑️. Use as children in SingleSelect or
605
+ * MultiSelect.
606
+ */
607
+ var OptionItem = /*#__PURE__*/function (_React$Component) {
608
+ _inherits(OptionItem, _React$Component);
609
+
610
+ var _super = _createSuper(OptionItem);
611
+
612
+ function OptionItem() {
613
+ var _this;
614
+
615
+ _classCallCheck(this, OptionItem);
616
+
617
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
618
+ args[_key] = arguments[_key];
619
+ }
620
+
621
+ _this = _super.call.apply(_super, [this].concat(args));
622
+
623
+ _defineProperty(_assertThisInitialized(_this), "handleClick", function () {
624
+ var _this$props = _this.props,
625
+ onClick = _this$props.onClick,
626
+ onToggle = _this$props.onToggle,
627
+ value = _this$props.value;
628
+ onToggle(value);
629
+
630
+ if (onClick) {
631
+ onClick();
632
+ }
633
+ });
634
+
635
+ return _this;
636
+ }
637
+
638
+ _createClass(OptionItem, [{
639
+ key: "getCheckComponent",
640
+ value: function getCheckComponent() {
641
+ if (this.props.variant === "check") {
642
+ return Check;
643
+ } else {
644
+ return Checkbox;
645
+ }
646
+ }
647
+ }, {
648
+ key: "render",
649
+ value: function render() {
650
+ var _this$props2 = this.props,
651
+ disabled = _this$props2.disabled,
652
+ label = _this$props2.label,
653
+ role = _this$props2.role,
654
+ selected = _this$props2.selected,
655
+ testId = _this$props2.testId,
656
+ style = _this$props2.style,
657
+ value = _this$props2.value,
658
+ onClick = _this$props2.onClick,
659
+ onToggle = _this$props2.onToggle,
660
+ variant = _this$props2.variant,
661
+ sharedProps = _objectWithoutProperties(_this$props2, ["disabled", "label", "role", "selected", "testId", "style", "value", "onClick", "onToggle", "variant"]);
662
+
663
+ var ClickableBehavior = getClickableBehavior();
664
+ var CheckComponent = this.getCheckComponent();
665
+ return /*#__PURE__*/createElement(ClickableBehavior, {
666
+ disabled: disabled,
667
+ onClick: this.handleClick,
668
+ role: role
669
+ }, function (state, childrenProps) {
670
+ var pressed = state.pressed,
671
+ hovered = state.hovered,
672
+ focused = state.focused;
673
+ var defaultStyle = [styles$3.itemContainer, pressed ? styles$3.active : (hovered || focused) && styles$3.focus, disabled && styles$3.disabled, // pass optional styles from react-window (if applies)
674
+ style];
675
+ return /*#__PURE__*/createElement(View, _extends({}, sharedProps, {
676
+ testId: testId,
677
+ style: defaultStyle,
678
+ "aria-selected": selected ? "true" : "false",
679
+ role: role
680
+ }, childrenProps), /*#__PURE__*/createElement(CheckComponent, {
681
+ disabled: disabled,
682
+ selected: selected,
683
+ pressed: pressed,
684
+ hovered: hovered,
685
+ focused: focused
686
+ }), /*#__PURE__*/createElement(LabelMedium, {
687
+ style: styles$3.label
688
+ }, label));
689
+ });
690
+ }
691
+ }], [{
692
+ key: "isClassOf",
693
+ value: function isClassOf(instance) {
694
+ return instance && instance.type && instance.type.__IS_OPTION_ITEM__;
695
+ }
696
+ }]);
697
+
698
+ return OptionItem;
699
+ }(Component);
700
+
701
+ _defineProperty(OptionItem, "contextTypes", {
702
+ router: any
703
+ });
704
+
705
+ _defineProperty(OptionItem, "defaultProps", {
706
+ disabled: false,
707
+ onToggle: function onToggle() {
708
+ return void 0;
709
+ },
710
+ role: "option",
711
+ selected: false
712
+ });
713
+
714
+ _defineProperty(OptionItem, "__IS_OPTION_ITEM__", true);
715
+ var blue$2 = Color.blue,
716
+ white$3 = Color.white,
717
+ offBlack$2 = Color.offBlack,
718
+ offBlack32$3 = Color.offBlack32;
719
+ var styles$3 = StyleSheet.create({
720
+ itemContainer: {
721
+ flexDirection: "row",
722
+ backgroundColor: white$3,
723
+ color: offBlack$2,
724
+ alignItems: "center",
725
+ height: DROPDOWN_ITEM_HEIGHT,
726
+ minHeight: DROPDOWN_ITEM_HEIGHT,
727
+ border: 0,
728
+ outline: 0,
729
+ paddingLeft: Spacing.xSmall_8,
730
+ paddingRight: Spacing.medium_16,
731
+ whiteSpace: "nowrap",
732
+ cursor: "default"
733
+ },
734
+ focus: {
735
+ color: white$3,
736
+ background: blue$2
737
+ },
738
+ active: {
739
+ color: mix(fade(blue$2, 0.32), white$3),
740
+ background: mix(offBlack32$3, blue$2)
741
+ },
742
+ disabled: {
743
+ color: offBlack32$3,
744
+ background: white$3
745
+ },
746
+ label: {
747
+ whiteSpace: "nowrap",
748
+ userSelect: "none",
749
+ marginLeft: Spacing.xSmall_8,
750
+ // added to truncate strings that are longer than expected
751
+ overflow: "hidden",
752
+ textOverflow: "ellipsis"
753
+ },
754
+ hide: {
755
+ visibility: "hidden"
756
+ }
757
+ });
758
+
759
+ /**
760
+ * A separator used in a dropdown menu.
761
+ */
762
+ var SeparatorItem = /*#__PURE__*/function (_React$Component) {
763
+ _inherits(SeparatorItem, _React$Component);
764
+
765
+ var _super = _createSuper(SeparatorItem);
766
+
767
+ function SeparatorItem() {
768
+ _classCallCheck(this, SeparatorItem);
769
+
770
+ return _super.apply(this, arguments);
771
+ }
772
+
773
+ _createClass(SeparatorItem, [{
774
+ key: "render",
775
+ value: function render() {
776
+ return (
777
+ /*#__PURE__*/
778
+ // pass optional styles from react-window (if applies)
779
+ createElement(View, {
780
+ style: [styles$4.separator, this.props.style],
781
+ "aria-hidden": "true"
782
+ })
783
+ );
784
+ }
785
+ }], [{
786
+ key: "isClassOf",
787
+ value: function isClassOf(instance) {
788
+ return instance && instance.type && instance.type.__IS_SEPARATOR_ITEM__;
789
+ }
790
+ }]);
791
+
792
+ return SeparatorItem;
793
+ }(Component);
794
+
795
+ _defineProperty(SeparatorItem, "__IS_SEPARATOR_ITEM__", true);
796
+ var styles$4 = StyleSheet.create({
797
+ separator: {
798
+ boxShadow: "0 -1px ".concat(Color.offBlack16),
799
+ height: 1,
800
+ minHeight: 1,
801
+ marginTop: Spacing.xxxSmall_4,
802
+ marginBottom: Spacing.xxxSmall_4
803
+ }
804
+ });
805
+
806
+ var DropdownOpener = /*#__PURE__*/function (_React$Component) {
807
+ _inherits(DropdownOpener, _React$Component);
808
+
809
+ var _super = _createSuper(DropdownOpener);
810
+
811
+ function DropdownOpener() {
812
+ var _this;
813
+
814
+ _classCallCheck(this, DropdownOpener);
815
+
816
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
817
+ args[_key] = arguments[_key];
818
+ }
819
+
820
+ _this = _super.call.apply(_super, [this].concat(args));
821
+
822
+ _defineProperty(_assertThisInitialized(_this), "getTestIdFromProps", function (childrenProps) {
823
+ return childrenProps.testId || childrenProps["data-test-id"];
824
+ });
825
+
826
+ return _this;
827
+ }
828
+
829
+ _createClass(DropdownOpener, [{
830
+ key: "renderAnchorChildren",
831
+ value: function renderAnchorChildren(eventState, clickableChildrenProps) {
832
+ var _this$props = this.props,
833
+ disabled = _this$props.disabled,
834
+ testId = _this$props.testId,
835
+ text = _this$props.text;
836
+ var renderedChildren = this.props.children(_objectSpread2(_objectSpread2({}, eventState), {}, {
837
+ text: text
838
+ }));
839
+ var childrenProps = renderedChildren.props;
840
+ var childrenTestId = this.getTestIdFromProps(childrenProps);
841
+ return cloneElement(renderedChildren, _objectSpread2(_objectSpread2({}, clickableChildrenProps), {}, {
842
+ disabled: disabled,
843
+ onClick: childrenProps.onClick ? function (e) {
844
+ // This is done to avoid overriding a
845
+ // custom onClick handler inside the
846
+ // children node
847
+ childrenProps.onClick(e);
848
+ clickableChildrenProps.onClick(e);
849
+ } : clickableChildrenProps.onClick,
850
+ // try to get the testId from the child element
851
+ // If it's not set, try to fallback to the parent's testId
852
+ "data-test-id": childrenTestId || testId
853
+ }));
854
+ }
855
+ }, {
856
+ key: "render",
857
+ value: function render() {
858
+ var _this2 = this;
859
+
860
+ return /*#__PURE__*/createElement(ClickableBehavior, {
861
+ onClick: this.props.onClick,
862
+ disabled: this.props.disabled
863
+ }, function (eventState, handlers) {
864
+ return _this2.renderAnchorChildren(eventState, handlers);
865
+ });
866
+ }
867
+ }]);
868
+
869
+ return DropdownOpener;
870
+ }(Component);
871
+
872
+ _defineProperty(DropdownOpener, "defaultProps", {
873
+ disabled: false
874
+ });
875
+
876
+ var EmptySizes = Object.freeze({
877
+ top: 0,
878
+ left: 0,
879
+ bottom: 0,
880
+ right: 0
881
+ });
882
+ /**
883
+ * Get the margin, padding, and border edges for a given element.
884
+ */
885
+
886
+ function getEdges(element, withoutEdges) {
887
+ if (!withoutEdges && element instanceof Element) {
888
+ var style = element.currentStyle || window.getComputedStyle(element);
889
+ return {
890
+ margin: {
891
+ left: parseFloat(style.marginLeft),
892
+ top: parseFloat(style.marginTop),
893
+ right: parseFloat(style.marginRight),
894
+ bottom: parseFloat(style.marginBottom)
895
+ },
896
+ padding: {
897
+ left: parseFloat(style.paddingLeft),
898
+ top: parseFloat(style.paddingTop),
899
+ right: parseFloat(style.paddingRight),
900
+ bottom: parseFloat(style.paddingBottom)
901
+ },
902
+ border: {
903
+ left: parseFloat(style.borderLeftWidth),
904
+ top: parseFloat(style.borderTopWidth),
905
+ right: parseFloat(style.borderRightWidth),
906
+ bottom: parseFloat(style.borderBottomWidth)
907
+ }
908
+ };
909
+ }
910
+
911
+ return {
912
+ margin: EmptySizes,
913
+ padding: EmptySizes,
914
+ border: EmptySizes
915
+ };
916
+ }
917
+
918
+ function getBounds(element) {
919
+ var withoutEdges = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
920
+ var elementRect = element.getBoundingClientRect();
921
+ var edges = getEdges(element, withoutEdges);
922
+ return {
923
+ left: elementRect.left + edges.margin.left + edges.padding.left + edges.border.left,
924
+ top: elementRect.top + edges.margin.top + edges.padding.top + edges.border.top,
925
+ right: elementRect.right - edges.margin.right - edges.padding.right - edges.border.right,
926
+ bottom: elementRect.bottom - edges.margin.bottom - edges.padding.bottom - edges.border.bottom
927
+ };
928
+ }
929
+
930
+ function getAxisIntersection(intersectingRect, boundsRect, axis) {
931
+ var start = function start(rect) {
932
+ return axis === "horizontal" ? rect.left : rect.top;
933
+ };
934
+
935
+ var end = function end(rect) {
936
+ return axis === "horizontal" ? rect.right : rect.bottom;
937
+ };
938
+
939
+ if (end(intersectingRect) <= start(boundsRect)) {
940
+ return "before";
941
+ } else if (start(intersectingRect) >= end(boundsRect)) {
942
+ return "after";
943
+ }
944
+
945
+ return "within";
946
+ }
947
+
948
+ /**
949
+ * Determine how one rectangle intersects another.
950
+ *
951
+ * The intersection should be interpreted as whether the first rectangle is
952
+ * within the second.
953
+ */
954
+
955
+ function getIntersection(intersectingRect, boundsRect) {
956
+ var horizontal = getAxisIntersection(intersectingRect, boundsRect, "horizontal");
957
+ var vertical = getAxisIntersection(intersectingRect, boundsRect, "vertical");
958
+ return {
959
+ horizontal: horizontal,
960
+ vertical: vertical
961
+ };
962
+ }
963
+
964
+ /**
965
+ * Determine if an element is obscured by other elements.
966
+ *
967
+ * This uses document.elementFromPoint to see if the given element is being
968
+ * overdrawn by another element. Note that this won't work if the given element
969
+ * has `pointer-events: none`.
970
+ */
971
+
972
+ function isObscured(anchorElement, popperElement) {
973
+ // TODO(somewhatabstract): We should be smarter in this algorithm and
974
+ // actually look at the intersection of the elements doing the obscuring
975
+ // just as we already do with our scroll parent intersections. That way we
976
+ // can not only check that the entire element is obscured, but think about
977
+ // partial obscurement so we can move the tooltip bubble when it's anchor
978
+ // point is not visible.
979
+ // Before we assume we're visible let's check to see if something else
980
+ // is obscuring us. Here we check a variety of points of the element
981
+ // like topleft, bottomright, and center to see if they are covered by
982
+ // something, and if so, assume we're not visible.
983
+ // There are ways that this can still not work, such as different
984
+ // elements only covering those points and the remainder being visible,
985
+ // or if some covering element has none for pointer-events style, but
986
+ // those edge cases shouldn't bother the main usages for this method.
987
+ // NOTE: If the anchor element has `pointer-events: none`, we're always
988
+ // going to end up hiding, so, you know, probably don't do that.
989
+ // We're not explicitly checking for that CSS since it's a corner-case and
990
+ // would impact perf of the regular cases if we were always checking it.
991
+ // TODO(somewhatabstract, WB-300): Need to cater to the case where the
992
+ // viewport is zoomed such that both corners are off screen but the rest
993
+ // isn't. In this case some browsers don't return the element from
994
+ // `elementFromPoint` then doesn't return the element.
995
+ // Also, consider how we might mitigate the pointer-events issue and make
996
+ // this call more robust.
997
+ var bounds = getBounds(anchorElement); // This method does the main work, taking some coordinates and determining
998
+ // if our element is visible at that point or not.
999
+
1000
+ var isVisible = function isVisible(x, y) {
1001
+ var elAtPoint = document.elementFromPoint(x, y);
1002
+
1003
+ if (elAtPoint != null && (elAtPoint === popperElement || popperElement.contains(elAtPoint))) {
1004
+ // Oh no, we're being obscured by our own popper.
1005
+ // We need to look behind it. Shenanigans time.
1006
+ var pointerEventsStyle = elAtPoint.style.pointerEvents; // Remove pointer events so that we can look through it.
1007
+
1008
+ elAtPoint.style.pointerEvents = "none";
1009
+
1010
+ try {
1011
+ var visible = isVisible(x, y);
1012
+ return visible;
1013
+ } finally {
1014
+ // Make sure we put things back the way we found them. :)
1015
+ elAtPoint.style.pointerEvents = pointerEventsStyle;
1016
+ }
1017
+ }
1018
+
1019
+ if (anchorElement instanceof Element) {
1020
+ // If we are working with an element, then we can do some decendency checks
1021
+ // to ensure we're not just hitting a child. We're ok with saying that
1022
+ // we're visible if we hit a parent because we check them for visibility
1023
+ // elsewhere.
1024
+ return elAtPoint != null && (anchorElement.contains(elAtPoint) || elAtPoint.contains(anchorElement));
1025
+ } // If element is a reference object, all we have to work with is
1026
+ // intersection for checking obscurity. Since this doesn't cover
1027
+ // parent/child relationships in the DOM, it's not really effective
1028
+ // on its own and is possibly about as good as just returning `true`.
1029
+
1030
+
1031
+ var intersection = elAtPoint && getIntersection(bounds, getBounds(elAtPoint, true));
1032
+ return (intersection === null || intersection === void 0 ? void 0 : intersection.horizontal) !== "within" || (intersection === null || intersection === void 0 ? void 0 : intersection.vertical) !== "within";
1033
+ }; // NOTE: We are using functions here so that we only do as much work
1034
+ // as we need to, short-circuiting as soon as we have a definitive
1035
+ // answer.
1036
+
1037
+
1038
+ var isTopLeftVisible = function isTopLeftVisible() {
1039
+ return isVisible(bounds.left, bounds.top);
1040
+ };
1041
+
1042
+ var isBottomRightVisible = function isBottomRightVisible() {
1043
+ return isVisible(bounds.right, bounds.bottom);
1044
+ };
1045
+
1046
+ var isCenterVisible = function isCenterVisible() {
1047
+ return isVisible(bounds.left + (bounds.right - bounds.left) / 2, bounds.top + (bounds.bottom - bounds.top) / 2);
1048
+ };
1049
+
1050
+ return !isTopLeftVisible() && !isBottomRightVisible() && !isCenterVisible();
1051
+ }
1052
+
1053
+ var _PopperJS$Defaults$mo, _PopperJS$Defaults$mo2;
1054
+ /**
1055
+ * The function that implements the modifier.
1056
+ */
1057
+
1058
+ function visibilityModifierFn(data) {
1059
+ var anchorElement = data.instance.reference; // First, we see how the element intersects with its scroll parents.
1060
+ // If it doesn't, then we should hide it.
1061
+ // Otherwise, we check to see if anything else obscures the component (like
1062
+ // a fixed or absolute positioned element).
1063
+
1064
+ var _getElementIntersecti = getElementIntersection(anchorElement),
1065
+ horizontal = _getElementIntersecti.horizontal,
1066
+ vertical = _getElementIntersecti.vertical;
1067
+
1068
+ var hide = horizontal !== "within" || vertical !== "within" || isObscured(anchorElement, data.instance.popper); // If we're hidden, we mimic what the built-in hide method does,
1069
+ // and set the hide flag and the OOB attribute with appropriate
1070
+ // short-circuiting.
1071
+ // https://github.com/FezVrasta/popper.js/blob/08c5d6010346bf9df06e9f81a54fa6c2c51e3639/packages/popper/src/modifiers/hide.js#L29-L42
1072
+
1073
+ if (hide) {
1074
+ if (data.hide) {
1075
+ return data;
1076
+ }
1077
+
1078
+ data.hide = true;
1079
+ data.attributes["x-out-of-boundaries"] = "";
1080
+ } else {
1081
+ // Avoid unnecessary DOM access if visibility hasn't changed
1082
+ if (!data.hide) {
1083
+ return data;
1084
+ }
1085
+
1086
+ data.hide = false;
1087
+ data.attributes["x-out-of-boundaries"] = false;
1088
+ } // Always have to return the data object to ensure the modifier chain
1089
+ // in popper.js is unbroken.
1090
+
1091
+
1092
+ return data;
1093
+ }
1094
+ /**
1095
+ * Default configuration that sets things up how usually want them.
1096
+ * Usage:
1097
+ * ```js
1098
+ * import visibilityModifier from "visibility-modifier.js";
1099
+ * const modifiers = [
1100
+ * wbvisibility: visibilityModifier,
1101
+ * ];
1102
+ * ```
1103
+ *
1104
+ * Where `wbvisibility` is a unique name to give the modifier entry,
1105
+ * and `modifiers` is the popper.js or react-popper modifiers array.
1106
+ */
1107
+
1108
+
1109
+ var visibilityModifierDefaultConfig = {
1110
+ enabled: true,
1111
+ // We want this to run after the "hide" modifier, by default.
1112
+ order: (((_PopperJS$Defaults$mo = PopperJS.Defaults.modifiers) === null || _PopperJS$Defaults$mo === void 0 ? void 0 : (_PopperJS$Defaults$mo2 = _PopperJS$Defaults$mo.hide) === null || _PopperJS$Defaults$mo2 === void 0 ? void 0 : _PopperJS$Defaults$mo2.order) || 0) + 1,
1113
+ fn: visibilityModifierFn
1114
+ };
1115
+
1116
+ /**
1117
+ * A virtualized list item - It's created by decorating the DropdownItem
1118
+ * (ActionItem, OptionItem, SeparatorItem) with custom styles to let
1119
+ * react-window make its own calculations.
1120
+ */
1121
+ var DropdownVirtualizedItem = /*#__PURE__*/function (_React$Component) {
1122
+ _inherits(DropdownVirtualizedItem, _React$Component);
1123
+
1124
+ var _super = _createSuper(DropdownVirtualizedItem);
1125
+
1126
+ function DropdownVirtualizedItem() {
1127
+ _classCallCheck(this, DropdownVirtualizedItem);
1128
+
1129
+ return _super.apply(this, arguments);
1130
+ }
1131
+
1132
+ _createClass(DropdownVirtualizedItem, [{
1133
+ key: "render",
1134
+ value: function render() {
1135
+ var _this$props = this.props,
1136
+ data = _this$props.data,
1137
+ index = _this$props.index,
1138
+ style = _this$props.style;
1139
+ var item = data[index];
1140
+
1141
+ if (SeparatorItem.isClassOf(item.component)) {
1142
+ // add react-window style to the separator to preserve the correct
1143
+ // position
1144
+ return cloneElement(item.component, {
1145
+ style: style
1146
+ });
1147
+ } else {
1148
+ var component = item.component,
1149
+ populatedProps = item.populatedProps,
1150
+ onClick = item.onClick,
1151
+ role = item.role,
1152
+ ref = item.ref;
1153
+ return cloneElement(component, _objectSpread2(_objectSpread2({
1154
+ style: style
1155
+ }, populatedProps), {}, {
1156
+ key: index,
1157
+ onClick: onClick,
1158
+ ref: item.focusable && ref,
1159
+ role: role
1160
+ }));
1161
+ }
1162
+ }
1163
+ }]);
1164
+
1165
+ return DropdownVirtualizedItem;
1166
+ }(Component);
1167
+
1168
+ var SearchTextInput = /*#__PURE__*/function (_React$Component) {
1169
+ _inherits(SearchTextInput, _React$Component);
1170
+
1171
+ var _super = _createSuper(SearchTextInput);
1172
+
1173
+ function SearchTextInput() {
1174
+ var _this;
1175
+
1176
+ _classCallCheck(this, SearchTextInput);
1177
+
1178
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
1179
+ args[_key] = arguments[_key];
1180
+ }
1181
+
1182
+ _this = _super.call.apply(_super, [this].concat(args));
1183
+
1184
+ _defineProperty(_assertThisInitialized(_this), "state", {
1185
+ focused: false,
1186
+ labels: _objectSpread2({
1187
+ clearSearch: defaultLabels.clearSearch,
1188
+ filter: defaultLabels.filter
1189
+ }, _this.props.labels)
1190
+ });
1191
+
1192
+ _defineProperty(_assertThisInitialized(_this), "handleChange", function (e) {
1193
+ e.preventDefault();
1194
+
1195
+ _this.props.onChange(e.target.value);
1196
+ });
1197
+
1198
+ _defineProperty(_assertThisInitialized(_this), "handleDismiss", function () {
1199
+ var _this$props = _this.props,
1200
+ onClick = _this$props.onClick,
1201
+ onChange = _this$props.onChange; // Empty the search text and focus the SearchTextInput
1202
+
1203
+ onChange("");
1204
+
1205
+ if (onClick) {
1206
+ onClick();
1207
+ }
1208
+ });
1209
+
1210
+ _defineProperty(_assertThisInitialized(_this), "handleBlur", function (e) {
1211
+ _this.setState({
1212
+ focused: false
1213
+ });
1214
+ });
1215
+
1216
+ _defineProperty(_assertThisInitialized(_this), "handleFocus", function (e) {
1217
+ _this.setState({
1218
+ focused: true
1219
+ });
1220
+ });
1221
+
1222
+ return _this;
1223
+ }
1224
+
1225
+ _createClass(SearchTextInput, [{
1226
+ key: "componentDidUpdate",
1227
+ value: function componentDidUpdate(prevProps) {
1228
+ if (this.props.labels !== prevProps.labels) {
1229
+ // eslint-disable-next-line react/no-did-update-set-state
1230
+ this.setState({
1231
+ labels: _objectSpread2(_objectSpread2({}, this.state.labels), this.props.labels)
1232
+ });
1233
+ }
1234
+ }
1235
+ }, {
1236
+ key: "maybeRenderDismissIconButton",
1237
+ value: function maybeRenderDismissIconButton() {
1238
+ var searchText = this.props.searchText;
1239
+ var clearSearch = this.state.labels.clearSearch;
1240
+
1241
+ if (searchText.length > 0) {
1242
+ return /*#__PURE__*/createElement(IconButton, {
1243
+ icon: icons.dismiss,
1244
+ kind: "tertiary",
1245
+ onClick: this.handleDismiss,
1246
+ style: styles$5.dismissIcon,
1247
+ "aria-label": clearSearch
1248
+ });
1249
+ }
1250
+
1251
+ return null;
1252
+ }
1253
+ }, {
1254
+ key: "render",
1255
+ value: function render() {
1256
+ var _this$props2 = this.props,
1257
+ onClick = _this$props2.onClick,
1258
+ itemRef = _this$props2.itemRef,
1259
+ searchText = _this$props2.searchText,
1260
+ style = _this$props2.style,
1261
+ testId = _this$props2.testId;
1262
+ var filter = this.state.labels.filter;
1263
+ return /*#__PURE__*/createElement(View, {
1264
+ onClick: onClick,
1265
+ style: [styles$5.inputContainer, this.state.focused && styles$5.focused, style]
1266
+ }, /*#__PURE__*/createElement(Icon, {
1267
+ icon: icons.search,
1268
+ size: "medium",
1269
+ color: Color.offBlack64,
1270
+ style: styles$5.searchIcon,
1271
+ "aria-hidden": "true"
1272
+ }), /*#__PURE__*/createElement("input", {
1273
+ type: "text",
1274
+ onChange: this.handleChange,
1275
+ onFocus: this.handleFocus,
1276
+ onBlur: this.handleBlur,
1277
+ ref: itemRef,
1278
+ placeholder: filter,
1279
+ value: searchText,
1280
+ className: css(styles$5.inputStyleReset, styles$a.LabelMedium),
1281
+ "data-test-id": testId
1282
+ }), this.maybeRenderDismissIconButton());
1283
+ }
1284
+ }], [{
1285
+ key: "isClassOf",
1286
+ value: function isClassOf(instance) {
1287
+ return instance && instance.type && instance.type.__IS_SEARCH_TEXT_INPUT__;
1288
+ }
1289
+ }]);
1290
+
1291
+ return SearchTextInput;
1292
+ }(Component);
1293
+
1294
+ _defineProperty(SearchTextInput, "defaultProps", {
1295
+ labels: {
1296
+ clearSearch: defaultLabels.clearSearch,
1297
+ filter: defaultLabels.filter
1298
+ }
1299
+ });
1300
+
1301
+ _defineProperty(SearchTextInput, "__IS_SEARCH_TEXT_INPUT__", true);
1302
+ var styles$5 = StyleSheet.create({
1303
+ inputContainer: {
1304
+ flexDirection: "row",
1305
+ border: "1px solid ".concat(Color.offBlack16),
1306
+ borderRadius: Spacing.xxxSmall_4,
1307
+ alignItems: "center",
1308
+ // The height of the text input is 40 in design spec and we need to
1309
+ // specify the height as well as minHeight to make sure the search text
1310
+ // input takes enough height to render. (otherwise, it will get
1311
+ // squashed)
1312
+ height: DROPDOWN_ITEM_HEIGHT,
1313
+ minHeight: DROPDOWN_ITEM_HEIGHT
1314
+ },
1315
+ focused: {
1316
+ border: "1px solid ".concat(Color.blue)
1317
+ },
1318
+ searchIcon: {
1319
+ marginLeft: Spacing.xSmall_8,
1320
+ marginRight: Spacing.xSmall_8
1321
+ },
1322
+ dismissIcon: {
1323
+ margin: 0,
1324
+ ":hover": {
1325
+ border: "none"
1326
+ }
1327
+ },
1328
+ inputStyleReset: {
1329
+ display: "flex",
1330
+ flex: 1,
1331
+ background: "inherit",
1332
+ border: "none",
1333
+ outline: "none",
1334
+ "::placeholder": {
1335
+ color: Color.offBlack64
1336
+ },
1337
+ width: "100%",
1338
+ color: "inherit"
1339
+ }
1340
+ });
1341
+
1342
+ /**
1343
+ * Maximum visible items inside the dropdown list
1344
+ */
1345
+ var MAX_VISIBLE_ITEMS = 10;
1346
+ /**
1347
+ * A react-window's List wrapper that instantiates the virtualized list and
1348
+ * dynamically calculates the item height depending on the type
1349
+ */
1350
+
1351
+ var DropdownCoreVirtualized = /*#__PURE__*/function (_React$Component) {
1352
+ _inherits(DropdownCoreVirtualized, _React$Component);
1353
+
1354
+ var _super = _createSuper(DropdownCoreVirtualized);
1355
+
1356
+ function DropdownCoreVirtualized() {
1357
+ var _this;
1358
+
1359
+ _classCallCheck(this, DropdownCoreVirtualized);
1360
+
1361
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
1362
+ args[_key] = arguments[_key];
1363
+ }
1364
+
1365
+ _this = _super.call.apply(_super, [this].concat(args));
1366
+
1367
+ _defineProperty(_assertThisInitialized(_this), "state", {
1368
+ height: _this.getHeight(),
1369
+ width: _this.props.width
1370
+ });
1371
+
1372
+ _defineProperty(_assertThisInitialized(_this), "getItemSize", function (index) {
1373
+ // get the current item in the list
1374
+ var item = _this.props.data[index];
1375
+
1376
+ if (SeparatorItem.isClassOf(item.component)) {
1377
+ // this is the separator's height (1px) + vertical margin (8px)
1378
+ return SEPARATOR_ITEM_HEIGHT;
1379
+ } else if (SearchTextInput.isClassOf(item.component)) {
1380
+ // search text input height
1381
+ return SEARCH_ITEM_HEIGHT;
1382
+ } else {
1383
+ // default dropdown item height
1384
+ return DROPDOWN_ITEM_HEIGHT;
1385
+ }
1386
+ });
1387
+
1388
+ return _this;
1389
+ }
1390
+
1391
+ _createClass(DropdownCoreVirtualized, [{
1392
+ key: "componentDidMount",
1393
+ value: function componentDidMount() {
1394
+ var _this2 = this;
1395
+
1396
+ var schedule = this.props.schedule; // Wait for styles to be applied. This way, we can get a more precise
1397
+ // value of the container dimensions.
1398
+
1399
+ schedule.animationFrame(function () {
1400
+ _this2.setWidth();
1401
+ });
1402
+ }
1403
+ }, {
1404
+ key: "componentDidUpdate",
1405
+ value: function componentDidUpdate(prevProps) {
1406
+ var _this$props = this.props,
1407
+ data = _this$props.data,
1408
+ listRef = _this$props.listRef; // if the items size has changed, then recalculate each item position
1409
+
1410
+ if (prevProps.data.length !== data.length) {
1411
+ this.setHeight();
1412
+
1413
+ if (listRef && listRef.current) {
1414
+ // the ref can't associate this instance method
1415
+ // $FlowIgnore
1416
+ listRef.current.resetAfterIndex(1);
1417
+ }
1418
+ }
1419
+ }
1420
+ /**
1421
+ * Update container width
1422
+ */
1423
+
1424
+ }, {
1425
+ key: "setWidth",
1426
+ value: function setWidth() {
1427
+ var rootNode = ReactDOM.findDOMNode(this);
1428
+ var parentNode = rootNode === null || rootNode === void 0 ? void 0 : rootNode.parentElement; // after the non-virtualized items are rendered, we get the container
1429
+ // width to pass it to react-window's List
1430
+
1431
+ if (parentNode) {
1432
+ var width = parentNode.getBoundingClientRect().width;
1433
+ this.setState({
1434
+ width: width
1435
+ });
1436
+ }
1437
+ }
1438
+ /**
1439
+ * Update container height
1440
+ */
1441
+
1442
+ }, {
1443
+ key: "setHeight",
1444
+ value: function setHeight() {
1445
+ // calculate dropdown's height depending on the type of items
1446
+ var height = this.getHeight();
1447
+ this.setState({
1448
+ height: height
1449
+ });
1450
+ }
1451
+ /**
1452
+ * The list height that is automatically calculated depending on the
1453
+ * component's type of each item (e.g. Separator, Option, Search, etc)
1454
+ */
1455
+
1456
+ }, {
1457
+ key: "getHeight",
1458
+ value: function getHeight() {
1459
+ // calculate using the first 10 items on the array as we want to display
1460
+ // this number of elements in the visible area
1461
+ return this.props.data.slice(0, MAX_VISIBLE_ITEMS).reduce(function (sum, item) {
1462
+ if (SeparatorItem.isClassOf(item.component)) {
1463
+ return sum + SEPARATOR_ITEM_HEIGHT;
1464
+ } else if (SearchTextInput.isClassOf(item.component)) {
1465
+ // search text input height
1466
+ return sum + SEARCH_ITEM_HEIGHT;
1467
+ } else {
1468
+ return sum + DROPDOWN_ITEM_HEIGHT;
1469
+ }
1470
+ }, 0);
1471
+ }
1472
+ /**
1473
+ * Calculates item height
1474
+ */
1475
+
1476
+ }, {
1477
+ key: "renderInitialItems",
1478
+
1479
+ /**
1480
+ * render non virtualized items to calculate the container max-width that
1481
+ * will be used by DropdownCoreVirtualized
1482
+ */
1483
+ value: function renderInitialItems() {
1484
+ var data = this.props.data;
1485
+ var allComponents = data.map(function (e) {
1486
+ return e.component;
1487
+ }); // 1. get the children opaque data structure to sort each item by its
1488
+ // label length
1489
+
1490
+ var longestItems = Children.toArray(allComponents).filter(Boolean).sort(function (a, b) {
1491
+ // 2. only sort elements that contain a `label` prop
1492
+ if (b.props.label && a.props.label) {
1493
+ return b.props.label.length - a.props.label.length;
1494
+ }
1495
+
1496
+ return -1;
1497
+ }) // 3. only render the possible visible items to minimize layout
1498
+ // jumps
1499
+ .slice(0, MAX_VISIBLE_ITEMS); // Append longest items to calculate the container width.
1500
+ // We need to hide these sorted elements to avoid any FOUC.
1501
+
1502
+ return longestItems.map(function (item) {
1503
+ return cloneElement(item, {
1504
+ style: {
1505
+ visibility: "hidden"
1506
+ }
1507
+ });
1508
+ });
1509
+ }
1510
+ }, {
1511
+ key: "renderVirtualizedList",
1512
+ value: function renderVirtualizedList() {
1513
+ var _this$props2 = this.props,
1514
+ data = _this$props2.data,
1515
+ listRef = _this$props2.listRef;
1516
+ var _this$state = this.state,
1517
+ height = _this$state.height,
1518
+ width = _this$state.width;
1519
+ return (
1520
+ /*#__PURE__*/
1521
+ // react-window has some issues for typing lists when passing refs
1522
+ // $FlowIgnore
1523
+ createElement(VariableSizeList // react-window doesn't accept maybe numbers. It wants numbers
1524
+ // or strings.
1525
+ // $FlowFixMe
1526
+ , {
1527
+ height: height,
1528
+ itemCount: data.length,
1529
+ itemSize: this.getItemSize,
1530
+ itemData: data,
1531
+ style: {
1532
+ overflowX: "hidden"
1533
+ } // react-window doesn't accept maybe numbers. It wants numbers
1534
+ // or strings.
1535
+ // $FlowFixMe
1536
+ ,
1537
+ width: width,
1538
+ overscanCount: 5,
1539
+ ref: listRef
1540
+ }, DropdownVirtualizedItem)
1541
+ );
1542
+ }
1543
+ }, {
1544
+ key: "render",
1545
+ value: function render() {
1546
+ if (this.state.width === undefined) {
1547
+ // if we don't pass a fixed value, then we need to render
1548
+ // non-virtualized items to calculate width
1549
+ return this.renderInitialItems();
1550
+ } else {
1551
+ // width has been provided, then render the virtualized list
1552
+ return this.renderVirtualizedList();
1553
+ }
1554
+ }
1555
+ }]);
1556
+
1557
+ return DropdownCoreVirtualized;
1558
+ }(Component);
1559
+
1560
+ var DropdownCoreVirtualized$1 = withActionScheduler(DropdownCoreVirtualized);
1561
+
1562
+ /**
1563
+ * A core dropdown component that takes an opener and children to display as
1564
+ * part of the dropdown menu. Renders the dropdown as a portal to avoid clipping
1565
+ * in overflow: auto containers.
1566
+ */
1567
+ var DropdownCore = /*#__PURE__*/function (_React$Component) {
1568
+ _inherits(DropdownCore, _React$Component);
1569
+
1570
+ var _super = _createSuper(DropdownCore);
1571
+
1572
+ _createClass(DropdownCore, null, [{
1573
+ key: "sameItemsFocusable",
1574
+ // Keeps track of the index of the focused item, out of a list of focusable items
1575
+ // Keeps track of the index of the focused item in the context of all the
1576
+ // items contained by this menu, whether focusable or not, used for figuring
1577
+ // out focus correctly when the items have changed in terms of whether
1578
+ // they're focusable or not
1579
+ // Whether any items have been selected since the menu was opened
1580
+ // Keeps a reference of the virtualized list instance
1581
+ // Figure out if the same items are focusable. If an item has been added or
1582
+ // removed, this method will return false.
1583
+ value: function sameItemsFocusable(prevItems, currentItems) {
1584
+ if (prevItems.length !== currentItems.length) {
1585
+ return false;
1586
+ }
1587
+
1588
+ for (var i = 0; i < prevItems.length; i++) {
1589
+ if (prevItems[i].focusable !== currentItems[i].focusable) {
1590
+ return false;
1591
+ }
1592
+ }
1593
+
1594
+ return true;
1595
+ }
1596
+ }, {
1597
+ key: "getDerivedStateFromProps",
1598
+ // This is here to avoid calling React.createRef on each rerender. Instead,
1599
+ // we create the itemRefs only if it's the first time or if the set of items
1600
+ // that are focusable has changed.
1601
+ value: function getDerivedStateFromProps(props, state) {
1602
+ if (state.itemRefs.length === 0 && props.open || !DropdownCore.sameItemsFocusable(state.prevItems, props.items)) {
1603
+ var itemRefs = [];
1604
+
1605
+ for (var i = 0; i < props.items.length; i++) {
1606
+ if (props.items[i].focusable) {
1607
+ var ref = createRef();
1608
+ itemRefs.push({
1609
+ ref: ref,
1610
+ originalIndex: i
1611
+ });
1612
+ }
1613
+ }
1614
+
1615
+ return {
1616
+ itemRefs: itemRefs,
1617
+ prevItems: props.items,
1618
+ sameItemsFocusable: false
1619
+ };
1620
+ } else {
1621
+ return {
1622
+ prevItems: props.items,
1623
+ sameItemsFocusable: true
1624
+ };
1625
+ }
1626
+ }
1627
+ }]);
1628
+
1629
+ function DropdownCore(props) {
1630
+ var _this;
1631
+
1632
+ _classCallCheck(this, DropdownCore);
1633
+
1634
+ _this = _super.call(this, props);
1635
+
1636
+ _defineProperty(_assertThisInitialized(_this), "focusedIndex", void 0);
1637
+
1638
+ _defineProperty(_assertThisInitialized(_this), "focusedOriginalIndex", void 0);
1639
+
1640
+ _defineProperty(_assertThisInitialized(_this), "itemsClicked", void 0);
1641
+
1642
+ _defineProperty(_assertThisInitialized(_this), "popperElement", void 0);
1643
+
1644
+ _defineProperty(_assertThisInitialized(_this), "listRef", void 0);
1645
+
1646
+ _defineProperty(_assertThisInitialized(_this), "handleInteract", function (event) {
1647
+ var _this$props = _this.props,
1648
+ open = _this$props.open,
1649
+ onOpenChanged = _this$props.onOpenChanged;
1650
+ var target = event.target;
1651
+ var thisElement = ReactDOM.findDOMNode(_assertThisInitialized(_this));
1652
+
1653
+ if (open && thisElement && !thisElement.contains(target) && _this.popperElement && !_this.popperElement.contains(target)) {
1654
+ onOpenChanged(false);
1655
+ }
1656
+ });
1657
+
1658
+ _defineProperty(_assertThisInitialized(_this), "handleKeyDown", function (event) {
1659
+ var _this$props2 = _this.props,
1660
+ onOpenChanged = _this$props2.onOpenChanged,
1661
+ open = _this$props2.open,
1662
+ onSearchTextChanged = _this$props2.onSearchTextChanged,
1663
+ searchText = _this$props2.searchText;
1664
+ var keyCode = event.which || event.keyCode; // If menu isn't open and user presses down, open the menu
1665
+
1666
+ if (!open) {
1667
+ if (keyCode === keyCodes.down) {
1668
+ event.preventDefault();
1669
+ onOpenChanged(true);
1670
+ return;
1671
+ }
1672
+
1673
+ return;
1674
+ }
1675
+
1676
+ var showSearchTextInput = !!onSearchTextChanged && typeof searchText === "string"; // Handle all other key behavior
1677
+
1678
+ switch (keyCode) {
1679
+ case keyCodes.tab:
1680
+ // When we show SearchTextInput and that is focused and the
1681
+ // searchText is entered at least one character, dismiss button
1682
+ // is displayed. When user presses tab, we should move focus
1683
+ // to the dismiss button.
1684
+ if (showSearchTextInput && _this.focusedIndex === 0 && searchText) {
1685
+ return;
1686
+ }
1687
+
1688
+ _this.restoreTabOrder();
1689
+
1690
+ onOpenChanged(false);
1691
+ return;
1692
+
1693
+ case keyCodes.space:
1694
+ // When we display SearchTextInput and the focus is on it,
1695
+ // we should let the user type space.
1696
+ if (showSearchTextInput && _this.focusedIndex === 0) {
1697
+ return;
1698
+ } // Prevent space from scrolling down the page
1699
+
1700
+
1701
+ event.preventDefault();
1702
+ return;
1703
+
1704
+ case keyCodes.up:
1705
+ event.preventDefault();
1706
+
1707
+ _this.focusPreviousItem();
1708
+
1709
+ return;
1710
+
1711
+ case keyCodes.down:
1712
+ event.preventDefault();
1713
+
1714
+ _this.focusNextItem();
1715
+
1716
+ return;
1717
+ }
1718
+ });
1719
+
1720
+ _defineProperty(_assertThisInitialized(_this), "handleKeyUp", function (event) {
1721
+ var _this$props3 = _this.props,
1722
+ onOpenChanged = _this$props3.onOpenChanged,
1723
+ open = _this$props3.open,
1724
+ onSearchTextChanged = _this$props3.onSearchTextChanged,
1725
+ searchText = _this$props3.searchText;
1726
+ var keyCode = event.which || event.keyCode;
1727
+ var showSearchTextInput = !!onSearchTextChanged && typeof searchText === "string";
1728
+
1729
+ switch (keyCode) {
1730
+ case keyCodes.space:
1731
+ // When we display SearchTextInput and the focus is on it,
1732
+ // we should let the user type space.
1733
+ if (showSearchTextInput && _this.focusedIndex === 0) {
1734
+ return;
1735
+ } // Prevent space from scrolling down the page
1736
+
1737
+
1738
+ event.preventDefault();
1739
+ return;
1740
+
1741
+ case keyCodes.escape:
1742
+ // Close only the dropdown, not other elements that are
1743
+ // listening for an escape press
1744
+ if (open) {
1745
+ event.stopPropagation();
1746
+
1747
+ _this.restoreTabOrder();
1748
+
1749
+ onOpenChanged(false);
1750
+ }
1751
+
1752
+ return;
1753
+ }
1754
+ });
1755
+
1756
+ _defineProperty(_assertThisInitialized(_this), "handleClickFocus", function (index) {
1757
+ // Turn itemsClicked on so pressing up or down would focus the
1758
+ // appropriate item in handleKeyDown
1759
+ _this.itemsClicked = true;
1760
+ _this.focusedIndex = index;
1761
+ _this.focusedOriginalIndex = _this.state.itemRefs[_this.focusedIndex].originalIndex;
1762
+ });
1763
+
1764
+ _defineProperty(_assertThisInitialized(_this), "handleDropdownMouseUp", function (event) {
1765
+ // $FlowIgnore[method-unbinding]
1766
+ if (event.nativeEvent.stopImmediatePropagation) {
1767
+ event.nativeEvent.stopImmediatePropagation();
1768
+ } else {
1769
+ // Workaround for jsdom
1770
+ event.stopPropagation();
1771
+ }
1772
+ });
1773
+
1774
+ _this.focusedIndex = _this.props.initialFocusedIndex;
1775
+ _this.state = {
1776
+ prevItems: _this.props.items,
1777
+ itemRefs: [],
1778
+ sameItemsFocusable: false,
1779
+ labels: _objectSpread2({
1780
+ noResults: defaultLabels.noResults
1781
+ }, props.labels)
1782
+ };
1783
+ _this.listRef = createRef();
1784
+ return _this;
1785
+ }
1786
+
1787
+ _createClass(DropdownCore, [{
1788
+ key: "componentDidMount",
1789
+ value: function componentDidMount() {
1790
+ this.updateEventListeners();
1791
+ this.initialFocusItem();
1792
+ }
1793
+ }, {
1794
+ key: "componentDidUpdate",
1795
+ value: function componentDidUpdate(prevProps) {
1796
+ var _this2 = this;
1797
+
1798
+ var open = this.props.open;
1799
+
1800
+ if (prevProps.open !== open) {
1801
+ this.updateEventListeners();
1802
+ this.initialFocusItem();
1803
+ } // If the menu changed, but from open to open, figure out if we need
1804
+ // to recalculate the focus somehow.
1805
+ else if (open) {
1806
+ var _this$state = this.state,
1807
+ itemRefs = _this$state.itemRefs,
1808
+ sameItemsFocusable = _this$state.sameItemsFocusable; // Check if the same items are focused by comparing the items at
1809
+ // each index and seeing if the {focusable} property is the same.
1810
+ // Very rarely do the set of focusable items change if the menu
1811
+ // hasn't been re-opened. This is for cases like a {Select all}
1812
+ // option that becomes disabled iff all the options are selected.
1813
+
1814
+ if (sameItemsFocusable) {
1815
+ return;
1816
+ } else {
1817
+ // If the set of items that was focusabled changed, it's very
1818
+ // likely that the previously focused item no longer has the
1819
+ // same index relative to the list of focusable items. Instead,
1820
+ // use the focusedOriginalIndex to find the new index of the
1821
+ // last item that was focused before this change
1822
+ var newFocusableIndex = itemRefs.findIndex(function (ref) {
1823
+ return ref.originalIndex === _this2.focusedOriginalIndex;
1824
+ });
1825
+
1826
+ if (newFocusableIndex === -1) {
1827
+ // Can't find the originally focused item, return focus to
1828
+ // the first item that IS focusable
1829
+ this.focusedIndex = 0; // Reset the knowlege that things had been clicked
1830
+
1831
+ this.itemsClicked = false;
1832
+ this.scheduleToFocusCurrentItem();
1833
+ } else {
1834
+ this.focusedIndex = newFocusableIndex;
1835
+ }
1836
+ }
1837
+
1838
+ if (this.props.labels !== prevProps.labels) {
1839
+ // eslint-disable-next-line react/no-did-update-set-state
1840
+ this.setState({
1841
+ labels: _objectSpread2(_objectSpread2({}, this.state.labels), this.props.labels)
1842
+ });
1843
+ }
1844
+ }
1845
+ }
1846
+ }, {
1847
+ key: "componentWillUnmount",
1848
+ value: function componentWillUnmount() {
1849
+ this.removeEventListeners();
1850
+ } // Figure out focus states for the dropdown after it has changed from open
1851
+ // to closed or vice versa
1852
+
1853
+ }, {
1854
+ key: "initialFocusItem",
1855
+ value: function initialFocusItem() {
1856
+ var _this$props4 = this.props,
1857
+ initialFocusedIndex = _this$props4.initialFocusedIndex,
1858
+ open = _this$props4.open;
1859
+
1860
+ if (open) {
1861
+ // Reset focused index
1862
+ this.focusedIndex = initialFocusedIndex;
1863
+ this.scheduleToFocusCurrentItem();
1864
+ } else if (!open) {
1865
+ this.itemsClicked = false;
1866
+ }
1867
+ }
1868
+ }, {
1869
+ key: "updateEventListeners",
1870
+ value: function updateEventListeners() {
1871
+ if (this.props.open) {
1872
+ this.addEventListeners();
1873
+ } else {
1874
+ this.removeEventListeners();
1875
+ }
1876
+ }
1877
+ }, {
1878
+ key: "addEventListeners",
1879
+ value: function addEventListeners() {
1880
+ document.addEventListener("mouseup", this.handleInteract);
1881
+ document.addEventListener("touchend", this.handleInteract);
1882
+ }
1883
+ }, {
1884
+ key: "removeEventListeners",
1885
+ value: function removeEventListeners() {
1886
+ document.removeEventListener("mouseup", this.handleInteract);
1887
+ document.removeEventListener("touchend", this.handleInteract);
1888
+ }
1889
+ }, {
1890
+ key: "scheduleToFocusCurrentItem",
1891
+ value: function scheduleToFocusCurrentItem() {
1892
+ var _this3 = this;
1893
+
1894
+ // wait for windowed items to be recalculated
1895
+ this.props.schedule.animationFrame(function () {
1896
+ return _this3.focusCurrentItem();
1897
+ });
1898
+ }
1899
+ }, {
1900
+ key: "focusCurrentItem",
1901
+ value: function focusCurrentItem() {
1902
+ var node = ReactDOM.findDOMNode(this.state.itemRefs[this.focusedIndex].ref.current);
1903
+
1904
+ if (node) {
1905
+ node.focus(); // Keep track of the original index of the newly focused item.
1906
+ // To be used if the set of focusable items in the menu changes
1907
+
1908
+ this.focusedOriginalIndex = this.state.itemRefs[this.focusedIndex].originalIndex;
1909
+ }
1910
+ }
1911
+ }, {
1912
+ key: "focusPreviousItem",
1913
+ value: function focusPreviousItem() {
1914
+ if (this.focusedIndex === 0) {
1915
+ this.focusedIndex = this.state.itemRefs.length - 1;
1916
+ } else {
1917
+ this.focusedIndex -= 1;
1918
+ } // force react-window to scroll on the correct position
1919
+
1920
+
1921
+ if (this.listRef.current) {
1922
+ this.listRef.current.scrollToItem(this.focusedIndex);
1923
+ }
1924
+
1925
+ this.scheduleToFocusCurrentItem();
1926
+ }
1927
+ }, {
1928
+ key: "focusNextItem",
1929
+ value: function focusNextItem() {
1930
+ if (this.focusedIndex === this.state.itemRefs.length - 1) {
1931
+ this.focusedIndex = 0;
1932
+ } else {
1933
+ this.focusedIndex += 1;
1934
+ } // force react-window to scroll on the correct position
1935
+
1936
+
1937
+ if (this.listRef.current) {
1938
+ this.listRef.current.scrollToItem(this.focusedIndex);
1939
+ }
1940
+
1941
+ this.scheduleToFocusCurrentItem();
1942
+ }
1943
+ }, {
1944
+ key: "restoreTabOrder",
1945
+ value: function restoreTabOrder() {
1946
+ // NOTE: Because the dropdown is portalled out of its natural
1947
+ // position in the DOM, we need to manually return focus to the
1948
+ // opener element before we let the natural propagation of tab
1949
+ // shift the focus to the next element in the tab order.
1950
+ if (this.props.openerElement) {
1951
+ this.props.openerElement.focus();
1952
+ }
1953
+ }
1954
+ }, {
1955
+ key: "getItemRole",
1956
+ value: function getItemRole() {
1957
+ var role = this.props.role;
1958
+
1959
+ switch (role) {
1960
+ case "listbox":
1961
+ return "option";
1962
+
1963
+ case "menu":
1964
+ return "menuitem";
1965
+
1966
+ default:
1967
+ throw new Error("Expected \"listbox\" or \"menu\" for role, but receieved \"".concat(role, "\" instead."));
1968
+ }
1969
+ }
1970
+ }, {
1971
+ key: "maybeRenderNoResults",
1972
+ value: function maybeRenderNoResults() {
1973
+ var _this$props5 = this.props,
1974
+ items = _this$props5.items,
1975
+ onSearchTextChanged = _this$props5.onSearchTextChanged,
1976
+ searchText = _this$props5.searchText,
1977
+ noResults = _this$props5.labels.noResults;
1978
+ var showSearchTextInput = !!onSearchTextChanged && typeof searchText === "string";
1979
+ var includeSearchCount = showSearchTextInput ? 1 : 0; // Verify if there are items to be rendered or not
1980
+
1981
+ var numResults = items.length - includeSearchCount;
1982
+
1983
+ if (numResults === 0) {
1984
+ return /*#__PURE__*/createElement(LabelMedium, {
1985
+ style: styles$6.noResult,
1986
+ testId: "dropdown-core-no-results"
1987
+ }, noResults);
1988
+ }
1989
+
1990
+ return null;
1991
+ }
1992
+ /**
1993
+ * Process the items and wrap them into an array that react-window can
1994
+ * interpret
1995
+ */
1996
+
1997
+ }, {
1998
+ key: "parseItemsList",
1999
+ value: function parseItemsList() {
2000
+ var _this4 = this;
2001
+
2002
+ var focusCounter = 0;
2003
+ var itemRole = this.getItemRole();
2004
+ return this.props.items.map(function (item, index) {
2005
+ if (!SeparatorItem.isClassOf(item.component) && item.focusable) {
2006
+ focusCounter += 1;
2007
+ }
2008
+
2009
+ var focusIndex = focusCounter - 1;
2010
+
2011
+ if (SearchTextInput.isClassOf(item.component)) {
2012
+ return _objectSpread2(_objectSpread2({}, item), {}, {
2013
+ // override to avoid losing focus when pressing a key
2014
+ onClick: function onClick() {
2015
+ _this4.handleClickFocus(0);
2016
+
2017
+ _this4.focusCurrentItem();
2018
+ },
2019
+ populatedProps: {
2020
+ style: searchInputStyle,
2021
+ // pass the current ref down to the input element
2022
+ itemRef: _this4.state.itemRefs[focusIndex] ? _this4.state.itemRefs[focusIndex].ref : null
2023
+ }
2024
+ });
2025
+ }
2026
+
2027
+ return _objectSpread2(_objectSpread2({}, item), {}, {
2028
+ role: itemRole,
2029
+ ref: item.focusable ? _this4.state.itemRefs[focusIndex] ? _this4.state.itemRefs[focusIndex].ref : null : null,
2030
+ onClick: function onClick() {
2031
+ _this4.handleClickFocus(focusIndex);
2032
+
2033
+ if (item.component.props.onClick) {
2034
+ item.component.props.onClick();
2035
+ }
2036
+
2037
+ if (item.populatedProps.onClick) {
2038
+ item.populatedProps.onClick();
2039
+ }
2040
+ }
2041
+ });
2042
+ });
2043
+ }
2044
+ }, {
2045
+ key: "renderItems",
2046
+ value: function renderItems(outOfBoundaries) {
2047
+ var _this$props6 = this.props,
2048
+ dropdownStyle = _this$props6.dropdownStyle,
2049
+ light = _this$props6.light,
2050
+ openerElement = _this$props6.openerElement; // The dropdown width is at least the width of the opener.
2051
+ // It's only used if the element exists in the DOM
2052
+
2053
+ var openerStyle = openerElement && window.getComputedStyle(openerElement);
2054
+ var minDropdownWidth = openerStyle ? openerStyle.getPropertyValue("width") : 0; // preprocess items data to pass it to the renderer
2055
+
2056
+ var itemsList = this.parseItemsList();
2057
+ return /*#__PURE__*/createElement(View // Stop propagation to prevent the mouseup listener on the
2058
+ // document from closing the menu.
2059
+ , {
2060
+ onMouseUp: this.handleDropdownMouseUp,
2061
+ role: this.props.role,
2062
+ style: [styles$6.dropdown, light && styles$6.light, outOfBoundaries && styles$6.hidden, {
2063
+ minWidth: minDropdownWidth
2064
+ }, dropdownStyle]
2065
+ }, /*#__PURE__*/createElement(DropdownCoreVirtualized$1, {
2066
+ data: itemsList,
2067
+ listRef: this.listRef
2068
+ }), this.maybeRenderNoResults());
2069
+ }
2070
+ }, {
2071
+ key: "renderDropdown",
2072
+ value: function renderDropdown() {
2073
+ var _this5 = this;
2074
+
2075
+ var _this$props7 = this.props,
2076
+ alignment = _this$props7.alignment,
2077
+ openerElement = _this$props7.openerElement; // If we are in a modal, we find where we should be portalling the menu
2078
+ // by using the helper function from the modal package on the opener
2079
+ // element.
2080
+ // If we are not in a modal, we use body as the location to portal to.
2081
+
2082
+ var modalHost = maybeGetPortalMountedModalHostElement(openerElement) || document.querySelector("body");
2083
+
2084
+ if (modalHost) {
2085
+ return ReactDOM.createPortal( /*#__PURE__*/createElement(Popper, {
2086
+ innerRef: function innerRef(node) {
2087
+ if (node) {
2088
+ _this5.popperElement = node;
2089
+ }
2090
+ },
2091
+ referenceElement: this.props.openerElement,
2092
+ placement: alignment === "left" ? "bottom-start" : "bottom-end",
2093
+ modifiers: {
2094
+ wbVisibility: visibilityModifierDefaultConfig,
2095
+ preventOverflow: {
2096
+ boundariesElement: "viewport",
2097
+ escapeWithReference: true
2098
+ }
2099
+ }
2100
+ }, function (_ref) {
2101
+ var placement = _ref.placement,
2102
+ ref = _ref.ref,
2103
+ style = _ref.style,
2104
+ outOfBoundaries = _ref.outOfBoundaries;
2105
+
2106
+ // For some reason react-popper includes `pointerEvents: "none"`
2107
+ // in the `style` it passes to us, but only when running the tests.
2108
+ var _ = style.pointerEvents,
2109
+ restStyle = _objectWithoutProperties(style, ["pointerEvents"]);
2110
+
2111
+ return /*#__PURE__*/createElement("div", {
2112
+ ref: ref,
2113
+ style: restStyle,
2114
+ "data-placement": placement
2115
+ }, _this5.renderItems(outOfBoundaries));
2116
+ }), modalHost);
2117
+ }
2118
+
2119
+ return null;
2120
+ }
2121
+ }, {
2122
+ key: "render",
2123
+ value: function render() {
2124
+ var _this$props8 = this.props,
2125
+ open = _this$props8.open,
2126
+ opener = _this$props8.opener,
2127
+ style = _this$props8.style,
2128
+ className = _this$props8.className;
2129
+ return /*#__PURE__*/createElement(View, {
2130
+ onKeyDown: this.handleKeyDown,
2131
+ onKeyUp: this.handleKeyUp,
2132
+ style: [styles$6.menuWrapper, style],
2133
+ className: className
2134
+ }, opener, open && this.renderDropdown());
2135
+ }
2136
+ }]);
2137
+
2138
+ return DropdownCore;
2139
+ }(Component);
2140
+
2141
+ _defineProperty(DropdownCore, "defaultProps", {
2142
+ alignment: "left",
2143
+ initialFocusedIndex: 0,
2144
+ labels: {
2145
+ noResults: defaultLabels.noResults
2146
+ },
2147
+ light: false
2148
+ });
2149
+
2150
+ var styles$6 = StyleSheet.create({
2151
+ menuWrapper: {
2152
+ width: "fit-content"
2153
+ },
2154
+ dropdown: {
2155
+ backgroundColor: Color.white,
2156
+ borderRadius: 4,
2157
+ paddingTop: Spacing.xxxSmall_4,
2158
+ paddingBottom: Spacing.xxxSmall_4,
2159
+ border: "solid 1px ".concat(Color.offBlack16),
2160
+ boxShadow: "0px 8px 8px 0px ".concat(fade(Color.offBlack, 0.1)),
2161
+ overflowY: "auto"
2162
+ },
2163
+ light: {
2164
+ // Pretty much just remove the border
2165
+ border: "none"
2166
+ },
2167
+ hidden: {
2168
+ visibility: "hidden"
2169
+ },
2170
+ noResult: {
2171
+ color: Color.offBlack64,
2172
+ alignSelf: "center",
2173
+ marginTop: Spacing.xxSmall_6
2174
+ }
2175
+ });
2176
+ var DropdownCore$1 = withActionScheduler(DropdownCore);
2177
+
2178
+ var StyledButton$1 = addStyle("button");
2179
+ /**
2180
+ * Although this component shares a lot with ButtonCore there are a couple
2181
+ * of differences:
2182
+ * - the down caret icon appears on the right instead of the left
2183
+ * - the down caret icon is smaller that the one that would be used by ButtonCore
2184
+ */
2185
+
2186
+ var ActionMenuOpenerCore = /*#__PURE__*/function (_React$Component) {
2187
+ _inherits(ActionMenuOpenerCore, _React$Component);
2188
+
2189
+ var _super = _createSuper(ActionMenuOpenerCore);
2190
+
2191
+ function ActionMenuOpenerCore() {
2192
+ _classCallCheck(this, ActionMenuOpenerCore);
2193
+
2194
+ return _super.apply(this, arguments);
2195
+ }
2196
+
2197
+ _createClass(ActionMenuOpenerCore, [{
2198
+ key: "render",
2199
+ value: function render() {
2200
+ var _this$props = this.props,
2201
+ children = _this$props.children,
2202
+ disabledProp = _this$props.disabled,
2203
+ focused = _this$props.focused,
2204
+ hovered = _this$props.hovered,
2205
+ pressed = _this$props.pressed,
2206
+ _ = _this$props.waiting,
2207
+ testId = _this$props.testId,
2208
+ opened = _this$props.opened,
2209
+ ariaLabel = _this$props["aria-label"],
2210
+ restProps = _objectWithoutProperties(_this$props, ["children", "disabled", "focused", "hovered", "pressed", "waiting", "testId", "opened", "aria-label"]);
2211
+
2212
+ var buttonColor = SemanticColor.controlDefault;
2213
+
2214
+ var buttonStyles = _generateStyles(buttonColor);
2215
+
2216
+ var disabled = disabledProp;
2217
+ var defaultStyle = [sharedStyles.shared, disabled && sharedStyles.disabled, buttonStyles.default, disabled && buttonStyles.disabled, !disabled && pressed && buttonStyles.active];
2218
+ var label = /*#__PURE__*/createElement(LabelLarge, {
2219
+ style: sharedStyles.text
2220
+ }, children);
2221
+ return /*#__PURE__*/createElement(StyledButton$1, _extends({
2222
+ "aria-expanded": opened ? "true" : "false",
2223
+ "aria-haspopup": "menu",
2224
+ "aria-label": ariaLabel,
2225
+ disabled: disabled,
2226
+ style: defaultStyle,
2227
+ type: "button"
2228
+ }, restProps, {
2229
+ "data-test-id": testId
2230
+ }), /*#__PURE__*/createElement(View, {
2231
+ style: !disabled && (hovered || focused) && buttonStyles.focus
2232
+ }, label), /*#__PURE__*/createElement(Strut, {
2233
+ size: Spacing.xxxSmall_4
2234
+ }), /*#__PURE__*/createElement(Icon, {
2235
+ size: "small",
2236
+ color: "currentColor",
2237
+ icon: icons.caretDown
2238
+ }));
2239
+ }
2240
+ }]);
2241
+
2242
+ return ActionMenuOpenerCore;
2243
+ }(Component);
2244
+
2245
+ _defineProperty(ActionMenuOpenerCore, "contextTypes", {
2246
+ router: any
2247
+ });
2248
+ var sharedStyles = StyleSheet.create({
2249
+ shared: {
2250
+ position: "relative",
2251
+ display: "inline-flex",
2252
+ alignItems: "center",
2253
+ justifyContent: "center",
2254
+ height: DROPDOWN_ITEM_HEIGHT,
2255
+ border: "none",
2256
+ borderRadius: Spacing.xxxSmall_4,
2257
+ cursor: "pointer",
2258
+ outline: "none",
2259
+ textDecoration: "none",
2260
+ boxSizing: "border-box",
2261
+ // This removes the 300ms click delay on mobile browsers by indicating that
2262
+ // "double-tap to zoom" shouldn't be used on this element.
2263
+ touchAction: "manipulation",
2264
+ ":focus": {
2265
+ // Mobile: Removes a blue highlight style shown when the user clicks a button
2266
+ WebkitTapHighlightColor: "rgba(0,0,0,0)"
2267
+ }
2268
+ },
2269
+ disabled: {
2270
+ cursor: "auto"
2271
+ },
2272
+ small: {
2273
+ height: Spacing.xLarge_32
2274
+ },
2275
+ text: {
2276
+ textAlign: "left",
2277
+ display: "inline-block",
2278
+ alignItems: "center",
2279
+ fontWeight: "bold",
2280
+ userSelect: "none",
2281
+ whiteSpace: "nowrap",
2282
+ overflow: "hidden",
2283
+ textOverflow: "ellipsis",
2284
+ pointerEvents: "none" // fix Safari bug where the browser was eating mouse events
2285
+
2286
+ },
2287
+ hiddenText: {
2288
+ visibility: "hidden"
2289
+ },
2290
+ spinner: {
2291
+ position: "absolute"
2292
+ }
2293
+ });
2294
+ var styles$7 = {};
2295
+
2296
+ var _generateStyles = function _generateStyles(color) {
2297
+ var buttonType = color;
2298
+
2299
+ if (styles$7[buttonType]) {
2300
+ return styles$7[buttonType];
2301
+ }
2302
+
2303
+ var offBlack32 = Color.offBlack32;
2304
+ var activeColor = mix(offBlack32, color);
2305
+ var newStyles = {};
2306
+ newStyles = {
2307
+ default: {
2308
+ background: "none",
2309
+ color: color
2310
+ },
2311
+ focus: {
2312
+ ":after": {
2313
+ content: "''",
2314
+ position: "absolute",
2315
+ height: 2,
2316
+ left: 0,
2317
+ right: 0,
2318
+ bottom: -1,
2319
+ background: "currentColor",
2320
+ borderRadius: 2
2321
+ }
2322
+ },
2323
+ active: {
2324
+ color: activeColor
2325
+ },
2326
+ disabled: {
2327
+ color: offBlack32,
2328
+ cursor: "default"
2329
+ }
2330
+ };
2331
+ styles$7[buttonType] = StyleSheet.create(newStyles);
2332
+ return styles$7[buttonType];
2333
+ };
2334
+
2335
+ /**
2336
+ * A menu that consists of various types of items.
2337
+ */
2338
+ var ActionMenu = /*#__PURE__*/function (_React$Component) {
2339
+ _inherits(ActionMenu, _React$Component);
2340
+
2341
+ var _super = _createSuper(ActionMenu);
2342
+
2343
+ function ActionMenu() {
2344
+ var _this;
2345
+
2346
+ _classCallCheck(this, ActionMenu);
2347
+
2348
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
2349
+ args[_key] = arguments[_key];
2350
+ }
2351
+
2352
+ _this = _super.call.apply(_super, [this].concat(args));
2353
+
2354
+ _defineProperty(_assertThisInitialized(_this), "openerElement", void 0);
2355
+
2356
+ _defineProperty(_assertThisInitialized(_this), "state", {
2357
+ opened: false
2358
+ });
2359
+
2360
+ _defineProperty(_assertThisInitialized(_this), "handleItemSelected", function () {
2361
+ // close menu
2362
+ _this.handleOpenChanged(false); // Bring focus back to the opener element.
2363
+
2364
+
2365
+ if (_this.openerElement) {
2366
+ _this.openerElement.focus();
2367
+ }
2368
+ });
2369
+
2370
+ _defineProperty(_assertThisInitialized(_this), "handleOpenChanged", function (opened) {
2371
+ _this.setState({
2372
+ opened: opened
2373
+ });
2374
+
2375
+ if (_this.props.onToggle) {
2376
+ _this.props.onToggle(opened);
2377
+ }
2378
+ });
2379
+
2380
+ _defineProperty(_assertThisInitialized(_this), "handleOptionSelected", function (selectedValue) {
2381
+ var _this$props = _this.props,
2382
+ onChange = _this$props.onChange,
2383
+ selectedValues = _this$props.selectedValues; // If either of these are not defined, return.
2384
+
2385
+ if (!onChange || !selectedValues) {
2386
+ return;
2387
+ }
2388
+
2389
+ if (selectedValues.includes(selectedValue)) {
2390
+ var index = selectedValues.indexOf(selectedValue);
2391
+ var updatedSelection = [].concat(_toConsumableArray(selectedValues.slice(0, index)), _toConsumableArray(selectedValues.slice(index + 1)));
2392
+ onChange(updatedSelection);
2393
+ } else {
2394
+ // Item was newly selected
2395
+ onChange([].concat(_toConsumableArray(selectedValues), [selectedValue]));
2396
+ }
2397
+
2398
+ _this.handleItemSelected();
2399
+ });
2400
+
2401
+ _defineProperty(_assertThisInitialized(_this), "handleOpenerRef", function (node) {
2402
+ _this.openerElement = ReactDOM.findDOMNode(node);
2403
+ });
2404
+
2405
+ _defineProperty(_assertThisInitialized(_this), "handleClick", function (e) {
2406
+ _this.handleOpenChanged(!_this.state.opened);
2407
+ });
2408
+
2409
+ return _this;
2410
+ }
2411
+
2412
+ _createClass(ActionMenu, [{
2413
+ key: "getMenuItems",
2414
+ value: function getMenuItems() {
2415
+ var _this2 = this;
2416
+
2417
+ var _this$props2 = this.props,
2418
+ children = _this$props2.children,
2419
+ selectedValues = _this$props2.selectedValues;
2420
+ var allChildren = Children.toArray(children).filter(Boolean); // verify if there's at least one OptionItem element to indent the
2421
+ // possible Action items
2422
+
2423
+ var isOptionItemIncluded = allChildren.some(function (item) {
2424
+ return OptionItem.isClassOf(item);
2425
+ });
2426
+ return allChildren.map(function (item) {
2427
+ var _item$props = item.props,
2428
+ value = _item$props.value,
2429
+ disabled = _item$props.disabled;
2430
+ var itemObject = {
2431
+ component: item,
2432
+ focusable: ActionItem.isClassOf(item) || OptionItem.isClassOf(item) ? !disabled : false,
2433
+ populatedProps: {}
2434
+ };
2435
+
2436
+ if (ActionItem.isClassOf(item)) {
2437
+ return _objectSpread2(_objectSpread2({}, itemObject), {}, {
2438
+ populatedProps: {
2439
+ indent: isOptionItemIncluded,
2440
+ onClick: _this2.handleItemSelected
2441
+ }
2442
+ });
2443
+ } else if (OptionItem.isClassOf(item)) {
2444
+ return _objectSpread2(_objectSpread2({}, itemObject), {}, {
2445
+ populatedProps: {
2446
+ onToggle: _this2.handleOptionSelected,
2447
+ selected: selectedValues ? selectedValues.includes(value) : false,
2448
+ variant: "check"
2449
+ }
2450
+ });
2451
+ } else {
2452
+ return itemObject;
2453
+ }
2454
+ });
2455
+ }
2456
+ }, {
2457
+ key: "renderOpener",
2458
+ value: function renderOpener(numItems) {
2459
+ var _this$props3 = this.props,
2460
+ disabled = _this$props3.disabled,
2461
+ menuText = _this$props3.menuText,
2462
+ opened = _this$props3.opened,
2463
+ opener = _this$props3.opener,
2464
+ testId = _this$props3.testId;
2465
+ return /*#__PURE__*/createElement(DropdownOpener, {
2466
+ onClick: this.handleClick,
2467
+ disabled: numItems === 0 || disabled,
2468
+ text: menuText,
2469
+ ref: this.handleOpenerRef,
2470
+ testId: opener ? undefined : testId
2471
+ }, opener ? opener : function (openerProps) {
2472
+ var text = openerProps.text,
2473
+ eventState = _objectWithoutProperties(openerProps, ["text"]);
2474
+
2475
+ return /*#__PURE__*/createElement(ActionMenuOpenerCore, _extends({}, eventState, {
2476
+ disabled: disabled,
2477
+ opened: !!opened,
2478
+ testId: testId
2479
+ }), menuText);
2480
+ });
2481
+ }
2482
+ }, {
2483
+ key: "render",
2484
+ value: function render() {
2485
+ var _this$props4 = this.props,
2486
+ alignment = _this$props4.alignment,
2487
+ dropdownStyle = _this$props4.dropdownStyle,
2488
+ style = _this$props4.style,
2489
+ className = _this$props4.className;
2490
+ var items = this.getMenuItems();
2491
+ var dropdownOpener = this.renderOpener(items.length);
2492
+ return /*#__PURE__*/createElement(DropdownCore$1, {
2493
+ role: "menu",
2494
+ style: style,
2495
+ className: className,
2496
+ opener: dropdownOpener,
2497
+ alignment: alignment,
2498
+ open: this.state.opened,
2499
+ items: items,
2500
+ openerElement: this.openerElement,
2501
+ onOpenChanged: this.handleOpenChanged,
2502
+ dropdownStyle: [styles$8.menuTopSpace, dropdownStyle]
2503
+ });
2504
+ }
2505
+ }], [{
2506
+ key: "getDerivedStateFromProps",
2507
+
2508
+ /**
2509
+ * Used to sync the `opened` state when this component acts as a controlled
2510
+ * component
2511
+ */
2512
+ value: function getDerivedStateFromProps(props, state) {
2513
+ return {
2514
+ opened: typeof props.opened === "boolean" ? props.opened : state.opened
2515
+ };
2516
+ }
2517
+ }]);
2518
+
2519
+ return ActionMenu;
2520
+ }(Component);
2521
+
2522
+ _defineProperty(ActionMenu, "defaultProps", {
2523
+ alignment: "left",
2524
+ disabled: false
2525
+ });
2526
+ var styles$8 = StyleSheet.create({
2527
+ caret: {
2528
+ marginLeft: 4
2529
+ },
2530
+ // The design calls for additional offset around the opener.
2531
+ opener: {
2532
+ whiteSpace: "nowrap",
2533
+ userSelect: "none",
2534
+ overflow: "hidden",
2535
+ textOverflow: "ellipsis"
2536
+ },
2537
+ // This is to adjust the space between the menu and the opener.
2538
+ menuTopSpace: {
2539
+ top: -4
2540
+ }
2541
+ });
2542
+
2543
+ var StyledButton$2 = addStyle("button");
2544
+ var blue$3 = Color.blue,
2545
+ white$4 = Color.white,
2546
+ white50 = Color.white50,
2547
+ offBlack$3 = Color.offBlack,
2548
+ offBlack16$1 = Color.offBlack16,
2549
+ offBlack32$4 = Color.offBlack32,
2550
+ offBlack64 = Color.offBlack64;
2551
+
2552
+ /**
2553
+ * An opener that opens select boxes.
2554
+ */
2555
+ var SelectOpener = /*#__PURE__*/function (_React$Component) {
2556
+ _inherits(SelectOpener, _React$Component);
2557
+
2558
+ var _super = _createSuper(SelectOpener);
2559
+
2560
+ function SelectOpener() {
2561
+ var _this;
2562
+
2563
+ _classCallCheck(this, SelectOpener);
2564
+
2565
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
2566
+ args[_key] = arguments[_key];
2567
+ }
2568
+
2569
+ _this = _super.call.apply(_super, [this].concat(args));
2570
+
2571
+ _defineProperty(_assertThisInitialized(_this), "handleClick", function (e) {
2572
+ var open = _this.props.open;
2573
+
2574
+ _this.props.onOpenChanged(!open);
2575
+ });
2576
+
2577
+ return _this;
2578
+ }
2579
+
2580
+ _createClass(SelectOpener, [{
2581
+ key: "render",
2582
+ value: function render() {
2583
+ var _this$props = this.props,
2584
+ children = _this$props.children,
2585
+ disabled = _this$props.disabled,
2586
+ id = _this$props.id,
2587
+ isPlaceholder = _this$props.isPlaceholder,
2588
+ light = _this$props.light,
2589
+ open = _this$props.open,
2590
+ testId = _this$props.testId,
2591
+ onOpenChanged = _this$props.onOpenChanged,
2592
+ sharedProps = _objectWithoutProperties(_this$props, ["children", "disabled", "id", "isPlaceholder", "light", "open", "testId", "onOpenChanged"]);
2593
+
2594
+ var ClickableBehavior = getClickableBehavior(this.context.router);
2595
+ return /*#__PURE__*/createElement(ClickableBehavior, {
2596
+ disabled: disabled,
2597
+ onClick: this.handleClick
2598
+ }, function (state, childrenProps) {
2599
+ var stateStyles = _generateStyles$1(light, isPlaceholder);
2600
+
2601
+ var hovered = state.hovered,
2602
+ focused = state.focused,
2603
+ pressed = state.pressed; // The icon colors are kind of fickle. This is just logic
2604
+ // based on the zeplin design.
2605
+
2606
+ var iconColor = light ? disabled || pressed ? "currentColor" : white$4 : disabled ? offBlack32$4 : offBlack64;
2607
+ var style = [styles$9.shared, stateStyles.default, disabled && stateStyles.disabled, !disabled && (pressed ? stateStyles.active : (hovered || focused) && stateStyles.focus)];
2608
+ return /*#__PURE__*/createElement(StyledButton$2, _extends({}, sharedProps, {
2609
+ "aria-expanded": open ? "true" : "false",
2610
+ "aria-haspopup": "listbox",
2611
+ "data-test-id": testId,
2612
+ disabled: disabled,
2613
+ id: id,
2614
+ style: style,
2615
+ type: "button"
2616
+ }, childrenProps), /*#__PURE__*/createElement(LabelMedium, {
2617
+ style: styles$9.text
2618
+ }, children), /*#__PURE__*/createElement(Icon, {
2619
+ icon: icons.caretDown,
2620
+ color: iconColor,
2621
+ size: "small",
2622
+ style: styles$9.caret,
2623
+ "aria-hidden": "true"
2624
+ }));
2625
+ });
2626
+ }
2627
+ }]);
2628
+
2629
+ return SelectOpener;
2630
+ }(Component);
2631
+
2632
+ _defineProperty(SelectOpener, "contextTypes", {
2633
+ router: any
2634
+ });
2635
+
2636
+ _defineProperty(SelectOpener, "defaultProps", {
2637
+ disabled: false,
2638
+ light: false,
2639
+ isPlaceholder: false
2640
+ });
2641
+ var buttonRadius = 4;
2642
+ var styles$9 = StyleSheet.create({
2643
+ // TODO: Dedupe with Button styles
2644
+ shared: {
2645
+ position: "relative",
2646
+ display: "inline-flex",
2647
+ alignItems: "center",
2648
+ justifyContent: "space-between",
2649
+ color: offBlack$3,
2650
+ height: DROPDOWN_ITEM_HEIGHT,
2651
+ // This asymmetry arises from the Icon on the right side, which has
2652
+ // extra padding built in. To have the component look more balanced,
2653
+ // we need to take off some paddingRight here.
2654
+ paddingLeft: 16,
2655
+ paddingRight: 12,
2656
+ borderWidth: 0,
2657
+ borderRadius: buttonRadius,
2658
+ borderStyle: "solid",
2659
+ outline: "none",
2660
+ textDecoration: "none",
2661
+ boxSizing: "border-box",
2662
+ whiteSpace: "nowrap",
2663
+ // This removes the 300ms click delay on mobile browsers by indicating that
2664
+ // "double-tap to zoom" shouldn't be used on this element.
2665
+ touchAction: "manipulation"
2666
+ },
2667
+ text: {
2668
+ marginRight: Spacing.xSmall_8,
2669
+ whiteSpace: "nowrap",
2670
+ userSelect: "none",
2671
+ overflow: "hidden",
2672
+ textOverflow: "ellipsis"
2673
+ },
2674
+ caret: {
2675
+ minWidth: 16
2676
+ }
2677
+ }); // These values are default padding (16 and 12) minus 1, because
2678
+ // changing the borderWidth to 2 messes up the button width
2679
+ // and causes it to move a couple pixels. This fixes that.
2680
+
2681
+ var adjustedPaddingLeft = 16 - 1;
2682
+ var adjustedPaddingRight = 12 - 1;
2683
+ var stateStyles = {};
2684
+
2685
+ var _generateStyles$1 = function _generateStyles(light, placeholder) {
2686
+ // "hash" the parameters
2687
+ var styleKey = "".concat(String(light), "-").concat(String(placeholder));
2688
+
2689
+ if (stateStyles[styleKey]) {
2690
+ return stateStyles[styleKey];
2691
+ }
2692
+
2693
+ var newStyles = {};
2694
+
2695
+ if (light) {
2696
+ newStyles = {
2697
+ default: {
2698
+ backgroundColor: "transparent",
2699
+ color: placeholder ? white50 : white$4,
2700
+ borderColor: white50,
2701
+ borderWidth: 1
2702
+ },
2703
+ focus: {
2704
+ borderColor: white$4,
2705
+ borderWidth: 2,
2706
+ paddingLeft: adjustedPaddingLeft,
2707
+ paddingRight: adjustedPaddingRight
2708
+ },
2709
+ active: {
2710
+ paddingLeft: adjustedPaddingLeft,
2711
+ paddingRight: adjustedPaddingRight,
2712
+ borderColor: mix(fade(blue$3, 0.32), white$4),
2713
+ borderWidth: 2,
2714
+ color: placeholder ? mix(fade(white$4, 0.32), blue$3) : mix(fade(blue$3, 0.32), white$4),
2715
+ backgroundColor: mix(offBlack32$4, blue$3)
2716
+ },
2717
+ disabled: {
2718
+ borderColor: mix(fade(white$4, 0.32), blue$3),
2719
+ color: mix(fade(white$4, 0.32), blue$3),
2720
+ cursor: "auto"
2721
+ }
2722
+ };
2723
+ } else {
2724
+ newStyles = {
2725
+ default: {
2726
+ backgroundColor: white$4,
2727
+ borderColor: offBlack16$1,
2728
+ borderWidth: 1,
2729
+ color: placeholder ? offBlack64 : offBlack$3
2730
+ },
2731
+ focus: {
2732
+ borderColor: blue$3,
2733
+ borderWidth: 2,
2734
+ paddingLeft: adjustedPaddingLeft,
2735
+ paddingRight: adjustedPaddingRight
2736
+ },
2737
+ active: {
2738
+ background: mix(fade(blue$3, 0.32), white$4),
2739
+ borderColor: mix(offBlack32$4, blue$3),
2740
+ borderWidth: 2,
2741
+ paddingLeft: adjustedPaddingLeft,
2742
+ paddingRight: adjustedPaddingRight
2743
+ },
2744
+ disabled: {
2745
+ backgroundColor: "transparent",
2746
+ borderColor: offBlack16$1,
2747
+ color: offBlack64,
2748
+ cursor: "auto"
2749
+ }
2750
+ };
2751
+ }
2752
+
2753
+ stateStyles[styleKey] = StyleSheet.create(newStyles);
2754
+ return stateStyles[styleKey];
2755
+ };
2756
+
2757
+ /**
2758
+ * The single select allows the selection of one item. Clients are responsible
2759
+ * for keeping track of the selected item in the select.
2760
+ *
2761
+ * The single select dropdown closes after the selection of an item. If the same
2762
+ * item is selected, there is no callback.
2763
+ *
2764
+ * *NOTE:* The component automatically uses
2765
+ * [react-window](https://github.com/bvaughn/react-window) to improve
2766
+ * performance when rendering these elements and is capable of handling many
2767
+ * hundreds of items without performance problems.
2768
+ *
2769
+ */
2770
+ var SingleSelect = /*#__PURE__*/function (_React$Component) {
2771
+ _inherits(SingleSelect, _React$Component);
2772
+
2773
+ var _super = _createSuper(SingleSelect);
2774
+
2775
+ function SingleSelect(props) {
2776
+ var _this;
2777
+
2778
+ _classCallCheck(this, SingleSelect);
2779
+
2780
+ _this = _super.call(this, props);
2781
+
2782
+ _defineProperty(_assertThisInitialized(_this), "selectedIndex", void 0);
2783
+
2784
+ _defineProperty(_assertThisInitialized(_this), "handleOpenChanged", function (opened) {
2785
+ _this.setState({
2786
+ open: opened,
2787
+ searchText: ""
2788
+ });
2789
+
2790
+ if (_this.props.onToggle) {
2791
+ _this.props.onToggle(opened);
2792
+ }
2793
+ });
2794
+
2795
+ _defineProperty(_assertThisInitialized(_this), "handleToggle", function (selectedValue) {
2796
+ // Call callback if selection changed.
2797
+ if (selectedValue !== _this.props.selectedValue) {
2798
+ _this.props.onChange(selectedValue);
2799
+ } // Bring focus back to the opener element.
2800
+
2801
+
2802
+ if (_this.state.open && _this.state.openerElement) {
2803
+ _this.state.openerElement.focus();
2804
+ }
2805
+
2806
+ _this.setState({
2807
+ open: false // close the menu upon selection
2808
+
2809
+ });
2810
+
2811
+ if (_this.props.onToggle) {
2812
+ _this.props.onToggle(false);
2813
+ }
2814
+ });
2815
+
2816
+ _defineProperty(_assertThisInitialized(_this), "mapOptionItemsToDropdownItems", function (children) {
2817
+ // Figure out which index should receive focus when this select opens
2818
+ // Needs to exclude counting items that are disabled
2819
+ var indexCounter = 0;
2820
+ _this.selectedIndex = 0;
2821
+ return children.map(function (option) {
2822
+ var selectedValue = _this.props.selectedValue;
2823
+ var _option$props = option.props,
2824
+ disabled = _option$props.disabled,
2825
+ value = _option$props.value;
2826
+ var selected = selectedValue === value;
2827
+
2828
+ if (!disabled) {
2829
+ indexCounter += 1;
2830
+ }
2831
+
2832
+ if (selected) {
2833
+ _this.selectedIndex = indexCounter;
2834
+ }
2835
+
2836
+ return {
2837
+ component: option,
2838
+ focusable: !disabled,
2839
+ populatedProps: {
2840
+ onToggle: _this.handleToggle,
2841
+ selected: selected,
2842
+ variant: "check"
2843
+ }
2844
+ };
2845
+ });
2846
+ });
2847
+
2848
+ _defineProperty(_assertThisInitialized(_this), "handleSearchTextChanged", function (searchText) {
2849
+ _this.setState({
2850
+ searchText: searchText
2851
+ });
2852
+ });
2853
+
2854
+ _defineProperty(_assertThisInitialized(_this), "handleOpenerRef", function (node) {
2855
+ var openerElement = ReactDOM.findDOMNode(node);
2856
+
2857
+ _this.setState({
2858
+ openerElement: openerElement
2859
+ });
2860
+ });
2861
+
2862
+ _defineProperty(_assertThisInitialized(_this), "handleClick", function (e) {
2863
+ _this.handleOpenChanged(!_this.state.open);
2864
+ });
2865
+
2866
+ _this.selectedIndex = 0;
2867
+ _this.state = {
2868
+ open: false,
2869
+ searchText: "",
2870
+ openerElement: null
2871
+ };
2872
+ return _this;
2873
+ }
2874
+ /**
2875
+ * Used to sync the `opened` state when this component acts as a controlled
2876
+ * component
2877
+ */
2878
+
2879
+
2880
+ _createClass(SingleSelect, [{
2881
+ key: "filterChildren",
2882
+ value: function filterChildren(children) {
2883
+ var searchText = this.state.searchText;
2884
+ var lowercasedSearchText = searchText.toLowerCase(); // Filter the children with the searchText if any.
2885
+
2886
+ return children.filter(function (_ref) {
2887
+ var props = _ref.props;
2888
+ return !searchText || props.label.toLowerCase().indexOf(lowercasedSearchText) > -1;
2889
+ });
2890
+ }
2891
+ }, {
2892
+ key: "getMenuItems",
2893
+ value: function getMenuItems(children) {
2894
+ var isFilterable = this.props.isFilterable; // If it's not filterable, no need to do any extra besides mapping the
2895
+ // option items to dropdown items.
2896
+
2897
+ return this.mapOptionItemsToDropdownItems(isFilterable ? this.filterChildren(children) : children);
2898
+ }
2899
+ }, {
2900
+ key: "getSearchField",
2901
+ value: function getSearchField() {
2902
+ if (!this.props.isFilterable) {
2903
+ return null;
2904
+ }
2905
+
2906
+ return {
2907
+ component: /*#__PURE__*/createElement(SearchTextInput, {
2908
+ key: "search-text-input",
2909
+ onChange: this.handleSearchTextChanged,
2910
+ searchText: this.state.searchText,
2911
+ labels: {
2912
+ clearSearch: defaultLabels.clearSearch,
2913
+ filter: defaultLabels.filter
2914
+ }
2915
+ }),
2916
+ focusable: true,
2917
+ populatedProps: {}
2918
+ };
2919
+ }
2920
+ }, {
2921
+ key: "renderOpener",
2922
+ value: function renderOpener(numItems) {
2923
+ var _this$props = this.props,
2924
+ children = _this$props.children,
2925
+ disabled = _this$props.disabled,
2926
+ id = _this$props.id,
2927
+ light = _this$props.light,
2928
+ opener = _this$props.opener,
2929
+ placeholder = _this$props.placeholder,
2930
+ selectedValue = _this$props.selectedValue,
2931
+ testId = _this$props.testId,
2932
+ alignment = _this$props.alignment,
2933
+ dropdownStyle = _this$props.dropdownStyle,
2934
+ isFilterable = _this$props.isFilterable,
2935
+ onChange = _this$props.onChange,
2936
+ onToggle = _this$props.onToggle,
2937
+ opened = _this$props.opened,
2938
+ style = _this$props.style,
2939
+ className = _this$props.className,
2940
+ sharedProps = _objectWithoutProperties(_this$props, ["children", "disabled", "id", "light", "opener", "placeholder", "selectedValue", "testId", "alignment", "dropdownStyle", "isFilterable", "onChange", "onToggle", "opened", "style", "className"]);
2941
+
2942
+ var selectedItem = Children.toArray(children).find(function (option) {
2943
+ return option.props.value === selectedValue;
2944
+ }); // If nothing is selected, or if the selectedValue doesn't match any
2945
+ // item in the menu, use the placeholder.
2946
+
2947
+ var menuText = selectedItem ? selectedItem.props.label : placeholder;
2948
+ var dropdownOpener = opener ? /*#__PURE__*/createElement(DropdownOpener, {
2949
+ onClick: this.handleClick,
2950
+ disabled: numItems === 0 || disabled,
2951
+ ref: this.handleOpenerRef,
2952
+ text: menuText
2953
+ }, opener) : /*#__PURE__*/createElement(SelectOpener, _extends({}, sharedProps, {
2954
+ disabled: numItems === 0 || disabled,
2955
+ id: id,
2956
+ isPlaceholder: !selectedItem,
2957
+ light: light,
2958
+ onOpenChanged: this.handleOpenChanged,
2959
+ open: this.state.open,
2960
+ ref: this.handleOpenerRef,
2961
+ testId: testId
2962
+ }), menuText);
2963
+ return dropdownOpener;
2964
+ }
2965
+ }, {
2966
+ key: "render",
2967
+ value: function render() {
2968
+ var _this$props2 = this.props,
2969
+ alignment = _this$props2.alignment,
2970
+ children = _this$props2.children,
2971
+ dropdownStyle = _this$props2.dropdownStyle,
2972
+ isFilterable = _this$props2.isFilterable,
2973
+ light = _this$props2.light,
2974
+ style = _this$props2.style,
2975
+ className = _this$props2.className;
2976
+ var searchText = this.state.searchText;
2977
+ var allChildren = Children.toArray(children).filter(Boolean);
2978
+ var filteredItems = this.getMenuItems(allChildren);
2979
+ var opener = this.renderOpener(allChildren.length);
2980
+ var searchField = this.getSearchField();
2981
+ var items = searchField ? [searchField].concat(_toConsumableArray(filteredItems)) : filteredItems;
2982
+ return /*#__PURE__*/createElement(DropdownCore$1, {
2983
+ role: "listbox",
2984
+ alignment: alignment,
2985
+ dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
2986
+ initialFocusedIndex: this.selectedIndex,
2987
+ items: items,
2988
+ light: light,
2989
+ onOpenChanged: this.handleOpenChanged,
2990
+ open: this.state.open,
2991
+ opener: opener,
2992
+ openerElement: this.state.openerElement,
2993
+ style: style,
2994
+ className: className,
2995
+ onSearchTextChanged: isFilterable ? this.handleSearchTextChanged : null,
2996
+ searchText: isFilterable ? searchText : ""
2997
+ });
2998
+ }
2999
+ }], [{
3000
+ key: "getDerivedStateFromProps",
3001
+ value: function getDerivedStateFromProps(props, state) {
3002
+ return {
3003
+ open: typeof props.opened === "boolean" ? props.opened : state.open
3004
+ };
3005
+ }
3006
+ }]);
3007
+
3008
+ return SingleSelect;
3009
+ }(Component);
3010
+
3011
+ _defineProperty(SingleSelect, "defaultProps", {
3012
+ alignment: "left",
3013
+ disabled: false,
3014
+ light: false
3015
+ });
3016
+
3017
+ /**
3018
+ * A dropdown that consists of multiple selection items. This select allows
3019
+ * multiple options to be selected. Clients are responsible for keeping track
3020
+ * of the selected items.
3021
+ *
3022
+ * The multi select stays open until closed by the user. The onChange callback
3023
+ * happens every time there is a change in the selection of the items.
3024
+ */
3025
+ var MultiSelect = /*#__PURE__*/function (_React$Component) {
3026
+ _inherits(MultiSelect, _React$Component);
3027
+
3028
+ var _super = _createSuper(MultiSelect);
3029
+
3030
+ function MultiSelect(props) {
3031
+ var _this;
3032
+
3033
+ _classCallCheck(this, MultiSelect);
3034
+
3035
+ _this = _super.call(this, props);
3036
+
3037
+ _defineProperty(_assertThisInitialized(_this), "labels", void 0);
3038
+
3039
+ _defineProperty(_assertThisInitialized(_this), "handleOpenChanged", function (opened) {
3040
+ _this.setState({
3041
+ open: opened,
3042
+ searchText: "",
3043
+ lastSelectedValues: _this.props.selectedValues
3044
+ });
3045
+
3046
+ if (_this.props.onToggle) {
3047
+ _this.props.onToggle(opened);
3048
+ }
3049
+ });
3050
+
3051
+ _defineProperty(_assertThisInitialized(_this), "handleToggle", function (selectedValue) {
3052
+ var _this$props = _this.props,
3053
+ onChange = _this$props.onChange,
3054
+ selectedValues = _this$props.selectedValues;
3055
+
3056
+ if (selectedValues.includes(selectedValue)) {
3057
+ var index = selectedValues.indexOf(selectedValue);
3058
+ var updatedSelection = [].concat(_toConsumableArray(selectedValues.slice(0, index)), _toConsumableArray(selectedValues.slice(index + 1)));
3059
+ onChange(updatedSelection);
3060
+ } else {
3061
+ // Item was newly selected
3062
+ onChange([].concat(_toConsumableArray(selectedValues), [selectedValue]));
3063
+ }
3064
+ });
3065
+
3066
+ _defineProperty(_assertThisInitialized(_this), "handleSelectAll", function () {
3067
+ var _this$props2 = _this.props,
3068
+ children = _this$props2.children,
3069
+ onChange = _this$props2.onChange;
3070
+ var selected = Children.toArray(children).filter(Boolean).map(function (option) {
3071
+ return option.props.value;
3072
+ });
3073
+ onChange(selected);
3074
+ });
3075
+
3076
+ _defineProperty(_assertThisInitialized(_this), "handleSelectNone", function () {
3077
+ var onChange = _this.props.onChange;
3078
+ onChange([]);
3079
+ });
3080
+
3081
+ _defineProperty(_assertThisInitialized(_this), "mapOptionItemToDropdownItem", function (option) {
3082
+ var selectedValues = _this.props.selectedValues;
3083
+ var _option$props = option.props,
3084
+ disabled = _option$props.disabled,
3085
+ value = _option$props.value;
3086
+ return {
3087
+ component: option,
3088
+ focusable: !disabled,
3089
+ populatedProps: {
3090
+ onToggle: _this.handleToggle,
3091
+ selected: selectedValues.includes(value),
3092
+ variant: "checkbox"
3093
+ }
3094
+ };
3095
+ });
3096
+
3097
+ _defineProperty(_assertThisInitialized(_this), "handleOpenerRef", function (node) {
3098
+ var openerElement = ReactDOM.findDOMNode(node);
3099
+
3100
+ _this.setState({
3101
+ openerElement: openerElement
3102
+ });
3103
+ });
3104
+
3105
+ _defineProperty(_assertThisInitialized(_this), "handleSearchTextChanged", function (searchText) {
3106
+ _this.setState({
3107
+ searchText: searchText
3108
+ });
3109
+ });
3110
+
3111
+ _defineProperty(_assertThisInitialized(_this), "handleClick", function (e) {
3112
+ _this.handleOpenChanged(!_this.state.open);
3113
+ });
3114
+
3115
+ _this.state = {
3116
+ open: false,
3117
+ searchText: "",
3118
+ lastSelectedValues: [],
3119
+ // merge custom labels with the default ones
3120
+ labels: _objectSpread2(_objectSpread2({}, defaultLabels), props.labels),
3121
+ openerElement: null
3122
+ }; // merge custom labels with the default ones
3123
+
3124
+ _this.labels = _objectSpread2(_objectSpread2({}, defaultLabels), props.labels);
3125
+ return _this;
3126
+ }
3127
+ /**
3128
+ * Used to sync the `opened` state when this component acts as a controlled
3129
+ * component
3130
+ */
3131
+
3132
+
3133
+ _createClass(MultiSelect, [{
3134
+ key: "componentDidUpdate",
3135
+ value: function componentDidUpdate(prevProps) {
3136
+ if (this.props.labels !== prevProps.labels) {
3137
+ // eslint-disable-next-line react/no-did-update-set-state
3138
+ this.setState({
3139
+ labels: _objectSpread2(_objectSpread2({}, this.state.labels), this.props.labels)
3140
+ });
3141
+ }
3142
+ }
3143
+ }, {
3144
+ key: "getMenuText",
3145
+ value: function getMenuText(children) {
3146
+ var _this$props3 = this.props,
3147
+ implicitAllEnabled = _this$props3.implicitAllEnabled,
3148
+ selectedValues = _this$props3.selectedValues;
3149
+ var _this$state$labels = this.state.labels,
3150
+ noneSelected = _this$state$labels.noneSelected,
3151
+ someSelected = _this$state$labels.someSelected,
3152
+ allSelected = _this$state$labels.allSelected; // When implicit all enabled, use `labels.allSelected` when no selection
3153
+ // otherwise, use the `labels.noneSelected` value
3154
+
3155
+ var noSelectionText = implicitAllEnabled ? allSelected : noneSelected;
3156
+
3157
+ switch (selectedValues.length) {
3158
+ case 0:
3159
+ return noSelectionText;
3160
+
3161
+ case 1:
3162
+ // If there is one item selected, we display its label. If for
3163
+ // some reason we can't find the selected item, we use the
3164
+ // display text for the case where nothing is selected.
3165
+ var selectedItem = children.find(function (option) {
3166
+ return option.props.value === selectedValues[0];
3167
+ });
3168
+ return selectedItem ? selectedItem.props.label : noSelectionText;
3169
+
3170
+ case children.length:
3171
+ return allSelected;
3172
+
3173
+ default:
3174
+ return someSelected(selectedValues.length);
3175
+ }
3176
+ }
3177
+ }, {
3178
+ key: "getSearchField",
3179
+ value: function getSearchField() {
3180
+ if (!this.props.isFilterable) {
3181
+ return [];
3182
+ }
3183
+
3184
+ var _this$state$labels2 = this.state.labels,
3185
+ clearSearch = _this$state$labels2.clearSearch,
3186
+ filter = _this$state$labels2.filter;
3187
+ return [{
3188
+ component: /*#__PURE__*/createElement(SearchTextInput, {
3189
+ key: "search-text-input",
3190
+ onChange: this.handleSearchTextChanged,
3191
+ searchText: this.state.searchText,
3192
+ labels: {
3193
+ clearSearch: clearSearch,
3194
+ filter: filter
3195
+ }
3196
+ }),
3197
+ focusable: true,
3198
+ populatedProps: {}
3199
+ }];
3200
+ }
3201
+ }, {
3202
+ key: "getShortcuts",
3203
+ value: function getShortcuts(numOptions) {
3204
+ var _this$props4 = this.props,
3205
+ selectedValues = _this$props4.selectedValues,
3206
+ shortcuts = _this$props4.shortcuts;
3207
+ var _this$state$labels3 = this.state.labels,
3208
+ selectAllLabel = _this$state$labels3.selectAllLabel,
3209
+ selectNoneLabel = _this$state$labels3.selectNoneLabel; // When there's search text input to filter, shortcuts should be hidden
3210
+
3211
+ if (shortcuts && !this.state.searchText) {
3212
+ var selectAllDisabled = numOptions === selectedValues.length;
3213
+ var selectAll = {
3214
+ component: /*#__PURE__*/createElement(ActionItem, {
3215
+ disabled: selectAllDisabled,
3216
+ label: selectAllLabel(numOptions),
3217
+ indent: true,
3218
+ onClick: this.handleSelectAll
3219
+ }),
3220
+ focusable: !selectAllDisabled,
3221
+ populatedProps: {}
3222
+ };
3223
+ var selectNoneDisabled = selectedValues.length === 0;
3224
+ var selectNone = {
3225
+ component: /*#__PURE__*/createElement(ActionItem, {
3226
+ disabled: selectNoneDisabled,
3227
+ label: selectNoneLabel,
3228
+ indent: true,
3229
+ onClick: this.handleSelectNone
3230
+ }),
3231
+ focusable: !selectNoneDisabled,
3232
+ populatedProps: {}
3233
+ };
3234
+ var separator = {
3235
+ component: /*#__PURE__*/createElement(SeparatorItem, {
3236
+ key: "shortcuts-separator"
3237
+ }),
3238
+ focusable: false,
3239
+ populatedProps: {}
3240
+ };
3241
+ return [selectAll, selectNone, separator];
3242
+ } else {
3243
+ return [];
3244
+ }
3245
+ }
3246
+ }, {
3247
+ key: "getMenuItems",
3248
+ value: function getMenuItems(children) {
3249
+ var isFilterable = this.props.isFilterable; // If it's not filterable, no need to do any extra besides mapping the
3250
+ // option items to dropdown items.
3251
+
3252
+ if (!isFilterable) {
3253
+ return children.map(this.mapOptionItemToDropdownItem);
3254
+ }
3255
+
3256
+ var _this$state = this.state,
3257
+ searchText = _this$state.searchText,
3258
+ lastSelectedValues = _this$state.lastSelectedValues;
3259
+ var lowercasedSearchText = searchText.toLowerCase(); // Filter the children with the searchText if any.
3260
+
3261
+ var filteredChildren = children.filter(function (_ref) {
3262
+ var props = _ref.props;
3263
+ return !searchText || props.label.toLowerCase().indexOf(lowercasedSearchText) > -1;
3264
+ });
3265
+ var lastSelectedChildren = [];
3266
+ var restOfTheChildren = [];
3267
+
3268
+ var _iterator = _createForOfIteratorHelper(filteredChildren),
3269
+ _step;
3270
+
3271
+ try {
3272
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
3273
+ var child = _step.value;
3274
+
3275
+ if (lastSelectedValues.includes(child.props.value)) {
3276
+ lastSelectedChildren.push(child);
3277
+ } else {
3278
+ restOfTheChildren.push(child);
3279
+ }
3280
+ }
3281
+ } catch (err) {
3282
+ _iterator.e(err);
3283
+ } finally {
3284
+ _iterator.f();
3285
+ }
3286
+
3287
+ var lastSelectedItems = lastSelectedChildren.map(this.mapOptionItemToDropdownItem); // We want to add SeparatorItem in between last selected items and the
3288
+ // rest of the items only when both of them exists.
3289
+
3290
+ if (lastSelectedChildren.length && restOfTheChildren.length) {
3291
+ lastSelectedItems.push({
3292
+ component: /*#__PURE__*/createElement(SeparatorItem, {
3293
+ key: "selected-separator"
3294
+ }),
3295
+ focusable: false,
3296
+ populatedProps: {}
3297
+ });
3298
+ }
3299
+
3300
+ return [].concat(_toConsumableArray(lastSelectedItems), _toConsumableArray(restOfTheChildren.map(this.mapOptionItemToDropdownItem)));
3301
+ }
3302
+ }, {
3303
+ key: "renderOpener",
3304
+ value: function renderOpener(allChildren) {
3305
+ var _this$props5 = this.props,
3306
+ disabled = _this$props5.disabled,
3307
+ id = _this$props5.id,
3308
+ light = _this$props5.light,
3309
+ opener = _this$props5.opener,
3310
+ testId = _this$props5.testId,
3311
+ alignment = _this$props5.alignment,
3312
+ dropdownStyle = _this$props5.dropdownStyle,
3313
+ implicitAllEnabled = _this$props5.implicitAllEnabled,
3314
+ isFilterable = _this$props5.isFilterable,
3315
+ labels = _this$props5.labels,
3316
+ onChange = _this$props5.onChange,
3317
+ onToggle = _this$props5.onToggle,
3318
+ opened = _this$props5.opened,
3319
+ selectedValues = _this$props5.selectedValues,
3320
+ shortcuts = _this$props5.shortcuts,
3321
+ style = _this$props5.style,
3322
+ className = _this$props5.className,
3323
+ sharedProps = _objectWithoutProperties(_this$props5, ["disabled", "id", "light", "opener", "testId", "alignment", "dropdownStyle", "implicitAllEnabled", "isFilterable", "labels", "onChange", "onToggle", "opened", "selectedValues", "shortcuts", "style", "className"]);
3324
+
3325
+ var noneSelected = this.state.labels.noneSelected;
3326
+ var menuText = this.getMenuText(allChildren);
3327
+ var numOptions = allChildren.length;
3328
+ var dropdownOpener = opener ? /*#__PURE__*/createElement(DropdownOpener, {
3329
+ onClick: this.handleClick,
3330
+ disabled: numOptions === 0 || disabled,
3331
+ ref: this.handleOpenerRef,
3332
+ text: menuText
3333
+ }, opener) : /*#__PURE__*/createElement(SelectOpener, _extends({}, sharedProps, {
3334
+ disabled: numOptions === 0 || disabled,
3335
+ id: id,
3336
+ isPlaceholder: menuText === noneSelected,
3337
+ light: light,
3338
+ onOpenChanged: this.handleOpenChanged,
3339
+ open: this.state.open,
3340
+ ref: this.handleOpenerRef,
3341
+ testId: testId
3342
+ }), menuText);
3343
+ return dropdownOpener;
3344
+ }
3345
+ }, {
3346
+ key: "render",
3347
+ value: function render() {
3348
+ var _this$props6 = this.props,
3349
+ alignment = _this$props6.alignment,
3350
+ light = _this$props6.light,
3351
+ style = _this$props6.style,
3352
+ className = _this$props6.className,
3353
+ dropdownStyle = _this$props6.dropdownStyle,
3354
+ children = _this$props6.children,
3355
+ isFilterable = _this$props6.isFilterable;
3356
+ var _this$state2 = this.state,
3357
+ open = _this$state2.open,
3358
+ searchText = _this$state2.searchText;
3359
+ var noResults = this.state.labels.noResults;
3360
+ var allChildren = Children.toArray(children).filter(Boolean);
3361
+ var numOptions = allChildren.length;
3362
+ var filteredItems = this.getMenuItems(allChildren);
3363
+ var opener = this.renderOpener(allChildren);
3364
+ return /*#__PURE__*/createElement(DropdownCore$1, {
3365
+ role: "listbox",
3366
+ alignment: alignment,
3367
+ dropdownStyle: [isFilterable && filterableDropdownStyle, selectDropdownStyle, dropdownStyle],
3368
+ items: [].concat(_toConsumableArray(this.getSearchField()), _toConsumableArray(this.getShortcuts(numOptions)), _toConsumableArray(filteredItems)),
3369
+ light: light,
3370
+ onOpenChanged: this.handleOpenChanged,
3371
+ open: open,
3372
+ opener: opener,
3373
+ openerElement: this.state.openerElement,
3374
+ style: style,
3375
+ className: className,
3376
+ onSearchTextChanged: isFilterable ? this.handleSearchTextChanged : null,
3377
+ searchText: isFilterable ? searchText : "",
3378
+ labels: {
3379
+ noResults: noResults
3380
+ }
3381
+ });
3382
+ }
3383
+ }], [{
3384
+ key: "getDerivedStateFromProps",
3385
+ value: function getDerivedStateFromProps(props, state) {
3386
+ return {
3387
+ open: typeof props.opened === "boolean" ? props.opened : state.open
3388
+ };
3389
+ }
3390
+ }]);
3391
+
3392
+ return MultiSelect;
3393
+ }(Component);
3394
+
3395
+ _defineProperty(MultiSelect, "defaultProps", {
3396
+ alignment: "left",
3397
+ disabled: false,
3398
+ light: false,
3399
+ shortcuts: false,
3400
+ selectedValues: []
3401
+ });
3402
+
3403
+ export { ActionItem, ActionMenu, MultiSelect, OptionItem, SeparatorItem, SingleSelect };