@dnax/ui 0.0.27 → 0.0.29
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/QBar.vue +42 -0
- package/components/QBoard.vue +285 -0
- package/components/QConfigProvider.vue +9 -5
- package/components/QDialog.vue +26 -12
- package/components/QEditorJs.vue +146 -0
- package/components/QFab.vue +19 -2
- package/components/QInteract.vue +387 -0
- package/components/QSpreadsheet.vue +4662 -0
- 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 +18 -0
- package/lib/config.ts +6 -0
- package/lib/formula.test.ts +206 -0
- package/lib/formula.ts +292 -0
- package/lib/icons.ts +43 -0
- package/lib/intersection.ts +118 -0
- package/lib/q.ts +5 -1
- package/lib/spreadsheet.test.ts +92 -0
- package/lib/spreadsheet.ts +175 -0
- 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/lib/useComponentProps.ts +7 -0
- package/module.ts +10 -0
- package/package.json +27 -1
- package/scripts/generate-exports.ts +12 -0
- package/styles/main.css +2217 -32
package/lib/touchPan.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// v-touch-pan — directive : détecte les gestes « pan » (glisser) sur un
|
|
2
|
+
// élément, tactile ET souris (modifier .mouse), équivalent Quasar
|
|
3
|
+
// https://quasar.dev/vue-directives/touch-pan
|
|
4
|
+
//
|
|
5
|
+
// Usage : <div v-touch-pan.horizontal.mouse.prevent="onPan" />
|
|
6
|
+
// modifiers : horizontal | vertical | up | down | left | right (filtres de
|
|
7
|
+
// direction), mouse (inclut la souris), prevent (bloque le scroll natif),
|
|
8
|
+
// stop, capture, passive, mouseCapture
|
|
9
|
+
//
|
|
10
|
+
// Détails passés au handler (compatible Quasar) :
|
|
11
|
+
// { evt, position: { top, left }, direction, delta: { x, y },
|
|
12
|
+
// distance: { x, y }, duration, speed: { x, y },
|
|
13
|
+
// isFirst, isFinal, isVertical, isHorizontal }
|
|
14
|
+
|
|
15
|
+
export interface PanPosition {
|
|
16
|
+
top: number
|
|
17
|
+
left: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PanDelta {
|
|
21
|
+
x: number
|
|
22
|
+
y: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type PanDirection = "up" | "down" | "left" | "right"
|
|
26
|
+
|
|
27
|
+
export interface PanDetails {
|
|
28
|
+
evt: PointerEvent
|
|
29
|
+
position: PanPosition
|
|
30
|
+
direction: PanDirection
|
|
31
|
+
delta: PanDelta
|
|
32
|
+
distance: PanDelta
|
|
33
|
+
duration: number
|
|
34
|
+
speed: PanDelta
|
|
35
|
+
isFirst: boolean
|
|
36
|
+
isFinal: boolean
|
|
37
|
+
isVertical: boolean
|
|
38
|
+
isHorizontal: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type TouchPanHandler = (details: PanDetails) => void
|
|
42
|
+
|
|
43
|
+
interface PanState {
|
|
44
|
+
pointerId: number
|
|
45
|
+
startX: number
|
|
46
|
+
startY: number
|
|
47
|
+
lastX: number
|
|
48
|
+
lastY: number
|
|
49
|
+
startTime: number
|
|
50
|
+
lastTime: number
|
|
51
|
+
axis: "horizontal" | "vertical" | null
|
|
52
|
+
panning: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const PAN_MIN = 4 // seuil avant de déclarer le pan (px)
|
|
56
|
+
|
|
57
|
+
interface TouchPanOptions {
|
|
58
|
+
handler: TouchPanHandler
|
|
59
|
+
allowHorizontal: boolean
|
|
60
|
+
allowVertical: boolean
|
|
61
|
+
allowUp: boolean
|
|
62
|
+
allowDown: boolean
|
|
63
|
+
allowLeft: boolean
|
|
64
|
+
allowRight: boolean
|
|
65
|
+
mouse: boolean
|
|
66
|
+
prevent: boolean
|
|
67
|
+
stop: boolean
|
|
68
|
+
capture: boolean
|
|
69
|
+
passive: boolean
|
|
70
|
+
mouseCapture: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type QElement = HTMLElement & { __qTouchPanCleanup?: () => void }
|
|
74
|
+
|
|
75
|
+
function axisAllowed(options: TouchPanOptions, direction: PanDirection) {
|
|
76
|
+
if (options.allowHorizontal) return direction === "left" || direction === "right"
|
|
77
|
+
if (options.allowVertical) return direction === "up" || direction === "down"
|
|
78
|
+
switch (direction) {
|
|
79
|
+
case "up": return options.allowUp
|
|
80
|
+
case "down": return options.allowDown
|
|
81
|
+
case "left": return options.allowLeft
|
|
82
|
+
case "right": return options.allowRight
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseDirection(dx: number, dy: number): PanDirection {
|
|
87
|
+
return Math.abs(dx) >= Math.abs(dy) ? (dx > 0 ? "right" : "left") : (dy > 0 ? "down" : "up")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function applyPrevent(e: Event, options: TouchPanOptions, active: boolean) {
|
|
91
|
+
if (options.stop) e.stopPropagation()
|
|
92
|
+
if (options.prevent && active) e.preventDefault()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const vTouchPan = {
|
|
96
|
+
mounted(el: HTMLElement, binding: { value?: unknown; modifiers?: Record<string, boolean> }) {
|
|
97
|
+
const handler = binding.value
|
|
98
|
+
if (typeof handler !== "function") {
|
|
99
|
+
console.warn("[v-touch-pan] value doit être une fonction (handler)")
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const m = binding.modifiers ?? {}
|
|
103
|
+
const options: TouchPanOptions = {
|
|
104
|
+
handler: handler as TouchPanHandler,
|
|
105
|
+
allowHorizontal: !!m.horizontal,
|
|
106
|
+
allowVertical: !!m.vertical,
|
|
107
|
+
allowUp: !!m.up,
|
|
108
|
+
allowDown: !!m.down,
|
|
109
|
+
allowLeft: !!m.left,
|
|
110
|
+
allowRight: !!m.right,
|
|
111
|
+
mouse: !!m.mouse || !!m.mouseCapture,
|
|
112
|
+
prevent: !!m.prevent,
|
|
113
|
+
stop: !!m.stop,
|
|
114
|
+
capture: !!m.capture || !!m.mouseCapture,
|
|
115
|
+
passive: !!m.passive,
|
|
116
|
+
mouseCapture: false,
|
|
117
|
+
}
|
|
118
|
+
options.mouseCapture = !!m.mouseCapture
|
|
119
|
+
|
|
120
|
+
let state: PanState | null = null
|
|
121
|
+
|
|
122
|
+
// touch-action : laisser le scroll natif quand on ne capture qu'un axe
|
|
123
|
+
if (options.prevent) el.style.touchAction = "none"
|
|
124
|
+
else if (options.allowHorizontal && !options.allowVertical) el.style.touchAction = "pan-y"
|
|
125
|
+
else if (options.allowVertical && !options.allowHorizontal) el.style.touchAction = "pan-x"
|
|
126
|
+
else el.style.touchAction = options.allowUp || options.allowDown || options.allowLeft || options.allowRight ? "pan-y pan-x" : "auto"
|
|
127
|
+
|
|
128
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
129
|
+
if (state) return
|
|
130
|
+
if (e.pointerType === "mouse" && !options.mouse) return
|
|
131
|
+
applyPrevent(e, options, false)
|
|
132
|
+
state = {
|
|
133
|
+
pointerId: e.pointerId,
|
|
134
|
+
startX: e.clientX,
|
|
135
|
+
startY: e.clientY,
|
|
136
|
+
lastX: e.clientX,
|
|
137
|
+
lastY: e.clientY,
|
|
138
|
+
startTime: performance.now(),
|
|
139
|
+
lastTime: performance.now(),
|
|
140
|
+
axis: null,
|
|
141
|
+
panning: false,
|
|
142
|
+
}
|
|
143
|
+
try { (e.currentTarget as Element).setPointerCapture?.(e.pointerId) } catch { /* ignore */ }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const onPointerMove = (e: PointerEvent) => {
|
|
147
|
+
if (!state || state.pointerId !== e.pointerId) return
|
|
148
|
+
const dx = e.clientX - state.startX
|
|
149
|
+
const dy = e.clientY - state.startY
|
|
150
|
+
const deltaX = e.clientX - state.lastX
|
|
151
|
+
const deltaY = e.clientY - state.lastY
|
|
152
|
+
const now = performance.now()
|
|
153
|
+
const duration = now - state.startTime
|
|
154
|
+
|
|
155
|
+
if (!state.panning) {
|
|
156
|
+
if (Math.abs(dx) < PAN_MIN && Math.abs(dy) < PAN_MIN) return
|
|
157
|
+
const direction = parseDirection(dx, dy)
|
|
158
|
+
if (!axisAllowed(options, direction)) return
|
|
159
|
+
state.axis = direction === "left" || direction === "right" ? "horizontal" : "vertical"
|
|
160
|
+
state.panning = true
|
|
161
|
+
applyPrevent(e, options, true)
|
|
162
|
+
state.lastX = e.clientX
|
|
163
|
+
state.lastY = e.clientY
|
|
164
|
+
state.lastTime = now
|
|
165
|
+
emit(state, e, direction, { x: 0, y: 0 }, { x: dx, y: dy }, duration, true)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const direction = state.axis === "horizontal" ? (dx > 0 ? "right" : "left") : (dy > 0 ? "down" : "up")
|
|
170
|
+
applyPrevent(e, options, true)
|
|
171
|
+
state.lastX = e.clientX
|
|
172
|
+
state.lastY = e.clientY
|
|
173
|
+
state.lastTime = now
|
|
174
|
+
emit(state, e, direction, { x: deltaX, y: deltaY }, { x: dx, y: dy }, duration, false)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const onPointerEnd = (e: PointerEvent) => {
|
|
178
|
+
if (!state || state.pointerId !== e.pointerId) return
|
|
179
|
+
const dx = e.clientX - state.startX
|
|
180
|
+
const dy = e.clientY - state.startY
|
|
181
|
+
const duration = performance.now() - state.startTime
|
|
182
|
+
if (state.panning) {
|
|
183
|
+
const direction = state.axis === "horizontal" ? (dx > 0 ? "right" : "left") : (dy > 0 ? "down" : "up")
|
|
184
|
+
emit(state, e, direction, { x: 0, y: 0 }, { x: dx, y: dy }, duration, false, true)
|
|
185
|
+
}
|
|
186
|
+
state = null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const onPointerCancel = (e: PointerEvent) => {
|
|
190
|
+
if (state && state.pointerId === e.pointerId) state = null
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const emit = (
|
|
194
|
+
s: PanState,
|
|
195
|
+
evt: PointerEvent,
|
|
196
|
+
direction: PanDirection,
|
|
197
|
+
delta: PanDelta,
|
|
198
|
+
distance: PanDelta,
|
|
199
|
+
duration: number,
|
|
200
|
+
isFirst: boolean,
|
|
201
|
+
isFinal = false,
|
|
202
|
+
) => {
|
|
203
|
+
const elapsed = Math.max(duration, 1)
|
|
204
|
+
options.handler({
|
|
205
|
+
evt,
|
|
206
|
+
position: { top: evt.clientY, left: evt.clientX },
|
|
207
|
+
direction,
|
|
208
|
+
delta,
|
|
209
|
+
distance,
|
|
210
|
+
duration,
|
|
211
|
+
speed: { x: Math.abs(distance.x) / elapsed * 1000, y: Math.abs(distance.y) / elapsed * 1000 },
|
|
212
|
+
isFirst,
|
|
213
|
+
isFinal,
|
|
214
|
+
isVertical: s.axis === "vertical",
|
|
215
|
+
isHorizontal: s.axis === "horizontal",
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const capture = options.capture
|
|
220
|
+
el.addEventListener("pointerdown", onPointerDown, { capture, passive: options.passive })
|
|
221
|
+
el.addEventListener("pointermove", onPointerMove, { capture, passive: options.passive })
|
|
222
|
+
el.addEventListener("pointerup", onPointerEnd, { capture })
|
|
223
|
+
el.addEventListener("pointercancel", onPointerCancel, { capture })
|
|
224
|
+
|
|
225
|
+
;(el as QElement).__qTouchPanCleanup = () => {
|
|
226
|
+
el.removeEventListener("pointerdown", onPointerDown, { capture } as any)
|
|
227
|
+
el.removeEventListener("pointermove", onPointerMove, { capture } as any)
|
|
228
|
+
el.removeEventListener("pointerup", onPointerEnd, { capture } as any)
|
|
229
|
+
el.removeEventListener("pointercancel", onPointerCancel, { capture } as any)
|
|
230
|
+
el.style.touchAction = ""
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
unmounted(el: HTMLElement) {
|
|
234
|
+
;(el as QElement).__qTouchPanCleanup?.()
|
|
235
|
+
delete (el as QElement).__qTouchPanCleanup
|
|
236
|
+
},
|
|
237
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// v-touch-repeat — directive : déclenche un handler en boucle tant que
|
|
2
|
+
// l'élément est pressé (appui long répété), équivalent Quasar
|
|
3
|
+
// https://quasar.dev/vue-directives/touch-repeat
|
|
4
|
+
//
|
|
5
|
+
// Usage :
|
|
6
|
+
// <button v-touch-repeat.mouse="onRepeat">…</button>
|
|
7
|
+
// <button v-touch-repeat.mouse="{ delay: 500, interval: 100 }">…</button>
|
|
8
|
+
// <button v-touch-repeat.mouse="'500:100'">…</button>
|
|
9
|
+
// modifiers : mouse (inclut la souris), capture / mouseCapture, stop, prevent,
|
|
10
|
+
// passive
|
|
11
|
+
//
|
|
12
|
+
// Détails du handler : { evt, count, elapsed }
|
|
13
|
+
|
|
14
|
+
export interface TouchRepeatDetails {
|
|
15
|
+
evt: Event
|
|
16
|
+
/** Nombre d'appels (1 = premier, après le délai) */
|
|
17
|
+
count: number
|
|
18
|
+
/** Temps écoulé depuis le pointerdown (ms) */
|
|
19
|
+
elapsed: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type TouchRepeatHandler = (details: TouchRepeatDetails) => void
|
|
23
|
+
|
|
24
|
+
const DEFAULTS = { delay: 600, interval: 150 }
|
|
25
|
+
|
|
26
|
+
interface RepeatOptions {
|
|
27
|
+
handler: TouchRepeatHandler
|
|
28
|
+
delay: number
|
|
29
|
+
interval: number
|
|
30
|
+
mouse: boolean
|
|
31
|
+
stop: boolean
|
|
32
|
+
prevent: boolean
|
|
33
|
+
capture: boolean
|
|
34
|
+
passive: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type QElement = HTMLElement & { __qTouchRepeatCleanup?: () => void }
|
|
38
|
+
|
|
39
|
+
function parseValue(value: unknown): { delay?: number; interval?: number } {
|
|
40
|
+
if (typeof value === "number") return { interval: value }
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
const [delay, interval] = value.split(":")
|
|
43
|
+
const out: { delay?: number; interval?: number } = {}
|
|
44
|
+
const d = Number(delay)
|
|
45
|
+
if (Number.isFinite(d)) out.delay = d
|
|
46
|
+
const i = Number(interval)
|
|
47
|
+
if (Number.isFinite(i)) out.interval = i
|
|
48
|
+
return out
|
|
49
|
+
}
|
|
50
|
+
if (value && typeof value === "object") {
|
|
51
|
+
const o = value as Record<string, unknown>
|
|
52
|
+
const out: { delay?: number; interval?: number } = {}
|
|
53
|
+
const d = Number(o.delay)
|
|
54
|
+
if (Number.isFinite(d)) out.delay = d
|
|
55
|
+
const i = Number(o.interval)
|
|
56
|
+
if (Number.isFinite(i)) out.interval = i
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
return {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const vTouchRepeat = {
|
|
63
|
+
mounted(el: HTMLElement, binding: { value?: unknown; modifiers?: Record<string, boolean> }) {
|
|
64
|
+
const handler = binding.value
|
|
65
|
+
if (typeof handler !== "function") {
|
|
66
|
+
console.warn("[v-touch-repeat] value doit être une fonction (handler)")
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
const m = binding.modifiers ?? {}
|
|
70
|
+
const parsed = parseValue(binding.value)
|
|
71
|
+
const options: RepeatOptions = {
|
|
72
|
+
handler: handler as TouchRepeatHandler,
|
|
73
|
+
delay: parsed.delay ?? DEFAULTS.delay,
|
|
74
|
+
interval: parsed.interval ?? DEFAULTS.interval,
|
|
75
|
+
mouse: !!m.mouse || !!m.mouseCapture,
|
|
76
|
+
stop: !!m.stop,
|
|
77
|
+
prevent: !!m.prevent,
|
|
78
|
+
capture: !!m.capture || !!m.mouseCapture,
|
|
79
|
+
passive: !!m.passive,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
el.style.touchAction = "none"
|
|
83
|
+
|
|
84
|
+
let active = false
|
|
85
|
+
let count = 0
|
|
86
|
+
let startTime = 0
|
|
87
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
88
|
+
let intervalTimer: ReturnType<typeof setInterval> | undefined
|
|
89
|
+
|
|
90
|
+
const stop = () => {
|
|
91
|
+
active = false
|
|
92
|
+
if (timer) clearTimeout(timer)
|
|
93
|
+
if (intervalTimer) clearInterval(intervalTimer)
|
|
94
|
+
timer = undefined
|
|
95
|
+
intervalTimer = undefined
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const tick = () => {
|
|
99
|
+
count++
|
|
100
|
+
options.handler({
|
|
101
|
+
evt: lastEvt ?? new Event("touch-repeat"),
|
|
102
|
+
count,
|
|
103
|
+
elapsed: Math.round(performance.now() - startTime),
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let lastEvt: Event | null = null
|
|
108
|
+
|
|
109
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
110
|
+
if (active) return
|
|
111
|
+
if (e.pointerType === "mouse" && !options.mouse) return
|
|
112
|
+
if (options.prevent && !options.passive) e.preventDefault()
|
|
113
|
+
if (options.stop) e.stopPropagation()
|
|
114
|
+
active = true
|
|
115
|
+
count = 0
|
|
116
|
+
startTime = performance.now()
|
|
117
|
+
lastEvt = e
|
|
118
|
+
timer = setTimeout(() => {
|
|
119
|
+
tick()
|
|
120
|
+
intervalTimer = setInterval(tick, options.interval)
|
|
121
|
+
}, options.delay)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const onPointerUp = (e: PointerEvent) => {
|
|
125
|
+
lastEvt = e
|
|
126
|
+
stop()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const capture = options.capture
|
|
130
|
+
el.addEventListener("pointerdown", onPointerDown, { capture, passive: options.passive })
|
|
131
|
+
el.addEventListener("pointerup", onPointerUp, { capture })
|
|
132
|
+
el.addEventListener("pointercancel", onPointerUp, { capture })
|
|
133
|
+
el.addEventListener("pointerleave", onPointerUp, { capture })
|
|
134
|
+
|
|
135
|
+
;(el as QElement).__qTouchRepeatCleanup = () => {
|
|
136
|
+
el.removeEventListener("pointerdown", onPointerDown, { capture } as any)
|
|
137
|
+
el.removeEventListener("pointerup", onPointerUp, { capture } as any)
|
|
138
|
+
el.removeEventListener("pointercancel", onPointerUp, { capture } as any)
|
|
139
|
+
el.removeEventListener("pointerleave", onPointerUp, { capture } as any)
|
|
140
|
+
stop()
|
|
141
|
+
el.style.touchAction = ""
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
unmounted(el: HTMLElement) {
|
|
145
|
+
;(el as QElement).__qTouchRepeatCleanup?.()
|
|
146
|
+
delete (el as QElement).__qTouchRepeatCleanup
|
|
147
|
+
},
|
|
148
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// v-touch-swipe — directive : détecte le « swipe » (glissement rapide puis
|
|
2
|
+
// relâchement), équivalent Quasar
|
|
3
|
+
// https://quasar.dev/vue-directives/touch-swipe
|
|
4
|
+
//
|
|
5
|
+
// Usage :
|
|
6
|
+
// <div v-touch-swipe.horizontal.mouse="onSwipe">…</div>
|
|
7
|
+
// <div v-touch-swipe="{ distance: 40, duration: 300 }">…</div>
|
|
8
|
+
// modifiers : horizontal | vertical | up | down | left | right (filtres de
|
|
9
|
+
// direction), mouse (inclut la souris), capture / mouseCapture, stop, prevent,
|
|
10
|
+
// passive
|
|
11
|
+
//
|
|
12
|
+
// Détails du handler : { evt, direction, distance: { x, y }, duration,
|
|
13
|
+
// speed: { x, y } }
|
|
14
|
+
|
|
15
|
+
export type SwipeDirection = "up" | "down" | "left" | "right"
|
|
16
|
+
|
|
17
|
+
export interface TouchSwipeDetails {
|
|
18
|
+
evt: PointerEvent
|
|
19
|
+
direction: SwipeDirection
|
|
20
|
+
/** Distance totale (px), par axe (signée) */
|
|
21
|
+
distance: { x: number; y: number }
|
|
22
|
+
duration: number
|
|
23
|
+
/** Vitesse finale (px/s), par axe */
|
|
24
|
+
speed: { x: number; y: number }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type TouchSwipeHandler = (details: TouchSwipeDetails) => void
|
|
28
|
+
|
|
29
|
+
const DEFAULTS = { distance: 50, duration: 300 }
|
|
30
|
+
|
|
31
|
+
interface SwipeOptions {
|
|
32
|
+
handler: TouchSwipeHandler
|
|
33
|
+
distance: number
|
|
34
|
+
duration: number
|
|
35
|
+
allowHorizontal: boolean
|
|
36
|
+
allowVertical: boolean
|
|
37
|
+
allowUp: boolean
|
|
38
|
+
allowDown: boolean
|
|
39
|
+
allowLeft: boolean
|
|
40
|
+
allowRight: boolean
|
|
41
|
+
mouse: boolean
|
|
42
|
+
stop: boolean
|
|
43
|
+
prevent: boolean
|
|
44
|
+
capture: boolean
|
|
45
|
+
passive: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type QElement = HTMLElement & { __qTouchSwipeCleanup?: () => void }
|
|
49
|
+
|
|
50
|
+
function parseValue(value: unknown): { distance?: number; duration?: number } {
|
|
51
|
+
if (value && typeof value === "object") {
|
|
52
|
+
const o = value as Record<string, unknown>
|
|
53
|
+
const out: { distance?: number; duration?: number } = {}
|
|
54
|
+
const d = Number(o.distance)
|
|
55
|
+
if (Number.isFinite(d)) out.distance = d
|
|
56
|
+
const t = Number(o.duration)
|
|
57
|
+
if (Number.isFinite(t)) out.duration = t
|
|
58
|
+
return out
|
|
59
|
+
}
|
|
60
|
+
return {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function axisAllowed(o: SwipeOptions, direction: SwipeDirection) {
|
|
64
|
+
if (o.allowHorizontal) return direction === "left" || direction === "right"
|
|
65
|
+
if (o.allowVertical) return direction === "up" || direction === "down"
|
|
66
|
+
switch (direction) {
|
|
67
|
+
case "up": return o.allowUp
|
|
68
|
+
case "down": return o.allowDown
|
|
69
|
+
case "left": return o.allowLeft
|
|
70
|
+
case "right": return o.allowRight
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const vTouchSwipe = {
|
|
75
|
+
mounted(el: HTMLElement, binding: { value?: unknown; modifiers?: Record<string, boolean> }) {
|
|
76
|
+
const handler = binding.value
|
|
77
|
+
if (typeof handler !== "function") {
|
|
78
|
+
console.warn("[v-touch-swipe] value doit être une fonction (handler)")
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const m = binding.modifiers ?? {}
|
|
82
|
+
const parsed = parseValue(binding.value)
|
|
83
|
+
const options: SwipeOptions = {
|
|
84
|
+
handler: handler as TouchSwipeHandler,
|
|
85
|
+
distance: parsed.distance ?? DEFAULTS.distance,
|
|
86
|
+
duration: parsed.duration ?? DEFAULTS.duration,
|
|
87
|
+
allowHorizontal: !!m.horizontal,
|
|
88
|
+
allowVertical: !!m.vertical,
|
|
89
|
+
allowUp: !!m.up,
|
|
90
|
+
allowDown: !!m.down,
|
|
91
|
+
allowLeft: !!m.left,
|
|
92
|
+
allowRight: !!m.right,
|
|
93
|
+
mouse: !!m.mouse || !!m.mouseCapture,
|
|
94
|
+
stop: !!m.stop,
|
|
95
|
+
prevent: !!m.prevent,
|
|
96
|
+
capture: !!m.capture || !!m.mouseCapture,
|
|
97
|
+
passive: !!m.passive,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (options.prevent) el.style.touchAction = "none"
|
|
101
|
+
else if (options.allowHorizontal && !options.allowVertical) el.style.touchAction = "pan-y"
|
|
102
|
+
else if (options.allowVertical && !options.allowHorizontal) el.style.touchAction = "pan-x"
|
|
103
|
+
else el.style.touchAction = "auto"
|
|
104
|
+
|
|
105
|
+
let startX = 0
|
|
106
|
+
let startY = 0
|
|
107
|
+
let startTime = 0
|
|
108
|
+
let lastX = 0
|
|
109
|
+
let lastY = 0
|
|
110
|
+
let lastTime = 0
|
|
111
|
+
let tracking = false
|
|
112
|
+
|
|
113
|
+
const guard = (e: Event) => {
|
|
114
|
+
if (options.stop) e.stopPropagation()
|
|
115
|
+
if (options.prevent) e.preventDefault()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const onPointerDown = (e: PointerEvent) => {
|
|
119
|
+
if (tracking) return
|
|
120
|
+
if (e.pointerType === "mouse" && !options.mouse) return
|
|
121
|
+
guard(e)
|
|
122
|
+
tracking = true
|
|
123
|
+
startX = lastX = e.clientX
|
|
124
|
+
startY = lastY = e.clientY
|
|
125
|
+
startTime = lastTime = performance.now()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const onPointerMove = (e: PointerEvent) => {
|
|
129
|
+
if (!tracking) return
|
|
130
|
+
lastX = e.clientX
|
|
131
|
+
lastY = e.clientY
|
|
132
|
+
lastTime = performance.now()
|
|
133
|
+
guard(e)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const onPointerUp = (e: PointerEvent) => {
|
|
137
|
+
if (!tracking) return
|
|
138
|
+
tracking = false
|
|
139
|
+
const now = performance.now()
|
|
140
|
+
const duration = now - startTime
|
|
141
|
+
const dx = e.clientX - startX
|
|
142
|
+
const dy = e.clientY - startY
|
|
143
|
+
const direction: SwipeDirection =
|
|
144
|
+
Math.abs(dx) >= Math.abs(dy) ? (dx > 0 ? "right" : "left") : (dy > 0 ? "down" : "up")
|
|
145
|
+
if (!axisAllowed(options, direction)) return
|
|
146
|
+
const along = direction === "left" || direction === "right" ? Math.abs(dx) : Math.abs(dy)
|
|
147
|
+
if (along < options.distance) return
|
|
148
|
+
if (duration > options.duration) return
|
|
149
|
+
const elapsed = Math.max(now - lastTime, 1)
|
|
150
|
+
const instSpeed = {
|
|
151
|
+
x: (lastX - startX) / elapsed * 1000,
|
|
152
|
+
y: (lastY - startY) / elapsed * 1000,
|
|
153
|
+
}
|
|
154
|
+
options.handler({
|
|
155
|
+
evt: e,
|
|
156
|
+
direction,
|
|
157
|
+
distance: { x: dx, y: dy },
|
|
158
|
+
duration,
|
|
159
|
+
speed: { x: Math.round(instSpeed.x), y: Math.round(instSpeed.y) },
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const onCancel = () => { tracking = false }
|
|
164
|
+
const capture = options.capture
|
|
165
|
+
el.addEventListener("pointerdown", onPointerDown, { capture, passive: options.passive })
|
|
166
|
+
el.addEventListener("pointermove", onPointerMove, { capture, passive: options.passive })
|
|
167
|
+
el.addEventListener("pointerup", onPointerUp, { capture })
|
|
168
|
+
el.addEventListener("pointercancel", onCancel, { capture })
|
|
169
|
+
|
|
170
|
+
;(el as QElement).__qTouchSwipeCleanup = () => {
|
|
171
|
+
el.removeEventListener("pointerdown", onPointerDown, { capture } as any)
|
|
172
|
+
el.removeEventListener("pointermove", onPointerMove, { capture } as any)
|
|
173
|
+
el.removeEventListener("pointerup", onPointerUp, { capture } as any)
|
|
174
|
+
el.removeEventListener("pointercancel", onCancel, { capture } as any)
|
|
175
|
+
el.style.touchAction = ""
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
unmounted(el: HTMLElement) {
|
|
179
|
+
;(el as QElement).__qTouchSwipeCleanup?.()
|
|
180
|
+
delete (el as QElement).__qTouchSwipeCleanup
|
|
181
|
+
},
|
|
182
|
+
}
|
package/lib/useComponentProps.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, inject, toValue } from "vue"
|
|
2
2
|
import type { ComputedRef, MaybeRefOrGetter } from "vue"
|
|
3
3
|
import { qConfigKey } from "./config"
|
|
4
|
+
import type { QAppLang } from "./config"
|
|
4
5
|
|
|
5
6
|
/** Échelle de rayon : none (carré) → lg (très arrondi) */
|
|
6
7
|
export type RadiusScale = "none" | "xs" | "sm" | "md" | "lg"
|
|
@@ -19,6 +20,12 @@ export const RADIUS_VALUES: Record<RadiusScale, string> = {
|
|
|
19
20
|
export const isRadiusScale = (v: unknown): v is RadiusScale =>
|
|
20
21
|
v === "none" || v === "xs" || v === "sm" || v === "md" || v === "lg"
|
|
21
22
|
|
|
23
|
+
/** Langue d'application fournie par QConfigProvider ("en" par défaut). */
|
|
24
|
+
export function useConfigLang(): ComputedRef<QAppLang> {
|
|
25
|
+
const config = inject(qConfigKey, null)
|
|
26
|
+
return computed(() => config?.lang.value ?? "en")
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
30
|
* Props par défaut d'un composant depuis `theme.componentProps` :
|
|
24
31
|
* `default` (appliqué à tous) puis `<Nom>` (spécifique, prioritaire).
|
package/module.ts
CHANGED
|
@@ -93,9 +93,19 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
93
93
|
getContents: () => `
|
|
94
94
|
import { defineNuxtPlugin } from "#imports"
|
|
95
95
|
import { vClose } from "@dnax/ui/runtime"
|
|
96
|
+
import { vTouchPan } from "@dnax/ui/runtime"
|
|
97
|
+
import { vTouchHold } from "@dnax/ui/runtime"
|
|
98
|
+
import { vTouchSwipe } from "@dnax/ui/runtime"
|
|
99
|
+
import { vTouchRepeat } from "@dnax/ui/runtime"
|
|
100
|
+
import { vIntersection } from "@dnax/ui/runtime"
|
|
96
101
|
|
|
97
102
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
98
103
|
nuxtApp.vueApp.directive("close", vClose)
|
|
104
|
+
nuxtApp.vueApp.directive("touch-pan", vTouchPan)
|
|
105
|
+
nuxtApp.vueApp.directive("touch-hold", vTouchHold)
|
|
106
|
+
nuxtApp.vueApp.directive("touch-swipe", vTouchSwipe)
|
|
107
|
+
nuxtApp.vueApp.directive("touch-repeat", vTouchRepeat)
|
|
108
|
+
nuxtApp.vueApp.directive("intersection", vIntersection)
|
|
99
109
|
})
|
|
100
110
|
`,
|
|
101
111
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dnax/ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"description": "UI design System",
|
|
6
6
|
"type": "module",
|
|
@@ -22,15 +22,41 @@
|
|
|
22
22
|
"vue": "^3.5.41"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"@editorjs/checklist": "^1.6.0",
|
|
26
|
+
"@editorjs/code": "^2.9.4",
|
|
27
|
+
"@editorjs/delimiter": "^1.4.2",
|
|
28
|
+
"@editorjs/editorjs": "^2.31.6",
|
|
29
|
+
"@editorjs/header": "^2.8.9",
|
|
30
|
+
"@editorjs/list": "^2.0.9",
|
|
31
|
+
"@editorjs/paragraph": "^2.11.7",
|
|
32
|
+
"@editorjs/quote": "^2.7.6",
|
|
25
33
|
"@iconify/vue": "^5.0.1",
|
|
26
34
|
"@nuxt/kit": "^4.5.2",
|
|
35
|
+
"@tiptap/extension-bubble-menu": "^3.31.3",
|
|
36
|
+
"@tiptap/extension-drag-handle": "^3.31.3",
|
|
37
|
+
"@tiptap/extension-drag-handle-vue-3": "^3.31.3",
|
|
38
|
+
"@tiptap/extension-floating-menu": "^3.31.3",
|
|
39
|
+
"@tiptap/extension-image": "^3.31.3",
|
|
40
|
+
"@tiptap/extension-mention": "^3.31.3",
|
|
41
|
+
"@tiptap/extension-table": "^3.31.3",
|
|
42
|
+
"@tiptap/extension-table-of-contents": "^3.31.3",
|
|
43
|
+
"@tiptap/extension-task-item": "^3.31.3",
|
|
44
|
+
"@tiptap/extension-task-list": "^3.31.3",
|
|
45
|
+
"@tiptap/extension-text-align": "^3.31.3",
|
|
46
|
+
"@tiptap/extension-text-style": "^3.31.3",
|
|
47
|
+
"@tiptap/pm": "^3.31.3",
|
|
48
|
+
"@tiptap/starter-kit": "^3.31.3",
|
|
49
|
+
"@tiptap/suggestion": "^3.31.3",
|
|
50
|
+
"@tiptap/vue-3": "^3.31.3",
|
|
27
51
|
"@videojs/html": "^10.0.0-beta.32",
|
|
28
52
|
"@vueuse/core": "^14.4.0",
|
|
53
|
+
"arktype": "^2.2.3",
|
|
29
54
|
"class-variance-authority": "^0.7.1",
|
|
30
55
|
"clsx": "^2.1.1",
|
|
31
56
|
"embla-carousel": "^8.5.0",
|
|
32
57
|
"embla-carousel-vue": "^8.5.0",
|
|
33
58
|
"fuse.js": "^7.1.0",
|
|
59
|
+
"moment": "^2.30.1",
|
|
34
60
|
"shiki": "^3.0.0",
|
|
35
61
|
"swiper": "^14.2.0",
|
|
36
62
|
"tailwind-merge": "^3.6.0",
|
|
@@ -42,6 +42,8 @@ export { screen } from "./lib/screen"
|
|
|
42
42
|
export type { QScreen } from "./lib/screen"
|
|
43
43
|
export { loading } from "./lib/loading"
|
|
44
44
|
export type { LoadingOptions, QLoadingPlugin } from "./lib/loading"
|
|
45
|
+
export { localStorage, sessionStorage } from "./lib/storage"
|
|
46
|
+
export type { QWebStorage } from "./lib/storage"
|
|
45
47
|
export {
|
|
46
48
|
registerOverlay,
|
|
47
49
|
unregisterOverlay,
|
|
@@ -52,6 +54,16 @@ export {
|
|
|
52
54
|
} from "./lib/overlayBack"
|
|
53
55
|
export type { OverlayHandle } from "./lib/overlayBack"
|
|
54
56
|
export { markOverlayClose, closeParentOverlay, vClose } from "./lib/closeOverlay"
|
|
57
|
+
export { vTouchPan } from "./lib/touchPan"
|
|
58
|
+
export type { PanDetails, TouchPanHandler, PanDirection } from "./lib/touchPan"
|
|
59
|
+
export { vTouchHold } from "./lib/touchHold"
|
|
60
|
+
export type { TouchHoldDetails, TouchHoldHandler } from "./lib/touchHold"
|
|
61
|
+
export { vTouchSwipe } from "./lib/touchSwipe"
|
|
62
|
+
export type { TouchSwipeDetails, TouchSwipeHandler, SwipeDirection } from "./lib/touchSwipe"
|
|
63
|
+
export { vTouchRepeat } from "./lib/touchRepeat"
|
|
64
|
+
export type { TouchRepeatDetails, TouchRepeatHandler } from "./lib/touchRepeat"
|
|
65
|
+
export { vIntersection } from "./lib/intersection"
|
|
66
|
+
export type { IntersectionValue } from "./lib/intersection"
|
|
55
67
|
`
|
|
56
68
|
|
|
57
69
|
await Bun.write(indexFile, `${exports}\n${manualExports}`)
|