@genspectrum/dashboard-components 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -638,7 +638,7 @@
638
638
  "type": {
639
639
  "text": "StoryObj<Required<LineageFilterProps>>"
640
640
  },
641
- "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*(677146)' })); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ pangoLineage: 'B.1.1.7*', }); }); }); }, args: { ...LineageFilter.args, value: '', }, }"
641
+ "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*(677,146)' })); await waitFor(() => { return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({ pangoLineage: 'B.1.1.7*', }); }); }); }, args: { ...LineageFilter.args, value: '', }, }"
642
642
  }
643
643
  ],
644
644
  "exports": [
@@ -1530,7 +1530,7 @@
1530
1530
  "type": {
1531
1531
  "text": "StoryObj<Required<TextFilterProps>>"
1532
1532
  },
1533
- "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: '', }, }"
1533
+ "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).toHaveBeenCalledWith( expect.objectContaining({ detail: { host: undefined, }, }), ); }); }, args: { ...Default.args, value: '', }, }"
1534
1534
  }
1535
1535
  ],
1536
1536
  "exports": [
@@ -1651,6 +1651,22 @@ declare global {
1651
1651
  }
1652
1652
 
1653
1653
 
1654
+ declare global {
1655
+ interface HTMLElementTagNameMap {
1656
+ 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1657
+ }
1658
+ }
1659
+
1660
+
1661
+ declare global {
1662
+ namespace JSX {
1663
+ interface IntrinsicElements {
1664
+ 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1665
+ }
1666
+ }
1667
+ }
1668
+
1669
+
1654
1670
  declare global {
1655
1671
  interface HTMLElementTagNameMap {
1656
1672
  'gs-genome-data-viewer': GenomeDataViewerComponent;
@@ -1749,7 +1765,7 @@ declare global {
1749
1765
 
1750
1766
  declare global {
1751
1767
  interface HTMLElementTagNameMap {
1752
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1768
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1753
1769
  }
1754
1770
  }
1755
1771
 
@@ -1757,7 +1773,7 @@ declare global {
1757
1773
  declare global {
1758
1774
  namespace JSX {
1759
1775
  interface IntrinsicElements {
1760
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1776
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1761
1777
  }
1762
1778
  }
1763
1779
  }
@@ -1765,7 +1781,7 @@ declare global {
1765
1781
 
1766
1782
  declare global {
1767
1783
  interface HTMLElementTagNameMap {
1768
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1784
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1769
1785
  }
1770
1786
  }
1771
1787
 
@@ -1773,7 +1789,7 @@ declare global {
1773
1789
  declare global {
1774
1790
  namespace JSX {
1775
1791
  interface IntrinsicElements {
1776
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1792
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1777
1793
  }
1778
1794
  }
1779
1795
  }
@@ -1813,7 +1829,10 @@ declare global {
1813
1829
 
1814
1830
  declare global {
1815
1831
  interface HTMLElementTagNameMap {
1816
- 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1832
+ 'gs-location-filter': LocationFilterComponent;
1833
+ }
1834
+ interface HTMLElementEventMap {
1835
+ [gsEventNames.locationChanged]: LocationChangedEvent;
1817
1836
  }
1818
1837
  }
1819
1838
 
@@ -1821,7 +1840,7 @@ declare global {
1821
1840
  declare global {
1822
1841
  namespace JSX {
1823
1842
  interface IntrinsicElements {
1824
- 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1843
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1825
1844
  }
1826
1845
  }
1827
1846
  }
@@ -1847,25 +1866,6 @@ declare global {
1847
1866
  }
1848
1867
 
1849
1868
 
1850
- declare global {
1851
- interface HTMLElementTagNameMap {
1852
- 'gs-location-filter': LocationFilterComponent;
1853
- }
1854
- interface HTMLElementEventMap {
1855
- [gsEventNames.locationChanged]: LocationChangedEvent;
1856
- }
1857
- }
1858
-
1859
-
1860
- declare global {
1861
- namespace JSX {
1862
- interface IntrinsicElements {
1863
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1864
- }
1865
- }
1866
- }
1867
-
1868
-
1869
1869
  declare global {
1870
1870
  interface HTMLElementTagNameMap {
1871
1871
  'gs-text-filter': TextFilterComponent;
@@ -1585,6 +1585,20 @@ const MinMaxRangeSlider = ({
1585
1585
  function getGradientBoundary(x2, lowerBound, upperBound) {
1586
1586
  return (x2 - lowerBound) / (upperBound - lowerBound) * 100;
1587
1587
  }
1588
+ const TOOLTIP_BASE_STYLES = "z-10 w-max bg-white p-4 border border-gray-200 rounded-md";
1589
+ const Tooltip = ({ children, content, position = "bottom", tooltipStyle }) => {
1590
+ return /* @__PURE__ */ u$1("div", { className: `relative group`, children: [
1591
+ /* @__PURE__ */ u$1("div", { children }),
1592
+ /* @__PURE__ */ u$1(
1593
+ "div",
1594
+ {
1595
+ className: `absolute ${TOOLTIP_BASE_STYLES} invisible group-hover:visible ${getPositionCss(position)}`,
1596
+ style: tooltipStyle,
1597
+ children: content
1598
+ }
1599
+ )
1600
+ ] });
1601
+ };
1588
1602
  function getPositionCss(position) {
1589
1603
  switch (position) {
1590
1604
  case "top":
@@ -1607,19 +1621,6 @@ function getPositionCss(position) {
1607
1621
  return "";
1608
1622
  }
1609
1623
  }
1610
- const Tooltip = ({ children, content, position = "bottom", tooltipStyle }) => {
1611
- return /* @__PURE__ */ u$1("div", { className: `relative group`, children: [
1612
- /* @__PURE__ */ u$1("div", { children }),
1613
- /* @__PURE__ */ u$1(
1614
- "div",
1615
- {
1616
- className: `absolute z-10 w-max bg-white p-4 border border-gray-200 rounded-md invisible group-hover:visible ${getPositionCss(position)}`,
1617
- style: tooltipStyle,
1618
- children: content
1619
- }
1620
- )
1621
- ] });
1622
- };
1623
1624
  const ColorsRGB = {
1624
1625
  indigo: [51, 34, 136],
1625
1626
  green: [17, 119, 51],
@@ -7422,7 +7423,7 @@ const MutationsOverTimeGridTooltip = ({
7422
7423
  value
7423
7424
  }) => {
7424
7425
  const dateClass = toTemporalClass(date);
7425
- return /* @__PURE__ */ u$1("div", { children: [
7426
+ return /* @__PURE__ */ u$1("div", { className: "text-center", children: [
7426
7427
  /* @__PURE__ */ u$1("p", { children: /* @__PURE__ */ u$1("span", { className: "font-bold", children: dateClass.englishName() }) }),
7427
7428
  /* @__PURE__ */ u$1("p", { children: [
7428
7429
  "(",
@@ -7549,6 +7550,79 @@ const getTextColorForScale = (value, colorScale) => {
7549
7550
  const alpha = (value - colorScale.min) / colorRange;
7550
7551
  return alpha <= 0.5 ? "black" : "white";
7551
7552
  };
7553
+ const PortalTooltip = ({
7554
+ children,
7555
+ content,
7556
+ position = "bottom",
7557
+ tooltipStyle,
7558
+ portalTarget
7559
+ }) => {
7560
+ const [isHovered, setIsHovered] = d(false);
7561
+ const [tooltipPosition, setTooltipPosition] = d({ top: 0, left: 0 });
7562
+ const triggerRef = A$1(null);
7563
+ const tooltipRef = A$1(null);
7564
+ _(() => {
7565
+ if (isHovered && triggerRef.current !== null && tooltipRef.current !== null) {
7566
+ const triggerRect = triggerRef.current.getBoundingClientRect();
7567
+ const tooltipRect = tooltipRef.current.getBoundingClientRect();
7568
+ const newPosition = calculateTooltipPosition(triggerRect, tooltipRect, position);
7569
+ setTooltipPosition(newPosition);
7570
+ }
7571
+ }, [isHovered, position]);
7572
+ const tooltipContent = /* @__PURE__ */ u$1(
7573
+ "div",
7574
+ {
7575
+ ref: tooltipRef,
7576
+ className: `fixed ${TOOLTIP_BASE_STYLES} ${isHovered ? "visible" : "invisible"}`,
7577
+ style: Object.assign({}, tooltipStyle, { top: tooltipPosition.top, left: tooltipPosition.left }),
7578
+ children: content
7579
+ }
7580
+ );
7581
+ return /* @__PURE__ */ u$1(Fragment, { children: [
7582
+ /* @__PURE__ */ u$1("div", { ref: triggerRef, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children }),
7583
+ portalTarget !== null && $(tooltipContent, portalTarget)
7584
+ ] });
7585
+ };
7586
+ function calculateTooltipPosition(triggerRect, tooltipRect, position) {
7587
+ const gap = 4;
7588
+ let top;
7589
+ let left;
7590
+ switch (position) {
7591
+ case "top":
7592
+ top = triggerRect.top - tooltipRect.height - gap;
7593
+ left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;
7594
+ break;
7595
+ case "top-start":
7596
+ top = triggerRect.top - tooltipRect.height - gap;
7597
+ left = triggerRect.left;
7598
+ break;
7599
+ case "top-end":
7600
+ top = triggerRect.top - tooltipRect.height - gap;
7601
+ left = triggerRect.right - tooltipRect.width;
7602
+ break;
7603
+ case "bottom":
7604
+ top = triggerRect.bottom + gap;
7605
+ left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;
7606
+ break;
7607
+ case "bottom-start":
7608
+ top = triggerRect.bottom + gap;
7609
+ left = triggerRect.left;
7610
+ break;
7611
+ case "bottom-end":
7612
+ top = triggerRect.bottom + gap;
7613
+ left = triggerRect.right - tooltipRect.width;
7614
+ break;
7615
+ case "left":
7616
+ top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;
7617
+ left = triggerRect.left - tooltipRect.width - gap;
7618
+ break;
7619
+ case "right":
7620
+ top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;
7621
+ left = triggerRect.right + gap;
7622
+ break;
7623
+ }
7624
+ return { top, left };
7625
+ }
7552
7626
  const pageSizeContext = createContext$1({
7553
7627
  pageSize: -1,
7554
7628
  setPageSize: () => {
@@ -7731,7 +7805,8 @@ const MutationsOverTimeGrid = ({
7731
7805
  data,
7732
7806
  colorScale,
7733
7807
  sequenceType,
7734
- pageSizes
7808
+ pageSizes,
7809
+ tooltipPortalTarget
7735
7810
  }) => {
7736
7811
  const tableData = T$1(() => {
7737
7812
  const allMutations = data.getFirstAxisKeys();
@@ -7772,14 +7847,15 @@ const MutationsOverTimeGrid = ({
7772
7847
  columnIndex,
7773
7848
  numberOfColumns
7774
7849
  ),
7775
- colorScale
7850
+ colorScale,
7851
+ tooltipPortalTarget
7776
7852
  }
7777
7853
  ) });
7778
7854
  }
7779
7855
  });
7780
7856
  });
7781
7857
  return [mutationHeader, ...dateHeaders];
7782
- }, [colorScale, data, sequenceType]);
7858
+ }, [colorScale, data, sequenceType, tooltipPortalTarget]);
7783
7859
  const { pageSize } = usePageSizeContext();
7784
7860
  const table = usePreactTable({
7785
7861
  data: tableData,
@@ -7818,13 +7894,14 @@ function getTooltipPosition(rowIndex, rows, columnIndex, columns) {
7818
7894
  const tooltipY = columnIndex < columns / 2 ? "start" : "end";
7819
7895
  return `${tooltipX}-${tooltipY}`;
7820
7896
  }
7821
- const ProportionCell = ({ value, mutation, date, tooltipPosition, colorScale }) => {
7897
+ const ProportionCell = ({ value, mutation, date, tooltipPosition, colorScale, tooltipPortalTarget }) => {
7822
7898
  const proportion = (value == null ? void 0 : value.type) === "belowThreshold" ? void 0 : value == null ? void 0 : value.proportion;
7823
7899
  return /* @__PURE__ */ u$1("div", { className: "py-1 w-full h-full", children: /* @__PURE__ */ u$1(
7824
- Tooltip,
7900
+ PortalTooltip,
7825
7901
  {
7826
7902
  content: /* @__PURE__ */ u$1(MutationsOverTimeGridTooltip, { mutation, date, value }),
7827
7903
  position: tooltipPosition,
7904
+ portalTarget: tooltipPortalTarget,
7828
7905
  children: /* @__PURE__ */ u$1(
7829
7906
  "div",
7830
7907
  {
@@ -8053,6 +8130,11 @@ const MutationsOverTimeTabs$1 = ({
8053
8130
  overallMutationData
8054
8131
  }) => {
8055
8132
  const tabsRef = useDispatchFinishedLoadingEvent();
8133
+ const tooltipPortalTargetRef = A$1(null);
8134
+ const [tooltipPortalTarget, setTooltipPortalTarget] = d(null);
8135
+ _(() => {
8136
+ setTooltipPortalTarget(tooltipPortalTargetRef.current);
8137
+ }, []);
8056
8138
  const [mutationFilterValue, setMutationFilterValue] = d({
8057
8139
  textFilter: "",
8058
8140
  annotationNameFilter: /* @__PURE__ */ new Set()
@@ -8102,7 +8184,8 @@ const MutationsOverTimeTabs$1 = ({
8102
8184
  data: filteredData,
8103
8185
  colorScale,
8104
8186
  sequenceType: originalComponentProps.sequenceType,
8105
- pageSizes: originalComponentProps.pageSizes
8187
+ pageSizes: originalComponentProps.pageSizes,
8188
+ tooltipPortalTarget
8106
8189
  }
8107
8190
  )
8108
8191
  };
@@ -8129,7 +8212,7 @@ const MutationsOverTimeTabs$1 = ({
8129
8212
  mutationFilterValue
8130
8213
  }
8131
8214
  );
8132
- return /* @__PURE__ */ u$1(PageSizeContextProvider, { pageSizes: originalComponentProps.pageSizes, children: /* @__PURE__ */ u$1(Tabs, { ref: tabsRef, tabs, toolbar }) });
8215
+ return /* @__PURE__ */ u$1("div", { ref: tooltipPortalTargetRef, children: /* @__PURE__ */ u$1(PageSizeContextProvider, { pageSizes: originalComponentProps.pageSizes, children: /* @__PURE__ */ u$1(Tabs, { ref: tabsRef, tabs, toolbar }) }) });
8133
8216
  };
8134
8217
  const Toolbar$2 = ({
8135
8218
  activeTab,
@@ -9718,6 +9801,7 @@ const MutationsOverTimeTabs = ({
9718
9801
  originalComponentProps
9719
9802
  }) => {
9720
9803
  const tabsRef = useDispatchFinishedLoadingEvent();
9804
+ const tooltipPortalTargetRef = A$1(null);
9721
9805
  const [mutationFilterValue, setMutationFilterValue] = d({
9722
9806
  textFilter: "",
9723
9807
  annotationNameFilter: /* @__PURE__ */ new Set()
@@ -9740,7 +9824,8 @@ const MutationsOverTimeTabs = ({
9740
9824
  }),
9741
9825
  colorScale,
9742
9826
  pageSizes: originalComponentProps.pageSizes,
9743
- sequenceType: originalComponentProps.sequenceType
9827
+ sequenceType: originalComponentProps.sequenceType,
9828
+ tooltipPortalTarget: tooltipPortalTargetRef.current
9744
9829
  }
9745
9830
  )
9746
9831
  })),
@@ -9767,7 +9852,7 @@ const MutationsOverTimeTabs = ({
9767
9852
  mutationFilterValue
9768
9853
  }
9769
9854
  );
9770
- return /* @__PURE__ */ u$1(PageSizeContextProvider, { pageSizes: originalComponentProps.pageSizes, children: /* @__PURE__ */ u$1(Tabs, { ref: tabsRef, tabs, toolbar }) });
9855
+ return /* @__PURE__ */ u$1("div", { ref: tooltipPortalTargetRef, children: /* @__PURE__ */ u$1(PageSizeContextProvider, { pageSizes: originalComponentProps.pageSizes, children: /* @__PURE__ */ u$1(Tabs, { ref: tabsRef, tabs, toolbar }) }) });
9771
9856
  };
9772
9857
  const Toolbar = ({
9773
9858
  colorScale,
@@ -14106,7 +14191,8 @@ function DownshiftCombobox({
14106
14191
  highlightedIndex,
14107
14192
  getItemProps,
14108
14193
  inputValue,
14109
- closeMenu
14194
+ closeMenu,
14195
+ reset
14110
14196
  } = useCombobox({
14111
14197
  onInputValueChange({ inputValue: inputValue2 }) {
14112
14198
  setInputIsInvalid(false);
@@ -14136,7 +14222,7 @@ function DownshiftCombobox({
14136
14222
  setInputIsInvalid(true);
14137
14223
  };
14138
14224
  const clearInput = () => {
14139
- selectItem(null);
14225
+ reset();
14140
14226
  };
14141
14227
  const buttonRef = A$1(null);
14142
14228
  return /* @__PURE__ */ u$1("div", { ref: divRef, className: "relative w-full", children: [
@@ -14273,7 +14359,7 @@ const LocationSelector = ({
14273
14359
  /* @__PURE__ */ u$1("span", { children: item.label }),
14274
14360
  !hideCounts && /* @__PURE__ */ u$1("span", { className: "ml-2 text-gray-500", children: [
14275
14361
  "(",
14276
- item.count,
14362
+ item.count.toLocaleString("en-US"),
14277
14363
  ")"
14278
14364
  ] })
14279
14365
  ] }),
@@ -14445,7 +14531,7 @@ const TextSelector = ({
14445
14531
  /* @__PURE__ */ u$1("span", { children: item.value }),
14446
14532
  !hideCounts && /* @__PURE__ */ u$1("span", { className: "ml-2 text-gray-500", children: [
14447
14533
  "(",
14448
- item.count,
14534
+ item.count.toLocaleString("en-US"),
14449
14535
  ")"
14450
14536
  ] })
14451
14537
  ] });
@@ -15310,27 +15396,35 @@ async function fetchLineageAutocompleteList({
15310
15396
  lapisFilter,
15311
15397
  signal
15312
15398
  }) {
15313
- const [countsByLineage, lineageTree] = await Promise.all([
15399
+ const [countsByLineage, { lineageTree, aliasMapping }] = await Promise.all([
15314
15400
  getCountsByLineage({
15315
15401
  lapisUrl,
15316
15402
  lapisField,
15317
15403
  lapisFilter,
15318
15404
  signal
15319
15405
  }),
15320
- getLineageTree({ lapisUrl, lapisField, signal })
15406
+ getLineageTreeAndAliases({ lapisUrl, lapisField, signal })
15321
15407
  ]);
15322
- return Array.from(lineageTree.keys()).sort((a2, b2) => a2.localeCompare(b2)).map((lineage2) => {
15323
- return [
15324
- {
15325
- lineage: lineage2,
15326
- count: countsByLineage.get(lineage2) ?? 0
15327
- },
15328
- {
15329
- lineage: `${lineage2}*`,
15330
- count: getCountsIncludingSublineages(lineage2, lineageTree, countsByLineage)
15331
- }
15332
- ];
15333
- }).flat();
15408
+ const prefixToLineage = findMissingPrefixMappings(lineageTree, aliasMapping);
15409
+ const actualLineageItems = Array.from(lineageTree.keys()).flatMap((lineage2) => [
15410
+ {
15411
+ lineage: lineage2,
15412
+ count: countsByLineage.get(lineage2) ?? 0
15413
+ },
15414
+ {
15415
+ lineage: `${lineage2}*`,
15416
+ count: getCountsIncludingSublineages(lineage2, lineageTree, countsByLineage)
15417
+ }
15418
+ ]);
15419
+ const prefixAliasItems = Array.from(prefixToLineage.entries()).map(([prefix, actualLineage]) => ({
15420
+ lineage: `${prefix}*`,
15421
+ count: getCountsIncludingSublineages(actualLineage, lineageTree, countsByLineage)
15422
+ }));
15423
+ return [...actualLineageItems, ...prefixAliasItems].sort((a2, b2) => {
15424
+ const aKey = a2.lineage.replace(/\*/g, " ");
15425
+ const bKey = b2.lineage.replace(/\*/g, " ");
15426
+ return aKey.localeCompare(bKey);
15427
+ });
15334
15428
  }
15335
15429
  async function getCountsByLineage({
15336
15430
  lapisUrl,
@@ -15344,18 +15438,22 @@ async function getCountsByLineage({
15344
15438
  const countsByLineageArray = (await fetchAggregatedOperator.evaluate(lapisUrl, signal)).content;
15345
15439
  return new Map(countsByLineageArray.map((value) => [value[lapisField], value.count]));
15346
15440
  }
15347
- async function getLineageTree({
15441
+ async function getLineageTreeAndAliases({
15348
15442
  lapisUrl,
15349
15443
  lapisField,
15350
15444
  signal
15351
15445
  }) {
15352
15446
  const lineageDefinitions = await fetchLineageDefinition({ lapisUrl, lapisField, signal });
15353
15447
  const lineageTree = /* @__PURE__ */ new Map();
15448
+ const aliasMapping = /* @__PURE__ */ new Map();
15354
15449
  Object.entries(lineageDefinitions).forEach(([lineage2, definition]) => {
15355
15450
  var _a;
15356
15451
  if (!lineageTree.has(lineage2)) {
15357
15452
  lineageTree.set(lineage2, { children: [] });
15358
15453
  }
15454
+ if (definition.aliases && definition.aliases.length > 0) {
15455
+ aliasMapping.set(lineage2, definition.aliases);
15456
+ }
15359
15457
  (_a = definition.parents) == null ? void 0 : _a.forEach((parent) => {
15360
15458
  var _a2;
15361
15459
  const parentChildren = (_a2 = lineageTree.get(parent)) == null ? void 0 : _a2.children;
@@ -15363,7 +15461,7 @@ async function getLineageTree({
15363
15461
  lineageTree.set(parent, { children: newParentChildren });
15364
15462
  });
15365
15463
  });
15366
- return lineageTree;
15464
+ return { lineageTree, aliasMapping };
15367
15465
  }
15368
15466
  function getCountsIncludingSublineages(lineage2, lineageTree, countsByLineage) {
15369
15467
  const descendants = getAllDescendants(lineage2, lineageTree);
@@ -15381,6 +15479,29 @@ function getAllDescendants(lineage2, lineageTree) {
15381
15479
  });
15382
15480
  return /* @__PURE__ */ new Set([...children, ...childrenOfChildren.flatMap((child) => Array.from(child))]);
15383
15481
  }
15482
+ function findMissingPrefixMappings(lineageTree, aliasMapping) {
15483
+ const lineages = Array.from(lineageTree.keys());
15484
+ const lineagesSet = new Set(lineages);
15485
+ const allPrefixes = lineages.flatMap((lineage2) => {
15486
+ const parts = lineage2.split(".");
15487
+ return parts.map((_2, i2) => parts.slice(0, i2 + 1).join("."));
15488
+ });
15489
+ const missingPrefixes = new Set(allPrefixes.filter((prefix) => !lineagesSet.has(prefix)));
15490
+ const reverseAliasMapping = /* @__PURE__ */ new Map();
15491
+ aliasMapping.forEach((aliases, lineage2) => {
15492
+ aliases.forEach((alias) => {
15493
+ reverseAliasMapping.set(alias, lineage2);
15494
+ });
15495
+ });
15496
+ const prefixToLineage = /* @__PURE__ */ new Map();
15497
+ missingPrefixes.forEach((prefix) => {
15498
+ const actualLineage = reverseAliasMapping.get(prefix);
15499
+ if (actualLineage) {
15500
+ prefixToLineage.set(prefix, actualLineage);
15501
+ }
15502
+ });
15503
+ return prefixToLineage;
15504
+ }
15384
15505
  const lineageSelectorPropsSchema = z$2.object({
15385
15506
  lapisField: z$2.string().min(1),
15386
15507
  placeholderText: z$2.string().optional(),
@@ -15450,7 +15571,7 @@ const LineageSelector = ({
15450
15571
  /* @__PURE__ */ u$1("span", { children: item.lineage }),
15451
15572
  !hideCounts && /* @__PURE__ */ u$1("span", { className: "ml-2 text-gray-500", children: [
15452
15573
  "(",
15453
- item.count,
15574
+ item.count.toLocaleString("en-US"),
15454
15575
  ")"
15455
15576
  ] })
15456
15577
  ] })