@genspectrum/dashboard-components 0.19.2 → 0.19.3

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 (52) hide show
  1. package/custom-elements.json +160 -10
  2. package/dist/{LineageFilterChangedEvent-ixHQkq8y.js → LineageFilterChangedEvent-b0iuroUL.js} +15 -5
  3. package/dist/LineageFilterChangedEvent-b0iuroUL.js.map +1 -0
  4. package/dist/assets/mutationOverTimeWorker-ChQTFL68.js.map +1 -1
  5. package/dist/components.d.ts +71 -25
  6. package/dist/components.js +9047 -8699
  7. package/dist/components.js.map +1 -1
  8. package/dist/util.d.ts +51 -25
  9. package/dist/util.js +2 -1
  10. package/package.json +1 -1
  11. package/src/componentsEntrypoint.ts +3 -1
  12. package/src/preact/components/error-display.stories.tsx +2 -1
  13. package/src/preact/components/error-display.tsx +2 -3
  14. package/src/preact/components/resize-container.tsx +7 -10
  15. package/src/preact/components/tooltip.tsx +7 -4
  16. package/src/preact/dateRangeFilter/date-range-filter.stories.tsx +5 -4
  17. package/src/preact/dateRangeFilter/date-range-filter.tsx +2 -1
  18. package/src/preact/dateRangeFilter/dateRangeOption.ts +2 -1
  19. package/src/preact/genomeViewer/CDSPlot.tsx +219 -0
  20. package/src/preact/genomeViewer/genome-data-viewer.stories.tsx +113 -0
  21. package/src/preact/genomeViewer/genome-data-viewer.tsx +69 -0
  22. package/src/preact/genomeViewer/loadGff3.spec.ts +61 -0
  23. package/src/preact/genomeViewer/loadGff3.ts +174 -0
  24. package/src/preact/lineageFilter/LineageFilterChangedEvent.ts +3 -1
  25. package/src/preact/lineageFilter/lineage-filter.stories.tsx +3 -2
  26. package/src/preact/locationFilter/LocationChangedEvent.ts +2 -1
  27. package/src/preact/locationFilter/location-filter.stories.tsx +3 -2
  28. package/src/preact/mutationFilter/mutation-filter.stories.tsx +3 -2
  29. package/src/preact/mutationFilter/mutation-filter.tsx +2 -1
  30. package/src/preact/shared/charts/colors.ts +1 -1
  31. package/src/preact/textFilter/TextFilterChangedEvent.ts +3 -1
  32. package/src/preact/textFilter/text-filter.stories.tsx +4 -3
  33. package/src/utilEntrypoint.ts +2 -0
  34. package/src/utils/gsEventNames.ts +9 -0
  35. package/src/web-components/input/gs-date-range-filter.stories.ts +4 -3
  36. package/src/web-components/input/gs-date-range-filter.tsx +3 -2
  37. package/src/web-components/input/gs-lineage-filter.stories.ts +3 -2
  38. package/src/web-components/input/gs-lineage-filter.tsx +2 -1
  39. package/src/web-components/input/gs-location-filter.stories.ts +3 -2
  40. package/src/web-components/input/gs-location-filter.tsx +2 -1
  41. package/src/web-components/input/gs-mutation-filter.stories.ts +3 -2
  42. package/src/web-components/input/gs-mutation-filter.tsx +2 -1
  43. package/src/web-components/input/gs-text-filter.stories.ts +3 -2
  44. package/src/web-components/input/gs-text-filter.tsx +2 -1
  45. package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +18 -0
  46. package/src/web-components/visualization/gs-genome-data-viewer.stories.ts +108 -0
  47. package/src/web-components/visualization/gs-genome-data-viewer.tsx +59 -0
  48. package/src/web-components/visualization/index.ts +1 -0
  49. package/standalone-bundle/assets/mutationOverTimeWorker-jChgWnwp.js.map +1 -1
  50. package/standalone-bundle/dashboard-components.js +8275 -8002
  51. package/standalone-bundle/dashboard-components.js.map +1 -1
  52. package/dist/LineageFilterChangedEvent-ixHQkq8y.js.map +0 -1
@@ -337,7 +337,7 @@
337
337
  "type": {
338
338
  "text": "Meta<Required<DateRangeFilterProps>>"
339
339
  },
340
- "default": "{ title: 'Input/DateRangeFilter', component: 'gs-date-range-filter', parameters: withComponentDocs({ actions: { handles: ['gs-date-range-filter-changed', 'gs-date-range-option-changed', ...previewHandles], }, fetchMock: {}, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { value: { control: { type: 'object' }, }, lapisDateField: { control: { type: 'text' } }, dateRangeOptions: { control: { type: 'object' }, }, earliestDate: { control: { type: 'text' }, }, width: { control: { type: 'text' }, }, placeholder: { control: { type: 'text' }, }, }, args: { dateRangeOptions: [ dateRangeOptionPresets.lastMonth, dateRangeOptionPresets.last3Months, dateRangeOptionPresets.allTimes, { label: '2021', dateFrom: '2021-01-01', dateTo: '2021-12-31' }, customDateRange, ], earliestDate: '1970-01-01', value: dateRangeOptionPresets.lastMonth.label, lapisDateField: 'aDateColumn', width: '100%', placeholder: 'Date range', }, tags: ['autodocs'], }"
340
+ "default": "{ title: 'Input/DateRangeFilter', component: 'gs-date-range-filter', parameters: withComponentDocs({ actions: { handles: [gsEventNames.dateRangeFilterChanged, gsEventNames.dateRangeOptionChanged, ...previewHandles], }, fetchMock: {}, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { value: { control: { type: 'object' }, }, lapisDateField: { control: { type: 'text' } }, dateRangeOptions: { control: { type: 'object' }, }, earliestDate: { control: { type: 'text' }, }, width: { control: { type: 'text' }, }, placeholder: { control: { type: 'text' }, }, }, args: { dateRangeOptions: [ dateRangeOptionPresets.lastMonth, dateRangeOptionPresets.last3Months, dateRangeOptionPresets.allTimes, { label: '2021', dateFrom: '2021-01-01', dateTo: '2021-12-31' }, customDateRange, ], earliestDate: '1970-01-01', value: dateRangeOptionPresets.lastMonth.label, lapisDateField: 'aDateColumn', width: '100%', placeholder: 'Date range', }, tags: ['autodocs'], }"
341
341
  },
