@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
package/dist/style.css CHANGED
@@ -3199,6 +3199,9 @@ input.tab:checked + .tab-content,
3199
3199
  .grid {
3200
3200
  display: grid;
3201
3201
  }
3202
+ .contents {
3203
+ display: contents;
3204
+ }
3202
3205
  .hidden {
3203
3206
  display: none;
3204
3207
  }
package/dist/util.d.ts CHANGED
@@ -160,7 +160,17 @@ declare const lapisFilterSchema: default_2.ZodIntersection<default_2.ZodRecord<d
160
160
  aminoAcidInsertions?: string[] | undefined;
161
161
  }>>;
162
162
 
163
- declare type LapisLocationFilter = Record<string, string | null | undefined>;
163
+ declare type LapisLineageFilter = Record<string, string | undefined>;
164
+
165
+ declare type LapisLocationFilter = default_2.infer<typeof lapisLocationFilterSchema>;
166
+
167
+ declare const lapisLocationFilterSchema: default_2.ZodRecord<default_2.ZodString, default_2.ZodUnion<[default_2.ZodString, default_2.ZodUndefined]>>;
168
+
169
+ declare type LapisTextFilter = Record<string, string | undefined>;
170
+
171
+ export declare class LineageFilterChangedEvent extends CustomEvent<LapisLineageFilter> {
172
+ constructor(detail: LapisLineageFilter);
173
+ }
164
174
 
