@genspectrum/dashboard-components 0.18.2 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/custom-elements.json +2 -2
  2. package/dist/assets/{mutationOverTimeWorker-ChQTFL68.js.map → mutationOverTimeWorker--b8ZHlji.js.map} +1 -1
  3. package/dist/components.d.ts +62 -54
  4. package/dist/components.js +114 -39
  5. package/dist/components.js.map +1 -1
  6. package/dist/style.css +2 -2
  7. package/dist/util.d.ts +70 -58
  8. package/package.json +4 -4
  9. package/src/preact/MutationAnnotationsContext.spec.tsx +103 -34
  10. package/src/preact/MutationAnnotationsContext.tsx +49 -7
  11. package/src/preact/components/annotated-mutation.stories.tsx +0 -5
  12. package/src/preact/components/annotated-mutation.tsx +6 -2
  13. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +3 -1
  14. package/src/preact/mutations/mutations.stories.tsx +4 -1
  15. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +3 -1
  16. package/src/preact/sequencesByLocation/leafletStyleModifications.css +5 -0
  17. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +25 -0
  18. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +65 -13
  19. package/src/web-components/MutationAnnotations.mdx +8 -0
  20. package/src/web-components/gs-app.stories.ts +2 -0
  21. package/src/web-components/gs-app.ts +4 -2
  22. package/src/web-components/mutation-annotations-context.ts +6 -2
  23. package/standalone-bundle/assets/mutationOverTimeWorker-jChgWnwp.js.map +1 -1
  24. package/standalone-bundle/dashboard-components.js +5699 -5643
  25. package/standalone-bundle/dashboard-components.js.map +1 -1
  26. package/standalone-bundle/style.css +1 -1
package/dist/util.d.ts CHANGED
@@ -208,40 +208,52 @@ declare const mutationAnnotationSchema: default_2.ZodObject<{
208
208
  name: default_2.ZodString;
209
209
  description: default_2.ZodString;
210
210
  symbol: default_2.ZodString;
211
- nucleotideMutations: default_2.ZodArray<default_2.ZodString, "many">;
212
- aminoAcidMutations: default_2.ZodArray<default_2.ZodString, "many">;
211
+ nucleotideMutations: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
212
+ nucleotidePositions: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
213
+ aminoAcidMutations: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
214
+ aminoAcidPositions: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
213
215
  }, "strip", default_2.ZodTypeAny, {
214
216
  symbol: string;
215
217
  name: string;
216
218
  description: string;
217
- nucleotideMutations: string[];
218
- aminoAcidMutations: string[];
219
+ nucleotideMutations?: string[] | undefined;
220
+ nucleotidePositions?: string[] | undefined;
221
+ aminoAcidMutations?: string[] | undefined;
222
+ aminoAcidPositions?: string[] | undefined;
219
223
  }, {
220
224
  symbol: string;
221
225
  name: string;
222
226
  description: string;
223
- nucleotideMutations: string[];
224
- aminoAcidMutations: string[];
227
+ nucleotideMutations?: string[] | undefined;
228
+ nucleotidePositions?: string[] | undefined;
229
+ aminoAcidMutations?: string[] | undefined;
230
+ aminoAcidPositions?: string[] | undefined;
225
231
  }>;
226
232
 
227
233
  declare const mutationAnnotationsSchema: default_2.ZodArray<default_2.ZodObject<{
228
234
  name: default_2.ZodString;
229
235
  description: default_2.ZodString;
230
236
  symbol: default_2.ZodString;
231
- nucleotideMutations: default_2.ZodArray<default_2.ZodString, "many">;
232
- aminoAcidMutations: default_2.ZodArray<default_2.ZodString, "many">;
237
+ nucleotideMutations: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
238
+ nucleotidePositions: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
239
+ aminoAcidMutations: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
240
+ aminoAcidPositions: default_2.ZodOptional<default_2.ZodArray<default_2.ZodString, "many">>;
233
241
  }, "strip", default_2.ZodTypeAny, {
234
242
  symbol: string;
235
243
  name: string;
236
244
  description: string;
237
- nucleotideMutations: string[];
238
- aminoAcidMutations: string[];
245
+ nucleotideMutations?: string[] | undefined;
246
+ nucleotidePositions?: string[] | undefined;
247
+ aminoAcidMutations?: string[] | undefined;
248
+ aminoAcidPositions?: string[] | undefined;
239
249
  }, {
240
250
  symbol: string;
241
251
  name: string;
242
252
  description: string;
243
- nucleotideMutations: string[];
244
- aminoAcidMutations: string[];
253
+ nucleotideMutations?: string[] | undefined;
254
+ nucleotidePositions?: string[] | undefined;
255
+ aminoAcidMutations?: string[] | undefined;
256
+ aminoAcidPositions?: string[] | undefined;
245
257
  }>, "many">;
