@genspectrum/dashboard-components 0.12.1 → 0.13.1

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.
Files changed (63) hide show
  1. package/custom-elements.json +292 -25
  2. package/dist/{LocationChangedEvent-CORvQvXv.js → LineageFilterChangedEvent-GedKNGFI.js} +25 -3
  3. package/dist/LineageFilterChangedEvent-GedKNGFI.js.map +1 -0
  4. package/dist/assets/mutationOverTimeWorker-B1-WrM4b.js.map +1 -0
  5. package/dist/components.d.ts +124 -25
  6. package/dist/components.js +765 -572
  7. package/dist/components.js.map +1 -1
  8. package/dist/style.css +3 -0
  9. package/dist/util.d.ts +48 -18
  10. package/dist/util.js +3 -1
  11. package/package.json +2 -2
  12. package/src/constants.ts +6 -0
  13. package/src/lapisApi/__mockData__/wiseReferenceGenome.json +9 -0
  14. package/src/lapisApi/lapisApi.ts +17 -0
  15. package/src/lapisApi/lapisTypes.ts +7 -1
  16. package/src/operator/FetchDetailsOperator.ts +28 -0
  17. package/src/preact/components/downshift-combobox.tsx +145 -0
  18. package/src/preact/components/tabs.tsx +1 -1
  19. package/src/preact/lineageFilter/LineageFilterChangedEvent.ts +11 -0
  20. package/src/preact/lineageFilter/fetchLineageAutocompleteList.spec.ts +16 -2
  21. package/src/preact/lineageFilter/fetchLineageAutocompleteList.ts +13 -2
  22. package/src/preact/lineageFilter/lineage-filter.stories.tsx +110 -9
  23. package/src/preact/lineageFilter/lineage-filter.tsx +40 -50
  24. package/src/preact/locationFilter/LocationChangedEvent.ts +1 -1
  25. package/src/preact/locationFilter/fetchAutocompletionList.spec.ts +6 -2
  26. package/src/preact/locationFilter/fetchAutocompletionList.ts +16 -6
  27. package/src/preact/locationFilter/location-filter.stories.tsx +33 -30
  28. package/src/preact/locationFilter/location-filter.tsx +47 -144
  29. package/src/preact/mutationsOverTime/MutationOverTimeData.ts +9 -5
  30. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +5 -3
  31. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +4 -7
  32. package/src/preact/textInput/TextInputChangedEvent.ts +1 -1
  33. package/src/preact/textInput/fetchStringAutocompleteList.ts +20 -0
  34. package/src/preact/textInput/text-input.stories.tsx +14 -11
  35. package/src/preact/textInput/text-input.tsx +39 -140
  36. package/src/preact/wastewater/mutationsOverTime/__mockData__/details.json +88 -0
  37. package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.spec.ts +159 -0
  38. package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.ts +51 -0
  39. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +71 -0
  40. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +151 -0
  41. package/src/query/queryMutationsOverTime.ts +6 -14
  42. package/src/query/queryWastewaterMutationsOverTime.spec.ts +94 -0
  43. package/src/query/queryWastewaterMutationsOverTime.ts +55 -0
  44. package/src/types.ts +3 -0
  45. package/src/utilEntrypoint.ts +2 -0
  46. package/src/utils/map2d.ts +39 -0
  47. package/src/web-components/index.ts +1 -0
  48. package/src/web-components/input/gs-lineage-filter.stories.ts +120 -31
  49. package/src/web-components/input/gs-lineage-filter.tsx +24 -8
  50. package/src/web-components/input/gs-location-filter.stories.ts +9 -0
  51. package/src/web-components/input/gs-location-filter.tsx +21 -3
  52. package/src/web-components/input/gs-text-input.stories.ts +14 -5
  53. package/src/web-components/input/gs-text-input.tsx +23 -7
  54. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +82 -0
  55. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +112 -0
  56. package/src/web-components/wastewaterVisualization/index.ts +1 -0
  57. package/standalone-bundle/assets/{mutationOverTimeWorker-DEybsZ5r.js.map → mutationOverTimeWorker-Cls1J0cl.js.map} +1 -1
  58. package/standalone-bundle/dashboard-components.js +6972 -6796
  59. package/standalone-bundle/dashboard-components.js.map +1 -1
  60. package/standalone-bundle/style.css +1 -1
  61. package/dist/LocationChangedEvent-CORvQvXv.js.map +0 -1
  62. package/dist/assets/mutationOverTimeWorker-DTv93Ere.js.map +0 -1
  63. package/src/preact/textInput/fetchAutocompleteList.ts +0 -9
