@genspectrum/dashboard-components 0.8.3 → 0.8.4
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 +3 -3
- package/dist/dashboard-components.js +170 -165
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +17 -16
- package/dist/style.css +20 -31
- package/package.json +1 -1
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +95 -11
- package/src/preact/mutationFilter/mutation-filter.tsx +178 -175
- package/src/preact/mutationFilter/parseAndValidateMutation.ts +1 -1
- package/src/web-components/input/gs-mutation-filter.stories.ts +12 -2
- package/src/web-components/input/gs-mutation-filter.tsx +3 -2
- package/standalone-bundle/dashboard-components.js +8591 -8600
- package/standalone-bundle/dashboard-components.js.map +1 -1
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
|
-
import { useContext,
|
|
2
|
+
import { useContext, useState, useRef } from 'preact/hooks';
|
|
3
3
|
|
|
4
4
|
import { MutationFilterInfo } from './mutation-filter-info';
|
|
5
|
-
import { parseAndValidateMutation } from './parseAndValidateMutation';
|
|
5
|
+
import { parseAndValidateMutation, type ParsedMutationFilter } from './parseAndValidateMutation';
|
|
6
6
|
import { type ReferenceGenome } from '../../lapisApi/ReferenceGenome';
|
|
7
|
-
import {
|
|
8
|
-
type DeletionClass,
|
|
9
|
-
type InsertionClass,
|
|
10
|
-
type MutationClass,
|
|
11
|
-
type SubstitutionClass,
|
|
12
|
-
} from '../../utils/mutations';
|
|
7
|
+
import { type DeletionClass, type InsertionClass, type SubstitutionClass } from '../../utils/mutations';
|
|
13
8
|
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
14
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
15
10
|
import { singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
@@ -48,37 +43,23 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
|
|
|
48
43
|
const [selectedFilters, setSelectedFilters] = useState<SelectedFilters>(
|
|
49
44
|
getInitialState(initialValue, referenceGenome),
|
|
50
45
|
);
|
|
51
|
-
const [inputValue, setInputValue] = useState('');
|
|
52
|
-
const [isError, setIsError] = useState(false);
|
|
53
|
-
const formRef = useRef<HTMLFormElement>(null);
|
|
54
|
-
|
|
55
|
-
const handleSubmit = (event: Event) => {
|
|
56
|
-
event.preventDefault();
|
|
57
|
-
if (inputValue === '') {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
46
|
|
|
61
|
-
|
|
47
|
+
const filterRef = useRef<HTMLDivElement>(null);
|
|
62
48
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const newSelectedValues = {
|
|
49
|
+
const handleRemoveValue = (option: ParsedMutationFilter) => {
|
|
50
|
+
const newSelectedFilters = {
|
|
69
51
|
...selectedFilters,
|
|
70
|
-
[
|
|
52
|
+
[option.type]: selectedFilters[option.type].filter((i) => option.value.toString() != i.toString()),
|
|
71
53
|
};
|
|
72
54
|
|
|
73
|
-
setSelectedFilters(
|
|
74
|
-
fireChangeEvent(
|
|
75
|
-
setInputValue('');
|
|
55
|
+
setSelectedFilters(newSelectedFilters);
|
|
56
|
+
fireChangeEvent(newSelectedFilters);
|
|
76
57
|
};
|
|
77
58
|
|
|
78
59
|
const fireChangeEvent = (selectedFilters: SelectedFilters) => {
|
|
79
60
|
const detail = mapToMutationFilterStrings(selectedFilters);
|
|
80
61
|
|
|
81
|
-
|
|
62
|
+
filterRef.current?.dispatchEvent(
|
|
82
63
|
new CustomEvent<SelectedMutationFilterStrings>('gs-mutation-filter-changed', {
|
|
83
64
|
detail,
|
|
84
65
|
bubbles: true,
|
|
@@ -87,38 +68,27 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
|
|
|
87
68
|
);
|
|
88
69
|
};
|
|
89
70
|
|
|
90
|
-
const handleInputChange = (event: Event) => {
|
|
91
|
-
setInputValue((event.target as HTMLInputElement).value);
|
|
92
|
-
setIsError(false);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
71
|
return (
|
|
96
|
-
<
|
|
97
|
-
<div className='absolute -top-3 -right-3'>
|
|
72
|
+
<div className='w-full border border-gray-300 rounded-md relative' ref={filterRef}>
|
|
73
|
+
<div className='absolute -top-3 -right-3 z-10'>
|
|
98
74
|
<MutationFilterInfo />
|
|
99
75
|
</div>
|
|
100
|
-
|
|
101
|
-
|
|
76
|
+
|
|
77
|
+
<div className='relative w-full p-1'>
|
|
78
|
+
<MutationFilterSelector
|
|
79
|
+
referenceGenome={referenceGenome}
|
|
80
|
+
setSelectedFilters={(newSelectedFilters) => {
|
|
81
|
+
setSelectedFilters(newSelectedFilters);
|
|
82
|
+
fireChangeEvent(newSelectedFilters);
|
|
83
|
+
}}
|
|
102
84
|
selectedFilters={selectedFilters}
|
|
103
|
-
setSelectedFilters={setSelectedFilters}
|
|
104
|
-
fireChangeEvent={fireChangeEvent}
|
|
105
85
|
/>
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
className='grow flex-1 p-1 border-none focus:outline-none focus:ring-0'
|
|
111
|
-
type='text'
|
|
112
|
-
value={inputValue}
|
|
113
|
-
onInput={handleInputChange}
|
|
114
|
-
placeholder={getPlaceholder(referenceGenome)}
|
|
115
|
-
/>
|
|
116
|
-
<button type='submit' className='btn btn-xs m-1'>
|
|
117
|
-
+
|
|
118
|
-
</button>
|
|
119
|
-
</div>
|
|
86
|
+
<SelectedMutationFilterDisplay
|
|
87
|
+
selectedFilters={selectedFilters}
|
|
88
|
+
handleRemoveValue={handleRemoveValue}
|
|
89
|
+
/>
|
|
120
90
|
</div>
|
|
121
|
-
</
|
|
91
|
+
</div>
|
|
122
92
|
);
|
|
123
93
|
};
|
|
124
94
|
|
|
@@ -158,6 +128,115 @@ function getInitialState(
|
|
|
158
128
|
);
|
|
159
129
|
}
|
|
160
130
|
|
|
131
|
+
const MutationFilterSelector: FunctionComponent<{
|
|
132
|
+
referenceGenome: ReferenceGenome;
|
|
133
|
+
setSelectedFilters: (option: SelectedFilters) => void;
|
|
134
|
+
selectedFilters: SelectedFilters;
|
|
135
|
+
}> = ({ referenceGenome, setSelectedFilters, selectedFilters }) => {
|
|
136
|
+
const [option, setOption] = useState<ParsedMutationFilter | null>(null);
|
|
137
|
+
const [inputValue, setInputValue] = useState('');
|
|
138
|
+
|
|
139
|
+
const selectorRef = useRef<HTMLDivElement>(null);
|
|
140
|
+
|
|
141
|
+
const handleInputChange = (newValue: string) => {
|
|
142
|
+
if (newValue.includes(',') || newValue.includes(';')) {
|
|
143
|
+
handleCommaSeparatedInput(newValue);
|
|
144
|
+
} else {
|
|
145
|
+
setInputValue(newValue);
|
|
146
|
+
const result = parseAndValidateMutation(newValue, referenceGenome);
|
|
147
|
+
setOption(result);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const handleCommaSeparatedInput = (inputValue: string) => {
|
|
152
|
+
const inputValues = inputValue.split(/[,;]/);
|
|
153
|
+
let newSelectedOptions = selectedFilters;
|
|
154
|
+
let updated: boolean = false;
|
|
155
|
+
const invalidQueries: string[] = [];
|
|
156
|
+
for (const value of inputValues) {
|
|
157
|
+
const trimmedValue = value.trim();
|
|
158
|
+
const parsedMutation = parseAndValidateMutation(trimmedValue, referenceGenome);
|
|
159
|
+
if (parsedMutation) {
|
|
160
|
+
const type = parsedMutation.type;
|
|
161
|
+
if (!selectedFilters[type].some((i) => parsedMutation.value.toString() === i.toString())) {
|
|
162
|
+
newSelectedOptions = {
|
|
163
|
+
...newSelectedOptions,
|
|
164
|
+
[parsedMutation.type]: [...newSelectedOptions[parsedMutation.type], parsedMutation.value],
|
|
165
|
+
};
|
|
166
|
+
updated = true;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
invalidQueries.push(trimmedValue);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setInputValue(invalidQueries.join(','));
|
|
174
|
+
|
|
175
|
+
if (updated) {
|
|
176
|
+
setSelectedFilters(newSelectedOptions);
|
|
177
|
+
setOption(null);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const handleOptionClick = () => {
|
|
182
|
+
if (option === null) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const type = option.type;
|
|
187
|
+
|
|
188
|
+
if (!selectedFilters[type].some((i) => option.value.toString() === i.toString())) {
|
|
189
|
+
const newSelectedValues = {
|
|
190
|
+
...selectedFilters,
|
|
191
|
+
[option?.type]: [...selectedFilters[option?.type], option?.value],
|
|
192
|
+
};
|
|
193
|
+
setSelectedFilters(newSelectedValues);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setInputValue('');
|
|
197
|
+
setOption(null);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const handleEnterPress = (event: KeyboardEvent) => {
|
|
201
|
+
if (event.key === 'Enter' && option !== null) {
|
|
202
|
+
handleOptionClick();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const handleBlur = (event: FocusEvent) => {
|
|
207
|
+
// Check if click was inside the selector
|
|
208
|
+
if (!selectorRef.current?.contains(event.relatedTarget as Node)) {
|
|
209
|
+
setOption(null);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div ref={selectorRef} tabIndex={-1}>
|
|
215
|
+
<input
|
|
216
|
+
type='text'
|
|
217
|
+
className='w-full p-2 border-gray-300 border rounded-md'
|
|
218
|
+
placeholder={getPlaceholder(referenceGenome)}
|
|
219
|
+
value={inputValue}
|
|
220
|
+
onInput={(e: Event) => {
|
|
221
|
+
handleInputChange((e.target as HTMLInputElement).value);
|
|
222
|
+
}}
|
|
223
|
+
onKeyDown={(e) => handleEnterPress(e)}
|
|
224
|
+
onFocus={() => handleInputChange(inputValue)}
|
|
225
|
+
onBlur={handleBlur}
|
|
226
|
+
/>
|
|
227
|
+
{option != null && (
|
|
228
|
+
<div
|
|
229
|
+
role='option'
|
|
230
|
+
className='hover:bg-gray-300 absolute cursor-pointer p-2 border-1 border-slate-500 bg-slate-200'
|
|
231
|
+
onClick={() => handleOptionClick()}
|
|
232
|
+
>
|
|
233
|
+
{option.value.toString()}
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
161
240
|
function getPlaceholder(referenceGenome: ReferenceGenome) {
|
|
162
241
|
const segmentPrefix =
|
|
163
242
|
referenceGenome.nucleotideSequences.length > 1 ? `${referenceGenome.nucleotideSequences[0].name}:` : '';
|
|
@@ -166,149 +245,73 @@ function getPlaceholder(referenceGenome: ReferenceGenome) {
|
|
|
166
245
|
return `Enter a mutation (e.g. ${segmentPrefix}A123T, ins_${segmentPrefix}123:AT, ${firstGene}:M123E, ins_${firstGene}:123:ME)`;
|
|
167
246
|
}
|
|
168
247
|
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
mutation: SelectedFilters[MutationType][number],
|
|
176
|
-
key: MutationType,
|
|
177
|
-
) => {
|
|
178
|
-
const newSelectedValues = {
|
|
179
|
-
...selectedFilters,
|
|
180
|
-
[key]: selectedFilters[key].filter((i) => !mutation.equals(i)),
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
setSelectedFilters(newSelectedValues);
|
|
248
|
+
const backgroundColor: { [key in keyof SelectedFilters]: string } = {
|
|
249
|
+
aminoAcidMutations: singleGraphColorRGBByName('teal', 0.4),
|
|
250
|
+
nucleotideMutations: singleGraphColorRGBByName('green', 0.4),
|
|
251
|
+
aminoAcidInsertions: singleGraphColorRGBByName('purple', 0.4),
|
|
252
|
+
nucleotideInsertions: singleGraphColorRGBByName('indigo', 0.4),
|
|
253
|
+
};
|
|
184
254
|
|
|
185
|
-
|
|
186
|
-
|
|
255
|
+
const backgroundColorMap = (data: ParsedMutationFilter) => {
|
|
256
|
+
return backgroundColor[data.type] || 'lightgray';
|
|
257
|
+
};
|
|
187
258
|
|
|
259
|
+
const SelectedMutationFilterDisplay: FunctionComponent<{
|
|
260
|
+
selectedFilters: SelectedFilters;
|
|
261
|
+
handleRemoveValue: (option: ParsedMutationFilter) => void;
|
|
262
|
+
}> = ({ selectedFilters, handleRemoveValue }) => {
|
|
188
263
|
return (
|
|
189
|
-
|
|
264
|
+
<div className='flex flex-wrap'>
|
|
190
265
|
{selectedFilters.nucleotideMutations.map((mutation) => (
|
|
191
|
-
<
|
|
266
|
+
<SelectedFilter
|
|
192
267
|
key={mutation.toString()}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
onSelectedRemoved(mutation, 'nucleotideMutations')
|
|
196
|
-
}
|
|
268
|
+
handleRemoveValue={handleRemoveValue}
|
|
269
|
+
mutationFilter={{ type: 'nucleotideMutations', value: mutation }}
|
|
197
270
|
/>
|
|
198
271
|
))}
|
|
199
272
|
{selectedFilters.aminoAcidMutations.map((mutation) => (
|
|
200
|
-
<
|
|
273
|
+
<SelectedFilter
|
|
201
274
|
key={mutation.toString()}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
onSelectedRemoved(mutation, 'aminoAcidMutations')
|
|
205
|
-
}
|
|
275
|
+
handleRemoveValue={handleRemoveValue}
|
|
276
|
+
mutationFilter={{ type: 'aminoAcidMutations', value: mutation }}
|
|
206
277
|
/>
|
|
207
278
|
))}
|
|
208
|
-
{selectedFilters.nucleotideInsertions.map((
|
|
209
|
-
<
|
|
210
|
-
key={
|
|
211
|
-
|
|
212
|
-
|
|
279
|
+
{selectedFilters.nucleotideInsertions.map((mutation) => (
|
|
280
|
+
<SelectedFilter
|
|
281
|
+
key={mutation.toString()}
|
|
282
|
+
handleRemoveValue={handleRemoveValue}
|
|
283
|
+
mutationFilter={{ type: 'nucleotideInsertions', value: mutation }}
|
|
213
284
|
/>
|
|
214
285
|
))}
|
|
215
|
-
{selectedFilters.aminoAcidInsertions.map((
|
|
216
|
-
<
|
|
217
|
-
key={
|
|
218
|
-
|
|
219
|
-
|
|
286
|
+
{selectedFilters.aminoAcidInsertions.map((mutation) => (
|
|
287
|
+
<SelectedFilter
|
|
288
|
+
key={mutation.toString()}
|
|
289
|
+
handleRemoveValue={handleRemoveValue}
|
|
290
|
+
mutationFilter={{ type: 'aminoAcidInsertions', value: mutation }}
|
|
220
291
|
/>
|
|
221
292
|
))}
|
|
222
|
-
|
|
223
|
-
);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const SelectedAminoAcidInsertion: FunctionComponent<{
|
|
227
|
-
insertion: InsertionClass;
|
|
228
|
-
onDelete: (insertion: InsertionClass) => void;
|
|
229
|
-
}> = ({ insertion, onDelete }) => {
|
|
230
|
-
const backgroundColor = singleGraphColorRGBByName('teal', 0.3);
|
|
231
|
-
const textColor = singleGraphColorRGBByName('teal', 1);
|
|
232
|
-
return (
|
|
233
|
-
<SelectedFilter
|
|
234
|
-
mutation={insertion}
|
|
235
|
-
onDelete={onDelete}
|
|
236
|
-
backgroundColor={backgroundColor}
|
|
237
|
-
textColor={textColor}
|
|
238
|
-
/>
|
|
239
|
-
);
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const SelectedAminoAcidMutation: FunctionComponent<{
|
|
243
|
-
mutation: SubstitutionClass | DeletionClass;
|
|
244
|
-
onDelete: (mutation: SubstitutionClass | DeletionClass) => void;
|
|
245
|
-
}> = ({ mutation, onDelete }) => {
|
|
246
|
-
const backgroundColor = singleGraphColorRGBByName('rose', 0.3);
|
|
247
|
-
const textColor = singleGraphColorRGBByName('rose', 1);
|
|
248
|
-
return (
|
|
249
|
-
<SelectedFilter
|
|
250
|
-
mutation={mutation}
|
|
251
|
-
onDelete={onDelete}
|
|
252
|
-
backgroundColor={backgroundColor}
|
|
253
|
-
textColor={textColor}
|
|
254
|
-
/>
|
|
255
|
-
);
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const SelectedNucleotideMutation: FunctionComponent<{
|
|
259
|
-
mutation: SubstitutionClass | DeletionClass;
|
|
260
|
-
onDelete: (insertion: SubstitutionClass | DeletionClass) => void;
|
|
261
|
-
}> = ({ mutation, onDelete }) => {
|
|
262
|
-
const backgroundColor = singleGraphColorRGBByName('indigo', 0.3);
|
|
263
|
-
const textColor = singleGraphColorRGBByName('indigo', 1);
|
|
264
|
-
return (
|
|
265
|
-
<SelectedFilter
|
|
266
|
-
mutation={mutation}
|
|
267
|
-
onDelete={onDelete}
|
|
268
|
-
backgroundColor={backgroundColor}
|
|
269
|
-
textColor={textColor}
|
|
270
|
-
/>
|
|
271
|
-
);
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
const SelectedNucleotideInsertion: FunctionComponent<{
|
|
275
|
-
insertion: InsertionClass;
|
|
276
|
-
onDelete: (insertion: InsertionClass) => void;
|
|
277
|
-
}> = ({ insertion, onDelete }) => {
|
|
278
|
-
const backgroundColor = singleGraphColorRGBByName('green', 0.3);
|
|
279
|
-
const textColor = singleGraphColorRGBByName('green', 1);
|
|
280
|
-
|
|
281
|
-
return (
|
|
282
|
-
<SelectedFilter
|
|
283
|
-
mutation={insertion}
|
|
284
|
-
onDelete={onDelete}
|
|
285
|
-
backgroundColor={backgroundColor}
|
|
286
|
-
textColor={textColor}
|
|
287
|
-
/>
|
|
293
|
+
</div>
|
|
288
294
|
);
|
|
289
295
|
};
|
|
290
296
|
|
|
291
|
-
type SelectedFilterProps
|
|
292
|
-
mutation:
|
|
293
|
-
|
|
294
|
-
backgroundColor: string;
|
|
295
|
-
textColor: string;
|
|
297
|
+
type SelectedFilterProps = {
|
|
298
|
+
handleRemoveValue: (mutation: ParsedMutationFilter) => void;
|
|
299
|
+
mutationFilter: ParsedMutationFilter;
|
|
296
300
|
};
|
|
297
301
|
|
|
298
|
-
const SelectedFilter =
|
|
299
|
-
mutation,
|
|
300
|
-
onDelete,
|
|
301
|
-
backgroundColor,
|
|
302
|
-
textColor,
|
|
303
|
-
}: SelectedFilterProps<MutationType>) => {
|
|
302
|
+
const SelectedFilter = ({ handleRemoveValue, mutationFilter }: SelectedFilterProps) => {
|
|
304
303
|
return (
|
|
305
304
|
<span
|
|
306
|
-
|
|
307
|
-
|
|
305
|
+
key={mutationFilter.value.toString()}
|
|
306
|
+
name={mutationFilter.value.toString()}
|
|
307
|
+
className='center p-2 m-1 inline-flex text-black rounded-md'
|
|
308
|
+
style={{
|
|
309
|
+
backgroundColor: backgroundColorMap(mutationFilter),
|
|
310
|
+
}}
|
|
308
311
|
>
|
|
309
|
-
{
|
|
310
|
-
<button className='ml-1'
|
|
311
|
-
|
|
312
|
+
{mutationFilter.value.toString()}
|
|
313
|
+
<button className='ml-1' onClick={() => handleRemoveValue(mutationFilter)}>
|
|
314
|
+
×
|
|
312
315
|
</button>
|
|
313
316
|
</span>
|
|
314
317
|
);
|
|
@@ -3,7 +3,7 @@ import { sequenceTypeFromSegment } from './sequenceTypeFromSegment';
|
|
|
3
3
|
import type { ReferenceGenome } from '../../lapisApi/ReferenceGenome';
|
|
4
4
|
import { DeletionClass, InsertionClass, SubstitutionClass } from '../../utils/mutations';
|
|
5
5
|
|
|
6
|
-
type ParsedMutationFilter = {
|
|
6
|
+
export type ParsedMutationFilter = {
|
|
7
7
|
[MutationType in keyof SelectedFilters]: { type: MutationType; value: SelectedFilters[MutationType][number] };
|
|
8
8
|
}[keyof SelectedFilters];
|
|
9
9
|
|
|
@@ -70,7 +70,6 @@ export const FiresFilterChangedEvent: StoryObj<MutationFilterProps> = {
|
|
|
70
70
|
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter');
|
|
71
71
|
|
|
72
72
|
const inputField = () => canvas.getByPlaceholderText('Enter a mutation', { exact: false });
|
|
73
|
-
const submitButton = () => canvas.getByRole('button', { name: '+' });
|
|
74
73
|
const listenerMock = fn();
|
|
75
74
|
await step('Setup event listener mock', async () => {
|
|
76
75
|
canvasElement.addEventListener('gs-mutation-filter-changed', listenerMock);
|
|
@@ -84,7 +83,8 @@ export const FiresFilterChangedEvent: StoryObj<MutationFilterProps> = {
|
|
|
84
83
|
|
|
85
84
|
await step('Enter a valid mutation', async () => {
|
|
86
85
|
await userEvent.type(inputField(), 'A123T');
|
|
87
|
-
|
|
86
|
+
const option = await canvas.findByRole('option');
|
|
87
|
+
await userEvent.click(option);
|
|
88
88
|
|
|
89
89
|
await waitFor(() =>
|
|
90
90
|
expect(listenerMock).toHaveBeenCalledWith(
|
|
@@ -150,6 +150,16 @@ export const MultiSegmentedReferenceGenomes: StoryObj<MutationFilterProps> = {
|
|
|
150
150
|
play: async ({ canvasElement }) => {
|
|
151
151
|
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter');
|
|
152
152
|
|
|
153
|
+
const inputField = () => canvas.getByPlaceholderText('Enter a mutation', { exact: false });
|
|
154
|
+
|
|
155
|
+
await waitFor(() => {
|
|
156
|
+
const placeholderText = inputField().getAttribute('placeholder');
|
|
157
|
+
|
|
158
|
+
expect(placeholderText).toEqual(
|
|
159
|
+
'Enter a mutation (e.g. seg1:A123T, ins_seg1:123:AT, gene1:M123E, ins_gene1:123:ME)',
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
153
163
|
await waitFor(() => {
|
|
154
164
|
expect(canvas.getByText('seg1:123T')).toBeVisible();
|
|
155
165
|
expect(canvas.getByText('gene2:56')).toBeVisible();
|
|
@@ -14,8 +14,9 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
14
14
|
* ## Context
|
|
15
15
|
* This component provides an input field to specify filters for nucleotide and amino acid mutations and insertions.
|
|
16
16
|
*
|
|
17
|
-
* Input values have to be provided one at a time and submitted by pressing the Enter key or by
|
|
18
|
-
*
|
|
17
|
+
* Input values have to be provided one at a time and submitted by pressing the Enter key or by selecting an option from the dropdown.
|
|
18
|
+
* Alternatively, they can be provided as a string of comma-separated values, which will be directly parsed and validated.
|
|
19
|
+
* After submission (after pressing Enter or pasting a comma-separated string) an event is fired with the selected mutations.
|
|
19
20
|
* All previously selected mutations are displayed at the input field and added to the event.
|
|
20
21
|
* Users can remove a mutation by clicking the 'x' button next to the mutation.
|
|
21
22
|
*
|