@dnax/ui 0.0.43 → 0.0.45

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,146 +0,0 @@
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>
@@ -1,305 +0,0 @@
1
- <script setup lang="ts">
2
- // QVideo — lecteur vidéo basé sur le framework HTML de Video.js v10 (@videojs/html) :
3
- // <q-video src="movie.mp4" poster="…" ratio="16/9" />
4
- // Custom elements composables : <video-player> > <video-skin> > media element.
5
- // Le média est choisi selon la source : <video> (mp4/webm/ogg), <youtube-video>
6
- // (YouTube), <hlsjs-video> (.m3u8) — mux-video est aussi enregistré (prêt à l'emploi).
7
- // Les modules sont chargés uniquement côté client (SSR safe — customElements).
8
- // Events : @ready (media element), @play, @pause, @ended, @timeupdate, @loadedmetadata, @volumechange.
9
- // Méthodes exposées : play / pause / togglePlay / seek / getCurrentTime / getDuration / setVolume / setMuted / isPlaying.
10
- import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"
11
- // Importé en texte brut + injecté côté client : passé dans le pipeline CSS normal, ce
12
- // fichier tiers (build Tailwind de @videojs/html) contient du `@layer`/`@scope` natif qui
13
- // casse le build des apps consommatrices en Tailwind v3 (PostCSS exige le `@tailwind`
14
- // correspondant). L'injection runtime l'exclut de PostCSS sans perdre les styles globaux.
15
- import skinCss from "@videojs/html/video/skin.css?raw"
16
-
17
- interface Props {
18
- /** URL de la vidéo (mp4, webm, ogg, HLS .m3u8, YouTube…) */
19
- src?: string
20
- /** Image d'affiche avant lecture */
21
- poster?: string
22
- /** Image de remplacement affichée tant que la vidéo n'est pas chargée (ou slot #placeholder) */
23
- placeholder?: string
24
- /** Texte alternatif de l'image placeholder (défaut "Video placeholder") */
25
- placeholderAlt?: string
26
- /** Affiche les contrôles (défaut true). Le skin par défaut gère ses contrôles
27
- * (état visible/masqué pendant la lecture). */
28
- controls?: boolean
29
- /** Démarre la lecture automatiquement (souvent combiné avec muted) */
30
- autoplay?: boolean
31
- /** Boucle à la fin */
32
- loop?: boolean
33
- /** Muet */
34
- muted?: boolean
35
- /** Chargement : "auto" | "metadata" (défaut) | "none" */
36
- preload?: string
37
- /** Ratio d'affichage : "16/9" (défaut), "4/3", "1/1", "none" (hauteur auto) */
38
- ratio?: string
39
- /** Largeur max du lecteur (défaut 100%) */
40
- width?: string
41
- }
42
-
43
- const props = withDefaults(defineProps<Props>(), {
44
- src: "",
45
- poster: "",
46
- placeholder: "",
47
- placeholderAlt: "Video placeholder",
48
- controls: true,
49
- autoplay: false,
50
- loop: false,
51
- muted: false,
52
- preload: "metadata",
53
- ratio: "16/9",
54
- width: "",
55
- })
56
-
57
- const emit = defineEmits<{
58
- ready: [media: any]
59
- play: []
60
- pause: []
61
- ended: []
62
- timeupdate: [payload: { currentTime: number; duration: number }]
63
- loadedmetadata: [payload: { duration: number }]
64
- volumechange: [payload: { muted: boolean; volume: number }]
65
- }>()
66
-
67
- /** URL YouTube (watch, shorts, embed, youtu.be) */
68
- const isYoutube = computed(() =>
69
- /(youtube\.com\/(watch\?v=|shorts\/|embed\/)|youtu\.be\/)/.test(props.src),
70
- )
71
- /** Flux HLS (.m3u8) → media element hlsjs-video */
72
- const isHls = computed(() => /\.m3u8(\?|$)/.test(props.src))
73
-
74
- const mediaEl = ref<HTMLElement | null>(null)
75
- const playing = ref(false)
76
- /** true dès que le média a chargé ses métadonnées (ou qu'il joue) → masque le placeholder */
77
- const metaLoaded = ref(false)
78
-
79
- const media = (): any => mediaEl.value
80
-
81
- const onPlay = () => {
82
- metaLoaded.value = true
83
- playing.value = true
84
- emit("play")
85
- }
86
- const onPause = () => {
87
- playing.value = false
88
- emit("pause")
89
- }
90
- const onEnded = () => emit("ended")
91
- const onTimeupdate = () =>
92
- emit("timeupdate", { currentTime: media()?.currentTime ?? 0, duration: media()?.duration ?? 0 })
93
- const onLoadedmetadata = () => {
94
- metaLoaded.value = true
95
- emit("loadedmetadata", { duration: media()?.duration ?? 0 })
96
- }
97
- const onVolumechange = () =>
98
- emit("volumechange", { muted: media()?.muted ?? false, volume: media()?.volume ?? 1 })
99
-
100
- const listeners: Array<[string, EventListener]> = [
101
- ["play", onPlay],
102
- ["pause", onPause],
103
- ["ended", onEnded],
104
- ["timeupdate", onTimeupdate],
105
- ["loadedmetadata", onLoadedmetadata],
106
- ["volumechange", onVolumechange],
107
- ]
108
-
109
- const attach = (el: HTMLElement) => {
110
- for (const [type, fn] of listeners) el.addEventListener(type, fn)
111
- }
112
-
113
- const detach = (el: HTMLElement | null) => {
114
- if (!el) return
115
- for (const [type, fn] of listeners) el.removeEventListener(type, fn)
116
- }
117
-
118
- // Attache les listeners dès que le media element existe (et à chaque changement de
119
- // type de média) — sinon loadedmetadata est émis avant l'init et le placeholder
120
- // resterait affiché, bloquant les contrôles.
121
- watch(mediaEl, (el, old) => {
122
- detach(old ?? null)
123
- if (el) attach(el)
124
- }, { immediate: true })
125
-
126
- // Clic sur le placeholder : masque-le et lance la lecture
127
- const onPlaceholderClick = () => {
128
- metaLoaded.value = true
129
- play()
130
- }
131
-
132
- let skinInjected = false
133
- const injectSkin = () => {
134
- if (skinInjected || typeof document === "undefined") return
135
- skinInjected = true
136
- if (document.getElementById("q-video-skin")) return
137
- const style = document.createElement("style")
138
- style.id = "q-video-skin"
139
- style.textContent = skinCss
140
- document.head.appendChild(style)
141
- }
142
-
143
- onMounted(async () => {
144
- injectSkin()
145
- // Charge le framework HTML (custom elements) — uniquement côté client
146
- await Promise.all([
147
- import("@videojs/html/video/player" as string),
148
- import("@videojs/html/video/skin" as string),
149
- import("@videojs/html/media/mux-video" as string),
150
- import("@videojs/html/media/youtube-video" as string),
151
- import("@videojs/html/media/hlsjs-video" as string),
152
- ])
153
- if (typeof customElements !== "undefined") {
154
- await customElements.whenDefined("video-player")
155
- }
156
- emit("ready", mediaEl.value)
157
- })
158
-
159
- onBeforeUnmount(() => detach(mediaEl.value ?? null))
160
-
161
- // — Méthodes exposées —
162
- const play = () => media()?.play()
163
- const pause = () => media()?.pause()
164
- const togglePlay = () => {
165
- const el = media()
166
- if (!el) return
167
- if (el.paused) el.play()
168
- else el.pause()
169
- }
170
- const seek = (t: number) => {
171
- if (media()) media().currentTime = t
172
- }
173
- const getCurrentTime = () => media()?.currentTime ?? 0
174
- const getDuration = () => media()?.duration ?? 0
175
- const setVolume = (v: number) => {
176
- if (media()) media().volume = v
177
- }
178
- const setMuted = (m: boolean) => {
179
- if (media()) media().muted = m
180
- }
181
- const isPlaying = () => playing.value
182
-
183
- defineExpose({
184
- play,
185
- pause,
186
- togglePlay,
187
- seek,
188
- getCurrentTime,
189
- getDuration,
190
- setVolume,
191
- setMuted,
192
- isPlaying,
193
- })
194
-
195
- const wrapperStyle = (): Record<string, string> => {
196
- const style: Record<string, string> = {}
197
- if (props.width) style.maxWidth = props.width
198
- if (props.ratio !== "none") style.aspectRatio = props.ratio.replace("/", " / ")
199
- else style.aspectRatio = "auto"
200
- return style
201
- }
202
- </script>
203
-
204
- <template>
205
- <div class="q-video" :style="wrapperStyle()">
206
- <!-- Custom elements @videojs/html : player > skin > media -->
207
- <video-player class="q-video__player">
208
- <video-skin>
209
- <video
210
- v-if="!isYoutube && !isHls"
211
- ref="mediaEl"
212
- :src="src"
213
- :poster="poster"
214
- :autoplay="autoplay"
215
- :loop="loop"
216
- :muted="muted"
217
- :preload="preload"
218
- playsinline
219
- />
220
- <youtube-video
221
- v-else-if="isYoutube"
222
- ref="mediaEl"
223
- :src="src"
224
- :poster="poster"
225
- :autoplay="autoplay"
226
- :loop="loop"
227
- :muted="muted"
228
- playsinline
229
- />
230
- <hlsjs-video
231
- v-else
232
- ref="mediaEl"
233
- :src="src"
234
- :poster="poster"
235
- :autoplay="autoplay"
236
- :loop="loop"
237
- :muted="muted"
238
- playsinline
239
- />
240
- </video-skin>
241
- </video-player>
242
-
243
- <!-- Placeholder tant que la vidéo n'est pas chargée (clic = lecture) -->
244
- <div
245
- v-if="!metaLoaded && (placeholder || $slots.placeholder)"
246
- class="q-video__placeholder"
247
- role="button"
248
- :aria-label="placeholderAlt"
249
- @click="onPlaceholderClick"
250
- >
251
- <img v-if="placeholder" :src="placeholder" :alt="placeholderAlt" />
252
- <slot name="placeholder" />
253
- </div>
254
- </div>
255
- </template>
256
-
257
- <style scoped>
258
- .q-video {
259
- position: relative;
260
- width: 100%;
261
- overflow: hidden;
262
- border-radius: var(--q-radius, 12px);
263
- background: #000;
264
- }
265
- /* Avant l'upgrade des custom elements : display block + remplir le cadre */
266
- .q-video video-player,
267
- .q-video video-skin,
268
- .q-video video,
269
- .q-video youtube-video,
270
- .q-video hlsjs-video {
271
- display: block;
272
- }
273
- .q-video video-player,
274
- .q-video video-skin {
275
- position: absolute;
276
- inset: 0;
277
- width: 100% !important;
278
- height: 100% !important;
279
- }
280
- .q-video video,
281
- .q-video youtube-video,
282
- .q-video hlsjs-video {
283
- width: 100%;
284
- height: 100%;
285
- object-fit: contain;
286
- }
287
-
288
- /* Placeholder de chargement : image ou contenu, cliquable pour lancer la lecture */
289
- .q-video__placeholder {
290
- position: absolute;
291
- inset: 0;
292
- z-index: 1;
293
- display: flex;
294
- align-items: center;
295
- justify-content: center;
296
- overflow: hidden;
297
- cursor: pointer;
298
- background: #000;
299
- }
300
- .q-video__placeholder img {
301
- width: 100%;
302
- height: 100%;
303
- object-fit: cover;
304
- }
305
- </style>