342
342
  {
343
343
  "kind": "variable",
@@ -369,7 +369,7 @@
369
369
  "type": {
370
370
  "text": "StoryObj<Required<DateRangeFilterProps>>"
371
371
  },
372
- "default": "{ ...Default, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-filter'); const filterChangedListenerMock = fn(); const optionChangedListenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener('gs-date-range-filter-changed', filterChangedListenerMock); canvasElement.addEventListener('gs-date-range-option-changed', optionChangedListenerMock); }); await step('Expect last 6 months to be selected', async () => { await waitFor(async () => { const placeholderOption = canvas.getByRole('combobox').querySelector('option:checked'); await expect(placeholderOption).toHaveTextContent('Last month'); }); await waitFor(async () => { await expect(dateToPicker(canvas)).toHaveValue(toYYYYMMDD(new Date())); }); }); await step('Expect event to be fired when selecting a different value', async () => { await userEvent.selectOptions(selectField(canvas), 'CustomDateRange'); await userEvent.click(canvas.getByText('CustomDateRange')); await waitFor(async () => { await expect(dateToPicker(canvas)).toHaveValue(customDateRange.dateTo); await expect(filterChangedListenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: { aDateColumnFrom: customDateRange.dateFrom, aDateColumnTo: customDateRange.dateTo, }, }), ); await expect(optionChangedListenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: customDateRange.label, }), ); }); }); }, }"
372
+ "default": "{ ...Default, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-filter'); const filterChangedListenerMock = fn(); const optionChangedListenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener(gsEventNames.dateRangeFilterChanged, filterChangedListenerMock); canvasElement.addEventListener(gsEventNames.dateRangeOptionChanged, optionChangedListenerMock); }); await step('Expect last 6 months to be selected', async () => { await waitFor(async () => { const placeholderOption = canvas.getByRole('combobox').querySelector('option:checked'); await expect(placeholderOption).toHaveTextContent('Last month'); }); await waitFor(async () => { await expect(dateToPicker(canvas)).toHaveValue(toYYYYMMDD(new Date())); }); }); await step('Expect event to be fired when selecting a different value', async () => { await userEvent.selectOptions(selectField(canvas), 'CustomDateRange'); await userEvent.click(canvas.getByText('CustomDateRange')); await waitFor(async () => { await expect(dateToPicker(canvas)).toHaveValue(customDateRange.dateTo); await expect(filterChangedListenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: { aDateColumnFrom: customDateRange.dateFrom, aDateColumnTo: customDateRange.dateTo, }, }), ); await expect(optionChangedListenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: customDateRange.label, }), ); }); }); }, }"
373
373
  }
374
374
  ],
375
375
  "exports": [
@@ -594,7 +594,7 @@
594
594
  "type": {
595
595
  "text": "Meta<Required<LineageFilterProps>>"
596
596
  },
597
- "default": "{ title: 'Input/Lineage filter', component: 'gs-lineage-filter', parameters: withComponentDocs({ actions: { handles: ['gs-lineage-filter-changed', ...previewHandles], }, fetchMock: { mocks: [ { matcher: { name: 'pangoLineage', url: AGGREGATED_ENDPOINT, body: { fields: ['pangoLineage'], country: 'Germany', }, }, response: { status: 200, body: aggregatedData, }, }, ], }, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), tags: ['autodocs'], argTypes: { lapisField: { control: { type: 'select', }, options: ['host'], }, placeholderText: { control: { type: 'text', }, }, value: { control: { type: 'text', }, }, width: { control: { type: 'text', }, }, lapisFilter: { control: { type: 'object', }, }, }, }"
597
+ "default": "{ title: 'Input/Lineage filter', component: 'gs-lineage-filter', parameters: withComponentDocs({ actions: { handles: [gsEventNames.lineageFilterChanged, ...previewHandles], }, fetchMock: { mocks: [ { matcher: { name: 'pangoLineage', url: AGGREGATED_ENDPOINT, body: { fields: ['pangoLineage'], country: 'Germany', }, }, response: { status: 200, body: aggregatedData, }, }, ], }, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), tags: ['autodocs'], argTypes: { lapisField: { control: { type: 'select', }, options: ['host'], }, placeholderText: { control: { type: 'text', }, }, value: { control: { type: 'text', }, }, width: { control: { type: 'text', }, }, lapisFilter: { control: { type: 'object', }, }, }, }"
598
598
  },
