11<script setup lang="ts">
22import type { Theme as VueDataUiTheme , VueUiXyConfig , VueUiXyDatasetItem } from ' vue-data-ui'
33import { VueUiXy } from ' vue-data-ui/vue-ui-xy'
4- import { useDebounceFn , useElementSize } from ' @vueuse/core'
4+ import { useDebounceFn , useElementSize , useTimeoutFn } from ' @vueuse/core'
55import { useCssVariables } from ' ~/composables/useColors'
66import { OKLCH_NEUTRAL_FALLBACK , transparentizeOklch , lightenOklch } from ' ~/utils/colors'
77import { getFrameworkColor , isListedFramework } from ' ~/utils/frameworks'
@@ -1350,12 +1350,46 @@ function drawSvgPrintLegend(svg: Record<string, any>) {
13501350 return seriesNames .join (' \n ' )
13511351}
13521352
1353+ const showCorrectionControls = shallowRef (false )
1354+ const isResizing = shallowRef (false )
1355+
1356+ const chartHeight = computed (() => {
1357+ if (isMobile .value ) {
1358+ return 950
1359+ }
1360+ return showCorrectionControls .value && props .inModal ? 494 : 600
1361+ })
1362+
1363+ const { start } = useTimeoutFn (
1364+ () => {
1365+ isResizing .value = false
1366+ },
1367+ 200 ,
1368+ { immediate: false },
1369+ )
1370+
1371+ function pauseChartTransitions() {
1372+ isResizing .value = true
1373+ start ()
1374+ }
1375+
1376+ watch (
1377+ chartHeight ,
1378+ (newH , oldH ) => {
1379+ if (newH !== oldH ) {
1380+ // Avoids triggering chart line transitions when the chart is resized
1381+ pauseChartTransitions ()
1382+ }
1383+ },
1384+ { immediate: true },
1385+ )
1386+
13531387// VueUiXy chart component configuration
13541388const chartConfig = computed <VueUiXyConfig >(() => {
13551389 return {
13561390 theme: isDarkMode .value ? ' dark' : (' ' as VueDataUiTheme ),
13571391 chart: {
1358- height: isMobile .value ? 950 : 600 ,
1392+ height: chartHeight .value ,
13591393 backgroundColor: colors .value .bg ,
13601394 padding: { bottom: displayedGranularity .value === ' yearly' ? 84 : 64 , right: 128 }, // padding right is set to leave space of last datapoint label(s)
13611395 userOptions: {
@@ -1559,7 +1593,6 @@ const chartConfig = computed<VueUiXyConfig>(() => {
15591593})
15601594
15611595const isDownloadsMetric = computed (() => selectedMetric .value === ' downloads' )
1562- const showCorrectionControls = shallowRef (false )
15631596
15641597const packageAnomalies = computed (() => getAnomaliesForPackages (effectivePackageNames .value ))
15651598const hasAnomalies = computed (() => packageAnomalies .value .length > 0 )
@@ -1648,6 +1681,8 @@ watch(selectedMetric, value => {
16481681
16491682 <button
16501683 v-if =" showResetButton"
1684+ :aria-expanded =" showCorrectionControls"
1685+ aria-controls =" trends-correction-controls"
16511686 type =" button"
16521687 aria-label =" Reset date range"
16531688 class =" self-end flex items-center justify-center px-2.5 py-2.25 border border-transparent rounded-md text-fg-subtle hover:text-fg transition-colors hover:border-border focus-visible:outline-accent/70 sm:mb-0"
@@ -1671,115 +1706,141 @@ watch(selectedMetric, value => {
16711706 />
16721707 {{ $t('package.trends.data_correction') }}
16731708 </button >
1674- <div v-if =" showCorrectionControls" class =" grid grid-cols-2 sm:flex items-end gap-3" >
1675- <label class =" flex flex-col gap-1 flex-1" >
1676- <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1677- {{ $t('package.trends.average_window') }}
1678- <span class =" text-fg-muted" >({{ settings.chartFilter.averageWindow }})</span >
1679- </span >
1680- <input
1681- v-model.number =" settings.chartFilter.averageWindow"
1682- type =" range"
1683- min =" 0"
1684- max =" 20"
1685- step =" 1"
1686- class =" accent-[var(--accent-color,var(--fg-subtle))]"
1687- />
1688- </label >
1689- <label class =" flex flex-col gap-1 flex-1" >
1690- <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1691- {{ $t('package.trends.smoothing') }}
1692- <span class =" text-fg-muted" >({{ settings.chartFilter.smoothingTau }})</span >
1693- </span >
1694- <input
1695- v-model.number =" settings.chartFilter.smoothingTau"
1696- type =" range"
1697- min =" 0"
1698- max =" 20"
1699- step =" 1"
1700- class =" accent-[var(--accent-color,var(--fg-subtle))]"
1701- />
1702- </label >
1703- <label class =" flex flex-col gap-1 flex-1" >
1704- <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1705- {{ $t('package.trends.prediction') }}
1706- <span class =" text-fg-muted" >({{ settings.chartFilter.predictionPoints }})</span >
1707- </span >
1708- <input
1709- v-model.number =" settings.chartFilter.predictionPoints"
1710- type =" range"
1711- min =" 0"
1712- max =" 30"
1713- step =" 1"
1714- class =" accent-[var(--accent-color,var(--fg-subtle))]"
1715- />
1716- </label >
1717- <div class =" flex flex-col gap-1 shrink-0" >
1718- <span
1719- class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase flex items-center justify-between"
1720- >
1721- {{ $t('package.trends.known_anomalies') }}
1722- <TooltipApp interactive :to =" inModal ? '#chart-modal' : undefined" >
1723- <button
1724- type =" button"
1725- class =" i-lucide:info w-3.5 h-3.5 text-fg-muted cursor-help"
1726- :aria-label =" $t('package.trends.known_anomalies')"
1709+ <div
1710+ class =" overflow-hidden transition-[opacity] duration-200 ease-out"
1711+ id =" trends-correction-controls"
1712+ :aria-hidden =" !showCorrectionControls"
1713+ :inert =" !showCorrectionControls"
1714+ :class ="
1715+ showCorrectionControls
1716+ ? 'max-h-[220px] opacity-100'
1717+ : 'max-h-0 opacity-0 pointer-events-none'
1718+ "
1719+ >
1720+ <div class =" pt-1 min-h-[160px] sm:min-h-[76px]" >
1721+ <div class =" grid grid-cols-2 sm:flex items-end gap-3" >
1722+ <label class =" flex flex-col gap-1 flex-1" >
1723+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1724+ {{ $t('package.trends.average_window') }}
1725+ <span class =" text-fg-muted" >({{ settings.chartFilter.averageWindow }})</span >
1726+ </span >
1727+ <input
1728+ v-model.number =" settings.chartFilter.averageWindow"
1729+ :disabled =" !showCorrectionControls"
1730+ type =" range"
1731+ min =" 0"
1732+ max =" 20"
1733+ step =" 1"
1734+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
17271735 />
1728- <template #content >
1729- <div class =" flex flex-col gap-3" >
1730- <p class =" text-xs text-fg-muted" >
1731- {{ $t('package.trends.known_anomalies_description') }}
1732- </p >
1733- <div v-if =" hasAnomalies" >
1734- <p class =" text-xs text-fg-subtle font-medium" >
1735- {{ $t('package.trends.known_anomalies_ranges') }}
1736- </p >
1737- <ul class =" text-xs text-fg-subtle list-disc list-inside" >
1738- <li v-for =" a in packageAnomalies" :key =" `${a.packageName}-${a.start}`" >
1736+ </label >
1737+ <label class =" flex flex-col gap-1 flex-1" >
1738+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1739+ {{ $t('package.trends.smoothing') }}
1740+ <span class =" text-fg-muted" >({{ settings.chartFilter.smoothingTau }})</span >
1741+ </span >
1742+ <input
1743+ v-model.number =" settings.chartFilter.smoothingTau"
1744+ :disabled =" !showCorrectionControls"
1745+ type =" range"
1746+ min =" 0"
1747+ max =" 20"
1748+ step =" 1"
1749+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1750+ />
1751+ </label >
1752+ <label class =" flex flex-col gap-1 flex-1" >
1753+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1754+ {{ $t('package.trends.prediction') }}
1755+ <span class =" text-fg-muted" >({{ settings.chartFilter.predictionPoints }})</span >
1756+ </span >
1757+ <input
1758+ v-model.number =" settings.chartFilter.predictionPoints"
1759+ :disabled =" !showCorrectionControls"
1760+ type =" range"
1761+ min =" 0"
1762+ max =" 30"
1763+ step =" 1"
1764+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1765+ />
1766+ </label >
1767+ <div class =" flex flex-col gap-1 shrink-0" >
1768+ <span
1769+ class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase flex items-center justify-between"
1770+ >
1771+ {{ $t('package.trends.known_anomalies') }}
1772+ <TooltipApp
1773+ interactive
1774+ :to =" inModal ? '#chart-modal' : undefined"
1775+ v-if =" showCorrectionControls"
1776+ >
1777+ <button
1778+ type =" button"
1779+ class =" i-lucide:info w-3.5 h-3.5 text-fg-muted cursor-help"
1780+ :aria-label =" $t('package.trends.known_anomalies')"
1781+ />
1782+ <template #content >
1783+ <div class =" flex flex-col gap-3" >
1784+ <p class =" text-xs text-fg-muted" >
1785+ {{ $t('package.trends.known_anomalies_description') }}
1786+ </p >
1787+ <div v-if =" hasAnomalies" >
1788+ <p class =" text-xs text-fg-subtle font-medium" >
1789+ {{ $t('package.trends.known_anomalies_ranges') }}
1790+ </p >
1791+ <ul class =" text-xs text-fg-subtle list-disc list-inside" >
1792+ <li v-for =" a in packageAnomalies" :key =" `${a.packageName}-${a.start}`" >
1793+ {{
1794+ isMultiPackageMode
1795+ ? $t('package.trends.known_anomalies_range_named', {
1796+ packageName: a.packageName,
1797+ start: formatAnomalyDate(a.start),
1798+ end: formatAnomalyDate(a.end),
1799+ })
1800+ : $t('package.trends.known_anomalies_range', {
1801+ start: formatAnomalyDate(a.start),
1802+ end: formatAnomalyDate(a.end),
1803+ })
1804+ }}
1805+ </li >
1806+ </ul >
1807+ </div >
1808+ <p v-else class =" text-xs text-fg-muted" >
17391809 {{
1740- isMultiPackageMode
1741- ? $t('package.trends.known_anomalies_range_named', {
1742- packageName: a.packageName,
1743- start: formatAnomalyDate(a.start),
1744- end: formatAnomalyDate(a.end),
1745- })
1746- : $t('package.trends.known_anomalies_range', {
1747- start: formatAnomalyDate(a.start),
1748- end: formatAnomalyDate(a.end),
1749- })
1810+ $t('package.trends.known_anomalies_none', effectivePackageNames.length)
17501811 }}
1751- </li >
1752- </ ul >
1753- </ div >
1754- < p v-else class = " text-xs text-fg-muted " >
1755- {{ $t('package.trends.known_anomalies_none', effectivePackageNames.length) }}
1756- </ p >
1757- < div class = " flex justify-end " >
1758- < LinkBase
1759- to = " https://github.com/npmx-dev/npmx.dev/edit/main/app/utils/download-anomalies.data.ts "
1760- class = " text-xs text-accent "
1761- >
1762- {{ $t('package.trends.known_anomalies_contribute') }}
1763- </ LinkBase >
1764- </ div >
1765- </ div >
1766- </ template >
1767- </ TooltipApp >
1768- </ span >
1769- < label
1770- class = " flex items-center gap-1.5 text-2xs font-mono text-fg-subtle cursor-pointer h-4 "
1771- :class = " { 'opacity-50': !hasAnomalies } "
1772- >
1773- < input
1774- :checked = " settings.chartFilter.anomaliesFixed "
1775- @change = "
1776- settings.chartFilter.anomaliesFixed = ($event.target as HTMLInputElement).checked
1777- "
1778- type = " checkbox "
1779- class = " accent-[var(--accent-color,var(--fg-subtle))] "
1780- / >
1781- {{ $t('package.trends.apply_correction') }}
1782- </label >
1812+ </p >
1813+ < div class = " flex justify-end " >
1814+ < LinkBase
1815+ to = " https://github.com/npmx-dev/npmx.dev/edit/main/app/utils/download-anomalies.data.ts "
1816+ class = " text-xs text-accent "
1817+ >
1818+ {{ $t('package.trends.known_anomalies_contribute') }}
1819+ </ LinkBase >
1820+ </ div >
1821+ </ div >
1822+ </ template >
1823+ </ TooltipApp >
1824+ </ span >
1825+ < label
1826+ class = " flex items-center gap-1.5 text-2xs font-mono text-fg-subtle cursor-pointer h-4 "
1827+ :class = " { 'opacity-50': !hasAnomalies } "
1828+ >
1829+ < input
1830+ :checked = " settings.chartFilter.anomaliesFixed "
1831+ :disabled = " !showCorrectionControls "
1832+ @change = "
1833+ settings.chartFilter.anomaliesFixed = (
1834+ $event.target as HTMLInputElement
1835+ ).checked
1836+ "
1837+ type = " checkbox "
1838+ class = " accent-[var(--accent-color,var(--fg-subtle))] "
1839+ />
1840+ {{ $t('package.trends.apply_correction') }}
1841+ </ label >
1842+ </ div >
1843+ </div >
17831844 </div >
17841845 </div >
17851846 </div >
@@ -1798,14 +1859,23 @@ watch(selectedMetric, value => {
17981859 <div
17991860 role =" region"
18001861 aria-labelledby =" trends-chart-title"
1801- :class =" isMobile === false && width > 0 ? 'min-h-[567px]' : 'min-h-[260px]'"
1862+ :class ="
1863+ isMobile === false && width > 0
1864+ ? showCorrectionControls
1865+ ? 'h-[491px]'
1866+ : 'h-[567px]'
1867+ : 'min-h-[260px]'
1868+ "
18021869 >
18031870 <ClientOnly v-if =" chartData.dataset" >
18041871 <div :data-pending =" pending" :data-minimap-visible =" maxDatapoints > 6" >
18051872 <VueUiXy
18061873 :dataset =" normalisedDataset"
18071874 :config =" chartConfig"
1808- class =" [direction:ltr]"
1875+ :class =" {
1876+ '[direction:ltr]': true,
1877+ 'no-transition': isResizing,
1878+ }"
18091879 @zoomStart =" setIsZoom"
18101880 @zoomEnd =" setIsZoom"
18111881 @zoomReset =" isZoomed = false"
@@ -2102,6 +2172,11 @@ watch(selectedMetric, value => {
21022172 top : calc (100% - 2rem ) !important ;
21032173}
21042174
2175+ .no-transition line ,
2176+ .no-transition circle {
2177+ transition : none !important ;
2178+ }
2179+
21052180input ::-webkit-date-and-time-value {
21062181 margin-inline : 4px ;
21072182}
0 commit comments