@datagouv/components-next 1.2.0 → 1.3.0

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 (47) hide show
  1. package/README.md +7 -6
  2. package/dist/{Control-ZFh5ta_U.js → Control-b-43UT2B.js} +10 -10
  3. package/dist/{Datafair.client-rf4T1IkA.js → Datafair.client-BrjISlaZ.js} +4 -4
  4. package/dist/{Event-DSQcW7OF.js → Event-py_AAwJY.js} +18 -18
  5. package/dist/{Image-BijNEG0p.js → Image-BHQYZWzH.js} +23 -23
  6. package/dist/{JsonPreview.client-dzar6iuh.js → JsonPreview.client-DivFAnbN.js} +5 -5
  7. package/dist/{Map-BUtPf5GN.js → Map-CtP7leHl.js} +1313 -1173
  8. package/dist/{MapContainer.client-D-MoRNhG.js → MapContainer.client-Cq8tNLZZ.js} +17 -17
  9. package/dist/{OSM-D4MTdBtk.js → OSM-gdDSIsDA.js} +15 -23
  10. package/dist/{PdfPreview.client-DoDYLmJD.js → PdfPreview.client-BNBgLsK2.js} +6 -10
  11. package/dist/Pmtiles.client-CQi5Rw8W.js +26123 -0
  12. package/dist/{PreviewWrapper.vue_vue_type_script_setup_true_lang-BRNYswg3.js → PreviewWrapper.vue_vue_type_script_setup_true_lang-C3dDzrIr.js} +2 -2
  13. package/dist/{ScaleLine-hJQIqcZm.js → ScaleLine-hG-FhNqy.js} +2 -2
  14. package/dist/{Tile-Dcl7oIVu.js → Tile-D6rFonpT.js} +163 -166
  15. package/dist/{TileImage-BJeHipMX.js → TileImage-cfrEgz5y.js} +131 -119
  16. package/dist/{View-xp_P_OHw.js → View-vp7vDb9q.js} +169 -119
  17. package/dist/{XmlPreview.client-cOhwff6P.js → XmlPreview.client-XCsNvnsZ.js} +5 -5
  18. package/dist/{common-BjQlan3k.js → common-C8efyyuc.js} +49 -49
  19. package/dist/components-next.css +1 -52
  20. package/dist/components-next.js +157 -153
  21. package/dist/components.css +2 -2
  22. package/dist/{index-NofRBuyf.js → index-CXUamc8Z.js} +6382 -6057
  23. package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-BRKv9Ksv.js} +162 -164
  24. package/dist/{main-Iz1ZCL6k.js → main-DKu5SOAl.js} +20671 -21654
  25. package/dist/{proj-CsNo9yH1.js → proj-CsjVeojN.js} +322 -306
  26. package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
  27. package/dist/{tilecoord-A0fLnBZr.js → tilecoord-CPcQJnrF.js} +270 -261
  28. package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
  29. package/dist/{vue3-xml-viewer.common-tVI9uXUz.js → vue3-xml-viewer.common-BIaEIFYG.js} +1 -1
  30. package/package.json +34 -34
  31. package/src/components/ResourceAccordion/Pmtiles.client.vue +3 -1
  32. package/src/components/ResourceAccordion/ResourceAccordion.vue +2 -2
  33. package/src/components/ResourceExplorer/ResourceExplorer.vue +8 -9
  34. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +4 -1
  35. package/src/components/Search/GlobalSearch.vue +16 -2
  36. package/src/components/TabularExplorer/TabularCellPopover.vue +2 -2
  37. package/src/components/TabularExplorer/TabularExplorer.vue +212 -74
  38. package/src/components/TabularExplorer/TabularFilterContent.vue +15 -13
  39. package/src/components/TabularExplorer/TabularFilterPopover.vue +21 -26
  40. package/src/components/TabularExplorer/types.ts +9 -9
  41. package/src/composables/useResourceCapabilities.ts +1 -1
  42. package/src/composables/useSearchFilter.ts +7 -5
  43. package/src/functions/api.ts +13 -7
  44. package/src/functions/resources.ts +5 -0
  45. package/src/functions/tabular.ts +103 -1
  46. package/src/types/resources.ts +0 -1
  47. package/dist/Pmtiles.client-Dzm01Zfm.js +0 -25185
