@datagouv/components-next 1.0.2-dev.173 → 1.0.2-dev.174

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 (57) hide show
  1. package/dist/{Datafair.client-DrxHU3d_.js → Datafair.client-BU2gspr8.js} +9 -9
  2. package/dist/ImagePreview.client-BU5QNqG_.js +36 -0
  3. package/dist/{JsonPreview.client-CvmhvpbB.js → JsonPreview.client-CvOpMCxW.js} +2 -2
  4. package/dist/{MapContainer.client-9VrKlUPt.js → MapContainer.client-DNOQ5XTT.js} +9 -9
  5. package/dist/{PdfPreview.client-CiPvfzzR.js → PdfPreview.client-DwAvLLq_.js} +438 -427
  6. package/dist/{Pmtiles.client-0R3Z5sYM.js → Pmtiles.client-CgaDrCHk.js} +1341 -1337
  7. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-CrWEJnPl.js +60 -0
  8. package/dist/{XmlPreview.client-N2n5j4WI.js → XmlPreview.client-B24FIhU7.js} +3 -3
  9. package/dist/components-next.css +1 -1
  10. package/dist/components-next.js +144 -138
  11. package/dist/components.css +1 -1
  12. package/dist/{index-D0JOBDBD.js → index-Dw1lR0oe.js} +1 -1
  13. package/dist/{leaflet-src-DxB74Wpp.js → leaflet-src-BoMqG6Yx.js} +1 -1
  14. package/dist/{main-QOs57DKh.js → main-I9G-L-lg.js} +16280 -15512
  15. package/dist/{vue3-xml-viewer.common-BnexQtzg.js → vue3-xml-viewer.common-PTUJZpH4.js} +1 -1
  16. package/package.json +1 -1
  17. package/src/components/AppLink.vue +3 -0
  18. package/src/components/CopyButton.vue +6 -2
  19. package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
  20. package/src/components/ResourceAccordion/ImagePreview.client.vue +3 -3
  21. package/src/components/ResourceAccordion/MapContainer.client.vue +1 -8
  22. package/src/components/ResourceAccordion/PdfPreview.client.vue +36 -12
  23. package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
  24. package/src/components/ResourceAccordion/Pmtiles.client.vue +25 -12
  25. package/src/components/ResourceAccordion/PreviewWrapper.vue +17 -10
  26. package/src/components/ResourceAccordion/ResourceAccordion.vue +4 -1
  27. package/src/components/ResourceExplorer/ResourceExplorer.vue +98 -209
  28. package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +74 -0
  29. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +103 -71
  30. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +123 -187
  31. package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
  32. package/src/components/ResourceExplorer/ResourceSelector.vue +24 -86
  33. package/src/components/ResourceExplorer/ResourceViewerHeader.vue +115 -0
  34. package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +94 -0
  35. package/src/components/ResourceListItem.vue +154 -0
  36. package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
  37. package/src/components/TabularExplorer/TabularCell.vue +2 -4
  38. package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
  39. package/src/components/TabularExplorer/TabularExplorer.vue +63 -994
  40. package/src/components/TabularExplorer/TabularFilterContent.vue +2 -10
  41. package/src/components/TabularExplorer/TabularFilterPopover.vue +5 -6
  42. package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
  43. package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
  44. package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
  45. package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
  46. package/src/components/TabularExplorer/TabularTable.vue +481 -0
  47. package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
  48. package/src/components/TabularExplorer/types.ts +0 -5
  49. package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
  50. package/src/components/TabularExplorer/useTabularContext.ts +82 -0
  51. package/src/composables/useDatasetResources.ts +164 -0
  52. package/src/composables/useResizable.ts +46 -0
  53. package/src/composables/useResourceCapabilities.ts +20 -7
  54. package/src/functions/resources.ts +19 -0
  55. package/src/main.ts +8 -0
  56. package/dist/ImagePreview.client-DHrr-JCM.js +0 -36
  57. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-B0PyOeL6.js +0 -58
@@ -1,4 +1,4 @@
1
- import { g as Ke } from "./main-QOs57DKh.js";
1
+ import { g as Ke } from "./main-I9G-L-lg.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.173",
3
+ "version": "1.0.2-dev.174",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -3,6 +3,7 @@
3
3
  :is="config?.appLink"
4
4
  v-if="config.appLink"
5
5
  :to
6
+ :replace
6
7
  >
7
8
  <slot />
8
9
  </component>
@@ -18,6 +19,7 @@
18
19
  <RouterLink
19
20
  v-else
20
21
  :to
22
+ :replace
21
23
  ><slot /></RouterLink>
22
24
  </template>
23
25
 
@@ -33,6 +35,7 @@ const { locale } = useTranslation()
33
35
 
