@genspectrum/dashboard-components 1.8.1 → 1.9.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 +2 -2
- package/dist/assets/{mutationOverTimeWorker-BRPqAM5Z.js.map → mutationOverTimeWorker-dhufsWQ2.js.map} +1 -1
- package/dist/components.d.ts +20 -20
- package/dist/components.js +118 -31
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +20 -20
- package/package.json +1 -1
- package/src/preact/components/downshift-combobox.tsx +2 -1
- package/src/preact/components/portal-tooltip.tsx +129 -0
- package/src/preact/components/tooltip.tsx +32 -16
- package/src/preact/lineageFilter/lineage-filter.stories.tsx +57 -1
- package/src/preact/lineageFilter/lineage-filter.tsx +1 -1
- package/src/preact/locationFilter/location-filter.tsx +3 -1
- package/src/preact/mutationsOverTime/__mockData__/withGaps.ts +0 -54
- package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.tsx +1 -1
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +19 -9
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +13 -4
- package/src/preact/textFilter/text-filter.tsx +3 -1
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +8 -4
- package/src/query/queryMutationsOverTime.ts +28 -11
- package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +6 -6
- package/src/web-components/input/gs-lineage-filter.stories.ts +1 -1
- package/src/web-components/input/gs-text-filter.stories.ts +1 -1
- package/standalone-bundle/assets/{mutationOverTimeWorker-DtFX4Ihx.js.map → mutationOverTimeWorker-CGqPKySO.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +4663 -4594
- package/standalone-bundle/dashboard-components.js.map +1 -1
package/dist/util.d.ts
CHANGED
|
@@ -1005,7 +1005,7 @@ declare global {
|
|
|
1005
1005
|
|
|
1006
1006
|
declare global {
|
|
1007
1007
|
interface HTMLElementTagNameMap {
|
|
1008
|
-
'gs-
|
|
1008
|
+
'gs-aggregate': AggregateComponent;
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
@@ -1013,7 +1013,7 @@ declare global {
|
|
|
1013
1013
|
declare global {
|
|
1014
1014
|
namespace JSX {
|
|
1015
1015
|
interface IntrinsicElements {
|
|
1016
|
-
'gs-
|
|
1016
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1017
1017
|
}
|
|
1018
1018
|
}
|
|
1019
1019
|
}
|
|
@@ -1021,7 +1021,7 @@ declare global {
|
|
|
1021
1021
|
|
|
1022
1022
|
declare global {
|
|
1023
1023
|
interface HTMLElementTagNameMap {
|
|
1024
|
-
'gs-
|
|
1024
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
1025
1025
|
}
|
|
1026
1026
|
}
|
|
1027
1027
|
|
|
@@ -1029,7 +1029,7 @@ declare global {
|
|
|
1029
1029
|
declare global {
|
|
1030
1030
|
namespace JSX {
|
|
1031
1031
|
interface IntrinsicElements {
|
|
1032
|
-
'gs-
|
|
1032
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1033
1033
|
}
|
|
1034
1034
|
}
|
|
1035
1035
|
}
|
|
@@ -1037,7 +1037,7 @@ declare global {
|
|
|
1037
1037
|
|
|
1038
1038
|
declare global {
|
|
1039
1039
|
interface HTMLElementTagNameMap {
|
|
1040
|
-
'gs-
|
|
1040
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
1041
1041
|
}
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
@@ -1045,7 +1045,7 @@ declare global {
|
|
|
1045
1045
|
declare global {
|
|
1046
1046
|
namespace JSX {
|
|
1047
1047
|
interface IntrinsicElements {
|
|
1048
|
-
'gs-
|
|
1048
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1049
1049
|
}
|
|
1050
1050
|
}
|
|
1051
1051
|
}
|
|
@@ -1053,7 +1053,7 @@ declare global {
|
|
|
1053
1053
|
|
|
1054
1054
|
declare global {
|
|
1055
1055
|
interface HTMLElementTagNameMap {
|
|
1056
|
-
'gs-
|
|
1056
|
+
'gs-sequences-by-location': SequencesByLocationComponent;
|
|
1057
1057
|
}
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
@@ -1061,7 +1061,7 @@ declare global {
|
|
|
1061
1061
|
declare global {
|
|
1062
1062
|
namespace JSX {
|
|
1063
1063
|
interface IntrinsicElements {
|
|
1064
|
-
'gs-
|
|
1064
|
+
'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1065
1065
|
}
|
|
1066
1066
|
}
|
|
1067
1067
|
}
|
|
@@ -1069,7 +1069,7 @@ declare global {
|
|
|
1069
1069
|
|
|
1070
1070
|
declare global {
|
|
1071
1071
|
interface HTMLElementTagNameMap {
|
|
1072
|
-
'gs-
|
|
1072
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
1073
1073
|
}
|
|
1074
1074
|
}
|
|
1075
1075
|
|
|
@@ -1077,7 +1077,7 @@ declare global {
|
|
|
1077
1077
|
declare global {
|
|
1078
1078
|
namespace JSX {
|
|
1079
1079
|
interface IntrinsicElements {
|
|
1080
|
-
'gs-
|
|
1080
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1081
1081
|
}
|
|
1082
1082
|
}
|
|
1083
1083
|
}
|
|
@@ -1085,7 +1085,7 @@ declare global {
|
|
|
1085
1085
|
|
|
1086
1086
|
declare global {
|
|
1087
1087
|
interface HTMLElementTagNameMap {
|
|
1088
|
-
'gs-
|
|
1088
|
+
'gs-statistics': StatisticsComponent;
|
|
1089
1089
|
}
|
|
1090
1090
|
}
|
|
1091
1091
|
|
|
@@ -1093,7 +1093,7 @@ declare global {
|
|
|
1093
1093
|
declare global {
|
|
1094
1094
|
namespace JSX {
|
|
1095
1095
|
interface IntrinsicElements {
|
|
1096
|
-
'gs-
|
|
1096
|
+
'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1097
1097
|
}
|
|
1098
1098
|
}
|
|
1099
1099
|
}
|
|
@@ -1101,7 +1101,11 @@ declare global {
|
|
|
1101
1101
|
|
|
1102
1102
|
declare global {
|
|
1103
1103
|
interface HTMLElementTagNameMap {
|
|
1104
|
-
'gs-
|
|
1104
|
+
'gs-date-range-filter': DateRangeFilterComponent;
|
|
1105
|
+
}
|
|
1106
|
+
interface HTMLElementEventMap {
|
|
1107
|
+
[gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
|
|
1108
|
+
[gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
|
|
1105
1109
|
}
|
|
1106
1110
|
}
|
|
1107
1111
|
|
|
@@ -1109,7 +1113,7 @@ declare global {
|
|
|
1109
1113
|
declare global {
|
|
1110
1114
|
namespace JSX {
|
|
1111
1115
|
interface IntrinsicElements {
|
|
1112
|
-
'gs-
|
|
1116
|
+
'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1113
1117
|
}
|
|
1114
1118
|
}
|
|
1115
1119
|
}
|
|
@@ -1117,11 +1121,7 @@ declare global {
|
|
|
1117
1121
|
|
|
1118
1122
|
declare global {
|
|
1119
1123
|
interface HTMLElementTagNameMap {
|
|
1120
|
-
'gs-
|
|
1121
|
-
}
|
|
1122
|
-
interface HTMLElementEventMap {
|
|
1123
|
-
[gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
|
|
1124
|
-
[gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
|
|
1124
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
1125
1125
|
}
|
|
1126
1126
|
}
|
|
1127
1127
|
|
|
@@ -1129,7 +1129,7 @@ declare global {
|
|
|
1129
1129
|
declare global {
|
|
1130
1130
|
namespace JSX {
|
|
1131
1131
|
interface IntrinsicElements {
|
|
1132
|
-
'gs-
|
|
1132
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1133
1133
|
}
|
|
1134
1134
|
}
|
|
1135
1135
|
}
|
package/package.json
CHANGED
|
@@ -64,6 +64,7 @@ export function DownshiftCombobox<Item>({
|
|
|
64
64
|
getItemProps,
|
|
65
65
|
inputValue,
|
|
66
66
|
closeMenu,
|
|
67
|
+
reset,
|
|
67
68
|
} = useCombobox({
|
|
68
69
|
onInputValueChange({ inputValue }) {
|
|
69
70
|
setInputIsInvalid(false);
|
|
@@ -97,7 +98,7 @@ export function DownshiftCombobox<Item>({
|
|
|
97
98
|
};
|
|
98
99
|
|
|
99
100
|
const clearInput = () => {
|
|
100
|
-
|
|
101
|
+
reset();
|
|
101
102
|
};
|
|
102
103
|
|
|
103
104
|
const buttonRef = useRef(null);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
import { type CSSProperties, createPortal } from 'preact/compat';
|
|
3
|
+
import { useState, useRef, useLayoutEffect } from 'preact/hooks';
|
|
4
|
+
import { type JSXInternal } from 'preact/src/jsx';
|
|
5
|
+
|
|
6
|
+
import { type TooltipPosition, TOOLTIP_BASE_STYLES } from './tooltip';
|
|
7
|
+
|
|
8
|
+
export type PortalTooltipProps = {
|
|
9
|
+
content: string | JSXInternal.Element;
|
|
10
|
+
position?: TooltipPosition;
|
|
11
|
+
tooltipStyle?: CSSProperties;
|
|
12
|
+
portalTarget: HTMLElement | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A portal-based tooltip component that renders content in a specified DOM element.
|
|
17
|
+
*
|
|
18
|
+
* Unlike the regular `Tooltip` component, this uses Preact portals to render the tooltip
|
|
19
|
+
* at a specific location in the DOM with fixed positioning. This is useful when:
|
|
20
|
+
* - The tooltip needs to escape overflow constraints from parent containers
|
|
21
|
+
* - You need precise control over the tooltip's rendering location
|
|
22
|
+
* - Parent containers have `overflow: hidden` or other clipping styles
|
|
23
|
+
*
|
|
24
|
+
* **Important:** The `portalTarget` element should still be within the same shadow DOM as the
|
|
25
|
+
* component to ensure proper styling and encapsulation. Typically, this is a container element
|
|
26
|
+
* at the root of your component. Do not use `document.body`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const portalTarget = document.getElementById('tooltip-root');
|
|
31
|
+
*
|
|
32
|
+
* <PortalTooltip
|
|
33
|
+
* content="This is a portal tooltip"
|
|
34
|
+
* position="top"
|
|
35
|
+
* portalTarget={portalTarget}
|
|
36
|
+
* >
|
|
37
|
+
* <button>Hover me</button>
|
|
38
|
+
* </PortalTooltip>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
const PortalTooltip: FunctionComponent<PortalTooltipProps> = ({
|
|
42
|
+
children,
|
|
43
|
+
content,
|
|
44
|
+
position = 'bottom',
|
|
45
|
+
tooltipStyle,
|
|
46
|
+
portalTarget,
|
|
47
|
+
}) => {
|
|
48
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
49
|
+
const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
|
|
50
|
+
const triggerRef = useRef<HTMLDivElement>(null);
|
|
51
|
+
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
52
|
+
|
|
53
|
+
useLayoutEffect(() => {
|
|
54
|
+
if (isHovered && triggerRef.current !== null && tooltipRef.current !== null) {
|
|
55
|
+
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
56
|
+
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
|
57
|
+
const newPosition = calculateTooltipPosition(triggerRect, tooltipRect, position);
|
|
58
|
+
setTooltipPosition(newPosition);
|
|
59
|
+
}
|
|
60
|
+
}, [isHovered, position]);
|
|
61
|
+
|
|
62
|
+
const tooltipContent = (
|
|
63
|
+
<div
|
|
64
|
+
ref={tooltipRef}
|
|
65
|
+
className={`fixed ${TOOLTIP_BASE_STYLES} ${isHovered ? 'visible' : 'invisible'}`}
|
|
66
|
+
style={Object.assign({}, tooltipStyle, { top: tooltipPosition.top, left: tooltipPosition.left })}
|
|
67
|
+
>
|
|
68
|
+
{content}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<div ref={triggerRef} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
|
|
75
|
+
{children}
|
|
76
|
+
</div>
|
|
77
|
+
{portalTarget !== null && createPortal(tooltipContent, portalTarget)}
|
|
78
|
+
</>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default PortalTooltip;
|
|
83
|
+
|
|
84
|
+
function calculateTooltipPosition(
|
|
85
|
+
triggerRect: DOMRect,
|
|
86
|
+
tooltipRect: DOMRect,
|
|
87
|
+
position: TooltipPosition,
|
|
88
|
+
): { top: number; left: number } {
|
|
89
|
+
const gap = 4;
|
|
90
|
+
let top;
|
|
91
|
+
let left;
|
|
92
|
+
|
|
93
|
+
switch (position) {
|
|
94
|
+
case 'top':
|
|
95
|
+
top = triggerRect.top - tooltipRect.height - gap;
|
|
96
|
+
left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;
|
|
97
|
+
break;
|
|
98
|
+
case 'top-start':
|
|
99
|
+
top = triggerRect.top - tooltipRect.height - gap;
|
|
100
|
+
left = triggerRect.left;
|
|
101
|
+
break;
|
|
102
|
+
case 'top-end':
|
|
103
|
+
top = triggerRect.top - tooltipRect.height - gap;
|
|
104
|
+
left = triggerRect.right - tooltipRect.width;
|
|
105
|
+
break;
|
|
106
|
+
case 'bottom':
|
|
107
|
+
top = triggerRect.bottom + gap;
|
|
108
|
+
left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;
|
|
109
|
+
break;
|
|
110
|
+
case 'bottom-start':
|
|
111
|
+
top = triggerRect.bottom + gap;
|
|
112
|
+
left = triggerRect.left;
|
|
113
|
+
break;
|
|
114
|
+
case 'bottom-end':
|
|
115
|
+
top = triggerRect.bottom + gap;
|
|
116
|
+
left = triggerRect.right - tooltipRect.width;
|
|
117
|
+
break;
|
|
118
|
+
case 'left':
|
|
119
|
+
top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;
|
|
120
|
+
left = triggerRect.left - tooltipRect.width - gap;
|
|
121
|
+
break;
|
|
122
|
+
case 'right':
|
|
123
|
+
top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;
|
|
124
|
+
left = triggerRect.right + gap;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { top, left };
|
|
129
|
+
}
|
|
@@ -18,6 +18,38 @@ export type TooltipProps = {
|
|
|
18
18
|
tooltipStyle?: CSSProperties;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export const TOOLTIP_BASE_STYLES = 'z-10 w-max bg-white p-4 border border-gray-200 rounded-md';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A simple CSS-based tooltip component that displays content on hover.
|
|
25
|
+
*
|
|
26
|
+
* **Note:** If you need the tooltip to escape overflow constraints or render at a specific
|
|
27
|
+
* location in the DOM (e.g., to avoid clipping by parent containers with `overflow: hidden`),
|
|
28
|
+
* use `PortalTooltip` instead.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* <Tooltip content="This is a tooltip" position="top">
|
|
33
|
+
* <button>Hover me</button>
|
|
34
|
+
* </Tooltip>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
const Tooltip: FunctionComponent<TooltipProps> = ({ children, content, position = 'bottom', tooltipStyle }) => {
|
|
38
|
+
return (
|
|
39
|
+
<div className={`relative group`}>
|
|
40
|
+
<div>{children}</div>
|
|
41
|
+
<div
|
|
42
|
+
className={`absolute ${TOOLTIP_BASE_STYLES} invisible group-hover:visible ${getPositionCss(position)}`}
|
|
43
|
+
style={tooltipStyle}
|
|
44
|
+
>
|
|
45
|
+
{content}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default Tooltip;
|
|
52
|
+
|
|
21
53
|
function getPositionCss(position?: TooltipPosition) {
|
|
22
54
|
switch (position) {
|
|
23
55
|
case 'top':
|
|
@@ -40,19 +72,3 @@ function getPositionCss(position?: TooltipPosition) {
|
|
|
40
72
|
return '';
|
|
41
73
|
}
|
|
42
74
|
}
|
|
43
|
-
|
|
44
|
-
const Tooltip: FunctionComponent<TooltipProps> = ({ children, content, position = 'bottom', tooltipStyle }) => {
|
|
45
|
-
return (
|
|
46
|
-
<div className={`relative group`}>
|
|
47
|
-
<div>{children}</div>
|
|
48
|
-
<div
|
|
49
|
-
className={`absolute z-10 w-max bg-white p-4 border border-gray-200 rounded-md invisible group-hover:visible ${getPositionCss(position)}`}
|
|
50
|
-
style={tooltipStyle}
|
|
51
|
-
>
|
|
52
|
-
{content}
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export default Tooltip;
|
|
@@ -108,7 +108,7 @@ export const Default: StoryObj<LineageFilterProps> = {
|
|
|
108
108
|
const input = await inputField(canvas);
|
|
109
109
|
await userEvent.clear(input);
|
|
110
110
|
await userEvent.type(input, 'B.1');
|
|
111
|
-
await userEvent.click(canvas.getByRole('option', { name: 'B.1(
|
|
111
|
+
await userEvent.click(canvas.getByRole('option', { name: 'B.1(53,802)' }));
|
|
112
112
|
|
|
113
113
|
await waitFor(() => {
|
|
114
114
|
return expect(lineageChangedListenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
@@ -187,6 +187,62 @@ export const WithHideCountsTrue: StoryObj<LineageFilterProps> = {
|
|
|
187
187
|
},
|
|
188
188
|
};
|
|
189
189
|
|
|
190
|
+
export const EnterAndClearMultipleTimes: StoryObj<LineageFilterProps> = {
|
|
191
|
+
...Default,
|
|
192
|
+
play: async ({ canvasElement, step }) => {
|
|
193
|
+
const { canvas, lineageChangedListenerMock } = await prepare(canvasElement, step);
|
|
194
|
+
const input = await inputField(canvas);
|
|
195
|
+
const inputContainer = canvas.getByRole('combobox').parentElement;
|
|
196
|
+
const clearSelectionButton = await canvas.findByLabelText('clear selection');
|
|
197
|
+
|
|
198
|
+
await step('clear field initially', async () => {
|
|
199
|
+
await userEvent.clear(input);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
await step('enter F in the input field', async () => {
|
|
203
|
+
await userEvent.type(input, 'F');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await step('clear selection using clear button', async () => {
|
|
207
|
+
await userEvent.click(clearSelectionButton);
|
|
208
|
+
|
|
209
|
+
await waitFor(() => {
|
|
210
|
+
return expect(lineageChangedListenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
211
|
+
pangoLineage: undefined,
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await step('verify input field is empty after clearing', async () => {
|
|
217
|
+
await expect(input).toHaveValue('');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await step('verify no red border after clearing', async () => {
|
|
221
|
+
await expect(inputContainer).not.toHaveClass('input-error');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// do it again
|
|
225
|
+
|
|
226
|
+
await step('enter F in the input field again', async () => {
|
|
227
|
+
await userEvent.type(input, 'F');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await step('clear selection using clear button again', async () => {
|
|
231
|
+
await userEvent.click(clearSelectionButton);
|
|
232
|
+
|
|
233
|
+
await waitFor(() => {
|
|
234
|
+
return expect(lineageChangedListenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
235
|
+
pangoLineage: undefined,
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await step('verify input field is empty after clearing again', async () => {
|
|
241
|
+
await expect(input).toHaveValue('');
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
190
246
|
async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRenderer, unknown>) {
|
|
191
247
|
const canvas = within(canvasElement);
|
|
192
248
|
|
|
@@ -99,7 +99,7 @@ const LineageSelector = ({
|
|
|
99
99
|
formatItemInList={(item: LineageItem) => (
|
|
100
100
|
<p>
|
|
101
101
|
<span>{item.lineage}</span>
|
|
102
|
-
{!hideCounts && <span className='ml-2 text-gray-500'>({item.count})</span>}
|
|
102
|
+
{!hideCounts && <span className='ml-2 text-gray-500'>({item.count.toLocaleString('en-US')})</span>}
|
|
103
103
|
</p>
|
|
104
104
|
)}
|
|
105
105
|
/>
|
|
@@ -112,7 +112,9 @@ const LocationSelector = ({
|
|
|
112
112
|
<>
|
|
113
113
|
<p>
|
|
114
114
|
<span>{item.label}</span>
|
|
115
|
-
{!hideCounts &&
|
|
115
|
+
{!hideCounts && (
|
|
116
|
+
<span className='ml-2 text-gray-500'>({item.count.toLocaleString('en-US')})</span>
|
|
117
|
+
)}
|
|
116
118
|
</p>
|
|
117
119
|
<span className='text-sm text-gray-500'>{item.description}</span>
|
|
118
120
|
</>
|
|
@@ -80,15 +80,6 @@ export const withGaps: MutationOverTimeMockData = {
|
|
|
80
80
|
totalCount: 8236,
|
|
81
81
|
},
|
|
82
82
|
],
|
|
83
|
-
[
|
|
84
|
-
'2024-04',
|
|
85
|
-
{
|
|
86
|
-
type: 'value',
|
|
87
|
-
count: 0,
|
|
88
|
-
proportion: NaN,
|
|
89
|
-
totalCount: 0,
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
83
|
[
|
|
93
84
|
'2024-05',
|
|
94
85
|
{
|
|
@@ -98,15 +89,6 @@ export const withGaps: MutationOverTimeMockData = {
|
|
|
98
89
|
totalCount: 8799,
|
|
99
90
|
},
|
|
100
91
|
],
|
|
101
|
-
[
|
|
102
|
-
'2024-06',
|
|
103
|
-
{
|
|
104
|
-
type: 'value',
|
|
105
|
-
count: 0,
|
|
106
|
-
proportion: NaN,
|
|
107
|
-
totalCount: 0,
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
92
|
[
|
|
111
93
|
'2024-07',
|
|
112
94
|
{
|
|
@@ -148,15 +130,6 @@ export const withGaps: MutationOverTimeMockData = {
|
|
|
148
130
|
totalCount: 8236,
|
|
149
131
|
},
|
|
150
132
|
],
|
|
151
|
-
[
|
|
152
|
-
'2024-04',
|
|
153
|
-
{
|
|
154
|
-
type: 'value',
|
|
155
|
-
count: 0,
|
|
156
|
-
proportion: NaN,
|
|
157
|
-
totalCount: 0,
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
133
|
[
|
|
161
134
|
'2024-05',
|
|
162
135
|
{
|
|
@@ -166,15 +139,6 @@ export const withGaps: MutationOverTimeMockData = {
|
|
|
166
139
|
totalCount: 8799,
|
|
167
140
|
},
|
|
168
141
|
],
|
|
169
|
-
[
|
|
170
|
-
'2024-06',
|
|
171
|
-
{
|
|
172
|
-
type: 'value',
|
|
173
|
-
count: 0,
|
|
174
|
-
proportion: NaN,
|
|
175
|
-
totalCount: 0,
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
142
|
[
|
|
179
143
|
'2024-07',
|
|
180
144
|
{
|
|
@@ -212,15 +176,6 @@ export const withGaps: MutationOverTimeMockData = {
|
|
|
212
176
|
totalCount: 8236,
|
|
213
177
|
},
|
|
214
178
|
],
|
|
215
|
-
[
|
|
216
|
-
'2024-04',
|
|
217
|
-
{
|
|
218
|
-
type: 'value',
|
|
219
|
-
count: 0,
|
|
220
|
-
proportion: NaN,
|
|
221
|
-
totalCount: 0,
|
|
222
|
-
},
|
|
223
|
-
],
|
|
224
179
|
[
|
|
225
180
|
'2024-05',
|
|
226
181
|
{
|
|
@@ -230,15 +185,6 @@ export const withGaps: MutationOverTimeMockData = {
|
|
|
230
185
|
totalCount: 8799,
|
|
231
186
|
},
|
|
232
187
|
],
|
|
233
|
-
[
|
|
234
|
-
'2024-06',
|
|
235
|
-
{
|
|
236
|
-
type: 'value',
|
|
237
|
-
count: 0,
|
|
238
|
-
proportion: NaN,
|
|
239
|
-
totalCount: 0,
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
188
|
[
|
|
243
189
|
'2024-07',
|
|
244
190
|
{
|
|
@@ -22,7 +22,7 @@ export const MutationsOverTimeGridTooltip: FunctionComponent<MutationsOverTimeGr
|
|
|
22
22
|
const dateClass = toTemporalClass(date);
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
|
-
<div>
|
|
25
|
+
<div className='text-center'>
|
|
26
26
|
<p>
|
|
27
27
|
<span className='font-bold'>{dateClass.englishName()}</span>
|
|
28
28
|
</p>
|
|
@@ -9,7 +9,8 @@ import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
|
9
9
|
import { type Temporal } from '../../utils/temporalClass';
|
|
10
10
|
import { AnnotatedMutation } from '../components/annotated-mutation';
|
|
11
11
|
import { type ColorScale, getColorWithinScale, getTextColorForScale } from '../components/color-scale-selector';
|
|
12
|
-
import
|
|
12
|
+
import PortalTooltip from '../components/portal-tooltip';
|
|
13
|
+
import { type TooltipPosition } from '../components/tooltip';
|
|
13
14
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
14
15
|
import { type PageSizes, Pagination } from '../shared/tanstackTable/pagination';
|
|
15
16
|
import { usePageSizeContext } from '../shared/tanstackTable/pagination-context';
|
|
@@ -21,11 +22,14 @@ import {
|
|
|
21
22
|
usePreactTable,
|
|
22
23
|
} from '../shared/tanstackTable/tanstackTable';
|
|
23
24
|
|
|
25
|
+
const NON_BREAKING_SPACE = '\u00A0';
|
|
26
|
+
|
|
24
27
|
export interface MutationsOverTimeGridProps {
|
|
25
28
|
data: MutationOverTimeDataMap;
|
|
26
29
|
colorScale: ColorScale;
|
|
27
30
|
sequenceType: SequenceType;
|
|
28
31
|
pageSizes: PageSizes;
|
|
32
|
+
tooltipPortalTarget: HTMLElement | null;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
type RowType = { mutation: Substitution | Deletion; values: (MutationOverTimeMutationValue | undefined)[] };
|
|
@@ -35,6 +39,7 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
35
39
|
colorScale,
|
|
36
40
|
sequenceType,
|
|
37
41
|
pageSizes,
|
|
42
|
+
tooltipPortalTarget,
|
|
38
43
|
}) => {
|
|
39
44
|
const tableData = useMemo(() => {
|
|
40
45
|
const allMutations = data.getFirstAxisKeys();
|
|
@@ -89,6 +94,7 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
89
94
|
numberOfColumns,
|
|
90
95
|
)}
|
|
91
96
|
colorScale={colorScale}
|
|
97
|
+
tooltipPortalTarget={tooltipPortalTarget}
|
|
92
98
|
/>
|
|
93
99
|
</div>
|
|
94
100
|
);
|
|
@@ -97,7 +103,7 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
97
103
|
});
|
|
98
104
|
|
|
99
105
|
return [mutationHeader, ...dateHeaders];
|
|
100
|
-
}, [colorScale, data, sequenceType]);
|
|
106
|
+
}, [colorScale, data, sequenceType, tooltipPortalTarget]);
|
|
101
107
|
|
|
102
108
|
const { pageSize } = usePageSizeContext();
|
|
103
109
|
const table = usePreactTable({
|
|
@@ -163,10 +169,10 @@ function styleGridHeader(columnIndex: number, numDateColumns: number) {
|
|
|
163
169
|
return { className: 'invisible @[6rem]:visible' };
|
|
164
170
|
}
|
|
165
171
|
|
|
166
|
-
function getTooltipPosition(rowIndex: number, rows: number, columnIndex: number, columns: number) {
|
|
172
|
+
function getTooltipPosition(rowIndex: number, rows: number, columnIndex: number, columns: number): TooltipPosition {
|
|
167
173
|
const tooltipX = rowIndex < rows / 2 || rowIndex < 6 ? 'bottom' : 'top';
|
|
168
174
|
const tooltipY = columnIndex < columns / 2 ? 'start' : 'end';
|
|
169
|
-
return `${tooltipX}-${tooltipY}
|
|
175
|
+
return `${tooltipX}-${tooltipY}`;
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
const ProportionCell: FunctionComponent<{
|
|
@@ -175,14 +181,16 @@ const ProportionCell: FunctionComponent<{
|
|
|
175
181
|
mutation: Substitution | Deletion;
|
|
176
182
|
tooltipPosition: TooltipPosition;
|
|
177
183
|
colorScale: ColorScale;
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
tooltipPortalTarget: HTMLElement | null;
|
|
185
|
+
}> = ({ value, mutation, date, tooltipPosition, colorScale, tooltipPortalTarget }) => {
|
|
186
|
+
const proportion = value?.type === 'belowThreshold' ? undefined : value?.proportion;
|
|
180
187
|
|
|
181
188
|
return (
|
|
182
189
|
<div className={'py-1 w-full h-full'}>
|
|
183
|
-
<
|
|
190
|
+
<PortalTooltip
|
|
184
191
|
content={<MutationsOverTimeGridTooltip mutation={mutation} date={date} value={value} />}
|
|
185
192
|
position={tooltipPosition}
|
|
193
|
+
portalTarget={tooltipPortalTarget}
|
|
186
194
|
>
|
|
187
195
|
<div
|
|
188
196
|
style={{
|
|
@@ -194,10 +202,12 @@ const ProportionCell: FunctionComponent<{
|
|
|
194
202
|
{value === null ? (
|
|
195
203
|
<span className='invisible'>No data</span>
|
|
196
204
|
) : (
|
|
197
|
-
<span className='invisible @[2rem]:visible'>
|
|
205
|
+
<span className='invisible @[2rem]:visible'>
|
|
206
|
+
{proportion !== undefined ? formatProportion(proportion, 0) : NON_BREAKING_SPACE}
|
|
207
|
+
</span>
|
|
198
208
|
)}
|
|
199
209
|
</div>
|
|
200
|
-
</
|
|
210
|
+
</PortalTooltip>
|
|
201
211
|
</div>
|
|
202
212
|
);
|
|
203
213
|
};
|