599
599
  {
600
600
  "kind": "variable",
@@ -626,7 +626,7 @@
626
626
  "type": {
627
627
  "text": "StoryObj<Required<LineageFilterProps>>"
628
628
  },
629
- "default": "{ ...LineageFilter, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter'); const inputField = () => canvas.getByPlaceholderText('Enter a lineage'); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener('gs-lineage-filter-changed', listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Enters an invalid lineage value', async () => { await userEvent.type(inputField(), 'notInList'); await expect(listenerMock).not.toHaveBeenCalled(); }); await step('Empty input', async () => { await userEvent.type(inputField(), '{backspace>9/}'); await userEvent.click(canvas.getByLabelText('toggle menu')); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ pangoLineage: undefined, }); }); }); await step('Enter a valid lineage value', async () => { await userEvent.type(inputField(), 'B.1.1.7*'); await userEvent.click(canvas.getByRole('option', { name: 'B.1.1.7*' })); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ pangoLineage: 'B.1.1.7*', }); }); }); }, args: { ...LineageFilter.args, value: '', }, }"
629
+ "default": "{ ...LineageFilter, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter'); const inputField = () => canvas.getByPlaceholderText('Enter a lineage'); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener(gsEventNames.lineageFilterChanged, listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Enters an invalid lineage value', async () => { await userEvent.type(inputField(), 'notInList'); await expect(listenerMock).not.toHaveBeenCalled(); }); await step('Empty input', async () => { await userEvent.type(inputField(), '{backspace>9/}'); await userEvent.click(canvas.getByLabelText('toggle menu')); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ pangoLineage: undefined, }); }); }); await step('Enter a valid lineage value', async () => { await userEvent.type(inputField(), 'B.1.1.7*'); await userEvent.click(canvas.getByRole('option', { name: 'B.1.1.7*' })); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ pangoLineage: 'B.1.1.7*', }); }); }); }, args: { ...LineageFilter.args, value: '', }, }"
630
630
  }
631
631
  ],
632
632
  "exports": [
@@ -825,7 +825,7 @@
825
825
  "type": {
826
826
  "text": "Meta"
827
827
  },
828
- "default": "{ title: 'Input/Location filter', component: 'gs-location-filter', parameters: withComponentDocs({ actions: { handles: ['gs-location-changed', ...previewHandles], }, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { fields: { control: { type: 'object', }, }, value: { control: { type: 'object', }, }, width: { control: { type: 'text', }, }, placeholderText: { control: { type: 'text', }, }, lapisFilter: { age: 18, }, }, tags: ['autodocs'], }"
828
+ "default": "{ title: 'Input/Location filter', component: 'gs-location-filter', parameters: withComponentDocs({ actions: { handles: [gsEventNames.locationChanged, ...previewHandles], }, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { fields: { control: { type: 'object', }, }, value: { control: { type: 'object', }, }, width: { control: { type: 'text', }, }, placeholderText: { control: { type: 'text', }, }, lapisFilter: { age: 18, }, }, tags: ['autodocs'], }"
829
829
  },
830
830
  {
831
831
  "kind": "variable",
@@ -857,7 +857,7 @@
857
857
  "type": {
858
858
  "text": "StoryObj<LocationFilterProps>"
859
859
  },
860
- "default": "{ ...Template, parameters: { fetchMock: { mocks: [ { matcher: aggregatedEndpointMatcher, response: { status: 200, body: data, }, }, ], }, }, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-location-filter'); const inputField = () => canvas.getByRole('combobox'); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener('gs-location-changed', listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Input invalid location', async () => { await userEvent.type(inputField(), 'Not / A / Location'); await expect(listenerMock).not.toHaveBeenCalled(); }); await step('Empty input', async () => { await userEvent.type(inputField(), '{backspace>18/}'); await userEvent.click(canvas.getByLabelText('toggle menu')); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ region: undefined, country: undefined, division: undefined, location: undefined, }); }); }); await step('Select Asia', async () => { await userEvent.type(inputField(), 'Asia'); await userEvent.click(canvas.getByRole('option', { name: /^Asia.*Asia$/ })); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ region: 'Asia', country: undefined, division: undefined, location: undefined, }); }); }); await step('Select Asia / Bangladesh / Rajshahi / Chapainawabgonj', async () => { await userEvent.type(inputField(), ' / Bangladesh / Rajshahi / Chapainawabgonj'); await userEvent.click(canvas.getByText('Asia / Bangladesh / Rajshahi / Chapainawabgonj')); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ region: 'Asia', country: 'Bangladesh', division: 'Rajshahi', location: 'Chapainawabgonj', }); }); }); }, }"
860
+ "default": "{ ...Template, parameters: { fetchMock: { mocks: [ { matcher: aggregatedEndpointMatcher, response: { status: 200, body: data, }, }, ], }, }, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-location-filter'); const inputField = () => canvas.getByRole('combobox'); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener(gsEventNames.locationChanged, listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Input invalid location', async () => { await userEvent.type(inputField(), 'Not / A / Location'); await expect(listenerMock).not.toHaveBeenCalled(); }); await step('Empty input', async () => { await userEvent.type(inputField(), '{backspace>18/}'); await userEvent.click(canvas.getByLabelText('toggle menu')); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ region: undefined, country: undefined, division: undefined, location: undefined, }); }); }); await step('Select Asia', async () => { await userEvent.type(inputField(), 'Asia'); await userEvent.click(canvas.getByRole('option', { name: /^Asia.*Asia$/ })); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ region: 'Asia', country: undefined, division: undefined, location: undefined, }); }); }); await step('Select Asia / Bangladesh / Rajshahi / Chapainawabgonj', async () => { await userEvent.type(inputField(), ' / Bangladesh / Rajshahi / Chapainawabgonj'); await userEvent.click(canvas.getByText('Asia / Bangladesh / Rajshahi / Chapainawabgonj')); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ region: 'Asia', country: 'Bangladesh', division: 'Rajshahi', location: 'Chapainawabgonj', }); }); }); }, }"
861
861
  }