@@ -0,0 +1,55 @@
1
+ import z from 'zod';
2
+
3
+ import { FetchDetailsOperator } from '../operator/FetchDetailsOperator';
4
+ import { type LapisFilter } from '../types';
5
+ import { type Substitution, SubstitutionClass } from '../utils/mutations';
6
+ import { parseDateStringToTemporal, type TemporalClass, toTemporalClass } from '../utils/temporalClass';
7
+
8
+ export type WastewaterData = {
9
+ location: string;
10
+ date: TemporalClass;
11
+ nucleotideMutationFrequency: { mutation: Substitution; proportion: number | null }[];
12
+ aminoAcidMutationFrequency: { mutation: Substitution; proportion: number | null }[];
13
+ }[];
14
+
15
+ export async function queryWastewaterMutationsOverTime(
16
+ lapis: string,
17
+ lapisFilter: LapisFilter,
18
+ signal?: AbortSignal,
19
+ ): Promise<WastewaterData> {
20
+ const fetchData = new FetchDetailsOperator(lapisFilter, [
21
+ 'date',
22
+ 'location',
23
+ 'nucleotideMutationFrequency',
24
+ 'aminoAcidMutationFrequency',
25
+ ]);
26
+ const data = (await fetchData.evaluate(lapis, signal)).content;
27
+
28
+ return data.map((row) => ({
29
+ location: row.location as string,
30
+ date: toTemporalClass(parseDateStringToTemporal(row.date as string, 'day')),
31
+ nucleotideMutationFrequency:
32
+ row.nucleotideMutationFrequency !== null
33
+ ? transformMutations(JSON.parse(row.nucleotideMutationFrequency as string))
34
+ : [],
35
+ aminoAcidMutationFrequency:
36
+ row.aminoAcidMutationFrequency !== null
37
+ ? transformMutations(JSON.parse(row.aminoAcidMutationFrequency as string))
38
+ : [],
39
+ }));
40
+ }
41
+
42
+ const mutationFrequencySchema = z.record(z.number().nullable());
43
+
44
+ function transformMutations(input: unknown): { mutation: Substitution; proportion: number | null }[] {
45
+ const mutationFrequency = mutationFrequencySchema.safeParse(input);
46
+
47
+ if (!mutationFrequency.success) {
48
+ throw new Error(`Failed to parse mutation frequency: ${mutationFrequency.error.message}`);
49
+ }
50
+
51
+ return Object.entries(mutationFrequency.data).map(([key, value]) => ({
52
+ mutation: SubstitutionClass.parse(key)!,
53
+ proportion: value,
54
+ }));
55
+ }
package/src/types.ts CHANGED
@@ -28,6 +28,9 @@ export const namedLapisFilterSchema = z.object({
28
28
  });
29
29
  export type NamedLapisFilter = z.infer<typeof namedLapisFilterSchema>;
30
30
 
31
+ export const lapisLocationFilterSchema = z.record(z.union([z.string(), z.undefined()]));
32
+ export type LapisLocationFilter = z.infer<typeof lapisLocationFilterSchema>;
33
+
31
34
  export const temporalGranularitySchema = z.union([
32
35
  z.literal('day'),
33
36
  z.literal('week'),
@@ -34,3 +34,5 @@ export type { ConfidenceIntervalMethod } from './preact/shared/charts/confideceI
34
34
  export type { AxisMax, YAxisMaxConfig } from './preact/shared/charts/getYAxisMax';
35
35
 
36
36
  export { LocationChangedEvent } from './preact/locationFilter/LocationChangedEvent';
37
+ export { LineageFilterChangedEvent } from './preact/lineageFilter/LineageFilterChangedEvent';
38
+ export { TextInputChangedEvent } from './preact/textInput/TextInputChangedEvent';
@@ -17,6 +17,8 @@ export interface Map2d<Key1, Key2, Value> {
17
17
 
18
18
  serializeSecondAxis(key: Key2): string;
19
19
 
20
+ getContents(): Map2DContents<Key1, Key2, Value>;
21
+
20
22
  readonly keysFirstAxis: Map<string, Key1>;
21
23
  readonly keysSecondAxis: Map<string, Key2>;
22
24
  }
@@ -106,6 +108,35 @@ export class Map2dBase<Key1 extends object | string, Key2 extends object | strin
106
108
  }
107
109
  }