34
36
  const props = defineProps<{
35
37
  to: string | RouteLocationRaw | null | undefined
38
+ replace?: boolean
36
39
  }>()
37
40
 
38
41
  const isExternal = computed(() => {
@@ -17,7 +17,7 @@
17
17
  />
18
18
  <span
19
19
  class="copy-label"
20
- :class="{ 'sr-only': hideLabel }"
20
+ :class="{ 'sr-only': hideLabel || iconOnly }"
21
21
  >{{ copiedLabel }}</span>
22
22
  </span>
23
23
  <span
@@ -32,7 +32,7 @@
32
32
  />
33
33
  <span
34
34
  class="copy-link copy-label"
35
- :class="{ 'sr-only': hideLabel }"
35
+ :class="{ 'sr-only': hideLabel || iconOnly }"
36
36
  >{{ label }}</span>
37
37
  </span>
38
38
  </button>
@@ -49,9 +49,13 @@ const props = withDefaults(defineProps<{
49
49
  label: string
50
50
  copiedLabel: string
51
51
  hideLabel?: boolean
52
+ // Like hideLabel but keeps the default icon and has no bordered box — just the
53
+ // plain copy icon with an sr-only label.
54
+ iconOnly?: boolean
52
55
  reverse?: boolean
53
56
  }>(), {
54
57
  hideLabel: false,
58
+ iconOnly: false,
55
59
  reverse: false,
56
60
  })
57
61
 
@@ -10,7 +10,7 @@
10
10
  />
11
11
  </div>
12
12
  <PreviewUnavailable v-else>
13
- {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
13
+ {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le pour le consulter.") }}
14
14
  </PreviewUnavailable>
15
15
  </div>
16
16
  </template>
@@ -2,12 +2,12 @@
2
2
  <div class="text-xs">
3
3
  <PreviewUnavailable v-if="!isSizeAllowed">
4
4
  {{ fileSizeBytes
5
- ? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.", { fileType })
6
- : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
5
+ ? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le pour le consulter.", { fileType })
6
+ : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le pour le consulter.")
7
7
  }}
8
8
  </PreviewUnavailable>
9
9
  <PreviewUnavailable v-else-if="hasError">
10
- {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
10
+ {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le pour le consulter.") }}
11
11
  </PreviewUnavailable>
12
12
  <img
13
13
  v-else
@@ -4,8 +4,8 @@
4
4
  </PreviewUnavailable>
5
5
  <div
6
6
  v-else
7
- id="map"
8
7
  ref="mapRef"
8
+ class="size-full"
9
9
  />
10
10
  </template>
11
11
 
@@ -128,10 +128,3 @@ onMounted(() => {
128
128
  displayMap()
129
129
  })
130
130
  </script>
131
-
132
- <style>
133
- #map {
134
- width: 100%;
135
- height: 500px;
136
- }
137
- </style>
@@ -1,23 +1,44 @@
1
1
  <template>
2
2
  <PreviewWrapper
3
- v-slot="{ data }"
4
3
  file-type="PDF"
5
4
  :resource="resource"
6
5
  :max-size="config.maxPdfPreviewByteSize"
7
6
  :load="load"
7
+ :fill="fill"
8
8
  @loaded="renderAllPages"
9
9
  >
10
- <div
11
- ref="containerRef"
12
- class="w-full overflow-y-auto max-h-[80vh] space-y-3"
13
- >
14
- <canvas
15
- v-for="page in (data as PDFDocumentProxy).numPages"
16
- :key="page"
17
- :ref="(el) => setCanvasRef(el as HTMLCanvasElement, page)"
18
- class="w-full"
19
- />
20
- </div>
10
+ <!-- While pdfjs downloads/parses the document, show the A4 skeleton instead of a
11
+ bare "loading" line — it matches the reader framing below seamlessly. -->
12
+ <template #loading>
13
+ <PdfSkeleton :fill="fill" />
14
+ </template>
15
+
16
+ <!-- Cap the page width (A4 never needs more) and center on a light backdrop so
17
+ it reads like a document reader. The width cap applies in both modes since
18
+ the render width follows the container; `fill` only drives the height
19
+ (fullscreen fills down, inline stays capped). The scroll container is a plain
20
+ block (not a flex) so the trailing `pb` is reliably scrollable — flex + overflow
21
+ drops the end padding of flex items. -->
22
+ <template #default="{ data }">
23
+ <div
24
+ class="overflow-y-auto bg-gray-100"
25
+ :class="fill ? 'min-h-0 flex-1' : 'max-h-[80vh]'"
26
+ >
27
+ <div class="flex justify-center p-4 pb-16">
28
+ <div
29
+ ref="containerRef"
30
+ class="w-[800px] max-w-full space-y-3"
31
+ >
32
+ <canvas
33
+ v-for="page in (data as PDFDocumentProxy).numPages"
34
+ :key="page"
35
+ :ref="(el) => setCanvasRef(el as HTMLCanvasElement, page)"
36
+ class="w-full bg-white shadow-sm"
37
+ />
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </template>
21
42
  </PreviewWrapper>
22
43
  </template>
23
44
 
@@ -27,6 +48,7 @@ import * as pdfjsLib from 'pdfjs-dist'
27
48
  import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
28
49
  import type { PDFDocumentProxy } from 'pdfjs-dist'
29
50
  import PreviewWrapper from './PreviewWrapper.vue'
51
+ import PdfSkeleton from './PdfSkeleton.vue'
30
52
  import { useComponentsConfig } from '../../config'
31
53
  import type { Resource } from '../../types/resources'
32
54
 
@@ -34,6 +56,8 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
34
56
 
35
57
  const props = defineProps<{
36
58
  resource: Resource
59
+ // Fill the available height (fullscreen) instead of the default capped height.
60
+ fill?: boolean
37
61
  }>()
38
62
 
39
63
  const config = useComponentsConfig()
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <!-- An A4 sheet on the reader backdrop with a few pulsing text lines, matching the
3
+ real PdfPreview framing (centered, capped ~800px, gray backdrop). -->
4
+ <div
5
+ class="overflow-hidden bg-gray-100"
6
+ :class="fill ? 'min-h-0 flex-1' : 'max-h-[80vh]'"
7
+ role="status"
8
+ :aria-label="t('Chargement de l\'aperçu…')"
9
+ >
10
+ <div class="flex justify-center p-4">
11
+ <div class="aspect-[210/297] w-[800px] max-w-full bg-white p-10 shadow-sm">
12
+ <div class="animate-pulse-placeholder space-y-6">
13
+ <div class="space-y-3">
14
+ <div class="h-5 w-1/2 rounded bg-gray-200" />
15
+ <div class="h-3 w-full rounded bg-gray-200" />
16
+ <div class="h-3 w-full rounded bg-gray-200" />
17
+ <div class="h-3 w-5/6 rounded bg-gray-200" />
18
+ </div>
19
+ <div class="space-y-3">
20
+ <div class="h-3 w-full rounded bg-gray-200" />
21
+ <div class="h-3 w-11/12 rounded bg-gray-200" />
22
+ <div class="h-3 w-full rounded bg-gray-200" />
23
+ <div class="h-3 w-3/4 rounded bg-gray-200" />
24
+ </div>
25
+ <div class="space-y-3">
26
+ <div class="h-3 w-full rounded bg-gray-200" />
27
+ <div class="h-3 w-5/6 rounded bg-gray-200" />
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ import { useTranslation } from '../../composables/useTranslation'
37
+
38
+ defineProps<{
39
+ // Fill the available height (fullscreen) instead of the default capped height.
40
+ fill?: boolean
41
+ }>()
42
+
43
+ const { t } = useTranslation()
44
+ </script>
@@ -1,15 +1,15 @@
1
1
  <template>
2
- <div>
2
+ <div class="flex h-full flex-col">
3
3
  <PreviewUnavailable v-if="hasError">
4
4
  {{ t("L'aperçu cartographique de ce fichier n'a pas pu être chargé.") }}
5
5
  </PreviewUnavailable>
6
6
  <div
7
7
  v-else
8
- class="-mx-4"
8
+ class="flex min-h-0 flex-1 flex-col"
9
9
  >
10
10
  <div
11
11
  v-if="pmtilesViewerUrl"
12
- class="bg-blue-100 text-datagouv fr-hidden fr-unhidden-md p-4"
12
+ class="bg-blue-100 text-datagouv fr-hidden fr-unhidden-md p-4 shrink-0"
13
13
  >
14
14
  <div class="fr-grid-row fr-grid-row--middle fr-grid-row--gutters">
15
15
  <div
@@ -35,12 +35,17 @@
35
35
  </p>
36
36
  </div>
37
37
  </div>
38
- <div
39
- ref="containerRef"
40
- style="height: 600px;"
41
- />
42
- <div class="fr-px-5v fr-pt-5v">
43
- {{ t("Aperçu de la carte mis à jour le {date}", { date: lastUpdate }) }}
38
+ <div class="relative min-h-0 flex-1">
39
+ <div
40
+ ref="containerRef"
41
+ class="size-full"
42
+ />
43
+ <!-- Freshness caption overlaid inside the map (bottom-left, the corner left
44
+ free by the navigation/geolocate controls top-right and the attribution
45
+ bottom-right) so the map itself can use the whole height. -->
46
+ <div class="pointer-events-none absolute bottom-2 left-2 z-10 rounded bg-white/90 px-2 py-1 text-[12px] leading-4 text-gray-medium shadow-sm">
47
+ {{ t("Aperçu de la carte mis à jour le {date}", { date: lastUpdate }) }}
48
+ </div>
44
49
  </div>
45
50
  </div>
46
51
  </div>
@@ -104,14 +109,22 @@ async function displayMap() {
104
109
  const p = new PMTiles(pmtilesUrl.value)
105
110
  protocol.add(p)
106
111
 
107
- p.getHeader().then((h: { maxZoom: number, centerLon: number, centerLat: number }) => {
112
+ p.getHeader().then((h: { minLon: number, minLat: number, maxLon: number, maxLat: number, centerZoom: number, centerLon: number, centerLat: number }) => {
113
+ // Frame the data's bounding box (carried by the archive header) so we land on
114
+ // the data. Fall back to the header center when the bbox is degenerate — an
115
+ // archive with a single feature, or bounds left unset (all zeroes).
116
+ const hasBounds = h.minLon < h.maxLon && h.minLat < h.maxLat
108
117
  const map = new maplibregl.Map({
109
118
  container: container.value!,
110
119
  // JSON import is inferred too loosely (e.g. center as number[]) to match
111
120
  // maplibre's strict StyleSpecification, so cast through unknown.
112
121
  style: styleVector as unknown as maplibregl.StyleSpecification,
113
- zoom: h.maxZoom - 2,
114
- center: [h.centerLon, h.centerLat],
122
+ ...(hasBounds
123
+ ? {
124
+ bounds: [[h.minLon, h.minLat], [h.maxLon, h.maxLat]] as [[number, number], [number, number]],
125
+ fitBoundsOptions: { padding: 24 },
126
+ }
127
+ : { center: [h.centerLon, h.centerLat] as [number, number], zoom: h.centerZoom }),
115
128
  })
116
129
  map.addControl(new maplibregl.NavigationControl())
117
130
  map.addControl(
@@ -1,29 +1,34 @@
1
1
  <template>
2
- <div class="text-xs">
2
+ <div
3
+ class="text-xs"
4
+ :class="fill ? 'flex min-h-0 flex-1 flex-col' : ''"
5
+ >
3
6
  <slot
4
7
  v-if="data !== null"
5
8
  :data="data"
6
9
  />
7
- <div
10
+ <slot
8
11
  v-else-if="loading"
9
- class="text-gray-medium"
12
+ name="loading"
10
13
  >
11
- {{ t("Chargement de l'aperçu {fileType}...", { fileType }) }}
12
- </div>
14
+ <div class="text-gray-medium">
15
+ {{ t("Chargement de l'aperçu {fileType}...", { fileType }) }}
16
+ </div>
17
+ </slot>
13
18
  <PreviewUnavailable v-else-if="!isSizeAllowed">
14
19
  {{ fileSizeBytes
15
- ? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.", { fileType })
16
- : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
20
+ ? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le pour le consulter.", { fileType })
21
+ : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le pour le consulter.")
17
22
  }}
18
23
  </PreviewUnavailable>
19
24
  <PreviewUnavailable v-else-if="corsStatus === 'blocked'">
20
- {{ t("Ce fichier {fileType} ne peut pas être prévisualisé car il est hébergé sur un site distant qui restreint l'accès (CORS). Téléchargez-le depuis l'onglet Téléchargements.", { fileType }) }}
25
+ {{ t("Ce fichier {fileType} ne peut pas être prévisualisé car il est hébergé sur un site distant qui restreint l'accès (CORS). Téléchargez-le pour le consulter.", { fileType }) }}
21
26
  </PreviewUnavailable>
22
27
  <PreviewUnavailable v-else-if="error === 'network'">
23
- {{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le depuis l'onglet Téléchargements.") }}
28
+ {{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le pour le consulter.") }}
24
29
  </PreviewUnavailable>
25
30
  <PreviewUnavailable v-else-if="error">
26
- {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
31
+ {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le pour le consulter.") }}
27
32
  </PreviewUnavailable>
28
33
  </div>
29
34
  </template>
@@ -40,6 +45,8 @@ const props = defineProps<{
40
45
  resource: Resource
41
46
  maxSize: number | undefined
42
47
  load: () => Promise<unknown>
48
+ // Fill the available height (fullscreen) instead of sizing to content.
49
+ fill?: boolean
43
50
  }>()
44
51
 
45
52
  const emit = defineEmits<{
@@ -197,7 +197,10 @@
197
197
  :key="tab.key"
198
198
  class="px-4"
199
199
  >
200
- <div v-if="tab.key === 'map'">
200
+ <div
201
+ v-if="tab.key === 'map'"
202
+ class="-mx-4 h-[600px]"
203
+ >
201
204
  <Pmtiles
202
205
  v-if="hasPmtiles"
203
206
  :resource="resource"