@genspectrum/dashboard-components 0.19.2 → 0.19.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 (61) hide show
  1. package/custom-elements.json +383 -10
  2. package/dist/{LineageFilterChangedEvent-ixHQkq8y.js → LineageFilterChangedEvent-GgkxoF3X.js} +17 -5
  3. package/dist/LineageFilterChangedEvent-GgkxoF3X.js.map +1 -0
  4. package/dist/assets/mutationOverTimeWorker-ChQTFL68.js.map +1 -1
  5. package/dist/components.d.ts +184 -21
  6. package/dist/components.js +9352 -8683
  7. package/dist/components.js.map +1 -1
  8. package/dist/util.d.ts +69 -21
  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/min-max-range-slider.tsx +19 -4
  15. package/src/preact/components/resize-container.tsx +7 -10
  16. package/src/preact/components/tooltip.tsx +7 -4
  17. package/src/preact/dateRangeFilter/date-range-filter.stories.tsx +9 -5
  18. package/src/preact/dateRangeFilter/date-range-filter.tsx +2 -1
  19. package/src/preact/dateRangeFilter/dateRangeOption.ts +2 -1
  20. package/src/preact/genomeViewer/CDSPlot.tsx +219 -0
  21. package/src/preact/genomeViewer/genome-data-viewer.stories.tsx +113 -0
  22. package/src/preact/genomeViewer/genome-data-viewer.tsx +69 -0
  23. package/src/preact/genomeViewer/loadGff3.spec.ts +61 -0
  24. package/src/preact/genomeViewer/loadGff3.ts +180 -0
  25. package/src/preact/lineageFilter/LineageFilterChangedEvent.ts +3 -1
  26. package/src/preact/lineageFilter/lineage-filter.stories.tsx +3 -2
  27. package/src/preact/locationFilter/LocationChangedEvent.ts +2 -1
  28. package/src/preact/locationFilter/location-filter.stories.tsx +3 -2
  29. package/src/preact/mutationFilter/mutation-filter.stories.tsx +3 -2
  30. package/src/preact/mutationFilter/mutation-filter.tsx +2 -1
  31. package/src/preact/numberRangeFilter/NumberRangeFilterChangedEvent.ts +31 -0
  32. package/src/preact/numberRangeFilter/number-range-filter.stories.tsx +383 -0
  33. package/src/preact/numberRangeFilter/number-range-filter.tsx +159 -0
  34. package/src/preact/numberRangeFilter/useSelectedRangeReducer.ts +137 -0
  35. package/src/preact/shared/charts/colors.ts +1 -1
  36. package/src/preact/textFilter/TextFilterChangedEvent.ts +3 -1
  37. package/src/preact/textFilter/text-filter.stories.tsx +4 -3
  38. package/src/utilEntrypoint.ts +2 -0
  39. package/src/utils/gsEventNames.ts +11 -0
  40. package/src/web-components/input/gs-date-range-filter.stories.ts +4 -3
  41. package/src/web-components/input/gs-date-range-filter.tsx +3 -2
  42. package/src/web-components/input/gs-lineage-filter.stories.ts +3 -2
  43. package/src/web-components/input/gs-lineage-filter.tsx +2 -1
  44. package/src/web-components/input/gs-location-filter.stories.ts +3 -2
  45. package/src/web-components/input/gs-location-filter.tsx +2 -1
  46. package/src/web-components/input/gs-mutation-filter.stories.ts +3 -2
  47. package/src/web-components/input/gs-mutation-filter.tsx +2 -1
  48. package/src/web-components/input/gs-number-range-filter.spec.ts +27 -0
  49. package/src/web-components/input/gs-number-range-filter.stories.ts +96 -0
  50. package/src/web-components/input/gs-number-range-filter.tsx +148 -0
  51. package/src/web-components/input/gs-text-filter.stories.ts +5 -4
  52. package/src/web-components/input/gs-text-filter.tsx +2 -1
  53. package/src/web-components/input/index.ts +1 -0
  54. package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +18 -0
  55. package/src/web-components/visualization/gs-genome-data-viewer.stories.ts +108 -0
  56. package/src/web-components/visualization/gs-genome-data-viewer.tsx +59 -0
  57. package/src/web-components/visualization/index.ts +1 -0
  58. package/standalone-bundle/assets/mutationOverTimeWorker-jChgWnwp.js.map +1 -1
  59. package/standalone-bundle/dashboard-components.js +9613 -9059
  60. package/standalone-bundle/dashboard-components.js.map +1 -1
  61. 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",