108
110
 
111
+ export class SortedMap2d<Key1 extends object | string, Key2 extends object | string, Value> extends Map2dBase<
112
+ Key1,
113
+ Key2,
114
+ Value
115
+ > {
116
+ constructor(
117
+ delegate: Map2d<Key1, Key2, Value>,
118
+ sortFirstAxis: (a: Key1, b: Key1) => number,
119
+ sortSecondAxis: (a: Key2, b: Key2) => number,
120
+ ) {
121
+ const contents = delegate.getContents();
122
+ const sortedFirstAxisKeys = new Map(
123
+ [...contents.keysFirstAxis.entries()].sort((a, b) => sortFirstAxis(a[1], b[1])),
124
+ );
125
+ const sortedSecondAxisKeys = new Map(
126
+ [...contents.keysSecondAxis.entries()].sort((a, b) => sortSecondAxis(a[1], b[1])),
127
+ );
128
+ super(
129
+ (key: Key1) => delegate.serializeFirstAxis(key),
130
+ (key: Key2) => delegate.serializeSecondAxis(key),
131
+ {
132
+ keysFirstAxis: sortedFirstAxisKeys,
133
+ keysSecondAxis: sortedSecondAxisKeys,
134
+ data: contents.data,
135
+ },
136
+ );
137
+ }
138
+ }
139
+
109
140
  export class Map2dView<Key1 extends object | string, Key2 extends object | string, Value>
110
141
  implements Map2d<Key1, Key2, Value>
111
142
  {
@@ -175,4 +206,12 @@ export class Map2dView<Key1 extends object | string, Key2 extends object | strin
175
206
 
176
207
  return this.baseMap.getRow(key);
177
208
  }
209
+
210
+ getContents() {
211
+ return {
212
+ keysFirstAxis: this.keysFirstAxis,
213
+ keysSecondAxis: this.keysSecondAxis,
214
+ data: this.baseMap.getContents().data,
215
+ };
216
+ }
178
217
  }
@@ -1,3 +1,4 @@
1
1
  export { App } from './app.js';
2
2
  export * from './visualization';
3
+ export * from './wastewaterVisualization';
3
4
  export * from './input';
@@ -14,8 +14,9 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
14
14
  const codeExample = String.raw`
15
15
  <gs-lineage-filter
16
16
  lapisField="pangoLineage"
17
+ lapisFilter='{"counrty": "Germany"}'
17
18
  placeholderText="Enter lineage"
18
- initialValue="B.1.1.7"
19
+ value="B.1.1.7"
19
20
  width="50%">
20
21
  </gs-lineage-filter>`;
21
22
 
@@ -34,6 +35,7 @@ const meta: Meta<Required<LineageFilterProps>> = {
34
35
  url: AGGREGATED_ENDPOINT,
35
36
  body: {
36
37
  fields: ['pangoLineage'],
38
+ country: 'Germany',
37
39
  },
38
40
  },
39
41
  response: {
@@ -50,18 +52,47 @@ const meta: Meta<Required<LineageFilterProps>> = {
50
52
  },
51
53
  }),
52
54
  tags: ['autodocs'],
55
+ argTypes: {
56
+ lapisField: {
57
+ control: {
58
+ type: 'select',
59
+ },
60
+ options: ['host'],
61
+ },
62
+ placeholderText: {
63
+ control: {
64
+ type: 'text',
65
+ },
66
+ },
67
+ value: {
68
+ control: {
69
+ type: 'text',
70
+ },
71
+ },
72
+ width: {
73
+ control: {
74
+ type: 'text',
75
+ },
76
+ },
77
+ lapisFilter: {
78
+ control: {
79
+ type: 'object',
80
+ },
81
+ },
82
+ },
53
83
  };
54
84
 
55
85
  export default meta;
56
86
 