@@ -1,5 +1,5 @@
1
1
  /** Response from /api/resources/{rid}/data/ */
2
- export interface TabularDataResponse {
2
+ export type TabularDataResponse = {
3
3
  data: TabularRow[]
4
4
  meta: {
5
5
  page: number
@@ -17,11 +17,11 @@ export interface TabularDataResponse {
17
17
  export type TabularRow = Record<string, unknown> & { __id: number }
18
18
 
19
19
  /** Response from /api/resources/{rid}/profile/ */
20
- export interface TabularProfileResponse {
20
+ export type TabularProfileResponse = {
21
21
  profile: TabularProfile
22
22
  }
23
23
 
24
- export interface TabularProfile {
24
+ export type TabularProfile = {
25
25
  header: string[]
26
26
  columns: Record<string, TabularColumnInfo>
27
27
  formats: Record<string, string[]>
@@ -38,13 +38,13 @@ export interface TabularProfile {
38
38
  trailing_columns: number
39
39
  }
40
40
 
41
- export interface TabularColumnInfo {
41
+ export type TabularColumnInfo = {
42
42
  score: number
43
43
  format: string
44
44
  python_type: string
45
45
  }
46
46
 
47
- export interface TabularColumnProfile {
47
+ export type TabularColumnProfile = {
48
48
  tops: TabularTopValue[]
49
49
  nb_distinct: number
50
50
  nb_missing_values: number
@@ -54,14 +54,14 @@ export interface TabularColumnProfile {
54
54
  mean?: number
55
55
  }
56
56
 
57
- export interface TabularTopValue {
57
+ export type TabularTopValue = {
58
58
  value: string
59
59
  count: number
60
60
  }
61
61
 
62
62
  export type ColumnType = 'number' | 'categorical' | 'text' | 'date' | 'boolean'
63
63
 
64
- export interface ColumnFilters {
64
+ export type ColumnFilters = {
65
65
  in?: string[]
66
66
  exact?: string
67
67
  min?: number
@@ -72,12 +72,12 @@ export interface ColumnFilters {
72
72
 
73
73
  export type SortDirection = 'asc' | 'desc'
74
74
 
75
- export interface SortConfig {
75
+ export type SortConfig = {
76
76
  column: string
77
77
  direction: SortDirection
78
78
  }
79
79
 
80
- export interface BadgeStyle {
80
+ export type BadgeStyle = {
81
81
  backgroundColor: string
82
82
  color: string
83
83
  }
@@ -108,7 +108,7 @@ export function useResourceCapabilities(
108
108
  options.push({ key: 'downloads', label: t('Téléchargements') })
109
109
 
110
110
  if (hasTabularData.value) {
111
- options.push({ key: 'swagger', label: t('Swagger') })
111
+ options.push({ key: 'api', label: t('API') })
112
112
  }
113
113
 
114
114
  return options
@@ -94,10 +94,12 @@ export function useSearchFilter(
94
94
  })
95
95
 
96
96
  onScopeDispose(() => {
97
- // Clear the URL param when the scope ends. This mirrors GlobalSearch's
98
- // own `watch(currentType)` logic that drops built-in filters which don't
99
- // apply to the new type: a custom filter's applicability is signalled
100
- // by the consumer via `v-if`, so its unmount is the equivalent signal.
97
+ // Type-scoped filters (typeKeys set) are cleared by GlobalSearch's
98
+ // watch(currentType) instead, so their URL update batches with the other
99
+ // type-change resets via useRouteQuery rather than racing them with the
100
+ // separate router.replace below. Here we only clear filters that apply to
101
+ // all types: their unmount is a consumer signal unrelated to a type switch,
102
+ // which GlobalSearch has no way to detect.
101
103
  //
102
104
  // We cannot use `value.value = defaultValue` here because VueUse's
103
105
  // useRouteQuery registers its own tryOnScopeDispose cleanup that zeroes
@@ -105,7 +107,7 @@ export function useSearchFilter(
105
107
  // The setter then sees `query === v` and early-returns without ever
106
108
  // calling router.replace(). Instead we read the live route.query directly
107
109
  // (which is router state, not affected by that cleanup) and push the update.
108
- if (route.query[urlParam] !== undefined) {
110
+ if (normalizedTypeKeys === undefined && route.query[urlParam] !== undefined) {
109
111
  const { [urlParam]: _removed, ...restQuery } = route.query
110
112
  router.replace({
111
113
  query: defaultValue === undefined ? restQuery : { ...restQuery, [urlParam]: String(defaultValue) },
@@ -26,7 +26,7 @@ export async function useFetch<DataT, ErrorT = never>(
26
26
  const error: Ref<ErrorT | null> = ref(null)
27
27
  const status = ref<AsyncDataRequestStatus>('idle')
28
28
 
29
- const execute = async () => {
29
+ const execute = async (signal?: AbortSignal) => {
30
30
  const urlValue = toValue(url)
31
31
  if (!urlValue) return
32
32
  const params = deepToValue(options?.params)
@@ -36,13 +36,15 @@ export async function useFetch<DataT, ErrorT = never>(
36
36
  data.value = isRaw
37
37
  // Raw targets another data.gouv service (the Tabular API in TabularExplorer) via its own
38
38
  // absolute URL, so it must stay bare ofetch: no datagouv apiBase, no datagouv auth attached.
39
- ? await ofetch<DataT | null>(urlValue, { params: params ?? query })
39
+ ? await ofetch<DataT | null>(urlValue, { params: params ?? query, signal })
40
40
  // The configured `$fetch` carries baseURL + auth + headers (see the `datagouv` plugin install
41
41
  // for the default one). We only forward the URL and params here.
42
- : await config.$fetch<DataT | null>(urlValue, { baseURL: config.apiBase, params: params ?? query })
42
+ : await config.$fetch<DataT | null>(urlValue, { baseURL: config.apiBase, params: params ?? query, signal })
43
43
  status.value = 'success'
44
44
  }
45
45
  catch (e) {
46
+ // Ignore aborted requests — they're superseded by a newer call, not a real error.
47
+ if (signal?.aborted) return
46
48
  error.value = e as ErrorT
47
49
  status.value = 'error'
48
50
  }
@@ -51,14 +53,18 @@ export async function useFetch<DataT, ErrorT = never>(
51
53
  // When server is false, only start watching after mount (client-side only)
52
54
  if (options?.server === false) {
53
55
  onMounted(() => {
54
- watchEffect(async () => {
55
- await execute()
56
+ watchEffect((onCleanup) => {
57
+ const controller = new AbortController()
58
+ onCleanup(() => controller.abort())
59
+ void execute(controller.signal)
56
60
  })
57
61
  })
58
62
  }
59
63
  else {
60
- watchEffect(async () => {
61
- await execute()
64
+ watchEffect((onCleanup) => {
65
+ const controller = new AbortController()
66
+ onCleanup(() => controller.abort())
67
+ void execute(controller.signal)
62
68
  })
63
69
  }
64
70
 
@@ -91,6 +91,11 @@ export function getResourceTitleId(resource: Resource) {
91
91
  export const RESOURCE_TYPE = readonly(['main', 'documentation', 'update', 'api', 'code', 'other'] as const)
92
92
  export type ResourceType = typeof RESOURCE_TYPE[number]
93
93
 
94
+ // Page size used by ResourceExplorer for its per-type resource fetches.
95
+ // Exported so consumers (e.g. the dataset page's `main` prefetch) can build a
96
+ // byte-for-byte identical cache key and let Nuxt dedupe the request.
97
+ export const RESOURCE_EXPLORER_PAGE_SIZE = 10
98
+
94
99
  export const getResourceLabel = (type: ResourceType, count?: number) => {
95
100
  const { t } = useTranslation()
96
101
  switch (type) {
@@ -4,9 +4,24 @@ import {
4
4
  RiPriceTag3Line,
5
5
  RiText,
6
6
  RiCalendarLine,
7
+ RiCalendarEventLine,
8
+ RiTimeLine,
7
9
  RiCheckboxLine,
10
+ RiMapPin2Line,
11
+ RiGlobalLine,
12
+ RiFocus3Line,
13
+ RiFingerprintLine,
14
+ RiMailLine,
15
+ RiLinksLine,
16
+ RiPhoneLine,
17
+ RiUserLine,
18
+ RiMoneyEuroCircleLine,
19
+ RiPercentLine,
20
+ RiBracesLine,
21
+ RiCodeLine,
8
22
  } from '@remixicon/vue'
9
23
  import { useTranslation } from '../composables/useTranslation'
24
+ import type { TranslationFunction } from '../composables/useTranslation'
10
25
  import type { ColumnFilters, ColumnType } from '../components/TabularExplorer/types'
11
26
 
12
27
  export function hasFilterForColumn(filters: Record<string, ColumnFilters>, column: string): boolean {
@@ -15,7 +30,12 @@ export function hasFilterForColumn(filters: Record<string, ColumnFilters>, colum
15
30
  return !!(f.in?.length || f.exact != null || f.contains || f.null || f.min != null || f.max != null)
16
31
  }
17
32
 
18
- export function buildTypeConfig(t: (s: string) => string): Record<ColumnType, { icon: Component, label: string }> {
33
+ export type TypeDisplay = {
34
+ icon: Component
35
+ label: string
36
+ }
37
+
38
+ export function buildTypeConfig(t: TranslationFunction): Record<ColumnType, TypeDisplay> {
19
39
  return {
20
40
  number: { icon: RiHashtag, label: t('Nombre') },
21
41
  categorical: { icon: RiPriceTag3Line, label: t('Catégoriel') },
@@ -25,6 +45,88 @@ export function buildTypeConfig(t: (s: string) => string): Record<ColumnType, {
25
45
  }
26
46
  }
27
47
 
48
+ // csv-detective formats that carry no semantic meaning beyond the generic
49
+ // display type already resolved by `ColumnType` — these fall back to
50
+ // `buildTypeConfig` (Texte / Nombre / Date / Booléen) in the header.
51
+ export const GENERIC_FORMATS = new Set(['string', 'int', 'float', 'date', 'bool', 'booleen'])
52
+
53
+ // Semantic types detected by csv-detective (see ../../../csv-detective/csv_detective/formats).
54
+ // The list is intentionally exhaustive, but unknown formats are tolerated:
55
+ // `getColumnDisplay` humanizes any format missing from this map.
56
+ export function buildFormatConfig(t: TranslationFunction): Record<string, TypeDisplay> {
57
+ return {
58
+ // Geo — territories & addresses
59
+ adresse: { icon: RiMapPin2Line, label: t('Adresse') },
60
+ commune: { icon: RiMapPin2Line, label: t('Commune') },
61
+ code_commune: { icon: RiMapPin2Line, label: t('Code INSEE commune') },
62
+ code_commune_insee: { icon: RiMapPin2Line, label: t('Code INSEE commune') },
63
+ departement: { icon: RiMapPin2Line, label: t('Département') },
64
+ code_departement: { icon: RiMapPin2Line, label: t('Code département') },
65
+ region: { icon: RiMapPin2Line, label: t('Région') },
66
+ code_region: { icon: RiMapPin2Line, label: t('Code région') },
67
+ code_epci: { icon: RiMapPin2Line, label: t('Code EPCI') },
68
+ code_postal: { icon: RiMapPin2Line, label: t('Code postal') },
69
+ insee_canton: { icon: RiMapPin2Line, label: t('Canton') },
70
+ code_fantoir: { icon: RiMapPin2Line, label: t('Code FANTOIR') },
71
+ id_rnb: { icon: RiMapPin2Line, label: t('Identifiant RNB') },
72
+ // Geo — countries
73
+ pays: { icon: RiGlobalLine, label: t('Pays') },
74
+ iso_country_code_alpha2: { icon: RiGlobalLine, label: t('Code pays (ISO α-2)') },
75
+ iso_country_code_alpha3: { icon: RiGlobalLine, label: t('Code pays (ISO α-3)') },
76
+ iso_country_code_numeric: { icon: RiGlobalLine, label: t('Code pays (ISO num.)') },
77
+ // Geo — coordinates
78
+ latitude_wgs: { icon: RiFocus3Line, label: t('Latitude') },
79
+ latitude_wgs_fr_metropole: { icon: RiFocus3Line, label: t('Latitude') },
80
+ longitude_wgs: { icon: RiFocus3Line, label: t('Longitude') },
81
+ longitude_wgs_fr_metropole: { icon: RiFocus3Line, label: t('Longitude') },
82
+ latitude_l93: { icon: RiFocus3Line, label: t('Latitude (Lambert 93)') },
83
+ longitude_l93: { icon: RiFocus3Line, label: t('Longitude (Lambert 93)') },
84
+ latlon_wgs: { icon: RiFocus3Line, label: t('Latitude / longitude') },
85
+ lonlat_wgs: { icon: RiFocus3Line, label: t('Longitude / latitude') },
86
+ // Identifiers
87
+ siren: { icon: RiFingerprintLine, label: t('SIREN') },
88
+ siret: { icon: RiFingerprintLine, label: t('SIRET') },
89
+ code_rna: { icon: RiFingerprintLine, label: t('Identifiant RNA') },
90
+ code_waldec: { icon: RiFingerprintLine, label: t('Identifiant WALDEC') },
91
+ code_import: { icon: RiFingerprintLine, label: t('Code Import') },
92
+ uai: { icon: RiFingerprintLine, label: t('UAI') },
93
+ insee_ape700: { icon: RiFingerprintLine, label: t('Code APE') },
94
+ code_csp_insee: { icon: RiFingerprintLine, label: t('Code CSP') },
95
+ uuid: { icon: RiFingerprintLine, label: t('UUID') },
96
+ mongo_object_id: { icon: RiFingerprintLine, label: t('Identifiant Mongo') },
97
+ // Contact
98
+ email: { icon: RiMailLine, label: t('Email') },
99
+ url: { icon: RiLinksLine, label: t('URL') },
100
+ tel_fr: { icon: RiPhoneLine, label: t('Téléphone') },
101
+ username: { icon: RiUserLine, label: t('Nom d\'utilisateur') },
102
+ // People
103
+ sexe: { icon: RiUserLine, label: t('Sexe') },
104
+ csp_insee: { icon: RiUserLine, label: t('Catégorie socio-pro.') },
105
+ // Quantities
106
+ money: { icon: RiMoneyEuroCircleLine, label: t('Montant') },
107
+ percent: { icon: RiPercentLine, label: t('Pourcentage') },
108
+ // Temporal
109
+ year: { icon: RiCalendarLine, label: t('Année') },
110
+ date_fr: { icon: RiCalendarLine, label: t('Date') },
111
+ jour_de_la_semaine: { icon: RiCalendarEventLine, label: t('Jour de la semaine') },
112
+ mois_de_lannee: { icon: RiCalendarEventLine, label: t('Mois de l\'année') },
113
+ datetime_naive: { icon: RiTimeLine, label: t('Date et heure') },
114
+ datetime_aware: { icon: RiTimeLine, label: t('Date et heure') },
115
+ datetime_rfc822: { icon: RiTimeLine, label: t('Date et heure') },
116
+ // Structured
117
+ json: { icon: RiBracesLine, label: t('JSON') },
118
+ geojson: { icon: RiBracesLine, label: t('GeoJSON') },
119
+ binary: { icon: RiCodeLine, label: t('Binaire') },
120
+ }
121
+ }
122
+
123
+ // Tolerant fallback for a semantic format we don't explicitly know about:
124
+ // "code_truc_machin" -> "Code truc machin".
125
+ export function humanizeFormat(format: string): string {
126
+ const spaced = format.replace(/_/g, ' ').trim()
127
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1)
128
+ }
129
+
28
130
  export function useFormatTabular() {
29
131
  const { locale } = useTranslation()
30
132
 
@@ -46,7 +46,6 @@ export type OgcLayerInfo = { name: string, default_crs: string | null }
46
46
 
47
47
  export type WfsMetadata = {
48
48
  format: string
49
- layers: Array<OgcLayerInfo>
50
49
  version: string
51
50
  detected_layer: OgcLayerInfo | null
52
51
  output_formats: Array<string>