@@ -1204,6 +1204,221 @@
1204
1204
  }
1205
1205
  ]
1206
1206
  },
1207
+ {
1208
+ "kind": "javascript-module",
1209
+ "path": "src/web-components/input/gs-number-range-filter.spec.ts",
1210
+ "declarations": [],
1211
+ "exports": []
1212
+ },
1213
+ {
1214
+ "kind": "javascript-module",
1215
+ "path": "src/web-components/input/gs-number-range-filter.stories.ts",
1216
+ "declarations": [
1217
+ {
1218
+ "kind": "variable",
1219
+ "name": "meta",
1220
+ "type": {
1221
+ "text": "Meta<NumberRangeFilterProps>"
1222
+ },
1223
+ "default": "{ title: 'Input/Number range filter', component: 'gs-number-range-filter', parameters: withComponentDocs({ actions: { handles: [gsEventNames.numberRangeFilterChanged, gsEventNames.numberRangeValueChanged], }, componentDocs: { opensShadowDom: true, expectsChildren: false, codeExample, }, }), tags: ['autodocs'], argTypes: { value: { control: { type: 'object', }, }, lapisField: { control: { type: 'text', }, }, sliderMin: { control: { type: 'number', }, }, sliderMax: { control: { type: 'number', }, }, sliderStep: { control: { type: 'number', }, }, width: { control: { type: 'text', }, }, }, }"
1224
+ },
1225
+ {
1226
+ "kind": "variable",
1227
+ "name": "Default",
1228
+ "type": {
1229
+ "text": "StoryObj<NumberRangeFilterProps>"
1230
+ },
1231
+ "default": "{ render: (args) => { return html` <gs-app lapis=\"${LAPIS_URL}\"> <gs-number-range-filter .value=${args.value} .lapisField=${args.lapisField} .sliderMin=${args.sliderMin} .sliderMax=${args.sliderMax} .sliderStep=${args.sliderStep} .width=${args.width} > </gs-number-range-filter> </gs-app> `; }, args: { lapisField: 'age', value: { min: 10, max: 90 }, sliderMin: 0, sliderMax: 100, sliderStep: 0.1, width: '100%', }, }"
1232
+ }
1233
+ ],
1234
+ "exports": [
1235
+ {
1236
+ "kind": "js",
1237
+ "name": "default",
1238
+ "declaration": {
1239
+ "name": "meta",
1240
+ "module": "src/web-components/input/gs-number-range-filter.stories.ts"
1241
+ }
1242
+ },
1243
+ {
1244
+ "kind": "js",
1245
+ "name": "Default",
1246
+ "declaration": {
1247
+ "name": "Default",
1248
+ "module": "src/web-components/input/gs-number-range-filter.stories.ts"
1249
+ }
1250
+ }
1251
+ ]
1252
+ },
1253
+ {
1254
+ "kind": "javascript-module",
1255
+ "path": "src/web-components/input/gs-number-range-filter.tsx",
1256
+ "declarations": [
1257
+ {
1258
+ "kind": "class",
1259
+ "description": "\n## Context\n\nThis component lets the user specify filters for a `float` or `int` field in LAPIS.\nIt consists of two text input fields and a slider.\n\nThe slider must be restricted to a certain range.\nUsers can however still enter values outside of this range in the text input fields.",
1260
+ "name": "NumberRangeFilterComponent",
1261
+ "members": [
1262
+ {
1263
+ "kind": "field",
1264
+ "name": "value",
1265
+ "type": {
1266
+ "text": "{ min?: number; max?: number }"
1267
+ },
1268
+ "default": "{}",
1269
+ "description": "The value to use for this number filter.\n\nMust be of the form:\n```\n{\n [`${lapisField}From`]: number | undefined\n [`${lapisField}To`]: number | undefined\n}\n```\n\nThis is the same format that the `gs-number-value-changed` event will emit.",
1270
+ "attribute": "value"
1271
+ },
1272
+ {
1273
+ "kind": "field",
1274
+ "name": "lapisField",
1275
+ "type": {
1276
+ "text": "string"
1277
+ },
1278
+ "default": "''",
1279
+ "description": "Required.\n\nThe LAPIS field name to use for this text filter.\nThe field must exist on this LAPIS instance.",
1280
+ "attribute": "lapisField"
1281
+ },
1282
+ {
1283
+ "kind": "field",
1284
+ "name": "sliderMin",
1285
+ "type": {
1286
+ "text": "number"
1287
+ },
1288
+ "default": "0",
1289
+ "description": "The `min` value to use for the slider.",
1290
+ "attribute": "sliderMin"
1291
+ },
1292
+ {
1293
+ "kind": "field",
1294
+ "name": "sliderMax",
1295
+ "type": {
1296
+ "text": "number"
1297
+ },
1298
+ "default": "100",
1299
+ "description": "The `max` value to use for the slider.",
1300
+ "attribute": "sliderMax"
1301
+ },
1302
+ {
1303
+ "kind": "field",
1304
+ "name": "sliderStep",
1305
+ "type": {
1306
+ "text": "number"
1307
+ },
1308
+ "default": "1",
1309
+ "description": "The `step` value to use for the slider.\n\nThis attribute has no effect on the text input.",
1310
+ "attribute": "sliderStep"
1311
+ },
1312
+ {
1313
+ "kind": "field",
1314
+ "name": "width",
1315
+ "type": {
1316
+ "text": "string"
1317
+ },
1318
+ "default": "'100%'",
1319
+ "description": "The width of the component.\n\nVisit https://genspectrum.github.io/dashboard-components/?path=/docs/concepts-size-of-components--docs for more information.",
1320
+ "attribute": "width"
1321
+ }
1322
+ ],
1323
+ "events": [
1324
+ {
1325
+ "type": {
1326
+ "text": "CustomEvent<Record<string, string | undefined>>"
1327
+ },
1328
+ "description": "Fired when the slider is released, `onBlur` on the input fields after the user has typed a valid range in the input fields, or when one of the input fields is cleared. The `details` of this event contain an object with `${lapisField}From` and `${lapisField}To` as keys. The values are the numbers from the input fields or `undefined` if the input field is empty: ``` { [`${lapisField}From`]: number | undefined [`${lapisField}To`]: number | undefined } ``` Example: ``` { ageFrom: 18, ageTo: undefined } ```",
1329
+ "name": "gs-number-range-filter-changed"
1330
+ },
1331
+ {
1332
+ "type": {
1333
+ "text": "CustomEvent<Record<string, string | undefined>>"
1334
+ },
1335
+ "description": "Similar to the `gs-number-range-filter-changed` event, but contains an `event.detail` that has a fixed format: ``` { min: number | undefined max: number | undefined } ``` This event should be used when you want to control this component externally. The `event.detail` can be used as the value of the component. Example: ``` { min: 18, max: undefined } ```",
1336
+ "name": "gs-number-range-value-changed"
1337
+ }
1338
+ ],
1339
+ "attributes": [
1340
+ {
1341
+ "name": "value",
1342
+ "type": {
1343
+ "text": "{ min?: number; max?: number }"
1344
+ },
1345
+ "default": "{}",
1346
+ "description": "The value to use for this number filter.\n\nMust be of the form:\n```\n{\n [`${lapisField}From`]: number | undefined\n [`${lapisField}To`]: number | undefined\n}\n```\n\nThis is the same format that the `gs-number-value-changed` event will emit.",
1347
+ "fieldName": "value"
1348
+ },
1349
+ {
1350
+ "name": "lapisField",
1351
+ "type": {
1352
+ "text": "string"
1353
+ },
1354
+ "default": "''",
1355
+ "description": "Required.\n\nThe LAPIS field name to use for this text filter.\nThe field must exist on this LAPIS instance.",
1356
+ "fieldName": "lapisField"
1357
+ },
1358
+ {
1359
+ "name": "sliderMin",
1360
+ "type": {
1361
+ "text": "number"
1362
+ },
1363
+ "default": "0",
1364
+ "description": "The `min` value to use for the slider.",
1365
+ "fieldName": "sliderMin"
1366
+ },
1367
+ {
1368
+ "name": "sliderMax",
1369
+ "type": {
1370
+ "text": "number"
1371
+ },
1372
+ "default": "100",
1373
+ "description": "The `max` value to use for the slider.",
1374
+ "fieldName": "sliderMax"
1375
+ },
1376
+ {
1377
+ "name": "sliderStep",
1378
+ "type": {
1379
+ "text": "number"
1380
+ },
1381
+ "default": "1",
1382
+ "description": "The `step` value to use for the slider.\n\nThis attribute has no effect on the text input.",
1383
+ "fieldName": "sliderStep"
1384
+ },
1385
+ {
1386
+ "name": "width",
1387
+ "type": {
1388
+ "text": "string"
1389
+ },
1390
+ "default": "'100%'",
1391
+ "description": "The width of the component.\n\nVisit https://genspectrum.github.io/dashboard-components/?path=/docs/concepts-size-of-components--docs for more information.",
1392
+ "fieldName": "width"
1393
+ }
1394
+ ],
1395
+ "superclass": {
1396
+ "name": "PreactLitAdapter",
1397
+ "module": "/src/web-components/PreactLitAdapter"
1398
+ },
1399
+ "tagName": "gs-number-range-filter",
1400
+ "customElement": true
1401
+ }
1402
+ ],
1403
+ "exports": [
1404
+ {
1405
+ "kind": "js",
1406
+ "name": "NumberRangeFilterComponent",
1407
+ "declaration": {
1408
+ "name": "NumberRangeFilterComponent",
1409
+ "module": "src/web-components/input/gs-number-range-filter.tsx"
1410
+ }
1411
+ },
1412
+ {
1413
+ "kind": "custom-element-definition",
1414
+ "name": "gs-number-range-filter",
1415
+ "declaration": {
1416
+ "name": "NumberRangeFilterComponent",
1417
+ "module": "src/web-components/input/gs-number-range-filter.tsx"
1418
+ }
1419
+ }
1420
+ ]
1421
+ },
1207
1422
  {
1208
1423
  "kind": "javascript-module",
1209
1424
  "path": "src/web-components/input/gs-text-filter.stories.ts",
@@ -1214,7 +1429,7 @@
1214
1429
  "type": {
1215
1430
  "text": "Meta<Required<TextFilterProps>>"
1216
1431
  },
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'], }"
1432
+ "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
1433
  },
