@dnax/ui 0.0.27 → 0.0.28
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 +26 -12
- package/components/QEditorJs.vue +146 -0
- package/components/QFab.vue +19 -2
- package/components/QSyntax.vue +22 -9
- package/components/QTiptap.vue +993 -0
- package/components/internal/QTiptapTableView.vue +123 -0
- package/components/internal/QTiptapTaskItemView.vue +49 -0
- package/index.ts +12 -0
- package/lib/icons.ts +26 -0
- package/lib/q.ts +5 -1
- package/lib/storage.ts +175 -0
- package/lib/tiptap-table.ts +19 -0
- package/lib/tiptap-task-list.ts +16 -0
- package/lib/touchHold.ts +221 -0
- package/lib/touchPan.ts +237 -0
- package/lib/touchRepeat.ts +148 -0
- package/lib/touchSwipe.ts +182 -0
- package/module.ts +8 -0
- package/package.json +25 -1
- package/scripts/generate-exports.ts +10 -0
- package/styles/main.css +873 -32
package/components/QDialog.vue
CHANGED
|
@@ -41,10 +41,18 @@ interface Props {
|
|
|
41
41
|
contentStyle?: StyleValue
|
|
42
42
|
noBackdropDismiss?: boolean
|
|
43
43
|
noEscDismiss?: boolean
|
|
44
|
-
/** Animation : fade | zoom | slide-up | slide-down |
|
|
45
|
-
|
|
44
|
+
/** Animation : fade | zoom | slide-up | slide-down | sheet-up | sheet-down
|
|
45
|
+
* (même glissement que slide, ouverture qui décélère en fin de course) |
|
|
46
|
+
* slide-left | slide-right | swipe-left | swipe-right (sinon basée sur position) */
|
|
47
|
+
transition?: "fade" | "zoom" | "slide-up" | "slide-down" | "sheet-up" | "sheet-down" | "slide-left" | "slide-right" | "swipe-left" | "swipe-right"
|
|
46
48
|
/** Durée des transitions d'entrée/sortie en ms (défaut CSS : ~200ms entrée, ~150ms sortie) */
|
|
47
49
|
transitionDuration?: number
|
|
50
|
+
/** Courbe d'easing de l'OUVERTURE — valeur CSS ("cubic-bezier(0.32, 0.72, 0, 1)",
|
|
51
|
+
* "ease-out", "linear"…) — défaut : courbe propre à chaque transition
|
|
52
|
+
* (sheet-* = décélération en fin de course, slide-* = ease…) */
|
|
53
|
+
transitionEasingEnter?: string
|
|
54
|
+
/** Courbe d'easing de la FERMETURE — même principe que transition-easing-enter */
|
|
55
|
+
transitionEasingLeave?: string
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -60,6 +68,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
60
68
|
noBackdropDismiss: false,
|
|
61
69
|
noEscDismiss: false,
|
|
62
70
|
transitionDuration: undefined,
|
|
71
|
+
transitionEasingEnter: "",
|
|
72
|
+
transitionEasingLeave: "",
|
|
63
73
|
})
|
|
64
74
|
|
|
65
75
|
const emit = defineEmits<{
|
|
@@ -159,15 +169,19 @@ const transitionName = computed(() =>
|
|
|
159
169
|
props.transition ? `q-dialog-${props.transition}` : "q-dialog",
|
|
160
170
|
)
|
|
161
171
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
// Réglages des transitions (durée + courbes d'easing) exposés en variables CSS
|
|
173
|
+
// sur l'overlay : --q-dialog-duration-enter/leave et
|
|
174
|
+
// --q-dialog-easing-enter/leave (consommées par styles/main.css).
|
|
175
|
+
const transitionStyle = computed<StyleValue>(() => {
|
|
176
|
+
const style: Record<string, string> = {}
|
|
177
|
+
if (props.transitionDuration) {
|
|
178
|
+
style["--q-dialog-duration-enter"] = `${props.transitionDuration}ms`
|
|
179
|
+
style["--q-dialog-duration-leave"] = `${props.transitionDuration}ms`
|
|
180
|
+
}
|
|
181
|
+
if (props.transitionEasingEnter) style["--q-dialog-easing-enter"] = props.transitionEasingEnter
|
|
182
|
+
if (props.transitionEasingLeave) style["--q-dialog-easing-leave"] = props.transitionEasingLeave
|
|
183
|
+
return style
|
|
184
|
+
})
|
|
171
185
|
</script>
|
|
172
186
|
|
|
173
187
|
<template>
|
|
@@ -178,7 +192,7 @@ const durationStyle = computed<StyleValue>(() =>
|
|
|
178
192
|
:ref="markOverlay"
|
|
179
193
|
class="q-dialog__overlay"
|
|
180
194
|
:class="overlayClasses"
|
|
181
|
-
:style="
|
|
195
|
+
:style="transitionStyle"
|
|
182
196
|
@mousedown.self="!persistent && !noBackdropDismiss && (open = false)"
|
|
183
197
|
>
|
|
184
198
|
<div
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// QEditorJs — éditeur par blocs basé sur Editor.js (https://editorjs.io).
|
|
3
|
+
// v-model = données JSON : { time?, blocks: [{ type, data }], version? }.
|
|
4
|
+
// Tools par défaut : paragraph, header (H1-H3), list (bullet/ordered),
|
|
5
|
+
// checklist, quote, code, delimiter. Extensible via la prop `tools`.
|
|
6
|
+
// SSR-safe : l'instance Editor.js n'est créée qu'au montage (client).
|
|
7
|
+
// Note : Editor.js v2 injecte lui-même ses styles à l'init (aucun import CSS
|
|
8
|
+
// nécessaire) ; les surcharges de thème vivent dans styles/main.css.
|
|
9
|
+
import { onBeforeUnmount, onMounted, ref, watch } from "vue"
|
|
10
|
+
|
|
11
|
+
interface QEditorJsBlock {
|
|
12
|
+
type: string
|
|
13
|
+
data: Record<string, unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface QEditorJsData {
|
|
17
|
+
time?: number
|
|
18
|
+
blocks: QEditorJsBlock[]
|
|
19
|
+
version?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Données du document (v-model) — { blocks: [{ type, data }] } */
|
|
24
|
+
data?: QEditorJsData | null
|
|
25
|
+
/** Placeholder du bloc par défaut */
|
|
26
|
+
placeholder?: string
|
|
27
|
+
/** Hauteur minimale de la zone d'édition (défaut : 220px) */
|
|
28
|
+
minHeight?: string
|
|
29
|
+
/** Lecture seule */
|
|
30
|
+
readonly?: boolean
|
|
31
|
+
/** Désactivé (lecture seule + style grisé) */
|
|
32
|
+
disable?: boolean
|
|
33
|
+
/** Tools supplémentaires (défaut : paragraph, header, list, checklist, quote, code, delimiter) */
|
|
34
|
+
tools?: Record<string, any>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
38
|
+
data: null,
|
|
39
|
+
placeholder: "Write something…",
|
|
40
|
+
minHeight: "220px",
|
|
41
|
+
readonly: false,
|
|
42
|
+
disable: false,
|
|
43
|
+
tools: () => ({}),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const emit = defineEmits<{
|
|
47
|
+
"update:modelValue": [data: QEditorJsData]
|
|
48
|
+
ready: [editor: unknown]
|
|
49
|
+
}>()
|
|
50
|
+
|
|
51
|
+
const holderEl = ref<HTMLElement | null>(null)
|
|
52
|
+
let editor: any = null
|
|
53
|
+
let lastJson = ""
|
|
54
|
+
let internal = false
|
|
55
|
+
|
|
56
|
+
const isEditable = () => !props.readonly && !props.disable
|
|
57
|
+
|
|
58
|
+
const emitData = async () => {
|
|
59
|
+
if (!editor) return
|
|
60
|
+
try {
|
|
61
|
+
const saved: QEditorJsData = (await editor.save()) as QEditorJsData
|
|
62
|
+
internal = true
|
|
63
|
+
lastJson = JSON.stringify(saved)
|
|
64
|
+
emit("update:modelValue", saved)
|
|
65
|
+
internal = false
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* document invalide — ignoré */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onMounted(async () => {
|
|
73
|
+
const mod = await import("@editorjs/editorjs")
|
|
74
|
+
const EditorJS = mod.default
|
|
75
|
+
// Les packages tools Editor.js n'embarquent pas tous des types TS
|
|
76
|
+
// @ts-ignore
|
|
77
|
+
const Header = (await import("@editorjs/header")).default
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
const Paragraph = (await import("@editorjs/paragraph")).default
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
const List = (await import("@editorjs/list")).default
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
const Checklist = (await import("@editorjs/checklist")).default
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
const Quote = (await import("@editorjs/quote")).default
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
const Code = (await import("@editorjs/code")).default
|
|
88
|
+
// @ts-ignore
|
|
89
|
+
const Delimiter = (await import("@editorjs/delimiter")).default
|
|
90
|
+
|
|
91
|
+
const defaults: Record<string, any> = {
|
|
92
|
+
paragraph: { class: Paragraph },
|
|
93
|
+
header: { class: Header, inlineToolbar: true, config: { levels: [1, 2, 3] } },
|
|
94
|
+
list: { class: List, inlineToolbar: true },
|
|
95
|
+
checklist: { class: Checklist, inlineToolbar: true },
|
|
96
|
+
quote: { class: Quote, inlineToolbar: true },
|
|
97
|
+
code: { class: Code },
|
|
98
|
+
delimiter: { class: Delimiter },
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
editor = new EditorJS({
|
|
102
|
+
holder: holderEl.value as HTMLElement,
|
|
103
|
+
tools: { ...defaults, ...props.tools },
|
|
104
|
+
data: props.data ?? undefined,
|
|
105
|
+
placeholder: props.placeholder,
|
|
106
|
+
readOnly: !isEditable(),
|
|
107
|
+
onChange: () => { emitData() },
|
|
108
|
+
onReady: () => { emit("ready", editor) },
|
|
109
|
+
})
|
|
110
|
+
emit("ready", editor)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Changement externe → render (sans boucle : on compare au dernier JSON émis)
|
|
114
|
+
watch(
|
|
115
|
+
() => props.data,
|
|
116
|
+
(value) => {
|
|
117
|
+
if (internal || !editor) return
|
|
118
|
+
const json = value ? JSON.stringify(value) : ""
|
|
119
|
+
if (json === lastJson) return
|
|
120
|
+
editor.render(value ?? { time: Date.now(), blocks: [], version: "2.31.6" })
|
|
121
|
+
.then(() => { lastJson = json })
|
|
122
|
+
.catch(() => { /* ignore */ })
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
onBeforeUnmount(() => {
|
|
127
|
+
if (editor && typeof editor.destroy === "function") editor.destroy()
|
|
128
|
+
editor = null
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// — Méthodes exposées —
|
|
132
|
+
const save = () => editor?.save() as Promise<QEditorJsData> | undefined
|
|
133
|
+
const clear = () => editor?.clear()
|
|
134
|
+
const getEditor = () => editor
|
|
135
|
+
|
|
136
|
+
defineExpose({ save, clear, getEditor })
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<template>
|
|
140
|
+
<div
|
|
141
|
+
class="q-editor-js"
|
|
142
|
+
:class="{ 'q-editor-js--readonly': readonly || disable, 'q-editor-js--disable': disable }"
|
|
143
|
+
>
|
|
144
|
+
<div ref="holderEl" class="q-editor-js__holder" :style="{ minHeight }" />
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
package/components/QFab.vue
CHANGED
|
@@ -10,7 +10,7 @@ export const qFabKey: InjectionKey<QFabContext> = Symbol("q-fab")
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<script setup lang="ts">
|
|
13
|
-
import { computed, provide, ref } from "vue"
|
|
13
|
+
import { computed, onBeforeUnmount, onMounted, provide, ref } from "vue"
|
|
14
14
|
import { Icon } from "@iconify/vue"
|
|
15
15
|
import { icons } from "../lib/icons"
|
|
16
16
|
import { colorValue, foregroundFor } from "../lib/colors"
|
|
@@ -48,6 +48,23 @@ const toggle = () => {
|
|
|
48
48
|
if (!props.disable) open.value = !open.value
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Clic à l'extérieur du FAB → fermeture (actions repliées)
|
|
52
|
+
const rootEl = ref<HTMLElement | null>(null)
|
|
53
|
+
const onDocPointerDown = (e: PointerEvent) => {
|
|
54
|
+
if (!props.modelValue || !rootEl.value) return
|
|
55
|
+
if (!rootEl.value.contains(e.target as Node)) open.value = false
|
|
56
|
+
}
|
|
57
|
+
onMounted(() => {
|
|
58
|
+
if (typeof document !== "undefined") {
|
|
59
|
+
document.addEventListener("pointerdown", onDocPointerDown)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
onBeforeUnmount(() => {
|
|
63
|
+
if (typeof document !== "undefined") {
|
|
64
|
+
document.removeEventListener("pointerdown", onDocPointerDown)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
51
68
|
provide<QFabContext>(qFabKey, {
|
|
52
69
|
close: () => {
|
|
53
70
|
open.value = false
|
|
@@ -61,7 +78,7 @@ const mainStyle = computed<Record<string, string>>(() => ({
|
|
|
61
78
|
</script>
|
|
62
79
|
|
|
63
80
|
<template>
|
|
64
|
-
<div class="q-fab" :class="[`q-fab--${position}`, open && 'q-fab--open']">
|
|
81
|
+
<div ref="rootEl" class="q-fab" :class="[`q-fab--${position}`, open && 'q-fab--open']">
|
|
65
82
|
<div class="q-fab__actions" role="menu">
|
|
66
83
|
<slot />
|
|
67
84
|
</div>
|
package/components/QSyntax.vue
CHANGED
|
@@ -63,15 +63,28 @@ const slotCode = computed(() => {
|
|
|
63
63
|
|
|
64
64
|
const source = computed(() => props.code ?? slotCode.value)
|
|
65
65
|
|
|
66
|
-
// Highlighter singleton : une seule instance pour toute l'application
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
66
|
+
// Highlighter singleton : une seule instance pour toute l'application.
|
|
67
|
+
// Stocké sur globalThis (et non au scope module) pour survivre aux
|
|
68
|
+
// réévaluations du module (HMR — édition de QSyntax.vue ou de ses imports,
|
|
69
|
+
// ex. lib/icons.ts) : sinon une instance orpheline est créée à chaque cycle
|
|
70
|
+
// et Shiki avertit (« [Shiki] N instances have been created… »).
|
|
71
|
+
const SHIKI_KEY = "__dnax_ui_shiki_highlighter__"
|
|
72
|
+
const getHighlighter = (): Promise<Highlighter> => {
|
|
73
|
+
const g = globalThis as { [SHIKI_KEY]?: Promise<Highlighter> }
|
|
74
|
+
let highlighter = g[SHIKI_KEY]
|
|
75
|
+
if (!highlighter) {
|
|
76
|
+
highlighter = createHighlighter({
|
|
77
|
+
themes: [DEFAULT_THEME],
|
|
78
|
+
langs: DEFAULT_LANGS,
|
|
79
|
+
engine: createJavaScriptRegexEngine(),
|
|
80
|
+
}).catch((error: unknown) => {
|
|
81
|
+
// Une création en échec ne doit pas bloquer les montages suivants
|
|
82
|
+
delete g[SHIKI_KEY]
|
|
83
|
+
throw error
|
|
84
|
+
})
|
|
85
|
+
g[SHIKI_KEY] = highlighter
|
|
86
|
+
}
|
|
87
|
+
return highlighter
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
const html = ref("")
|