1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | import 'dom4';
|
9 | import React, {Component, cloneElement} from 'react';
|
10 | import PropTypes from 'prop-types';
|
11 | import classNames from 'classnames';
|
12 | import VirtualizedList from 'react-virtualized/dist/commonjs/List';
|
13 | import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
|
14 | import WindowScroller from 'react-virtualized/dist/commonjs/WindowScroller';
|
15 | import {CellMeasurer, CellMeasurerCache} from 'react-virtualized/dist/commonjs/CellMeasurer';
|
16 |
|
17 | import dataTests from '../global/data-tests';
|
18 | import getUID from '../global/get-uid';
|
19 | import scheduleRAF from '../global/schedule-raf';
|
20 | import memoize from '../global/memoize';
|
21 | import {preventDefault} from '../global/dom';
|
22 | import Shortcuts from '../shortcuts/shortcuts';
|
23 |
|
24 | import styles from './list.css';
|
25 | import ListItem from './list__item';
|
26 | import ListCustom from './list__custom';
|
27 | import ListLink from './list__link';
|
28 | import ListTitle from './list__title';
|
29 | import ListSeparator from './list__separator';
|
30 | import ListHint from './list__hint';
|
31 |
|
32 | const scheduleScrollListener = scheduleRAF();
|
33 | const scheduleHoverListener = scheduleRAF();
|
34 |
|
35 |
|
36 |
|
37 | const Type = {
|
38 | SEPARATOR: 0,
|
39 | LINK: 1,
|
40 | ITEM: 2,
|
41 | HINT: 3,
|
42 | CUSTOM: 4,
|
43 | TITLE: 5,
|
44 | MARGIN: 6
|
45 | };
|
46 |
|
47 | const Dimension = {
|
48 | ITEM_PADDING: 16,
|
49 | ITEM_HEIGHT: 32,
|
50 | COMPACT_ITEM_HEIGHT: 24,
|
51 | SEPARATOR_HEIGHT: 25,
|
52 | SEPARATOR_FIRST_HEIGHT: 16,
|
53 | SEPARATOR_TEXT_HEIGHT: 18,
|
54 | TITLE_HEIGHT: 42,
|
55 | INNER_PADDING: 8,
|
56 | MARGIN: 8
|
57 | };
|
58 |
|
59 | const DEFAULT_ITEM_TYPE = Type.ITEM;
|
60 |
|
61 | function noop() {}
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | function isItemType(listItemType, item) {
|
68 | let type = item.rgItemType;
|
69 | if (type == null) {
|
70 | type = DEFAULT_ITEM_TYPE;
|
71 | }
|
72 | return type === listItemType;
|
73 | }
|
74 |
|
75 | function isActivatable(item) {
|
76 | return !(item.rgItemType === Type.HINT ||
|
77 | item.rgItemType === Type.SEPARATOR ||
|
78 | item.rgItemType === Type.TITLE ||
|
79 | item.disabled);
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | export default class List extends Component {
|
90 | static isItemType = isItemType;
|
91 |
|
92 | static ListHint = ListHint;
|
93 |
|
94 | static ListProps = {
|
95 | Type,
|
96 | Dimension
|
97 | };
|
98 |
|
99 | static propTypes = {
|
100 | className: PropTypes.string,
|
101 | hint: PropTypes.string,
|
102 | hintOnSelection: PropTypes.string,
|
103 | data: PropTypes.array,
|
104 | maxHeight: PropTypes.oneOfType([
|
105 | PropTypes.string,
|
106 | PropTypes.number
|
107 | ]),
|
108 | activeIndex: PropTypes.number,
|
109 | restoreActiveIndex: PropTypes.bool,
|
110 | activateSingleItem: PropTypes.bool,
|
111 | activateFirstItem: PropTypes.bool,
|
112 | shortcuts: PropTypes.bool,
|
113 | onMouseOut: PropTypes.func,
|
114 | onSelect: PropTypes.func,
|
115 | onScrollToBottom: PropTypes.func,
|
116 | onResize: PropTypes.func,
|
117 | useMouseUp: PropTypes.bool,
|
118 | visible: PropTypes.bool,
|
119 | renderOptimization: PropTypes.bool,
|
120 | disableMoveOverflow: PropTypes.bool,
|
121 | disableMoveDownOverflow: PropTypes.bool,
|
122 | compact: PropTypes.bool
|
123 | };
|
124 |
|
125 | static defaultProps = {
|
126 | data: [],
|
127 | restoreActiveIndex: false,
|
128 | activateSingleItem: false,
|
129 | activateFirstItem: false,
|
130 | onMouseOut: noop,
|
131 | onSelect: noop,
|
132 | onScrollToBottom: noop,
|
133 | onResize: noop,
|
134 | shortcuts: false,
|
135 | renderOptimization: true,
|
136 | disableMoveDownOverflow: false
|
137 | };
|
138 |
|
139 | state = {
|
140 | activeIndex: null,
|
141 | activeItem: null,
|
142 | needScrollToActive: false,
|
143 | scrolling: false,
|
144 | hasOverflow: false,
|
145 | disabledHover: false,
|
146 | scrolledToBottom: false
|
147 | };
|
148 |
|
149 | componentWillMount() {
|
150 | const {data, activeIndex} = this.props;
|
151 | this.checkActivatableItems(data);
|
152 | if (activeIndex != null && data[this.props.activeIndex]) {
|
153 | this.setState({
|
154 | activeIndex,
|
155 | activeItem: data[activeIndex],
|
156 | needScrollToActive: true
|
157 | });
|
158 | } else if (
|
159 | activeIndex == null &&
|
160 | this.shouldActivateFirstItem(this.props) &&
|
161 | this.hasActivatableItems()
|
162 | ) {
|
163 | const firstActivatableIndex = data.findIndex(isActivatable);
|
164 | this.setState({
|
165 | activeIndex: firstActivatableIndex,
|
166 | activeItem: data[firstActivatableIndex],
|
167 | needScrollToActive: true
|
168 | });
|
169 | }
|
170 | }
|
171 |
|
172 | componentDidMount() {
|
173 | document.addEventListener('mousemove', this.onDocumentMouseMove);
|
174 | document.addEventListener('keydown', this.onDocumentKeyDown, true);
|
175 | }
|
176 |
|
177 | componentWillReceiveProps(props) {
|
178 | if (props.data) {
|
179 |
|
180 |
|
181 |
|
182 | this.checkActivatableItems(props.data);
|
183 |
|
184 | this.setState(prevState => {
|
185 | let activeIndex = null;
|
186 | let activeItem = null;
|
187 |
|
188 | if (
|
189 | props.restoreActiveIndex &&
|
190 | prevState.activeItem &&
|
191 | prevState.activeItem.key != null
|
192 | ) {
|
193 | for (let i = 0; i < props.data.length; i++) {
|
194 |
|
195 | if (props.data[i].key !== undefined && props.data[i].key === prevState.activeItem.key) {
|
196 | activeIndex = i;
|
197 | activeItem = props.data[i];
|
198 | break;
|
199 | }
|
200 | }
|
201 | }
|
202 |
|
203 | if (
|
204 | activeIndex === null &&
|
205 | this.shouldActivateFirstItem(props) &&
|
206 | this.hasActivatableItems()
|
207 | ) {
|
208 | activeIndex = props.data.findIndex(isActivatable);
|
209 | activeItem = props.data[activeIndex];
|
210 | } else if (
|
211 | props.activeIndex != null &&
|
212 | props.activeIndex !== this.props.activeIndex &&
|
213 | props.data[props.activeIndex]
|
214 | ) {
|
215 | activeIndex = props.activeIndex;
|
216 | activeItem = props.data[props.activeIndex];
|
217 | }
|
218 |
|
219 | return {
|
220 | activeIndex,
|
221 | activeItem,
|
222 | needScrollToActive:
|
223 | activeIndex !== prevState.activeIndex ? true : prevState.needScrollToActive
|
224 | };
|
225 | });
|
226 | }
|
227 | }
|
228 |
|
229 | shouldComponentUpdate(nextProps, nextState) {
|
230 | return nextProps !== this.props ||
|
231 | Object.keys(nextState).some(key => nextState[key] !== this.state[key]);
|
232 | }
|
233 |
|
234 | componentDidUpdate(prevProps) {
|
235 | if (this.virtualizedList && prevProps.data !== this.props.data) {
|
236 | this.virtualizedList.recomputeRowHeights();
|
237 | }
|
238 |
|
239 | this.checkOverflow();
|
240 | }
|
241 |
|
242 | componentWillUnmount() {
|
243 | this.unmounted = true;
|
244 | document.removeEventListener('mousemove', this.onDocumentMouseMove);
|
245 | document.removeEventListener('keydown', this.onDocumentKeyDown, true);
|
246 | }
|
247 |
|
248 | hoverHandler = memoize(index => () =>
|
249 | scheduleHoverListener(() => {
|
250 | if (this.state.disabledHover) {
|
251 | return;
|
252 | }
|
253 |
|
254 | if (this.container) {
|
255 | this.setState({
|
256 | activeIndex: index,
|
257 | activeItem: this.props.data[index],
|
258 | needScrollToActive: false
|
259 | });
|
260 | }
|
261 | })
|
262 | );
|
263 |
|
264 | _activatableItems = false;
|
265 |
|
266 |
|
267 | _bufferSize = 10;
|
268 |
|
269 | sizeCacheKey = index => {
|
270 | if (index === 0 || index === this.props.data.length + 1) {
|
271 | return Type.MARGIN;
|
272 | }
|
273 |
|
274 | const item = this.props.data[index - 1];
|
275 | const isFirst = index === 1;
|
276 | switch (item.rgItemType) {
|
277 | case Type.SEPARATOR:
|
278 | case Type.TITLE:
|
279 | return `${item.rgItemType}${isFirst ? '_first' : ''}${item.description ? '_desc' : ''}`;
|
280 | case Type.MARGIN:
|
281 | return Type.MARGIN;
|
282 | case Type.CUSTOM:
|
283 | return `${Type.CUSTOM}_${item.key}`;
|
284 | case Type.ITEM:
|
285 | case Type.LINK:
|
286 | default:
|
287 | if (item.details) {
|
288 | return `${Type.ITEM}_${item.details}`;
|
289 | }
|
290 | return Type.ITEM;
|
291 | }
|
292 | };
|
293 |
|
294 | _cache = new CellMeasurerCache({
|
295 | defaultHeight: this.defaultItemHeight(),
|
296 | fixedWidth: true,
|
297 | keyMapper: this.sizeCacheKey
|
298 | });
|
299 |
|
300 | hasActivatableItems() {
|
301 | return this._activatableItems;
|
302 | }
|
303 |
|
304 | checkActivatableItems(items) {
|
305 | this._activatableItems = false;
|
306 | for (let i = 0; i < items.length; i++) {
|
307 | if (isActivatable(items[i])) {
|
308 | this._activatableItems = true;
|
309 | return;
|
310 | }
|
311 | }
|
312 | }
|
313 |
|
314 | selectHandler = memoize(index => event => {
|
315 | const item = this.props.data[index];
|
316 | if (!this.props.useMouseUp && item.onClick) {
|
317 | item.onClick(item, event);
|
318 | } else if (this.props.useMouseUp && item.onMouseUp) {
|
319 | item.onMouseUp(item, event);
|
320 | }
|
321 |
|
322 | if (this.props.onSelect) {
|
323 | this.props.onSelect(item, event);
|
324 | }
|
325 | });
|
326 |
|
327 | upHandler = e => {
|
328 | const {data, disableMoveOverflow} = this.props;
|
329 | const index = this.state.activeIndex;
|
330 | let newIndex;
|
331 |
|
332 | if (index === null || index === 0) {
|
333 | if (!disableMoveOverflow) {
|
334 | newIndex = data.length - 1;
|
335 | } else {
|
336 | return;
|
337 | }
|
338 | } else {
|
339 | newIndex = index - 1;
|
340 | }
|
341 |
|
342 | this.moveHandler(newIndex, this.upHandler, e);
|
343 | };
|
344 |
|
345 | downHandler = e => {
|
346 | const {data, disableMoveOverflow, disableMoveDownOverflow} = this.props;
|
347 | const index = this.state.activeIndex;
|
348 | let newIndex;
|
349 |
|
350 | if (index === null) {
|
351 | newIndex = 0;
|
352 | } else if (index + 1 === data.length) {
|
353 | if (!disableMoveOverflow && !disableMoveDownOverflow) {
|
354 | newIndex = 0;
|
355 | } else {
|
356 | return;
|
357 | }
|
358 | } else {
|
359 | newIndex = index + 1;
|
360 | }
|
361 |
|
362 | this.moveHandler(newIndex, this.downHandler, e);
|
363 | };
|
364 |
|
365 | homeHandler = e => {
|
366 | this.moveHandler(0, this.downHandler, e);
|
367 | };
|
368 |
|
369 | endHandler = e => {
|
370 | this.moveHandler(this.props.data.length - 1, this.upHandler, e);
|
371 | };
|
372 |
|
373 | onDocumentMouseMove = () => {
|
374 | if (this.state.disabledHover) {
|
375 | this.setState({disabledHover: false});
|
376 | }
|
377 | };
|
378 |
|
379 | onDocumentKeyDown = e => {
|
380 | const metaKeys = [16, 17, 18, 19, 20, 91];
|
381 | if (!this.state.disabledHover && !metaKeys.includes(e.keyCode)) {
|
382 | this.setState({disabledHover: true});
|
383 | }
|
384 | };
|
385 |
|
386 | moveHandler(index, retryCallback, e) {
|
387 | let correctedIndex;
|
388 | if (this.props.data.length === 0 || !this.hasActivatableItems()) {
|
389 | return;
|
390 | } else if (this.props.data.length < index) {
|
391 | correctedIndex = 0;
|
392 | } else {
|
393 | correctedIndex = index;
|
394 | }
|
395 |
|
396 | const item = this.props.data[correctedIndex];
|
397 | this.setState(
|
398 | {
|
399 | activeIndex: correctedIndex,
|
400 | activeItem: item,
|
401 | needScrollToActive: true
|
402 | },
|
403 | function onSet() {
|
404 | if (!isActivatable(item)) {
|
405 | retryCallback(e);
|
406 | return;
|
407 | }
|
408 |
|
409 | preventDefault(e);
|
410 | }
|
411 | );
|
412 | }
|
413 |
|
414 | mouseHandler = () => {
|
415 | this.setState({scrolling: false});
|
416 | };
|
417 |
|
418 | scrollHandler = () => {
|
419 | this.setState({scrolling: true}, this.scrollEndHandler);
|
420 | };
|
421 |
|
422 | enterHandler = (event, shortcut) => {
|
423 | if (this.state.activeIndex !== null) {
|
424 | const item = this.props.data[this.state.activeIndex];
|
425 | this.selectHandler(this.state.activeIndex)(event);
|
426 |
|
427 | if (item.href && !event.defaultPrevented) {
|
428 | if (['command+enter', 'ctrl+enter'].includes(shortcut)) {
|
429 | window.open(item.href, '_blank');
|
430 | } else if (shortcut === 'shift+enter') {
|
431 | window.open(item.href);
|
432 | } else {
|
433 | window.location.href = item.href;
|
434 | }
|
435 | }
|
436 | return false;
|
437 | } else {
|
438 | return true;
|
439 | }
|
440 | };
|
441 |
|
442 | getFirst() {
|
443 | return this.props.data.find(
|
444 | item => item.rgItemType === Type.ITEM || item.rgItemType === Type.CUSTOM
|
445 | );
|
446 | }
|
447 |
|
448 | getSelected() {
|
449 | return this.props.data[this.state.activeIndex];
|
450 | }
|
451 |
|
452 | clearSelected = () => {
|
453 | this.setState({
|
454 | activeIndex: null,
|
455 | needScrollToActive: false
|
456 | });
|
457 | };
|
458 |
|
459 | defaultItemHeight() {
|
460 | return this.props.compact ? Dimension.COMPACT_ITEM_HEIGHT : Dimension.ITEM_HEIGHT;
|
461 | }
|
462 |
|
463 | shouldActivateFirstItem(props) {
|
464 | return props.activateFirstItem ||
|
465 | props.activateSingleItem && props.length === 1;
|
466 | }
|
467 |
|
468 | scrollEndHandler = () => scheduleScrollListener(() => {
|
469 | const innerContainer = this.inner;
|
470 | if (innerContainer) {
|
471 | const maxScrollingPosition = innerContainer.scrollHeight;
|
472 | const sensitivity = this.defaultItemHeight() / 2;
|
473 | const currentScrollingPosition =
|
474 | innerContainer.scrollTop + innerContainer.clientHeight + sensitivity;
|
475 | const scrolledToBottom =
|
476 | maxScrollingPosition > 0 && currentScrollingPosition >= maxScrollingPosition;
|
477 | if (!this.unmounted) {
|
478 | this.setState({scrolledToBottom});
|
479 | }
|
480 | if (scrolledToBottom) {
|
481 | this.props.onScrollToBottom();
|
482 | }
|
483 | }
|
484 | });
|
485 |
|
486 | checkOverflow = () => {
|
487 | if (this.inner) {
|
488 | this.setState({
|
489 | hasOverflow: this.inner.scrollHeight - this.inner.clientHeight > 1
|
490 | });
|
491 | }
|
492 | };
|
493 |
|
494 | getVisibleListHeight(props) {
|
495 | return props.maxHeight - this.defaultItemHeight() - Dimension.INNER_PADDING;
|
496 | }
|
497 |
|
498 | renderItem = ({index, style, isScrolling, parent, key}) => {
|
499 | let itemKey;
|
500 | let el;
|
501 |
|
502 | const realIndex = index - 1;
|
503 |
|
504 | const item = this.props.data[realIndex];
|
505 |
|
506 |
|
507 | if (index === 0 || index === this.props.data.length + 1 || item.rgItemType === Type.MARGIN) {
|
508 | itemKey = key || `${Type.MARGIN}_${index}`;
|
509 | el = <div style={{height: Dimension.MARGIN}}/>;
|
510 | } else {
|
511 |
|
512 | // Hack around SelectNG implementation
|
513 | // eslint-disable-next-line no-unused-vars
|
514 | const {selectedLabel, originalModel, ...cleanedProps} = item;
|
515 | const itemProps = Object.assign({rgItemType: DEFAULT_ITEM_TYPE}, cleanedProps);
|
516 |
|
517 | if (itemProps.url) {
|
518 | itemProps.href = itemProps.url;
|
519 | }
|
520 | if (itemProps.href) {
|
521 | itemProps.rgItemType = Type.LINK;
|
522 | }
|
523 |
|
524 | // Probably unique enough key
|
525 | itemKey = key || itemProps.key ||
|
526 | `${itemProps.rgItemType}_${itemProps.label || itemProps.description}`;
|
527 |
|
528 | itemProps.hover = (realIndex === this.state.activeIndex);
|
529 | itemProps.onMouseOver = this.hoverHandler(realIndex);
|
530 | itemProps.tabIndex = -1;
|
531 | itemProps.scrolling = isScrolling;
|
532 |
|
533 | const selectHandler = this.selectHandler(realIndex);
|
534 |
|
535 | if (this.props.useMouseUp) {
|
536 | itemProps.onMouseUp = selectHandler;
|
537 | } else {
|
538 | itemProps.onClick = selectHandler;
|
539 | }
|
540 | itemProps.onCheckboxChange = selectHandler;
|
541 |
|
542 | if (itemProps.compact == null) {
|
543 | itemProps.compact = this.props.compact;
|
544 | }
|
545 |
|
546 | let ItemComponent;
|
547 | const isFirst = index === 1;
|
548 | switch (itemProps.rgItemType) {
|
549 | case Type.SEPARATOR:
|
550 | ItemComponent = ListSeparator;
|
551 | itemProps.isFirst = isFirst;
|
552 | break;
|
553 | case Type.LINK:
|
554 | ItemComponent = ListLink;
|
555 | this.addItemDataTestToProp(itemProps);
|
556 | break;
|
557 | case Type.ITEM:
|
558 | ItemComponent = ListItem;
|
559 | this.addItemDataTestToProp(itemProps);
|
560 | break;
|
561 | case Type.CUSTOM:
|
562 | ItemComponent = ListCustom;
|
563 | this.addItemDataTestToProp(itemProps);
|
564 | break;
|
565 | case Type.TITLE:
|
566 | itemProps.isFirst = isFirst;
|
567 | ItemComponent = ListTitle;
|
568 | break;
|
569 | default:
|
570 | throw new Error(`Unknown menu element type: ${itemProps.rgItemType}`);
|
571 | }
|
572 |
|
573 | el = <ItemComponent {...itemProps}/>;
|
574 | }
|
575 |
|
576 | return parent ? (
|
577 | <CellMeasurer
|
578 | cache={this._cache}
|
579 | key={itemKey}
|
580 | parent={parent}
|
581 | rowIndex={index}
|
582 | columnIndex={0}
|
583 | >
|
584 | <div style={style}>{el}</div>
|
585 | </CellMeasurer>
|
586 | ) : cloneElement(el, {key: itemKey});
|
587 | };
|
588 |
|
589 | addItemDataTestToProp = props => {
|
590 | props['data-test'] = dataTests('ring-list-item', props['data-test']);
|
591 | return props;
|
592 | };
|
593 |
|
594 | virtualizedListRef = el => {
|
595 | this.virtualizedList = el;
|
596 | };
|
597 |
|
598 | containerRef = el => {
|
599 | this.container = el;
|
600 | };
|
601 |
|
602 | get inner() {
|
603 | if (!this._inner) {
|
604 | this._inner = this.container && this.container.query('.ring-list__i');
|
605 | }
|
606 | return this._inner;
|
607 | }
|
608 |
|
609 | renderVirtualizedInner({
|
610 | height,
|
611 | maxHeight,
|
612 | autoHeight = false,
|
613 | rowCount,
|
614 | isScrolling,
|
615 | onChildScroll = noop,
|
616 | scrollTop
|
617 | }) {
|
618 | const dirOverride = {direction: 'auto'}; // Virtualized sets "direction: ltr" by defaulthttps://github.com/bvaughn/react-virtualized/issues/457
|
619 | return (
|
620 | <AutoSizer disableHeight onResize={this.props.onResize}>
|
621 | {({width}) => (
|
622 | <VirtualizedList
|
623 | ref={this.virtualizedListRef}
|
624 | className="ring-list__i"
|
625 | autoHeight={autoHeight}
|
626 | style={maxHeight ? {maxHeight, height: 'auto', ...dirOverride} : dirOverride}
|
627 | autoContainerWidth
|
628 | height={height}
|
629 | width={width}
|
630 | isScrolling={isScrolling}
|
631 | // eslint-disable-next-line react/jsx-no-bind
|
632 | onScroll={e => {
|
633 | onChildScroll(e);
|
634 | this.scrollEndHandler(e);
|
635 | }}
|
636 | scrollTop={scrollTop}
|
637 | rowCount={rowCount}
|
638 | estimatedRowSize={this.defaultItemHeight()}
|
639 | rowHeight={this._cache.rowHeight}
|
640 | rowRenderer={this.renderItem}
|
641 | overscanRowCount={this._bufferSize}
|
642 |
|
643 | // ensure rerendering
|
644 | // eslint-disable-next-line react/jsx-no-bind
|
645 | noop={() => {}}
|
646 |
|
647 | scrollToIndex={
|
648 | this.state.needScrollToActive && this.state.activeIndex != null
|
649 | ? this.state.activeIndex + 1
|
650 | : undefined
|
651 | }
|
652 | scrollToAlignment="center"
|
653 | deferredMeasurementCache={this._cache}
|
654 | onRowsRendered={this.checkOverflow}
|
655 | />
|
656 | )}
|
657 | </AutoSizer>
|
658 | );
|
659 | }
|
660 |
|
661 | renderVirtualized(maxHeight, rowCount) {
|
662 | if (maxHeight) {
|
663 | return this.renderVirtualizedInner({height: maxHeight, maxHeight, rowCount});
|
664 | }
|
665 |
|
666 | return (
|
667 | <WindowScroller>
|
668 | {props => this.renderVirtualizedInner({...props, rowCount, autoHeight: true})}
|
669 | </WindowScroller>
|
670 | );
|
671 | }
|
672 |
|
673 | renderSimple(maxHeight, rowCount) {
|
674 | const items = [];
|
675 |
|
676 | for (let index = 0; index < rowCount; index++) {
|
677 | items.push(this.renderItem({
|
678 | index,
|
679 | isScrolling: this.state.scrolling
|
680 | }));
|
681 | }
|
682 |
|
683 | return (
|
684 | <div
|
685 | className={classNames('ring-list__i', styles.simpleInner)}
|
686 | onScroll={this.scrollHandler}
|
687 | onMouseMove={this.mouseHandler}
|
688 | >
|
689 | <div
|
690 | style={maxHeight
|
691 | ? {maxHeight: this.getVisibleListHeight(this.props)}
|
692 | : null
|
693 | }
|
694 | >
|
695 | {items}
|
696 | </div>
|
697 | </div>
|
698 | );
|
699 | }
|
700 |
|
701 | shortcutsScope = getUID('list-');
|
702 | shortcutsMap = {
|
703 | up: this.upHandler,
|
704 | down: this.downHandler,
|
705 | home: this.homeHandler,
|
706 | end: this.endHandler,
|
707 | enter: this.enterHandler,
|
708 | 'meta+enter': this.enterHandler,
|
709 | 'ctrl+enter': this.enterHandler,
|
710 | 'command+enter': this.enterHandler,
|
711 | 'shift+enter': this.enterHandler
|
712 | };
|
713 |
|
714 | /** @override */
|
715 | render() {
|
716 | const hint = this.getSelected() && this.props.hintOnSelection || this.props.hint;
|
717 | const fadeStyles = hint ? {bottom: Dimension.ITEM_HEIGHT} : null;
|
718 |
|
719 | const rowCount = this.props.data.length + 2;
|
720 |
|
721 | const maxHeight = this.props.maxHeight && this.getVisibleListHeight(this.props);
|
722 |
|
723 | const classes = classNames(styles.list, this.props.className);
|
724 |
|
725 | return (
|
726 | <div
|
727 | ref={this.containerRef}
|
728 | className={classes}
|
729 | onMouseOut={this.props.onMouseOut}
|
730 | onMouseLeave={this.clearSelected}
|
731 | data-test="ring-list"
|
732 | >
|
733 | {this.props.shortcuts &&
|
734 | (
|
735 | <Shortcuts
|
736 | map={this.shortcutsMap}
|
737 | scope={this.shortcutsScope}
|
738 | />
|
739 | )
|
740 | }
|
741 | {this.props.renderOptimization
|
742 | ? this.renderVirtualized(maxHeight, rowCount)
|
743 | : this.renderSimple(maxHeight, rowCount)
|
744 | }
|
745 | {this.state.hasOverflow && !this.state.scrolledToBottom && (
|
746 | <div
|
747 | className={styles.fade}
|
748 | style={fadeStyles}
|
749 | />
|
750 | )}
|
751 | {hint && (
|
752 | <ListHint
|
753 | label={hint}
|
754 | />
|
755 | )}
|
756 | </div>
|
757 | );
|
758 | }
|
759 | }
|