Feat: Suggestion list in Explore is virtualized (#16342)
* Wip: virtualize suggestions list * Refactor: Separate components to different files * Refactor: Made TypeaheadItem a FunctionComponent using emotion * Refactor: Use theme to calculate width instead of hardcoded values * Refactor: Calculate list height and item size * Style: Adds labels to emotion classes * Refactor: Flattens CompletionItems to one list * Chore: merge yarn.lock * Refactor: Adds documentation popup on the side * Refactor: Makes position of TypeaheadInfo dynamic * Refactor: Calculations moved to separate file
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
"@types/react-select": "^2.0.4",
|
||||
"@types/react-transition-group": "^2.0.15",
|
||||
"@types/react-virtualized": "^9.18.12",
|
||||
"@types/react-window": "1.7.0",
|
||||
"angular-mocks": "1.6.6",
|
||||
"autoprefixer": "^9.4.10",
|
||||
"axios": "^0.18.0",
|
||||
@@ -180,6 +181,7 @@
|
||||
"angular-sanitize": "1.6.6",
|
||||
"baron": "^3.0.3",
|
||||
"brace": "^0.10.0",
|
||||
"calculate-size": "1.1.1",
|
||||
"classnames": "^2.2.6",
|
||||
"clipboard": "^2.0.4",
|
||||
"d3": "^4.11.0",
|
||||
@@ -207,6 +209,7 @@
|
||||
"react-table": "^6.8.6",
|
||||
"react-transition-group": "^2.2.1",
|
||||
"react-virtualized": "^9.21.0",
|
||||
"react-window": "1.7.1",
|
||||
"redux": "^4.0.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.3.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ThemeContext, withTheme } from './ThemeContext';
|
||||
import { getTheme, mockTheme } from './getTheme';
|
||||
import { selectThemeVariant } from './selectThemeVariant';
|
||||
|
||||
export { ThemeContext, withTheme, mockTheme, getTheme };
|
||||
export { ThemeContext, withTheme, mockTheme, getTheme, selectThemeVariant };
|
||||
|
||||
@@ -15,7 +15,7 @@ import { CompletionItem, CompletionItemGroup, TypeaheadOutput } from 'app/types/
|
||||
import ClearPlugin from './slate-plugins/clear';
|
||||
import NewlinePlugin from './slate-plugins/newline';
|
||||
|
||||
import Typeahead from './Typeahead';
|
||||
import { TypeaheadWithTheme } from './Typeahead';
|
||||
import { makeFragment, makeValue } from './Value';
|
||||
import PlaceholdersBuffer from './PlaceholdersBuffer';
|
||||
|
||||
@@ -359,7 +359,11 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
if (this.menuEl) {
|
||||
// Select next suggestion
|
||||
event.preventDefault();
|
||||
this.setState({ typeaheadIndex: typeaheadIndex + 1 });
|
||||
const itemsCount =
|
||||
this.state.suggestions.length > 0
|
||||
? this.state.suggestions.reduce((totalCount, current) => totalCount + current.items.length, 0)
|
||||
: 0;
|
||||
this.setState({ typeaheadIndex: Math.min(itemsCount - 1, typeaheadIndex + 1) });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -461,12 +465,13 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
// Create typeahead in DOM root so we can later position it absolutely
|
||||
return (
|
||||
<Portal origin={portalOrigin}>
|
||||
<Typeahead
|
||||
<TypeaheadWithTheme
|
||||
menuRef={this.menuRef}
|
||||
selectedItem={selectedItem}
|
||||
onClickItem={this.onClickMenu}
|
||||
prefix={typeaheadPrefix}
|
||||
groupedItems={suggestions}
|
||||
typeaheadIndex={typeaheadIndex}
|
||||
/>
|
||||
</Portal>
|
||||
);
|
||||
|
||||
@@ -1,107 +1,132 @@
|
||||
import React from 'react';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import React, { createRef } from 'react';
|
||||
// @ts-ignore
|
||||
import _ from 'lodash';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
|
||||
import { Themeable, withTheme } from '@grafana/ui';
|
||||
|
||||
import { CompletionItem, CompletionItemGroup } from 'app/types/explore';
|
||||
import { TypeaheadItem } from './TypeaheadItem';
|
||||
import { TypeaheadInfo } from './TypeaheadInfo';
|
||||
import { flattenGroupItems, calculateLongestLabel, calculateListSizes } from './utils/typeahead';
|
||||
|
||||
function scrollIntoView(el: HTMLElement) {
|
||||
if (!el || !el.offsetParent) {
|
||||
return;
|
||||
}
|
||||
const container = el.offsetParent as HTMLElement;
|
||||
if (el.offsetTop > container.scrollTop + container.offsetHeight || el.offsetTop < container.scrollTop) {
|
||||
container.scrollTop = el.offsetTop - container.offsetTop;
|
||||
}
|
||||
}
|
||||
|
||||
interface TypeaheadItemProps {
|
||||
isSelected: boolean;
|
||||
item: CompletionItem;
|
||||
onClickItem: (Suggestion) => void;
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
class TypeaheadItem extends React.PureComponent<TypeaheadItemProps> {
|
||||
el: HTMLElement;
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.isSelected && !prevProps.isSelected) {
|
||||
requestAnimationFrame(() => {
|
||||
scrollIntoView(this.el);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getRef = el => {
|
||||
this.el = el;
|
||||
};
|
||||
|
||||
onClick = () => {
|
||||
this.props.onClickItem(this.props.item);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isSelected, item, prefix } = this.props;
|
||||
const className = isSelected ? 'typeahead-item typeahead-item__selected' : 'typeahead-item';
|
||||
const label = item.label || '';
|
||||
return (
|
||||
<li ref={this.getRef} className={className} onClick={this.onClick}>
|
||||
<Highlighter textToHighlight={label} searchWords={[prefix]} highlightClassName="typeahead-match" />
|
||||
{item.documentation && isSelected ? <div className="typeahead-item-hint">{item.documentation}</div> : null}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface TypeaheadGroupProps {
|
||||
items: CompletionItem[];
|
||||
label: string;
|
||||
onClickItem: (suggestion: CompletionItem) => void;
|
||||
selected: CompletionItem;
|
||||
prefix?: string;
|
||||
}
|
||||
|
||||
class TypeaheadGroup extends React.PureComponent<TypeaheadGroupProps> {
|
||||
render() {
|
||||
const { items, label, selected, onClickItem, prefix } = this.props;
|
||||
return (
|
||||
<li className="typeahead-group">
|
||||
<div className="typeahead-group__title">{label}</div>
|
||||
<ul className="typeahead-group__list">
|
||||
{items.map(item => {
|
||||
return (
|
||||
<TypeaheadItem
|
||||
key={item.label}
|
||||
onClickItem={onClickItem}
|
||||
isSelected={selected === item}
|
||||
item={item}
|
||||
prefix={prefix}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface TypeaheadProps {
|
||||
interface Props extends Themeable {
|
||||
groupedItems: CompletionItemGroup[];
|
||||
menuRef: any;
|
||||
selectedItem: CompletionItem | null;
|
||||
onClickItem: (Suggestion) => void;
|
||||
onClickItem: (suggestion: CompletionItem) => void;
|
||||
prefix?: string;
|
||||
typeaheadIndex: number;
|
||||
}
|
||||
class Typeahead extends React.PureComponent<TypeaheadProps> {
|
||||
|
||||
interface State {
|
||||
allItems: CompletionItem[];
|
||||
listWidth: number;
|
||||
listHeight: number;
|
||||
itemHeight: number;
|
||||
}
|
||||
|
||||
export class Typeahead extends React.PureComponent<Props, State> {
|
||||
listRef: any = createRef();
|
||||
documentationRef: any = createRef();
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const allItems = flattenGroupItems(props.groupedItems);
|
||||
const longestLabel = calculateLongestLabel(allItems);
|
||||
const { listWidth, listHeight, itemHeight } = calculateListSizes(props.theme, allItems, longestLabel);
|
||||
this.state = { listWidth, listHeight, itemHeight, allItems };
|
||||
}
|
||||
|
||||
componentDidUpdate = (prevProps: Readonly<Props>) => {
|
||||
if (prevProps.typeaheadIndex !== this.props.typeaheadIndex && this.listRef && this.listRef.current) {
|
||||
if (prevProps.typeaheadIndex === 1 && this.props.typeaheadIndex === 0) {
|
||||
this.listRef.current.scrollToItem(0); // special case for handling the first group label
|
||||
this.refreshDocumentation();
|
||||
return;
|
||||
}
|
||||
const index = this.state.allItems.findIndex(item => item === this.props.selectedItem);
|
||||
this.listRef.current.scrollToItem(index);
|
||||
this.refreshDocumentation();
|
||||
}
|
||||
|
||||
if (_.isEqual(prevProps.groupedItems, this.props.groupedItems) === false) {
|
||||
const allItems = flattenGroupItems(this.props.groupedItems);
|
||||
const longestLabel = calculateLongestLabel(allItems);
|
||||
const { listWidth, listHeight, itemHeight } = calculateListSizes(this.props.theme, allItems, longestLabel);
|
||||
this.setState({ listWidth, listHeight, itemHeight, allItems }, () => this.refreshDocumentation());
|
||||
}
|
||||
};
|
||||
|
||||
refreshDocumentation = () => {
|
||||
if (!this.documentationRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this.state.allItems.findIndex(item => item === this.props.selectedItem);
|
||||
const item = this.state.allItems[index];
|
||||
|
||||
if (item) {
|
||||
this.documentationRef.current.refresh(item);
|
||||
}
|
||||
};
|
||||
|
||||
onMouseEnter = (item: CompletionItem) => {
|
||||
this.documentationRef.current.refresh(item);
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
this.documentationRef.current.hide();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { groupedItems, menuRef, selectedItem, onClickItem, prefix } = this.props;
|
||||
const { menuRef, selectedItem, onClickItem, prefix, theme } = this.props;
|
||||
const { listWidth, listHeight, itemHeight, allItems } = this.state;
|
||||
|
||||
return (
|
||||
<ul className="typeahead" ref={menuRef}>
|
||||
{groupedItems.map(g => (
|
||||
<TypeaheadGroup key={g.label} onClickItem={onClickItem} prefix={prefix} selected={selectedItem} {...g} />
|
||||
))}
|
||||
<TypeaheadInfo
|
||||
ref={this.documentationRef}
|
||||
width={listWidth}
|
||||
height={listHeight}
|
||||
theme={theme}
|
||||
initialItem={selectedItem}
|
||||
/>
|
||||
<FixedSizeList
|
||||
ref={this.listRef}
|
||||
itemCount={allItems.length}
|
||||
itemSize={itemHeight}
|
||||
itemKey={index => {
|
||||
const item = allItems && allItems[index];
|
||||
const key = item ? `${index}-${item.label}` : `${index}`;
|
||||
return key;
|
||||
}}
|
||||
width={listWidth}
|
||||
height={listHeight}
|
||||
>
|
||||
{({ index, style }) => {
|
||||
const item = allItems && allItems[index];
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TypeaheadItem
|
||||
onClickItem={onClickItem}
|
||||
isSelected={selectedItem === item}
|
||||
item={item}
|
||||
prefix={prefix}
|
||||
style={style}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</FixedSizeList>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Typeahead;
|
||||
export const TypeaheadWithTheme = withTheme(Typeahead);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Themeable, selectThemeVariant } from '@grafana/ui';
|
||||
import { css, cx } from 'emotion';
|
||||
|
||||
import { CompletionItem } from 'app/types/explore';
|
||||
|
||||
interface Props extends Themeable {
|
||||
initialItem: CompletionItem;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
item: CompletionItem;
|
||||
}
|
||||
|
||||
export class TypeaheadInfo extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { item: props.initialItem };
|
||||
}
|
||||
|
||||
getStyles = (visible: boolean) => {
|
||||
const { width, height, theme } = this.props;
|
||||
const selection = window.getSelection();
|
||||
const node = selection.anchorNode;
|
||||
if (!node) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Read from DOM
|
||||
const rect = node.parentElement.getBoundingClientRect();
|
||||
const scrollX = window.scrollX;
|
||||
const scrollY = window.scrollY;
|
||||
const left = `${rect.left + scrollX + width + parseInt(theme.spacing.xs, 10)}px`;
|
||||
const top = `${rect.top + scrollY + rect.height + 6}px`;
|
||||
|
||||
return {
|
||||
typeaheadItem: css`
|
||||
label: type-ahead-item;
|
||||
z-index: auto;
|
||||
padding: ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.md};
|
||||
border-radius: ${theme.border.radius.md};
|
||||
border: ${selectThemeVariant(
|
||||
{ light: `solid 1px ${theme.colors.gray5}`, dark: `solid 1px ${theme.colors.dark1}` },
|
||||
theme.type
|
||||
)};
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
outline: none;
|
||||
background: ${selectThemeVariant({ light: theme.colors.white, dark: theme.colors.dark4 }, theme.type)};
|
||||
color: ${theme.colors.text};
|
||||
box-shadow: ${selectThemeVariant(
|
||||
{ light: `0 5px 10px 0 ${theme.colors.gray5}`, dark: `0 5px 10px 0 ${theme.colors.black}` },
|
||||
theme.type
|
||||
)};
|
||||
visibility: ${visible === true ? 'visible' : 'hidden'};
|
||||
left: ${left};
|
||||
top: ${top};
|
||||
width: 250px;
|
||||
height: ${height + parseInt(theme.spacing.xxs, 10)}px;
|
||||
position: fixed;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
refresh = (item: CompletionItem) => {
|
||||
this.setState({ item });
|
||||
};
|
||||
|
||||
hide = () => {
|
||||
this.setState({ item: null });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { item } = this.state;
|
||||
const visible = item && !!item.documentation;
|
||||
const label = item ? item.label : '';
|
||||
const documentation = item && item.documentation ? item.documentation : '';
|
||||
const styles = this.getStyles(visible);
|
||||
|
||||
return (
|
||||
<div className={cx([styles.typeaheadItem])}>
|
||||
<b>{label}</b>
|
||||
<hr />
|
||||
<span>{documentation}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import React, { FunctionComponent, useContext } from 'react';
|
||||
// @ts-ignore
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import { css, cx } from 'emotion';
|
||||
import { GrafanaTheme, ThemeContext, selectThemeVariant } from '@grafana/ui';
|
||||
|
||||
import { CompletionItem } from 'app/types/explore';
|
||||
|
||||
export const GROUP_TITLE_KIND = 'GroupTitle';
|
||||
|
||||
export const isGroupTitle = (item: CompletionItem) => {
|
||||
return item.kind && item.kind === GROUP_TITLE_KIND ? true : false;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
isSelected: boolean;
|
||||
item: CompletionItem;
|
||||
onClickItem: (suggestion: CompletionItem) => void;
|
||||
prefix?: string;
|
||||
style: any;
|
||||
onMouseEnter: (item: CompletionItem) => void;
|
||||
onMouseLeave: (item: CompletionItem) => void;
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
typeaheadItem: css`
|
||||
label: type-ahead-item;
|
||||
height: auto;
|
||||
font-family: ${theme.typography.fontFamily.monospace};
|
||||
padding: ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.sm} ${theme.spacing.md};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), padding 0.15s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
`,
|
||||
typeaheadItemSelected: css`
|
||||
label: type-ahead-item-selected;
|
||||
background-color: ${selectThemeVariant({ light: theme.colors.gray6, dark: theme.colors.dark9 }, theme.type)};
|
||||
`,
|
||||
typeaheadItemMatch: css`
|
||||
label: type-ahead-item-match;
|
||||
color: ${theme.colors.yellow};
|
||||
border-bottom: 1px solid ${theme.colors.yellow};
|
||||
padding: inherit;
|
||||
background: inherit;
|
||||
`,
|
||||
typeaheadItemGroupTitle: css`
|
||||
label: type-ahead-item-group-title;
|
||||
color: ${theme.colors.textWeak};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
line-height: ${theme.typography.lineHeight.lg};
|
||||
padding: ${theme.spacing.sm};
|
||||
`,
|
||||
});
|
||||
|
||||
export const TypeaheadItem: FunctionComponent<Props> = (props: Props) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
const styles = getStyles(theme);
|
||||
|
||||
const { isSelected, item, prefix, style, onClickItem } = props;
|
||||
const onClick = () => onClickItem(item);
|
||||
const onMouseEnter = () => props.onMouseEnter(item);
|
||||
const onMouseLeave = () => props.onMouseLeave(item);
|
||||
const className = isSelected ? cx([styles.typeaheadItem, styles.typeaheadItemSelected]) : cx([styles.typeaheadItem]);
|
||||
const highlightClassName = cx([styles.typeaheadItemMatch]);
|
||||
const itemGroupTitleClassName = cx([styles.typeaheadItemGroupTitle]);
|
||||
const label = item.label || '';
|
||||
|
||||
if (isGroupTitle(item)) {
|
||||
return (
|
||||
<li className={itemGroupTitleClassName} style={style}>
|
||||
<span>{label}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={className} onClick={onClick} style={style} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
<Highlighter textToHighlight={label} searchWords={[prefix]} highlightClassName={highlightClassName} />
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { GrafanaTheme } from '@grafana/ui';
|
||||
import { default as calculateSize } from 'calculate-size';
|
||||
|
||||
import { CompletionItemGroup, CompletionItem } from 'app/types';
|
||||
import { GROUP_TITLE_KIND } from '../TypeaheadItem';
|
||||
|
||||
export const flattenGroupItems = (groupedItems: CompletionItemGroup[]): CompletionItem[] => {
|
||||
return groupedItems.reduce((all, current) => {
|
||||
const titleItem: CompletionItem = {
|
||||
label: current.label,
|
||||
kind: GROUP_TITLE_KIND,
|
||||
};
|
||||
return all.concat(titleItem, current.items);
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const calculateLongestLabel = (allItems: CompletionItem[]): string => {
|
||||
return allItems.reduce((longest, current) => {
|
||||
return longest.length < current.label.length ? current.label : longest;
|
||||
}, '');
|
||||
};
|
||||
|
||||
export const calculateListSizes = (theme: GrafanaTheme, allItems: CompletionItem[], longestLabel: string) => {
|
||||
const size = calculateSize(longestLabel, {
|
||||
font: theme.typography.fontFamily.monospace,
|
||||
fontSize: theme.typography.size.sm,
|
||||
fontWeight: 'normal',
|
||||
});
|
||||
|
||||
const listWidth = calculateListWidth(size.width, theme);
|
||||
const itemHeight = calculateItemHeight(size.height, theme);
|
||||
const listHeight = calculateListHeight(itemHeight, allItems);
|
||||
|
||||
return {
|
||||
listWidth,
|
||||
listHeight,
|
||||
itemHeight,
|
||||
};
|
||||
};
|
||||
|
||||
export const calculateItemHeight = (longestLabelHeight: number, theme: GrafanaTheme) => {
|
||||
const horizontalPadding = parseInt(theme.spacing.sm, 10) * 2;
|
||||
const itemHeight = longestLabelHeight + horizontalPadding;
|
||||
|
||||
return itemHeight;
|
||||
};
|
||||
|
||||
export const calculateListWidth = (longestLabelWidth: number, theme: GrafanaTheme) => {
|
||||
const verticalPadding = parseInt(theme.spacing.sm, 10) + parseInt(theme.spacing.md, 10);
|
||||
const maxWidth = 800;
|
||||
const listWidth = Math.min(Math.max(longestLabelWidth + verticalPadding, 200), maxWidth);
|
||||
|
||||
return listWidth;
|
||||
};
|
||||
|
||||
export const calculateListHeight = (itemHeight: number, allItems: CompletionItem[]) => {
|
||||
const numberOfItemsToShow = Math.min(allItems.length, 10);
|
||||
const minHeight = 100;
|
||||
const itemsInView = allItems.slice(0, numberOfItemsToShow);
|
||||
const totalHeight = itemsInView.length * itemHeight;
|
||||
const listHeight = Math.max(totalHeight, minHeight);
|
||||
|
||||
return listHeight;
|
||||
};
|
||||
@@ -2439,6 +2439,13 @@
|
||||
"@types/prop-types" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-window@1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.7.0.tgz#8dd99822c54380c9c05df213b7b4400c24c9877e"
|
||||
integrity sha512-HyxhB3TFL/2WKRi69paA1Ch7kowomhR2eSZe7sJz8OtKuNhzRrlYSteSID7GIUpV95k246iVOlxEXmG2bjZQFA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@16.8.8", "@types/react@^16.8.8":
|
||||
version "16.8.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.8.tgz#4b60a469fd2469f7aa6eaa0f8cfbc51f6d76e662"
|
||||
@@ -4331,6 +4338,11 @@ cache-base@^1.0.1:
|
||||
union-value "^1.0.0"
|
||||
unset-value "^1.0.0"
|
||||
|
||||
calculate-size@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/calculate-size/-/calculate-size-1.1.1.tgz#ae7caa1c7795f82c4f035dc7be270e3581dae3ee"
|
||||
integrity sha1-rnyqHHeV+CxPA13HvicONYHa4+4=
|
||||
|
||||
call-limit@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.0.tgz#6fd61b03f3da42a2cd0ec2b60f02bd0e71991fea"
|
||||
@@ -11171,16 +11183,16 @@ mem@^4.0.0:
|
||||
mimic-fn "^2.0.0"
|
||||
p-is-promise "^2.0.0"
|
||||
|
||||
"memoize-one@>=3.1.1 <6", memoize-one@^5.0.0:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.2.tgz#6aba5276856d72fb44ead3efab86432f94ba203d"
|
||||
integrity sha512-o7lldN4fs/axqctc03NF+PMhd2veRrWeJ2n2GjEzUPBD4F9rmNg4A+bQCACIzwjHJEXuYv4aFFMaH35KZfHUrw==
|
||||
|
||||
memoize-one@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906"
|
||||
integrity sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA==
|
||||
|
||||
memoize-one@^5.0.0:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.2.tgz#6aba5276856d72fb44ead3efab86432f94ba203d"
|
||||
integrity sha512-o7lldN4fs/axqctc03NF+PMhd2veRrWeJ2n2GjEzUPBD4F9rmNg4A+bQCACIzwjHJEXuYv4aFFMaH35KZfHUrw==
|
||||
|
||||
memoizerific@^1.11.3:
|
||||
version "1.11.3"
|
||||
resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a"
|
||||
@@ -14324,6 +14336,14 @@ react-virtualized@^9.21.0:
|
||||
prop-types "^15.6.0"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react-window@1.7.1:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.7.1.tgz#c1db640415b97b85bc0a1c66eb82dadabca39b86"
|
||||
integrity sha512-y4/Qc98agCtHulpeI5b6K2Hh8J7TeZIfvccBVesfqOFx4CS+TSUpnJl1/ipeXzhfvzPwvVEmaU/VosQ6O5VhTg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
memoize-one ">=3.1.1 <6"
|
||||
|
||||
react@^16.8.1:
|
||||
version "16.8.6"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
||||
|
||||
Reference in New Issue
Block a user