165
175
  export declare class LocationChangedEvent extends CustomEvent<LapisLocationFilter> {
166
176
  constructor(detail: LapisLocationFilter);
@@ -772,6 +782,10 @@ export declare type TemporalGranularity = default_2.infer<typeof temporalGranula
772
782
 
773
783
  declare const temporalGranularitySchema: default_2.ZodUnion<[default_2.ZodLiteral<"day">, default_2.ZodLiteral<"week">, default_2.ZodLiteral<"month">, default_2.ZodLiteral<"year">]>;
774
784
 
785
+ export declare class TextInputChangedEvent extends CustomEvent<LapisTextFilter> {
786
+ constructor(detail: LapisTextFilter);
787
+ }
788
+
775
789
  export declare const views: {
776
790
  readonly table: "table";
777
791
  readonly venn: "venn";
@@ -822,6 +836,22 @@ declare global {
822
836
  }
823
837
 
824
838
 
839
+ declare global {
840
+ interface HTMLElementTagNameMap {
841
+ 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
842
+ }
843
+ }
844
+
845
+
846
+ declare global {
847
+ namespace JSX {
848
+ interface IntrinsicElements {
849
+ 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
850
+ }
851
+ }
852
+ }
853
+
854
+
825
855
  declare global {
826
856
  interface HTMLElementTagNameMap {
827
857
  'gs-mutation-comparison-component': MutationComparisonComponent;
@@ -888,7 +918,7 @@ declare global {
888
918
 
889
919
  declare global {
890
920
  interface HTMLElementTagNameMap {
891
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
921
+ 'gs-aggregate': AggregateComponent;
892
922
  }
893
923
  }
894
924
 
@@ -896,7 +926,7 @@ declare global {
896
926
  declare global {
897
927
  namespace JSX {
898
928
  interface IntrinsicElements {
899
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
929
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
900
930
  }
901
931
  }
902
932
  }
@@ -904,7 +934,7 @@ declare global {
904
934
 
905
935
  declare global {
906
936
  interface HTMLElementTagNameMap {
907
- 'gs-aggregate': AggregateComponent;
937
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
908
938
  }
909
939
  }
910
940
 
@@ -912,7 +942,7 @@ declare global {
912
942
  declare global {
913
943
  namespace JSX {
914
944
  interface IntrinsicElements {
915
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
945
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
916
946
  }
917
947
  }
918
948
  }
@@ -920,7 +950,7 @@ declare global {
920
950
 
921
951
  declare global {
922
952
  interface HTMLElementTagNameMap {
923
- 'gs-mutations-over-time': MutationsOverTimeComponent;
953
+ 'gs-sequences-by-location': SequencesByLocationComponent;
924
954
  }
925
955
  }
926
956
 
@@ -928,7 +958,7 @@ declare global {
928
958
  declare global {
929
959
  namespace JSX {
930
960
  interface IntrinsicElements {
931
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
961
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
932
962
  }
933
963
  }
934
964
  }
@@ -936,7 +966,7 @@ declare global {
936
966
 
937
967
  declare global {
938
968
  interface HTMLElementTagNameMap {
939
- 'gs-sequences-by-location': SequencesByLocationComponent;
969
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
940
970
  }
941
971
  }
942
972
 
@@ -944,7 +974,7 @@ declare global {
944
974
  declare global {
945
975
  namespace JSX {
946
976
  interface IntrinsicElements {
947
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
977
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
948
978
  }
949
979
  }
950
980
  }
@@ -1007,10 +1037,10 @@ declare global {
1007
1037
 
1008
1038
  declare global {
1009
1039
  interface HTMLElementTagNameMap {
1010
- 'gs-mutation-filter': MutationFilterComponent;
1040
+ 'gs-text-input': TextInputComponent;
1011
1041
  }
1012
1042
  interface HTMLElementEventMap {
1013
- 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
1043
+ 'gs-text-input-changed': TextInputChangedEvent;
1014
1044
  }
1015
1045
  }
1016
1046
 
@@ -1018,7 +1048,7 @@ declare global {
1018
1048
  declare global {
1019
1049
  namespace JSX {
1020
1050
  interface IntrinsicElements {
1021
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1051
+ 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1022
1052
  }
1023
1053
  }
1024
1054
  }
@@ -1026,10 +1056,10 @@ declare global {
1026
1056
 
1027
1057
  declare global {
1028
1058
  interface HTMLElementTagNameMap {
1029
- 'gs-lineage-filter': LineageFilterComponent;
1059
+ 'gs-mutation-filter': MutationFilterComponent;
1030
1060
  }
1031
1061
  interface HTMLElementEventMap {
1032
- 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
1062
+ 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
1033
1063
  }
1034
1064
  }
1035
1065
 
@@ -1037,7 +1067,7 @@ declare global {
1037
1067
  declare global {
1038
1068
  namespace JSX {
1039
1069
  interface IntrinsicElements {
1040
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1070
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1041
1071
  }
1042
1072
  }
1043
1073
  }
@@ -1045,10 +1075,10 @@ declare global {
1045
1075
 
1046
1076
  declare global {
1047
1077
  interface HTMLElementTagNameMap {
1048
- 'gs-text-input': TextInputComponent;
1078
+ 'gs-lineage-filter': LineageFilterComponent;
1049
1079
  }
1050
1080
  interface HTMLElementEventMap {
1051
- 'gs-text-input-changed': CustomEvent<Record<string, string>>;
1081
+ 'gs-lineage-filter-changed': LineageFilterChangedEvent;
1052
1082
  }
1053
1083
  }
1054
1084
 
@@ -1056,7 +1086,7 @@ declare global {
1056
1086
  declare global {
1057
1087
  namespace JSX {
1058
1088
  interface IntrinsicElements {
1059
- 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1089
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1060
1090
  }
1061
1091
  }
1062
1092
  }
package/dist/util.js CHANGED
@@ -1,7 +1,9 @@
1
- import { D, L, d, v } from "./LocationChangedEvent-CORvQvXv.js";
1
+ import { D, a, L, T, d, v } from "./LineageFilterChangedEvent-GedKNGFI.js";
2
2
  export {
3
3
  D as DateRangeOptionChangedEvent,
4
+ a as LineageFilterChangedEvent,
4
5
  L as LocationChangedEvent,
6
+ T as TextInputChangedEvent,
5
7
  d as dateRangeOptionPresets,
6
8
  v as views
7
9
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -142,6 +142,6 @@
142
142
  "typescript": "~5.7.2",
143
143
  "vite": "^6.0.3",
144
144
  "vite-plugin-dts": "^4.0.3",
145
- "vitest": "^2.0.1"
145
+ "vitest": "^3.0.2"
146
146
  }
147
147
  }
package/src/constants.ts CHANGED
@@ -5,3 +5,9 @@ export const NUCLEOTIDE_MUTATIONS_ENDPOINT = `${LAPIS_URL}/sample/nucleotideMuta
5
5
  export const AMINO_ACID_MUTATIONS_ENDPOINT = `${LAPIS_URL}/sample/aminoAcidMutations`;
6
6
  export const NUCLEOTIDE_INSERTIONS_ENDPOINT = `${LAPIS_URL}/sample/nucleotideInsertions`;
7
7
  export const REFERENCE_GENOME_ENDPOINT = `${LAPIS_URL}/sample/referenceGenome`;
8
+
9
+ // WISE Wastewater
10
+ // This is a special instance for storing Swiss wastewater data generated by the WISE consortium
11
+ export const WISE_LAPIS_URL = 'https://api.wise-loculus.genspectrum.org/rsv';
12
+ export const WISE_DETAILS_ENDPOINT = `${WISE_LAPIS_URL}/sample/details`;
13
+ export const WISE_REFERENCE_GENOME_ENDPOINT = `${WISE_LAPIS_URL}/sample/referenceGenome`;
@@ -0,0 +1,9 @@
1
+ {
2
+ "nucleotideSequences": [
3
+ {
4
+ "name": "main",
5
+ "sequence": "NNN"
6
+ }
7
+ ],
8
+ "genes": []
9
+ }
@@ -1,6 +1,7 @@
1
1
  import { referenceGenomeResponse } from './ReferenceGenome';
2
2
  import {
3
3
  aggregatedResponse,
4
+ detailsResponse,
4
5
  insertionsResponse,
5
6
  type LapisBaseRequest,
6
7
  lapisError,
@@ -51,6 +52,21 @@ export async function fetchAggregated(lapisUrl: string, body: LapisBaseRequest,
51
52
  return aggregatedResponse.parse(await response.json());
52
53
  }
53
54
 
55
+ export async function fetchDetails(lapisUrl: string, body: LapisBaseRequest, signal?: AbortSignal) {
56
+ const response = await fetch(detailsEndpoint(lapisUrl), {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ },
61
+ body: JSON.stringify(body),
62
+ signal,
63
+ });
64
+
65
+ await handleErrors(response, 'aggregated data');
66
+
67
+ return detailsResponse.parse(await response.json());
68
+ }
69
+
54
70
  export async function fetchInsertions(
55
71
  lapisUrl: string,
56
72
  body: LapisBaseRequest,
@@ -163,6 +179,7 @@ const handleErrors = async (response: Response, requestedData: string) => {
163
179
  };
164
180
 
165
181
  export const aggregatedEndpoint = (lapisUrl: string) => `${lapisUrl}/sample/aggregated`;
182
+ export const detailsEndpoint = (lapisUrl: string) => `${lapisUrl}/sample/details`;
166
183
  export const insertionsEndpoint = (lapisUrl: string, sequenceType: SequenceType) => {
167
184
  return sequenceType === 'amino acid'
168
185
  ? `${lapisUrl}/sample/aminoAcidInsertions`
@@ -41,10 +41,16 @@ const insertionCount = z.object({
41
41
  });
42
42
  export const insertionsResponse = makeLapisResponse(z.array(insertionCount));
43
43
 
44
- export const aggregatedItem = z.object({ count: z.number() }).catchall(z.union([z.string(), z.number(), z.null()]));
44
+ const baseResponseValueSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
45
+
46
+ export const aggregatedItem = z.object({ count: z.number() }).catchall(baseResponseValueSchema);
45
47
  export const aggregatedResponse = makeLapisResponse(z.array(aggregatedItem));
46
48
  export type AggregatedItem = z.infer<typeof aggregatedItem>;
47
49
 
50
+ export const detailsItem = z.object({}).catchall(baseResponseValueSchema);
51
+ export const detailsResponse = makeLapisResponse(z.array(detailsItem));
52
+ export type DetailsItem = z.infer<typeof detailsItem>;
53
+
48
54
  function makeLapisResponse<T extends ZodTypeAny>(data: T) {
49
55
  return z.object({
50
56
  data,
@@ -0,0 +1,28 @@
1
+ import { type Dataset } from './Dataset';
2
+ import { type Operator } from './Operator';
3
+ import { fetchDetails } from '../lapisApi/lapisApi';
4
+ import { type LapisFilter } from '../types';
5
+
6
+ type Details<Fields extends string> = { [field in Fields]: string | number | boolean | null };
7
+
8
+ export class FetchDetailsOperator<Fields extends string> implements Operator<Details<Fields>> {
9
+ constructor(
10
+ private filter: LapisFilter,
11
+ private fields: Fields[] = [],
12
+ ) {}
13
+
14
+ async evaluate(lapisUrl: string, signal?: AbortSignal): Promise<Dataset<Details<Fields>>> {
15
+ const detailsResponse = (
16
+ await fetchDetails(
17
+ lapisUrl,
18
+ {
19
+ ...this.filter,
20
+ fields: this.fields,
21
+ },
22
+ signal,
23
+ )
24
+ ).data;
25
+
26
+ return { content: detailsResponse as Details<Fields>[] };
27
+ }
28
+ }
@@ -0,0 +1,145 @@
1
+ import { useCombobox } from 'downshift/preact';
2
+ import { type ComponentChild } from 'preact';
3
+ import { useRef, useState } from 'preact/hooks';
4
+
5
+ export function DownshiftCombobox<Item>({
6
+ allItems,
7
+ value,
8
+ filterItemsByInputValue,
9
+ createEvent,
10
+ itemToString,
11
+ placeholderText,
12
+ formatItemInList,
13
+ }: {
14
+ allItems: Item[];
15
+ value?: Item;
16
+ filterItemsByInputValue: (item: Item, value: string) => boolean;
17
+ createEvent: (item: Item | null) => CustomEvent;
18
+ itemToString: (item: Item | undefined | null) => string;
19
+ placeholderText?: string;
20
+ formatItemInList: (item: Item) => ComponentChild;
21
+ }) {
22
+ const initialSelectedItem = value ?? null;
23
+
24
+ const [items, setItems] = useState(
25
+ allItems.filter((item) => filterItemsByInputValue(item, itemToString(initialSelectedItem))),
26
+ );
27
+ const divRef = useRef<HTMLDivElement>(null);
28
+
29
+ const shadowRoot = divRef.current?.shadowRoot ?? undefined;
30
+
31
+ const environment =
32
+ shadowRoot !== undefined
33
+ ? {
34
+ addEventListener: window.addEventListener.bind(window),
35
+ removeEventListener: window.removeEventListener.bind(window),
36
+ document: shadowRoot.ownerDocument,
37
+ Node: window.Node,
38
+ }
39
+ : undefined;
40
+
41
+ const {
42
+ isOpen,
43
+ getToggleButtonProps,
44
+ getMenuProps,
45
+ getInputProps,
46
+ highlightedIndex,
47
+ getItemProps,
48
+ selectedItem,
49
+ inputValue,
50
+ selectItem,
51
+ setInputValue,
52
+ closeMenu,
53
+ } = useCombobox({
54
+ onInputValueChange({ inputValue }) {
55
+ setItems(allItems.filter((item) => filterItemsByInputValue(item, inputValue)));
56
+ },
57
+ onSelectedItemChange({ selectedItem }) {
58
+ if (selectedItem !== null) {
59
+ divRef.current?.dispatchEvent(createEvent(selectedItem));
60
+ }
61
+ },
62
+ items,
63
+ itemToString(item) {
64
+ return itemToString(item);
65
+ },
66
+ initialSelectedItem,
67
+ environment,
68
+ });
69
+
70
+ const onInputBlur = () => {
71
+ if (inputValue === '') {
72
+ divRef.current?.dispatchEvent(createEvent(null));
73
+ selectItem(null);
74
+ } else if (inputValue !== itemToString(selectedItem)) {
75
+ setInputValue(itemToString(selectedItem) || '');
76
+ }
77
+ };
78
+
79
+ const clearInput = () => {
80
+ divRef.current?.dispatchEvent(createEvent(null));
81
+ selectItem(null);
82
+ };
83
+
84
+ const buttonRef = useRef(null);
85
+
86
+ return (
87
+ <div ref={divRef} className={'relative w-full'}>
88
+ <div className='w-full flex flex-col gap-1'>
89
+ <div
90
+ className='flex gap-0.5 input input-bordered min-w-32'
91
+ onBlur={(event) => {
92
+ if (event.relatedTarget != buttonRef.current) {
93
+ closeMenu();
94
+ }
95
+ }}
96
+ >
97
+ <input
98
+ placeholder={placeholderText}
99
+ className='w-full p-1.5'
100
+ {...getInputProps()}
101
+ onBlur={onInputBlur}
102
+ />
103
+ <button
104
+ aria-label='clear selection'
105
+ className={`px-2 ${inputValue === '' && 'hidden'}`}
106
+ type='button'
107
+ onClick={clearInput}
108
+ tabIndex={-1}
109
+ >
110
+ ×
111
+ </button>
112
+ <button
113
+ aria-label='toggle menu'
114
+ className='px-2'
115
+ type='button'
116
+ {...getToggleButtonProps()}
117
+ ref={buttonRef}
118
+ >
119
+ {isOpen ? <>↑</> : <>↓</>}
120
+ </button>
121
+ </div>
122
+ </div>
123
+ {isOpen && (
124
+ <ul
125
+ className='absolute bg-white mt-1 shadow-md max-h-80 overflow-scroll z-10 w-full min-w-32'
126
+ {...getMenuProps()}
127
+ >
128
+ {items.length > 0 ? (
129
+ items.map((item, index) => (
130
+ <li
131
+ className={`${highlightedIndex === index ? 'bg-blue-300' : ''} ${selectedItem !== null && itemToString(selectedItem) === itemToString(item) ? 'font-bold' : ''} py-2 px-3 shadow-sm flex flex-col`}
132
+ key={itemToString(item)}
133
+ {...getItemProps({ item, index })}
134
+ >
135
+ {formatItemInList(item)}
136
+ </li>
137
+ ))
138
+ ) : (
139
+ <li className='py-2 px-3 shadow-sm flex flex-col'>No elements to select.</li>
140
+ )}
141
+ </ul>
142
+ )}
143
+ </div>
144
+ );
145
+ }
@@ -16,7 +16,7 @@ const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
16
16
  const [activeTab, setActiveTab] = useState(tabs[0]?.title);
17
17
 
18
18
  const tabElements = (
19
- <div className='flex flex-row'>
19
+ <div className='flex flex-row flex-wrap'>
20
20
  {tabs.map((tab) => {
21
21
  return (
22
22
  <button
@@ -0,0 +1,11 @@
1
+ type LapisLineageFilter = Record<string, string | undefined>;
2
+
3
+ export class LineageFilterChangedEvent extends CustomEvent<LapisLineageFilter> {
4
+ constructor(detail: LapisLineageFilter) {
5
+ super('gs-lineage-filter-changed', {
6
+ detail,
7
+ bubbles: true,
8
+ composed: true,
9
+ });
10
+ }
11
+ }
@@ -5,9 +5,23 @@ import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../../vitest.setup';
5
5
 
6
6
  describe('fetchLineageAutocompleteList', () => {
7
7
  test('should add sublineage values', async () => {
8
- lapisRequestMocks.aggregated({ fields: ['lineageField'] }, { data: [{ lineageField: 'B.1.1.7', count: 1 }] });
8
+ lapisRequestMocks.aggregated(
9
+ { fields: ['lineageField'], country: 'Germany' },
10
+ {
11
+ data: [
12
+ {
13
+ lineageField: 'B.1.1.7',
14
+ count: 1,
15
+ },
16
+ ],
17
+ },
18
+ );
9
19
 
10
- const result = await fetchLineageAutocompleteList(DUMMY_LAPIS_URL, 'lineageField');
20
+ const result = await fetchLineageAutocompleteList({
21
+ lapis: DUMMY_LAPIS_URL,
22
+ field: 'lineageField',
23
+ lapisFilter: { country: 'Germany' },
24
+ });
11
25
 
12
26
  expect(result).to.deep.equal(['B.1.1.7', 'B.1.1.7*']);
13
27
  });
@@ -1,7 +1,18 @@
1
1
  import { FetchAggregatedOperator } from '../../operator/FetchAggregatedOperator';
2
+ import type { LapisFilter } from '../../types';
2
3
 
3
- export async function fetchLineageAutocompleteList(lapis: string, field: string, signal?: AbortSignal) {
4
- const fetchAggregatedOperator = new FetchAggregatedOperator<Record<string, string>>({}, [field]);
4
+ export async function fetchLineageAutocompleteList({
5
+ lapis,
6
+ field,
7
+ signal,
8
+ lapisFilter,
9
+ }: {
10
+ lapis: string;
11
+ field: string;
12
+ lapisFilter?: LapisFilter;
13
+ signal?: AbortSignal;
14
+ }) {
15
+ const fetchAggregatedOperator = new FetchAggregatedOperator<Record<string, string>>(lapisFilter ?? {}, [field]);
5
16
 
6
17
  const data = (await fetchAggregatedOperator.evaluate(lapis, signal)).content;
7
18