kopia lustrzana https://github.com/wagtail/wagtail
Add new ComboBox React component based on downshift
rodzic
46d92d8711
commit
3a7e489cdf
|
@ -101,6 +101,7 @@ These are classes for components.
|
|||
@import '../src/components/Transition/Transition';
|
||||
@import '../src/components/LoadingSpinner/LoadingSpinner';
|
||||
@import '../src/components/PublicationStatus/PublicationStatus';
|
||||
@import '../src/components/ComboBox/ComboBox';
|
||||
@import '../src/components/PageExplorer/PageExplorer';
|
||||
@import '../src/components/CommentApp/main';
|
||||
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
// Ensure consistent spacing across the whole component.
|
||||
// With the scrolling and show/hide of the field, correct spacing is critical.
|
||||
$spacing: theme('spacing.[2.5]');
|
||||
$spacing-sm: theme('spacing.5');
|
||||
|
||||
.w-combobox {
|
||||
width: min(400px, 80vw);
|
||||
background: $color-white;
|
||||
color: $color-input-text;
|
||||
border-radius: theme('borderRadius.DEFAULT');
|
||||
font-size: theme('fontSize.18');
|
||||
box-shadow: theme('boxShadow.md');
|
||||
outline: 10px solid transparent;
|
||||
}
|
||||
|
||||
.w-combobox__field {
|
||||
padding: $spacing;
|
||||
padding-bottom: 0;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
padding: $spacing-sm;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.w-combobox [role='combobox'] {
|
||||
margin-bottom: $spacing-sm;
|
||||
|
||||
&[disabled] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.w-combobox__menu {
|
||||
max-height: min(480px, 70vh);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.w-combobox__optgroup {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-flow: column;
|
||||
gap: theme('spacing.[0.5]');
|
||||
padding: $spacing;
|
||||
padding-top: 0;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
width: 400px;
|
||||
padding: $spacing-sm;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.w-combobox__optgroup-label {
|
||||
@apply w-label-3;
|
||||
grid-column: 1 / span 2;
|
||||
margin-bottom: $spacing;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
margin-bottom: $spacing-sm;
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
color: GrayText;
|
||||
}
|
||||
}
|
||||
|
||||
.w-combobox__option {
|
||||
display: grid;
|
||||
grid-template-columns: theme('spacing.8') 1fr;
|
||||
align-items: center;
|
||||
padding: theme('spacing.1');
|
||||
border: 1px solid transparent;
|
||||
font-size: 0.875rem;
|
||||
line-height: theme('lineHeight.tight');
|
||||
border-radius: theme('borderRadius.sm');
|
||||
|
||||
&[aria-selected='true'] {
|
||||
border-color: currentColor;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
@media (forced-colors: active) {
|
||||
background: Highlight;
|
||||
color: HighlightText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.w-combobox__option--col1 {
|
||||
grid-column: 1 / span 1;
|
||||
}
|
||||
|
||||
.w-combobox__option--col2 {
|
||||
grid-column: 2 / span 1;
|
||||
}
|
||||
|
||||
.w-combobox__option-icon {
|
||||
color: theme('colors.grey.200');
|
||||
height: theme('spacing.4');
|
||||
|
||||
.icon {
|
||||
width: theme('spacing.4');
|
||||
height: theme('spacing.4');
|
||||
}
|
||||
|
||||
// Give more width to icons with wide visuals.
|
||||
.icon-h1,
|
||||
.icon-h2,
|
||||
.icon-h3,
|
||||
.icon-h4,
|
||||
.icon-h5,
|
||||
.icon-h6 {
|
||||
width: theme('spacing.6');
|
||||
}
|
||||
|
||||
// Explicitly override the selected color for SVG support.
|
||||
[aria-selected='true'] & {
|
||||
@media (forced-colors: active) {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.w-combobox__option-text {
|
||||
// Force to CanvasText even when highlighted, because the extra div
|
||||
// makes WHCM add a mandatory Canvas background below the text.
|
||||
@media (forced-colors: active) {
|
||||
color: CanvasText;
|
||||
}
|
||||
}
|
||||
|
||||
.w-combobox__status {
|
||||
padding: $spacing-sm;
|
||||
|
||||
@media (forced-colors: active) {
|
||||
color: GrayText;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import ComboBox from './ComboBox';
|
||||
import Icon from '../Icon/Icon';
|
||||
|
||||
const testProps = {
|
||||
label: 'Search options…',
|
||||
placeholder: 'Search options…',
|
||||
getItemLabel: (_, item) => item.label,
|
||||
getItemDescription: (item) => item.description,
|
||||
getSearchFields: (item) => [item.label, item.description, item.type],
|
||||
noResultsText: 'No results, sorry!',
|
||||
onSelect: () => {},
|
||||
};
|
||||
|
||||
describe('ComboBox', () => {
|
||||
it('renders empty', () => {
|
||||
const wrapper = shallow(<ComboBox {...testProps} items={[]} />);
|
||||
expect(wrapper.find('.w-combobox__status').text()).toBe(
|
||||
'No results, sorry!',
|
||||
);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
let items;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
items = [
|
||||
{
|
||||
type: 'blockTypes',
|
||||
label: 'Blocks',
|
||||
items: [
|
||||
{
|
||||
type: 'blockquote',
|
||||
description: 'Blockquote',
|
||||
icon: 'blockquote',
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
description: 'Paragraph',
|
||||
icon: <span className="custom-icon">P</span>,
|
||||
},
|
||||
{
|
||||
type: 'heading-one',
|
||||
label: 'H1',
|
||||
description: 'Heading 1',
|
||||
},
|
||||
{
|
||||
type: 'heading-two',
|
||||
label: 'H2',
|
||||
render: ({ option }) => (
|
||||
<span className="custom-text">{option.label}</span>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'entityTypes',
|
||||
items: [
|
||||
{
|
||||
type: 'link',
|
||||
label: '🔗',
|
||||
description: 'Link',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
wrapper = shallow(<ComboBox {...testProps} items={items} />);
|
||||
});
|
||||
|
||||
it('shows items', () => {
|
||||
const options = wrapper.find('.w-combobox__option-text');
|
||||
expect(options).toHaveLength(
|
||||
items[0].items.length + items[1].items.length,
|
||||
);
|
||||
expect(options.at(0).text()).toBe('Blockquote');
|
||||
});
|
||||
|
||||
it('uses Icon component', () => {
|
||||
expect(wrapper.find(Icon).at(0).prop('name')).toBe('blockquote');
|
||||
});
|
||||
|
||||
it('supports custom icons', () => {
|
||||
expect(wrapper.find('.custom-icon').text()).toBe('P');
|
||||
});
|
||||
|
||||
it('supports label as icon', () => {
|
||||
expect(wrapper.find('.custom-text').text()).toBe('H2');
|
||||
});
|
||||
|
||||
it('combines two categories into one, with two columns', () => {
|
||||
expect(wrapper.find('.w-combobox__optgroup-label')).toHaveLength(1);
|
||||
expect(wrapper.find('.w-combobox__option--col1')).toHaveLength(3);
|
||||
expect(wrapper.find('.w-combobox__option--col2')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,240 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
useCombobox,
|
||||
UseComboboxStateChange,
|
||||
UseComboboxStateChangeTypes,
|
||||
} from 'downshift';
|
||||
|
||||
import { gettext } from '../../utils/gettext';
|
||||
import Icon from '../Icon/Icon';
|
||||
|
||||
import findMatches from './findMatches';
|
||||
|
||||
export const comboBoxTriggerLabel = gettext('Insert a block');
|
||||
export const comboBoxLabel = gettext('Search options…');
|
||||
export const comboBoxNoResults = gettext('No results');
|
||||
|
||||
export interface ComboBoxCategory<ItemType> {
|
||||
type: string;
|
||||
label: string | null;
|
||||
items: ItemType[];
|
||||
}
|
||||
|
||||
export interface ComboBoxItem {
|
||||
type?: string;
|
||||
label?: string | null;
|
||||
description?: string | null;
|
||||
icon?: string | JSX.Element | null;
|
||||
category?: string;
|
||||
render?: (props: { option: ComboBoxItem }) => JSX.Element | string;
|
||||
}
|
||||
|
||||
export { UseComboboxStateChange };
|
||||
|
||||
export type ComboBoxStateChange = UseComboboxStateChange<ComboBoxItem>;
|
||||
|
||||
export interface ComboBoxProps<ComboBoxOption> {
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
inputValue?: string;
|
||||
items: ComboBoxCategory<ComboBoxOption>[];
|
||||
getItemLabel: (
|
||||
type: string | undefined,
|
||||
item: ComboBoxOption,
|
||||
) => string | null | undefined;
|
||||
getItemDescription: (item: ComboBoxOption) => string | null | undefined;
|
||||
getSearchFields: (item: ComboBoxOption) => (string | null | undefined)[];
|
||||
onSelect: (change: UseComboboxStateChange<ComboBoxOption>) => void;
|
||||
noResultsText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic ComboBox component built with downshift, with a 2-column layout.
|
||||
*/
|
||||
export default function ComboBox<ComboBoxOption extends ComboBoxItem>({
|
||||
label,
|
||||
placeholder,
|
||||
inputValue,
|
||||
items,
|
||||
getItemLabel,
|
||||
getItemDescription,
|
||||
getSearchFields,
|
||||
onSelect,
|
||||
noResultsText,
|
||||
}: ComboBoxProps<ComboBoxOption>) {
|
||||
// If there is no label defined, we treat the combobox as not needing its own field.
|
||||
const inlineCombobox = !label;
|
||||
const flatItems = items.flatMap<ComboBoxOption>(
|
||||
(category) => category.items || [],
|
||||
);
|
||||
const [inputItems, setInputItems] = useState<ComboBoxOption[]>(flatItems);
|
||||
// Re-create the categories so the two-column layout flows as expected.
|
||||
const categories = items.reduce<ComboBoxCategory<ComboBoxOption>[]>(
|
||||
(cats, cat, index) => {
|
||||
if (cat.label || index === 0) {
|
||||
return [...cats, { ...cat, items: cat.items.slice() }];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
cats[index - 1].items = cats[index - 1].items.concat(cat.items);
|
||||
|
||||
return cats;
|
||||
},
|
||||
[],
|
||||
);
|
||||
const noResults = inputItems.length === 0;
|
||||
const {
|
||||
getLabelProps,
|
||||
getMenuProps,
|
||||
getInputProps,
|
||||
getItemProps,
|
||||
setHighlightedIndex,
|
||||
setInputValue,
|
||||
openMenu,
|
||||
} = useCombobox<ComboBoxOption>({
|
||||
...(typeof inputValue !== 'undefined' && { inputValue }),
|
||||
initialInputValue: inputValue || '',
|
||||
items: inputItems,
|
||||
itemToString(item: ComboBoxOption | null) {
|
||||
if (!item) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return getItemDescription(item) || getItemLabel(item.type, item) || '';
|
||||
},
|
||||
selectedItem: null,
|
||||
|
||||
onSelectedItemChange: onSelect,
|
||||
|
||||
onInputValueChange: (changes) => {
|
||||
const { inputValue: val } = changes;
|
||||
if (!val) {
|
||||
setInputItems(flatItems);
|
||||
return;
|
||||
}
|
||||
|
||||
const filtered = findMatches<ComboBoxOption>(
|
||||
flatItems,
|
||||
getSearchFields,
|
||||
val,
|
||||
);
|
||||
setInputItems(filtered);
|
||||
// Always reset the first item to highlighted on filtering, to speed up selection.
|
||||
setHighlightedIndex(0);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (inputValue) {
|
||||
openMenu();
|
||||
setInputValue(inputValue);
|
||||
const filtered = findMatches<ComboBoxOption>(
|
||||
flatItems,
|
||||
getSearchFields,
|
||||
inputValue,
|
||||
);
|
||||
setInputItems(filtered);
|
||||
// Always reset the first item to highlighted on filtering, to speed up selection.
|
||||
setHighlightedIndex(0);
|
||||
} else {
|
||||
setInputValue('');
|
||||
setInputItems(flatItems);
|
||||
setHighlightedIndex(-1);
|
||||
}
|
||||
}, [inputValue]);
|
||||
|
||||
return (
|
||||
<div className="w-combobox">
|
||||
{/* downshift does the label-field association itself. */}
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label {...getLabelProps()} className="w-sr-only">
|
||||
{label}
|
||||
</label>
|
||||
<div className="w-combobox__field">
|
||||
<input
|
||||
{...getInputProps()}
|
||||
type="text"
|
||||
// Prevent the field from receiving focus if it’s not visible.
|
||||
disabled={inlineCombobox}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</div>
|
||||
{noResults ? (
|
||||
<div className="w-combobox__status">{noResultsText}</div>
|
||||
) : null}
|
||||
<div {...getMenuProps()} className="w-combobox__menu">
|
||||
{categories.map((category) => {
|
||||
const categoryItems = (category.items || []).filter((item) =>
|
||||
inputItems.find((i) => i.type === item.type),
|
||||
);
|
||||
const itemColumns = Math.ceil(categoryItems.length / 2);
|
||||
|
||||
if (categoryItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-combobox__optgroup" key={category.type}>
|
||||
{category.label ? (
|
||||
<div className="w-combobox__optgroup-label">
|
||||
{category.label}
|
||||
</div>
|
||||
) : null}
|
||||
{categoryItems.map((item, index) => {
|
||||
const itemLabel = getItemLabel(item.type, item);
|
||||
const description = getItemDescription(item);
|
||||
const itemIndex = inputItems.findIndex(
|
||||
(i) => i.type === item.type,
|
||||
);
|
||||
const itemColumn = index + 1 <= itemColumns ? 1 : 2;
|
||||
const hasIcon =
|
||||
typeof item.icon !== 'undefined' && item.icon !== null;
|
||||
let icon: JSX.Element | null | undefined = null;
|
||||
|
||||
if (hasIcon) {
|
||||
icon =
|
||||
typeof item.icon === 'string' ? (
|
||||
<Icon name={item.icon} />
|
||||
) : (
|
||||
item.icon
|
||||
);
|
||||
}
|
||||
|
||||
const onMouseDown = (e) => {
|
||||
e.stopPropagation();
|
||||
onSelect({
|
||||
selectedItem: item,
|
||||
type: '__item_click__' as UseComboboxStateChangeTypes.ItemClick,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
// Side-step Downshift event handling and trigger selection on mouse down for clicks,
|
||||
// so we preserve keyboard focus when used within rich text editors.
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
key={item.type}
|
||||
{...getItemProps({ item, index: itemIndex })}
|
||||
onMouseDown={onMouseDown}
|
||||
className={`w-combobox__option w-combobox__option--col${itemColumn}`}
|
||||
>
|
||||
<div className="w-combobox__option-icon">
|
||||
{icon}
|
||||
{/* Support for rich text options using text as an icon (for example "B" for bold). */}
|
||||
{itemLabel && !hasIcon ? <span>{itemLabel}</span> : null}
|
||||
</div>
|
||||
<div className="w-combobox__option-text">
|
||||
{item.render
|
||||
? item.render({ option: item })
|
||||
: description}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import findMatches, { contains } from './findMatches';
|
||||
|
||||
describe('findMatches', () => {
|
||||
describe.each`
|
||||
label | string | substring | result
|
||||
${'full match'} | ${'abcä'} | ${'abcä'} | ${true}
|
||||
${'start match'} | ${'abcä'} | ${'ab'} | ${true}
|
||||
${'end match'} | ${'abcä'} | ${'cä'} | ${true}
|
||||
${'base full match'} | ${'abcä'} | ${'abca'} | ${true}
|
||||
${'base partial match'} | ${'abcä'} | ${'ca'} | ${true}
|
||||
${'base full match reverse'} | ${'abca'} | ${'abcä'} | ${true}
|
||||
${'base partial match reverse'} | ${'abca'} | ${'cä'} | ${true}
|
||||
${'no match'} | ${'abcä'} | ${'potato'} | ${false}
|
||||
`('contains', ({ label, string, substring, result }) => {
|
||||
test(label, () => {
|
||||
expect(contains(string, substring)).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
const findMatchesItems = [
|
||||
{ label: 'label', desc: '' },
|
||||
{ label: '', desc: 'description' },
|
||||
{ label: 'abcä', desc: 'abcä' },
|
||||
{ label: 'abca', desc: 'abca' },
|
||||
{ label: 'ab', desc: 'ab' },
|
||||
{ label: null, desc: null },
|
||||
{ label: undefined, desc: undefined },
|
||||
];
|
||||
|
||||
describe.each`
|
||||
label | input | results
|
||||
${'one match label'} | ${'label'} | ${[0]}
|
||||
${'one match desc'} | ${'description'} | ${[1]}
|
||||
${'multiple matches'} | ${'ab'} | ${[2, 3, 4]}
|
||||
${'base match'} | ${'ca'} | ${[2, 3]}
|
||||
`('findMatches', ({ label, input, results }) => {
|
||||
test(label, () => {
|
||||
const getSearchFields = (i) => [i.label, i.desc];
|
||||
expect(findMatches(findMatchesItems, getSearchFields, input)).toEqual(
|
||||
expect.arrayContaining(results.map((i) => findMatchesItems[i])),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
// Language-sensitive string comparison.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator.
|
||||
const collator = new Intl.Collator(undefined, {
|
||||
usage: 'search',
|
||||
sensitivity: 'base',
|
||||
ignorePunctuation: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Whether a string contains a subsring, with case-insensitive, locale-insensitive search.
|
||||
* See https://github.com/adobe/react-spectrum/blob/70e769acf639fc4ef3a704cb8fad81349cb4137a/packages/%40react-aria/i18n/src/useFilter.ts#L57.
|
||||
* See also https://github.com/arty-name/locale-index-of,
|
||||
* and https://github.com/tc39/ecma402/issues/506.
|
||||
*/
|
||||
export const contains = (string: string, substring: string) => {
|
||||
if (substring.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const haystack = string.normalize('NFC');
|
||||
const needle = substring.normalize('NFC');
|
||||
|
||||
for (let scan = 0; scan + needle.length <= haystack.length; scan += 1) {
|
||||
const slice = haystack.slice(scan, scan + needle.length);
|
||||
if (collator.compare(needle, slice) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all items where a search field matches the input.
|
||||
*/
|
||||
const findMatches = <T extends object>(
|
||||
items: T[],
|
||||
getSearchFields: (item: T) => (string | null | undefined)[],
|
||||
input: string,
|
||||
) =>
|
||||
items.filter((item) => {
|
||||
const matches = getSearchFields(item);
|
||||
|
||||
return matches.some((match) => match && contains(match, input));
|
||||
});
|
||||
|
||||
export default findMatches;
|
|
@ -12,6 +12,7 @@
|
|||
"@tippyjs/react": "^4.2.6",
|
||||
"a11y-dialog": "^7.4.0",
|
||||
"axe-core": "^4.6.2",
|
||||
"downshift": "^7.2.0",
|
||||
"draft-js": "^0.10.5",
|
||||
"draftail": "^2.0.0-rc.5",
|
||||
"draftjs-filters": "^3.0.1",
|
||||
|
@ -13168,6 +13169,28 @@
|
|||
"react-dom": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/ui/node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@storybook/ui/node_modules/downshift": {
|
||||
"version": "6.1.12",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.12.tgz",
|
||||
"integrity": "sha512-7XB/iaSJVS4T8wGFT3WRXmSF1UlBHAA40DshZtkrIscIN+VC+Lh363skLxFTvJwtNgHxAMDGEHT4xsyQFWL+UA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"compute-scroll-into-view": "^1.0.17",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tippyjs/react": {
|
||||
"version": "4.2.6",
|
||||
"license": "MIT",
|
||||
|
@ -16539,9 +16562,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.17",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
|
||||
"integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
|
@ -17962,12 +17985,12 @@
|
|||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/downshift": {
|
||||
"version": "6.1.7",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-7.2.0.tgz",
|
||||
"integrity": "sha512-dEn1Sshe7iTelUhmdbmiJhtIiwIBxBV8p15PuvEBh0qZcHXZnEt0geuCIIkCL4+ooaKRuLE0Wc+Fz9SwWuBIyg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"compute-scroll-into-view": "^1.0.17",
|
||||
"compute-scroll-into-view": "^2.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"tslib": "^2.3.0"
|
||||
|
@ -18023,26 +18046,6 @@
|
|||
"react-dom": "^16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/draftail/node_modules/compute-scroll-into-view": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
|
||||
"integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
|
||||
},
|
||||
"node_modules/draftail/node_modules/downshift": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-7.2.0.tgz",
|
||||
"integrity": "sha512-dEn1Sshe7iTelUhmdbmiJhtIiwIBxBV8p15PuvEBh0qZcHXZnEt0geuCIIkCL4+ooaKRuLE0Wc+Fz9SwWuBIyg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"compute-scroll-into-view": "^2.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/draftjs-conductor": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/draftjs-conductor/-/draftjs-conductor-3.0.0.tgz",
|
||||
|
@ -40740,6 +40743,25 @@
|
|||
"resolve-from": "^5.0.0",
|
||||
"ts-dedent": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
|
||||
"dev": true
|
||||
},
|
||||
"downshift": {
|
||||
"version": "6.1.12",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.12.tgz",
|
||||
"integrity": "sha512-7XB/iaSJVS4T8wGFT3WRXmSF1UlBHAA40DshZtkrIscIN+VC+Lh363skLxFTvJwtNgHxAMDGEHT4xsyQFWL+UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"compute-scroll-into-view": "^1.0.17",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -43100,8 +43122,9 @@
|
|||
}
|
||||
},
|
||||
"compute-scroll-into-view": {
|
||||
"version": "1.0.17",
|
||||
"dev": true
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
|
||||
"integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
|
@ -44086,11 +44109,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"downshift": {
|
||||
"version": "6.1.7",
|
||||
"dev": true,
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-7.2.0.tgz",
|
||||
"integrity": "sha512-dEn1Sshe7iTelUhmdbmiJhtIiwIBxBV8p15PuvEBh0qZcHXZnEt0geuCIIkCL4+ooaKRuLE0Wc+Fz9SwWuBIyg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"compute-scroll-into-view": "^1.0.17",
|
||||
"compute-scroll-into-view": "^2.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"tslib": "^2.3.0"
|
||||
|
@ -44125,25 +44149,6 @@
|
|||
"draft-js-plugins-editor": "^2.1.1",
|
||||
"draftjs-conductor": "^3.0.0",
|
||||
"draftjs-filters": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
|
||||
"integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
|
||||
},
|
||||
"downshift": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-7.2.0.tgz",
|
||||
"integrity": "sha512-dEn1Sshe7iTelUhmdbmiJhtIiwIBxBV8p15PuvEBh0qZcHXZnEt0geuCIIkCL4+ooaKRuLE0Wc+Fz9SwWuBIyg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"compute-scroll-into-view": "^2.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2",
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"draftjs-conductor": {
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
"@tippyjs/react": "^4.2.6",
|
||||
"a11y-dialog": "^7.4.0",
|
||||
"axe-core": "^4.6.2",
|
||||
"downshift": "^7.2.0",
|
||||
"draft-js": "^0.10.5",
|
||||
"draftail": "^2.0.0-rc.5",
|
||||
"draftjs-filters": "^3.0.1",
|
||||
|
|
Ładowanie…
Reference in New Issue