1219
1434
  {
1220
1435
  "kind": "variable",
@@ -1230,7 +1445,7 @@
1230
1445
  "type": {
1231
1446
  "text": "StoryObj<Required<TextFilterProps>>"
1232
1447
  },
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: '', }, }"
1448
+ "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
1449
  }
1235
1450
  ],
1236
1451
  "exports": [
@@ -1447,6 +1662,14 @@
1447
1662
  "name": "LineageFilterComponent",
1448
1663
  "module": "./gs-lineage-filter"
1449
1664
  }
1665
+ },
1666
+ {
1667
+ "kind": "js",
1668
+ "name": "NumberRangeFilterComponent",
1669
+ "declaration": {
1670
+ "name": "NumberRangeFilterComponent",
1671
+ "module": "./gs-number-range-filter"
1672
+ }
1450
1673
  }
1451
1674
  ]
1452
1675
  },
@@ -1838,6 +2061,148 @@
1838
2061
  }
1839
2062
  ]
1840
2063
  },
2064
+ {
2065
+ "kind": "javascript-module",
2066
+ "path": "src/web-components/visualization/gs-genome-data-viewer.spec-d.ts",
2067
+ "declarations": [],
2068
+ "exports": []
2069
+ },
2070
+ {
2071
+ "kind": "javascript-module",
2072
+ "path": "src/web-components/visualization/gs-genome-data-viewer.stories.ts",
2073
+ "declarations": [
2074
+ {
2075
+ "kind": "variable",
2076
+ "name": "meta",
2077
+ "type": {
2078
+ "text": "Meta<Required<GenomeDataViewerProps>>"
2079
+ },
2080
+ "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'], }"
2081
+ },
2082
+ {
2083
+ "kind": "variable",
2084
+ "name": "Default",
2085
+ "type": {
2086
+ "text": "StoryObj<Required<GenomeDataViewerProps>>"
2087
+ },
2088
+ "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)); }); }, }"
2089
+ }
2090
+ ],
2091
+ "exports": [
2092
+ {
2093
+ "kind": "js",
2094
+ "name": "default",
2095
+ "declaration": {
2096
+ "name": "meta",
2097
+ "module": "src/web-components/visualization/gs-genome-data-viewer.stories.ts"
2098
+ }
2099
+ },
2100
+ {
2101
+ "kind": "js",
2102
+ "name": "Default",
2103
+ "declaration": {
2104
+ "name": "Default",
2105
+ "module": "src/web-components/visualization/gs-genome-data-viewer.stories.ts"
2106
+ }
2107
+ }
2108
+ ]
2109
+ },
2110
+ {
2111
+ "kind": "javascript-module",
2112
+ "path": "src/web-components/visualization/gs-genome-data-viewer.tsx",
2113
+ "declarations": [
2114
+ {
2115
+ "kind": "class",
2116
+ "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.",
2117
+ "name": "GenomeDataViewerComponent",
2118
+ "members": [
2119
+ {
2120
+ "kind": "field",
2121
+ "name": "gff3Source",
2122
+ "type": {
2123
+ "text": "string"
2124
+ },
2125
+ "default": "''",
2126
+ "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.",
2127
+ "attribute": "gff3Source"
2128
+ },
2129
+ {
2130
+ "kind": "field",
2131
+ "name": "genomeLength",
2132
+ "type": {
2133
+ "text": "number | undefined"
2134
+ },
2135
+ "default": "0",
2136
+ "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.",
2137
+ "attribute": "genomeLength"
2138
+ },
2139
+ {
2140
+ "kind": "field",
2141
+ "name": "width",
2142
+ "type": {
2143
+ "text": "string"
2144
+ },
2145
+ "default": "'100%'",
2146
+ "description": "The width of the component.\n\nVisit https://genspectrum.github.io/dashboard-components/?path=/docs/concepts-size-of-components--docs for more information.",
2147
+ "attribute": "width"
2148
+ }
2149
+ ],
2150
+ "attributes": [
2151
+ {
2152
+ "name": "gff3Source",
2153
+ "type": {
2154
+ "text": "string"
2155
+ },
2156
+ "default": "''",
2157
+ "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.",
2158
+ "fieldName": "gff3Source"
2159
+ },
2160
+ {
2161
+ "name": "genomeLength",
2162
+ "type": {
2163
+ "text": "number | undefined"
2164
+ },
2165
+ "default": "0",
2166
+ "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.",
2167
+ "fieldName": "genomeLength"
2168
+ },
2169
+ {
2170
+ "name": "width",
2171
+ "type": {
2172
+ "text": "string"
2173
+ },
2174
+ "default": "'100%'",
2175
+ "description": "The width of the component.\n\nVisit https://genspectrum.github.io/dashboard-components/?path=/docs/concepts-size-of-components--docs for more information.",
2176
+ "fieldName": "width"
2177
+ }
2178
+ ],
2179
+ "superclass": {
2180
+ "name": "PreactLitAdapter",
2181
+ "module": "/src/web-components/PreactLitAdapter"
2182
+ },
2183
+ "tagName": "gs-genome-data-viewer",
2184
+ "customElement": true
2185
+ }
2186
+ ],
2187
+ "exports": [
2188
+ {
2189
+ "kind": "js",
2190
+ "name": "GenomeDataViewerComponent",
2191
+ "declaration": {
2192
+ "name": "GenomeDataViewerComponent",
2193
+ "module": "src/web-components/visualization/gs-genome-data-viewer.tsx"
2194
+ }
2195
+ },
2196
+ {
2197
+ "kind": "custom-element-definition",
2198
+ "name": "gs-genome-data-viewer",
2199
+ "declaration": {
2200
+ "name": "GenomeDataViewerComponent",
2201
+ "module": "src/web-components/visualization/gs-genome-data-viewer.tsx"
2202
+ }
2203
+ }
2204
+ ]
2205
+ },
1841
2206
  {
1842
2207
  "kind": "javascript-module",
1843
2208
  "path": "src/web-components/visualization/gs-mutation-comparison.stories.ts",
@@ -4170,6 +4535,14 @@
4170
4535
  "path": "src/web-components/visualization/index.ts",
4171
4536
  "declarations": [],
4172
4537
  "exports": [
4538
+ {
4539
+ "kind": "js",
4540
+ "name": "GenomeDataViewerComponent",
4541
+ "declaration": {
4542
+ "name": "GenomeDataViewerComponent",
4543
+ "module": "./gs-genome-data-viewer"
4544
+ }
4545
+ },
4173
4546
  {
4174
4547
  "kind": "js",
4175
4548
  "name": "MutationComparisonComponent",
@@ -1,4 +1,15 @@
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
+ numberRangeFilterChanged: "gs-number-range-filter-changed",
11
+ numberRangeValueChanged: "gs-number-range-value-changed"
12
+ };
2
13
  const mutationsFilterSchema = z.object({
3
14
  nucleotideMutations: z.array(z.string()),
4
15
  aminoAcidMutations: z.array(z.string()),
@@ -55,7 +66,7 @@ const dateRangeValueSchema = z.union([
55
66
  ]).nullable();
56
67
  class DateRangeOptionChangedEvent extends CustomEvent {
57
68
  constructor(detail) {
58
- super("gs-date-range-option-changed", {
69
+ super(gsEventNames.dateRangeOptionChanged, {
59
70
  detail,
60
71
  bubbles: true,
61
72
  composed: true
@@ -106,7 +117,7 @@ const dateRangeOptionPresets = {
106
117
  };
107
118
  class LocationChangedEvent extends CustomEvent {
108
119
  constructor(detail) {
109
- super("gs-location-changed", {
120
+ super(gsEventNames.locationChanged, {
110
121
  detail,
111
122
  bubbles: true,
112
123
  composed: true
@@ -115,7 +126,7 @@ class LocationChangedEvent extends CustomEvent {
115
126
  }
116
127
  class TextFilterChangedEvent extends CustomEvent {
117
128
  constructor(detail) {
118
- super("gs-text-filter-changed", {
129
+ super(gsEventNames.textFilterChanged, {
119
130
  detail,
120
131
  bubbles: true,
121
132
  composed: true
@@ -124,7 +135,7 @@ class TextFilterChangedEvent extends CustomEvent {
124
135
  }
125
136
  class LineageFilterChangedEvent extends CustomEvent {
126
137
  constructor(detail) {
127
- super("gs-lineage-filter-changed", {
138
+ super(gsEventNames.lineageFilterChanged, {
128
139
  detail,
129
140
  bubbles: true,
130
141
  composed: true
@@ -141,6 +152,7 @@ export {
141
152
  dateRangeOptionPresets as d,
142
153
  toYYYYMMDD as e,
143
154
  lapisLocationFilterSchema as f,
155
+ gsEventNames as g,
144
156
  lapisFilterSchema as l,
145
157
  mutationsFilterSchema as m,
146
158
  namedLapisFilterSchema as n,
@@ -148,4 +160,4 @@ export {
148
160
  temporalGranularitySchema as t,
149
161
  views as v
150
162
  };
151
- //# sourceMappingURL=LineageFilterChangedEvent-ixHQkq8y.js.map
163
+ //# sourceMappingURL=LineageFilterChangedEvent-GgkxoF3X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LineageFilterChangedEvent-GgkxoF3X.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 numberRangeFilterChanged: 'gs-number-range-filter-changed',\n numberRangeValueChanged: 'gs-number-range-value-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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"}