@genspectrum/dashboard-components 0.8.3 → 0.8.5
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/assets/{mutationOverTimeWorker-kjUXkRmn.js.map → mutationOverTimeWorker-DuWGESoO.js.map} +1 -1
- package/dist/dashboard-components.js +177 -169
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +7 -6
- 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/preact/webWorkers/useWebWorker.ts +32 -10
- package/src/preact/webWorkers/workerFunction.ts +19 -3
- 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 +8717 -8724
- package/standalone-bundle/dashboard-components.js.map +1 -1
|
@@ -411,8 +411,9 @@ export declare class MutationComparisonComponent extends PreactLitAdapterWithGri
|
|
|
411
411
|
* ## Context
|
|
412
412
|
* This component provides an input field to specify filters for nucleotide and amino acid mutations and insertions.
|
|
413
413
|
*
|
|
414
|
-
* Input values have to be provided one at a time and submitted by pressing the Enter key or by
|
|
415
|
-
*
|
|
414
|
+
* 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.
|
|
415
|
+
* Alternatively, they can be provided as a string of comma-separated values, which will be directly parsed and validated.
|
|
416
|
+
* After submission (after pressing Enter or pasting a comma-separated string) an event is fired with the selected mutations.
|
|
416
417
|
* All previously selected mutations are displayed at the input field and added to the event.
|
|
417
418
|
* Users can remove a mutation by clicking the 'x' button next to the mutation.
|
|
418
419
|
*
|
|
@@ -1045,7 +1046,7 @@ declare global {
|
|
|
1045
1046
|
|
|
1046
1047
|
declare global {
|
|
1047
1048
|
interface HTMLElementTagNameMap {
|
|
1048
|
-
'gs-
|
|
1049
|
+
'gs-mutations-component': MutationsComponent;
|
|
1049
1050
|
}
|
|
1050
1051
|
}
|
|
1051
1052
|
|
|
@@ -1053,7 +1054,7 @@ declare global {
|
|
|
1053
1054
|
declare global {
|
|
1054
1055
|
namespace JSX {
|
|
1055
1056
|
interface IntrinsicElements {
|
|
1056
|
-
'gs-
|
|
1057
|
+
'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1057
1058
|
}
|
|
1058
1059
|
}
|
|
1059
1060
|
}
|
|
@@ -1061,7 +1062,7 @@ declare global {
|
|
|
1061
1062
|
|
|
1062
1063
|
declare global {
|
|
1063
1064
|
interface HTMLElementTagNameMap {
|
|
1064
|
-
'gs-
|
|
1065
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
1065
1066
|
}
|
|
1066
1067
|
}
|
|
1067
1068
|
|
|
@@ -1069,7 +1070,7 @@ declare global {
|
|
|
1069
1070
|
declare global {
|
|
1070
1071
|
namespace JSX {
|
|
1071
1072
|
interface IntrinsicElements {
|
|
1072
|
-
'gs-
|
|
1073
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1073
1074
|
}
|
|
1074
1075
|
}
|
|
1075
1076
|
}
|
package/dist/style.css
CHANGED
|
@@ -3029,15 +3029,15 @@ input.tab:checked + .tab-content,
|
|
|
3029
3029
|
.mt-4 {
|
|
3030
3030
|
margin-top: 1rem;
|
|
3031
3031
|
}
|
|
3032
|
-
.inline-block {
|
|
3033
|
-
display: inline-block;
|
|
3034
|
-
}
|
|
3035
3032
|
.inline {
|
|
3036
3033
|
display: inline;
|
|
3037
3034
|
}
|
|
3038
3035
|
.flex {
|
|
3039
3036
|
display: flex;
|
|
3040
3037
|
}
|
|
3038
|
+
.inline-flex {
|
|
3039
|
+
display: inline-flex;
|
|
3040
|
+
}
|
|
3041
3041
|
.table {
|
|
3042
3042
|
display: table;
|
|
3043
3043
|
}
|
|
@@ -3107,6 +3107,9 @@ input.tab:checked + .tab-content,
|
|
|
3107
3107
|
--tw-translate-y: -50%;
|
|
3108
3108
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
3109
3109
|
}
|
|
3110
|
+
.cursor-pointer {
|
|
3111
|
+
cursor: pointer;
|
|
3112
|
+
}
|
|
3110
3113
|
.resize {
|
|
3111
3114
|
resize: both;
|
|
3112
3115
|
}
|
|
@@ -3155,9 +3158,6 @@ input.tab:checked + .tab-content,
|
|
|
3155
3158
|
.break-words {
|
|
3156
3159
|
overflow-wrap: break-word;
|
|
3157
3160
|
}
|
|
3158
|
-
.rounded-full {
|
|
3159
|
-
border-radius: 9999px;
|
|
3160
|
-
}
|
|
3161
3161
|
.rounded-lg {
|
|
3162
3162
|
border-radius: 0.5rem;
|
|
3163
3163
|
}
|
|
@@ -3186,12 +3186,6 @@ input.tab:checked + .tab-content,
|
|
|
3186
3186
|
.border-b-2 {
|
|
3187
3187
|
border-bottom-width: 2px;
|
|
3188
3188
|
}
|
|
3189
|
-
.border-solid {
|
|
3190
|
-
border-style: solid;
|
|
3191
|
-
}
|
|
3192
|
-
.border-none {
|
|
3193
|
-
border-style: none;
|
|
3194
|
-
}
|
|
3195
3189
|
.border-error {
|
|
3196
3190
|
--tw-border-opacity: 1;
|
|
3197
3191
|
border-color: var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity, 1)));
|
|
@@ -3212,14 +3206,18 @@ input.tab:checked + .tab-content,
|
|
|
3212
3206
|
--tw-border-opacity: 1;
|
|
3213
3207
|
border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
|
|
3214
3208
|
}
|
|
3215
|
-
.border-
|
|
3209
|
+
.border-slate-500 {
|
|
3216
3210
|
--tw-border-opacity: 1;
|
|
3217
|
-
border-color: rgb(
|
|
3211
|
+
border-color: rgb(100 116 139 / var(--tw-border-opacity, 1));
|
|
3218
3212
|
}
|
|
3219
3213
|
.bg-red-200 {
|
|
3220
3214
|
--tw-bg-opacity: 1;
|
|
3221
3215
|
background-color: rgb(254 202 202 / var(--tw-bg-opacity, 1));
|
|
3222
3216
|
}
|
|
3217
|
+
.bg-slate-200 {
|
|
3218
|
+
--tw-bg-opacity: 1;
|
|
3219
|
+
background-color: rgb(226 232 240 / var(--tw-bg-opacity, 1));
|
|
3220
|
+
}
|
|
3223
3221
|
.bg-white {
|
|
3224
3222
|
--tw-bg-opacity: 1;
|
|
3225
3223
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
|
@@ -3233,10 +3231,6 @@ input.tab:checked + .tab-content,
|
|
|
3233
3231
|
.p-4 {
|
|
3234
3232
|
padding: 1rem;
|
|
3235
3233
|
}
|
|
3236
|
-
.px-2 {
|
|
3237
|
-
padding-left: 0.5rem;
|
|
3238
|
-
padding-right: 0.5rem;
|
|
3239
|
-
}
|
|
3240
3234
|
.px-4 {
|
|
3241
3235
|
padding-left: 1rem;
|
|
3242
3236
|
padding-right: 1rem;
|
|
@@ -3303,6 +3297,10 @@ input.tab:checked + .tab-content,
|
|
|
3303
3297
|
--tw-text-opacity: 1;
|
|
3304
3298
|
color: rgb(96 96 96 / var(--tw-text-opacity, 1));
|
|
3305
3299
|
}
|
|
3300
|
+
.text-black {
|
|
3301
|
+
--tw-text-opacity: 1;
|
|
3302
|
+
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
|
|
3303
|
+
}
|
|
3306
3304
|
.text-blue-600 {
|
|
3307
3305
|
--tw-text-opacity: 1;
|
|
3308
3306
|
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
|
@@ -3378,10 +3376,6 @@ input.tab:checked + .tab-content,
|
|
|
3378
3376
|
border-bottom-left-radius: var(--rounded-box, 1rem);
|
|
3379
3377
|
}
|
|
3380
3378
|
}
|
|
3381
|
-
.focus-within\:border-gray-400:focus-within {
|
|
3382
|
-
--tw-border-opacity: 1;
|
|
3383
|
-
border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
|
|
3384
|
-
}
|
|
3385
3379
|
.hover\:scale-110:hover {
|
|
3386
3380
|
--tw-scale-x: 1.1;
|
|
3387
3381
|
--tw-scale-y: 1.1;
|
|
@@ -3396,6 +3390,10 @@ input.tab:checked + .tab-content,
|
|
|
3396
3390
|
--tw-bg-opacity: 1;
|
|
3397
3391
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
|
3398
3392
|
}
|
|
3393
|
+
.hover\:bg-gray-300:hover {
|
|
3394
|
+
--tw-bg-opacity: 1;
|
|
3395
|
+
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
|
|
3396
|
+
}
|
|
3399
3397
|
.hover\:font-bold:hover {
|
|
3400
3398
|
font-weight: 700;
|
|
3401
3399
|
}
|
|
@@ -3415,15 +3413,6 @@ input.tab:checked + .tab-content,
|
|
|
3415
3413
|
--tw-text-opacity: 1;
|
|
3416
3414
|
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
|
3417
3415
|
}
|
|
3418
|
-
.focus\:outline-none:focus {
|
|
3419
|
-
outline: 2px solid transparent;
|
|
3420
|
-
outline-offset: 2px;
|
|
3421
|
-
}
|
|
3422
|
-
.focus\:ring-0:focus {
|
|
3423
|
-
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
3424
|
-
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
3425
|
-
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
3426
|
-
}
|
|
3427
3416
|
.peer:hover ~ .peer-hover\:visible {
|
|
3428
3417
|
visibility: visible;
|
|
3429
3418
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type PreactRenderer, type Meta, type StoryObj } from '@storybook/preact';
|
|
2
2
|
import { expect, fireEvent, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
import { type StepFunction } from '@storybook/types';
|
|
4
4
|
|
|
@@ -43,13 +43,75 @@ export const Default: StoryObj<MutationFilterProps> = {
|
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
export const FiresFilterMultipleCommaSeparatedQueries: StoryObj<MutationFilterProps> = {
|
|
47
|
+
...Default,
|
|
48
|
+
play: async ({ canvasElement, step }) => {
|
|
49
|
+
const { canvas, changedListenerMock } = await prepare(canvasElement, step);
|
|
50
|
+
|
|
51
|
+
await step('Enter a valid mutation', async () => {
|
|
52
|
+
await submitMutation(canvas, 'A123T');
|
|
53
|
+
|
|
54
|
+
await waitFor(() =>
|
|
55
|
+
expect(changedListenerMock).toHaveBeenCalledWith(
|
|
56
|
+
expect.objectContaining({
|
|
57
|
+
detail: {
|
|
58
|
+
nucleotideMutations: ['A123T'],
|
|
59
|
+
aminoAcidMutations: [],
|
|
60
|
+
nucleotideInsertions: [],
|
|
61
|
+
aminoAcidInsertions: [],
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await step('Enter a comma separated list of valid and invalid mutations', async () => {
|
|
69
|
+
await pasteMutations(canvas, 'A123T, error_insX, A234T, ins_123:AA');
|
|
70
|
+
|
|
71
|
+
await waitFor(() =>
|
|
72
|
+
expect(changedListenerMock).toHaveBeenCalledWith(
|
|
73
|
+
expect.objectContaining({
|
|
74
|
+
detail: {
|
|
75
|
+
nucleotideMutations: ['A123T', 'A234T'],
|
|
76
|
+
aminoAcidMutations: [],
|
|
77
|
+
nucleotideInsertions: ['ins_123:AA'],
|
|
78
|
+
aminoAcidInsertions: [],
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
await expect(canvas.queryByText('A123T')).toBeVisible();
|
|
84
|
+
await expect(canvas.queryByText('A234T')).toBeVisible();
|
|
85
|
+
await expect(inputField(canvas)).toHaveValue('error_insX');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await step('Remove the first mutation', async () => {
|
|
89
|
+
const mutationItem = within(canvas.getByText('A234T'));
|
|
90
|
+
await fireEvent.click(mutationItem.getByRole('button', { name: '×' }));
|
|
91
|
+
|
|
92
|
+
await waitFor(() =>
|
|
93
|
+
expect(changedListenerMock).toHaveBeenCalledWith(
|
|
94
|
+
expect.objectContaining({
|
|
95
|
+
detail: {
|
|
96
|
+
nucleotideMutations: ['A123T'],
|
|
97
|
+
aminoAcidMutations: [],
|
|
98
|
+
nucleotideInsertions: ['ins_123:AA'],
|
|
99
|
+
aminoAcidInsertions: [],
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
46
108
|
export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
|
|
47
109
|
...Default,
|
|
48
110
|
play: async ({ canvasElement, step }) => {
|
|
49
111
|
const { canvas, changedListenerMock } = await prepare(canvasElement, step);
|
|
50
112
|
|
|
51
113
|
await step('Enters an invalid mutation', async () => {
|
|
52
|
-
await
|
|
114
|
+
await testNoOptionsExist(canvas, 'notAMutation');
|
|
53
115
|
await expect(changedListenerMock).not.toHaveBeenCalled();
|
|
54
116
|
|
|
55
117
|
await userEvent.type(inputField(canvas), '{backspace>12/}');
|
|
@@ -88,7 +150,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
|
|
|
88
150
|
});
|
|
89
151
|
|
|
90
152
|
await step('Enter another valid mutation', async () => {
|
|
91
|
-
await submitMutation(canvas, 'ins_123:AA');
|
|
153
|
+
await submitMutation(canvas, 'ins_123:AA', 'enter');
|
|
92
154
|
|
|
93
155
|
await expect(changedListenerMock).toHaveBeenCalledWith(
|
|
94
156
|
expect.objectContaining({
|
|
@@ -103,13 +165,13 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
|
|
|
103
165
|
});
|
|
104
166
|
|
|
105
167
|
await step('Remove the first mutation', async () => {
|
|
106
|
-
const
|
|
107
|
-
await
|
|
168
|
+
const mutationItem = within(canvas.getByText('A234-'));
|
|
169
|
+
await fireEvent.click(mutationItem.getByRole('button', { name: '×' }));
|
|
108
170
|
|
|
109
171
|
await expect(changedListenerMock).toHaveBeenCalledWith(
|
|
110
172
|
expect.objectContaining({
|
|
111
173
|
detail: {
|
|
112
|
-
nucleotideMutations: ['
|
|
174
|
+
nucleotideMutations: ['A123T'],
|
|
113
175
|
aminoAcidMutations: [],
|
|
114
176
|
nucleotideInsertions: ['ins_123:AA'],
|
|
115
177
|
aminoAcidInsertions: [],
|
|
@@ -140,7 +202,7 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
|
|
|
140
202
|
play: async ({ canvasElement, step }) => {
|
|
141
203
|
const { canvas, changedListenerMock } = await prepare(canvasElement, step);
|
|
142
204
|
|
|
143
|
-
await step('
|
|
205
|
+
await step('Add input to initial value', async () => {
|
|
144
206
|
await submitMutation(canvas, 'G500T');
|
|
145
207
|
|
|
146
208
|
await expect(changedListenerMock).toHaveBeenCalledWith(
|
|
@@ -174,12 +236,34 @@ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRend
|
|
|
174
236
|
return { canvas, changedListenerMock };
|
|
175
237
|
}
|
|
176
238
|
|
|
177
|
-
|
|
239
|
+
export type SubmissionMethod = 'click' | 'enter';
|
|
240
|
+
|
|
241
|
+
const submitMutation = async (
|
|
242
|
+
canvas: ReturnType<typeof within>,
|
|
243
|
+
mutation: string,
|
|
244
|
+
submissionMethod: SubmissionMethod = 'click',
|
|
245
|
+
) => {
|
|
178
246
|
await userEvent.type(inputField(canvas), mutation);
|
|
179
|
-
|
|
247
|
+
const firstOption = await canvas.findByRole('option', { name: mutation });
|
|
248
|
+
if (submissionMethod === 'click') {
|
|
249
|
+
await userEvent.click(firstOption);
|
|
250
|
+
}
|
|
251
|
+
if (submissionMethod === 'enter') {
|
|
252
|
+
await userEvent.keyboard('{enter}');
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const pasteMutations = async (canvas: ReturnType<typeof within>, mutation: string) => {
|
|
257
|
+
await userEvent.click(inputField(canvas));
|
|
258
|
+
await userEvent.paste(mutation);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const testNoOptionsExist = async (canvas: ReturnType<typeof within>, mutation: string) => {
|
|
262
|
+
await userEvent.type(inputField(canvas), mutation);
|
|
263
|
+
const options = canvas.queryAllByRole('option');
|
|
264
|
+
|
|
265
|
+
await expect(options).toHaveLength(0);
|
|
180
266
|
};
|
|
181
267
|
|
|
182
268
|
const inputField = (canvas: ReturnType<typeof within>) =>
|
|
183
269
|
canvas.getByPlaceholderText('Enter a mutation', { exact: false });
|
|
184
|
-
|
|
185
|
-
const submitButton = (canvas: ReturnType<typeof within>) => canvas.getByRole('button', { name: '+' });
|