862
862
  ],
863
863
  "exports": [
@@ -1056,7 +1056,7 @@
1056
1056
  "type": {
1057
1057
  "text": "Meta<MutationFilterProps>"
1058
1058
  },
1059
- "default": "{ title: 'Input/Mutation filter', component: 'gs-mutation-filter', parameters: withComponentDocs({ actions: { handles: ['gs-mutation-filter-changed', ...previewHandles], }, fetchMock: {}, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { initialValue: { control: { type: 'object', }, }, width: { control: 'text' }, }, tags: ['autodocs'], }"
1059
+ "default": "{ title: 'Input/Mutation filter', component: 'gs-mutation-filter', parameters: withComponentDocs({ actions: { handles: [gsEventNames.mutationFilterChanged, ...previewHandles], }, fetchMock: {}, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { initialValue: { control: { type: 'object', }, }, width: { control: 'text' }, }, tags: ['autodocs'], }"
1060
1060
  },
1061
1061
  {
1062
1062
  "kind": "variable",
@@ -1072,7 +1072,7 @@
1072
1072
  "type": {
1073
1073
  "text": "StoryObj<MutationFilterProps>"
1074
1074
  },
1075
- "default": "{ ...Template, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter'); const inputField = () => canvas.getByPlaceholderText('Enter a mutation', { exact: false }); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener('gs-mutation-filter-changed', listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Enter a valid mutation', async () => { await userEvent.type(inputField(), 'A123T'); const option = await canvas.findByRole('option'); await userEvent.click(option); await waitFor(() => expect(listenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: { nucleotideMutations: ['A123T'], aminoAcidMutations: [], nucleotideInsertions: [], aminoAcidInsertions: [], }, }), ), ); }); }, }"
1075
+ "default": "{ ...Template, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter'); const inputField = () => canvas.getByPlaceholderText('Enter a mutation', { exact: false }); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener(gsEventNames.mutationFilterChanged, listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Enter a valid mutation', async () => { await userEvent.type(inputField(), 'A123T'); const option = await canvas.findByRole('option'); await userEvent.click(option); await waitFor(() => expect(listenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: { nucleotideMutations: ['A123T'], aminoAcidMutations: [], nucleotideInsertions: [], aminoAcidInsertions: [], }, }), ), ); }); }, }"
1076
1076
  },
1077
1077
  {
1078
1078
  "kind": "variable",
@@ -1214,7 +1214,7 @@
1214
1214
  "type": {
1215
1215
  "text": "Meta<Required<TextFilterProps>>"
1216
1216
  },
1217
- "default": "{ title: 'Input/Text filter', component: 'gs-text-filter', parameters: withComponentDocs({ actions: { handles: ['gs-text-filter-changed', ...previewHandles], }, fetchMock: { mocks: [ { matcher: { name: 'hosts', url: AGGREGATED_ENDPOINT, body: { fields: ['host'], country: 'Germany', }, }, response: { status: 200, body: data, }, }, ], }, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { lapisField: { control: { type: 'text', }, }, placeholderText: { control: { type: 'text', }, }, value: { control: { type: 'text', }, }, width: { control: { type: 'text', }, }, lapisFilter: { control: { type: 'object', }, }, }, tags: ['autodocs'], }"
1217
+ "default": "{ title: 'Input/Text filter', component: 'gs-text-filter', parameters: withComponentDocs({ actions: { handles: [gsEventNames.textFilterChanged, ...previewHandles], }, fetchMock: { mocks: [ { matcher: { name: 'hosts', url: AGGREGATED_ENDPOINT, body: { fields: ['host'], country: 'Germany', }, }, response: { status: 200, body: data, }, }, ], }, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), argTypes: { lapisField: { control: { type: 'text', }, }, placeholderText: { control: { type: 'text', }, }, value: { control: { type: 'text', }, }, width: { control: { type: 'text', }, }, lapisFilter: { control: { type: 'object', }, }, }, tags: ['autodocs'], }"
1218
1218
  },
