@datagouv/components-next 1.0.2-dev.195 → 1.0.2-dev.196

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.
@@ -1,4 +1,4 @@
1
- import { g as Ke } from "./main-B3IzELg_.js";
1
+ import { g as Ke } from "./main-B03fYwPN.js";
2
2
  import We from "vue";
3
3
  function Fe($, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.2-dev.195",
3
+ "version": "1.0.2-dev.196",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -1,8 +1,11 @@
1
1
  <template>
2
2
  <div class="relative">
3
3
  <div :class="{ 'opacity-50 min-h-64': loading }">
4
+ <!-- Keep the previous content while reloading, dimmed under the loader:
5
+ dropping it collapses the page height, which makes the browser clamp
6
+ the scroll and throws the reader back to the top. -->
4
7
  <slot
5
- v-if="!loading && !hasError && data"
8
+ v-if="!hasError && data"
6
9
  :data="data"
7
10
  />
8
11
  <slot
@@ -104,6 +104,7 @@ import { computed, useTemplateRef } from 'vue'
104
104
  import { useRoute } from 'vue-router'
105
105
  import { useTranslation } from '../composables/useTranslation'
106
106
  import { clampPage, getVisiblePages } from '../functions/paginate'
107
+ import { scrollToBlockTop } from '../functions/scroll'
107
108
 
108
109
  type Props = {
109
110
  /**
@@ -141,10 +142,7 @@ const nav = useTemplateRef('navRef')
141
142
  function change(index: number) {
142
143
  emit('change', index)
143
144
 
144
- if (!nav.value || !nav.value.parentElement) return
145
-
146
- nav.value.parentElement.style.scrollMarginTop = '100px'
147
- nav.value.parentElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
145
+ scrollToBlockTop(nav.value?.parentElement)
148
146
  }
149
147
 
150
148
  function onClick(index: number) {
@@ -6,7 +6,6 @@
6
6
  >
7
7
  <div
8
8
  v-if="!hideSearchInput"
9
- ref="search"
10
9
  class="flex flex-wrap items-center justify-between"
11
10
  data-cy="search"
12
11
  >
@@ -357,6 +356,7 @@
357
356
  <script setup lang="ts">
358
357
  import { computed, provide, shallowReactive, useSlots, watch, useTemplateRef, type Component, type Ref } from 'vue'
359
358
  import { useRouteQuery } from '@vueuse/router'
359
+ import { useRoute } from 'vue-router'
360
360
  import { RiBookShelfLine, RiBuilding2Line, RiCloseCircleLine, RiDatabase2Line, RiLightbulbLine, RiLineChartLine, RiRssLine, RiTerminalLine } from '@remixicon/vue'
361
361
  import magnifyingGlassSrc from '../../../assets/illustrations/magnifying_glass.svg?url'
362
362
  import { useTranslation } from '../../composables/useTranslation'
@@ -365,6 +365,7 @@ import { configKey, forEachActiveCustomFilter, isCustomFilterActive, searchFilte
365
365
  import { useStableQueryParams } from '../../composables/useStableQueryParams'
366
366
  import { useComponentsConfig } from '../../config'
367
367
  import { useFetch } from '../../functions/api'
368
+ import { scrollToBlockTop } from '../../functions/scroll'
368
369
  import type { AsyncDataRequestStatus } from '../../functions/api.types'
369
370
  import type { Dataset } from '../../types/datasets'
370
371
  import type { Dataservice } from '../../types/dataservices'
@@ -423,6 +424,7 @@ if (!currentType.value) currentType.value = configKey(props.config[0] ?? { class
423
424
 
424
425
  const { t } = useTranslation()
425
426
  const componentsConfig = useComponentsConfig()
427
+ const route = useRoute()
426
428
 
427
429
  // Custom filter registry for useSearchFilter composable
428
430
  const customFilterRegistry = shallowReactive(new Map<string, CustomFilterEntry>())
@@ -797,14 +799,15 @@ function getFacets(key: string): FacetItem[] | undefined {
797
799
  }
798
800
 
799
801
  // Scroll handling
800
- const searchRef = useTemplateRef('search')
802
+ const resultsRef = useTemplateRef('results')
801
803
 
802
- function scrollToTop() {
803
- searchRef.value?.scrollIntoView({ behavior: 'smooth' })
804
- }
804
+ // Every criteria lives in the URL, custom filters included, so watching the
805
+ // query covers them all: whenever the result list is replaced, bring its top
806
+ // back into view rather than leaving the reader in the middle of a list they
807
+ // have not seen yet.
808
+ watch(() => route.query, () => scrollToBlockTop(resultsRef.value))
805
809
 
806
810
  function changePage(newPage: number) {
807
811
  page.value = newPage
808
- scrollToTop()
809
812
  }
810
813
  </script>
@@ -24,25 +24,28 @@ export type TabularProfileResponse = {
24
24
  indexes: null
25
25
  }
26
26
 
27
+ // Only CSV resources go through csv-detective: for the other formats the API
28
+ // indexes (parquet…), the profile is reduced to the column names, their types
29
+ // and the row count. Everything csv-detective produces is therefore optional.
27
30
  export type TabularProfile = {
28
31
  header: string[]
29
32
  columns: Record<string, TabularColumnInfo>
30
- formats: Record<string, string[]>
31
- profile: Record<string, TabularColumnProfile>
32
- encoding: string
33
- separator: string
34
- categorical: string[]
35
33
  total_lines: number
36
- nb_duplicates: number
37
- columns_fields: Record<string, TabularColumnInfo>
38
- columns_labels: Record<string, TabularColumnInfo>
39
- header_row_idx: number
40
- heading_columns: number
41
- trailing_columns: number
34
+ formats?: Record<string, string[]>
35
+ profile?: Record<string, TabularColumnProfile>
36
+ encoding?: string
37
+ separator?: string
38
+ categorical?: string[]
39
+ nb_duplicates?: number
40
+ columns_fields?: Record<string, TabularColumnInfo>
41
+ columns_labels?: Record<string, TabularColumnInfo>
42
+ header_row_idx?: number
43
+ heading_columns?: number
44
+ trailing_columns?: number
42
45
  }
43
46
 
44
47
  export type TabularColumnInfo = {
45
- score: number
48
+ score?: number
46
49
  format: string
47
50
  python_type: string
48
51
  }
@@ -38,7 +38,7 @@ export function useColumnMetadata(
38
38
  if (!profile) return 'text'
39
39
  const colInfo = profile.columns[col]
40
40
  if (!colInfo) return 'text'
41
- return resolveColumnType(colInfo, profile.categorical.includes(col))
41
+ return resolveColumnType(colInfo, profile.categorical?.includes(col) ?? false)
42
42
  }
43
43
 
44
44
  const columnTypesMap = computed<Record<string, ColumnType>>(() => {
@@ -72,13 +72,13 @@ export function toChartApi(chartForm: ChartForm): ChartForApi {
72
72
  export function buildColumnsFromProfile(profile: { profile: TabularProfile }): Array<ColumnDefinition> {
73
73
  return profile.profile.header.map((name) => {
74
74
  const colInfo = profile.profile.columns[name]
75
- const isCategorical = profile.profile.categorical.includes(name)
75
+ const isCategorical = profile.profile.categorical?.includes(name) ?? false
76
76
  // The chart stack only branches on 'number' and 'date' (continuous axis,
77
77
  // aggregatable series, time formatting): it has no notion of a year column,
78
78
  // and on an axis a year is a number.
79
79
  const resolvedType = resolveColumnType(colInfo ?? { python_type: 'unknown', format: undefined }, isCategorical)
80
80
  const colType = resolvedType === 'year' ? 'number' : resolvedType
81
- const colProfile = profile.profile.profile[name]
81
+ const colProfile = profile.profile.profile?.[name]
82
82
  return {
83
83
  name,
84
84
  type: colType,
@@ -0,0 +1,20 @@
1
+ // Leaves room for the sticky elements sitting at the top of the viewport.
2
+ const SCROLL_MARGIN = 100
3
+
4
+ /**
5
+ * Bring the top of a block back into view after its content changed (paging,
6
+ * searching, filtering), so the reader restarts at the first result.
7
+ *
8
+ * Does nothing when the top of the block is already on screen: aligning the
9
+ * viewport on a block the reader is already looking at moves the page under
10
+ * them for no reason.
11
+ */
12
+ export function scrollToBlockTop(block: HTMLElement | null | undefined) {
13
+ if (!block) return
14
+
15
+ const { top } = block.getBoundingClientRect()
16
+ if (top >= 0 && top <= window.innerHeight) return
17
+
18
+ block.style.scrollMarginTop = `${SCROLL_MARGIN}px`
19
+ block.scrollIntoView({ behavior: 'smooth', block: 'start' })
20
+ }