@genspectrum/dashboard-components 0.12.0 → 0.13.0

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 (37) hide show
  1. package/custom-elements.json +117 -28
  2. package/dist/{LocationChangedEvent-CORvQvXv.js → LineageFilterChangedEvent-GedKNGFI.js} +25 -3
  3. package/dist/LineageFilterChangedEvent-GedKNGFI.js.map +1 -0
  4. package/dist/components.d.ts +86 -52
  5. package/dist/components.js +251 -196
  6. package/dist/components.js.map +1 -1
  7. package/dist/util.d.ts +59 -45
  8. package/dist/util.js +3 -1
  9. package/package.json +1 -1
  10. package/src/preact/components/downshift-combobox.tsx +145 -0
  11. package/src/preact/lineageFilter/LineageFilterChangedEvent.ts +11 -0
  12. package/src/preact/lineageFilter/fetchLineageAutocompleteList.spec.ts +16 -2
  13. package/src/preact/lineageFilter/fetchLineageAutocompleteList.ts +13 -2
  14. package/src/preact/lineageFilter/lineage-filter.stories.tsx +110 -9
  15. package/src/preact/lineageFilter/lineage-filter.tsx +40 -50
  16. package/src/preact/locationFilter/LocationChangedEvent.ts +1 -1
  17. package/src/preact/locationFilter/fetchAutocompletionList.spec.ts +6 -2
  18. package/src/preact/locationFilter/fetchAutocompletionList.ts +16 -6
  19. package/src/preact/locationFilter/location-filter.stories.tsx +33 -30
  20. package/src/preact/locationFilter/location-filter.tsx +47 -144
  21. package/src/preact/map/sequences-by-location-map.tsx +3 -3
  22. package/src/preact/textInput/TextInputChangedEvent.ts +11 -0
  23. package/src/preact/textInput/fetchStringAutocompleteList.ts +20 -0
  24. package/src/preact/textInput/text-input.stories.tsx +34 -14
  25. package/src/preact/textInput/text-input.tsx +47 -45
  26. package/src/types.ts +3 -0
  27. package/src/utilEntrypoint.ts +2 -0
  28. package/src/web-components/input/gs-lineage-filter.stories.ts +120 -31
  29. package/src/web-components/input/gs-lineage-filter.tsx +24 -8
  30. package/src/web-components/input/gs-location-filter.stories.ts +9 -0
  31. package/src/web-components/input/gs-location-filter.tsx +21 -3
  32. package/src/web-components/input/gs-text-input.stories.ts +44 -12
  33. package/src/web-components/input/gs-text-input.tsx +23 -7
  34. package/standalone-bundle/dashboard-components.js +4931 -4863
  35. package/standalone-bundle/dashboard-components.js.map +1 -1
  36. package/dist/LocationChangedEvent-CORvQvXv.js.map +0 -1
  37. package/src/preact/textInput/fetchAutocompleteList.ts +0 -9
@@ -1,5 +1,5 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/preact';
2
- import { expect, waitFor, within } from '@storybook/test';
2
+ import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';
3
3
 
4
4
  import data from './__mockData__/aggregated_hosts.json';
5
5
  import { TextInput, type TextInputProps } from './text-input';
@@ -23,6 +23,7 @@ const meta: Meta<TextInputProps> = {
23
23
  url: AGGREGATED_ENDPOINT,
24
24
  body: {
25
25
  fields: ['host'],
26
+ country: 'Germany',
26
27
  },
27
28
  },