57
- export const Default: StoryObj<Required<LineageFilterProps>> = {
87
+ const Template: StoryObj<Required<LineageFilterProps>> = {
58
88
  render: (args) => {
59
89
  return html` <gs-app lapis="${LAPIS_URL}">
60
90
  <div class="max-w-screen-lg">
61
91
  <gs-lineage-filter
62
92
  .lapisField=${args.lapisField}
93
+ .lapisFilter=${args.lapisFilter}
63
94
  .placeholderText=${args.placeholderText}
64
- .initialValue=${args.initialValue}
95
+ .value=${args.value}
65
96
  .width=${args.width}
66
97
  ></gs-lineage-filter>
67
98
  </div>
@@ -69,18 +100,86 @@ export const Default: StoryObj<Required<LineageFilterProps>> = {
69
100
  },
70
101
  args: {
71
102
  lapisField: 'pangoLineage',
72
- placeholderText: 'Enter lineage',
73
- initialValue: 'B.1.1.7',
103
+ lapisFilter: {
104
+ country: 'Germany',
105
+ },
106
+ placeholderText: 'Enter a lineage',
107
+ value: 'B.1.1.7',
74
108
  width: '100%',
75
109
  },
76
110
  };
77
111
 
112
+ const aggregatedEndpointMatcher = {
113
+ name: 'pangoLineage',
114
+ url: AGGREGATED_ENDPOINT,
115
+ body: {
116
+ fields: ['pangoLineage'],
117
+ country: 'Germany',
118
+ },
119
+ };
120
+
121
+ export const LineageFilter: StoryObj<Required<LineageFilterProps>> = {
122
+ ...Template,
123
+ play: async ({ canvasElement }) => {
124
+ const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
125
+ await waitFor(() => {
126
+ return expect(canvas.getByPlaceholderText('Enter a lineage')).toBeVisible();
127
+ });
128
+ },
129
+ };
130
+
131
+ export const DelayToShowLoadingState: StoryObj<Required<LineageFilterProps>> = {
132
+ ...Template,
133
+ parameters: {
134
+ fetchMock: {
135
+ mocks: [
136
+ {
137
+ matcher: aggregatedEndpointMatcher,
138
+ response: {
139
+ status: 200,
140
+ body: aggregatedData,
141
+ },
142
+ options: {
143
+ delay: 5000,
144
+ },
145
+ },
146
+ ],
147
+ },
148
+ },
149
+ };
150
+
151
+ export const FetchingLocationsFails: StoryObj<Required<LineageFilterProps>> = {
152
+ ...Template,
153
+ parameters: {
154
+ fetchMock: {
155
+ mocks: [
156
+ {
157
+ matcher: aggregatedEndpointMatcher,
158
+ response: {
159
+ status: 400,
160
+ body: {
161
+ error: { status: 400, detail: 'Dummy error message from mock LAPIS', type: 'about:blank' },
162
+ },
163
+ },
164
+ },
165
+ ],
166
+ },
167
+ },
168
+ play: async ({ canvasElement }) => {
169
+ const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
170
+
171
+ await waitFor(() =>
172
+ expect(canvas.getByText('Oops! Something went wrong.', { exact: false })).toBeInTheDocument(),
173
+ );
174
+ },
175
+ };
176
+
78
177
  export const FiresEvent: StoryObj<Required<LineageFilterProps>> = {
79
- ...Default,
178
+ ...LineageFilter,
80
179
  play: async ({ canvasElement, step }) => {
81
180
  const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
82
181
 
83
- const inputField = () => canvas.getByPlaceholderText('Enter lineage');
182
+ const inputField = () => canvas.getByPlaceholderText('Enter a lineage');
84
183
  const listenerMock = fn();
85
184
  await step('Setup event listener mock', async () => {
86
185
  canvasElement.addEventListener('gs-lineage-filter-changed', listenerMock);
@@ -99,38 +198,28 @@ export const FiresEvent: StoryObj<Required<LineageFilterProps>> = {
99
198
 
100
199
  await step('Empty input', async () => {
101
200
  await userEvent.type(inputField(), '{backspace>9/}');
102
- await expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
103
- pangoLineage: undefined,
104
- });
105
- });
106
-
107
- await step('Enter a valid lineage value', async () => {
108
- await userEvent.type(inputField(), 'B.1.1.7');
201
+ await userEvent.click(canvas.getByLabelText('toggle menu'));
109
202
 
110
- await expect(listenerMock).toHaveBeenCalledWith(
111
- expect.objectContaining({
112
- detail: {
113
- pangoLineage: 'B.1.1.7',
114
- },
115
- }),
116
- );
203
+ await waitFor(() => {
204
+ return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
205
+ pangoLineage: undefined,
206
+ });
207
+ });
117
208
  });
118
209
 
119
210
  await step('Enter a valid lineage value', async () => {
120
- await userEvent.type(inputField(), '{backspace>9/}');
121
211
  await userEvent.type(inputField(), 'B.1.1.7*');
212
+ await userEvent.click(canvas.getByRole('option', { name: 'B.1.1.7*' }));
122
213
 
123
- await expect(listenerMock).toHaveBeenCalledWith(
124
- expect.objectContaining({
125
- detail: {
126
- pangoLineage: 'B.1.1.7*',
127
- },
128
- }),
129
- );
214
+ await waitFor(() => {
215
+ return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
216
+ pangoLineage: 'B.1.1.7*',
217
+ });
218
+ });
130
219
  });
131
220
  },
132
221
  args: {
133
- ...Default.args,
134
- initialValue: '',
222
+ ...LineageFilter.args,
223
+ value: '',
135
224
  },
136
225
  };
@@ -1,6 +1,7 @@
1
1
  import { customElement, property } from 'lit/decorators.js';
2
2
  import type { DetailedHTMLProps, HTMLAttributes } from 'react';
3
3
 
4
+ import { type LineageFilterChangedEvent } from '../../preact/lineageFilter/LineageFilterChangedEvent';
4
5
  import { LineageFilter, type LineageFilterProps } from '../../preact/lineageFilter/lineage-filter';
5
6
  import type { Equals, Expect } from '../../utils/typeAssertions';
6
7
  import { PreactLitAdapter } from '../PreactLitAdapter';
@@ -13,11 +14,11 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
13
14
  * Currently, it is designed to work well with Pango Lineages,
14
15
  * but it may also be used for other lineage types, if suitable.
15
16
  *
16
- * It fetches all available values of the `lapisField` from the LAPIS instance
17
+ * It fetches all available values of the `lapisField` from the LAPIS instance within the given `lapisFilter`
17
18
  * and provides an autocomplete list with the available values of the lineage and sublineage queries
18
19
  * (a `*` appended to the lineage value).
19
20
  *
20
- * @fires {CustomEvent<Record<string, string>>} gs-lineage-filter-changed
21
+ * @fires {CustomEvent<Record<string, string | undefined>>} gs-lineage-filter-changed
21
22
  * Fired when the input field is changed.
22
23
  * The `details` of this event contain an object with the `lapisField` as key and the input value as value.
23
24
  * Example:
@@ -33,7 +34,7 @@ export class LineageFilterComponent extends PreactLitAdapter {
33
34
  * The initial value to use for this lineage filter.
34
35
  */
35
36
  @property()
36
- initialValue: string = '';
37
+ value: string = '';
37
38
 
38
39
  /**
39
40
  * Required.
@@ -44,6 +45,19 @@ export class LineageFilterComponent extends PreactLitAdapter {
44
45
  @property()
45
46
  lapisField = '';
46
47
 
48
+ /**
49
+ * The filter that is used to fetch the available the autocomplete options.
50
+ * If not set it fetches all available options.
51
+ * It must be a valid LAPIS filter object.
52
+ */
53
+ @property({ type: Object })
54
+ lapisFilter: Record<string, string | string[] | number | null | boolean | undefined> & {
55
+ nucleotideMutations?: string[];
56
+ aminoAcidMutations?: string[];
57
+ nucleotideInsertions?: string[];
58
+ aminoAcidInsertions?: string[];
59
+ } = {};
60
+
47
61
  /**
48
62
  * The placeholder text to display in the input field.
49
63
  */
@@ -62,8 +76,9 @@ export class LineageFilterComponent extends PreactLitAdapter {
62
76
  return (
63
77
  <LineageFilter
64
78
  lapisField={this.lapisField}
79
+ lapisFilter={this.lapisFilter}
65
80
  placeholderText={this.placeholderText}
66
- initialValue={this.initialValue}
81
+ value={this.value}
67
82
  width={this.width}
68
83
  />
69
84
  );
@@ -76,7 +91,7 @@ declare global {
76
91
  }
77
92
 
78
93
  interface HTMLElementEventMap {
79
- 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
94
+ 'gs-lineage-filter-changed': LineageFilterChangedEvent;
80
95
  }
81
96
  }
82
97
 
@@ -90,12 +105,13 @@ declare global {
90
105
  }
91
106
 
92
107
  /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
93
- type InitialValueMatches = Expect<
94
- Equals<typeof LineageFilterComponent.prototype.initialValue, LineageFilterProps['initialValue']>
95
- >;
108
+ type InitialValueMatches = Expect<Equals<typeof LineageFilterComponent.prototype.value, LineageFilterProps['value']>>;
96
109
  type LapisFieldMatches = Expect<
97
110
  Equals<typeof LineageFilterComponent.prototype.lapisField, LineageFilterProps['lapisField']>
98
111
  >;
112
+ type LapisFilterMatches = Expect<
113
+ Equals<typeof LineageFilterComponent.prototype.lapisFilter, LineageFilterProps['lapisFilter']>
114
+ >;
99
115
  type PlaceholderTextMatches = Expect<
100
116
  Equals<typeof LineageFilterComponent.prototype.placeholderText, LineageFilterProps['placeholderText']>
101
117
  >;
@@ -15,6 +15,7 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
15
15
  const codeExample = String.raw`
16
16
  <gs-location-filter
17
17
  fields='["region", "country"]'
18
+ lapisFilter='{"age": 10}'
18
19
  value='{ "region": "Europe", "country": null}'
19
20
  width="100%"
20
21
  placeholderText="Enter a location"
@@ -54,6 +55,9 @@ const meta: Meta = {
54
55
  type: 'text',
55
56
  },
56
57
  },
58
+ lapisFilter: {
59
+ age: 18,
60
+ },
57
61
  },
58
62
  tags: ['autodocs'],
59
63
  };
