@datagouv/components-next 1.2.0 → 1.4.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.
- package/README.md +7 -6
- package/dist/{Control-ZFh5ta_U.js → Control-B9WQxlpJ.js} +1 -1
- package/dist/{Datafair.client-rf4T1IkA.js → Datafair.client-D_6yzJUG.js} +9 -9
- package/dist/{Event-DSQcW7OF.js → Event-iXDrp-fy.js} +11 -11
- package/dist/{Image-BijNEG0p.js → Image-mNMBOofs.js} +5 -5
- package/dist/ImagePreview.client-B0iPhFm3.js +36 -0
- package/dist/{JsonPreview.client-dzar6iuh.js → JsonPreview.client-DSl1kiwC.js} +5 -5
- package/dist/{Map-BUtPf5GN.js → Map-BfqKMw0S.js} +1306 -1166
- package/dist/{MapContainer.client-D-MoRNhG.js → MapContainer.client-BSfpk_rT.js} +15 -15
- package/dist/{OSM-D4MTdBtk.js → OSM-CcrUhItr.js} +15 -23
- package/dist/{PdfPreview.client-DoDYLmJD.js → PdfPreview.client-BANFeM6K.js} +441 -434
- package/dist/Pmtiles.client-DH9ZrsZR.js +26333 -0
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-I8EZCgzp.js +60 -0
- package/dist/{ScaleLine-hJQIqcZm.js → ScaleLine-VGQ7nkMS.js} +2 -2
- package/dist/{Tile-Dcl7oIVu.js → Tile-CSpn0Ca8.js} +153 -156
- package/dist/{TileImage-BJeHipMX.js → TileImage-CpbaEFwd.js} +143 -131
- package/dist/{View-xp_P_OHw.js → View-BYBpt-AU.js} +167 -117
- package/dist/{XmlPreview.client-cOhwff6P.js → XmlPreview.client-KCB5mwVf.js} +5 -5
- package/dist/{common-BjQlan3k.js → common-BoeTUnDZ.js} +26 -26
- package/dist/components-next.css +1 -52
- package/dist/components-next.js +201 -170
- package/dist/components.css +2 -2
- package/dist/{index-NofRBuyf.js → index-DHWzX5vK.js} +5999 -5472
- package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-pVQfonBI.js} +162 -164
- package/dist/{main-Iz1ZCL6k.js → main-BDcYkhOc.js} +36671 -29402
- package/dist/{proj-CsNo9yH1.js → proj-DxGbobp7.js} +312 -296
- package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
- package/dist/{tilecoord-A0fLnBZr.js → tilecoord-DvX7WWiA.js} +264 -255
- package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
- package/dist/{vue3-xml-viewer.common-tVI9uXUz.js → vue3-xml-viewer.common-Bdcn54ST.js} +1 -1
- package/package.json +40 -39
- package/src/components/ActivityList/ActivityList.vue +21 -12
- package/src/components/AppLink.vue +3 -0
- package/src/components/BannerAction.vue +6 -2
- package/src/components/Chart/ChartViewer.vue +111 -17
- package/src/components/Chart/ChartViewerWrapper.vue +28 -16
- package/src/components/ChartCard.vue +112 -0
- package/src/components/CopyButton.vue +6 -2
- package/src/components/DataserviceCard.vue +23 -6
- package/src/components/DataserviceQuality.vue +82 -0
- package/src/components/DataserviceQualityInline.vue +31 -0
- package/src/components/DataserviceQualityTooltipContent.vue +79 -0
- package/src/components/DatasetCard.vue +17 -6
- package/src/components/DatasetInformation/DatasetTemporalitySection.vue +7 -4
- package/src/components/DatasetQuality.vue +11 -11
- package/src/components/DatasetQualityInline.vue +2 -2
- package/src/components/DatasetQualityTooltipContent.vue +1 -1
- package/src/components/DiscussionMessageCard.vue +14 -15
- package/src/components/Form/SchemaSelect.vue +2 -0
- package/src/components/FormattedDate.vue +69 -0
- package/src/components/LoadingBlock.vue +4 -1
- package/src/components/ObjectCard.vue +4 -1
- package/src/components/ObjectCardOwner.vue +13 -1
- package/src/components/Pagination.vue +18 -58
- package/src/components/Placeholder.vue +3 -2
- package/src/components/PostCard.vue +13 -15
- package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
- package/src/components/ResourceAccordion/Downloads.vue +19 -7
- package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
- package/src/components/ResourceAccordion/MapContainer.client.vue +1 -8
- package/src/components/ResourceAccordion/Metadata.vue +21 -5
- package/src/components/ResourceAccordion/PdfPreview.client.vue +36 -12
- package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
- package/src/components/ResourceAccordion/Pmtiles.client.vue +30 -19
- package/src/components/ResourceAccordion/Preview.vue +8 -4
- package/src/components/ResourceAccordion/PreviewWrapper.vue +17 -10
- package/src/components/ResourceAccordion/ResourceAccordion.vue +31 -11
- package/src/components/ResourceExplorer/ResourceExplorer.vue +105 -210
- package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +79 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +104 -71
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +140 -184
- package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
- package/src/components/ResourceExplorer/ResourceSelector.vue +24 -86
- package/src/components/ResourceExplorer/ResourceViewerHeader.vue +124 -0
- package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +96 -0
- package/src/components/ResourceListItem.vue +168 -0
- package/src/components/ReuseCard.vue +18 -6
- package/src/components/ReuseDetails.vue +2 -2
- package/src/components/ReuseHorizontalCard.vue +4 -3
- package/src/components/SchemaCard.vue +10 -1
- package/src/components/Search/Filter/AccessTypeFilter.vue +2 -2
- package/src/components/Search/GlobalSearch.vue +28 -10
- package/src/components/Search/SearchInput.vue +1 -1
- package/src/components/Search/Sidemenu.vue +4 -3
- package/src/components/SmallChart.vue +11 -10
- package/src/components/StatBox.vue +13 -5
- package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
- package/src/components/TabularExplorer/TabularCell.vue +7 -8
- package/src/components/TabularExplorer/TabularCellPopover.vue +21 -5
- package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
- package/src/components/TabularExplorer/TabularDateField.vue +41 -0
- package/src/components/TabularExplorer/TabularDateFilter.vue +403 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +127 -780
- package/src/components/TabularExplorer/TabularFilterContent.vue +48 -38
- package/src/components/TabularExplorer/TabularFilterPopover.vue +27 -32
- package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
- package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
- package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
- package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
- package/src/components/TabularExplorer/TabularTable.vue +484 -0
- package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
- package/src/components/TabularExplorer/types.ts +40 -28
- package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
- package/src/components/TabularExplorer/useTabularContext.ts +82 -0
- package/src/components/TopicCard.vue +15 -14
- package/src/composables/useActiveDescendant.ts +5 -0
- package/src/composables/useDatasetResources.ts +164 -0
- package/src/composables/useHasTabularData.ts +2 -1
- package/src/composables/useResizable.ts +46 -0
- package/src/composables/useResourceCapabilities.ts +48 -40
- package/src/composables/useSearchFilter.ts +7 -5
- package/src/composables/useTranslation.ts +24 -9
- package/src/config.ts +1 -0
- package/src/functions/api.ts +13 -7
- package/src/functions/charts.ts +32 -1
- package/src/functions/dates.ts +26 -7
- package/src/functions/helpers.ts +15 -0
- package/src/functions/metrics.ts +57 -56
- package/src/functions/owned.ts +21 -0
- package/src/functions/paginate.ts +46 -0
- package/src/functions/resourceCapabilities.ts +34 -0
- package/src/functions/resources.ts +88 -0
- package/src/functions/schemas.ts +27 -0
- package/src/functions/scroll.ts +20 -0
- package/src/functions/tabular.ts +222 -5
- package/src/functions/tabularApi.ts +1 -43
- package/src/main.ts +32 -6
- package/src/types/dataservices.ts +15 -0
- package/src/types/organizations.ts +10 -0
- package/src/types/resources.ts +0 -1
- package/src/types/reuses.ts +12 -1
- package/src/types/search.ts +2 -2
- package/src/types/visualizations.ts +5 -0
- package/dist/Pmtiles.client-Dzm01Zfm.js +0 -25185
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BRNYswg3.js +0 -61
- /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
- /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
- /package/src/components/{DatasetQualityScore.vue → QualityScore.vue} +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Spacing carries the grouping: what the filter is (operator, dates), then the
|
|
3
|
+
calendar with the bar that drives it, then the actions. -->
|
|
4
|
+
<form
|
|
5
|
+
class="border-b border-black/10 px-3 py-2"
|
|
6
|
+
@submit.prevent="apply"
|
|
7
|
+
>
|
|
8
|
+
<select
|
|
9
|
+
v-model="operator"
|
|
10
|
+
class="h-8 w-full rounded-lg border border-transparent bg-[#f3f3f5] px-2 text-sm focus:border-new-primary focus:outline-none"
|
|
11
|
+
:aria-label="t('Condition du filtre')"
|
|
12
|
+
>
|
|
13
|
+
<option
|
|
14
|
+
v-for="option in operatorOptions"
|
|
15
|
+
:key="option.value"
|
|
16
|
+
:value="option.value"
|
|
17
|
+
>
|
|
18
|
+
{{ option.label }}
|
|
19
|
+
</option>
|
|
20
|
+
</select>
|
|
21
|
+
|
|
22
|
+
<div class="mt-2 flex items-stretch gap-2">
|
|
23
|
+
<TabularDateField
|
|
24
|
+
v-model="start"
|
|
25
|
+
v-model:placeholder="placeholder"
|
|
26
|
+
:label="isRange ? t('Date de début') : t('Date')"
|
|
27
|
+
/>
|
|
28
|
+
<TabularDateField
|
|
29
|
+
v-if="isRange"
|
|
30
|
+
v-model="end"
|
|
31
|
+
v-model:placeholder="placeholder"
|
|
32
|
+
:min-value="start ?? undefined"
|
|
33
|
+
:label="t('Date de fin')"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Month and year are pickers rather than a heading: reaching a date years
|
|
38
|
+
away otherwise takes one click per month. -->
|
|
39
|
+
<div class="mt-4 flex items-center gap-1">
|
|
40
|
+
<select
|
|
41
|
+
:value="placeholder.month"
|
|
42
|
+
class="h-7 min-w-0 flex-1 rounded border border-black/10 bg-white px-1 text-xs focus:border-new-primary focus:outline-none"
|
|
43
|
+
:aria-label="t('Mois')"
|
|
44
|
+
@change="goToMonth(Number(($event.target as HTMLSelectElement).value))"
|
|
45
|
+
>
|
|
46
|
+
<option
|
|
47
|
+
v-for="(label, index) in monthNames"
|
|
48
|
+
:key="label"
|
|
49
|
+
:value="index + 1"
|
|
50
|
+
>
|
|
51
|
+
{{ label }}
|
|
52
|
+
</option>
|
|
53
|
+
</select>
|
|
54
|
+
<select
|
|
55
|
+
:value="placeholder.year"
|
|
56
|
+
class="h-7 w-18 rounded border border-black/10 bg-white px-1 text-xs tabular-nums focus:border-new-primary focus:outline-none"
|
|
57
|
+
:aria-label="t('Année')"
|
|
58
|
+
@change="goToYear(Number(($event.target as HTMLSelectElement).value))"
|
|
59
|
+
>
|
|
60
|
+
<option
|
|
61
|
+
v-for="year in selectableYears"
|
|
62
|
+
:key="year"
|
|
63
|
+
:value="year"
|
|
64
|
+
>
|
|
65
|
+
{{ year }}
|
|
66
|
+
</option>
|
|
67
|
+
</select>
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
class="flex size-7 shrink-0 items-center justify-center rounded text-gray-plain transition-colors hover:bg-gray-some focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-new-primary"
|
|
71
|
+
:aria-label="t('Mois précédent')"
|
|
72
|
+
@click="shiftMonths(-1)"
|
|
73
|
+
>
|
|
74
|
+
<RiArrowLeftSLine
|
|
75
|
+
class="size-4"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
/>
|
|
78
|
+
</button>
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
class="flex size-7 shrink-0 items-center justify-center rounded text-gray-plain transition-colors hover:bg-gray-some focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-new-primary"
|
|
82
|
+
:aria-label="t('Mois suivant')"
|
|
83
|
+
@click="shiftMonths(1)"
|
|
84
|
+
>
|
|
85
|
+
<RiArrowRightSLine
|
|
86
|
+
class="size-4"
|
|
87
|
+
aria-hidden="true"
|
|
88
|
+
/>
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<RangeCalendarRoot
|
|
93
|
+
v-if="isRange"
|
|
94
|
+
v-slot="{ grid, weekDays }"
|
|
95
|
+
v-model="range"
|
|
96
|
+
v-model:placeholder="placeholder"
|
|
97
|
+
:locale="locale"
|
|
98
|
+
fixed-weeks
|
|
99
|
+
weekday-format="short"
|
|
100
|
+
:calendar-label="t('Choisir une plage de dates')"
|
|
101
|
+
>
|
|
102
|
+
<RangeCalendarGrid
|
|
103
|
+
v-for="month in grid"
|
|
104
|
+
:key="month.value.toString()"
|
|
105
|
+
class="w-full border-collapse select-none"
|
|
106
|
+
>
|
|
107
|
+
<RangeCalendarGridHead>
|
|
108
|
+
<RangeCalendarGridRow>
|
|
109
|
+
<RangeCalendarHeadCell
|
|
110
|
+
v-for="day in weekDays"
|
|
111
|
+
:key="day"
|
|
112
|
+
:class="HEAD_CELL_CLASS"
|
|
113
|
+
>
|
|
114
|
+
{{ day }}
|
|
115
|
+
</RangeCalendarHeadCell>
|
|
116
|
+
</RangeCalendarGridRow>
|
|
117
|
+
</RangeCalendarGridHead>
|
|
118
|
+
<RangeCalendarGridBody>
|
|
119
|
+
<RangeCalendarGridRow
|
|
120
|
+
v-for="(week, index) in month.rows"
|
|
121
|
+
:key="`week-${index}`"
|
|
122
|
+
>
|
|
123
|
+
<RangeCalendarCell
|
|
124
|
+
v-for="day in week"
|
|
125
|
+
:key="day.toString()"
|
|
126
|
+
:date="day"
|
|
127
|
+
class="p-0"
|
|
128
|
+
>
|
|
129
|
+
<RangeCalendarCellTrigger
|
|
130
|
+
v-slot="cell"
|
|
131
|
+
:day="day"
|
|
132
|
+
:month="month.value"
|
|
133
|
+
:class="TRIGGER_CLASS"
|
|
134
|
+
>
|
|
135
|
+
<span :class="rangeDayClass(cell)">{{ cell.dayValue }}</span>
|
|
136
|
+
</RangeCalendarCellTrigger>
|
|
137
|
+
</RangeCalendarCell>
|
|
138
|
+
</RangeCalendarGridRow>
|
|
139
|
+
</RangeCalendarGridBody>
|
|
140
|
+
</RangeCalendarGrid>
|
|
141
|
+
</RangeCalendarRoot>
|
|
142
|
+
|
|
143
|
+
<CalendarRoot
|
|
144
|
+
v-else
|
|
145
|
+
v-slot="{ grid, weekDays }"
|
|
146
|
+
v-model="start"
|
|
147
|
+
v-model:placeholder="placeholder"
|
|
148
|
+
:locale="locale"
|
|
149
|
+
fixed-weeks
|
|
150
|
+
weekday-format="short"
|
|
151
|
+
:calendar-label="t('Choisir une date')"
|
|
152
|
+
>
|
|
153
|
+
<CalendarGrid
|
|
154
|
+
v-for="month in grid"
|
|
155
|
+
:key="month.value.toString()"
|
|
156
|
+
class="w-full border-collapse select-none"
|
|
157
|
+
>
|
|
158
|
+
<CalendarGridHead>
|
|
159
|
+
<CalendarGridRow>
|
|
160
|
+
<CalendarHeadCell
|
|
161
|
+
v-for="day in weekDays"
|
|
162
|
+
:key="day"
|
|
163
|
+
:class="HEAD_CELL_CLASS"
|
|
164
|
+
>
|
|
165
|
+
{{ day }}
|
|
166
|
+
</CalendarHeadCell>
|
|
167
|
+
</CalendarGridRow>
|
|
168
|
+
</CalendarGridHead>
|
|
169
|
+
<CalendarGridBody>
|
|
170
|
+
<CalendarGridRow
|
|
171
|
+
v-for="(week, index) in month.rows"
|
|
172
|
+
:key="`week-${index}`"
|
|
173
|
+
>
|
|
174
|
+
<CalendarCell
|
|
175
|
+
v-for="day in week"
|
|
176
|
+
:key="day.toString()"
|
|
177
|
+
:date="day"
|
|
178
|
+
class="p-0"
|
|
179
|
+
>
|
|
180
|
+
<CalendarCellTrigger
|
|
181
|
+
v-slot="cell"
|
|
182
|
+
:day="day"
|
|
183
|
+
:month="month.value"
|
|
184
|
+
:class="TRIGGER_CLASS"
|
|
185
|
+
>
|
|
186
|
+
<span :class="dayClass(cell)">{{ cell.dayValue }}</span>
|
|
187
|
+
</CalendarCellTrigger>
|
|
188
|
+
</CalendarCell>
|
|
189
|
+
</CalendarGridRow>
|
|
190
|
+
</CalendarGridBody>
|
|
191
|
+
</CalendarGrid>
|
|
192
|
+
</CalendarRoot>
|
|
193
|
+
|
|
194
|
+
<div class="mt-4 flex items-center justify-end gap-2">
|
|
195
|
+
<BrandedButton
|
|
196
|
+
color="tertiary"
|
|
197
|
+
size="2xs"
|
|
198
|
+
type="button"
|
|
199
|
+
keep-margins-even-without-borders
|
|
200
|
+
data-testid="clear-date-filter"
|
|
201
|
+
@click="clear"
|
|
202
|
+
>
|
|
203
|
+
{{ t('Effacer') }}
|
|
204
|
+
</BrandedButton>
|
|
205
|
+
<BrandedButton
|
|
206
|
+
color="primary"
|
|
207
|
+
size="2xs"
|
|
208
|
+
type="submit"
|
|
209
|
+
:disabled="!isComplete"
|
|
210
|
+
>
|
|
211
|
+
{{ t('Appliquer') }}
|
|
212
|
+
</BrandedButton>
|
|
213
|
+
</div>
|
|
214
|
+
</form>
|
|
215
|
+
</template>
|
|
216
|
+
|
|
217
|
+
<script setup lang="ts">
|
|
218
|
+
import { computed, ref, shallowRef, watch } from 'vue'
|
|
219
|
+
import { today, getLocalTimeZone, type DateValue } from '@internationalized/date'
|
|
220
|
+
import {
|
|
221
|
+
CalendarCell,
|
|
222
|
+
CalendarCellTrigger,
|
|
223
|
+
CalendarGrid,
|
|
224
|
+
CalendarGridBody,
|
|
225
|
+
CalendarGridHead,
|
|
226
|
+
CalendarGridRow,
|
|
227
|
+
CalendarHeadCell,
|
|
228
|
+
CalendarRoot,
|
|
229
|
+
RangeCalendarCell,
|
|
230
|
+
RangeCalendarCellTrigger,
|
|
231
|
+
RangeCalendarGrid,
|
|
232
|
+
RangeCalendarGridBody,
|
|
233
|
+
RangeCalendarGridHead,
|
|
234
|
+
RangeCalendarGridRow,
|
|
235
|
+
RangeCalendarHeadCell,
|
|
236
|
+
RangeCalendarRoot,
|
|
237
|
+
} from 'reka-ui'
|
|
238
|
+
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/vue'
|
|
239
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
240
|
+
import { parseIsoDate } from '../../functions/tabular'
|
|
241
|
+
import BrandedButton from '../BrandedButton.vue'
|
|
242
|
+
import TabularDateField from './TabularDateField.vue'
|
|
243
|
+
import type { ColumnFilters, DateFilter, DateFilterOperator } from './types'
|
|
244
|
+
|
|
245
|
+
const props = defineProps<{
|
|
246
|
+
column: string
|
|
247
|
+
}>()
|
|
248
|
+
|
|
249
|
+
const filters = defineModel<Record<string, ColumnFilters>>('filters', { default: () => ({}) })
|
|
250
|
+
|
|
251
|
+
const { t, locale } = useTranslation()
|
|
252
|
+
|
|
253
|
+
const operatorOptions = computed<Array<{ value: DateFilterOperator, label: string }>>(() => [
|
|
254
|
+
{ value: 'is', label: t('Est') },
|
|
255
|
+
{ value: 'before', label: t('Est avant') },
|
|
256
|
+
{ value: 'after', label: t('Est après') },
|
|
257
|
+
{ value: 'between', label: t('Est entre') },
|
|
258
|
+
])
|
|
259
|
+
|
|
260
|
+
const HEAD_CELL_CLASS = 'pb-1 text-xs font-normal text-gray-medium'
|
|
261
|
+
const TRIGGER_CLASS = 'group flex h-8 w-full items-center justify-center focus:outline-none'
|
|
262
|
+
|
|
263
|
+
type DayState = {
|
|
264
|
+
selected: boolean
|
|
265
|
+
today: boolean
|
|
266
|
+
outsideView: boolean
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const DAY_BASE_CLASS = 'flex size-8 items-center justify-center text-sm tabular-nums transition-colors'
|
|
270
|
+
|
|
271
|
+
// One utility per axis, never two: `!important` puts every Tailwind class at the
|
|
272
|
+
// same specificity, so a cell carrying two `text-*` is resolved by the order they
|
|
273
|
+
// happen to be emitted in rather than by the order written here.
|
|
274
|
+
function dayText(cell: DayState): string {
|
|
275
|
+
if (cell.selected) return 'font-medium text-white'
|
|
276
|
+
if (cell.today) return 'font-bold text-new-primary'
|
|
277
|
+
return cell.outsideView ? 'text-gray-low' : 'text-gray-title'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function dayClass(cell: DayState): string[] {
|
|
281
|
+
return [
|
|
282
|
+
DAY_BASE_CLASS,
|
|
283
|
+
'rounded-md',
|
|
284
|
+
dayText(cell),
|
|
285
|
+
cell.selected
|
|
286
|
+
? 'bg-new-primary'
|
|
287
|
+
: 'group-hover:bg-gray-some group-focus-visible:ring-2 group-focus-visible:ring-new-primary',
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// The range background has to be continuous across cells, so it is the day
|
|
292
|
+
// element that fills its cell and only the two ends get rounded corners.
|
|
293
|
+
function rangeDayClass(cell: DayState & { highlighted: boolean, selectionStart: boolean, selectionEnd: boolean }): string[] {
|
|
294
|
+
const isEnd = cell.selectionStart || cell.selectionEnd
|
|
295
|
+
const inRange = cell.highlighted || cell.selected
|
|
296
|
+
return [
|
|
297
|
+
DAY_BASE_CLASS,
|
|
298
|
+
'w-full',
|
|
299
|
+
// Only the two ends read as selected here: the days in between are carried by
|
|
300
|
+
// the background, not by the text
|
|
301
|
+
dayText({ ...cell, selected: isEnd }),
|
|
302
|
+
isEnd ? 'bg-new-primary' : inRange ? 'bg-new-primary/10' : 'group-hover:bg-gray-some',
|
|
303
|
+
isEnd || !inRange ? 'rounded-md' : '',
|
|
304
|
+
cell.selectionStart ? 'rounded-r-none' : '',
|
|
305
|
+
cell.selectionEnd ? 'rounded-l-none' : '',
|
|
306
|
+
'group-focus-visible:ring-2 group-focus-visible:ring-new-primary',
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const operator = ref<DateFilterOperator>('is')
|
|
311
|
+
|
|
312
|
+
// `shallowRef` rather than `ref`: these dates are immutable values, always
|
|
313
|
+
// replaced whole, and `UnwrapRef` would flatten the class into a plain object
|
|
314
|
+
// type that no longer matches what the calendar components expect.
|
|
315
|
+
const start = shallowRef<DateValue | undefined>()
|
|
316
|
+
const end = shallowRef<DateValue | undefined>()
|
|
317
|
+
// The month the calendar and the fields are looking at.
|
|
318
|
+
const placeholder = shallowRef<DateValue>(today(getLocalTimeZone()))
|
|
319
|
+
|
|
320
|
+
const isRange = computed(() => operator.value === 'between')
|
|
321
|
+
|
|
322
|
+
// `range` bridges the two separate refs the fields bind to and the single
|
|
323
|
+
// object the range calendar expects.
|
|
324
|
+
const range = computed({
|
|
325
|
+
get: () => ({ start: start.value, end: end.value }),
|
|
326
|
+
set: (value) => {
|
|
327
|
+
start.value = value.start
|
|
328
|
+
end.value = value.end
|
|
329
|
+
},
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
const isComplete = computed(() => Boolean(start.value && (!isRange.value || end.value)))
|
|
333
|
+
|
|
334
|
+
const monthNames = computed(() => {
|
|
335
|
+
// `timeZone` matches the instants below: west of UTC, midnight UTC on the 1st
|
|
336
|
+
// falls in the previous month locally, and every name shifts by one
|
|
337
|
+
const formatter = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' })
|
|
338
|
+
return Array.from({ length: 12 }, (_, index) =>
|
|
339
|
+
formatter.format(new Date(Date.UTC(2000, index, 1))))
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// A century back covers historical datasets; the year of the current view is
|
|
343
|
+
// added so a date typed in the field is always representable in the picker.
|
|
344
|
+
const selectableYears = computed(() => {
|
|
345
|
+
const currentYear = today(getLocalTimeZone()).year
|
|
346
|
+
const years = new Set<number>([placeholder.value.year])
|
|
347
|
+
for (let year = currentYear + 10; year >= currentYear - 100; year--) {
|
|
348
|
+
years.add(year)
|
|
349
|
+
}
|
|
350
|
+
return [...years].sort((a, b) => b - a)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
function goToMonth(month: number) {
|
|
354
|
+
placeholder.value = placeholder.value.set({ month })
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function goToYear(year: number) {
|
|
358
|
+
placeholder.value = placeholder.value.set({ year })
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function shiftMonths(amount: number) {
|
|
362
|
+
placeholder.value = placeholder.value.add({ months: amount })
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Reflect the applied filter: the panel stays mounted between openings, and the
|
|
366
|
+
// filter can also be dropped from the active-filter chip or the panel header.
|
|
367
|
+
watch(() => filters.value[props.column]?.date, (dateFilter) => {
|
|
368
|
+
if (!dateFilter) {
|
|
369
|
+
start.value = undefined
|
|
370
|
+
end.value = undefined
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
operator.value = dateFilter.operator
|
|
374
|
+
start.value = parseIsoDate(dateFilter.start) ?? undefined
|
|
375
|
+
end.value = parseIsoDate(dateFilter.end) ?? undefined
|
|
376
|
+
if (start.value) {
|
|
377
|
+
placeholder.value = start.value
|
|
378
|
+
}
|
|
379
|
+
}, { immediate: true })
|
|
380
|
+
|
|
381
|
+
function apply() {
|
|
382
|
+
if (!start.value) return
|
|
383
|
+
const dateFilter: DateFilter = {
|
|
384
|
+
operator: operator.value,
|
|
385
|
+
start: start.value.toString(),
|
|
386
|
+
}
|
|
387
|
+
if (isRange.value) {
|
|
388
|
+
if (!end.value) return
|
|
389
|
+
dateFilter.end = end.value.toString()
|
|
390
|
+
}
|
|
391
|
+
const existing = filters.value[props.column] ?? {}
|
|
392
|
+
filters.value = { ...filters.value, [props.column]: { ...existing, date: dateFilter } }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function clear() {
|
|
396
|
+
start.value = undefined
|
|
397
|
+
end.value = undefined
|
|
398
|
+
const existing = filters.value[props.column]
|
|
399
|
+
if (!existing?.date) return
|
|
400
|
+
const { date: _date, ...rest } = existing
|
|
401
|
+
filters.value = { ...filters.value, [props.column]: rest }
|
|
402
|
+
}
|
|
403
|
+
</script>
|