@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.
- package/custom-elements.json +117 -28
- package/dist/{LocationChangedEvent-CORvQvXv.js → LineageFilterChangedEvent-GedKNGFI.js} +25 -3
- package/dist/LineageFilterChangedEvent-GedKNGFI.js.map +1 -0
- package/dist/components.d.ts +86 -52
- package/dist/components.js +251 -196
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +59 -45
- package/dist/util.js +3 -1
- package/package.json +1 -1
- package/src/preact/components/downshift-combobox.tsx +145 -0
- package/src/preact/lineageFilter/LineageFilterChangedEvent.ts +11 -0
- package/src/preact/lineageFilter/fetchLineageAutocompleteList.spec.ts +16 -2
- package/src/preact/lineageFilter/fetchLineageAutocompleteList.ts +13 -2
- package/src/preact/lineageFilter/lineage-filter.stories.tsx +110 -9
- package/src/preact/lineageFilter/lineage-filter.tsx +40 -50
- package/src/preact/locationFilter/LocationChangedEvent.ts +1 -1
- package/src/preact/locationFilter/fetchAutocompletionList.spec.ts +6 -2
- package/src/preact/locationFilter/fetchAutocompletionList.ts +16 -6
- package/src/preact/locationFilter/location-filter.stories.tsx +33 -30
- package/src/preact/locationFilter/location-filter.tsx +47 -144
- package/src/preact/map/sequences-by-location-map.tsx +3 -3
- package/src/preact/textInput/TextInputChangedEvent.ts +11 -0
- package/src/preact/textInput/fetchStringAutocompleteList.ts +20 -0
- package/src/preact/textInput/text-input.stories.tsx +34 -14
- package/src/preact/textInput/text-input.tsx +47 -45
- package/src/types.ts +3 -0
- package/src/utilEntrypoint.ts +2 -0
- package/src/web-components/input/gs-lineage-filter.stories.ts +120 -31
- package/src/web-components/input/gs-lineage-filter.tsx +24 -8
- package/src/web-components/input/gs-location-filter.stories.ts +9 -0
- package/src/web-components/input/gs-location-filter.tsx +21 -3
- package/src/web-components/input/gs-text-input.stories.ts +44 -12
- package/src/web-components/input/gs-text-input.tsx +23 -7
- package/standalone-bundle/dashboard-components.js +4931 -4863
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/LocationChangedEvent-CORvQvXv.js.map +0 -1
- 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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 "./
|
|
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
|
@@ -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(
|
|
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(
|
|
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(
|
|
4
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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');
|