@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
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";
@@ -824,10 +838,7 @@ declare global {
824
838
 
825
839
  declare global {
826
840
  interface HTMLElementTagNameMap {
827
- 'gs-location-filter': LocationFilterComponent;
828
- }
829
- interface HTMLElementEventMap {
830
- 'gs-location-changed': LocationChangedEvent;
841
+ 'gs-mutation-comparison-component': MutationComparisonComponent;
831
842
  }
832
843
  }
833
844
 
@@ -835,7 +846,7 @@ declare global {
835
846
  declare global {
836
847
  namespace JSX {
837
848
  interface IntrinsicElements {
838
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
849
+ 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
839
850
  }
840
851
  }
841
852
  }
@@ -843,11 +854,7 @@ declare global {
843
854
 
844
855
  declare global {
845
856
  interface HTMLElementTagNameMap {
846
- 'gs-date-range-selector': DateRangeSelectorComponent;
847
- }
848
- interface HTMLElementEventMap {
849
- 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
850
- 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
857
+ 'gs-mutations-component': MutationsComponent;
851
858
  }
852
859
  }
853
860
 
@@ -855,7 +862,7 @@ declare global {
855
862
  declare global {
856
863
  namespace JSX {
857
864
  interface IntrinsicElements {
858
- 'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
865
+ 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
859
866
  }
860
867
  }
861
868
  }
@@ -863,10 +870,7 @@ declare global {
863
870
 
864
871
  declare global {
865
872
  interface HTMLElementTagNameMap {
866
- 'gs-text-input': TextInputComponent;
867
- }
868
- interface HTMLElementEventMap {
869
- 'gs-text-input-changed': CustomEvent<Record<string, string>>;
873
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
870
874
  }
871
875
  }
872
876
 
@@ -874,7 +878,7 @@ declare global {
874
878
  declare global {
875
879
  namespace JSX {
876
880
  interface IntrinsicElements {
877
- 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
881
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
878
882
  }
879
883
  }
880
884
  }
@@ -882,10 +886,7 @@ declare global {
882
886
 
883
887
  declare global {
884
888
  interface HTMLElementTagNameMap {
885
- 'gs-mutation-filter': MutationFilterComponent;
886
- }
887
- interface HTMLElementEventMap {
888
- 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
889
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
889
890
  }
890
891
  }
891
892
 
@@ -893,7 +894,7 @@ declare global {
893
894
  declare global {
894
895
  namespace JSX {
895
896
  interface IntrinsicElements {
896
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
897
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
897
898
  }
898
899
  }
899
900
  }
@@ -901,10 +902,7 @@ declare global {
901
902
 
902
903
  declare global {
903
904
  interface HTMLElementTagNameMap {
904
- 'gs-lineage-filter': LineageFilterComponent;
905
- }
906
- interface HTMLElementEventMap {
907
- 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
905
+ 'gs-aggregate': AggregateComponent;
908
906
  }
909
907
  }
910
908
 
@@ -912,7 +910,7 @@ declare global {
912
910
  declare global {
913
911
  namespace JSX {
914
912
  interface IntrinsicElements {
915
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
913
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
916
914
  }
917
915
  }
918
916
  }
@@ -920,7 +918,7 @@ declare global {
920
918
 
921
919
  declare global {
922
920
  interface HTMLElementTagNameMap {
923
- 'gs-mutation-comparison-component': MutationComparisonComponent;
921
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
924
922
  }
925
923
  }
926
924
 
@@ -928,7 +926,7 @@ declare global {
928
926
  declare global {
929
927
  namespace JSX {
930
928
  interface IntrinsicElements {
931
- 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
929
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
932
930
  }
933
931
  }
934
932
  }
@@ -936,7 +934,7 @@ declare global {
936
934
 
937
935
  declare global {
938
936
  interface HTMLElementTagNameMap {
939
- 'gs-mutations-component': MutationsComponent;
937
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
940
938
  }
941
939
  }
942
940
 
@@ -944,7 +942,7 @@ declare global {
944
942
  declare global {
945
943
  namespace JSX {
946
944
  interface IntrinsicElements {
947
- 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
945
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
948
946
  }
949
947
  }
950
948
  }
@@ -952,7 +950,7 @@ declare global {
952
950
 
953
951
  declare global {
954
952
  interface HTMLElementTagNameMap {
955
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
953
+ 'gs-sequences-by-location': SequencesByLocationComponent;
956
954
  }
957
955
  }
958
956
 
@@ -960,7 +958,7 @@ declare global {
960
958
  declare global {
961
959
  namespace JSX {
962
960
  interface IntrinsicElements {
963
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
961
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
964
962
  }
965
963
  }
966
964
  }
@@ -968,7 +966,7 @@ declare global {
968
966
 
969
967
  declare global {
970
968
  interface HTMLElementTagNameMap {
971
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
969
+ 'gs-statistics': StatisticsComponent;
972
970
  }
973
971
  }
974
972
 
@@ -976,7 +974,7 @@ declare global {
976
974
  declare global {
977
975
  namespace JSX {
978
976
  interface IntrinsicElements {
979
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
977
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
980
978
  }
981
979
  }
982
980
  }
@@ -984,7 +982,11 @@ declare global {
984
982
 
985
983
  declare global {
986
984
  interface HTMLElementTagNameMap {
987
- 'gs-aggregate': AggregateComponent;
985
+ 'gs-date-range-selector': DateRangeSelectorComponent;
986
+ }
987
+ interface HTMLElementEventMap {
988
+ 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
989
+ 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
988
990
  }
989
991
  }
990
992
 
@@ -992,7 +994,7 @@ declare global {
992
994
  declare global {
993
995
  namespace JSX {
994
996
  interface IntrinsicElements {
995
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
997
+ 'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
996
998
  }
997
999
  }
998
1000
  }
@@ -1000,7 +1002,10 @@ declare global {
1000
1002
 
1001
1003
  declare global {
1002
1004
  interface HTMLElementTagNameMap {
1003
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1005
+ 'gs-location-filter': LocationFilterComponent;
1006
+ }
1007
+ interface HTMLElementEventMap {
1008
+ 'gs-location-changed': LocationChangedEvent;
1004
1009
  }
1005
1010
  }
1006
1011
 
@@ -1008,7 +1013,7 @@ declare global {
1008
1013
  declare global {
1009
1014
  namespace JSX {
1010
1015
  interface IntrinsicElements {
1011
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1016
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1012
1017
  }
1013
1018
  }
1014
1019
  }
@@ -1016,7 +1021,10 @@ declare global {
1016
1021
 
1017
1022
  declare global {
1018
1023
  interface HTMLElementTagNameMap {
1019
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1024
+ 'gs-text-input': TextInputComponent;
1025
+ }
1026
+ interface HTMLElementEventMap {
1027
+ 'gs-text-input-changed': TextInputChangedEvent;
1020
1028
  }
1021
1029
  }
1022
1030
 
@@ -1024,7 +1032,7 @@ declare global {
1024
1032
  declare global {
1025
1033
  namespace JSX {
1026
1034
  interface IntrinsicElements {
1027
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1035
+ 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1028
1036
  }
1029
1037
  }
1030
1038
  }
@@ -1032,7 +1040,10 @@ declare global {
1032
1040
 
1033
1041
  declare global {
1034
1042
  interface HTMLElementTagNameMap {
1035
- 'gs-sequences-by-location': SequencesByLocationComponent;
1043
+ 'gs-mutation-filter': MutationFilterComponent;
1044
+ }
1045
+ interface HTMLElementEventMap {
1046
+ 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
1036
1047
  }
1037
1048
  }
1038
1049
 
@@ -1040,7 +1051,7 @@ declare global {
1040
1051
  declare global {
1041
1052
  namespace JSX {
1042
1053
  interface IntrinsicElements {
1043
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1054
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1044
1055
  }
1045
1056
  }
1046
1057
  }
@@ -1048,7 +1059,10 @@ declare global {
1048
1059
 
1049
1060
  declare global {
1050
1061
  interface HTMLElementTagNameMap {
1051
- 'gs-statistics': StatisticsComponent;
1062
+ 'gs-lineage-filter': LineageFilterComponent;
1063
+ }
1064
+ interface HTMLElementEventMap {
1065
+ 'gs-lineage-filter-changed': LineageFilterChangedEvent;
1052
1066
  }
1053
1067
  }
1054
1068
 
@@ -1056,7 +1070,7 @@ declare global {
1056
1070
  declare global {
1057
1071
  namespace JSX {
1058
1072
  interface IntrinsicElements {
1059
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1073
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1060
1074
  }
1061
1075
  }
1062
1076
  }
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.0",
3
+ "version": "0.13.0",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -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
+ }
@@ -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
 
@@ -1,4 +1,6 @@
1
- import { type Meta, type StoryObj } from '@storybook/preact';
1
+ import { type Meta, type PreactRenderer, type StoryObj } from '@storybook/preact';
2
+ import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
3
+ import type { StepFunction } from '@storybook/types';
2
4
 
3
5
  import { LineageFilter, type LineageFilterProps } from './lineage-filter';
4
6
  import { previewHandles } from '../../../.storybook/preview';
@@ -22,6 +24,7 @@ const meta: Meta = {
22
24
  url: AGGREGATED_ENDPOINT,
23
25
  body: {
24
26
  fields: ['pangoLineage'],
27
+ country: 'Germany',
25
28
  },
26
29
  },
27
30
  response: {
@@ -32,10 +35,41 @@ const meta: Meta = {
32
35
  ],
33
36
  },
34
37
  },
38
+ argTypes: {
39
+ lapisField: {
40
+ control: {
41
+ type: 'text',
42
+ },
43
+ },
44
+ placeholderText: {
45
+ control: {
46
+ type: 'text',
47
+ },
48
+ },
49
+ value: {
50
+ control: {
51
+ type: 'text',
52
+ },
53
+ },
54
+ width: {
55
+ control: {
56
+ type: 'text',
57
+ },
58
+ },
59
+ lapisFilter: {
60
+ control: {
61
+ type: 'object',
62
+ },
63
+ },
64
+ },
65
+
35
66
  args: {
36
67
  lapisField: 'pangoLineage',
37
- placeholderText: 'Enter lineage',
38
- initialValue: '',
68
+ lapisFilter: {
69
+ country: 'Germany',
70
+ },
71
+ placeholderText: 'Enter a lineage',
72
+ value: 'A.1',
39
73
  width: '100%',
40
74
  },
41
75
  };
@@ -45,14 +79,62 @@ export default meta;
45
79
  export const Default: StoryObj<LineageFilterProps> = {
46
80
  render: (args) => (
47
81
  <LapisUrlContext.Provider value={LAPIS_URL}>
48
- <LineageFilter
49
- lapisField={args.lapisField}
50
- placeholderText={args.placeholderText}
51
- initialValue={args.initialValue}
52
- width={args.width}
53
- />
82
+ <LineageFilter {...args} />
54
83
  </LapisUrlContext.Provider>
55
84
  ),
85
+ play: async ({ canvasElement, step }) => {
86
+ const { canvas, lineageChangedListenerMock } = await prepare(canvasElement, step);
87
+
88
+ step('change lineage filter value fires event', async () => {
89
+ const input = await inputField(canvas);
90
+ await userEvent.clear(input);
91
+ await userEvent.type(input, 'B.1');
92
+ await userEvent.click(canvas.getByRole('option', { name: 'B.1' }));
93
+
94
+ await waitFor(() => {
95
+ return expect(lineageChangedListenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
96
+ pangoLineage: 'B.1',
97
+ });
98
+ });
99
+ });
100
+ },
101
+ };
102
+
103
+ export const ClearSelection: StoryObj<LineageFilterProps> = {
104
+ ...Default,
105
+ play: async ({ canvasElement, step }) => {
106
+ const { canvas, lineageChangedListenerMock } = await prepare(canvasElement, step);
107
+
108
+ step('clear selection fires event with empty filter', async () => {
109
+ const clearSelectionButton = await canvas.findByLabelText('clear selection');
110
+ await userEvent.click(clearSelectionButton);
111
+
112
+ await waitFor(() => {
113
+ return expect(lineageChangedListenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
114
+ pangoLineage: undefined,
115
+ });
116
+ });
117
+ });
118
+ },
119
+ };
120
+
121
+ export const OnBlurInput: StoryObj<LineageFilterProps> = {
122
+ ...Default,
123
+ play: async ({ canvasElement, step }) => {
124
+ const { canvas, lineageChangedListenerMock } = await prepare(canvasElement, step);
125
+
126
+ step('after cleared selection by hand and then blur fires event with empty filter', async () => {
127
+ const input = await inputField(canvas);
128
+ await userEvent.clear(input);
129
+ await userEvent.click(canvas.getByLabelText('toggle menu'));
130
+
131
+ await waitFor(() => {
132
+ return expect(lineageChangedListenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
133
+ pangoLineage: undefined,
134
+ });
135
+ });
136
+ });
137
+ },
56
138
  };
57
139
 
58
140
  export const WithNoLapisField: StoryObj<LineageFilterProps> = {
@@ -67,3 +149,22 @@ export const WithNoLapisField: StoryObj<LineageFilterProps> = {
67
149
  });
68
150
  },
69
151
  };
152
+
153
+ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRenderer, unknown>) {
154
+ const canvas = within(canvasElement);
155
+
156
+ const lineageChangedListenerMock = fn();
157
+ step('Setup event listener mock', () => {
158
+ canvasElement.addEventListener('gs-lineage-filter-changed', lineageChangedListenerMock);
159
+ });
160
+
161
+ step('location filter is rendered with value', async () => {
162
+ await waitFor(async () => {
163
+ return expect(await inputField(canvas)).toHaveValue('A.1');
164
+ });
165
+ });
166
+
167
+ return { canvas, lineageChangedListenerMock };
168
+ }
169
+
170
+ const inputField = (canvas: ReturnType<typeof within>) => canvas.findByPlaceholderText('Enter a lineage');