@genspectrum/dashboard-components 0.12.0 → 0.12.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.
- package/custom-elements.json +4 -4
- package/dist/components.d.ts +44 -44
- package/dist/components.js +155 -43
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +44 -44
- package/package.json +1 -1
- package/src/preact/map/sequences-by-location-map.tsx +3 -3
- package/src/preact/textInput/TextInputChangedEvent.ts +11 -0
- package/src/preact/textInput/fetchAutocompleteList.ts +1 -1
- package/src/preact/textInput/text-input.stories.tsx +20 -3
- package/src/preact/textInput/text-input.tsx +139 -36
- package/src/web-components/input/gs-text-input.stories.ts +30 -7
- package/standalone-bundle/dashboard-components.js +2366 -2267
- package/standalone-bundle/dashboard-components.js.map +1 -1
package/dist/util.d.ts
CHANGED
|
@@ -824,10 +824,7 @@ declare global {
|
|
|
824
824
|
|
|
825
825
|
declare global {
|
|
826
826
|
interface HTMLElementTagNameMap {
|
|
827
|
-
'gs-
|
|
828
|
-
}
|
|
829
|
-
interface HTMLElementEventMap {
|
|
830
|
-
'gs-location-changed': LocationChangedEvent;
|
|
827
|
+
'gs-mutation-comparison-component': MutationComparisonComponent;
|
|
831
828
|
}
|
|
832
829
|
}
|
|
833
830
|
|
|
@@ -835,7 +832,7 @@ declare global {
|
|
|
835
832
|
declare global {
|
|
836
833
|
namespace JSX {
|
|
837
834
|
interface IntrinsicElements {
|
|
838
|
-
'gs-
|
|
835
|
+
'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
839
836
|
}
|
|
840
837
|
}
|
|
841
838
|
}
|
|
@@ -843,11 +840,7 @@ declare global {
|
|
|
843
840
|
|
|
844
841
|
declare global {
|
|
845
842
|
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;
|
|
843
|
+
'gs-mutations-component': MutationsComponent;
|
|
851
844
|
}
|
|
852
845
|
}
|
|
853
846
|
|
|
@@ -855,7 +848,7 @@ declare global {
|
|
|
855
848
|
declare global {
|
|
856
849
|
namespace JSX {
|
|
857
850
|
interface IntrinsicElements {
|
|
858
|
-
'gs-
|
|
851
|
+
'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
859
852
|
}
|
|
860
853
|
}
|
|
861
854
|
}
|
|
@@ -863,10 +856,7 @@ declare global {
|
|
|
863
856
|
|
|
864
857
|
declare global {
|
|
865
858
|
interface HTMLElementTagNameMap {
|
|
866
|
-
'gs-
|
|
867
|
-
}
|
|
868
|
-
interface HTMLElementEventMap {
|
|
869
|
-
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
859
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
870
860
|
}
|
|
871
861
|
}
|
|
872
862
|
|
|
@@ -874,7 +864,7 @@ declare global {
|
|
|
874
864
|
declare global {
|
|
875
865
|
namespace JSX {
|
|
876
866
|
interface IntrinsicElements {
|
|
877
|
-
'gs-
|
|
867
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
878
868
|
}
|
|
879
869
|
}
|
|
880
870
|
}
|
|
@@ -882,10 +872,7 @@ declare global {
|
|
|
882
872
|
|
|
883
873
|
declare global {
|
|
884
874
|
interface HTMLElementTagNameMap {
|
|
885
|
-
'gs-
|
|
886
|
-
}
|
|
887
|
-
interface HTMLElementEventMap {
|
|
888
|
-
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
875
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
889
876
|
}
|
|
890
877
|
}
|
|
891
878
|
|
|
@@ -893,7 +880,7 @@ declare global {
|
|
|
893
880
|
declare global {
|
|
894
881
|
namespace JSX {
|
|
895
882
|
interface IntrinsicElements {
|
|
896
|
-
'gs-
|
|
883
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
897
884
|
}
|
|
898
885
|
}
|
|
899
886
|
}
|
|
@@ -901,10 +888,7 @@ declare global {
|
|
|
901
888
|
|
|
902
889
|
declare global {
|
|
903
890
|
interface HTMLElementTagNameMap {
|
|
904
|
-
'gs-
|
|
905
|
-
}
|
|
906
|
-
interface HTMLElementEventMap {
|
|
907
|
-
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
891
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
908
892
|
}
|
|
909
893
|
}
|
|
910
894
|
|
|
@@ -912,7 +896,7 @@ declare global {
|
|
|
912
896
|
declare global {
|
|
913
897
|
namespace JSX {
|
|
914
898
|
interface IntrinsicElements {
|
|
915
|
-
'gs-
|
|
899
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
916
900
|
}
|
|
917
901
|
}
|
|
918
902
|
}
|
|
@@ -920,7 +904,7 @@ declare global {
|
|
|
920
904
|
|
|
921
905
|
declare global {
|
|
922
906
|
interface HTMLElementTagNameMap {
|
|
923
|
-
'gs-
|
|
907
|
+
'gs-aggregate': AggregateComponent;
|
|
924
908
|
}
|
|
925
909
|
}
|
|
926
910
|
|
|
@@ -928,7 +912,7 @@ declare global {
|
|
|
928
912
|
declare global {
|
|
929
913
|
namespace JSX {
|
|
930
914
|
interface IntrinsicElements {
|
|
931
|
-
'gs-
|
|
915
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
932
916
|
}
|
|
933
917
|
}
|
|
934
918
|
}
|
|
@@ -936,7 +920,7 @@ declare global {
|
|
|
936
920
|
|
|
937
921
|
declare global {
|
|
938
922
|
interface HTMLElementTagNameMap {
|
|
939
|
-
'gs-mutations-
|
|
923
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
940
924
|
}
|
|
941
925
|
}
|
|
942
926
|
|
|
@@ -944,7 +928,7 @@ declare global {
|
|
|
944
928
|
declare global {
|
|
945
929
|
namespace JSX {
|
|
946
930
|
interface IntrinsicElements {
|
|
947
|
-
'gs-mutations-
|
|
931
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
948
932
|
}
|
|
949
933
|
}
|
|
950
934
|
}
|
|
@@ -952,7 +936,7 @@ declare global {
|
|
|
952
936
|
|
|
953
937
|
declare global {
|
|
954
938
|
interface HTMLElementTagNameMap {
|
|
955
|
-
'gs-
|
|
939
|
+
'gs-sequences-by-location': SequencesByLocationComponent;
|
|
956
940
|
}
|
|
957
941
|
}
|
|
958
942
|
|
|
@@ -960,7 +944,7 @@ declare global {
|
|
|
960
944
|
declare global {
|
|
961
945
|
namespace JSX {
|
|
962
946
|
interface IntrinsicElements {
|
|
963
|
-
'gs-
|
|
947
|
+
'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
964
948
|
}
|
|
965
949
|
}
|
|
966
950
|
}
|
|
@@ -968,7 +952,7 @@ declare global {
|
|
|
968
952
|
|
|
969
953
|
declare global {
|
|
970
954
|
interface HTMLElementTagNameMap {
|
|
971
|
-
'gs-
|
|
955
|
+
'gs-statistics': StatisticsComponent;
|
|
972
956
|
}
|
|
973
957
|
}
|
|
974
958
|
|
|
@@ -976,7 +960,7 @@ declare global {
|
|
|
976
960
|
declare global {
|
|
977
961
|
namespace JSX {
|
|
978
962
|
interface IntrinsicElements {
|
|
979
|
-
'gs-
|
|
963
|
+
'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
980
964
|
}
|
|
981
965
|
}
|
|
982
966
|
}
|
|
@@ -984,7 +968,11 @@ declare global {
|
|
|
984
968
|
|
|
985
969
|
declare global {
|
|
986
970
|
interface HTMLElementTagNameMap {
|
|
987
|
-
'gs-
|
|
971
|
+
'gs-date-range-selector': DateRangeSelectorComponent;
|
|
972
|
+
}
|
|
973
|
+
interface HTMLElementEventMap {
|
|
974
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
975
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
988
976
|
}
|
|
989
977
|
}
|
|
990
978
|
|
|
@@ -992,7 +980,7 @@ declare global {
|
|
|
992
980
|
declare global {
|
|
993
981
|
namespace JSX {
|
|
994
982
|
interface IntrinsicElements {
|
|
995
|
-
'gs-
|
|
983
|
+
'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
996
984
|
}
|
|
997
985
|
}
|
|
998
986
|
}
|
|
@@ -1000,7 +988,10 @@ declare global {
|
|
|
1000
988
|
|
|
1001
989
|
declare global {
|
|
1002
990
|
interface HTMLElementTagNameMap {
|
|
1003
|
-
'gs-
|
|
991
|
+
'gs-location-filter': LocationFilterComponent;
|
|
992
|
+
}
|
|
993
|
+
interface HTMLElementEventMap {
|
|
994
|
+
'gs-location-changed': LocationChangedEvent;
|
|
1004
995
|
}
|
|
1005
996
|
}
|
|
1006
997
|
|
|
@@ -1008,7 +999,7 @@ declare global {
|
|
|
1008
999
|
declare global {
|
|
1009
1000
|
namespace JSX {
|
|
1010
1001
|
interface IntrinsicElements {
|
|
1011
|
-
'gs-
|
|
1002
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1012
1003
|
}
|
|
1013
1004
|
}
|
|
1014
1005
|
}
|
|
@@ -1016,7 +1007,10 @@ declare global {
|
|
|
1016
1007
|
|
|
1017
1008
|
declare global {
|
|
1018
1009
|
interface HTMLElementTagNameMap {
|
|
1019
|
-
'gs-
|
|
1010
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1011
|
+
}
|
|
1012
|
+
interface HTMLElementEventMap {
|
|
1013
|
+
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
1020
1014
|
}
|
|
1021
1015
|
}
|
|
1022
1016
|
|
|
@@ -1024,7 +1018,7 @@ declare global {
|
|
|
1024
1018
|
declare global {
|
|
1025
1019
|
namespace JSX {
|
|
1026
1020
|
interface IntrinsicElements {
|
|
1027
|
-
'gs-
|
|
1021
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1028
1022
|
}
|
|
1029
1023
|
}
|
|
1030
1024
|
}
|
|
@@ -1032,7 +1026,10 @@ declare global {
|
|
|
1032
1026
|
|
|
1033
1027
|
declare global {
|
|
1034
1028
|
interface HTMLElementTagNameMap {
|
|
1035
|
-
'gs-
|
|
1029
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1030
|
+
}
|
|
1031
|
+
interface HTMLElementEventMap {
|
|
1032
|
+
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1036
1033
|
}
|
|
1037
1034
|
}
|
|
1038
1035
|
|
|
@@ -1040,7 +1037,7 @@ declare global {
|
|
|
1040
1037
|
declare global {
|
|
1041
1038
|
namespace JSX {
|
|
1042
1039
|
interface IntrinsicElements {
|
|
1043
|
-
'gs-
|
|
1040
|
+
'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1044
1041
|
}
|
|
1045
1042
|
}
|
|
1046
1043
|
}
|
|
@@ -1048,7 +1045,10 @@ declare global {
|
|
|
1048
1045
|
|
|
1049
1046
|
declare global {
|
|
1050
1047
|
interface HTMLElementTagNameMap {
|
|
1051
|
-
'gs-
|
|
1048
|
+
'gs-text-input': TextInputComponent;
|
|
1049
|
+
}
|
|
1050
|
+
interface HTMLElementEventMap {
|
|
1051
|
+
'gs-text-input-changed': CustomEvent<Record<string, string>>;
|
|
1052
1052
|
}
|
|
1053
1053
|
}
|
|
1054
1054
|
|
|
@@ -1056,7 +1056,7 @@ declare global {
|
|
|
1056
1056
|
declare global {
|
|
1057
1057
|
namespace JSX {
|
|
1058
1058
|
interface IntrinsicElements {
|
|
1059
|
-
'gs-
|
|
1059
|
+
'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1060
1060
|
}
|
|
1061
1061
|
}
|
|
1062
1062
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Feature, Geometry, GeometryObject } from 'geojson';
|
|
2
|
-
import
|
|
2
|
+
import { geoJson, type Layer, type LayerGroup, map } from 'leaflet';
|
|
3
3
|
import type { FunctionComponent } from 'preact';
|
|
4
4
|
import { useEffect, useRef } from 'preact/hooks';
|
|
5
5
|
|
|
@@ -42,7 +42,7 @@ export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapPro
|
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const leafletMap =
|
|
45
|
+
const leafletMap = map(ref.current, {
|
|
46
46
|
scrollWheelZoom: enableMapNavigation,
|
|
47
47
|
zoomControl: enableMapNavigation,
|
|
48
48
|
keyboard: enableMapNavigation,
|
|
@@ -52,7 +52,7 @@ export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapPro
|
|
|
52
52
|
center: [offsetY, offsetX],
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
geoJson(locations, {
|
|
56
56
|
style: (feature: Feature<GeometryObject, EnhancedGeoJsonFeatureProperties> | undefined) => ({
|
|
57
57
|
fillColor: getColor(feature?.properties.data?.proportion),
|
|
58
58
|
fillOpacity: 1,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type LapisTextFilter = Record<string, string | null | undefined>;
|
|
2
|
+
|
|
3
|
+
export class TextInputChangedEvent extends CustomEvent<LapisTextFilter> {
|
|
4
|
+
constructor(detail: LapisTextFilter) {
|
|
5
|
+
super('gs-text-input-changed', {
|
|
6
|
+
detail,
|
|
7
|
+
bubbles: true,
|
|
8
|
+
composed: true,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -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';
|
|
@@ -79,19 +79,36 @@ export const Default: StoryObj<TextInputProps> = {
|
|
|
79
79
|
},
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
export const
|
|
82
|
+
export const RemoveInitialValue: StoryObj<TextInputProps> = {
|
|
83
83
|
...Default,
|
|
84
84
|
args: {
|
|
85
85
|
...Default.args,
|
|
86
86
|
initialValue: 'Homo sapiens',
|
|
87
87
|
},
|
|
88
|
-
play: async ({ canvasElement }) => {
|
|
88
|
+
play: async ({ canvasElement, step }) => {
|
|
89
89
|
const canvas = within(canvasElement);
|
|
90
90
|
|
|
91
|
+
const changedListenerMock = fn();
|
|
92
|
+
await step('Setup event listener mock', async () => {
|
|
93
|
+
canvasElement.addEventListener('gs-text-input-changed', changedListenerMock);
|
|
94
|
+
});
|
|
95
|
+
|
|
91
96
|
await waitFor(() => {
|
|
92
97
|
const input = canvas.getByPlaceholderText('Enter a host name', { exact: false });
|
|
93
98
|
expect(input).toHaveValue('Homo sapiens');
|
|
94
99
|
});
|
|
100
|
+
|
|
101
|
+
await step('Remove initial value', async () => {
|
|
102
|
+
await fireEvent.click(canvas.getByRole('button', { name: 'clear selection' }));
|
|
103
|
+
|
|
104
|
+
await expect(changedListenerMock).toHaveBeenCalledWith(
|
|
105
|
+
expect.objectContaining({
|
|
106
|
+
detail: {
|
|
107
|
+
host: undefined,
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
});
|
|
95
112
|
},
|
|
96
113
|
};
|
|
97
114
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { useCombobox } from 'downshift/preact';
|
|
1
2
|
import { type FunctionComponent } from 'preact';
|
|
2
|
-
import { useContext, useRef } from 'preact/hooks';
|
|
3
|
+
import { useContext, useRef, useState } from 'preact/hooks';
|
|
3
4
|
import z from 'zod';
|
|
4
5
|
|
|
5
6
|
import { fetchAutocompleteList } from './fetchAutocompleteList';
|
|
6
7
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
8
|
+
import { TextInputChangedEvent } from './TextInputChangedEvent';
|
|
7
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
8
10
|
import { LoadingDisplay } from '../components/loading-display';
|
|
9
11
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
@@ -36,11 +38,9 @@ export const TextInput: FunctionComponent<TextInputProps> = (props) => {
|
|
|
36
38
|
);
|
|
37
39
|
};
|
|
38
40
|
|
|
39
|
-
const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, placeholderText
|
|
41
|
+
const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ initialValue, lapisField, placeholderText }) => {
|
|
40
42
|
const lapis = useContext(LapisUrlContext);
|
|
41
43
|
|
|
42
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
43
|
-
|
|
44
44
|
const { data, error, isLoading } = useQuery(() => fetchAutocompleteList(lapis, lapisField), [lapisField, lapis]);
|
|
45
45
|
|
|
46
46
|
if (isLoading) {
|
|
@@ -55,43 +55,146 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl
|
|
|
55
55
|
return <NoDataDisplay />;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
58
|
+
return (
|
|
59
|
+
<TextSelector
|
|
60
|
+
lapisField={lapisField}
|
|
61
|
+
initialValue={initialValue}
|
|
62
|
+
placeholderText={placeholderText}
|
|
63
|
+
data={data}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const TextSelector = ({
|
|
69
|
+
lapisField,
|
|
70
|
+
initialValue,
|
|
71
|
+
placeholderText,
|
|
72
|
+
data,
|
|
73
|
+
}: TextInputInnerProps & {
|
|
74
|
+
data: string[];
|
|
75
|
+
}) => {
|
|
76
|
+
const [items, setItems] = useState(data.filter((item) => filterByInputValue(item, initialValue)));
|
|
77
|
+
|
|
78
|
+
const divRef = useRef<HTMLDivElement>(null);
|
|
79
|
+
|
|
80
|
+
const shadowRoot = divRef.current?.shadowRoot ?? undefined;
|
|
81
|
+
|
|
82
|
+
const environment =
|
|
83
|
+
shadowRoot !== undefined
|
|
84
|
+
? {
|
|
85
|
+
addEventListener: window.addEventListener.bind(window),
|
|
86
|
+
removeEventListener: window.removeEventListener.bind(window),
|
|
87
|
+
document: shadowRoot.ownerDocument,
|
|
88
|
+
Node: window.Node,
|
|
89
|
+
}
|
|
90
|
+
: undefined;
|
|
91
|
+
|
|
92
|
+
function filterByInputValue(item: string, inputValue: string | undefined | null) {
|
|
93
|
+
if (inputValue === undefined || inputValue === null || inputValue === '') {
|
|
74
94
|
return true;
|
|
75
95
|
}
|
|
76
|
-
return
|
|
96
|
+
return item?.toLowerCase().includes(inputValue?.toLowerCase() || '');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const {
|
|
100
|
+
isOpen,
|
|
101
|
+
getToggleButtonProps,
|
|
102
|
+
getMenuProps,
|
|
103
|
+
getInputProps,
|
|
104
|
+
highlightedIndex,
|
|
105
|
+
getItemProps,
|
|
106
|
+
selectedItem,
|
|
107
|
+
inputValue,
|
|
108
|
+
selectItem,
|
|
109
|
+
setInputValue,
|
|
110
|
+
closeMenu,
|
|
111
|
+
} = useCombobox({
|
|
112
|
+
onInputValueChange({ inputValue }) {
|
|
113
|
+
setItems(data.filter((item) => filterByInputValue(item, inputValue)));
|
|
114
|
+
},
|
|
115
|
+
onSelectedItemChange({ selectedItem }) {
|
|
116
|
+
if (selectedItem !== null) {
|
|
117
|
+
divRef.current?.dispatchEvent(new TextInputChangedEvent({ [lapisField]: selectedItem }));
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
items,
|
|
121
|
+
itemToString(item) {
|
|
122
|
+
return item ?? '';
|
|
123
|
+
},
|
|
124
|
+
initialSelectedItem: initialValue,
|
|
125
|
+
environment,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const onInputBlur = () => {
|
|
129
|
+
if (inputValue === '') {
|
|
130
|
+
divRef.current?.dispatchEvent(new TextInputChangedEvent({ [lapisField]: undefined }));
|
|
131
|
+
selectItem(null);
|
|
132
|
+
} else if (inputValue !== selectedItem) {
|
|
133
|
+
setInputValue(selectedItem ?? '');
|
|
134
|
+
}
|
|
77
135
|
};
|
|
78
136
|
|
|
137
|
+
const clearInput = () => {
|
|
138
|
+
divRef.current?.dispatchEvent(new TextInputChangedEvent({ [lapisField]: undefined }));
|
|
139
|
+
selectItem(null);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const buttonRef = useRef(null);
|
|
143
|
+
|
|
79
144
|
return (
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
145
|
+
<div ref={divRef} className={'relative w-full'}>
|
|
146
|
+
<div className='w-full flex flex-col gap-1'>
|
|
147
|
+
<div
|
|
148
|
+
className='flex gap-0.5 input input-bordered min-w-32'
|
|
149
|
+
onBlur={(event) => {
|
|
150
|
+
if (event.relatedTarget != buttonRef.current) {
|
|
151
|
+
closeMenu();
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<input
|
|
156
|
+
placeholder={placeholderText}
|
|
157
|
+
className='w-full p-1.5'
|
|
158
|
+
{...getInputProps()}
|
|
159
|
+
onBlur={onInputBlur}
|
|
160
|
+
/>
|
|
161
|
+
<button
|
|
162
|
+
aria-label='clear selection'
|
|
163
|
+
className={`px-2 ${inputValue === '' && 'hidden'}`}
|
|
164
|
+
type='button'
|
|
165
|
+
onClick={clearInput}
|
|
166
|
+
tabIndex={-1}
|
|
167
|
+
>
|
|
168
|
+
×
|
|
169
|
+
</button>
|
|
170
|
+
<button
|
|
171
|
+
aria-label='toggle menu'
|
|
172
|
+
className='px-2'
|
|
173
|
+
type='button'
|
|
174
|
+
{...getToggleButtonProps()}
|
|
175
|
+
ref={buttonRef}
|
|
176
|
+
>
|
|
177
|
+
{isOpen ? <>↑</> : <>↓</>}
|
|
178
|
+
</button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<ul
|
|
182
|
+
className={`absolute bg-white mt-1 shadow-md max-h-80 overflow-scroll z-10 w-full min-w-32 ${
|
|
183
|
+
!(isOpen && items.length > 0) && 'hidden'
|
|
184
|
+
}`}
|
|
185
|
+
{...getMenuProps()}
|
|
186
|
+
>
|
|
187
|
+
{isOpen &&
|
|
188
|
+
items.map((item, index) => (
|
|
189
|
+
<li
|
|
190
|
+
className={`${highlightedIndex === index && 'bg-blue-300'} ${selectedItem !== null} py-2 px-3 shadow-sm flex flex-col`}
|
|
191
|
+
key={item}
|
|
192
|
+
{...getItemProps({ item, index })}
|
|
193
|
+
>
|
|
194
|
+
<span>{item}</span>
|
|
195
|
+
</li>
|
|
196
|
+
))}
|
|
197
|
+
</ul>
|
|
198
|
+
</div>
|
|
96
199
|
);
|
|
97
200
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expect, fn, userEvent, waitFor } from '@storybook/test';
|
|
1
|
+
import { expect, fireEvent, fn, userEvent, waitFor } from '@storybook/test';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
3
3
|
import { html } from 'lit';
|
|
4
4
|
|
|
@@ -97,7 +97,7 @@ export const Default: StoryObj<Required<TextInputProps>> = {
|
|
|
97
97
|
},
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
export const
|
|
100
|
+
export const FiresEvents: StoryObj<Required<TextInputProps>> = {
|
|
101
101
|
...Default,
|
|
102
102
|
play: async ({ canvasElement, step }) => {
|
|
103
103
|
const canvas = await withinShadowRoot(canvasElement, 'gs-text-input');
|
|
@@ -121,22 +121,45 @@ export const FiresEvent: StoryObj<Required<TextInputProps>> = {
|
|
|
121
121
|
|
|
122
122
|
await step('Empty input', async () => {
|
|
123
123
|
await userEvent.type(inputField(), '{backspace>9/}');
|
|
124
|
-
await expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
125
|
-
host: undefined,
|
|
126
|
-
});
|
|
127
124
|
});
|
|
128
125
|
|
|
129
126
|
await step('Enter a valid host name', async () => {
|
|
130
|
-
await userEvent.type(inputField(), 'Homo');
|
|
127
|
+
await userEvent.type(inputField(), 'Homo s');
|
|
128
|
+
|
|
129
|
+
const dropdownOption = await canvas.findByText('Homo sapiens');
|
|
130
|
+
await userEvent.click(dropdownOption);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await step('Verify event is fired with correct detail', async () => {
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(listenerMock).toHaveBeenCalledWith(
|
|
136
|
+
expect.objectContaining({
|
|
137
|
+
detail: {
|
|
138
|
+
host: 'Homo sapiens',
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await step('Remove initial value', async () => {
|
|
146
|
+
await fireEvent.click(canvas.getByRole('button', { name: 'clear selection' }));
|
|
131
147
|
|
|
132
148
|
await expect(listenerMock).toHaveBeenCalledWith(
|
|
133
149
|
expect.objectContaining({
|
|
134
150
|
detail: {
|
|
135
|
-
host:
|
|
151
|
+
host: undefined,
|
|
136
152
|
},
|
|
137
153
|
}),
|
|
138
154
|
);
|
|
139
155
|
});
|
|
156
|
+
|
|
157
|
+
await step('Empty input', async () => {
|
|
158
|
+
inputField().blur();
|
|
159
|
+
await expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
160
|
+
host: undefined,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
140
163
|
},
|
|
141
164
|
args: {
|
|
142
165
|
...Default.args,
|