1219
1219
  {
1220
1220
  "kind": "variable",
@@ -1230,7 +1230,7 @@
1230
1230
  "type": {
1231
1231
  "text": "StoryObj<Required<TextFilterProps>>"
1232
1232
  },
1233
- "default": "{ ...Default, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-text-filter'); const inputField = () => canvas.getByPlaceholderText('Enter host name'); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener('gs-text-filter-changed', listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Enters an invalid host name', async () => { await userEvent.type(inputField(), 'notInList'); await expect(listenerMock).not.toHaveBeenCalled(); }); await step('Empty input', async () => { await userEvent.type(inputField(), '{backspace>9/}'); }); await step('Enter a valid host name', async () => { await userEvent.type(inputField(), 'Homo s'); const dropdownOption = await canvas.findByText('Homo sapiens'); await userEvent.click(dropdownOption); }); await step('Verify event is fired with correct detail', async () => { await waitFor(async () => { await expect(listenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: { host: 'Homo sapiens', }, }), ); }); }); await step('Remove initial value', async () => { await userEvent.click(canvas.getByRole('button', { name: 'clear selection' })); await expect(listenerMock).toHaveBeenLastCalledWith( expect.objectContaining({ detail: { host: undefined, }, }), ); }); }, args: { ...Default.args, value: '', }, }"
1233
+ "default": "{ ...Default, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-text-filter'); const inputField = () => canvas.getByPlaceholderText('Enter host name'); const listenerMock = fn(); await step('Setup event listener mock', () => { canvasElement.addEventListener(gsEventNames.textFilterChanged, listenerMock); }); await step('wait until data is loaded', async () => { await waitFor(() => { return expect(inputField()).toBeEnabled(); }); }); await step('Enters an invalid host name', async () => { await userEvent.type(inputField(), 'notInList'); await expect(listenerMock).not.toHaveBeenCalled(); }); await step('Empty input', async () => { await userEvent.type(inputField(), '{backspace>9/}'); }); await step('Enter a valid host name', async () => { await userEvent.type(inputField(), 'Homo s'); const dropdownOption = await canvas.findByText('Homo sapiens'); await userEvent.click(dropdownOption); }); await step('Verify event is fired with correct detail', async () => { await waitFor(async () => { await expect(listenerMock).toHaveBeenCalledWith( expect.objectContaining({ detail: { host: 'Homo sapiens', }, }), ); }); }); await step('Remove initial value', async () => { await userEvent.click(canvas.getByRole('button', { name: 'clear selection' })); await expect(listenerMock).toHaveBeenLastCalledWith( expect.objectContaining({ detail: { host: undefined, }, }), ); }); }, args: { ...Default.args, value: '', }, }"
1234
1234
  }
1235
1235
  ],
1236
1236
  "exports": [
@@ -1838,6 +1838,148 @@
1838
1838
  }
1839
1839
  ]
1840
1840
  },
