@genspectrum/dashboard-components 0.16.4 → 0.17.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 +130 -74
- package/dist/{LineageFilterChangedEvent-COWV-Y0k.js → LineageFilterChangedEvent-DkvWdq_G.js} +2 -2
- package/dist/LineageFilterChangedEvent-DkvWdq_G.js.map +1 -0
- package/dist/components.d.ts +64 -48
- package/dist/components.js +858 -242
- package/dist/components.js.map +1 -1
- package/dist/style.css +391 -12
- package/dist/util.d.ts +23 -25
- package/dist/util.js +1 -1
- package/package.json +2 -1
- package/src/preact/components/clearable-select.stories.tsx +75 -0
- package/src/preact/components/clearable-select.tsx +76 -0
- package/src/preact/components/downshift-combobox.tsx +9 -7
- package/src/preact/dateRangeFilter/computeInitialValues.spec.ts +31 -33
- package/src/preact/dateRangeFilter/computeInitialValues.ts +2 -15
- package/src/preact/dateRangeFilter/date-picker.tsx +66 -0
- package/src/preact/dateRangeFilter/date-range-filter.stories.tsx +69 -31
- package/src/preact/dateRangeFilter/date-range-filter.tsx +136 -139
- package/src/preact/dateRangeFilter/dateRangeOption.ts +11 -11
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +133 -84
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +46 -16
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +3 -0
- package/src/preact/shared/WithClassName/WithClassName.ts +1 -0
- package/src/preact/shared/icons/DeleteIcon.tsx +3 -0
- package/src/preact/shared/stories/expectOptionSelected.tsx +7 -0
- package/src/preact/shared/tanstackTable/pagination.tsx +132 -0
- package/src/preact/shared/tanstackTable/tanstackTable.tsx +43 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +2 -1
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +3 -5
- package/src/utilEntrypoint.ts +1 -1
- package/src/web-components/MutationAnnotations.mdx +33 -0
- package/src/web-components/ResizeContainer.mdx +1 -1
- package/src/web-components/errorHandling.mdx +1 -1
- package/src/web-components/gs-app.ts +2 -2
- package/src/web-components/input/gs-date-range-filter.stories.ts +38 -32
- package/src/web-components/input/gs-date-range-filter.tsx +8 -2
- package/src/web-components/input/gs-lineage-filter.tsx +1 -1
- package/src/web-components/input/gs-location-filter.tsx +1 -1
- package/src/web-components/input/gs-mutation-filter.tsx +1 -1
- package/src/web-components/input/gs-text-filter.tsx +1 -1
- package/src/web-components/visualization/gs-aggregate.tsx +2 -2
- package/src/web-components/visualization/gs-mutation-comparison.tsx +5 -2
- package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +39 -0
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +4 -0
- package/src/web-components/visualization/gs-mutations-over-time.tsx +13 -33
- package/src/web-components/visualization/gs-mutations.tsx +5 -2
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
- package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
- package/src/web-components/visualization/gs-statistics.tsx +2 -2
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.spec-d.ts +24 -0
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +3 -3
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +7 -38
- package/standalone-bundle/dashboard-components.js +18384 -16486
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/LineageFilterChangedEvent-COWV-Y0k.js.map +0 -1
package/dist/util.d.ts
CHANGED
|
@@ -73,8 +73,8 @@ declare const confidenceIntervalMethodSchema: default_2.ZodUnion<[default_2.ZodL
|
|
|
73
73
|
|
|
74
74
|
export declare type DateRangeOption = default_2.infer<typeof dateRangeOptionSchema>;
|
|
75
75
|
|
|
76
|
-
export declare class DateRangeOptionChangedEvent extends CustomEvent<
|
|
77
|
-
constructor(detail:
|
|
76
|
+
export declare class DateRangeOptionChangedEvent extends CustomEvent<DateRangeValue> {
|
|
77
|
+
constructor(detail: DateRangeValue);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
@@ -136,11 +136,9 @@ declare const dateRangeOptionSchema: default_2.ZodObject<{
|
|
|
136
136
|
dateTo?: string | undefined;
|
|
137
137
|
}>;
|
|
138
138
|
|
|
139
|
-
export declare type
|
|
139
|
+
export declare type DateRangeValue = default_2.infer<typeof dateRangeValueSchema>;
|
|
140
140
|
|
|
141
|
-
declare
|
|
142
|
-
|
|
143
|
-
declare const dateRangeValueSchema: default_2.ZodUnion<[default_2.ZodString, default_2.ZodObject<{
|
|
141
|
+
declare const dateRangeValueSchema: default_2.ZodOptional<default_2.ZodUnion<[default_2.ZodString, default_2.ZodObject<{
|
|
144
142
|
dateFrom: default_2.ZodOptional<default_2.ZodString>;
|
|
145
143
|
dateTo: default_2.ZodOptional<default_2.ZodString>;
|
|
146
144
|
}, "strip", default_2.ZodTypeAny, {
|
|
@@ -149,7 +147,7 @@ declare const dateRangeValueSchema: default_2.ZodUnion<[default_2.ZodString, def
|
|
|
149
147
|
}, {
|
|
150
148
|
dateFrom?: string | undefined;
|
|
151
149
|
dateTo?: string | undefined;
|
|
152
|
-
}>]
|
|
150
|
+
}>]>>;
|
|
153
151
|
|
|
154
152
|
export declare type LapisFilter = default_2.infer<typeof lapisFilterSchema>;
|
|
155
153
|
|
|
@@ -972,7 +970,7 @@ declare global {
|
|
|
972
970
|
|
|
973
971
|
declare global {
|
|
974
972
|
interface HTMLElementTagNameMap {
|
|
975
|
-
'gs-
|
|
973
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
976
974
|
}
|
|
977
975
|
}
|
|
978
976
|
|
|
@@ -980,7 +978,7 @@ declare global {
|
|
|
980
978
|
declare global {
|
|
981
979
|
namespace JSX {
|
|
982
980
|
interface IntrinsicElements {
|
|
983
|
-
'gs-
|
|
981
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
984
982
|
}
|
|
985
983
|
}
|
|
986
984
|
}
|
|
@@ -988,7 +986,7 @@ declare global {
|
|
|
988
986
|
|
|
989
987
|
declare global {
|
|
990
988
|
interface HTMLElementTagNameMap {
|
|
991
|
-
'gs-
|
|
989
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
992
990
|
}
|
|
993
991
|
}
|
|
994
992
|
|
|
@@ -996,7 +994,7 @@ declare global {
|
|
|
996
994
|
declare global {
|
|
997
995
|
namespace JSX {
|
|
998
996
|
interface IntrinsicElements {
|
|
999
|
-
'gs-
|
|
997
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1000
998
|
}
|
|
1001
999
|
}
|
|
1002
1000
|
}
|
|
@@ -1036,11 +1034,7 @@ declare global {
|
|
|
1036
1034
|
|
|
1037
1035
|
declare global {
|
|
1038
1036
|
interface HTMLElementTagNameMap {
|
|
1039
|
-
'gs-
|
|
1040
|
-
}
|
|
1041
|
-
interface HTMLElementEventMap {
|
|
1042
|
-
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
1043
|
-
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1037
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
1044
1038
|
}
|
|
1045
1039
|
}
|
|
1046
1040
|
|
|
@@ -1048,7 +1042,7 @@ declare global {
|
|
|
1048
1042
|
declare global {
|
|
1049
1043
|
namespace JSX {
|
|
1050
1044
|
interface IntrinsicElements {
|
|
1051
|
-
'gs-
|
|
1045
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1052
1046
|
}
|
|
1053
1047
|
}
|
|
1054
1048
|
}
|
|
@@ -1056,7 +1050,11 @@ declare global {
|
|
|
1056
1050
|
|
|
1057
1051
|
declare global {
|
|
1058
1052
|
interface HTMLElementTagNameMap {
|
|
1059
|
-
'gs-
|
|
1053
|
+
'gs-date-range-filter': DateRangeFilterComponent;
|
|
1054
|
+
}
|
|
1055
|
+
interface HTMLElementEventMap {
|
|
1056
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
1057
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1060
1058
|
}
|
|
1061
1059
|
}
|
|
1062
1060
|
|
|
@@ -1064,7 +1062,7 @@ declare global {
|
|
|
1064
1062
|
declare global {
|
|
1065
1063
|
namespace JSX {
|
|
1066
1064
|
interface IntrinsicElements {
|
|
1067
|
-
'gs-
|
|
1065
|
+
'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1068
1066
|
}
|
|
1069
1067
|
}
|
|
1070
1068
|
}
|
|
@@ -1110,10 +1108,10 @@ declare global {
|
|
|
1110
1108
|
|
|
1111
1109
|
declare global {
|
|
1112
1110
|
interface HTMLElementTagNameMap {
|
|
1113
|
-
'gs-
|
|
1111
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1114
1112
|
}
|
|
1115
1113
|
interface HTMLElementEventMap {
|
|
1116
|
-
'gs-
|
|
1114
|
+
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
1117
1115
|
}
|
|
1118
1116
|
}
|
|
1119
1117
|
|
|
@@ -1121,7 +1119,7 @@ declare global {
|
|
|
1121
1119
|
declare global {
|
|
1122
1120
|
namespace JSX {
|
|
1123
1121
|
interface IntrinsicElements {
|
|
1124
|
-
'gs-
|
|
1122
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1125
1123
|
}
|
|
1126
1124
|
}
|
|
1127
1125
|
}
|
|
@@ -1129,10 +1127,10 @@ declare global {
|
|
|
1129
1127
|
|
|
1130
1128
|
declare global {
|
|
1131
1129
|
interface HTMLElementTagNameMap {
|
|
1132
|
-
'gs-
|
|
1130
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1133
1131
|
}
|
|
1134
1132
|
interface HTMLElementEventMap {
|
|
1135
|
-
'gs-
|
|
1133
|
+
'gs-lineage-filter-changed': LineageFilterChangedEvent;
|
|
1136
1134
|
}
|
|
1137
1135
|
}
|
|
1138
1136
|
|
|
@@ -1140,7 +1138,7 @@ declare global {
|
|
|
1140
1138
|
declare global {
|
|
1141
1139
|
namespace JSX {
|
|
1142
1140
|
interface IntrinsicElements {
|
|
1143
|
-
'gs-
|
|
1141
|
+
'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1144
1142
|
}
|
|
1145
1143
|
}
|
|
1146
1144
|
}
|
package/dist/util.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "GenSpectrum web components for building dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"@lit/context": "^1.1.1",
|
|
84
84
|
"@lit/reactive-element": "^2.0.4",
|
|
85
85
|
"@lit/task": "^1.0.0",
|
|
86
|
+
"@tanstack/table-core": "^8.21.2",
|
|
86
87
|
"chart.js": "^4.4.6",
|
|
87
88
|
"chartjs-chart-error-bars": "^4.4.0",
|
|
88
89
|
"chartjs-chart-venn": "^4.3.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { fn, userEvent, within } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import { ClearableSelect, type ClearableSelectProps } from './clearable-select';
|
|
5
|
+
import { expectOptionSelected } from '../shared/stories/expectOptionSelected';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<ClearableSelectProps> = {
|
|
8
|
+
title: 'Component/ClearableSelect',
|
|
9
|
+
component: ClearableSelect,
|
|
10
|
+
parameters: { fetchMock: {} },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
export const Default: StoryObj<ClearableSelectProps> = {
|
|
16
|
+
render: (args) => (
|
|
17
|
+
<div class='flex justify-center px-4 py-16'>
|
|
18
|
+
<ClearableSelect {...args} />
|
|
19
|
+
</div>
|
|
20
|
+
),
|
|
21
|
+
args: {
|
|
22
|
+
items: ['firstOption', 'secondOption'],
|
|
23
|
+
onChange: fn(),
|
|
24
|
+
},
|
|
25
|
+
play: async ({ canvasElement, step }) => {
|
|
26
|
+
await step('Show default placeholder', async () => {
|
|
27
|
+
await expectOptionSelected(canvasElement, 'Select an option');
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const UseInitialSelectedItem: StoryObj<ClearableSelectProps> = {
|
|
33
|
+
...Default,
|
|
34
|
+
args: {
|
|
35
|
+
...Default.args,
|
|
36
|
+
initiallySelectedItem: 'firstOption',
|
|
37
|
+
},
|
|
38
|
+
play: async ({ canvasElement, step }) => {
|
|
39
|
+
await step('Show initiallySelectedItem', async () => {
|
|
40
|
+
await expectOptionSelected(canvasElement, 'firstOption');
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const SwitchToOption: StoryObj<ClearableSelectProps> = {
|
|
46
|
+
...Default,
|
|
47
|
+
play: async ({ canvasElement, step }) => {
|
|
48
|
+
const canvas = within(canvasElement);
|
|
49
|
+
|
|
50
|
+
await step('Select an option', async () => {
|
|
51
|
+
await userEvent.selectOptions(getSelectElement(canvas), 'firstOption');
|
|
52
|
+
await expectOptionSelected(canvasElement, 'firstOption');
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const ClearOption: StoryObj<ClearableSelectProps> = {
|
|
58
|
+
...Default,
|
|
59
|
+
args: {
|
|
60
|
+
...Default.args,
|
|
61
|
+
initiallySelectedItem: 'firstOption',
|
|
62
|
+
},
|
|
63
|
+
play: async ({ canvasElement, step }) => {
|
|
64
|
+
const canvas = within(canvasElement);
|
|
65
|
+
|
|
66
|
+
await step('Clear the selected option', async () => {
|
|
67
|
+
await userEvent.click(canvas.getByRole('button', { name: '×' }));
|
|
68
|
+
await expectOptionSelected(canvasElement, 'Select an option');
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const getSelectElement = (canvas: ReturnType<typeof within>) => {
|
|
74
|
+
return canvas.getByRole('combobox');
|
|
75
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type ChangeEvent } from 'preact/compat';
|
|
2
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
3
|
+
|
|
4
|
+
import { type WithClassName } from '../shared/WithClassName/WithClassName';
|
|
5
|
+
import { DeleteIcon } from '../shared/icons/DeleteIcon';
|
|
6
|
+
|
|
7
|
+
export const undefinedValue = '__undefined__';
|
|
8
|
+
|
|
9
|
+
export type ClearableSelectProps = {
|
|
10
|
+
items: string[];
|
|
11
|
+
initiallySelectedItem?: string | null;
|
|
12
|
+
onChange?: (item: string | null) => void;
|
|
13
|
+
placeholderText?: string;
|
|
14
|
+
value?: string | null;
|
|
15
|
+
selectClassName?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function ClearableSelect({
|
|
19
|
+
items,
|
|
20
|
+
initiallySelectedItem,
|
|
21
|
+
onChange,
|
|
22
|
+
placeholderText,
|
|
23
|
+
className,
|
|
24
|
+
value,
|
|
25
|
+
selectClassName,
|
|
26
|
+
}: WithClassName<ClearableSelectProps>) {
|
|
27
|
+
const [selectedOption, setSelectedOption] = useState<string | null>(initiallySelectedItem ?? null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (value !== undefined) {
|
|
31
|
+
setSelectedOption(value);
|
|
32
|
+
}
|
|
33
|
+
}, [value]);
|
|
34
|
+
|
|
35
|
+
const handleClear = () => {
|
|
36
|
+
setSelectedOption(null);
|
|
37
|
+
if (onChange) {
|
|
38
|
+
onChange(null);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
|
43
|
+
const newValue = event.currentTarget.value;
|
|
44
|
+
setSelectedOption(newValue);
|
|
45
|
+
if (onChange) {
|
|
46
|
+
onChange(newValue);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className={`relative inline min-w-24 ${className}`}>
|
|
52
|
+
<select
|
|
53
|
+
className={`w-full select select-bordered pr-14 ${selectClassName}`}
|
|
54
|
+
value={selectedOption ?? undefinedValue}
|
|
55
|
+
onChange={handleChange}
|
|
56
|
+
>
|
|
57
|
+
<option value={undefinedValue} disabled>
|
|
58
|
+
{placeholderText ?? 'Select an option'}
|
|
59
|
+
</option>
|
|
60
|
+
{items.map((item) => (
|
|
61
|
+
<option key={item} value={item}>
|
|
62
|
+
{item}
|
|
63
|
+
</option>
|
|
64
|
+
))}
|
|
65
|
+
</select>
|
|
66
|
+
{selectedOption && (
|
|
67
|
+
<button
|
|
68
|
+
onClick={handleClear}
|
|
69
|
+
className='absolute right-10 top-1/2 -translate-y-1/2 bg-transparent border-0 cursor-pointer'
|
|
70
|
+
>
|
|
71
|
+
<DeleteIcon />
|
|
72
|
+
</button>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -2,6 +2,8 @@ import { useCombobox } from 'downshift/preact';
|
|
|
2
2
|
import { type ComponentChild } from 'preact';
|
|
3
3
|
import { useMemo, useRef, useState } from 'preact/hooks';
|
|
4
4
|
|
|
5
|
+
import { DeleteIcon } from '../shared/icons/DeleteIcon';
|
|
6
|
+
|
|
5
7
|
export function DownshiftCombobox<Item>({
|
|
6
8
|
allItems,
|
|
7
9
|
value,
|
|
@@ -10,18 +12,18 @@ export function DownshiftCombobox<Item>({
|
|
|
10
12
|
itemToString,
|
|
11
13
|
placeholderText,
|
|
12
14
|
formatItemInList,
|
|
15
|
+
inputClassName = '',
|
|
13
16
|
}: {
|
|
14
17
|
allItems: Item[];
|
|
15
|
-
value?: Item;
|
|
18
|
+
value?: Item | null;
|
|
16
19
|
filterItemsByInputValue: (item: Item, value: string) => boolean;
|
|
17
20
|
createEvent: (item: Item | null) => CustomEvent;
|
|
18
21
|
itemToString: (item: Item | undefined | null) => string;
|
|
19
22
|
placeholderText?: string;
|
|
20
23
|
formatItemInList: (item: Item) => ComponentChild;
|
|
24
|
+
inputClassName?: string;
|
|
21
25
|
}) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const [itemsFilter, setItemsFilter] = useState(itemToString(initialSelectedItem));
|
|
26
|
+
const [itemsFilter, setItemsFilter] = useState(itemToString(value));
|
|
25
27
|
const items = useMemo(
|
|
26
28
|
() => allItems.filter((item) => filterItemsByInputValue(item, itemsFilter)),
|
|
27
29
|
[allItems, filterItemsByInputValue, itemsFilter],
|
|
@@ -65,7 +67,7 @@ export function DownshiftCombobox<Item>({
|
|
|
65
67
|
itemToString(item) {
|
|
66
68
|
return itemToString(item);
|
|
67
69
|
},
|
|
68
|
-
|
|
70
|
+
selectedItem: value,
|
|
69
71
|
environment,
|
|
70
72
|
});
|
|
71
73
|
|
|
@@ -89,7 +91,7 @@ export function DownshiftCombobox<Item>({
|
|
|
89
91
|
<div ref={divRef} className={'relative w-full'}>
|
|
90
92
|
<div className='w-full flex flex-col gap-1'>
|
|
91
93
|
<div
|
|
92
|
-
className=
|
|
94
|
+
className={`flex gap-0.5 input input-bordered min-w-32 ${inputClassName}`}
|
|
93
95
|
onBlur={(event) => {
|
|
94
96
|
if (event.relatedTarget != buttonRef.current) {
|
|
95
97
|
closeMenu();
|
|
@@ -109,7 +111,7 @@ export function DownshiftCombobox<Item>({
|
|
|
109
111
|
onClick={clearInput}
|
|
110
112
|
tabIndex={-1}
|
|
111
113
|
>
|
|
112
|
-
|
|
114
|
+
<DeleteIcon />
|
|
113
115
|
</button>
|
|
114
116
|
<button
|
|
115
117
|
aria-label='toggle menu'
|
|
@@ -18,36 +18,34 @@ const dateRangeOptions = [
|
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
describe('computeInitialValues', () => {
|
|
21
|
+
it('should return undefined for unedfined value', () => {
|
|
22
|
+
const result = computeInitialValues(undefined, earliestDate, dateRangeOptions);
|
|
23
|
+
|
|
24
|
+
expect(result).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
21
27
|
it('should compute initial value if value is dateRangeOption label', () => {
|
|
22
28
|
const result = computeInitialValues(fromToOption, earliestDate, dateRangeOptions);
|
|
23
29
|
|
|
24
|
-
expect(result
|
|
25
|
-
expectDateMatches(result
|
|
26
|
-
expectDateMatches(result
|
|
30
|
+
expect(result?.initialSelectedDateRange).toEqual(fromToOption);
|
|
31
|
+
expectDateMatches(result?.initialSelectedDateFrom, new Date(dateFromOptionValue));
|
|
32
|
+
expectDateMatches(result?.initialSelectedDateTo, new Date(dateToOptionValue));
|
|
27
33
|
});
|
|
28
34
|
|
|
29
35
|
it('should use today as "dateTo" if it is unset in selected option', () => {
|
|
30
36
|
const result = computeInitialValues(fromOption, earliestDate, dateRangeOptions);
|
|
31
37
|
|
|
32
|
-
expect(result
|
|
33
|
-
expectDateMatches(result
|
|
34
|
-
expectDateMatches(result
|
|
38
|
+
expect(result?.initialSelectedDateRange).toEqual(fromOption);
|
|
39
|
+
expectDateMatches(result?.initialSelectedDateFrom, new Date(dateFromOptionValue));
|
|
40
|
+
expectDateMatches(result?.initialSelectedDateTo, today);
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
it('should use earliest date as "dateFrom" if it is unset in selected option', () => {
|
|
38
44
|
const result = computeInitialValues(toOption, earliestDate, dateRangeOptions);
|
|
39
45
|
|
|
40
|
-
expect(result
|
|
41
|
-
expectDateMatches(result
|
|
42
|
-
expectDateMatches(result
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should fall back to full range if initial value is not set', () => {
|
|
46
|
-
const result = computeInitialValues(undefined, earliestDate, dateRangeOptions);
|
|
47
|
-
|
|
48
|
-
expect(result.initialSelectedDateRange).toBeUndefined();
|
|
49
|
-
expectDateMatches(result.initialSelectedDateFrom, new Date(earliestDate));
|
|
50
|
-
expectDateMatches(result.initialSelectedDateTo, today);
|
|
46
|
+
expect(result?.initialSelectedDateRange).toEqual(toOption);
|
|
47
|
+
expectDateMatches(result?.initialSelectedDateFrom, new Date(earliestDate));
|
|
48
|
+
expectDateMatches(result?.initialSelectedDateTo, new Date(dateToOptionValue));
|
|
51
49
|
});
|
|
52
50
|
|
|
53
51
|
it('should throw when initial value is unknown', () => {
|
|
@@ -66,18 +64,18 @@ describe('computeInitialValues', () => {
|
|
|
66
64
|
const initialDateFrom = '2020-01-01';
|
|
67
65
|
const result = computeInitialValues({ dateFrom: initialDateFrom }, earliestDate, dateRangeOptions);
|
|
68
66
|
|
|
69
|
-
expect(result
|
|
70
|
-
expectDateMatches(result
|
|
71
|
-
expectDateMatches(result
|
|
67
|
+
expect(result?.initialSelectedDateRange).toBeUndefined();
|
|
68
|
+
expectDateMatches(result?.initialSelectedDateFrom, new Date(initialDateFrom));
|
|
69
|
+
expectDateMatches(result?.initialSelectedDateTo, today);
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
it('should select from earliest date until date if only dateTo is given', () => {
|
|
75
73
|
const initialDateTo = '2020-01-01';
|
|
76
74
|
const result = computeInitialValues({ dateTo: initialDateTo }, earliestDate, dateRangeOptions);
|
|
77
75
|
|
|
78
|
-
expect(result
|
|
79
|
-
expectDateMatches(result
|
|
80
|
-
expectDateMatches(result
|
|
76
|
+
expect(result?.initialSelectedDateRange).toBeUndefined();
|
|
77
|
+
expectDateMatches(result?.initialSelectedDateFrom, new Date(earliestDate));
|
|
78
|
+
expectDateMatches(result?.initialSelectedDateTo, new Date(initialDateTo));
|
|
81
79
|
});
|
|
82
80
|
|
|
83
81
|
it('should select date range is dateFrom and dateTo are given', () => {
|
|
@@ -92,9 +90,9 @@ describe('computeInitialValues', () => {
|
|
|
92
90
|
dateRangeOptions,
|
|
93
91
|
);
|
|
94
92
|
|
|
95
|
-
expect(result
|
|
96
|
-
expectDateMatches(result
|
|
97
|
-
expectDateMatches(result
|
|
93
|
+
expect(result?.initialSelectedDateRange).toBeUndefined();
|
|
94
|
+
expectDateMatches(result?.initialSelectedDateFrom, new Date(initialDateFrom));
|
|
95
|
+
expectDateMatches(result?.initialSelectedDateTo, new Date(initialDateTo));
|
|
98
96
|
});
|
|
99
97
|
|
|
100
98
|
it('should set initial "to" to "from" if "from" is after "to"', () => {
|
|
@@ -109,9 +107,9 @@ describe('computeInitialValues', () => {
|
|
|
109
107
|
dateRangeOptions,
|
|
110
108
|
);
|
|
111
109
|
|
|
112
|
-
expect(result
|
|
113
|
-
expectDateMatches(result
|
|
114
|
-
expectDateMatches(result
|
|
110
|
+
expect(result?.initialSelectedDateRange).toBeUndefined();
|
|
111
|
+
expectDateMatches(result?.initialSelectedDateFrom, new Date(initialDateFrom));
|
|
112
|
+
expectDateMatches(result?.initialSelectedDateTo, new Date(initialDateFrom));
|
|
115
113
|
});
|
|
116
114
|
|
|
117
115
|
it('should throw if initial "from" is not a valid date', () => {
|
|
@@ -126,9 +124,9 @@ describe('computeInitialValues', () => {
|
|
|
126
124
|
);
|
|
127
125
|
});
|
|
128
126
|
|
|
129
|
-
function expectDateMatches(actual: Date, expected: Date) {
|
|
130
|
-
expect(actual
|
|
131
|
-
expect(actual
|
|
132
|
-
expect(actual
|
|
127
|
+
function expectDateMatches(actual: Date | undefined, expected: Date | undefined) {
|
|
128
|
+
expect(actual?.getFullYear()).toEqual(expected?.getFullYear());
|
|
129
|
+
expect(actual?.getMonth()).toEqual(expected?.getMonth());
|
|
130
|
+
expect(actual?.getDate()).toEqual(expected?.getDate());
|
|
133
131
|
}
|
|
134
132
|
});
|
|
@@ -2,22 +2,9 @@ import { type DateRangeOption, type DateRangeValue } from './dateRangeOption';
|
|
|
2
2
|
import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
|
|
3
3
|
import { UserFacingError } from '../components/error-display';
|
|
4
4
|
|
|
5
|
-
export function computeInitialValues(
|
|
6
|
-
value: DateRangeValue | undefined,
|
|
7
|
-
earliestDate: string,
|
|
8
|
-
dateRangeOptions: DateRangeOption[],
|
|
9
|
-
): {
|
|
10
|
-
initialSelectedDateRange: string | undefined;
|
|
11
|
-
initialSelectedDateFrom: Date;
|
|
12
|
-
initialSelectedDateTo: Date;
|
|
13
|
-
} {
|
|
5
|
+
export function computeInitialValues(value: DateRangeValue, earliestDate: string, dateRangeOptions: DateRangeOption[]) {
|
|
14
6
|
if (value === undefined) {
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
initialSelectedDateRange: undefined,
|
|
18
|
-
initialSelectedDateFrom: dateFrom,
|
|
19
|
-
initialSelectedDateTo: dateTo,
|
|
20
|
-
};
|
|
7
|
+
return undefined;
|
|
21
8
|
}
|
|
22
9
|
|
|
23
10
|
if (typeof value === 'string') {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import 'flatpickr/dist/flatpickr.min.css';
|
|
2
|
+
import flatpickr from 'flatpickr';
|
|
3
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
4
|
+
|
|
5
|
+
import { type WithClassName } from '../shared/WithClassName/WithClassName';
|
|
6
|
+
|
|
7
|
+
export function DatePicker({
|
|
8
|
+
onChange,
|
|
9
|
+
value,
|
|
10
|
+
minDate,
|
|
11
|
+
maxDate,
|
|
12
|
+
placeholderText,
|
|
13
|
+
className,
|
|
14
|
+
}: WithClassName<{
|
|
15
|
+
onChange?: (date: Date | undefined) => void;
|
|
16
|
+
value?: Date;
|
|
17
|
+
minDate?: Date;
|
|
18
|
+
maxDate?: Date;
|
|
19
|
+
placeholderText?: string;
|
|
20
|
+
}>) {
|
|
21
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
22
|
+
|
|
23
|
+
const [datePicker, setDatePicker] = useState<flatpickr.Instance | null>(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!inputRef.current) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const instance = flatpickr(inputRef.current, {
|
|
31
|
+
allowInput: true,
|
|
32
|
+
dateFormat: 'Y-m-d',
|
|
33
|
+
defaultDate: value,
|
|
34
|
+
minDate,
|
|
35
|
+
maxDate,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
setDatePicker(instance);
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
instance.destroy();
|
|
42
|
+
};
|
|
43
|
+
}, [maxDate, minDate, onChange, value]);
|
|
44
|
+
|
|
45
|
+
if (value === undefined && inputRef.current) {
|
|
46
|
+
inputRef.current.value = '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleChange = () => {
|
|
50
|
+
const newValue = datePicker?.selectedDates[0];
|
|
51
|
+
if (onChange) {
|
|
52
|
+
onChange(newValue);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<input
|
|
58
|
+
className={`input input-bordered w-full ${className}`}
|
|
59
|
+
type='text'
|
|
60
|
+
placeholder={placeholderText}
|
|
61
|
+
ref={inputRef}
|
|
62
|
+
onChange={handleChange}
|
|
63
|
+
onBlur={handleChange}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|