@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.
@@ -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) | modal (centré) | sheet (bottom sheet) | dialog (plein écran).
5
- import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"
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 (modal/sheet) */
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 naviguer
77
- const panelOpen = computed(() => isPanelMode.value && open.value)
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
- if (typeof document !== "undefined" && isPanelMode.value) {
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
- document.addEventListener("mousedown", onDocMousedown)
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="`q-date-picker__overlay--${mode}`"
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
- <div class="q-date-picker__sheet-header">
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>
@@ -56,12 +56,14 @@ const onDocPointerDown = (e: PointerEvent) => {
56
56
  }
57
57
  onMounted(() => {
58
58
  if (typeof document !== "undefined") {
59
- document.addEventListener("pointerdown", onDocPointerDown)
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
 
@@ -61,10 +61,12 @@ const onKeydown = (e: KeyboardEvent) => {
61
61
  }
62
62
 
63
63
  onMounted(() => {
64
- if (typeof document !== "undefined") document.addEventListener("mousedown", onDocMousedown)
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
 
@@ -603,13 +603,15 @@ watch(open, (v) => {
603
603
 
604
604
  onMounted(() => {
605
605
  if (typeof document !== "undefined") {
606
- document.addEventListener("mousedown", onDocMousedown)
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
- /** Libellé affiché */
24
- label: string
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
- window.addEventListener("pointerdown", onDocPointerDown)
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>
@@ -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
- document.addEventListener("mousedown", onDown)
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
- <span class="q-date-calendar__nav-label">{{ monthLabel }}</span>
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
- <div class="q-date-calendar__weekdays">
129
- <span v-for="(d, i) in weekdayLabels" :key="i" class="q-date-calendar__weekday">{{ d }}</span>
130
- </div>
131
-
132
- <div class="q-date-calendar__weeks">
133
- <div v-for="(week, wi) in weeks" :key="wi" class="q-date-calendar__week">
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-calendar__day"
139
- :class="cn(
140
- !d && 'q-date-calendar__day--empty',
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
- {{ d ? d.getDate() : "" }}
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"