1841
+ {
1842
+ "kind": "javascript-module",
1843
+ "path": "src/web-components/visualization/gs-genome-data-viewer.spec-d.ts",
1844
+ "declarations": [],
1845
+ "exports": []
1846
+ },
1847
+ {
1848
+ "kind": "javascript-module",
1849
+ "path": "src/web-components/visualization/gs-genome-data-viewer.stories.ts",
1850
+ "declarations": [
1851
+ {
1852
+ "kind": "variable",
1853
+ "name": "meta",
1854
+ "type": {
1855
+ "text": "Meta<Required<GenomeDataViewerProps>>"
1856
+ },
1857
+ "default": "{ title: 'Visualization/Genome Data Viewer', component: 'gs-genome-data-viewer', argTypes: { gff3Source: { control: 'text' }, genomeLength: { control: 'number' }, width: { control: 'text' }, }, parameters: withComponentDocs({ componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), tags: ['autodocs'], }"
1858
+ },
1859
+ {
1860
+ "kind": "variable",
1861
+ "name": "Default",
1862
+ "type": {
1863
+ "text": "StoryObj<Required<GenomeDataViewerProps>>"
1864
+ },
1865
+ "default": "{ ...Template, args: { genomeLength: 11029, gff3Source: 'https://gff3Url', width: '100%', }, parameters: { fetchMock: { mocks: [ { matcher: { name: 'gff3Data', url: 'https://gff3Url', }, response: { status: 200, body: SimpleData, headers: { 'Content-Type': 'text/plain', }, }, }, ], }, }, play: async ({ canvasElement, step }) => { const canvas = await withinShadowRoot(canvasElement, 'gs-genome-data-viewer'); await step('CDS visible', async () => { const ns1NameMatches = await canvas.findAllByText('NS1'); await waitFor(async () => expect(ns1NameMatches[0]).toBeVisible()); const xAxisTick = await canvas.findAllByText('1000'); await waitFor(async () => expect(xAxisTick[0]).toBeVisible()); await waitFor(async () => expect(xAxisTick.length).toBe(2)); }); }, }"
1866
+ }
1867
+ ],
1868
+ "exports": [
1869
+ {
1870
+ "kind": "js",
1871
+ "name": "default",
1872
+ "declaration": {
1873
+ "name": "meta",
1874
+ "module": "src/web-components/visualization/gs-genome-data-viewer.stories.ts"
1875
+ }
1876
+ },
1877
+ {
1878
+ "kind": "js",
1879
+ "name": "Default",
1880
+ "declaration": {
1881
+ "name": "Default",
1882
+ "module": "src/web-components/visualization/gs-genome-data-viewer.stories.ts"
1883
+ }
1884
+ }
1885
+ ]
1886
+ },
1887
+ {
1888
+ "kind": "javascript-module",
1889
+ "path": "src/web-components/visualization/gs-genome-data-viewer.tsx",
1890
+ "declarations": [
1891
+ {
1892
+ "kind": "class",
1893
+ "description": "## Context\n\nThis component shows the Coding Sequence (CDS) of a genome using a gff3 file as input.\nThe CDS shows which parts of the genome are translated into proteins.",
1894
+ "name": "GenomeDataViewerComponent",
1895
+ "members": [
1896
+ {
1897
+ "kind": "field",
1898
+ "name": "gff3Source",
1899
+ "type": {
1900
+ "text": "string"
1901
+ },
1902
+ "default": "''",
1903
+ "description": "Required\n\nThe source of the gff3 file. Any spec-compliant gff3 should be accepted, however we use the same format as Nextclade.\nSee https://docs.nextstrain.org/projects/nextclade/en/stable/user/input-files/03-genome-annotation.html for more information.\nWe only use the CDS and gene features from the gff3 file, if you have other features in the gff3 file they will be ignored.\nAlso note that if a CDS has a gene feature as a parent, the gene feature will be ignored.",
1904
+ "attribute": "gff3Source"
1905
+ },
1906
+ {
1907
+ "kind": "field",
1908
+ "name": "genomeLength",
1909
+ "type": {
1910
+ "text": "number | undefined"
1911
+ },
1912
+ "default": "0",
1913
+ "description": "The length of the genome, if this is not given it will be computed from the `sequence-region` line of the start of the gff3 file.",
1914
+ "attribute": "genomeLength"
1915
+ },
1916
+ {
1917
+ "kind": "field",
1918
+ "name": "width",
1919
+ "type": {
1920
+ "text": "string"
1921
+ },
1922
+ "default": "'100%'",
1923
+ "description": "The width of the component.\n\nVisit https://genspectrum.github.io/dashboard-components/?path=/docs/concepts-size-of-components--docs for more information.",
1924
+ "attribute": "width"
1925
+ }
1926
+ ],
1927
+ "attributes": [
1928
+ {
1929
+ "name": "gff3Source",
1930
+ "type": {
1931
+ "text": "string"
1932
+ },
1933
+ "default": "''",
1934
+ "description": "Required\n\nThe source of the gff3 file. Any spec-compliant gff3 should be accepted, however we use the same format as Nextclade.\nSee https://docs.nextstrain.org/projects/nextclade/en/stable/user/input-files/03-genome-annotation.html for more information.\nWe only use the CDS and gene features from the gff3 file, if you have other features in the gff3 file they will be ignored.\nAlso note that if a CDS has a gene feature as a parent, the gene feature will be ignored.",
1935
+ "fieldName": "gff3Source"
1936
+ },
1937
+ {
1938
+ "name": "genomeLength",
1939
+ "type": {
1940
+ "text": "number | undefined"
1941
+ },
1942
+ "default": "0",
1943
+ "description": "The length of the genome, if this is not given it will be computed from the `sequence-region` line of the start of the gff3 file.",
1944
+ "fieldName": "genomeLength"
1945
+ },
1946
+ {
1947
+ "name": "width",
1948
+ "type": {
1949
+ "text": "string"
1950
+ },
1951
+ "default": "'100%'",
1952
+ "description": "The width of the component.\n\nVisit https://genspectrum.github.io/dashboard-components/?path=/docs/concepts-size-of-components--docs for more information.",
1953
+ "fieldName": "width"
1954
+ }
1955
+ ],
1956
+ "superclass": {
1957
+ "name": "PreactLitAdapter",
1958
+ "module": "/src/web-components/PreactLitAdapter"
1959
+ },
1960
+ "tagName": "gs-genome-data-viewer",
1961
+ "customElement": true
1962
+ }
1963
+ ],
1964
+ "exports": [
1965
+ {
1966
+ "kind": "js",
1967
+ "name": "GenomeDataViewerComponent",
1968
+ "declaration": {
1969
+ "name": "GenomeDataViewerComponent",
1970
+ "module": "src/web-components/visualization/gs-genome-data-viewer.tsx"
1971
+ }
1972
+ },
1973
+ {
1974
+ "kind": "custom-element-definition",
1975
+ "name": "gs-genome-data-viewer",
1976
+ "declaration": {
1977
+ "name": "GenomeDataViewerComponent",
1978
+ "module": "src/web-components/visualization/gs-genome-data-viewer.tsx"
1979
+ }
1980
+ }
1981
+ ]
1982
+ },
1841
1983
  {
1842
1984
  "kind": "javascript-module",
1843
1985
  "path": "src/web-components/visualization/gs-mutation-comparison.stories.ts",
@@ -4170,6 +4312,14 @@
4170
4312
  "path": "src/web-components/visualization/index.ts",
4171
4313
  "declarations": [],
4172
4314
  "exports": [
4315
+ {
4316
+ "kind": "js",
4317
+ "name": "GenomeDataViewerComponent",
4318
+ "declaration": {
4319
+ "name": "GenomeDataViewerComponent",
4320
+ "module": "./gs-genome-data-viewer"
4321
+ }
4322
+ },
4173
4323
  {
4174
4324
  "kind": "js",
4175
4325
  "name": "MutationComparisonComponent",
@@ -1,4 +1,13 @@
1
1
  import z from "zod";
2
+ const gsEventNames = {
3
+ error: "gs-error",
4
+ dateRangeFilterChanged: "gs-date-range-filter-changed",
5
+ dateRangeOptionChanged: "gs-date-range-option-changed",
6
+ mutationFilterChanged: "gs-mutation-filter-changed",
7
+ lineageFilterChanged: "gs-lineage-filter-changed",
8
+ locationChanged: "gs-location-changed",
9
+ textFilterChanged: "gs-text-filter-changed"
10
+ };
2
11
  const mutationsFilterSchema = z.object({
3
12
  nucleotideMutations: z.array(z.string()),
4
13
  aminoAcidMutations: z.array(z.string()),
@@ -55,7 +64,7 @@ const dateRangeValueSchema = z.union([
55
64
  ]).nullable();
56
65
  class DateRangeOptionChangedEvent extends CustomEvent {
57
66
  constructor(detail) {
58
- super("gs-date-range-option-changed", {
67
+ super(gsEventNames.dateRangeOptionChanged, {
59
68
  detail,
60
69
  bubbles: true,
61
70
  composed: true
@@ -106,7 +115,7 @@ const dateRangeOptionPresets = {
106
115
  };
107
116
  class LocationChangedEvent extends CustomEvent {
108
117
  constructor(detail) {
109
- super("gs-location-changed", {
118
+ super(gsEventNames.locationChanged, {
110
119
  detail,
111
120
  bubbles: true,
112
121
  composed: true
@@ -115,7 +124,7 @@ class LocationChangedEvent extends CustomEvent {
115
124
  }
116
125
  class TextFilterChangedEvent extends CustomEvent {
117
126
  constructor(detail) {
118
- super("gs-text-filter-changed", {
127
+ super(gsEventNames.textFilterChanged, {
119
128
  detail,
120
129
  bubbles: true,
121
130
  composed: true
@@ -124,7 +133,7 @@ class TextFilterChangedEvent extends CustomEvent {
124
133
  }
125
134
  class LineageFilterChangedEvent extends CustomEvent {
126
135
  constructor(detail) {
127
- super("gs-lineage-filter-changed", {
136
+ super(gsEventNames.lineageFilterChanged, {
128
137
  detail,
129
138
  bubbles: true,
130
139
  composed: true
@@ -141,6 +150,7 @@ export {
141
150
  dateRangeOptionPresets as d,
142
151
  toYYYYMMDD as e,
143
152
  lapisLocationFilterSchema as f,
153
+ gsEventNames as g,
144
154
  lapisFilterSchema as l,
145
155
  mutationsFilterSchema as m,
146
156
  namedLapisFilterSchema as n,
@@ -148,4 +158,4 @@ export {
148
158
  temporalGranularitySchema as t,
149
159
  views as v
150
160
  };
151
- //# sourceMappingURL=LineageFilterChangedEvent-ixHQkq8y.js.map
161
+ //# sourceMappingURL=LineageFilterChangedEvent-b0iuroUL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LineageFilterChangedEvent-b0iuroUL.js","sources":["../src/utils/gsEventNames.ts","../src/types.ts","../src/preact/dateRangeFilter/dateConversion.ts","../src/preact/dateRangeFilter/dateRangeOption.ts","../src/preact/locationFilter/LocationChangedEvent.ts","../src/preact/textFilter/TextFilterChangedEvent.ts","../src/preact/lineageFilter/LineageFilterChangedEvent.ts"],"sourcesContent":["export const gsEventNames = {\n error: 'gs-error',\n dateRangeFilterChanged: 'gs-date-range-filter-changed',\n dateRangeOptionChanged: 'gs-date-range-option-changed',\n mutationFilterChanged: 'gs-mutation-filter-changed',\n lineageFilterChanged: 'gs-lineage-filter-changed',\n locationChanged: 'gs-location-changed',\n textFilterChanged: 'gs-text-filter-changed',\n} as const;\n","import z from 'zod';\n\nimport {\n type Deletion,\n type DeletionClass,\n type Insertion,\n type InsertionClass,\n type Substitution,\n type SubstitutionClass,\n} from './utils/mutations';\n\nexport const mutationsFilterSchema = z.object({\n nucleotideMutations: z.array(z.string()),\n aminoAcidMutations: z.array(z.string()),\n nucleotideInsertions: z.array(z.string()),\n aminoAcidInsertions: z.array(z.string()),\n});\nexport type MutationsFilter = z.infer<typeof mutationsFilterSchema>;\n\nexport const lapisFilterSchema = z\n .record(z.union([z.string(), z.array(z.string()), z.number(), z.null(), z.boolean(), z.undefined()]))\n .and(mutationsFilterSchema.partial());\nexport type LapisFilter = z.infer<typeof lapisFilterSchema>;\n\nexport const namedLapisFilterSchema = z.object({\n lapisFilter: lapisFilterSchema,\n displayName: z.string(),\n});\nexport type NamedLapisFilter = z.infer<typeof namedLapisFilterSchema>;\n\nexport const lapisLocationFilterSchema = z.record(z.union([z.string(), z.undefined()]));\nexport type LapisLocationFilter = z.infer<typeof lapisLocationFilterSchema>;\n\nexport const temporalGranularitySchema = z.union([\n z.literal('day'),\n z.literal('week'),\n z.literal('month'),\n z.literal('year'),\n]);\nexport type TemporalGranularity = z.infer<typeof temporalGranularitySchema>;\n\nexport const sequenceTypeSchema = z.union([z.literal('nucleotide'), z.literal('amino acid')]);\nexport type SequenceType = z.infer<typeof sequenceTypeSchema>;\n\nexport type SubstitutionOrDeletion = 'substitution' | 'deletion';\n\nexport type MutationType = SubstitutionOrDeletion | 'insertion';\n\nexport type SubstitutionEntry<T extends Substitution = SubstitutionClass> = {\n type: 'substitution';\n mutation: T;\n count: number;\n proportion: number;\n};\n\nexport type DeletionEntry<T extends Deletion = DeletionClass> = {\n type: 'deletion';\n mutation: T;\n count: number;\n proportion: number;\n};\n\nexport type InsertionEntry<T extends Insertion = InsertionClass> = { type: 'insertion'; mutation: T; count: number };\n\nexport type SubstitutionOrDeletionEntry<\n S extends Substitution = SubstitutionClass,\n D extends Deletion = DeletionClass,\n> = SubstitutionEntry<S> | DeletionEntry<D>;\n\nexport type MutationEntry = SubstitutionEntry | DeletionEntry | InsertionEntry;\n\nexport const views = {\n table: 'table',\n venn: 'venn',\n grid: 'grid',\n insertions: 'insertions',\n bar: 'bar',\n line: 'line',\n bubble: 'bubble',\n map: 'map',\n} as const;\n","export const toYYYYMMDD = (date: Date) => {\n const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: '2-digit', day: '2-digit' };\n return date.toLocaleDateString('en-CA', options);\n};\n","import z from 'zod';\n\nimport { toYYYYMMDD } from './dateConversion';\nimport { gsEventNames } from '../../utils/gsEventNames';\n\n/**\n * A date range option that can be used in the `gs-date-range-filter` component.\n */\nexport const dateRangeOptionSchema = z.object({\n /** The label of the date range option that will be shown to the user */\n label: z.string(),\n /**\n * The start date of the date range in the format `YYYY-MM-DD`.\n * If not set, the date range selector will default to the `earliestDate` property.\n */\n dateFrom: z.string().date().optional(),\n /**\n * The end date of the date range in the format `YYYY-MM-DD`.\n * If not set, the date range selector will default to the current date.\n */\n dateTo: z.string().date().optional(),\n});\n\nexport type DateRangeOption = z.infer<typeof dateRangeOptionSchema>;\n\nexport const dateRangeValueSchema = z\n .union([\n z.string(),\n z.object({\n dateFrom: z.string().date().optional(),\n dateTo: z.string().date().optional(),\n }),\n ])\n .nullable();\n\nexport type DateRangeValue = z.infer<typeof dateRangeValueSchema>;\n\nexport class DateRangeOptionChangedEvent extends CustomEvent<DateRangeValue> {\n constructor(detail: DateRangeValue) {\n super(gsEventNames.dateRangeOptionChanged, {\n detail,\n bubbles: true,\n composed: true,\n });\n }\n}\n\nconst today = new Date();\n\nconst twoWeeksAgo = new Date();\ntwoWeeksAgo.setDate(today.getDate() - 14);\n\nconst lastMonth = new Date(today);\nlastMonth.setMonth(today.getMonth() - 1);\n\nconst last2Months = new Date(today);\nlast2Months.setMonth(today.getMonth() - 2);\n\nconst last3Months = new Date(today);\nlast3Months.setMonth(today.getMonth() - 3);\n\nconst last6Months = new Date(today);\nlast6Months.setMonth(today.getMonth() - 6);\n\nconst lastYear = new Date(today);\nlastYear.setFullYear(today.getFullYear() - 1);\n\n/**\n * Presets for the `gs-date-range-filter` component that can be used as `dateRangeOptions`.\n */\nexport const dateRangeOptionPresets = {\n last2Weeks: {\n label: 'Last 2 weeks',\n dateFrom: toYYYYMMDD(twoWeeksAgo),\n },\n lastMonth: {\n label: 'Last month',\n dateFrom: toYYYYMMDD(lastMonth),\n },\n last2Months: {\n label: 'Last 2 months',\n dateFrom: toYYYYMMDD(last2Months),\n },\n last3Months: {\n label: 'Last 3 months',\n dateFrom: toYYYYMMDD(last3Months),\n },\n last6Months: {\n label: 'Last 6 months',\n dateFrom: toYYYYMMDD(last6Months),\n },\n lastYear: {\n label: 'Last year',\n dateFrom: toYYYYMMDD(lastYear),\n },\n allTimes: {\n label: 'All times',\n },\n} satisfies Record<string, DateRangeOption>;\n","import { type LapisLocationFilter } from '../../types';\nimport { gsEventNames } from '../../utils/gsEventNames';\n\nexport class LocationChangedEvent extends CustomEvent<LapisLocationFilter> {\n constructor(detail: LapisLocationFilter) {\n super(gsEventNames.locationChanged, {\n detail,\n bubbles: true,\n composed: true,\n });\n }\n}\n","import { gsEventNames } from '../../utils/gsEventNames';\n\ntype LapisTextFilter = Record<string, string | undefined>;\n\nexport class TextFilterChangedEvent extends CustomEvent<LapisTextFilter> {\n constructor(detail: LapisTextFilter) {\n super(gsEventNames.textFilterChanged, {\n detail,\n bubbles: true,\n composed: true,\n });\n }\n}\n","import { gsEventNames } from '../../utils/gsEventNames';\n\ntype LapisLineageFilter = Record<string, string | undefined>;\n\nexport class LineageFilterChangedEvent extends CustomEvent<LapisLineageFilter> {\n constructor(detail: LapisLineageFilter) {\n super(gsEventNames.lineageFilterChanged, {\n detail,\n bubbles: true,\n composed: true,\n });\n }\n}\n"],"names":[],"mappings":"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"}