@@ -66,6 +70,7 @@ const Template: StoryObj<LocationFilterProps> = {
66
70
  <div class="max-w-screen-lg">
67
71
  <gs-location-filter
68
72
  .fields=${args.fields}
73
+ .lapisFilter=${args.lapisFilter}
69
74
  .value=${args.value}
70
75
  .width=${args.width}
71
76
  placeholderText=${ifDefined(args.placeholderText)}
@@ -75,6 +80,9 @@ const Template: StoryObj<LocationFilterProps> = {
75
80
  },
76
81
  args: {
77
82
  fields: ['region', 'country', 'division', 'location'],
83
+ lapisFilter: {
84
+ age: 18,
85
+ },
78
86
  value: undefined,
79
87
  width: '100%',
80
88
  placeholderText: 'Enter a location',
@@ -86,6 +94,7 @@ const aggregatedEndpointMatcher = {
86
94
  url: AGGREGATED_ENDPOINT,
87
95
  body: {
88
96
  fields: ['region', 'country', 'division', 'location'],
97
+ age: 18,
89
98
  },
90
99
  };
91
100
 
@@ -12,10 +12,11 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
12
12
  * This component provides an input field to specify filters for locations.
13
13
  *
14
14
  * It expects a list of fields that form a strict hierarchical order, such as continent, country, and city.
15
- * The component retrieves a list of all possible values for these fields from the Lapis instance.
15
+ * The component retrieves a list of all possible values for these fields from the Lapis instance,
16
+ * within the `lapisFilter`.
16
17
  * This list is then utilized to display autocomplete suggestions and to validate the input.
17
18
  *
18
- * @fires {CustomEvent<Record<string, string>>} gs-location-changed
19
+ * @fires {CustomEvent<Record<string, string | undefined>>} gs-location-changed
19
20
  * Fired when a value from the datalist is selected or when a valid value is typed into the field.
20
21
  * The `details` of this event contain an object with all `fields` as keys
21
22
  * and the corresponding values as values, even if they are `undefined`.
@@ -35,7 +36,7 @@ export class LocationFilterComponent extends PreactLitAdapter {
35
36
  * The initial value to use for this location filter.
36
37
  */
37
38
  @property({ type: Object })
38
- value: Record<string, string | null | undefined> | undefined = undefined;
39
+ value: Record<string, string | undefined> | undefined = undefined;
39
40
 
40
41
  /**
41
42
  * Required.
@@ -48,6 +49,19 @@ export class LocationFilterComponent extends PreactLitAdapter {
48
49
  @property({ type: Array })
49
50
  fields: string[] = [];
50
51
 
52
+ /**
53
+ * The filter that is used to fetch the available the autocomplete options.
54
+ * If not set it fetches all available options.
55
+ * It must be a valid LAPIS filter object.
56
+ */
57
+ @property({ type: Object })
58
+ lapisFilter: Record<string, string | string[] | number | null | boolean | undefined> & {
59
+ nucleotideMutations?: string[];
60
+ aminoAcidMutations?: string[];
61
+ nucleotideInsertions?: string[];
62
+ aminoAcidInsertions?: string[];
63
+ } = {};
64
+
51
65
  /**
52
66
  * The width of the component.
53
67
  *
@@ -67,6 +81,7 @@ export class LocationFilterComponent extends PreactLitAdapter {
67
81
  <LocationFilter
68
82
  value={this.value}
69
83
  fields={this.fields}
84
+ lapisFilter={this.lapisFilter}
70
85
  width={this.width}
71
86
  placeholderText={this.placeholderText}
72
87
  />
@@ -96,6 +111,9 @@ declare global {
96
111
  /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
97
112
  type InitialValueMatches = Expect<Equals<typeof LocationFilterComponent.prototype.value, LocationFilterProps['value']>>;
98
113
  type FieldsMatches = Expect<Equals<typeof LocationFilterComponent.prototype.fields, LocationFilterProps['fields']>>;
114
+ type LapisFilterMatches = Expect<
115
+ Equals<typeof LocationFilterComponent.prototype.lapisFilter, LocationFilterProps['lapisFilter']>
116
+ >;
99
117
  type PlaceholderTextMatches = Expect<
100
118
  Equals<typeof LocationFilterComponent.prototype.placeholderText, LocationFilterProps['placeholderText']>
101
119
  >;
@@ -14,8 +14,9 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
14
14
  const codeExample = String.raw`
15
15
  <gs-text-input
16
16
  lapisField="host"
17
+ lapisFilter='{"country": "Germany"}'
17
18
  placeholderText="Enter host name"
18
- initialValue="Homo sapiens"
19
+ value="Homo sapiens"
19
20
  width="50%">
20
21
  </gs-text-input>`;
21
22
 
@@ -34,6 +35,7 @@ const meta: Meta<Required<TextInputProps>> = {
34
35
  url: AGGREGATED_ENDPOINT,
35
36
  body: {
36
37
  fields: ['host'],
38
+ country: 'Germany',
37
39
  },
38
40
  },
39
41
  response: {
@@ -60,7 +62,7 @@ const meta: Meta<Required<TextInputProps>> = {
60
62
  type: 'text',
61
63
  },
62
64
  },
63
- initialValue: {
65
+ value: {
64
66
  control: {
65
67
  type: 'text',
66
68
  },
@@ -70,6 +72,11 @@ const meta: Meta<Required<TextInputProps>> = {
70
72
  type: 'text',
71
73
  },
72
74
  },
75
+ lapisFilter: {
76
+ control: {
77
+ type: 'object',
78
+ },
79
+ },
73
80
  },
74
81
  tags: ['autodocs'],
75
82
  };
@@ -82,8 +89,9 @@ export const Default: StoryObj<Required<TextInputProps>> = {
82
89
  <div class="max-w-screen-lg">
83
90
  <gs-text-input
84
91
  .lapisField=${args.lapisField}
92
+ .lapisFilter=${args.lapisFilter}
85
93
  .placeholderText=${args.placeholderText}
86
- .initialValue=${args.initialValue}
94
+ .value=${args.value}
87
95
  .width=${args.width}
88
96
  ></gs-text-input>
89
97
  </div>
@@ -91,8 +99,9 @@ export const Default: StoryObj<Required<TextInputProps>> = {
91
99
  },
92
100
  args: {
93
101
  lapisField: 'host',
102
+ lapisFilter: { country: 'Germany' },
94
103
  placeholderText: 'Enter host name',
95
- initialValue: 'Homo sapiens',
104
+ value: 'Homo sapiens',
96
105
  width: '100%',
97
106
  },
98
107
  };
@@ -163,6 +172,6 @@ export const FiresEvents: StoryObj<Required<TextInputProps>> = {
163
172
  },
164
173
  args: {
165
174
  ...Default.args,
166
- initialValue: '',
175
+ value: '',
167
176
  },
168
177
  };