@dnax/ui 0.0.46 → 0.0.49
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/QBottomSheet.vue +172 -6
- package/components/QBtn.vue +3 -4
- package/components/QContainer.vue +2 -2
- package/components/QImagePicker.vue +28 -1
- package/components/QInputChat.vue +859 -0
- package/components/QInputCurrency.vue +319 -0
- package/components/QItemLabel.vue +99 -0
- package/components/QMap.vue +410 -0
- package/components/QNumericKeyboard.vue +304 -0
- package/components/QPage.vue +26 -6
- package/components/QPageContainer.vue +32 -0
- package/components/QPopupProxy.vue +378 -0
- package/components/QQrcode.vue +132 -0
- package/components/QSpreadsheet.vue +395 -69
- package/components/QTime.vue +845 -0
- package/components/QTimeline.vue +111 -0
- package/components/QTimelineEntry.vue +271 -0
- package/index.ts +11 -1
- package/lib/bottomSheet.test.ts +121 -0
- package/lib/bottomSheet.ts +115 -0
- package/lib/color.ts +13 -0
- package/lib/currency.test.ts +193 -0
- package/lib/currency.ts +301 -0
- package/lib/fixedLayout.ts +12 -11
- package/lib/icons.ts +3 -20
- package/lib/map.test.ts +365 -0
- package/lib/map.ts +438 -0
- package/lib/mapEngine.ts +158 -0
- package/lib/mapLeaflet.ts +142 -0
- package/lib/mapMaplibre.ts +106 -0
- package/lib/mapMaptiler.ts +93 -0
- package/lib/numericKeyboard.test.ts +179 -0
- package/lib/numericKeyboard.ts +166 -0
- package/lib/pagePadding.test.ts +37 -0
- package/lib/pagePadding.ts +32 -0
- package/lib/qrcode.test.ts +103 -0
- package/lib/qrcode.ts +125 -0
- package/lib/spreadsheetChanges.test.ts +230 -0
- package/lib/spreadsheetChanges.ts +331 -0
- package/package.json +5 -17
- package/shims.d.ts +19 -0
- package/styles/main.css +702 -893
- package/components/QTiptap.vue +0 -996
- package/components/internal/QTiptapTableView.vue +0 -123
- package/components/internal/QTiptapTaskItemView.vue +0 -49
- package/lib/tiptap-table.ts +0 -19
- package/lib/tiptap-task-list.ts +0 -16
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QNumericKeyboard — pavé numérique à l'écran : code PIN, montant, numéro. La valeur est une
|
|
3
|
+
// **chaîne** (elle garde les zéros de tête d'un code et le séparateur tel quel) ; c'est
|
|
4
|
+
// l'application qui l'affiche (points d'un code, montant formaté…).
|
|
5
|
+
//
|
|
6
|
+
// <q-numeric-keyboard v-model="pin" :max-length="6" @complete="submit" />
|
|
7
|
+
// <q-numeric-keyboard v-model="amount" mode="decimal" :max-decimals="2" clearable />
|
|
8
|
+
//
|
|
9
|
+
// Les règles de saisie (longueur, décimales, zéros de tête, effacement) vivent dans
|
|
10
|
+
// `lib/numericKeyboard.ts` (pur, testé).
|
|
11
|
+
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"
|
|
12
|
+
import { Icon } from "@iconify/vue"
|
|
13
|
+
import { icons } from "../lib/icons"
|
|
14
|
+
import {
|
|
15
|
+
applyDigitOrder,
|
|
16
|
+
digitCount,
|
|
17
|
+
keypadLayout,
|
|
18
|
+
pressKey,
|
|
19
|
+
shuffledDigits,
|
|
20
|
+
type KeypadKey,
|
|
21
|
+
type KeypadMode,
|
|
22
|
+
} from "../lib/numericKeyboard"
|
|
23
|
+
import { cn } from "../lib/utils"
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
/** Valeur saisie (chaîne) */
|
|
27
|
+
modelValue?: string
|
|
28
|
+
/** `numeric` (défaut : code, téléphone) ou `decimal` (montant : touche séparateur) */
|
|
29
|
+
mode?: KeypadMode
|
|
30
|
+
/** Longueur maximale, en **chiffres** (0 = illimité) — 6 pour un code à six chiffres */
|
|
31
|
+
maxLength?: number
|
|
32
|
+
/** Décimales maximales après le séparateur (0 = illimité) */
|
|
33
|
+
maxDecimals?: number
|
|
34
|
+
/** Séparateur décimal affiché — `,` par défaut (français), `.` en anglais */
|
|
35
|
+
decimalSeparator?: string
|
|
36
|
+
/** Touche « tout effacer » (quand une case est libre : mode `numeric`) */
|
|
37
|
+
clearable?: boolean
|
|
38
|
+
/** Nombre de colonnes du pavé */
|
|
39
|
+
columns?: number
|
|
40
|
+
/** Touches plus compactes */
|
|
41
|
+
dense?: boolean
|
|
42
|
+
/** Affiche une rangée de **points** au-dessus du pavé (progression d'un code) ; le point qui
|
|
43
|
+
* se remplit s'anime (respecte `prefers-reduced-motion`). */
|
|
44
|
+
showDots?: boolean
|
|
45
|
+
/** Nombre de points affichés : par défaut `maxLength` ; si les deux sont à 0, un point par
|
|
46
|
+
* chiffre saisi (un champ masqué qui grandit). */
|
|
47
|
+
dots?: number
|
|
48
|
+
/** Taille des points : nombre (**px**) ou valeur CSS (`"14px"`, `"1.2rem"`, `"18px"`).
|
|
49
|
+
* Prioritaire sur `dense` (le défaut reste `--q-nk-dot-size`). */
|
|
50
|
+
dotsSize?: number | string
|
|
51
|
+
/** **Disposition aléatoire** des chiffres (anti-observation) : le mélange est tiré au montage
|
|
52
|
+
* côté client, puis **stable** — il ne change pas à chaque touche. Relancez-le via la méthode
|
|
53
|
+
* `shuffle()` (par ex. après chaque code). Les touches d'édition (`separator`, `C`, `⌫`) ne
|
|
54
|
+
* bougent jamais. */
|
|
55
|
+
random?: boolean
|
|
56
|
+
/** Thème sombre forcé (sinon celui du document) */
|
|
57
|
+
dark?: boolean
|
|
58
|
+
/** **État d'erreur** : les points passent en rouge et `errorMessage` s'affiche sous le pavé.
|
|
59
|
+
* Se remet à zéro tout seul dès que l'on **efface tout** (valeur vide) — l'événement
|
|
60
|
+
* `update:error` permet de le piloter en `v-model:error`. */
|
|
61
|
+
error?: boolean
|
|
62
|
+
/** Message affiché sous le pavé pendant l'erreur (masqué aussitôt l'erreur levée) */
|
|
63
|
+
errorMessage?: string
|
|
64
|
+
disable?: boolean
|
|
65
|
+
/** Libellé accessible du pavé */
|
|
66
|
+
label?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
70
|
+
modelValue: "",
|
|
71
|
+
mode: "numeric",
|
|
72
|
+
maxLength: 0,
|
|
73
|
+
maxDecimals: 0,
|
|
74
|
+
decimalSeparator: ",",
|
|
75
|
+
clearable: false,
|
|
76
|
+
columns: 3,
|
|
77
|
+
dense: false,
|
|
78
|
+
showDots: false,
|
|
79
|
+
dots: 0,
|
|
80
|
+
random: false,
|
|
81
|
+
dark: false,
|
|
82
|
+
error: false,
|
|
83
|
+
errorMessage: "",
|
|
84
|
+
disable: false,
|
|
85
|
+
label: "Clavier numérique",
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const emit = defineEmits<{
|
|
89
|
+
"update:modelValue": [value: string]
|
|
90
|
+
/** Touche pressée : un chiffre, `"separator"`, `"backspace"` ou `"clear"` */
|
|
91
|
+
press: [key: string]
|
|
92
|
+
/** La valeur vient d'atteindre `maxLength` (valider un code automatiquement) */
|
|
93
|
+
complete: [value: string]
|
|
94
|
+
/** L'erreur est levée parce que l'on a tout effacé (`v-model:error`) */
|
|
95
|
+
"update:error": [value: boolean]
|
|
96
|
+
}>()
|
|
97
|
+
|
|
98
|
+
const options = computed(() => ({
|
|
99
|
+
mode: props.mode,
|
|
100
|
+
maxLength: props.maxLength,
|
|
101
|
+
maxDecimals: props.maxDecimals,
|
|
102
|
+
decimalSeparator: props.decimalSeparator,
|
|
103
|
+
clearable: props.clearable,
|
|
104
|
+
}))
|
|
105
|
+
|
|
106
|
+
// — Disposition aléatoire (optionnelle) —
|
|
107
|
+
// L'ordre est tiré **côté client** (au montage) : tirer pendant le rendu serveur ferait un écart
|
|
108
|
+
// d'hydratation (le serveur et le client obtiendraient deux dispositions différentes). Tant que
|
|
109
|
+
// l'ordre n'est pas tiré, la disposition canonique est rendue (SSR et premier rendu client
|
|
110
|
+
// identiques), puis le mélange s'applique.
|
|
111
|
+
const digitOrder = ref<string[] | null>(null)
|
|
112
|
+
|
|
113
|
+
/** Retire une nouvelle disposition aléatoire (voir la méthode exposée `shuffle`) */
|
|
114
|
+
const shuffle = () => {
|
|
115
|
+
digitOrder.value = shuffledDigits()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onMounted(() => {
|
|
119
|
+
if (props.random) shuffle()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
watch(
|
|
123
|
+
() => props.random,
|
|
124
|
+
(enabled) => {
|
|
125
|
+
if (enabled) shuffle()
|
|
126
|
+
else digitOrder.value = null
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const cells = computed(() => {
|
|
131
|
+
const base = keypadLayout(options.value)
|
|
132
|
+
return props.random && digitOrder.value ? applyDigitOrder(base, digitOrder.value) : base
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// — Points de progression (optionnels) —
|
|
136
|
+
const digitsEntered = computed(() => digitCount(props.modelValue ?? ""))
|
|
137
|
+
|
|
138
|
+
/** Total de points : `dots` explicite, sinon `maxLength`, sinon un point par chiffre saisi */
|
|
139
|
+
const dotTotal = computed(() => {
|
|
140
|
+
if (props.dots > 0) return props.dots
|
|
141
|
+
if (props.maxLength > 0) return props.maxLength
|
|
142
|
+
return digitsEntered.value
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const dotsFilled = computed(() => Math.min(digitsEntered.value, dotTotal.value))
|
|
146
|
+
const dotIndexes = computed(() => Array.from({ length: dotTotal.value }, (_, i) => i))
|
|
147
|
+
|
|
148
|
+
/** Points qui viennent de se remplir : le temps d'une animation « pop » */
|
|
149
|
+
const pulsingDots = ref<number[]>([])
|
|
150
|
+
let pulseTimer: ReturnType<typeof setTimeout> | undefined
|
|
151
|
+
|
|
152
|
+
watch(dotsFilled, (next, previous) => {
|
|
153
|
+
if (!props.showDots || next <= (previous ?? 0)) return
|
|
154
|
+
|
|
155
|
+
const started: number[] = []
|
|
156
|
+
for (let i = Math.max(0, previous ?? 0); i < next; i++) started.push(i)
|
|
157
|
+
pulsingDots.value = [...new Set([...pulsingDots.value, ...started])]
|
|
158
|
+
|
|
159
|
+
if (pulseTimer) clearTimeout(pulseTimer)
|
|
160
|
+
pulseTimer = setTimeout(() => {
|
|
161
|
+
pulsingDots.value = []
|
|
162
|
+
pulseTimer = undefined
|
|
163
|
+
}, 340)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
onBeforeUnmount(() => {
|
|
167
|
+
if (pulseTimer) clearTimeout(pulseTimer)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
/** Annonce lecteur d'écran (« 3 sur 6 ») : la rangée de points elle-même est décorative */
|
|
171
|
+
const dotsAnnouncement = computed(() =>
|
|
172
|
+
props.dots > 0 || props.maxLength > 0
|
|
173
|
+
? `${dotsFilled.value} sur ${dotTotal.value}`
|
|
174
|
+
: `${dotsFilled.value}`,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
// — État d'erreur (optionnel) —
|
|
178
|
+
/** Erreur levée localement : l'utilisateur a **tout effacé** depuis l'erreur (état initial) */
|
|
179
|
+
const errorReset = ref(false)
|
|
180
|
+
|
|
181
|
+
// Une **nouvelle** erreur (la prop repasse à vrai) réaffiche points rouges + message
|
|
182
|
+
watch(
|
|
183
|
+
() => props.error,
|
|
184
|
+
(on) => {
|
|
185
|
+
if (on) errorReset.value = false
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
/** Vrai tant que l'erreur doit être montrée (points rouges + message) */
|
|
190
|
+
const hasError = computed(() => props.error && !errorReset.value)
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Remet l'erreur à zéro : masque les points rouges et le message, et prévient le parent
|
|
194
|
+
* (`v-model:error`). Appelé quand l'utilisateur vide complètement le pavé.
|
|
195
|
+
*/
|
|
196
|
+
const clearError = () => {
|
|
197
|
+
errorReset.value = true
|
|
198
|
+
emit("update:error", false)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// — Taille des points —
|
|
202
|
+
/** `dotsSize` normalisé : nombre → px, chaîne → CSS tel quel (`undefined` = défaut CSS) */
|
|
203
|
+
const dotSize = computed<string | undefined>(() => {
|
|
204
|
+
const v = props.dotsSize
|
|
205
|
+
if (v === undefined || v === null || v === "") return undefined
|
|
206
|
+
return typeof v === "number" ? `${v}px` : String(v)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const rootStyle = computed<Record<string, string>>(() => ({
|
|
210
|
+
"--q-nk-columns": String(props.columns),
|
|
211
|
+
...(dotSize.value ? { "--q-nk-dot-size": dotSize.value } : {}),
|
|
212
|
+
}))
|
|
213
|
+
|
|
214
|
+
const onKey = (cell: KeypadKey) => {
|
|
215
|
+
if (props.disable || cell.kind === "empty") return
|
|
216
|
+
|
|
217
|
+
const current = props.modelValue ?? ""
|
|
218
|
+
const next = pressKey(current, cell.key, options.value)
|
|
219
|
+
|
|
220
|
+
emit("press", cell.key)
|
|
221
|
+
|
|
222
|
+
if (next !== current) {
|
|
223
|
+
emit("update:modelValue", next)
|
|
224
|
+
if (props.maxLength > 0 && digitCount(next) >= props.maxLength) emit("complete", next)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Tout effacé → retour à l'état initial : les points rouges et le message disparaissent.
|
|
228
|
+
// On se fie à la touche (⌫ / C) : un pavé **déjà vide** doit aussi pouvoir lever l'erreur.
|
|
229
|
+
if (props.error && next === "" && (cell.kind === "backspace" || cell.kind === "clear"))
|
|
230
|
+
clearError()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Insère une touche depuis l'extérieur (raccourci clavier matériel, saisie programmée) */
|
|
234
|
+
const insert = (key: string) => onKey({ key, kind: "digit", label: key })
|
|
235
|
+
|
|
236
|
+
const backspace = () => onKey({ key: "backspace", kind: "backspace", label: "⌫" })
|
|
237
|
+
const clear = () => onKey({ key: "clear", kind: "clear", label: "C" })
|
|
238
|
+
|
|
239
|
+
const rootClasses = computed(() =>
|
|
240
|
+
cn(
|
|
241
|
+
"q-numeric-keyboard",
|
|
242
|
+
props.dense && "q-numeric-keyboard--dense",
|
|
243
|
+
props.dark && "q-numeric-keyboard--dark",
|
|
244
|
+
hasError.value && "q-numeric-keyboard--error",
|
|
245
|
+
props.disable && "q-numeric-keyboard--disabled",
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
defineExpose({
|
|
250
|
+
/** La valeur courante */
|
|
251
|
+
value: computed(() => props.modelValue ?? ""),
|
|
252
|
+
/** Applique une touche (`"7"`, `"separator"`, `"backspace"`, `"clear"`) */
|
|
253
|
+
press: insert,
|
|
254
|
+
backspace,
|
|
255
|
+
clear,
|
|
256
|
+
/** Retire une nouvelle disposition aléatoire (props `random`) */
|
|
257
|
+
shuffle,
|
|
258
|
+
})
|
|
259
|
+
</script>
|
|
260
|
+
|
|
261
|
+
<template>
|
|
262
|
+
<div
|
|
263
|
+
class="q-numeric-keyboard"
|
|
264
|
+
:class="rootClasses"
|
|
265
|
+
:style="rootStyle"
|
|
266
|
+
role="group"
|
|
267
|
+
:aria-label="label"
|
|
268
|
+
:aria-invalid="hasError ? 'true' : undefined"
|
|
269
|
+
>
|
|
270
|
+
<div v-if="showDots" class="q-numeric-keyboard__dots">
|
|
271
|
+
<span
|
|
272
|
+
v-for="index in dotIndexes"
|
|
273
|
+
:key="index"
|
|
274
|
+
class="q-numeric-keyboard__dot"
|
|
275
|
+
:class="{
|
|
276
|
+
'q-numeric-keyboard__dot--filled': index < dotsFilled,
|
|
277
|
+
'q-numeric-keyboard__dot--pop': pulsingDots.includes(index),
|
|
278
|
+
}"
|
|
279
|
+
aria-hidden="true"
|
|
280
|
+
/>
|
|
281
|
+
<span class="q-numeric-keyboard__sr" role="status" aria-live="polite">{{ dotsAnnouncement }}</span>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<template v-for="(cell, index) in cells" :key="`${cell.kind}-${index}`">
|
|
285
|
+
<span v-if="cell.kind === 'empty'" class="q-numeric-keyboard__key q-numeric-keyboard__key--empty" />
|
|
286
|
+
<button
|
|
287
|
+
v-else
|
|
288
|
+
type="button"
|
|
289
|
+
class="q-numeric-keyboard__key"
|
|
290
|
+
:class="`q-numeric-keyboard__key--${cell.kind}`"
|
|
291
|
+
:disabled="disable"
|
|
292
|
+
:aria-label="cell.ariaLabel"
|
|
293
|
+
@click="onKey(cell)"
|
|
294
|
+
>
|
|
295
|
+
<Icon v-if="cell.kind === 'backspace'" :icon="icons.backspace" aria-hidden="true" />
|
|
296
|
+
<template v-else>{{ cell.label }}</template>
|
|
297
|
+
</button>
|
|
298
|
+
</template>
|
|
299
|
+
|
|
300
|
+
<p v-if="hasError && errorMessage" class="q-numeric-keyboard__error" role="alert">
|
|
301
|
+
{{ errorMessage }}
|
|
302
|
+
</p>
|
|
303
|
+
</div>
|
|
304
|
+
</template>
|
package/components/QPage.vue
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
// QPage — zone de contenu. Prop `
|
|
3
|
-
// (
|
|
2
|
+
// QPage — zone de contenu. Prop `padding` : padding uniforme du contenu —
|
|
3
|
+
// présence (modifier) = 14px, valeur CSS (« 12px ») = cette valeur.
|
|
4
|
+
// Prop `virtual` : active le virtual scroll (rendu fenêtré via QVirtualScroll) quand
|
|
5
|
+
// on a beaucoup d'items.
|
|
4
6
|
// Padding-top/padding-bottom automatiques quand des barres fixed
|
|
5
7
|
// (q-header / q-back-header avant, q-footer après) entourent la page : le
|
|
6
8
|
// contenu reste visible, jamais masqué par les barres au scroll.
|
|
7
|
-
|
|
9
|
+
// La composition du padding (utilisateur + offsets des barres + safe-area) vit dans
|
|
10
|
+
// `styles/main.css` : ce composant ne pose que `--q-page-padding`, l'offset mesuré est
|
|
11
|
+
// publié en variables par `lib/fixedLayout.ts`.
|
|
12
|
+
import { computed, onBeforeUnmount, onMounted, ref } from "vue"
|
|
13
|
+
import type { CSSProperties } from "vue"
|
|
8
14
|
import { useFixedBarOffset } from "../lib/fixedLayout"
|
|
15
|
+
import { resolvePagePadding } from "../lib/pagePadding"
|
|
9
16
|
import QVirtualScroll from "./QVirtualScroll.vue"
|
|
10
17
|
|
|
11
18
|
interface Props {
|
|
19
|
+
/** Padding uniforme du contenu : modifier (sans valeur) = 14px, valeur CSS = cette valeur */
|
|
20
|
+
padding?: boolean | string | number
|
|
12
21
|
/** Active le virtual scroll (rendu fenêtré des items) */
|
|
13
22
|
virtual?: boolean
|
|
14
23
|
/** Données à afficher (requis si virtual) */
|
|
@@ -29,7 +38,9 @@ interface Props {
|
|
|
29
38
|
virtualScrollStickySizeEnd?: number
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
defineProps<Props>()
|
|
41
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
42
|
+
padding: false,
|
|
43
|
+
})
|
|
33
44
|
|
|
34
45
|
const rootEl = ref<HTMLElement | null>(null)
|
|
35
46
|
// ref fonctionnel : sur un élément → l'élément ; sur q-virtual-scroll (composant) → .$el
|
|
@@ -38,8 +49,16 @@ const setRoot = (el: unknown) => {
|
|
|
38
49
|
rootEl.value = node?.$el ?? node ?? null
|
|
39
50
|
}
|
|
40
51
|
|
|
41
|
-
// Offset automatique : padding
|
|
52
|
+
// Offset automatique : padding (haut/bas) = hauteur des barres fixed autour de la page
|
|
42
53
|
useFixedBarOffset(rootEl, "page")
|
|
54
|
+
|
|
55
|
+
// Padding utilisateur → variable CSS lue par `.q-page` (styles/main.css), qui le
|
|
56
|
+
// compose avec les offsets et la safe-area. Inline (et non posé à l'impératif) pour
|
|
57
|
+
// être rendu dès le SSR.
|
|
58
|
+
const rootStyle = computed<CSSProperties | undefined>(() => {
|
|
59
|
+
const padding = resolvePagePadding(props.padding)
|
|
60
|
+
return padding ? ({ "--q-page-padding": padding } as CSSProperties) : undefined
|
|
61
|
+
})
|
|
43
62
|
</script>
|
|
44
63
|
|
|
45
64
|
<template>
|
|
@@ -47,6 +66,7 @@ useFixedBarOffset(rootEl, "page")
|
|
|
47
66
|
v-if="virtual"
|
|
48
67
|
:ref="setRoot"
|
|
49
68
|
class="q-page"
|
|
69
|
+
:style="rootStyle"
|
|
50
70
|
:items="items"
|
|
51
71
|
:item-key="itemKey"
|
|
52
72
|
:virtual-scroll-slice-size="virtualScrollSliceSize"
|
|
@@ -67,7 +87,7 @@ useFixedBarOffset(rootEl, "page")
|
|
|
67
87
|
<slot name="after" />
|
|
68
88
|
</template>
|
|
69
89
|
</q-virtual-scroll>
|
|
70
|
-
<div v-else :ref="setRoot" class="q-page" v-bind="$attrs">
|
|
90
|
+
<div v-else :ref="setRoot" class="q-page" :style="rootStyle" v-bind="$attrs">
|
|
71
91
|
<slot />
|
|
72
92
|
</div>
|
|
73
93
|
</template>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QPageContainer — zone de contenu du QLayout (modèle Quasar) : l'élément qui
|
|
3
|
+
// enveloppe la ou les pages, typiquement le <router-view />.
|
|
4
|
+
//
|
|
5
|
+
// <q-layout view="hHh LpR fFf">
|
|
6
|
+
// <q-header>…</q-header>
|
|
7
|
+
// <q-sidebar side="left" show-if-above>…</q-sidebar>
|
|
8
|
+
// <q-page-container>
|
|
9
|
+
// <q-page padding>…</q-page>
|
|
10
|
+
// </q-page-container>
|
|
11
|
+
// </q-layout>
|
|
12
|
+
//
|
|
13
|
+
// Il occupe la cellule « page » du QLayout (règle `.q-layout > *`) et sert de colonne
|
|
14
|
+
// flex : la page qu'il contient remplit la zone (cf. styles/main.css).
|
|
15
|
+
//
|
|
16
|
+
// Compensation des barres `fixed` : le conteneur ne l'applique que s'il **ne contient
|
|
17
|
+
// pas** de q-page — dans ce cas c'est la page qui s'en charge (règle `:has()` du CSS,
|
|
18
|
+
// donc jamais de double padding). Les hauteurs sont mesurées et publiées en variables
|
|
19
|
+
// par `lib/fixedLayout.ts`, exactement comme pour q-page.
|
|
20
|
+
import { ref } from "vue"
|
|
21
|
+
import { useFixedBarOffset } from "../lib/fixedLayout"
|
|
22
|
+
|
|
23
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
24
|
+
|
|
25
|
+
useFixedBarOffset(rootEl, "page")
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div ref="rootEl" class="q-page-container" v-bind="$attrs">
|
|
30
|
+
<slot />
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|