@genspectrum/dashboard-components 0.19.0 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +1 -1
- package/dist/components.d.ts +11 -11
- package/dist/components.js +212 -201
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +11 -11
- package/package.json +1 -1
- package/src/preact/components/downshift-combobox.tsx +31 -16
- package/src/preact/lineageFilter/lineage-filter.tsx +4 -4
- package/src/preact/locationFilter/location-filter.tsx +5 -7
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +1 -1
- package/src/preact/textFilter/text-filter.stories.tsx +68 -8
- package/src/preact/textFilter/text-filter.tsx +5 -7
- package/src/web-components/input/gs-text-filter.stories.ts +3 -10
- package/standalone-bundle/dashboard-components.js +6165 -6138
- package/standalone-bundle/dashboard-components.js.map +1 -1
package/dist/util.d.ts
CHANGED
|
@@ -918,7 +918,7 @@ declare global {
|
|
|
918
918
|
|
|
919
919
|
declare global {
|
|
920
920
|
interface HTMLElementTagNameMap {
|
|
921
|
-
'gs-
|
|
921
|
+
'gs-mutations-component': MutationsComponent;
|
|
922
922
|
}
|
|
923
923
|
}
|
|
924
924
|
|
|
@@ -926,7 +926,7 @@ declare global {
|
|
|
926
926
|
declare global {
|
|
927
927
|
namespace JSX {
|
|
928
928
|
interface IntrinsicElements {
|
|
929
|
-
'gs-
|
|
929
|
+
'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
930
930
|
}
|
|
931
931
|
}
|
|
932
932
|
}
|
|
@@ -934,7 +934,7 @@ declare global {
|
|
|
934
934
|
|
|
935
935
|
declare global {
|
|
936
936
|
interface HTMLElementTagNameMap {
|
|
937
|
-
'gs-
|
|
937
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
938
938
|
}
|
|
939
939
|
}
|
|
940
940
|
|
|
@@ -942,7 +942,7 @@ declare global {
|
|
|
942
942
|
declare global {
|
|
943
943
|
namespace JSX {
|
|
944
944
|
interface IntrinsicElements {
|
|
945
|
-
'gs-
|
|
945
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
946
946
|
}
|
|
947
947
|
}
|
|
948
948
|
}
|
|
@@ -1081,10 +1081,11 @@ declare global {
|
|
|
1081
1081
|
|
|
1082
1082
|
declare global {
|
|
1083
1083
|
interface HTMLElementTagNameMap {
|
|
1084
|
-
'gs-
|
|
1084
|
+
'gs-date-range-filter': DateRangeFilterComponent;
|
|
1085
1085
|
}
|
|
1086
1086
|
interface HTMLElementEventMap {
|
|
1087
|
-
'gs-
|
|
1087
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
1088
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1088
1089
|
}
|
|
1089
1090
|
}
|
|
1090
1091
|
|
|
@@ -1092,7 +1093,7 @@ declare global {
|
|
|
1092
1093
|
declare global {
|
|
1093
1094
|
namespace JSX {
|
|
1094
1095
|
interface IntrinsicElements {
|
|
1095
|
-
'gs-
|
|
1096
|
+
'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1096
1097
|
}
|
|
1097
1098
|
}
|
|
1098
1099
|
}
|
|
@@ -1100,11 +1101,10 @@ declare global {
|
|
|
1100
1101
|
|
|
1101
1102
|
declare global {
|
|
1102
1103
|
interface HTMLElementTagNameMap {
|
|
1103
|
-
'gs-
|
|
1104
|
+
'gs-text-filter': TextFilterComponent;
|
|
1104
1105
|
}
|
|
1105
1106
|
interface HTMLElementEventMap {
|
|
1106
|
-
'gs-
|
|
1107
|
-
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1107
|
+
'gs-text-filter-changed': TextFilterChangedEvent;
|
|
1108
1108
|
}
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
@@ -1112,7 +1112,7 @@ declare global {
|
|
|
1112
1112
|
declare global {
|
|
1113
1113
|
namespace JSX {
|
|
1114
1114
|
interface IntrinsicElements {
|
|
1115
|
-
'gs-
|
|
1115
|
+
'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1116
1116
|
}
|
|
1117
1117
|
}
|
|
1118
1118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCombobox } from 'downshift/preact';
|
|
2
2
|
import { type ComponentChild } from 'preact';
|
|
3
|
-
import { useMemo, useRef, useState } from 'preact/hooks';
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
4
4
|
|
|
5
5
|
import { DeleteIcon } from '../shared/icons/DeleteIcon';
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ export function DownshiftCombobox<Item>({
|
|
|
15
15
|
inputClassName = '',
|
|
16
16
|
}: {
|
|
17
17
|
allItems: Item[];
|
|
18
|
-
value
|
|
18
|
+
value: Item | null;
|
|
19
19
|
filterItemsByInputValue: (item: Item, value: string) => boolean;
|
|
20
20
|
createEvent: (item: Item | null) => CustomEvent;
|
|
21
21
|
itemToString: (item: Item | undefined | null) => string;
|
|
@@ -23,12 +23,25 @@ export function DownshiftCombobox<Item>({
|
|
|
23
23
|
formatItemInList: (item: Item) => ComponentChild;
|
|
24
24
|
inputClassName?: string;
|
|
25
25
|
}) {
|
|
26
|
-
const [
|
|
26
|
+
const [selectedItem, setSelectedItem] = useState<Item | null>(() => value);
|
|
27
|
+
const [itemsFilter, setItemsFilter] = useState(() => itemToString(selectedItem));
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setSelectedItem(value);
|
|
31
|
+
setItemsFilter(itemToString(value));
|
|
32
|
+
}, [itemToString, value]);
|
|
33
|
+
|
|
27
34
|
const items = useMemo(
|
|
28
35
|
() => allItems.filter((item) => filterItemsByInputValue(item, itemsFilter)),
|
|
29
36
|
[allItems, filterItemsByInputValue, itemsFilter],
|
|
30
37
|
);
|
|
31
38
|
const divRef = useRef<HTMLDivElement>(null);
|
|
39
|
+
const [inputIsInvalid, setInputIsInvalid] = useState(false);
|
|
40
|
+
|
|
41
|
+
const selectItem = (item: Item | null) => {
|
|
42
|
+
setSelectedItem(item);
|
|
43
|
+
divRef.current?.dispatchEvent(createEvent(item));
|
|
44
|
+
};
|
|
32
45
|
|
|
33
46
|
const shadowRoot = divRef.current?.shadowRoot ?? undefined;
|
|
34
47
|
|
|
@@ -49,39 +62,41 @@ export function DownshiftCombobox<Item>({
|
|
|
49
62
|
getInputProps,
|
|
50
63
|
highlightedIndex,
|
|
51
64
|
getItemProps,
|
|
52
|
-
selectedItem,
|
|
53
65
|
inputValue,
|
|
54
|
-
selectItem,
|
|
55
|
-
setInputValue,
|
|
56
66
|
closeMenu,
|
|
57
67
|
} = useCombobox({
|
|
58
68
|
onInputValueChange({ inputValue }) {
|
|
59
|
-
|
|
69
|
+
setInputIsInvalid(false);
|
|
70
|
+
setItemsFilter(inputValue.trim());
|
|
60
71
|
},
|
|
61
72
|
onSelectedItemChange({ selectedItem }) {
|
|
62
|
-
|
|
63
|
-
divRef.current?.dispatchEvent(createEvent(selectedItem));
|
|
64
|
-
}
|
|
73
|
+
selectItem(selectedItem);
|
|
65
74
|
},
|
|
66
75
|
items,
|
|
67
76
|
itemToString(item) {
|
|
68
77
|
return itemToString(item);
|
|
69
78
|
},
|
|
70
|
-
selectedItem
|
|
79
|
+
selectedItem,
|
|
71
80
|
environment,
|
|
72
81
|
});
|
|
73
82
|
|
|
74
83
|
const onInputBlur = () => {
|
|
75
84
|
if (inputValue === '') {
|
|
76
|
-
divRef.current?.dispatchEvent(createEvent(null));
|
|
77
85
|
selectItem(null);
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const trimmedInput = inputValue.trim();
|
|
90
|
+
const matchingItem = items.find((item) => itemToString(item) === trimmedInput);
|
|
91
|
+
if (matchingItem !== undefined) {
|
|
92
|
+
selectItem(matchingItem);
|
|
93
|
+
return;
|
|
80
94
|
}
|
|
95
|
+
|
|
96
|
+
setInputIsInvalid(true);
|
|
81
97
|
};
|
|
82
98
|
|
|
83
99
|
const clearInput = () => {
|
|
84
|
-
divRef.current?.dispatchEvent(createEvent(null));
|
|
85
100
|
selectItem(null);
|
|
86
101
|
};
|
|
87
102
|
|
|
@@ -91,7 +106,7 @@ export function DownshiftCombobox<Item>({
|
|
|
91
106
|
<div ref={divRef} className={'relative w-full'}>
|
|
92
107
|
<div className='w-full flex flex-col gap-1'>
|
|
93
108
|
<div
|
|
94
|
-
className={`flex gap-0.5 input min-w-32 w-full ${inputClassName}`}
|
|
109
|
+
className={`flex gap-0.5 input min-w-32 w-full ${inputClassName} ${inputIsInvalid ? 'input-error' : ''}`}
|
|
95
110
|
onBlur={(event) => {
|
|
96
111
|
if (event.relatedTarget != buttonRef.current) {
|
|
97
112
|
closeMenu();
|
|
@@ -77,16 +77,16 @@ const LineageSelector = ({
|
|
|
77
77
|
allItems={data}
|
|
78
78
|
value={value}
|
|
79
79
|
filterItemsByInputValue={filterByInputValue}
|
|
80
|
-
createEvent={(item
|
|
81
|
-
itemToString={(item
|
|
80
|
+
createEvent={(item) => new LineageFilterChangedEvent({ [lapisField]: item ?? undefined })}
|
|
81
|
+
itemToString={(item) => item ?? ''}
|
|
82
82
|
placeholderText={placeholderText}
|
|
83
83
|
formatItemInList={(item: string) => item}
|
|
84
84
|
/>
|
|
85
85
|
);
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
function filterByInputValue(item: string, inputValue: string |
|
|
89
|
-
if (inputValue ===
|
|
88
|
+
function filterByInputValue(item: string, inputValue: string | null) {
|
|
89
|
+
if (inputValue === null || inputValue === '') {
|
|
90
90
|
return true;
|
|
91
91
|
}
|
|
92
92
|
return item?.toLowerCase().includes(inputValue?.toLowerCase() || '');
|
|
@@ -87,12 +87,10 @@ const LocationSelector = ({
|
|
|
87
87
|
return (
|
|
88
88
|
<DownshiftCombobox
|
|
89
89
|
allItems={allItems}
|
|
90
|
-
value={selectedItem}
|
|
90
|
+
value={selectedItem ?? null}
|
|
91
91
|
filterItemsByInputValue={filterByInputValue}
|
|
92
|
-
createEvent={(item
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
itemToString={(item: SelectItem | undefined | null) => item?.label ?? ''}
|
|
92
|
+
createEvent={(item) => new LocationChangedEvent(item?.lapisFilter ?? emptyLocationFilter(fields))}
|
|
93
|
+
itemToString={(item) => item?.label ?? ''}
|
|
96
94
|
placeholderText={placeholderText}
|
|
97
95
|
formatItemInList={(item: SelectItem) => (
|
|
98
96
|
<>
|
|
@@ -107,8 +105,8 @@ const LocationSelector = ({
|
|
|
107
105
|
);
|
|
108
106
|
};
|
|
109
107
|
|
|
110
|
-
function filterByInputValue(item: SelectItem, inputValue: string |
|
|
111
|
-
if (inputValue ===
|
|
108
|
+
function filterByInputValue(item: SelectItem, inputValue: string | null) {
|
|
109
|
+
if (inputValue === null) {
|
|
112
110
|
return true;
|
|
113
111
|
}
|
|
114
112
|
return (
|
|
@@ -47,7 +47,7 @@ export function getFilteredMutationOverTimeData({
|
|
|
47
47
|
return true;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
if (displayMutationsSet !== null && !displayMutationsSet.has(entry.mutation.code)) {
|
|
50
|
+
if (displayMutationsSet !== null && !displayMutationsSet.has(entry.mutation.code.toUpperCase())) {
|
|
51
51
|
return true;
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
-
import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';
|
|
2
|
+
import { expect, fireEvent, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import data from './__mockData__/aggregated_hosts.json';
|
|
5
5
|
import { TextFilter, type TextFilterProps } from './text-filter';
|
|
@@ -104,13 +104,73 @@ export const RemoveInitialValue: StoryObj<TextFilterProps> = {
|
|
|
104
104
|
await step('Remove initial value', async () => {
|
|
105
105
|
await fireEvent.click(canvas.getByRole('button', { name: 'clear selection' }));
|
|
106
106
|
|
|
107
|
-
await
|
|
108
|
-
expect.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
await waitFor(async () => {
|
|
108
|
+
await expect(changedListenerMock).toHaveBeenCalledWith(
|
|
109
|
+
expect.objectContaining({
|
|
110
|
+
detail: {
|
|
111
|
+
host: undefined,
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const KeepsPartialInputInInputField: StoryObj<TextFilterProps> = {
|
|
121
|
+
...Default,
|
|
122
|
+
render: (args) => (
|
|
123
|
+
<>
|
|
124
|
+
<button data-testid='focusHelper'>Focus helper</button>
|
|
125
|
+
<LapisUrlContextProvider value={LAPIS_URL}>
|
|
126
|
+
<TextFilter {...args} />
|
|
127
|
+
</LapisUrlContextProvider>
|
|
128
|
+
</>
|
|
129
|
+
),
|
|
130
|
+
args: {
|
|
131
|
+
...Default.args,
|
|
132
|
+
value: '',
|
|
133
|
+
},
|
|
134
|
+
play: async ({ canvasElement, step }) => {
|
|
135
|
+
const canvas = within(canvasElement);
|
|
136
|
+
|
|
137
|
+
const changedListenerMock = fn();
|
|
138
|
+
await step('Setup event listener mock', () => {
|
|
139
|
+
canvasElement.addEventListener('gs-text-filter-changed', changedListenerMock);
|
|
140
|
+
});
|
|
141
|
+
const inputField = () => canvas.getByPlaceholderText('Enter a host name', { exact: false });
|
|
142
|
+
async function typeAndBlur(input: string) {
|
|
143
|
+
await userEvent.click(inputField());
|
|
144
|
+
await userEvent.type(inputField(), input);
|
|
145
|
+
await userEvent.click(canvas.getByTestId('focusHelper'));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await waitFor(async () => {
|
|
149
|
+
await expect(inputField()).toHaveValue('');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await step('Type a value that it not valid yet and focus out', async () => {
|
|
153
|
+
await typeAndBlur('Homo sapi');
|
|
154
|
+
await waitFor(async () => {
|
|
155
|
+
await expect(inputField()).toHaveValue('Homo sapi');
|
|
156
|
+
});
|
|
157
|
+
await expect(changedListenerMock).not.toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await step('Complete the input value and expect an event', async () => {
|
|
161
|
+
await typeAndBlur('ens');
|
|
162
|
+
await waitFor(async () => {
|
|
163
|
+
await expect(inputField()).toHaveValue('Homo sapiens');
|
|
164
|
+
});
|
|
165
|
+
await waitFor(async () => {
|
|
166
|
+
await expect(changedListenerMock).toHaveBeenLastCalledWith(
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
detail: {
|
|
169
|
+
host: 'Homo sapiens',
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
});
|
|
114
174
|
});
|
|
115
175
|
},
|
|
116
176
|
};
|
|
@@ -85,12 +85,10 @@ const TextSelector = ({
|
|
|
85
85
|
return (
|
|
86
86
|
<DownshiftCombobox
|
|
87
87
|
allItems={data}
|
|
88
|
-
value={initialSelectedItem}
|
|
88
|
+
value={initialSelectedItem ?? null}
|
|
89
89
|
filterItemsByInputValue={filterByInputValue}
|
|
90
|
-
createEvent={(item:
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
itemToString={(item: SelectItem | undefined | null) => item?.value ?? ''}
|
|
90
|
+
createEvent={(item) => new TextFilterChangedEvent({ [lapisField]: item?.value ?? undefined })}
|
|
91
|
+
itemToString={(item) => item?.value ?? ''}
|
|
94
92
|
placeholderText={placeholderText}
|
|
95
93
|
formatItemInList={(item: SelectItem) => {
|
|
96
94
|
return (
|
|
@@ -104,8 +102,8 @@ const TextSelector = ({
|
|
|
104
102
|
);
|
|
105
103
|
};
|
|
106
104
|
|
|
107
|
-
function filterByInputValue(item: SelectItem, inputValue: string |
|
|
108
|
-
if (inputValue ===
|
|
105
|
+
function filterByInputValue(item: SelectItem, inputValue: string | null) {
|
|
106
|
+
if (inputValue === null || inputValue === '') {
|
|
109
107
|
return true;
|
|
110
108
|
}
|
|
111
109
|
return item.value?.toLowerCase().includes(inputValue?.toLowerCase() || '');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect,
|
|
1
|
+
import { expect, fn, userEvent, waitFor } from '@storybook/test';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
3
3
|
import { html } from 'lit';
|
|
4
4
|
|
|
@@ -152,9 +152,9 @@ export const FiresEvents: StoryObj<Required<TextFilterProps>> = {
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
await step('Remove initial value', async () => {
|
|
155
|
-
await
|
|
155
|
+
await userEvent.click(canvas.getByRole('button', { name: 'clear selection' }));
|
|
156
156
|
|
|
157
|
-
await expect(listenerMock).
|
|
157
|
+
await expect(listenerMock).toHaveBeenLastCalledWith(
|
|
158
158
|
expect.objectContaining({
|
|
159
159
|
detail: {
|
|
160
160
|
host: undefined,
|
|
@@ -162,13 +162,6 @@ export const FiresEvents: StoryObj<Required<TextFilterProps>> = {
|
|
|
162
162
|
}),
|
|
163
163
|
);
|
|
164
164
|
});
|
|
165
|
-
|
|
166
|
-
await step('Empty input', async () => {
|
|
167
|
-
inputField().blur();
|
|
168
|
-
await expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
169
|
-
host: undefined,
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
165
|
},
|
|
173
166
|
args: {
|
|
174
167
|
...Default.args,
|