@dnax/ui 0.0.42 → 0.0.44
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/components/QAutocomplete.vue +5 -2
- package/components/QBtnActions.vue +4 -2
- package/components/QChart.vue +533 -0
- package/components/QCountryPicker.vue +4 -2
- package/components/QDatePicker.vue +99 -16
- package/components/QFab.vue +4 -2
- package/components/QNavMenu.vue +4 -2
- package/components/QSelect.vue +4 -2
- package/components/QSpreadsheet.vue +10 -4
- package/components/QSwipeCell.vue +3 -3
- package/components/QTiptap.vue +4 -2
- package/components/internal/QDateCalendar.vue +112 -19
- package/index.ts +21 -1
- package/lib/chart.test.ts +991 -0
- package/lib/chart.ts +1340 -0
- package/lib/color.test.ts +81 -0
- package/lib/color.ts +60 -0
- package/lib/datePicker.test.ts +122 -0
- package/lib/datePicker.ts +114 -0
- package/package.json +2 -9
- package/scripts/generate-exports.ts +13 -0
- package/styles/main.css +251 -130
- package/components/QEditorJs.vue +0 -146
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
// QDatePicker — sélecteur de date type DatePicker shadcn-vue, API Quasar :
|
|
3
3
|
// <q-date-picker v-model="date" mode="sheet" label="Échéance" clearable outlined />
|
|
4
|
-
// Modes : inline (calendrier en place) |
|
|
5
|
-
|
|
4
|
+
// Modes : inline (calendrier en place) | popover (panneau ancré sous le champ) |
|
|
5
|
+
// modal (centré) | sheet (bottom sheet) | dialog (plein écran).
|
|
6
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
|
|
6
7
|
import { Icon } from "@iconify/vue"
|
|
7
8
|
import { icons } from "../lib/icons"
|
|
8
9
|
import { cn } from "../lib/utils"
|
|
9
10
|
import { useOverlayBack } from "../lib/overlayBack"
|
|
11
|
+
import { placePopover, type PopoverPlacement } from "../lib/datePicker"
|
|
10
12
|
import QDateCalendar from "./internal/QDateCalendar.vue"
|
|
11
13
|
|
|
12
14
|
interface Props {
|
|
13
15
|
/** Date sélectionnée */
|
|
14
16
|
modelValue?: Date | null
|
|
15
17
|
/** Mode d'affichage */
|
|
16
|
-
mode?: "inline" | "sheet" | "modal" | "dialog"
|
|
18
|
+
mode?: "inline" | "popover" | "sheet" | "modal" | "dialog"
|
|
17
19
|
label?: string
|
|
18
20
|
stackLabel?: boolean
|
|
19
21
|
hint?: string
|
|
@@ -37,10 +39,14 @@ interface Props {
|
|
|
37
39
|
disabledDates?: (d: Date) => boolean
|
|
38
40
|
/** Premier jour de la semaine : 0 = dimanche, 1 = lundi (défaut) */
|
|
39
41
|
firstDayOfWeek?: number
|
|
40
|
-
/** Largeur du panneau (
|
|
42
|
+
/** Largeur du panneau (popover/sheet/modal) */
|
|
41
43
|
width?: string
|
|
42
|
-
/** Titre du panneau */
|
|
44
|
+
/** Titre du panneau (modes à voile : sheet/modal/dialog) */
|
|
43
45
|
title?: string
|
|
46
|
+
/** Raccourci « Today » sous le calendrier (tous les modes) */
|
|
47
|
+
todayBtn?: boolean
|
|
48
|
+
/** En-tête : libellé cliquable ouvrant le choix du mois (tous les modes) */
|
|
49
|
+
monthDropdown?: boolean
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -58,6 +64,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
58
64
|
firstDayOfWeek: 1,
|
|
59
65
|
width: "",
|
|
60
66
|
title: "",
|
|
67
|
+
todayBtn: false,
|
|
68
|
+
monthDropdown: false,
|
|
61
69
|
})
|
|
62
70
|
|
|
63
71
|
const emit = defineEmits<{
|
|
@@ -67,14 +75,22 @@ const emit = defineEmits<{
|
|
|
67
75
|
clear: []
|
|
68
76
|
}>()
|
|
69
77
|
|
|
78
|
+
/** Champ déclencheur (tout sauf `inline`) */
|
|
70
79
|
const isPanelMode = computed(() => props.mode !== "inline")
|
|
80
|
+
/** Panneau ancré au champ : ni voile, ni blocage du scroll, ni retour navigateur */
|
|
81
|
+
const isPopover = computed(() => props.mode === "popover")
|
|
82
|
+
/** Modes à voile sombre (sheet/modal/dialog) */
|
|
83
|
+
const isOverlayMode = computed(
|
|
84
|
+
() => props.mode === "sheet" || props.mode === "modal" || props.mode === "dialog",
|
|
85
|
+
)
|
|
71
86
|
|
|
72
87
|
const open = ref(false)
|
|
73
88
|
const rootEl = ref<HTMLElement | null>(null)
|
|
74
89
|
const sheetRef = ref<HTMLElement | null>(null)
|
|
75
90
|
|
|
76
|
-
// « Retour » navigateur → ferme le panneau (sheet/modal/dialog) au lieu de
|
|
77
|
-
|
|
91
|
+
// « Retour » navigateur → ferme le panneau au voile (sheet/modal/dialog) au lieu de
|
|
92
|
+
// naviguer. Le popover, lui, reste un menu : clic extérieur / Échap suffisent.
|
|
93
|
+
const panelOpen = computed(() => isOverlayMode.value && open.value)
|
|
78
94
|
useOverlayBack(panelOpen, () => closePopup(), "QDatePicker")
|
|
79
95
|
|
|
80
96
|
// — Affichage du champ —
|
|
@@ -129,6 +145,51 @@ const sheetStyle = computed<Record<string, string> | undefined>(() =>
|
|
|
129
145
|
props.width ? { width: props.width, maxWidth: props.width } : undefined,
|
|
130
146
|
)
|
|
131
147
|
|
|
148
|
+
// — Popover : panneau ancré au champ, position calculée en `fixed` (téléporté) —
|
|
149
|
+
// Le calcul vit dans un helper pur (`lib/datePicker.ts`) : ici on ne fait que
|
|
150
|
+
// mesurer les rectangles et sérialiser le résultat.
|
|
151
|
+
const popoverPlacement = ref<PopoverPlacement | null>(null)
|
|
152
|
+
// Conserve le dernier emplacement à la fermeture : l'animation de sortie en dépend.
|
|
153
|
+
const popoverStyle = computed<Record<string, string>>(() => {
|
|
154
|
+
const p = popoverPlacement.value
|
|
155
|
+
if (!p) return { visibility: "hidden" }
|
|
156
|
+
return {
|
|
157
|
+
top: p.top === null ? "auto" : `${p.top}px`,
|
|
158
|
+
bottom: p.bottom === null ? "auto" : `${p.bottom}px`,
|
|
159
|
+
left: `${p.left}px`,
|
|
160
|
+
"--q-date-picker-caret": `${p.caret}px`,
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
/** Le panneau borne sa hauteur à l'espace visible ; la liste scrolle au-delà. */
|
|
165
|
+
const popoverPanelStyle = computed<Record<string, string | undefined>>(() => ({
|
|
166
|
+
...sheetStyle.value,
|
|
167
|
+
maxHeight: popoverPlacement.value ? `${popoverPlacement.value.maxHeight}px` : undefined,
|
|
168
|
+
}))
|
|
169
|
+
|
|
170
|
+
const positionPopover = () => {
|
|
171
|
+
const el = rootEl.value
|
|
172
|
+
if (!el || typeof window === "undefined") return
|
|
173
|
+
|
|
174
|
+
// Ancre = bas du CHAMP : `.q-field__bottom` réserve ~24px même vide, ce qui
|
|
175
|
+
// éloignerait le panneau. Quand un hint / une erreur est affiché, on prend la
|
|
176
|
+
// racine pour ne pas recouvrir le texte (même règle que le popup de QSelect).
|
|
177
|
+
const control = el.querySelector<HTMLElement>(".q-field__control")
|
|
178
|
+
const hasBottomText = !!el.querySelector(".q-field__hint, .q-field__error")
|
|
179
|
+
const anchor = (!hasBottomText && control ? control : el).getBoundingClientRect()
|
|
180
|
+
|
|
181
|
+
popoverPlacement.value = placePopover({
|
|
182
|
+
anchor: { top: anchor.top, bottom: anchor.bottom, left: anchor.left, width: anchor.width },
|
|
183
|
+
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
184
|
+
// Largeur mesurée après rendu (le champ `width` peut être en %, vw…)
|
|
185
|
+
panelWidth: sheetRef.value?.getBoundingClientRect().width,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const onViewportChange = () => {
|
|
190
|
+
if (open.value && isPopover.value) positionPopover()
|
|
191
|
+
}
|
|
192
|
+
|
|
132
193
|
// — Fermetures : clic extérieur (hors panneau téléporté), Échap, scroll lock —
|
|
133
194
|
const onDocMousedown = (e: MouseEvent) => {
|
|
134
195
|
const target = e.target as Node
|
|
@@ -141,22 +202,34 @@ const onDocKeydown = (e: KeyboardEvent) => {
|
|
|
141
202
|
if (e.key === "Escape" && open.value) closePopup()
|
|
142
203
|
}
|
|
143
204
|
|
|
144
|
-
watch(open, (v) => {
|
|
145
|
-
|
|
205
|
+
watch(open, async (v) => {
|
|
206
|
+
// Le voile bloque le scroll de la page ; le popover le laisse vivre.
|
|
207
|
+
if (typeof document !== "undefined" && isOverlayMode.value) {
|
|
146
208
|
document.body.style.overflow = v ? "hidden" : ""
|
|
147
209
|
}
|
|
210
|
+
if (v && isPopover.value) {
|
|
211
|
+
await nextTick()
|
|
212
|
+
positionPopover()
|
|
213
|
+
}
|
|
148
214
|
})
|
|
149
215
|
|
|
150
216
|
onMounted(() => {
|
|
151
217
|
if (typeof document !== "undefined") {
|
|
152
|
-
|
|
218
|
+
// Phase de CAPTURE : un `@mousedown.stop` parent (contenu de QDialog…) bloque
|
|
219
|
+
// sinon l'événement avant `document` → calendrier jamais fermé au clic extérieur.
|
|
220
|
+
document.addEventListener("mousedown", onDocMousedown, true)
|
|
153
221
|
document.addEventListener("keydown", onDocKeydown)
|
|
222
|
+
// Le champ peut bouger (scroll) ou la fenêtre changer de taille : on repositionne
|
|
223
|
+
window.addEventListener("scroll", onViewportChange, true)
|
|
224
|
+
window.addEventListener("resize", onViewportChange)
|
|
154
225
|
}
|
|
155
226
|
})
|
|
156
227
|
onBeforeUnmount(() => {
|
|
157
228
|
if (typeof document !== "undefined") {
|
|
158
|
-
document.removeEventListener("mousedown", onDocMousedown)
|
|
229
|
+
document.removeEventListener("mousedown", onDocMousedown, true)
|
|
159
230
|
document.removeEventListener("keydown", onDocKeydown)
|
|
231
|
+
window.removeEventListener("scroll", onViewportChange, true)
|
|
232
|
+
window.removeEventListener("resize", onViewportChange)
|
|
160
233
|
document.body.style.overflow = ""
|
|
161
234
|
}
|
|
162
235
|
})
|
|
@@ -172,6 +245,8 @@ onBeforeUnmount(() => {
|
|
|
172
245
|
:max-date="maxDate"
|
|
173
246
|
:disabled-dates="disabledDates"
|
|
174
247
|
:first-day-of-week="firstDayOfWeek"
|
|
248
|
+
:today-btn="todayBtn"
|
|
249
|
+
:month-dropdown="monthDropdown"
|
|
175
250
|
@select="onSelect"
|
|
176
251
|
/>
|
|
177
252
|
</div>
|
|
@@ -201,11 +276,15 @@ onBeforeUnmount(() => {
|
|
|
201
276
|
</div>
|
|
202
277
|
|
|
203
278
|
<Teleport to="body">
|
|
204
|
-
<Transition name="q-date-modal">
|
|
279
|
+
<Transition :name="isPopover ? 'q-date-popover' : 'q-date-modal'">
|
|
205
280
|
<div
|
|
206
281
|
v-if="open"
|
|
207
282
|
class="q-date-picker__overlay"
|
|
208
|
-
:class="
|
|
283
|
+
:class="[
|
|
284
|
+
`q-date-picker__overlay--${mode}`,
|
|
285
|
+
isPopover && popoverPlacement?.direction === 'up' && 'q-date-picker__overlay--popover-up',
|
|
286
|
+
]"
|
|
287
|
+
:style="isPopover ? popoverStyle : undefined"
|
|
209
288
|
role="presentation"
|
|
210
289
|
@mousedown.self="closePopup"
|
|
211
290
|
>
|
|
@@ -213,12 +292,14 @@ onBeforeUnmount(() => {
|
|
|
213
292
|
ref="sheetRef"
|
|
214
293
|
class="q-date-picker__sheet"
|
|
215
294
|
:class="`q-date-picker__sheet--${mode}`"
|
|
216
|
-
:style="sheetStyle"
|
|
295
|
+
:style="isPopover ? popoverPanelStyle : sheetStyle"
|
|
217
296
|
role="dialog"
|
|
218
|
-
aria-modal="true"
|
|
297
|
+
:aria-modal="isOverlayMode ? 'true' : undefined"
|
|
219
298
|
:aria-label="sheetTitle"
|
|
220
299
|
>
|
|
221
|
-
|
|
300
|
+
<!-- En-tête (titre + fermeture) réservé aux modes à voile : un popover
|
|
301
|
+
n'a ni titre ni bouton de fermeture, il se ferme au clic extérieur. -->
|
|
302
|
+
<div v-if="isOverlayMode" class="q-date-picker__sheet-header">
|
|
222
303
|
<span class="q-date-picker__sheet-title">{{ sheetTitle }}</span>
|
|
223
304
|
<button
|
|
224
305
|
type="button"
|
|
@@ -236,6 +317,8 @@ onBeforeUnmount(() => {
|
|
|
236
317
|
:max-date="maxDate"
|
|
237
318
|
:disabled-dates="disabledDates"
|
|
238
319
|
:first-day-of-week="firstDayOfWeek"
|
|
320
|
+
:today-btn="todayBtn"
|
|
321
|
+
:month-dropdown="monthDropdown"
|
|
239
322
|
@select="onSelect"
|
|
240
323
|
/>
|
|
241
324
|
</div>
|
package/components/QFab.vue
CHANGED
|
@@ -56,12 +56,14 @@ const onDocPointerDown = (e: PointerEvent) => {
|
|
|
56
56
|
}
|
|
57
57
|
onMounted(() => {
|
|
58
58
|
if (typeof document !== "undefined") {
|
|
59
|
-
|
|
59
|
+
// Phase de CAPTURE : un `@pointerdown.stop` parent bloque sinon l'événement
|
|
60
|
+
// avant `document` → actions du FAB jamais repliées au clic extérieur.
|
|
61
|
+
document.addEventListener("pointerdown", onDocPointerDown, true)
|
|
60
62
|
}
|
|
61
63
|
})
|
|
62
64
|
onBeforeUnmount(() => {
|
|
63
65
|
if (typeof document !== "undefined") {
|
|
64
|
-
document.removeEventListener("pointerdown", onDocPointerDown)
|
|
66
|
+
document.removeEventListener("pointerdown", onDocPointerDown, true)
|
|
65
67
|
}
|
|
66
68
|
})
|
|
67
69
|
|
package/components/QNavMenu.vue
CHANGED
|
@@ -61,10 +61,12 @@ const onKeydown = (e: KeyboardEvent) => {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
onMounted(() => {
|
|
64
|
-
|
|
64
|
+
// Phase de CAPTURE : un `@mousedown.stop` parent (contenu de QDialog…) bloque
|
|
65
|
+
// sinon l'événement avant `document` → menu jamais fermé au clic extérieur.
|
|
66
|
+
if (typeof document !== "undefined") document.addEventListener("mousedown", onDocMousedown, true)
|
|
65
67
|
})
|
|
66
68
|
onBeforeUnmount(() => {
|
|
67
|
-
if (typeof document !== "undefined") document.removeEventListener("mousedown", onDocMousedown)
|
|
69
|
+
if (typeof document !== "undefined") document.removeEventListener("mousedown", onDocMousedown, true)
|
|
68
70
|
})
|
|
69
71
|
</script>
|
|
70
72
|
|
package/components/QSelect.vue
CHANGED
|
@@ -603,13 +603,15 @@ watch(open, (v) => {
|
|
|
603
603
|
|
|
604
604
|
onMounted(() => {
|
|
605
605
|
if (typeof document !== "undefined") {
|
|
606
|
-
|
|
606
|
+
// Phase de CAPTURE : un `@mousedown.stop` parent (contenu de QDialog…) bloque
|
|
607
|
+
// sinon l'événement avant `document` → popup jamais fermé au clic extérieur.
|
|
608
|
+
document.addEventListener("mousedown", onDocMousedown, true)
|
|
607
609
|
document.addEventListener("keydown", onDocKeydown)
|
|
608
610
|
}
|
|
609
611
|
})
|
|
610
612
|
onBeforeUnmount(() => {
|
|
611
613
|
if (typeof document !== "undefined") {
|
|
612
|
-
document.removeEventListener("mousedown", onDocMousedown)
|
|
614
|
+
document.removeEventListener("mousedown", onDocMousedown, true)
|
|
613
615
|
document.removeEventListener("keydown", onDocKeydown)
|
|
614
616
|
document.body.style.overflow = ""
|
|
615
617
|
}
|
|
@@ -20,8 +20,12 @@ export type QSpreadsheetCellType =
|
|
|
20
20
|
export interface QSpreadsheetCellOption {
|
|
21
21
|
/** Valeur stockée dans la cellule */
|
|
22
22
|
value: any
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Libellé affiché. Les nombres sont acceptés (`{ value: 1, label: 1 }` pour une
|
|
25
|
+
* note, un niveau…) : le composant les normalise en string partout où il fait des
|
|
26
|
+
* opérations de chaîne (recherche, filtre, tri, éditeur).
|
|
27
|
+
*/
|
|
28
|
+
label: string | number
|
|
25
29
|
/** Couleur du badge (token ou hex, ex. "positive", "#22c55e") */
|
|
26
30
|
color?: string
|
|
27
31
|
}
|
|
@@ -2639,7 +2643,9 @@ const onFilterSearch = (e: Event) => {
|
|
|
2639
2643
|
|
|
2640
2644
|
onMounted(() => {
|
|
2641
2645
|
window.addEventListener("pointerup", onPointerUpGlobal)
|
|
2642
|
-
|
|
2646
|
+
// Phase de CAPTURE : un `@pointerdown.stop` parent bloque sinon l'événement
|
|
2647
|
+
// avant `window` → menu contextuel / filtre jamais fermé au clic extérieur.
|
|
2648
|
+
window.addEventListener("pointerdown", onDocPointerDown, true)
|
|
2643
2649
|
window.addEventListener("resize", onResize)
|
|
2644
2650
|
nextTick(() => {
|
|
2645
2651
|
updateViewportMetrics()
|
|
@@ -2648,7 +2654,7 @@ onMounted(() => {
|
|
|
2648
2654
|
})
|
|
2649
2655
|
onBeforeUnmount(() => {
|
|
2650
2656
|
window.removeEventListener("pointerup", onPointerUpGlobal)
|
|
2651
|
-
window.removeEventListener("pointerdown", onDocPointerDown)
|
|
2657
|
+
window.removeEventListener("pointerdown", onDocPointerDown, true)
|
|
2652
2658
|
window.removeEventListener("resize", onResize)
|
|
2653
2659
|
endResize()
|
|
2654
2660
|
endRowResize()
|
|
@@ -150,12 +150,12 @@ const onDocPointerDown = (e: PointerEvent) => {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
watch(opened, (v) => {
|
|
153
|
-
if (v) document.addEventListener("pointerdown", onDocPointerDown)
|
|
154
|
-
else document.removeEventListener("pointerdown", onDocPointerDown)
|
|
153
|
+
if (v) document.addEventListener("pointerdown", onDocPointerDown, true)
|
|
154
|
+
else document.removeEventListener("pointerdown", onDocPointerDown, true)
|
|
155
155
|
})
|
|
156
156
|
|
|
157
157
|
onBeforeUnmount(() => {
|
|
158
|
-
document.removeEventListener("pointerdown", onDocPointerDown)
|
|
158
|
+
document.removeEventListener("pointerdown", onDocPointerDown, true)
|
|
159
159
|
if (wrapperEl.value) wrapperEl.value.style.removeProperty("transition")
|
|
160
160
|
})
|
|
161
161
|
</script>
|
package/components/QTiptap.vue
CHANGED
|
@@ -293,12 +293,14 @@ watch(colorOpen, (open) => {
|
|
|
293
293
|
if (e.key === "Escape") colorOpen.value = false
|
|
294
294
|
}
|
|
295
295
|
const close = () => { colorOpen.value = false }
|
|
296
|
-
|
|
296
|
+
// Phase de CAPTURE : un `@mousedown.stop` parent bloque sinon l'événement avant
|
|
297
|
+
// `document` → palette de couleurs jamais fermée au clic extérieur.
|
|
298
|
+
document.addEventListener("mousedown", onDown, true)
|
|
297
299
|
document.addEventListener("keydown", onKey)
|
|
298
300
|
window.addEventListener("blur", close)
|
|
299
301
|
window.addEventListener("resize", close)
|
|
300
302
|
return () => {
|
|
301
|
-
document.removeEventListener("mousedown", onDown)
|
|
303
|
+
document.removeEventListener("mousedown", onDown, true)
|
|
302
304
|
document.removeEventListener("keydown", onKey)
|
|
303
305
|
window.removeEventListener("blur", close)
|
|
304
306
|
window.removeEventListener("resize", close)
|
|
@@ -15,11 +15,17 @@ interface Props {
|
|
|
15
15
|
disabledDates?: (d: Date) => boolean
|
|
16
16
|
/** Premier jour de la semaine : 0 = dimanche, 1 = lundi (défaut) */
|
|
17
17
|
firstDayOfWeek?: number
|
|
18
|
+
/** Raccourci « Today » sous la grille */
|
|
19
|
+
todayBtn?: boolean
|
|
20
|
+
/** En-tête : libellé cliquable ouvrant le choix du mois (et de l'année) */
|
|
21
|
+
monthDropdown?: boolean
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const props = withDefaults(defineProps<Props>(), {
|
|
21
25
|
modelValue: null,
|
|
22
26
|
firstDayOfWeek: 1,
|
|
27
|
+
todayBtn: false,
|
|
28
|
+
monthDropdown: false,
|
|
23
29
|
})
|
|
24
30
|
|
|
25
31
|
const emit = defineEmits<{ select: [date: Date] }>()
|
|
@@ -27,6 +33,8 @@ const emit = defineEmits<{ select: [date: Date] }>()
|
|
|
27
33
|
const today = new Date()
|
|
28
34
|
const viewYear = ref(props.modelValue?.getFullYear() ?? today.getFullYear())
|
|
29
35
|
const viewMonth = ref(props.modelValue?.getMonth() ?? today.getMonth())
|
|
36
|
+
/** Choix du mois ouvert (en-tête) — remplace la grille des jours */
|
|
37
|
+
const monthPickerOpen = ref(false)
|
|
30
38
|
|
|
31
39
|
watch(
|
|
32
40
|
() => props.modelValue,
|
|
@@ -68,6 +76,13 @@ const monthLabel = computed(() =>
|
|
|
68
76
|
),
|
|
69
77
|
)
|
|
70
78
|
|
|
79
|
+
/** Noms courts des 12 mois, dans la langue du navigateur (comme `monthLabel`) */
|
|
80
|
+
const monthNames = computed(() =>
|
|
81
|
+
Array.from({ length: 12 }, (_, i) =>
|
|
82
|
+
new Intl.DateTimeFormat(undefined, { month: "short" }).format(new Date(2000, i, 1)),
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
71
86
|
const prevMonth = () => {
|
|
72
87
|
if (viewMonth.value === 0) {
|
|
73
88
|
viewMonth.value = 11
|
|
@@ -88,6 +103,15 @@ const nextMonth = () => {
|
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
|
|
106
|
+
const prevYear = () => viewYear.value--
|
|
107
|
+
const nextYear = () => viewYear.value++
|
|
108
|
+
|
|
109
|
+
/** Choix d'un mois dans l'en-tête : on saute au mois, la grille revient */
|
|
110
|
+
const onPickMonth = (month: number) => {
|
|
111
|
+
viewMonth.value = month
|
|
112
|
+
monthPickerOpen.value = false
|
|
113
|
+
}
|
|
114
|
+
|
|
91
115
|
const isSelected = (d: Date) => !!props.modelValue && sameDay(props.modelValue, d)
|
|
92
116
|
const isToday = (d: Date) => sameDay(d, new Date())
|
|
93
117
|
|
|
@@ -101,6 +125,14 @@ const onSelectDate = (d: Date) => {
|
|
|
101
125
|
if (isDisabled(d)) return
|
|
102
126
|
emit("select", d)
|
|
103
127
|
}
|
|
128
|
+
|
|
129
|
+
/** Raccourci « Today » : désactivé quand aujourd'hui est hors bornes / désactivé */
|
|
130
|
+
const todaySelectable = computed(() => !isDisabled(startOfDay(new Date())))
|
|
131
|
+
|
|
132
|
+
const selectToday = () => {
|
|
133
|
+
if (!todaySelectable.value) return
|
|
134
|
+
emit("select", startOfDay(new Date()))
|
|
135
|
+
}
|
|
104
136
|
</script>
|
|
105
137
|
|
|
106
138
|
<template>
|
|
@@ -114,7 +146,19 @@ const onSelectDate = (d: Date) => {
|
|
|
114
146
|
>
|
|
115
147
|
<Icon :icon="icons.chevronLeft" aria-hidden="true" />
|
|
116
148
|
</button>
|
|
117
|
-
<
|
|
149
|
+
<button
|
|
150
|
+
v-if="monthDropdown"
|
|
151
|
+
type="button"
|
|
152
|
+
class="q-date-calendar__nav-label q-date-calendar__nav-label--button"
|
|
153
|
+
:class="{ 'q-date-calendar__nav-label--open': monthPickerOpen }"
|
|
154
|
+
:aria-expanded="monthPickerOpen"
|
|
155
|
+
aria-label="Choisir le mois"
|
|
156
|
+
@click="monthPickerOpen = !monthPickerOpen"
|
|
157
|
+
>
|
|
158
|
+
<span class="q-date-calendar__nav-text">{{ monthLabel }}</span>
|
|
159
|
+
<Icon :icon="icons.chevronDown" class="q-date-calendar__nav-caret" aria-hidden="true" />
|
|
160
|
+
</button>
|
|
161
|
+
<span v-else class="q-date-calendar__nav-label">{{ monthLabel }}</span>
|
|
118
162
|
<button
|
|
119
163
|
type="button"
|
|
120
164
|
class="q-date-calendar__nav-btn"
|
|
@@ -125,29 +169,78 @@ const onSelectDate = (d: Date) => {
|
|
|
125
169
|
</button>
|
|
126
170
|
</div>
|
|
127
171
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
172
|
+
<!-- Choix du mois / de l'année (en-tête cliquable) -->
|
|
173
|
+
<div v-if="monthDropdown && monthPickerOpen" class="q-date-calendar__picker">
|
|
174
|
+
<div class="q-date-calendar__picker-year">
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
class="q-date-calendar__nav-btn"
|
|
178
|
+
aria-label="Année précédente"
|
|
179
|
+
@click="prevYear"
|
|
180
|
+
>
|
|
181
|
+
<Icon :icon="icons.chevronLeft" aria-hidden="true" />
|
|
182
|
+
</button>
|
|
183
|
+
<span class="q-date-calendar__picker-year-label">{{ viewYear }}</span>
|
|
134
184
|
<button
|
|
135
|
-
v-for="(d, di) in week"
|
|
136
|
-
:key="di"
|
|
137
185
|
type="button"
|
|
138
|
-
class="q-date-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
!!d && isToday(d) && 'q-date-calendar__day--today',
|
|
142
|
-
!!d && isSelected(d) && 'q-date-calendar__day--selected',
|
|
143
|
-
)"
|
|
144
|
-
:disabled="!d || isDisabled(d)"
|
|
145
|
-
:aria-selected="!!d && isSelected(d) ? 'true' : 'false'"
|
|
146
|
-
@click="d && onSelectDate(d)"
|
|
186
|
+
class="q-date-calendar__nav-btn"
|
|
187
|
+
aria-label="Année suivante"
|
|
188
|
+
@click="nextYear"
|
|
147
189
|
>
|
|
148
|
-
|
|
190
|
+
<Icon :icon="icons.chevronRight" aria-hidden="true" />
|
|
149
191
|
</button>
|
|
150
192
|
</div>
|
|
193
|
+
<div class="q-date-calendar__picker-months">
|
|
194
|
+
<button
|
|
195
|
+
v-for="(name, index) in monthNames"
|
|
196
|
+
:key="name"
|
|
197
|
+
type="button"
|
|
198
|
+
class="q-date-calendar__picker-month"
|
|
199
|
+
:class="{ 'q-date-calendar__picker-month--current': index === viewMonth }"
|
|
200
|
+
:aria-current="index === viewMonth ? 'true' : undefined"
|
|
201
|
+
@click="onPickMonth(index)"
|
|
202
|
+
>
|
|
203
|
+
{{ name }}
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<template v-else>
|
|
209
|
+
<div class="q-date-calendar__weekdays">
|
|
210
|
+
<span v-for="(d, i) in weekdayLabels" :key="i" class="q-date-calendar__weekday">{{ d }}</span>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div class="q-date-calendar__weeks">
|
|
214
|
+
<div v-for="(week, wi) in weeks" :key="wi" class="q-date-calendar__week">
|
|
215
|
+
<button
|
|
216
|
+
v-for="(d, di) in week"
|
|
217
|
+
:key="di"
|
|
218
|
+
type="button"
|
|
219
|
+
class="q-date-calendar__day"
|
|
220
|
+
:class="cn(
|
|
221
|
+
!d && 'q-date-calendar__day--empty',
|
|
222
|
+
!!d && isToday(d) && 'q-date-calendar__day--today',
|
|
223
|
+
!!d && isSelected(d) && 'q-date-calendar__day--selected',
|
|
224
|
+
)"
|
|
225
|
+
:disabled="!d || isDisabled(d)"
|
|
226
|
+
:aria-selected="!!d && isSelected(d) ? 'true' : 'false'"
|
|
227
|
+
@click="d && onSelectDate(d)"
|
|
228
|
+
>
|
|
229
|
+
{{ d ? d.getDate() : "" }}
|
|
230
|
+
</button>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</template>
|
|
234
|
+
|
|
235
|
+
<div v-if="todayBtn" class="q-date-calendar__footer">
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
class="q-date-calendar__today"
|
|
239
|
+
:disabled="!todaySelectable"
|
|
240
|
+
@click="selectToday"
|
|
241
|
+
>
|
|
242
|
+
Today
|
|
243
|
+
</button>
|
|
151
244
|
</div>
|
|
152
245
|
</div>
|
|
153
246
|
</template>
|
package/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ export { default as QCarousel } from "./components/QCarousel.vue"
|
|
|
33
33
|
export { default as QCarouselContent } from "./components/QCarouselContent.vue"
|
|
34
34
|
export { default as QCarouselItem } from "./components/QCarouselItem.vue"
|
|
35
35
|
export { default as QCarouselNav } from "./components/QCarouselNav.vue"
|
|
36
|
+
export { default as QChart } from "./components/QChart.vue"
|
|
36
37
|
export { default as QCheckbox } from "./components/QCheckbox.vue"
|
|
37
38
|
export { default as QChip } from "./components/QChip.vue"
|
|
38
39
|
export { default as QCircularProgress } from "./components/QCircularProgress.vue"
|
|
@@ -50,7 +51,6 @@ export { default as QDialogFooter } from "./components/QDialogFooter.vue"
|
|
|
50
51
|
export { default as QDialogHeader } from "./components/QDialogHeader.vue"
|
|
51
52
|
export { default as QDialogProvider } from "./components/QDialogProvider.vue"
|
|
52
53
|
export { default as QDialogTrigger } from "./components/QDialogTrigger.vue"
|
|
53
|
-
export { default as QEditorJs } from "./components/QEditorJs.vue"
|
|
54
54
|
export { default as QFab } from "./components/QFab.vue"
|
|
55
55
|
export { default as QFabAction } from "./components/QFabAction.vue"
|
|
56
56
|
export { default as QFilePicker } from "./components/QFilePicker.vue"
|
|
@@ -166,8 +166,28 @@ export {
|
|
|
166
166
|
} from "./lib/overlayBack"
|
|
167
167
|
export type { OverlayHandle } from "./lib/overlayBack"
|
|
168
168
|
export { markOverlayClose, closeParentOverlay, vClose } from "./lib/closeOverlay"
|
|
169
|
+
export { placePopover } from "./lib/datePicker"
|
|
170
|
+
export type {
|
|
171
|
+
PlacePopoverOptions,
|
|
172
|
+
PopoverAnchor,
|
|
173
|
+
PopoverPlacement,
|
|
174
|
+
PopoverViewport,
|
|
175
|
+
} from "./lib/datePicker"
|
|
169
176
|
export { vRipple } from "./lib/ripple"
|
|
170
177
|
export type { RippleOptions, RippleValue } from "./lib/ripple"
|
|
178
|
+
export { chartToECharts, channelValue, pickFromEvent, tableModel, textValue } from "./lib/chart"
|
|
179
|
+
export type {
|
|
180
|
+
QChartAxis,
|
|
181
|
+
QChartChannel,
|
|
182
|
+
QChartColumn,
|
|
183
|
+
QChartConfig,
|
|
184
|
+
QChartLegend,
|
|
185
|
+
QChartMark,
|
|
186
|
+
QChartMarkType,
|
|
187
|
+
QChartOrientation,
|
|
188
|
+
QChartTable,
|
|
189
|
+
QChartPick,
|
|
190
|
+
} from "./lib/chart"
|
|
171
191
|
export { vTouchPan } from "./lib/touchPan"
|
|
172
192
|
export type { PanDetails, TouchPanHandler, PanDirection } from "./lib/touchPan"
|
|
173
193
|
export { vTouchHold } from "./lib/touchHold"
|