28
29
  response: {
@@ -36,16 +37,15 @@ const meta: Meta<TextInputProps> = {
36
37
  argTypes: {
37
38
  lapisField: {
38
39
  control: {
39
- type: 'select',
40
+ type: 'text',
40
41
  },
41
- options: ['host'],
42
42
  },
43
43
  placeholderText: {
44
44
  control: {
45
45
  type: 'text',
46
46
  },
47
47
  },
48
- initialValue: {
48
+ value: {
49
49
  control: {
50
50
  type: 'text',
51
51
  },
@@ -55,6 +55,11 @@ const meta: Meta<TextInputProps> = {
55
55
  type: 'text',
56
56
  },
57
57
  },
58
+ lapisFilter: {
59
+ control: {
60
+ type: 'object',
61
+ },
62
+ },
58
63
  },
59
64
  };
60
65
 
@@ -63,35 +68,50 @@ export default meta;
63
68
  export const Default: StoryObj<TextInputProps> = {
64
69
  render: (args) => (
65
70
  <LapisUrlContext.Provider value={LAPIS_URL}>
66
- <TextInput
67
- lapisField={args.lapisField}
68
- placeholderText={args.placeholderText}
69
- initialValue={args.initialValue}
70
- width={args.width}
71
- />
71
+ <TextInput {...args} />
72
72
  </LapisUrlContext.Provider>
73
73
  ),
74
74
  args: {
75
75
  lapisField: 'host',
76
76
  placeholderText: 'Enter a host name',
77
- initialValue: '',
77
+ value: '',
78
78
  width: '100%',
79
+ lapisFilter: {
80
+ country: 'Germany',
81
+ },
79
82
  },
80
83
  };
81
84
 
82
- export const WithInitialValue: StoryObj<TextInputProps> = {
85
+ export const RemoveInitialValue: StoryObj<TextInputProps> = {
83
86
  ...Default,
84
87
  args: {
85
88
  ...Default.args,
86
- initialValue: 'Homo sapiens',
89
+ value: 'Homo sapiens',
87
90
  },
88
- play: async ({ canvasElement }) => {
91
+ play: async ({ canvasElement, step }) => {
89
92
  const canvas = within(canvasElement);
90
93
 
94
+ const changedListenerMock = fn();
95
+ await step('Setup event listener mock', async () => {
96
+ canvasElement.addEventListener('gs-text-input-changed', changedListenerMock);
97
+ });
98
+
91
99
  await waitFor(() => {
92
100
  const input = canvas.getByPlaceholderText('Enter a host name', { exact: false });
93
101
  expect(input).toHaveValue('Homo sapiens');
94
102
  });
103
+
104
+ await step('Remove initial value', async () => {
105
+ await fireEvent.click(canvas.getByRole('button', { name: 'clear selection' }));
106
+
107
+ await expect(changedListenerMock).toHaveBeenCalledWith(
108
+ expect.objectContaining({
109
+ detail: {
110
+ host: undefined,
111
+ },
112
+ }),
113
+ );
114
+ });
95
115
  },
96
116
  };
97
117
 
@@ -1,27 +1,31 @@
1
1
  import { type FunctionComponent } from 'preact';
2
- import { useContext, useRef } from 'preact/hooks';
2
+ import { useContext } from 'preact/hooks';
3
3
  import z from 'zod';
4
4
 
5
- import { fetchAutocompleteList } from './fetchAutocompleteList';
5
+ import { fetchStringAutocompleteList } from './fetchStringAutocompleteList';
6
6
  import { LapisUrlContext } from '../LapisUrlContext';
7
+ import { TextInputChangedEvent } from './TextInputChangedEvent';
8
+ import { lapisFilterSchema } from '../../types';
9
+ import { DownshiftCombobox } from '../components/downshift-combobox';
7
10
  import { ErrorBoundary } from '../components/error-boundary';
8
11
  import { LoadingDisplay } from '../components/loading-display';
9
12
  import { NoDataDisplay } from '../components/no-data-display';
10
13
  import { ResizeContainer } from '../components/resize-container';
11
14
  import { useQuery } from '../useQuery';
12
15
 
13
- const textInputInnerPropsSchema = z.object({
16
+ const textSelectorPropsSchema = z.object({
14
17
  lapisField: z.string().min(1),
15
18
  placeholderText: z.string().optional(),
16
- initialValue: z.string().optional(),
19
+ value: z.string().optional(),
17
20
  });
18
-
21
+ const textInputInnerPropsSchema = textSelectorPropsSchema.extend({ lapisFilter: lapisFilterSchema });
19
22
  const textInputPropsSchema = textInputInnerPropsSchema.extend({
20
23
  width: z.string(),
21
24
  });
22
25
 
23
26
  export type TextInputInnerProps = z.infer<typeof textInputInnerPropsSchema>;
24
27
  export type TextInputProps = z.infer<typeof textInputPropsSchema>;
28
+ type TextSelectorProps = z.infer<typeof textSelectorPropsSchema>;
25
29
 
26
30
  export const TextInput: FunctionComponent<TextInputProps> = (props) => {
27
31
  const { width, ...innerProps } = props;
@@ -36,12 +40,18 @@ export const TextInput: FunctionComponent<TextInputProps> = (props) => {
36
40
  );
37
41
  };
38
42
 
39
- const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, placeholderText, initialValue }) => {
43
+ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({
44
+ value,
45
+ lapisField,
46
+ placeholderText,
47
+ lapisFilter,
48
+ }) => {
40
49
  const lapis = useContext(LapisUrlContext);
41
50
 
42
- const inputRef = useRef<HTMLInputElement>(null);
43
-
44
- const { data, error, isLoading } = useQuery(() => fetchAutocompleteList(lapis, lapisField), [lapisField, lapis]);
51
+ const { data, error, isLoading } = useQuery(
52
+ () => fetchStringAutocompleteList({ lapis, field: lapisField, lapisFilter }),
53
+ [lapisField, lapis, lapisFilter],
54
+ );
45
55
 
46
56
  if (isLoading) {
47
57
  return <LoadingDisplay />;
@@ -55,43 +65,35 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl
55
65
  return <NoDataDisplay />;
56
66
  }
57
67
 
58
- const onInput = () => {
59
- const value = inputRef.current?.value === '' ? undefined : inputRef.current?.value;
60
-
61
- if (isValidValue(value)) {
62
- inputRef.current?.dispatchEvent(
63
- new CustomEvent('gs-text-input-changed', {
64
- detail: { [lapisField]: value },
65
- bubbles: true,
66
- composed: true,
67
- }),
68
- );
69
- }
70
- };
71
-
72
- const isValidValue = (value: string | undefined) => {
73
- if (value === undefined) {
74
- return true;
75
- }
76
- return data.includes(value);
77
- };
68
+ return <TextSelector lapisField={lapisField} value={value} placeholderText={placeholderText} data={data} />;
69
+ };
78
70
 
71
+ const TextSelector = ({
72
+ lapisField,
73
+ value,
74
+ placeholderText,
75
+ data,
76
+ }: TextSelectorProps & {
77
+ data: string[];
78
+ }) => {
79
79
  return (
80
- <>
81
- <input
82
- type='text'
83
- class='input input-bordered w-full'
84
- placeholder={placeholderText ?? lapisField}
85
- onInput={onInput}
86
- ref={inputRef}
87
- list={lapisField}
88
- value={initialValue}
89
- />
90
- <datalist id={lapisField}>
91
- {data.map((item) => (
92
- <option value={item} key={item} />
93
- ))}
94
- </datalist>
95
- </>
80
+ <DownshiftCombobox
81
+ allItems={data}
82
+ value={value}
83
+ filterItemsByInputValue={filterByInputValue}
84
+ createEvent={(item: string | null) => new TextInputChangedEvent({ [lapisField]: item ?? undefined })}
85
+ itemToString={(item: string | undefined | null) => item ?? ''}
86
+ placeholderText={placeholderText}
87
+ formatItemInList={(item: string) => {
88
+ return <span>{item}</span>;
89
+ }}
90
+ />
96
91
  );
97
92
  };
93
+
94
+ function filterByInputValue(item: string, inputValue: string | undefined | null) {
95
+ if (inputValue === undefined || inputValue === null || inputValue === '') {
96
+ return true;
97
+ }
98
+ return item?.toLowerCase().includes(inputValue?.toLowerCase() || '');
99
+ }
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';
@@ -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
  >;