@dnax/ui 0.0.9 → 0.0.11
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/QDialog.vue +2 -2
- package/components/QDialogProvider.vue +20 -26
- package/components/QSidebar.vue +12 -0
- package/components/internal/QDialogHost.vue +45 -0
- package/index.ts +1 -1
- package/lib/q.ts +35 -2
- package/package.json +1 -1
- package/scripts/generate-exports.ts +1 -1
- package/styles/main.css +67 -1
package/components/QDialog.vue
CHANGED
|
@@ -40,8 +40,8 @@ interface Props {
|
|
|
40
40
|
contentStyle?: StyleValue
|
|
41
41
|
noBackdropDismiss?: boolean
|
|
42
42
|
noEscDismiss?: boolean
|
|
43
|
-
/** Animation : fade | zoom | slide-up | slide-down | slide-left | slide-right (sinon basée sur position) */
|
|
44
|
-
transition?: "fade" | "zoom" | "slide-up" | "slide-down" | "slide-left" | "slide-right"
|
|
43
|
+
/** Animation : fade | zoom | slide-up | slide-down | slide-left | slide-right | swipe-left | swipe-right (sinon basée sur position) */
|
|
44
|
+
transition?: "fade" | "zoom" | "slide-up" | "slide-down" | "slide-left" | "slide-right" | "swipe-left" | "swipe-right"
|
|
45
45
|
/** Durée des transitions d'entrée/sortie en ms (défaut CSS : ~200ms entrée, ~150ms sortie) */
|
|
46
46
|
transitionDuration?: number
|
|
47
47
|
}
|
|
@@ -2,26 +2,28 @@
|
|
|
2
2
|
// QDialogProvider — rend la pile de dialogues programmatiques ($q.dialog).
|
|
3
3
|
// Intégré dans QConfigProvider (rendu automatiquement par le plus externe),
|
|
4
4
|
// mais utilisable aussi en autonome : <q-dialog-provider><slot /></q-dialog-provider>.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
//
|
|
6
|
+
// Pattern Quasar : le composant passé à $q.dialog.open({ component }) DOIT contenir
|
|
7
|
+
// un <q-dialog v-model="open" @hide="onDialogHide"> à sa racine, piloté via le
|
|
8
|
+
// composable useDialogPluginComponent() (exporté par @dnax/ui). Le provider fournit
|
|
9
|
+
// le contexte (open, onOK, onCancel) via QDialogHost ; à la fermeture (backdrop,
|
|
10
|
+
// Échap, ×, onDialogOK, onDialogCancel), le résolveur correspondant est appelé
|
|
7
11
|
// puis l'entrée est retirée de la pile.
|
|
8
12
|
import { computed, inject } from "vue"
|
|
9
13
|
import { dialogStack, closeDialog } from "../lib/q"
|
|
10
14
|
import type { DialogController } from "../lib/q"
|
|
11
15
|
import { qConfigKey } from "../lib/config"
|
|
12
16
|
import { themeVarsStyle } from "../lib/themeVars"
|
|
17
|
+
import QDialogHost from "./internal/QDialogHost.vue"
|
|
13
18
|
|
|
14
19
|
const dialogs = dialogStack
|
|
15
20
|
|
|
16
21
|
const config = inject(qConfigKey, null)
|
|
17
22
|
|
|
18
|
-
// Les dialogs sont téléportés au body (QDialog) : on repose
|
|
19
|
-
// sur
|
|
23
|
+
// Les dialogs sont téléportés au body (QDialog du composant passé) : on repose
|
|
24
|
+
// les tokens du thème sur le host pour qu'ils héritent des couleurs/radius du provider.
|
|
20
25
|
const dialogThemeStyle = themeVarsStyle(computed(() => config?.theme.value ?? {}))
|
|
21
26
|
|
|
22
|
-
const contentStyle = (ctrl: DialogController) =>
|
|
23
|
-
typeof ctrl._opts.fullscreen === "string" ? { width: ctrl._opts.fullscreen } : undefined
|
|
24
|
-
|
|
25
27
|
const handleOk = (ctrl: DialogController, data?: unknown) => {
|
|
26
28
|
ctrl._resolvers.ok?.(data)
|
|
27
29
|
closeDialog(ctrl)
|
|
@@ -38,28 +40,20 @@ const handleDismiss = (ctrl: DialogController) => {
|
|
|
38
40
|
|
|
39
41
|
<template>
|
|
40
42
|
<slot />
|
|
41
|
-
<
|
|
43
|
+
<div
|
|
42
44
|
v-for="(dialog, idx) in dialogs"
|
|
43
45
|
:key="idx"
|
|
44
|
-
:
|
|
45
|
-
:maximized="dialog._opts.fullscreen === true"
|
|
46
|
-
:content-style="[contentStyle(dialog), dialogThemeStyle]"
|
|
47
|
-
:content-class="dialog._opts.class"
|
|
48
|
-
:persistent="dialog._opts.persistent"
|
|
49
|
-
@update:model-value="(v: boolean) => !v && handleDismiss(dialog)"
|
|
46
|
+
:style="dialogThemeStyle"
|
|
50
47
|
>
|
|
51
|
-
<q-dialog-
|
|
52
|
-
|
|
53
|
-
:
|
|
54
|
-
:
|
|
55
|
-
|
|
56
|
-
<component
|
|
57
|
-
:is="dialog._opts.component"
|
|
58
|
-
v-bind="dialog._opts.componentProps"
|
|
48
|
+
<q-dialog-host
|
|
49
|
+
:ctrl="dialog"
|
|
50
|
+
:on-ok="(data?: unknown) => handleOk(dialog, data)"
|
|
51
|
+
:on-cancel="() => handleCancel(dialog)"
|
|
52
|
+
:on-hide="() => handleDismiss(dialog)"
|
|
59
53
|
@ok="(data?: unknown) => handleOk(dialog, data)"
|
|
60
|
-
@cancel="handleCancel(dialog)"
|
|
61
|
-
@dismiss="handleDismiss(dialog)"
|
|
62
|
-
@close="handleDismiss(dialog)"
|
|
54
|
+
@cancel="() => handleCancel(dialog)"
|
|
55
|
+
@dismiss="() => handleDismiss(dialog)"
|
|
56
|
+
@close="() => handleDismiss(dialog)"
|
|
63
57
|
/>
|
|
64
|
-
</
|
|
58
|
+
</div>
|
|
65
59
|
</template>
|
package/components/QSidebar.vue
CHANGED
|
@@ -113,6 +113,18 @@ onBeforeUnmount(() => {
|
|
|
113
113
|
if (typeof document !== "undefined") document.removeEventListener("keydown", onDocKeydown)
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
+
// Verrouille le scroll du body quand la sidebar offcanvas est ouverte : le body
|
|
117
|
+
// ne bouge plus en arrière-plan (et le swipe ne part pas sur la page)
|
|
118
|
+
watch(
|
|
119
|
+
() => !isStatic.value && open.value,
|
|
120
|
+
(locked) => {
|
|
121
|
+
if (typeof document !== "undefined") {
|
|
122
|
+
document.body.style.overflow = locked ? "hidden" : ""
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{ immediate: true },
|
|
126
|
+
)
|
|
127
|
+
|
|
116
128
|
const rootClasses = computed(() =>
|
|
117
129
|
cn(
|
|
118
130
|
"q-sidebar",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QDialogHost — host interne du $q.dialog : rend le composant passé tel quel et
|
|
3
|
+
// lui fournit le contexte plugin (pattern Quasar). Le composant passé DOIT
|
|
4
|
+
// contenir un <q-dialog v-model="open" @hide="onDialogHide"> comme racine, piloté
|
|
5
|
+
// via useDialogPluginComponent(). À la fermeture (backdrop/Échap/× → @hide), ou
|
|
6
|
+
// via onDialogOK / onDialogCancel, les résolveurs sont appelés et l'entrée retirée.
|
|
7
|
+
import { provide, ref } from "vue"
|
|
8
|
+
import { qDialogPluginKey } from "../../lib/q"
|
|
9
|
+
import type { DialogController } from "../../lib/q"
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
ctrl: DialogController
|
|
13
|
+
onOk: (data?: unknown) => void
|
|
14
|
+
onCancel: () => void
|
|
15
|
+
onHide: () => void
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const open = ref(true)
|
|
19
|
+
|
|
20
|
+
provide(qDialogPluginKey, {
|
|
21
|
+
open,
|
|
22
|
+
onOK: (data?: unknown) => props.onOk(data),
|
|
23
|
+
onCancel: () => props.onCancel(),
|
|
24
|
+
onHide: () => props.onHide(),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Le composant émet aussi les events legacy (ok/cancel/dismiss/close)
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
ok: [data?: unknown]
|
|
30
|
+
cancel: []
|
|
31
|
+
dismiss: []
|
|
32
|
+
close: []
|
|
33
|
+
}>()
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<component
|
|
38
|
+
:is="props.ctrl._opts.component"
|
|
39
|
+
v-bind="props.ctrl._opts.componentProps"
|
|
40
|
+
@ok="emit('ok', $event)"
|
|
41
|
+
@cancel="emit('cancel')"
|
|
42
|
+
@dismiss="emit('dismiss')"
|
|
43
|
+
@close="emit('close')"
|
|
44
|
+
/>
|
|
45
|
+
</template>
|
package/index.ts
CHANGED
|
@@ -126,7 +126,7 @@ export { default as QUploader } from "./components/QUploader.vue"
|
|
|
126
126
|
export { default as QVideo } from "./components/QVideo.vue"
|
|
127
127
|
export { default as QVirtualScroll } from "./components/QVirtualScroll.vue"
|
|
128
128
|
|
|
129
|
-
export { $q, usePlugin, useQ, QPlugin, dialogStack, closeDialog, bottomSheetStack, closeBottomSheet } from "./lib/q"
|
|
129
|
+
export { $q, usePlugin, useQ, QPlugin, dialogStack, closeDialog, bottomSheetStack, closeBottomSheet, useDialogPluginComponent } from "./lib/q"
|
|
130
130
|
export type {
|
|
131
131
|
DialogOptions,
|
|
132
132
|
DialogController,
|
package/lib/q.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// app.use(QPlugin) → accès $q via this.$q / getCurrentInstance().proxy.$q
|
|
6
6
|
// ou import direct :
|
|
7
7
|
// import { $q } from "@dnax/ui" → $q.dialog.open(...)
|
|
8
|
-
import { ref } from "vue"
|
|
8
|
+
import { inject, ref } from "vue"
|
|
9
9
|
import { h } from "vue"
|
|
10
|
-
import type { App, Component, Ref } from "vue"
|
|
10
|
+
import type { App, Component, InjectionKey, Ref } from "vue"
|
|
11
11
|
import { toast } from "vue-sonner"
|
|
12
12
|
import type { ExternalToast } from "vue-sonner"
|
|
13
13
|
import QNotifyToast from "../components/QNotifyToast.vue"
|
|
@@ -44,6 +44,39 @@ export interface DialogController {
|
|
|
44
44
|
_opts: DialogOptions
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// — Contexte plugin dialog (pattern Quasar) —
|
|
48
|
+
// Le composant passé à $q.dialog.open({ component }) DOIT contenir un
|
|
49
|
+
// <q-dialog v-model="open" @hide="onDialogHide"> à sa racine ; il pilote
|
|
50
|
+
// l'ouverture via le composable useDialogPluginComponent().
|
|
51
|
+
export interface DialogPluginContext {
|
|
52
|
+
/** Ref d'ouverture (v-model du <q-dialog> du composant) */
|
|
53
|
+
open: Ref<boolean>
|
|
54
|
+
/** Ferme en validant — le résolveur onOK est appelé */
|
|
55
|
+
onOK: (data?: unknown) => void
|
|
56
|
+
/** Ferme en annulant — le résolveur onCancel est appelé */
|
|
57
|
+
onCancel: () => void
|
|
58
|
+
/** Fermé par backdrop / Échap / × — le résolveur onDismiss est appelé */
|
|
59
|
+
onHide: () => void
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const qDialogPluginKey: InjectionKey<DialogPluginContext> = Symbol("q-dialog-plugin")
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Composable Quasar-style pour les composants passés à $q.dialog.open().
|
|
66
|
+
* Le composant doit rendre un <q-dialog v-model="open" @hide="onDialogHide">
|
|
67
|
+
* comme racine. Expose aussi onDialogOK / onDialogCancel pour fermer en validant
|
|
68
|
+
* ou en annulant, et onDialogHide à brancher sur @hide du q-dialog.
|
|
69
|
+
*/
|
|
70
|
+
export function useDialogPluginComponent() {
|
|
71
|
+
const ctx = inject(qDialogPluginKey, null)
|
|
72
|
+
return {
|
|
73
|
+
open: ctx?.open ?? ref(false),
|
|
74
|
+
onDialogHide: () => ctx?.onHide(),
|
|
75
|
+
onDialogOK: (data?: unknown) => ctx?.onOK(data),
|
|
76
|
+
onDialogCancel: () => ctx?.onCancel(),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
47
80
|
// — Pile de dialogues (singleton module-level, client-side) —
|
|
48
81
|
const dialogs: Ref<DialogController[]> = ref([])
|
|
49
82
|
|
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ const exports = files
|
|
|
26
26
|
|
|
27
27
|
// Exports manuels (composables / helpers) — conservés à chaque régénération
|
|
28
28
|
const manualExports = `
|
|
29
|
-
export { $q, usePlugin, useQ, QPlugin, dialogStack, closeDialog, bottomSheetStack, closeBottomSheet } from "./lib/q"
|
|
29
|
+
export { $q, usePlugin, useQ, QPlugin, dialogStack, closeDialog, bottomSheetStack, closeBottomSheet, useDialogPluginComponent } from "./lib/q"
|
|
30
30
|
export type {
|
|
31
31
|
DialogOptions,
|
|
32
32
|
DialogController,
|
package/styles/main.css
CHANGED
|
@@ -3621,8 +3621,13 @@ html.dark body {
|
|
|
3621
3621
|
transition:
|
|
3622
3622
|
transform 0.25s ease,
|
|
3623
3623
|
visibility 0.25s;
|
|
3624
|
-
/* Scroll vertical natif autorisé, gestes horizontaux (swipe pour fermer) au JS
|
|
3624
|
+
/* Scroll vertical natif autorisé, gestes horizontaux (swipe pour fermer) au JS.
|
|
3625
|
+
touch-action n'est PAS hérité → on l'applique à tous les descendants : le
|
|
3626
|
+
geste peut démarrer sur le contenu scrollable (.q-sidebar__content) sans que
|
|
3627
|
+
le navigateur ne prenne le swipe pour le body. */
|
|
3625
3628
|
touch-action: pan-y;
|
|
3629
|
+
/* Empêche le scroll-chaining : swiper la sidebar ne fait plus bouger le body */
|
|
3630
|
+
overscroll-behavior: contain;
|
|
3626
3631
|
/* Safe-area : encoche, barre d'accueil et côtés en paysage */
|
|
3627
3632
|
padding-top: 0;
|
|
3628
3633
|
padding-top: constant(safe-area-inset-top);
|
|
@@ -3635,6 +3640,13 @@ html.dark body {
|
|
|
3635
3640
|
padding-left: env(safe-area-inset-left);
|
|
3636
3641
|
}
|
|
3637
3642
|
|
|
3643
|
+
/* Tous les descendants (contenu scrollable, menu, boutons…) : gestes horizontaux
|
|
3644
|
+
au JS, scroll vertical natif uniquement */
|
|
3645
|
+
.q-sidebar--offcanvas,
|
|
3646
|
+
.q-sidebar--offcanvas * {
|
|
3647
|
+
touch-action: pan-y;
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3638
3650
|
.q-sidebar--offcanvas.q-sidebar--right {
|
|
3639
3651
|
left: auto;
|
|
3640
3652
|
right: 0;
|
|
@@ -3702,6 +3714,8 @@ html.dark body {
|
|
|
3702
3714
|
overflow-y: auto;
|
|
3703
3715
|
padding: 8px;
|
|
3704
3716
|
scrollbar-width: thin;
|
|
3717
|
+
/* Le scroll du contenu ne doit pas se propager au body (swipe-to-close) */
|
|
3718
|
+
overscroll-behavior: contain;
|
|
3705
3719
|
}
|
|
3706
3720
|
|
|
3707
3721
|
.q-sidebar__footer {
|
|
@@ -6223,6 +6237,58 @@ html.dark body {
|
|
|
6223
6237
|
}
|
|
6224
6238
|
}
|
|
6225
6239
|
|
|
6240
|
+
/* swipe-* : le panneau glisse depuis un bord et y REVIENT à la fermeture
|
|
6241
|
+
(aller-retour, comme la sidebar) — swipe-left : vient de la droite et y
|
|
6242
|
+
retourne ; swipe-right : vient de la gauche et y retourne. */
|
|
6243
|
+
.q-dialog-swipe-left-enter-active,
|
|
6244
|
+
.q-dialog-swipe-left-leave-active,
|
|
6245
|
+
.q-dialog-swipe-right-enter-active,
|
|
6246
|
+
.q-dialog-swipe-right-leave-active {
|
|
6247
|
+
transition: opacity var(--q-dialog-duration-enter, 0.2s) ease;
|
|
6248
|
+
}
|
|
6249
|
+
|
|
6250
|
+
.q-dialog-swipe-left-enter-active .q-dialog__content,
|
|
6251
|
+
.q-dialog-swipe-left-leave-active .q-dialog__content {
|
|
6252
|
+
--q-dialog-swipe-x: 100vw;
|
|
6253
|
+
}
|
|
6254
|
+
|
|
6255
|
+
.q-dialog-swipe-right-enter-active .q-dialog__content,
|
|
6256
|
+
.q-dialog-swipe-right-leave-active .q-dialog__content {
|
|
6257
|
+
--q-dialog-swipe-x: -100vw;
|
|
6258
|
+
}
|
|
6259
|
+
|
|
6260
|
+
.q-dialog-swipe-left-enter-active .q-dialog__content,
|
|
6261
|
+
.q-dialog-swipe-right-enter-active .q-dialog__content {
|
|
6262
|
+
animation: q-dialog-swipe-in var(--q-dialog-duration-enter, 0.3s) cubic-bezier(0.32, 0.72, 0, 1);
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
.q-dialog-swipe-left-leave-active .q-dialog__content,
|
|
6266
|
+
.q-dialog-swipe-right-leave-active .q-dialog__content {
|
|
6267
|
+
animation: q-dialog-swipe-out var(--q-dialog-duration-leave, 0.2s) ease-in forwards;
|
|
6268
|
+
}
|
|
6269
|
+
|
|
6270
|
+
@keyframes q-dialog-swipe-in {
|
|
6271
|
+
from {
|
|
6272
|
+
opacity: 0.6;
|
|
6273
|
+
transform: translateX(var(--q-dialog-swipe-x, 100vw));
|
|
6274
|
+
}
|
|
6275
|
+
to {
|
|
6276
|
+
opacity: 1;
|
|
6277
|
+
transform: none;
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
|
|
6281
|
+
@keyframes q-dialog-swipe-out {
|
|
6282
|
+
from {
|
|
6283
|
+
opacity: 1;
|
|
6284
|
+
transform: none;
|
|
6285
|
+
}
|
|
6286
|
+
to {
|
|
6287
|
+
opacity: 0;
|
|
6288
|
+
transform: translateX(var(--q-dialog-swipe-x, 100vw));
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
|
|
6226
6292
|
/* ===== QContainer ===== */
|
|
6227
6293
|
.q-container {
|
|
6228
6294
|
width: 100%;
|