246
258
 
247
259
  export declare type MutationComparisonProps = default_2.infer<typeof mutationComparisonPropsSchema>;
@@ -890,7 +902,7 @@ declare global {
890
902
 
891
903
  declare global {
892
904
  interface HTMLElementTagNameMap {
893
- 'gs-mutation-comparison-component': MutationComparisonComponent;
905
+ 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
894
906
  }
895
907
  }
896
908
 
@@ -898,7 +910,7 @@ declare global {
898
910
  declare global {
899
911
  namespace JSX {
900
912
  interface IntrinsicElements {
901
- 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
913
+ 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
902
914
  }
903
915
  }
904
916
  }
@@ -906,7 +918,11 @@ declare global {
906
918
 
907
919
  declare global {
908
920
  interface HTMLElementTagNameMap {
909
- 'gs-mutations-component': MutationsComponent;
921
+ 'gs-date-range-filter': DateRangeFilterComponent;
922
+ }
923
+ interface HTMLElementEventMap {
924
+ 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
925
+ 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
910
926
  }
911
927
  }
912
928
 
@@ -914,7 +930,7 @@ declare global {
914
930
  declare global {
915
931
  namespace JSX {
916
932
  interface IntrinsicElements {
917
- 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
933
+ 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
918
934
  }
919
935
  }
920
936
  }
@@ -922,7 +938,10 @@ declare global {
922
938
 
923
939
  declare global {
924
940
  interface HTMLElementTagNameMap {
925
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
941
+ 'gs-location-filter': LocationFilterComponent;
942
+ }
943
+ interface HTMLElementEventMap {
944
+ 'gs-location-changed': LocationChangedEvent;
926
945
  }
927
946
  }
928
947
 
@@ -930,7 +949,7 @@ declare global {
930
949
  declare global {
931
950
  namespace JSX {
932
951
  interface IntrinsicElements {
933
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
952
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
934
953
  }
935
954
  }
936
955
  }
@@ -938,7 +957,10 @@ declare global {
938
957
 
939
958
  declare global {
940
959
  interface HTMLElementTagNameMap {
941
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
960
+ 'gs-text-filter': TextFilterComponent;
961
+ }
962
+ interface HTMLElementEventMap {
963
+ 'gs-text-filter-changed': TextFilterChangedEvent;
942
964
  }
943
965
  }
944
966
 
@@ -946,7 +968,7 @@ declare global {
946
968
  declare global {
947
969
  namespace JSX {
948
970
  interface IntrinsicElements {
949
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
971
+ 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
950
972
  }
951
973
  }
952
974
  }
@@ -954,7 +976,10 @@ declare global {
954
976
 
955
977
  declare global {
956
978
  interface HTMLElementTagNameMap {
957
- 'gs-aggregate': AggregateComponent;
979
+ 'gs-lineage-filter': LineageFilterComponent;
980
+ }
981
+ interface HTMLElementEventMap {
982
+ 'gs-lineage-filter-changed': LineageFilterChangedEvent;
958
983
  }
959
984
  }
960
985
 
@@ -962,7 +987,7 @@ declare global {
962
987
  declare global {
963
988
  namespace JSX {
964
989
  interface IntrinsicElements {
965
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
990
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
966
991
  }
967
992
  }
968
993
  }
@@ -970,7 +995,10 @@ declare global {
970
995
 
971
996
  declare global {
972
997
  interface HTMLElementTagNameMap {
973
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
998
+ 'gs-mutation-filter': MutationFilterComponent;
999
+ }
1000
+ interface HTMLElementEventMap {
1001
+ 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
974
1002
  }
975
1003
  }
976
1004
 
@@ -978,7 +1006,7 @@ declare global {
978
1006
  declare global {
979
1007
  namespace JSX {
980
1008
  interface IntrinsicElements {
981
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1009
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
982
1010
  }
983
1011
  }
984
1012
  }
@@ -986,7 +1014,7 @@ declare global {
986
1014
 
987
1015
  declare global {
988
1016
  interface HTMLElementTagNameMap {
989
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1017
+ 'gs-mutation-comparison-component': MutationComparisonComponent;
990
1018
  }
991
1019
  }
992
1020
 
@@ -994,7 +1022,7 @@ declare global {
994
1022
  declare global {
995
1023
  namespace JSX {
996
1024
  interface IntrinsicElements {
997
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1025
+ 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
998
1026
  }
999
1027
  }
1000
1028
  }
@@ -1002,7 +1030,7 @@ declare global {
1002
1030
 
1003
1031
  declare global {
1004
1032
  interface HTMLElementTagNameMap {
1005
- 'gs-statistics': StatisticsComponent;
1033
+ 'gs-mutations-component': MutationsComponent;
1006
1034
  }
1007
1035
  }
1008
1036
 
@@ -1010,7 +1038,7 @@ declare global {
1010
1038
  declare global {
1011
1039
  namespace JSX {
1012
1040
  interface IntrinsicElements {
1013
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1041
+ 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1014
1042
  }
1015
1043
  }
1016
1044
  }
@@ -1018,7 +1046,7 @@ declare global {
1018
1046
 
1019
1047
  declare global {
1020
1048
  interface HTMLElementTagNameMap {
1021
- 'gs-sequences-by-location': SequencesByLocationComponent;
1049
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1022
1050
  }
1023
1051
  }
1024
1052
 
@@ -1026,7 +1054,7 @@ declare global {
1026
1054
  declare global {
1027
1055
  namespace JSX {
1028
1056
  interface IntrinsicElements {
1029
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1057
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1030
1058
  }
1031
1059
  }
1032
1060
  }
@@ -1034,11 +1062,7 @@ declare global {
1034
1062
 
1035
1063
  declare global {
1036
1064
  interface HTMLElementTagNameMap {
1037
- 'gs-date-range-filter': DateRangeFilterComponent;
1038
- }
1039
- interface HTMLElementEventMap {
1040
- 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
1041
- 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
1065
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1042
1066
  }
1043
1067
  }
1044
1068
 
@@ -1046,7 +1070,7 @@ declare global {
1046
1070
  declare global {
1047
1071
  namespace JSX {
1048
1072
  interface IntrinsicElements {
1049
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1073
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1050
1074
  }
1051
1075
  }
1052
1076
  }
@@ -1054,10 +1078,7 @@ declare global {
1054
1078
 
1055
1079
  declare global {
1056
1080
  interface HTMLElementTagNameMap {
1057
- 'gs-location-filter': LocationFilterComponent;
1058
- }
1059
- interface HTMLElementEventMap {
1060
- 'gs-location-changed': LocationChangedEvent;
1081
+ 'gs-aggregate': AggregateComponent;
1061
1082
  }
1062
1083
  }
1063
1084
 
@@ -1065,7 +1086,7 @@ declare global {
1065
1086
  declare global {
1066
1087
  namespace JSX {
1067
1088
  interface IntrinsicElements {
1068
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1089
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1069
1090
  }
1070
1091
  }
1071
1092
  }
@@ -1073,10 +1094,7 @@ declare global {
1073
1094
 
1074
1095
  declare global {
1075
1096
  interface HTMLElementTagNameMap {
1076
- 'gs-text-filter': TextFilterComponent;
1077
- }
1078
- interface HTMLElementEventMap {
1079
- 'gs-text-filter-changed': TextFilterChangedEvent;
1097
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1080
1098
  }
1081
1099
  }
1082
1100
 
@@ -1084,7 +1102,7 @@ declare global {
1084
1102
  declare global {
1085
1103
  namespace JSX {
1086
1104
  interface IntrinsicElements {
1087
- 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1105
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1088
1106
  }
1089
1107
  }
1090
1108
  }
@@ -1092,10 +1110,7 @@ declare global {
1092
1110
 
1093
1111
  declare global {
1094
1112
  interface HTMLElementTagNameMap {
1095
- 'gs-mutation-filter': MutationFilterComponent;
1096
- }
1097
- interface HTMLElementEventMap {
1098
- 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
1113
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1099
1114
  }
1100
1115
  }
1101
1116
 
@@ -1103,7 +1118,7 @@ declare global {
1103
1118
  declare global {
1104
1119
  namespace JSX {
1105
1120
  interface IntrinsicElements {
1106
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1121
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1107
1122
  }
1108
1123
  }
1109
1124
  }
@@ -1111,10 +1126,7 @@ declare global {
1111
1126
 
1112
1127
  declare global {
1113
1128
  interface HTMLElementTagNameMap {
1114
- 'gs-lineage-filter': LineageFilterComponent;
1115
- }
1116
- interface HTMLElementEventMap {
1117
- 'gs-lineage-filter-changed': LineageFilterChangedEvent;
1129
+ 'gs-sequences-by-location': SequencesByLocationComponent;
1118
1130
  }
1119
1131
  }
1120
1132
 
@@ -1122,7 +1134,7 @@ declare global {
1122
1134
  declare global {
1123
1135
  namespace JSX {
1124
1136
  interface IntrinsicElements {
1125
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1137
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1126
1138
  }
1127
1139
  }
1128
1140
  }
@@ -1130,7 +1142,7 @@ declare global {
1130
1142
 
1131
1143
  declare global {
1132
1144
  interface HTMLElementTagNameMap {
1133
- 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1145
+ 'gs-statistics': StatisticsComponent;
1134
1146
  }
1135
1147
  }
1136
1148
 
@@ -1138,7 +1150,7 @@ declare global {
1138
1150
  declare global {
1139
1151
  namespace JSX {
1140
1152
  interface IntrinsicElements {
1141
- 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1153
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1142
1154
  }
1143
1155
  }
1144
1156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.18.2",
3
+ "version": "0.18.4",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -112,7 +112,7 @@
112
112
  "@storybook/preact": "^8.0.9",
113
113
  "@storybook/preact-vite": "^8.0.9",
114
114
  "@storybook/test": "^8.0.0",
115
- "@storybook/test-runner": "^0.21.0",
115
+ "@storybook/test-runner": "^0.22.0",
116
116
  "@storybook/types": "^8.0.9",
117
117
  "@storybook/web-components": "^8.0.9",
118
118
  "@storybook/web-components-vite": "^8.0.9",
@@ -143,9 +143,9 @@
143
143
  "storybook": "^8.0.9",
144
144
  "storybook-addon-fetch-mock": "^2.0.0",
145
145
  "tailwindcss": "^4.0.9",
146
- "typescript": "~5.7.2",
146
+ "typescript": "^5.8.2",
147
147
  "vite": "^6.0.3",
148
- "vite-plugin-dts": "^4.0.3",
148
+ "vite-plugin-dts": "4.5.0",
149
149
  "vitest": "^3.0.2"
150
150
  }
151
151
  }
@@ -3,56 +3,125 @@ import { type FunctionalComponent } from 'preact';
3
3
  import { describe, expect, it } from 'vitest';
4
4
 
5
5
  import { MutationAnnotationsContextProvider, useMutationAnnotationsProvider } from './MutationAnnotationsContext';
6
+ import { SubstitutionClass } from '../utils/mutations';
6
7
  import { type MutationAnnotations } from '../web-components/mutation-annotations-context';
7
8
 
8
9
  describe('useMutationAnnotation', () => {
9
- const mockAnnotations: MutationAnnotations = [
10
- {
11
- name: 'Annotation 1',
12
- description: 'Description 1',
13
- symbol: 'A1',
14
- nucleotideMutations: ['A123', 'A456'],
15
- aminoAcidMutations: ['B123'],
16
- },
17
- {
18
- name: 'Annotation 2',
19
- description: 'Description 2',
20
- symbol: 'A2',
21
- nucleotideMutations: ['A456', 'A789'],
22
- aminoAcidMutations: ['B456', 'B789'],
23
- },
24
- ];
25
-
26
- const wrapper: FunctionalComponent = ({ children }) => (
27
- <MutationAnnotationsContextProvider value={mockAnnotations}>{children}</MutationAnnotationsContextProvider>
28
- );
29
-
30
- function renderAnnotationsHook() {
10
+ function renderAnnotationsHook(mockAnnotations: MutationAnnotations) {
11
+ const wrapper: FunctionalComponent = ({ children }) => (
12
+ <MutationAnnotationsContextProvider value={mockAnnotations}>{children}</MutationAnnotationsContextProvider>
13
+ );
14
+
31
15
  const { result } = renderHook(() => useMutationAnnotationsProvider(), { wrapper });
32
16
  return result.current;
33
17
  }
34
18
 
35
- it('should return the correct annotation for a given nucleotide mutation', () => {
36
- const result = renderAnnotationsHook()('A123', 'nucleotide');
19
+ describe('annotations for nucleotide mutations', () => {
20
+ const mockAnnotations: MutationAnnotations = [
21
+ {
22
+ name: 'Annotation 1',
23
+ description: 'Description 1',
24
+ symbol: 'A1',
25
+ nucleotideMutations: ['A123', 'A456'],
26
+ },
27
+ {
28
+ name: 'Annotation 2',
29
+ description: 'Description 2',
30
+ symbol: 'A2',
31
+ nucleotideMutations: ['A456', 'A789'],
32
+ },
33
+ ];
34
+
35
+ it('should return the correct annotation for a given mutation', () => {
36
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('A123')!, 'nucleotide');
37
+
38
+ expect(result).toEqual([mockAnnotations[0]]);
39
+ });
37
40
 
38
- expect(result).toEqual([mockAnnotations[0]]);
41
+ it('should return the correct annotations if multiple contain a mutation', () => {
42
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('A456')!, 'nucleotide');
43
+
44
+ expect(result).toEqual([mockAnnotations[0], mockAnnotations[1]]);
45
+ });
39
46
  });
40
47
 
41
- it('should return the correct annotations if multiple contain a mutation', () => {
42
- const result = renderAnnotationsHook()('A456', 'nucleotide');
48
+ describe('annotations for amino acid mutations', () => {
49
+ const mockAnnotations: MutationAnnotations = [
50
+ {
51
+ name: 'Annotation 1',
52
+ description: 'Description 1',
53
+ symbol: 'A1',
54
+ aminoAcidMutations: ['B456', 'B789'],
55
+ },
56
+ ];
57
+
58
+ it('should return the correct mutation annotation for a given mutations', () => {
59
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('B456')!, 'amino acid');
43
60
 
44
- expect(result).toEqual([mockAnnotations[0], mockAnnotations[1]]);
61
+ expect(result).toEqual([mockAnnotations[0]]);
62
+ });
45
63
  });
46
64
 
47
- it('should return undefined for a non-existent mutation code', () => {
48
- const result = renderAnnotationsHook()('NON_EXISTENT', 'nucleotide');
65
+ describe('annotations for nucleotide positions', () => {
66
+ const mockAnnotations: MutationAnnotations = [
67
+ {
68
+ name: 'Annotation 1',
69
+ description: 'Description 1',
70
+ symbol: 'A1',
71
+ nucleotideMutations: ['A321T', 'A432T'],
72
+ nucleotidePositions: ['321', '543'],
73
+ },
74
+ {
75
+ name: 'Annotation 2',
76
+ description: 'Description 2',
77
+ symbol: 'A2',
78
+ nucleotidePositions: ['432'],
79
+ },
80
+ ];
49
81
 
50
- expect(result).toBeUndefined();
82
+ it('should return the correct mutation annotation covered by position only', () => {
83
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('A543T')!, 'nucleotide');
84
+ expect(result).toEqual([mockAnnotations[0]]);
85
+ });
86
+
87
+ it('should return the correct mutation annotation covered both by position and mutation', () => {
88
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('A321T')!, 'nucleotide');
89
+ expect(result).toEqual([mockAnnotations[0]]);
90
+ });
91
+
92
+ it('should return both annotations if one matches the mutations and the other the position', () => {
93
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('A432T')!, 'nucleotide');
94
+ expect(result).toEqual([mockAnnotations[1], mockAnnotations[0]]);
95
+ });
51
96
  });
52
97
 
53
- it('should return the correct mutation annotation for amino acid mutations', () => {
54
- const result = renderAnnotationsHook()('B456', 'amino acid');
98
+ describe('annotations for amino acid positions', () => {
99
+ const mockAnnotations: MutationAnnotations = [
100
+ {
101
+ name: 'Annotation 1',
102
+ description: 'Description 1',
103
+ symbol: 'A1',
104
+ aminoAcidMutations: ['Gene:B321C', 'Gene:B432G'],
105
+ aminoAcidPositions: ['Gene:432', 'Gene:543'],
106
+ },
107
+ ];
108
+
109
+ it('should return the correct mutation annotation covered both by position and mutation', () => {
110
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('Gene:B432G')!, 'amino acid');
111
+ expect(result).toEqual([mockAnnotations[0]]);
112
+ });
113
+
114
+ it('should return the correct mutation annotation covered both by position only', () => {
115
+ const result = renderAnnotationsHook(mockAnnotations)(SubstitutionClass.parse('Gene:B543G')!, 'amino acid');
116
+ expect(result).toEqual([mockAnnotations[0]]);
117
+ });
55
118
 
56
- expect(result).toEqual([mockAnnotations[1]]);
119
+ it('should return no mutation annotation for an amino acid position of wrong gene', () => {
120
+ const result = renderAnnotationsHook(mockAnnotations)(
121
+ SubstitutionClass.parse('NotTheGene:B543G')!,
122
+ 'amino acid',
123
+ );
124
+ expect(result).toBeUndefined();
125
+ });
57
126
  });
58
127
  });
@@ -9,10 +9,22 @@ import {
9
9
  } from '../web-components/mutation-annotations-context';
10
10
  import { ErrorDisplay } from './components/error-display';
11
11
  import { ResizeContainer } from './components/resize-container';
12
+ import { type Mutation } from '../utils/mutations';
12
13
 
13
- const MutationAnnotationsContext = createContext<Record<SequenceType, Map<string, MutationAnnotations>>>({
14
- nucleotide: new Map(),
15
- 'amino acid': new Map(),
14
+ type MutationAnnotationPerSequenceType = {
15
+ mutation: Map<string, MutationAnnotations>;
16
+ position: Map<string, MutationAnnotations>;
17
+ };
18
+
19
+ const MutationAnnotationsContext = createContext<Record<SequenceType, MutationAnnotationPerSequenceType>>({
20
+ nucleotide: {
21
+ mutation: new Map(),
22
+ position: new Map(),
23
+ },
24
+ 'amino acid': {
25
+ mutation: new Map(),
26
+ position: new Map(),
27
+ },
16
28
  });
17
29
 
18
30
  export const MutationAnnotationsContextProvider: FunctionalComponent<
@@ -26,7 +38,9 @@ export const MutationAnnotationsContextProvider: FunctionalComponent<
26
38
  }
27
39
 
28
40
  const nucleotideMap = new Map<string, MutationAnnotations>();
41
+ const nucleotidePositions = new Map<string, MutationAnnotations>();
29
42
  const aminoAcidMap = new Map<string, MutationAnnotations>();
43
+ const aminoAcidPositions = new Map<string, MutationAnnotations>();
30
44
 
31
45
  value.forEach((annotation) => {
32
46
  new Set(annotation.nucleotideMutations).forEach((code) => {
@@ -35,13 +49,19 @@ export const MutationAnnotationsContextProvider: FunctionalComponent<
35
49
  new Set(annotation.aminoAcidMutations).forEach((code) => {
36
50
  addAnnotationToMap(aminoAcidMap, code, annotation);
37
51
  });
52
+ new Set(annotation.nucleotidePositions).forEach((position) => {
53
+ addAnnotationToMap(nucleotidePositions, position, annotation);
54
+ });
55
+ new Set(annotation.aminoAcidPositions).forEach((position) => {
56
+ addAnnotationToMap(aminoAcidPositions, position, annotation);
57
+ });
38
58
  });
39
59
 
40
60
  return {
41
61
  success: true as const,
42
62
  value: {
43
- nucleotide: nucleotideMap,
44
- 'amino acid': aminoAcidMap,
63
+ nucleotide: { mutation: nucleotideMap, position: nucleotidePositions },
64
+ 'amino acid': { mutation: aminoAcidMap, position: aminoAcidPositions },
45
65
  },
46
66
  };
47
67
  }, [value]);
@@ -67,6 +87,28 @@ function addAnnotationToMap(map: Map<string, MutationAnnotations>, code: string,
67
87
  export function useMutationAnnotationsProvider() {
68
88
  const mutationAnnotations = useContext(MutationAnnotationsContext);
69
89
 
70
- return (mutationCode: string, sequenceType: SequenceType) =>
71
- mutationAnnotations[sequenceType].get(mutationCode.toUpperCase());
90
+ return (mutation: Mutation, sequenceType: SequenceType) => {
91
+ const position =
92
+ mutation.segment === undefined
93
+ ? `${mutation.position}`
94
+ : `${mutation.segment.toUpperCase()}:${mutation.position}`;
95
+
96
+ const possiblePositionAnnotations = mutationAnnotations[sequenceType].position.get(position);
97
+ const possibleExactAnnotations = mutationAnnotations[sequenceType].mutation.get(mutation.code.toUpperCase());
98
+
99
+ const annotations =
100
+ possiblePositionAnnotations && possibleExactAnnotations
101
+ ? [...possiblePositionAnnotations, ...possibleExactAnnotations]
102
+ : (possiblePositionAnnotations ?? possibleExactAnnotations);
103
+
104
+ const uniqueNames = new Set<string>();
105
+
106
+ return annotations?.filter((item) => {
107
+ if (uniqueNames.has(item.name)) {
108
+ return false;
109
+ }
110
+ uniqueNames.add(item.name);
111
+ return true;
112
+ });
113
+ };
72
114
  }
@@ -46,7 +46,6 @@ export const MutationWithoutAnnotationEntry: StoryObj<AnnotatedMutationProps & {
46
46
  description: 'This is a test annotation',
47
47
  symbol: '*',
48
48
  nucleotideMutations: ['123T'],
49
- aminoAcidMutations: [],
50
49
  },
51
50
  ],
52
51
  },
@@ -69,7 +68,6 @@ export const MutationWithAnnotationEntry: StoryObj<AnnotatedMutationProps & { an
69
68
  description: 'This is a test annotation <a class="link" href="/">with a link.</a>',
70
69
  symbol: '*',
71
70
  nucleotideMutations: ['A23403G'],
72
- aminoAcidMutations: [],
73
71
  },
74
72
  ],
75
73
  },
@@ -97,14 +95,12 @@ export const MutationWithMultipleAnnotationEntries: StoryObj<
97
95
  description: 'This is a test annotation',
98
96
  symbol: '*',
99
97
  nucleotideMutations: ['A23403G'],
100
- aminoAcidMutations: [],
101
98
  },
102
99
  {
103
100
  name: 'Another test annotation',
104
101
  description: 'This is a test annotation',
105
102
  symbol: '+',
106
103
  nucleotideMutations: ['A23403G'],
107
- aminoAcidMutations: [],
108
104
  },
109
105
  ],
110
106
  },
@@ -139,7 +135,6 @@ export const AminoAcidMutationWithAnnotationEntry: StoryObj<
139
135
  name: 'Test annotation',
140
136
  description: 'This is a test annotation',
141
137
  symbol: '*',
142
- nucleotideMutations: [],
143
138
  aminoAcidMutations: ['S:A501G'],
144
139
  },
145
140
  ],